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.
Well. It is a docker container. Idk what you expected. 1 container = 1 application.
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.
Tells your k8s cluster what pods (containers) to create. In this case we have 3 pod types:
- web app
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
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
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
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
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.