Wednesday, March 17, 2021

Protect your microservice by limiting which IPs can access it

If you are using ingress-nginx controller to route your web services, it is accessible from the whole internet.  This is definitely not what you want.  Among with other security protection, you might want to consider to set a white list for who can access your web services. 

The solution is pretty simple.  Just use the 'loadBalancerSourceRanges' to set these IPs for your white list.

Basically, your ingress controller service will be defined as something like this:

apiVersion: v1 kind: Service metadata: name: ingress-nginx spec: type: LoadBalancer ports: - protocol: TCP port: 80 targetPort: 80 loadBalancerSourceRanges: - x.x.x.x/32

To make it happen, add following to your values.yaml file (replace x.x.x.x with your white list IPs separated by comma) and apply such change to your ingress-nginx controller:

controller:
service:
loadBalancerSourceRanges: [x.x.x.x/32]

Sunday, March 7, 2021

Kubernetes Ingress: which implementation is better?

Three general approaches to exposing your web services running in Kubernetes:

  • NodePort: port 30000-32767 only; not good option for production.
  • LoadBalancer: simplest and safest; depends on cloud provider’s load balancer; each service needs one load balancer (expensive if you have multiple external services).
  • Ingress: an Ingress is an API object that manages external access to the services in a cluster (typically HTTP/S). still need one load balancer from cloud provider.
Given the cost saving from load balancer and the features in Ingress, it makes sense to explore the way for using Ingress.  Except the cost saving, one of the other benefits is to leverage cache ability from Ingress controller.

Nginx is popularly used in Ingress solution.  There are at least two nginx based ingress controller implementations:

  1. ingress-nginx: https://kubernetes.github.io/ingress-nginx/; repo in https://github.com/kubernetes/ingress-nginx/tree/master/charts/ingress-nginx
  2. NGINX kubernetes-ingress: https://www.nginx.com/products/nginx-ingress-controller/repo in https://github.com/nginxinc/kubernetes-ingress
The first one is maintained by kubernetes open source community; the second one is also open source, but maintained by nginx.com and offers a commercial version based on "Nginx Plus".

The ingress controller itself is a Kubernetes service.  It is responsible for fulfilling the ingress by generating/updating nginx.conf file based on all ingresses. 

Except above two, you can also find ingress solution from Google cloud load balancer, Istio, Envoy, Contour, etc.

We will mainly compare ingress-nginx and NGINX kubernetes-ingress in this blog. 

Ingress Controller Deployment and Ingress 


First of all, use the helm to install Ingress Controller:

  • ingress-nginx: https://github.com/kubernetes/ingress-nginx/tree/master/charts/ingress-nginx
  • NGINX Kubernetes-ingress: https://github.com/nginxinc/kubernetes-ingress/tree/master/deployments/helm-chart
Need to prepare a yaml file before you run 'helm install'.  Such yaml file should contains anything you want to set for customized values.

In my case, I put these cache related values in to my yaml file:

controller:
  config:
    http-snippet: |
      proxy_cache_path /tmp/mycachefile levels=1:2 keys_zone=mycache:32m use_temp_path=off max_size=10g inactive=12h;
      proxy_cache_key $scheme$proxy_host$request_uri;
      proxy_cache_lock on;
                      access_log off;  <<-- need this only you want to disable access log


Pass this file to 'helm install' through '--values' argument.  This will put above snippet to http block of nginx.conf.

** Use this to add ingress-nginx repo: 

    helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx


After you install the controller, you will need to create at least one Ingress resource to do the routing. Ingress resource can be created through a yaml file.  A sample Ingress yaml file is:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-for-my-k8s
  namespace: my-namespace
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/proxy-buffering: "on"
    nginx.ingress.kubernetes.io/server-snippet: |
      proxy_cache mycache;
      proxy_cache_valid 200 10m;
      proxy_cache_use_stale error timeout updating http_404 http_500 http_502 http_503 http_504;
      proxy_cache_bypass $http_x_purge;
      add_header X-Cache-Status $upstream_cache_status;
spec:
  rules:
  - host: my.hostname.com
    http:
      paths:
      - path: /mypath
        backend:
          serviceName: my-service

          servicePort: 80



In above sample, we are routing the traffic to my-service on port 80 from external load balancer in front of Ingress Controller.  We also add a server snippet in this case. 

Once your ingress resource is applied, ingress controller will take it automatically and update nginx.conf file accordingly.  

Overall, the relationship between Ingress Controller, Ingress and Load Balancer is:



Comparison between ingress-nginx and NGINX kubernetes-ingress


Now, Let us compare these two implementations.

Both have similar functionalities so there is no big mistake if you choose one or another one. But after using both, my recommendation is ingress-nginx.  The reasons are based on following 4 criteria:
  • Easy for troubleshooting
  • Support friendly
  • Contributor's activities
  • Lua enabled
I will go through an example to explain why ingress-nginx is better.  My recent work needs to support cache for some of our microservices.  Of course, I will not reinvent the wheel.  I will leverage the cache functionality through nginx so I tried to implement it through both ingress-nginx and NGINX kubernetes-ingress.  Following are what I observed.


Troubleshooting:

ingress-nginx is way better.  For example, if I create a cache zone by putting http-snippet to Ingress controller and I did it incorrectly, the cache zone is not created.  Now when trying to add cache to a host or a path, ingress-nginx will immediately show error:

Error: exit status 1

2021/03/04 01:18:39 [emerg] 186#186: "proxy_cache" zone "mycache" is unknown in /tmp/nginx-cfg216240277:863

nginx: [emerg] "proxy_cache" zone "mycache" is unknown in /tmp/nginx-cfg216240277:863

nginx: configuration file /tmp/nginx-cfg216240277 test failed

If this happens on NGINX kubernetes-ingress, it just silently drops the corresponding server-snippet without any error.  Then you will have to open nginx.conf file and realize the snippet is not there.  But you are still clueless for why it is not there.

Support friendly:

Some feature is really easy to add.  But even so many people voted on it, NGINX kubernetes-ingress's contributors just decided not to do anything.  Here is an example:

https://github.com/nginxinc/kubernetes-ingress/issues/209

The discussion was started in 2017. So many people expressed this is something we should have.  But they just decided to do nothing with all kinds of weird explanations.  I believe the request is reasonable and I actually also need it. ingress-nginx has had it already. 

Contributor's activities:

I was wondering why above issue was constantly refused by NGINX kubernetes-ingress contributors.  So, I did some searches on contributors activities for both projects.  And I found ingress-nginx has much higher activities than NGINX kubernetes-ingress in last 5 years.  Following graphs are from both project's official website (the first graph is scaled from 0-15 when the second graph is scaled from 0-40):


Contributions of NGINX-kubernetes-ingress


Contributions of ingress-nginx


Lua enabled, cache support, etc.:

By inspecting to the nginx.conf file created by ingress controller, I realized only ingress-nginx enabled it. Not sure why NGINX kubernetes-ingress does not enable it or if there is way to enable it. 

Also, the way to put http-snippet to nginx.conf works only for ingress-nginx. I used the same way for NGINX kubernetes-ingress, it never works for me.  

In summary, ingress-nginx works for me much better. This is just my personal opinion based my experience working on both.  

(Updated on December 2, 2021):

I am confirmed that nginx-ingress doesn’t support Lua and openresty. 

One of another difference between these two is:

When ingress-nginx put everything into /etc/nginx/nginx.conf, nginx-ingress creates a separate file for each ingress under /etc/nginx/conf.d/  

It’s hard to say which way is better  it’s just different  




Monday, November 23, 2020

Gitlab

Predefined variables:

https://docs.gitlab.com/ee/ci/variables/predefined_variables.html


Variable precedence:



Setup a runner from Gitlab UI:



Register a docker runner:

sudo gitlab-runner register -n \ --url https://gitlab.com/ \ --registration-token REGISTRATION_TOKEN \ --executor docker \ --description "My Docker Runner" \ --docker-image "docker:19.03.12" \ --docker-privileged \ --docker-volumes "/certs/client"


sudo gitlab-runner register \ --non-interactive \ --url "https://gitlab.com/" \ --registration-token "PROJECT_REGISTRATION_TOKEN" \ --executor "docker" \ --docker-image alpine:latest \ --description "docker-runner" \ --tag-list "docker,aws" \ --run-untagged="true" \ --locked="false" \ --access-level="not_protected"

 

More info: https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-socket-binding


Use the runner to push microservice images (using tag to choose runner):

build image:
  image: docker:stable
  stage: build
  variables:
    CONTAINER_BUILD_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
  tags:
    - docker
  before_script:
    - docker info
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $CONTAINER_BUILD_IMAGE .
    - docker push $CONTAINER_BUILD_IMAGE
  when: manual
  # only:
  #   - schedules

More info: https://gitlab-core.us.gitlabdemo.cloud/ci-cd-training/gitlab-ci-cd-hands-on/-/snippets/433

CI_REGISTRY_IMAGE=gitlab-core.us.gitlabdemo.cloud:5050/iuecytjr/johnzsproject

CI_COMMIT_SHORT_SHA=25bbfbbb

CI_REGISTRY_USER=gitlab-ci-token

CI_REGISTRY=gitlab-core.us.gitlabdemo.cloud:5050


Gitlab-runner debug:

gitlab-runner --debug run




Sunday, July 26, 2020

Rolling update

There are 3 major update methods for the services running inside k8s:


  1. Rolling update
  2. Blue/Green
  3. Canary


I will not repeat them since there is a very good article there:

https://searchitoperations.techtarget.com/answer/When-to-use-canary-vs-blue-green-vs-rolling-deployment

For rolling update, Google has an introduction here:

https://cloud.google.com/kubernetes-engine/docs/how-to/updating-apps

There are multiple ways to use rolling update:


  • 'kubectl set image'
  • Use new image (through terraform either from command line or Gitlab)


Some considerations for a successful rolling update:


  • Backward compatible
  • Testing
  • Rollback strategy
  • Timing



Tuesday, June 16, 2020

How to preserve the format for code piece in Goolge Blogger

Add your code to the line in following section and paste them back in HTML view:

 <pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; 
                color: #000000; background-color: #eee;
                font-size: 12px; border: 1px dashed #999999;
                line-height: 14px; padding: 5px; 
                overflow: auto; width: 100%">
       <code style="color:#000000;word-wrap:normal;">

            <<<<<<<YOUR CODE HERE>>>>>>>

       </code>
</pre>

Build a fat jar for Spring boot to include classes under 'test' in gradle

       

plugins {
     id 'org.springframework.boot' version '2.2.2.RELEASE'
     id 'io.spring.dependency-management' version '1.0.8.RELEASE'
     id 'java'
     id "com.palantir.docker" version "0.22.1"
     id "com.gorylenko.gradle-git-properties" version "1.5.1"   // auto generate git.properties and visible at /actuator/info
     id 'com.github.johnrengelman.shadow' version '6.0.0'
}


shadowJar {
    mergeServiceFiles()

    manifest {
        attributes 'Main-Class': 'com.company.product.cloud.packageName.TestClassName'
    }
    from sourceSets.test.output.classesDirs
}



Build:

./gradlew :project-name:shadowJar



Run:

java -cp ./project-name/build/libs/* com.company.product.cloud.packageName.TestClassName

Thursday, May 21, 2020

Ways to change property values for Java Spring application in EKS (Kubernetes in AWS)


Issue to solve

When deploying micro services to EKS, we might need to pass different values to the same micro service for different environments.  For example, we might need to use different MSK instances or different encryption keys.  The traditional way (i.e. using application.properties file or yaml file) will not work since the values in these files will be set before docker image is built. Once micro service is deployed to cloud as container, you will have to login into the container to change values in these files and may have to restart your service to take the new value. 

When micro service is deployed through terraform code, we have to pass these values through terraform. 


Solution for passing values from terraform to container

One way to pass property value from terraform to micro service is to use following 'args' in kubernetes_deployment (spec/template/spec/container/args):

 args = [
  "--kafka.bootstrapServers =${var.msk_bootstrap_brokers_tls}",
  "--kafka.zooKeeper_hosts=${var.msk_zookeeper_hosts}",
  "--kafka.topic=mytopic",
  "--kafka.use_ssl=true",
  "--aws_encyption_key=${var.aws_encyption_key}"
]

Another way to do so is to use 'env' in the kubernetes_deployment too (spec/template/spec/container/env).

env {
  name = "kafka.hosts"
  value = ${var.msk_bootstrap_brokers_tls}"
}

env {
  name = "POD_IP"
  value_from  {
    field_ref  {
      field_path = "status.podIP"
    }
  }
}

This will pass these property values through container ENTRYPOINT or environment variables.


Solution for Java Spring code to get and use these values

One way to do so is to use @Value annotation.

@Configuration.  // or @Component
@Slf4j
public class myConfig {

    public myConfig(@Value("${NODE_IP:localhost}") String statsdNodeIP, // default value "localhost"
                    @Value("${kafka.bootstrapServers}") String kafkaServers) {
        // use these values here such as set value to class variable _xxxx;
    }
    @Bean
    public XXXX getXXXX() {
        return  _xxxx;
    }
}


Order of property value overwrite
  1. Command line arguments.
  2. Java System properties (System.getProperties()).
  3. OS environment variables.
  4. @PropertySource annotations on your @Configuration classes.
  5. Application properties outside of your packaged jar (application.properties including YAML and profile variants).
  6. Application properties packaged inside your jar (application.properties including YAML and profile variants).
  7. Default properties (specified using SpringApplication.setDefaultProperties).

Reference

https://docs.spring.io/spring-boot/docs/1.0.1.RELEASE/reference/html/boot-features-external-config.html