Kubernetes: Skaffold

Updating a Docker images used by a Kubernetes deployment can be painful since there are several steps that have to be accomplished:

  • the deployment must use the ‘latest’ tag in the pod spec section
  • update the code
  • build the image
  • push the image to Docker Hub or have it available inside Minikube cluster
  • run the command: $ kubectl rollout restart deployment [deployment_name]

The steps above are important since they have to be followed in a production environment.

In a development environment there is an easier approach: when we are developing code actively inside of a Kubernetes cluster we can use a tool called Skaffold.

Skaffold is a command line tool for the dev environment:

  • automates many tasks in a Kubernetes dev environment
  • makes it really easy to update code in a running pod
  • makes it really easy to create/delete all objects tied to a project at once
  • homepage https://skaffold.dev/

After installing Skaffold (https://skaffold.dev/docs/install/) check if it exists on your system:
$ scaffold

NOTE: Skaffold is a tool that runs outside of our cluster.


Configuring Skaffold

  • configuration is done using an YAML file
  • the config file is going to tell Skaffold how to manage all the different sub-projects inside of our cluster

Use as reference the following project: https://github.com/luckpp/microservices-demo

Steps:

  • in the root folder of your project creae a file skaffold.yaml
  • in the config ‘deploy’ section this tells Skaffold that there is a collection of config files for Kubernetes inside infra/k8s/ folder that should be watched
    • anytime we make a change to a config file, Skaffold is going to automatically reapply that config file to our Kubernetes cluster
    • the config files will be applied also when we start Skaffold
    • all the objects associated with those config files will be deleted when we stop Skaffold
      => this is helpful when switching between different projects -> we will end-up with a clean cluster
  • in the config ‘build’ section
    • in ‘local’ sub-section make sure that whenever an image is rebuild, it is not pushed to Docker Hub
    • in ‘artifacts’ sub-section tell Skaffold about the sections in our project it needs to maintain
      • in the example below we actually tell that there is going to be some pod that is running code out of the ‘client’ directory
      • whenever something changes in the ‘client’ directory Skaffold will try to take those changes and update our pod; there are two ways in which Skaffold is going to update our pod:
        • if we are going to make a change to a *.js file Skaffold is going to take that change and throw it into our pod
        • if we make a change inside the directory and that change is not a *.js file, Skaffold will try to rebuild that image
apiVersion: skaffold/v2alpha3
kind: Config
deploy:
  kubectl:
    manifests:
      - ./infra/k8s/*
build:
  local:
    push: false
  artifacts:
    - image: luckpp/client
      context: client
      docker:
        dockerfile: Dockerfile
      sync:
        manual:
          - src: "src/**/*.js"
            dest: .
    # all other sub-projects should follow

Make sure to make docker command point to your shell to minikube’s docker-daemon, run:
$ minikube docker-env
-> execute the command suggested by the output of this command

In order to start Skaffold up run the following command form the folder where skaffold.yaml is located:
$ skaffold dev

Make some changes to one or more files inside your project and notice the Skaffold output.

To close Skaffold hit CTRL+c:

  • Skaffold closes
  • all objects associated with the config files handled by Skaffold will be deleted

Critical info to understand:

  • with the configuration we saw above, whenever Skaffold detects a change inside a *.js file it will copy it into the appropriate pod
  • inside the pod we host containers that have the entry command: ‘npm start’
  • if we look into the package.json files, we see that ‘npm start’ actually starts ‘nodemon’
  • in the end ‘nodemon’ is the tool that detects a change to a *.js file from the underlying project and restarts the Node.js service code inside the container

As a conclusion we can say we have 2 levels of change detection:

  • outside Kubernetes we have Skafflod detection change
  • inside the container we have detection change with ‘nodemon’

NOTE: Using change detection or file change detection inside Docker containers traditionally has requested great attention to details:

  • there are file watchers like nodemon or create-react-app that do not always detect changes inside a Docker container
  • I will try to give a couple of more notes on the issue above in my next blog posts

Kubernetes: nginx-ingress routing notes

Nginx can differentiate only on the route not on the HTTP verb itself.

Nginx does not support wildcards in URLs like Node.js does:

  • for example Nginx does not support routes like: /posts/:id/comments
  • for those cases when we use wildcard annotations we have to provide a Regex inside the configuration route
  • when Regex is used inside a route of an ingress config file, one should add an additional annotation nginx.ingress.kubernetes.io/use-regex: 'true'

The route config available on: https://github.com/luckpp/microservices-demo/blob/master/infra/k8s/ingress-srv.yaml

apiVersion: networking.k8s.io/v1beta1 # extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-srv
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
spec:
  rules:
    - host: posts.com
      http:
        paths:
          - path: /posts/create
            backend:
              serviceName: posts-srv
              servicePort: 4000
          - path: /posts
            backend:
              serviceName: query-srv
              servicePort: 4002
          - path: /posts/?(.*)/comments
            backend:
              serviceName: comments-srv
              servicePort: 4001
          - path: /?(.*) # for a React app that uses React Router to handle routing inside the React app you have to use the wild-card Regex istead of simple '/'
            backend:
              serviceName: client-srv
              servicePort: 3000