Kubernetes

Introduction to Kubernetes

A practical introduction to Kubernetes


Prerequisites


What you will learn today


What Kubernetes is?

Kubernetes is an open source container orchestrator. It automates configuration, deployment, scaling of containers in a simple and declarative way.

docker_kubernetes.png|500


Kubernetes Main Features


Pods

Pods are the smallest deployable units of computing that you can create and manage in Kubernetes.

A Pod is a group of one or more containers, with shared storage and network resources, and a specification for how to run the containers.


Specifications

This is a simple Pod specification:

apiVersion: v1 # Tells Kubernetes what api version to use
kind: Pod # Tells kubernetes what type of resource to create
metadata:
  name: nginx # Add a name to the pod
  labels:
    app: nginx # Add a label to this pod. It can be used to find pods easier
spec:
  containers: # Describes the containers in this pod
  - name: nginx
    image: nginx:1.14.2
    ports:
    - containerPort: 80

Main Commands

kubectl apply -f [filename] # creates file contents
kubectl get pods # Gets jobs
kubectl port-forward [resourceType]/[resourceName] [hostPort]:[resourcePort]
kubectl logs [pod name] -c [container name (optional)] # Get logs of a pod
kubectl delete [resourceType] [resourceName]

Hands-on

Now try to run our Postgres database on a Pod and connect to it:

apiVersion: v1
kind: Pod
metadata:
  name: postgres
  labels:
    name: postgres
spec: 
  containers:
    - name: postgres
      image: postgres:14
      ports:
        - containerPort: 5432
      env:
        - name: POSTGRES_PASSWORD
          value: rn6yBZ1QG7x1asvBRzaX
        - name: POSTGRES_DB
          value: demo

Deployments

Deployments allow Kubernetes to create and manage replicas of pods, allowing you to scale the application automatically and self-heal the application in case of failure.

While pods are interesting and one of the foundations of Kubernetes they work more often than not only as building blocks to other more complex solutions.


Deploying frontend

apiVersion: apps/v1  # A different API version than for pods
kind: Deployment
metadata:
  name: frontend # the name will serve as prefix for pods created eg. frontend-dashjkf
spec:
  replicas: 3 # Desired number of replicas
  selector: # Selector that tells the deployment which pods belong to it
    matchLabels:
      app: frontend
  template: # Template for pods
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: demo
        image: rodsantos/demo-frontend:latest
        env: # Environment variables
        - name: FOO
          value: bar
        ports:
        - containerPort: 3000

Main Commands

kubectl apply -f [filename] # creates file contents
kubectl get deployments # Gets jobs
kubectl get pods # Gets pods
kubectl port-forward [resourceType]/[resourceName] [hostPort]:[resourcePort]
kubectl delete pod [resourceName]
kubectl delete deployment [resourceName]

Hands-on

Now it is your turn to create a deployment for the image rodsantos/demo-backend-application ๐Ÿš€

Hint:

- name: DATABASE_URL # Composing env variable
  value: postgres://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@$(POSTGRES_SERVICE):$(POSTGRES_PORT)/$(POSTGRES_DB)

Hands-on

The deployment would look like this:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
      - name: demo
        image: rodsantos/demo-backend-application:latest
        env:
        - name: POSTGRES_PASSWORD
          value: rn6yBZ1QG7x1asvBRzaX
        - name: POSTGRES_DB
          value: demo
        - name: POSTGRES_USER
          value: postgres
        - name: POSTGRES_SERVICE
          value: postgres-service
        - name: POSTGRES_PORT
          value: "80"
        - name: DATABASE_URL # Composing env variable
          value: postgres://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@$(POSTGRES_SERVICE):$(POSTGRES_PORT)/$(POSTGRES_DB)
        ports:
        - containerPort: 4000
  

Services

Now that we have multiple pods running we need to have a way to distribute the request between them services serve this purpose.

They act as a load balancer and they know which pods to send the request by using a selector.


Specification

apiVersion: v1
kind: Service
metadata:
  name: postgres-service
spec:
  selector:
    name: postgres
  ports:
    - protocol: TCP
      port: 80
      targetPort: 5432

Specification

apiVersion: v1
kind: Service
metadata:
  name: frontend-service # service name
spec:
  selector:
    app: frontend # job label it will search for
  ports:
    - protocol: TCP
      port: 80 # Port exposed by the service
      targetPort: 3000 # Pod Port

Hands-on

Now it is your chance to create a service for your backend deployment ๐Ÿš€

apiVersion: v1
kind: Service
metadata:
  name: backend-service # service name
spec:
  selector:
    app: backend # job label it will search for
  ports:
    - protocol: TCP
      port: 80 # Port exposed by the service
      targetPort: 4000 # Pod Port

Secrets

So far we have been copying and pasting the db password every time we need to use it, but this is not secure or practical.

Secrets allow you to store sensitive values and to consume them in other Kubernetes objects such as Pods, Deployments or Jobs.


Creating secrets

apiVersion: v1
kind: Secret
metadata:
   name: secret_name
data:
   mysecret: YWRtaW4= # secret on base 64

### Consuming secrets
env:
  - name: MYSQL_USER # env Variable name
	valueFrom:
	  secretKeyRef:
		name: secret_name # secret name
		key: mysecret # key on file

Hands-on

Now it is your chance to create a secret for your database ๐Ÿš€

Hint:

echo 'My secret password' | base64 # Encrypts value to base 64 

Hands-on

Secret:

apiVersion: v1
kind: Secret
metadata:
  name: postgres
data:
  password: cm42eUJaMVFHN3gxYXN2QlJ6YVgK # secret on base 64


Deployment:

#...
        env: # Environment variables
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres
              key: password
#...

Jobs

Kubernetes allow you to create jobs or cronjobs that will create a pod and exit after finishing the task.

Jobs have built-in functionality to retry in case of failure.


Applying this configuration:

apiVersion: batch/v1
kind: Job
metadata:
  name: migration
spec:
  backoffLimit: 4 # retry limit
  template: # Template for the job that will be created
    spec:
      containers:
      - name: migration
        image: rodsantos/demo-migration:latest
        env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres
              key: password
        - name: POSTGRES_DB
          value: demo
        - name: POSTGRES_USER
          value: postgres
        - name: POSTGRES_SERVICE
          value: postgres-service
        - name: POSTGRES_PORT
          value: "80"
        - name: DATABASE_URL
          value: postgres://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@$(POSTGRES_SERVICE):$(POSTGRES_PORT)/$(POSTGRES_DB)
      restartPolicy: Never

Will result into:

jobs_execution_result.png


Volumes

If you turn delete the Postgres instance you will notice that when you create it again all data that we saved is gone. For obvious issues this is not desired in a database, so what causes this?

Docker images are immutable by nature and container have only a temporary layer of storage. If a container store data on disk and it is deleted the data stored will be gone.


Volumes

Volumes are the mechanism that can prevent this from happening. A volume mounts an external data storage to your image, so when you store data there it will be persisted even when the container is deleted!


Volumes

This is a basic example of a Volume:

# ...
spec: 
  containers:
    - name: postgres
      image: postgres:14
	  # ...
      volumeMounts: # Mounts a volume to a container 
        - mountPath: /var/lib/postgresql/data # tells the path it should be mounted
          name: storage # describes which store should be used
  volumes:
  - name: storage # name of the volume
    hostPath: # volume type that uses host to store files
      path: /tmp/volume # host path
      type: DirectoryOrCreate # path type

What was not covered


FAQ


Questions?


Thank you :rocket: