Kubernetes is a container management platform to manage your workloads without having to deal with the servers that run them. In this blog you’ll find the basics of deploying a container on a Kubernetes cluster and how to expose it to the world.
Why Kubernetes?
Almost everyone nowadays has worked with, or created containers. While working with a single container is easy and fun, running and maintaining highly available services at scale can be a enormous burden.
Luckily, Kubernetes simplifies the creation, scheduling and distribution of workloads.
Hands-on walkthrough?
To be able to work with Kubernetes we need a cluster. Most major cloud providers have ready to go Kubernetes cluster solutions. For easy local testing however Minikube is a great solution.
First install Minikube
Get Minikube here: https://minikube.sigs.k8s.io/docs/start/
Once installed, start Minikube with the command:
minikube start
This will start downloading a vm with Kubernetes and some tools. Once that’s finished, we check if it works.
Test and verify
minikube kubectl #this will test that kubectl is installed and working kubectl get nodes #NAME STATUS ROLES AGE VERSION #minikube Ready master 51m v1.17.0
Running an application
Now that we have a working Kubernetes environment let’s create our deployment. The deployment will create and manage the containers inside, the so-called ‘pods’. An abstraction that Kubernetes uses to manage containers and their resources.
First create a file: deployment.yml
--- apiVersion: apps/v1 #api-to-use kind: Deployment #what kind of file is this metadata: name: nginx #name of the deployment spec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx #name of the docker image to deploy image: nginx:latest #docker image:version ports: - containerPort: 80 #port in use by the application
Tell Kubernetes to create a deployment for our Nginx app.
kubectl apply -f ./deployment.yml #deployment.apps/nginx created
Check if the pods are running:
kubectl get po -o wide #NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES #nginx-59c9f8dff-fvjqw 1/1 Running 0 2m4s 172.17.0.4 minikube <none> <none>
Now let us scale up the number of pods so we have more than 1 webserver.
kubectl edit deployment nginx
Look for the part where it shows the number of replicas.
spec: progressDeadlineSeconds: 600 replicas: 1 #right here revisionHistoryLimit: 10 selector: matchLabels: app: nginx strategy: rollingUpdate: maxSurge: 25% maxUnavailable: 25% type: RollingUpdate
Change the number to the amount of Nginx servers you want.
spec: progressDeadlineSeconds: 600 replicas: 3 #i'll take 3 of those please revisionHistoryLimit: 10 selector: matchLabels: app: nginx strategy: rollingUpdate: maxSurge: 25% maxUnavailable: 25% type: RollingUpdate
Let’s see what the number of replications does to our pods.
kubectl get po #NAME READY STATUS RESTARTS AGE #nginx-59c9f8dff-fcp7b 0/1 ContainerCreating 0 1s #nginx-59c9f8dff-rd8hw 0/1 ContainerCreating 0 1s #nginx-59c9f8dff-z2gkw 1/1 Running 0 16h
So now we have 3 pods running Nginx but no way to access them. Let’s first test if they actually work.
kubectl get po -o wide # -o wide will give more details like ip address #NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES #nginx-59c9f8dff-fcp7b 1/1 Running 0 2m46s 172.17.0.7 minikube <none> <none> #nginx-59c9f8dff-rd8hw 1/1 Running 0 2m46s 172.17.0.6 minikube <none> <none> #nginx-59c9f8dff-z2gkw 1/1 Running 0 1h 172.17.0.4 minikube <none> <none>
With the IP’s we can access the pods to see if they work. First, we go into the Minikube vm so that we can access the containers directly:
minikube ssh _ _ _ _ ( ) ( ) ___ ___ (_) ___ (_)| |/') _ _ | |_ __ /' _ ` _ `\| |/' _ `\| || , < ( ) ( )| '_`\ /'__`\ | ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )( ___/ (_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____) $
Inside the vm we use curl to check the index page of all 3 pods. Note that the IP I use for curl is the IP bound to the pod.
curl --head 172.17.0.7 #HTTP/1.1 200 OK #Server: nginx/1.19.7 #Date: Tue, 23 Feb 2021 08:59:14 GMT #Content-Type: text/html #Content-Length: 612 #Last-Modified: Tue, 16 Feb 2021 15:57:18 GMT #Connection: keep-alive #ETag: "602beb5e-264" #Accept-Ranges: bytes
Create a service
Here we have our first problem. We have 3 pods that are not accessible and every time we add or remove pods, we get varying IP addresses. We need a way to dynamically access the pods based on their Kubernetes properties, rather than static IP addressing.
This is where the service comes into play.
The service will dynamically route traffic to 1 of the available Nginx containers inside and expose itself to the outside world as an accessible NodePort on the host.
So how do we create a service?
Make a yml file with the following content. service.yml ->
apiVersion: v1 #stable v1 api kind: Service #what are we trying to create metadata: name: nginx #what is my name spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: app: nginx type: NodePort #nodeport will open a port on the host to connect to the service status: loadBalancer: {}
Now lets create this service.
kubectl apply -f ./service.yml #service/nginx created
To check if the service was created.
kubectl get service #NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE #kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 20h #nginx NodePort 10.96.168.208 <none> 80:31575/TCP 71s
Note that we now have a Nginx service that still has a IP we can not reach. But because we made a service of the type ‘nodeport’ it’s now bound to an external port on the Minikube vm. Thus we can access it.
We ask Minikube to provide us with the URL. (your IP and port may be different)
minikube service nginx --url #http://192.168.64.2:31575 curl --head http://192.168.64.2:31575 #HTTP/1.1 200 OK #Server: nginx/1.19.7 #Date: Tue, 23 Feb 2021 09:32:36 GMT #Content-Type: text/html #Content-Length: 612 #Last-Modified: Tue, 16 Feb 2021 15:57:18 GMT #Connection: keep-alive #ETag: "602beb5e-264" #Accept-Ranges: bytes
Access from the outside & ingress explained
So we now have 3 containers with Nginx that we can use from the outside.
But what if we want to use a hostname and port 80 (or 443), but both port 80 and 443 are already in use by Kubernetes? Or what if we might want multiple applications that all use port 80?
That is where the ingress comes in handy. The ingress translates incoming traffic based on hostname or path and directs it to the correct service.
Ingress deployment
To be able to deploy an ingress in Minikube we must enable the ingress controller first.
minikube addons enable ingress #✅ ingress was successfully enabled
Next we create a yml file for the ingress.
--- apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: nginx-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: /$1 spec: rules: - host: nginx.techforce1.nl # hostname the ingress will match on http: paths: - path: / # path to check for backend: serviceName: nginx # service to bind on servicePort: 80 # port on the service to bind on
Now apply this file:
kubectl create -f ingress.yml #ingress.networking.k8s.io/nginx-ingress created
Check if the creation was a success.
kubectl get ingress #NAME HOSTS ADDRESS PORTS AGE #nginx-ingress nginx.techforce1.nl 192.168.64.2 80 5m20s
Now add the information from the ingress in our hosts file (/etc/hosts on mac and linux).
sudo echo "192.168.64.2 nginx.techforce1.nl" >> /etc/hosts
We can then access the Nginx via fqdn.
curl --head nginx.techforce1.nl #HTTP/1.1 200 OK #Server: openresty/1.15.8.2 #Date: Tue, 23 Feb 2021 13:23:42 GMT #Content-Type: text/html #Content-Length: 612 #Connection: keep-alive #Vary: Accept-Encoding #Last-Modified: Tue, 16 Feb 2021 15:57:18 GMT #ETag: "602beb5e-264" #Accept-Ranges: bytes
Or in our browser:
It works!