Understanding kubekutr config

Let's create a folder listmonk-infra to store all our manifest configuration files. We will use this as a base folder for the rest of the guide unless specified otherwise.

mkdir listmonk-infra && cd listmonk-infra

kubekutr needs to be configured with its own configuration file. To make things easier for newcomers, we have a default configuration template that can be edited according to your needs.

kubekutr init --default

You should see kubekutr.yml generated by kubekutr in the previous step. Let's edit the file with the below configuration suited for listmonk:

workloads:
  - name: listmonk # name of the project
    deployments:
      - name: app # name of the individual component
        replicas: 1
        labels:
          - name: 'app.kubernetes.io/component: app'
        containers:
          - name: app
            createService: true
            image: 'localhost:32000/listmonk:0.5'
            command: '["./listmonk"]'
            args: '["--config", "/etc/listmonk/config.toml"]'
            envSecret: app-secrets
            ports:
            - name: app-port
              port: 9000
            cpuLimits: 800m
            memoryLimits: 500Mi
            cpuRequests: 400m
            memoryRequests: 250Mi
            readinessPort: 9000
            readinessPath: /
            livenessPort: 9000
            livenessPath: /
            volumeMounts:
              - name: config-dir
                mountPath: /etc/listmonk
        volumes:
          - name: config-dir
    services:
      - name: postgres
        type: ClusterIP
        headless: true
        ports:
          - name: db-port
            targetPort: db-port
            port: 5432
        labels:
          - name: 'app.kubernetes.io/component: svc-headless'
        selectors:
          - name: 'app.kubernetes.io/component: db'
    statefulsets:
      - name: db
        serviceName: postgres
        labels:
          - name: 'app.kubernetes.io/component: db'
        containers:
          - name: postgres
            image: 'postgres:12.2-alpine'
            ports:
            - name: db-port
              port: 5432
            envSecret: db-secrets
            volumeMounts:
              - name: postgres-storage
                mountPath: /var/lib/postgres
            cpuLimits: 500m
            memoryLimits: 800Mi
            cpuRequests: 250m
            memoryRequests: 400Mi
        volumes:
          - name: postgres-storage

A quick overview

Let's break down the giant config into pieces easy to understand.

workloads

Workload represents a project/application. All of the different components of an application together form one workload.

So when we use:

workloads:
  - name: listmonk # name of the project
  ...

We are naming our workload listmonk and going to add different components like the app container, the db container, etc to it.

deployments

Before we describe Deployments object, let's first understand how Kubernetes schedules the containers. Kubernetes control plane runs a component kube-scheduler. The job of this scheduler is to talk to an agent running on different nodes (called kubelet) and figure out which node is the best to run your container depending on resource requirements.

A container in Kubernetes is basically wrapped around something called Pods. So Pods contain the information about network, storage resources, volume mounts, and the Docker image/arg etc. Kubernetes manages the pod directly, not the container so any changes that the controller makes are applied at the Pod level.

Sidecars: Running a single container per pod is the most common use case, but you often may find the usage of sidecars, which means running a lightweight container in addition to your application. Multiple containers together also form one Pod. The most common use cases of sidecar are for running logging/monitoring agents or running a lightweight web proxy etc.

K8s pods

Pods are managed by Replica Set. Replica set controls the rollout/rollback of a group of pods. You can configure the rollout pattern by tweaking the RollingUpdateStrategy spec in Deployment.

K8s pods

The deployment contains the history of Replica sets at any point in time. The Deployment Controller changes the actual state to the desired state if there's any drift noticed.

K8s pods

services

Service object is used to expose your pod to other pods or to the world. There are multiple services types available for different use-cases about which you can read more here.

K8s pods

In this guide, we will use the service of type ClusterIP. Each pod exposes an IP on the cluster and there are various ways to connect this IP. But what happens if you run multiple replicas of one app? You cannot expect the client to remember n IPs for n replicas. So, when you create a Service object of ClusterIP you get a virtual IP. This Service object backs all other IPs in the form of another object called Endpoints. The endpoint object is responsible for keeping a track of all Pod IPs that the Service object connects to. Kubernetes runs a component kube-proxy which resolves the virtual IP to the actual Pod IP present in Endpoints. This selection is not round-robin and completely random so it is very much possible that you see skewed traffic across pods.

Another crucial concept in service is Labels & Selectors. While reading the above paragraph if you were thinking how does the Service object know which Pod IP to track, then the answer lies in using the field .spec.selector of the Service object.

To understand this easily, let's imagine our Deployment spec looks like:

name: listmonk
replicas: 1
...
labels:
app: listmonk
tier: web
...
image: listmonk:latest

Now, to target the Pod with labels app:listmonk and tier:web we need to create a Service object which matches the .spec.selector:

selector:
    app: listmonk
    tier: web

So, this is how using Labels and matching the equivalent values with Selectors we can target the Pods from a Service object.

Service-K8s

Now that we have covered the broader concepts, let's start building the base manifest using kubekutr here.