part 1 - how to create k8s application configuration

What is k8s

K8s is a tool that allows for easy scaling and deployment of applications. It consists of:

Your queue has too many tasks in it? Just deploy 5 more workers. K8s will handle “where to deploy them”.

k8s diagram

This is a diagram of example aplication deployed to the k8s cluster. It is called jp2gmd and has a web UI, a database and a worker running in the background. I will now explain each of the components and how to use it.

Container

Well. It is a docker container. Idk what you expected. 1 container = 1 application.

Pod

This is an abstraction layer used in more advanced applications. For now think of pods as a small wrapper around container. When the time comes, you will know what “pod” really does, but for now you don’t need to know.

Deployment

Tells your k8s cluster what pods (containers) to create. In this case we have 3 pod types:

so we need 3 separate deployments.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: example-deployment          # name of this deployment
  labels:
    app: jp2gmd                     # name of our application
spec:
  replicas: 1                       # how many pods we want to spawn
  selector:
    matchLabels:
      app: jp2gmd                   # not important for now, just match app name
  template:
    spec:
      containers:
      - name: container-postgres    # name of our container
        image: postgres:latest      # docker image to use
        ports:
          - containerPort: 5432     # port to expose

ConfigMap

At some point you might want to have a configuration file for your worker. Since we’re using docker containers we need to provide it somehow.

ConfigMap - key + value pair of configuration provided to pods.

ConfigMaps are flexible, they can create environment variables inside your containers or create file with provided contents.

kind: ConfigMap
apiVersion: v1
metadata:
  name: configmap-example           # name of your config map
  labels: jp2gmd                    # name of application
data:
  key: value                        # key: value pairs

To use ConfigMap in our Pod:

# this a fragment of a deployment
containers:
  - name: container-worker
    image: jp2.gmd/worker:latest
    env:
      - name: SEARCH_ENGINE_URL       # environment variable name
        valueFrom:
          configMapKeyRef:
            name: configmap-example   # name of the ConfigMap
            key: key                  # key from the ConfigMap

Secret

Your web application might use an OAuth Token from different website. You could put it in a ConfigMap, but there’s a special utility for handling secrets.

Just like ConfigMaps they can also be environement variables or files inside container.

apiVersion: v1
kind: Secret
metadata:
  name: secret-example
  labels:
    app: jp2gmd
type: Opaque
stringData:
  accessToken: 'jp2gmd'
  secretToken: 'hunter2'

To use it in your pod:

# this is a fragment of a deployment
containers:
  - name: container-worker
    image: jp2.gmd/webapp:latest
    env:
      - name: OAUTH_ACCESS_TOKEN       # environment variable name
        valueFrom:
          secretKeyRef:
            name: secret-example       # name of the Secret
            key: accessToken           # key from the Secret

PV and PVC

Remember how docker containers aren’t persistent? It would be a shame if your postgres lost all it’s data after application upgrade. Docker solved that problem with volumes. Well, this is the same :)

PV Persistent Volumes - abstraction of storage, be it physical or logical. There are many types of storages that can work as PV: aws, glusterfs, nfs, local drive, you name it.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-example              # name of PV
spec:
  capacity:
    storage: 5Gi                # size
  accessModes:
    - ReadWriteOnce             # how we plan to use the volume
  awsElasticBlockStore:         # PV specific config (here AWS)
    fsType: ext4
    volumeID: vol-1337-2137

PVC - Physical Volume Claim - pod requests a PV to be mounted.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-example
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

And to finally use PVC

# this is a fragment of a deployment
spec:
  volumes:
    - name: db-claim                             # volume name
      persistentVolumeClaim: pvc-example         # name of PVC
  containers:
    - name: container-postgres
      image: postgres:latest
      volumeMounts:
        - mountPath: "/var/lib/postgresql/data"  # path where to mount (inside container)
          name: db-claim                         # name of the volume

Service

So, let’s say we deploy our application on our k8s cluster. It even works. Very nice. But here’s the thing. If you have multiple applications deployed to the same k8s cluster, how do these applications talk between each other if they don’t know each others IPs? That’s where services come into place.

A service is a gateway and a load balancer for pods. This eliminates the need of knowing someone’s IP. If you want to communicate with another sevice - just use it’s name as a hostname. Internal k8s voodoo magic will route your connection.

apiVersion: v1
kind: Service
metadata:
  name: service-example
  labels:
    app: jp2gmd
spec:
  selector:
    app: jp2gmd
  ports:
    - protocol: TCP
      port: 8080           # port inside container you want to connect to

Ingress

This is the last piece of the puzzle.

Ingress - a gateway to the internet. It routes HTTP(S) paths to k8s services. It can also do routing and stuff like path rewriting.

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress-example
spec:
  rules:
  - host: jp2gmd.org
    http:
      paths:
        - webapp:
            serviceName: service-example
            servicePort: 8080

Aaaand that’s about it. It’s a lot of information, so I don’t expect anyone to instantly understand everything. This post won’t run away. Feel free to come back to it if you’ve forgotten about something.