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:
- master node - which organizes stuff
- arbitrary number of regular nodes - which run your applications
Your queue has too many tasks in it? Just deploy 5 more workers. K8s will handle “where to deploy them”.
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:
- web app
- postgres
- worker
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.