10 min read

From monolithic to Kubernetes

The aim of this mini guide is to focus on shipping your ideas and making your users happy. Kubernates is the key.
From monolithic to Kubernetes

Intro

The aim of this mini guide is to introduce the concept of container in order to build microservices managed through kubernetes.

The highest aim is to focus on shipping your ideas and making your users happy.

Goal

Back to the past

Simple Monolithic Application
  • many hours to deploy;
  • single source code;
  • single point of failure.

I remember a project in which i was a developer 3 years ago. In a single source code we had PHP+AltoRouter in order to expose HTTP routes and Smarty Template Engine to pass PHP variables to HTML. Javascript was a hell, but this is an other story.

So if i wanted to change a front-end label or a critical logic in PHP i had to make changes in the same source code.

Microservices

Simple Microservices Oriented Application

Interviewer: "How do you coordinate all those pieces that are out of there?"

Adrian Cockcroft (ex Cloud Architect on Netflix): "You don't coordinate, right?" [2]

Microservices are:

  • Modular;
  • Easy to deploy;
  • Scale Independently.
monolithic vs microservices code base 

12 factor

In order to start building microservices we must read at least the 12factor; a bible where we can find 12 fondamental principles to apply while developing software-as-a-service apps.

12factor is Written by the founder and CTO of Heroku, Adam Wiggins (https://www.linkedin.com/in/adam-wiggins-a7623845/).

12 principles

I. Codebase: One codebase tracked in revision control, many deploys

II. Dependencies: Explicitly declare and isolate dependencies

III. Config: Store config in the environment

IV. Backing services: Treat backing services as attached resources

V. Build, release, run: Strictly separate build and run stages

VI. Processes: Execute the app as one or more stateless processes

VII. Port binding: Export services via port binding

VIII. Concurrency: Scale out via the process model

IX. Disposability: Maximize robustness with fast startup and graceful shutdown

X. Dev/prod parity: Keep development, staging, and production as similar as possible

XI. Logs. Treat logs as event streams

XII. Admin processes: Run admin/management tasks as one-off processes

What is a container?

In order to garantee portability and heterogeneous technology stacks we need containerized microservices. We can imagine a container like an app on a phone full of others etherogeneous apps.

a container is like an app on a phone

Container is a technology that makes it easy to run and distribute applications across different operating environments. They are similar to Virtual Machine but much lighter weight.

Container ensures *:

  • Indipendent  packages;
  • Namespace isolation.

Most used container technologies:

Containers with Docker

Problem: easly run different versions of nginx on a linux server.

Without container: In a linux virtual machine we usually have a unique nginx service and in order to start/stop/restart it we use

service nginx <command>

In order to change nginx configs we need to edit "/etc/nginx/nginx.conf" file but it's not a safe editing because it has a complex structure.

With docker containers:-)

version: '3'
services:
    web1:
        image: nginx:1.17.2
        volumes:
        - ./mysite.template:/etc/nginx/conf.d/mysite.template
        ports:
        - "8080:80"
        environment:
        - NGINX_HOST=foobar.com
        - NGINX_PORT=80
        command: /bin/bash -c "envsubst < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'"
    web2:
        image: nginx:1.9.8
        volumes:
        - ./mysite2.template:/etc/nginx/conf.d/mysite2.template
        ports:
        - "8081:80"
        environment:
        - NGINX_HOST=foobar2.com
        - NGINX_PORT=80
        command: /bin/bash -c "envsubst < /etc/nginx/conf.d/mysite2.template > /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'"

In a single file called docker-compose.yml (https://docs.docker.com/compose/gettingstarted/) we have achieved both goals:

  • running different version of nginx;
  • easy configure through environments variable.

Packaging containers is like 5% of the problem. The real problems are:

  • app configuration;
  • service discovery;
  • managing updates;
  • monitoring.

While we can build all those things on top of Docker,  it's better delegating all these complexity to a platform.

This is where Kubernetes comes in.

Previously we focused on deploying in a virtual machine which lock you into a limited workflows. Kubernetes allows us to abstract the individual machines and treat the entire cluster like a single logical machine.

Kubernetes!

Assumption

First of all, you can't build microserivces with a waterfall organization.

Where/When is born?

Kubernetes was founded by Joe Beda, Brendan Burns and Craig McLuckie.

First announced by Google in mid-2014 influenced by Google's Borg system. Borg is a Cluster OS Google uses for managing internal workloads.

Kubernetes is a production-ready, open source platform designed with Google's accumulated experience in container orchestration, combined with best-of-breed ideas from the community. Kubernetes coordinates a highly available cluster of computers that are connected to work as a single unit.

Kubernetes Cluster of 3 nodes + 1 master

Kubernetes node

Every Kubernetes Node runs at least:

  • Kubelet, a process responsible for communication between the Kubernetes Master and the Node; it manages the Pods and the containers running on a machine.
  • A container runtime (like Docker, rkt) responsible for pulling the container image from a registry, unpacking the container, and running the application.

But what is a Pod?

Let's start talking from bottom of Kubernetes. The smallest unit is the Pod. A pod is a collection of one or many containers and volumes.

  • Every containers in a Pod share the same namespace;
  • A Pod has an unique ip address.

Containers should only be scheduled together in a single Pod if they are tightly coupled and need to share resources such as disk.

Example of a Pod

I really recommend to read this paper [1] where a founder of Kubernetes talks about distribuited system patterns and multi-container application patterns.

Let's go inside a pod yml.

cat pods/healthy-monolith.yaml
apiVersion: v1
kind: Pod
metadata:
  name: "secure-monolith"
  labels:
    app: monolith
spec:
  containers:
    - name: nginx
      image: "nginx:1.9.14"
      lifecycle:
        preStop:
          exec:
            command: ["/usr/sbin/nginx","-s","quit"]
      volumeMounts:
        - name: "nginx-proxy-conf"
          mountPath: "/etc/nginx/conf.d"
        - name: "tls-certs"
          mountPath: "/etc/tls"
    - name: monolith
      image: "udacity/example-monolith:1.0.0"
      ports:
        - name: http
          containerPort: 80
        - name: health
          containerPort: 81
      resources:
        limits:
          cpu: 0.2
          memory: "10Mi"
      livenessProbe:
        httpGet:
          path: /healthz
          port: 81
          scheme: HTTP
        initialDelaySeconds: 5
        periodSeconds: 15
        timeoutSeconds: 5
      readinessProbe:
        httpGet:
          path: /readiness
          port: 81
          scheme: HTTP
        initialDelaySeconds: 5
        timeoutSeconds: 1
  volumes:
    - name: "tls-certs"
      secret:
        secretName: "tls-certs"
    - name: "nginx-proxy-conf"
      configMap:
        name: "nginx-proxy-conf"
        items:
          - key: "proxy.conf"
            path: "proxy.conf"

Create the pod

kubectl create -f pods/health-monolith.yaml

Examine pods

kubectl get pods

Monitoring and health checks in Kubernetes [6]

Liveness probes

The kubelet uses liveness probes to know when to restart a Container. For example, liveness probes could catch a deadlock, where an application is running, but unable to make progress. Restarting a Container in such a state can help to make the application more available despite bugs.

Liveness config example (this is a section inside the pod yml):

  livenessProbe:
    httpGet:
      path: /healthz
      port: 81
      scheme: HTTP
    initialDelaySeconds: 5
    periodSeconds: 15
    timeoutSeconds: 5

Imagine two people, the name of the first is Kubelet and the second one is Pod1. Periodically Kubelet tries to call Pod1: "Hey Pod1, are you alive?".

If Pod1 doesn't answer, Kubelet reanimate him.

The calls are obviously in HTTP protocol and the reanimation is a restart of the Pod. This is the liveness check of kubelet.

Readiness probes

The kubelet uses readiness probes to know when a Container is ready to start accepting traffic. A Pod is considered ready when all of its Containers are ready. One use of this signal is to control which Pods are used as backends for Services. When a Pod is not ready, it is removed from Service load balancers.

Readiness config example (this is a section inside the pod yml):

  readinessProbe:
    httpGet:
      path: /readiness
      port: 81
      scheme: HTTP
    initialDelaySeconds: 5
    timeoutSeconds: 1

App config and security overview in Kubernetes

Kubernetes provides two ways to inject configs:

  • configMaps: used for not sensitive data (es. nginx.conf)
  • secrets: used for sensitive data (es.tls certificates)

Let's try to create a configMap for proxy.conf file of nginx and check it.

Now, same things but for secrets.

Once configMaps/Secrets are creates we need to attach it, we do it through the "volumeMounts" section in pod yml:

  volumeMounts:
    - name: "nginx-proxy-conf"
      mountPath: "/etc/nginx/conf.d"
    - name: "tls-certs"
      mountPath: "/etc/tls"

We need also, always in pod yml, to declare volumes:

  volumes:
    - name: "tls-certs"
      secret:
        secretName: "tls-certs"
    - name: "nginx-proxy-conf"
      configMap:
        name: "nginx-proxy-conf"
        items:
          - key: "proxy.conf"
            path: "proxy.conf"

How to expose pods

https://medium.com/google-cloud/kubernetes-nodeport-vs-loadbalancer-vs-ingress-when-should-i-use-what-922f010849e0

Deployment

Deployment yml has the same template as pod yml.

Pods are suitable only for dev purpose, deployment, instead, also for production.

The following are typical use cases for Deployments:

Let's go scaling!

Scaling in Kubernetes

These are the 2 scaling modes available:

  • Horizontal Pod Autoscaler based on the following rule
desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )]

Algorithm detail ->  https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#algorithm-details

  • Manual using CLI

Multiple instances benefits

PRO: Once you have multiple instances of an Application running, you would be able to do Rolling updates without downtime.

Rolling updates allow Deployments' update to take place with zero downtime by incrementally updating Pods instances with new ones. By default, the maximum number of Pods that can be unavailable during the update and the maximum number of new Pods that can be created, is one.

Delivery without downtime

Namespaces

Namescapaces allow us to share projects/environments inside the same cluster.

An interesting feature is limit cpu usage of a namespace https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/cpu-constraint-namespace/.

Es. As a cluster administrator, you might want to impose restrictions on the CPU resources that Pods can use. For example:

  • Each Node in a cluster has 2 CPU. You do not want to accept any Pod that requests more than 2 CPU, because no Node in the cluster can support the request;
  • A cluster is shared by your production and development departments. You want to allow production workloads to consume up to 3 CPU, but you want development workloads to be limited to 1 CPU. You create separate namespaces for production and development, and you apply CPU constraints to each namespace.

Scaffolding a Kubernetes cluster

Kubernetes addons

https://kubernetes.io/docs/concepts/cluster-administration/addons/#service-discovery

General Configuration Tips

  • When defining configurations, specify the latest stable API version;
  • Configuration files should be stored in version control before being pushed to the cluster. This allows you to quickly roll back a configuration change if necessary. It also aids cluster re-creation and restoration;
  • Write your configuration files using YAML rather than JSON. Though these formats can be used interchangeably in almost all scenarios, YAML tends to be more user-friendly;
  • Group related objects into a single file whenever it makes sense. One file is often easier to manage than several. See the guestbook-all-in-one.yaml file as an example of this syntax;
  • Note also that many kubectl commands can be called on a directory. For example, you can call kubectl apply on a directory of config files;
  • Don’t specify default values unnecessarily: simple, minimal configuration will make errors less likely;
  • Put object descriptions in annotations, to allow better introspection.

References

1) https://static.googleusercontent.com/media/research.google.com/it//pubs/archive/45406.pdf

2) https://classroom.udacity.com/courses/ud615

3) https://docs.docker.com

4) https://github.com/udacity/ud615

5) https://kubernetes.io/docs/home/

6) https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/


Notes:

*ensure!==it always do it. To ensure it you need to correctly use docker.

Tweets by YBacciarini