Kubernetes - Basics

Overview

Spring Boot development with docker & kubernetes.

Github: https://github.com/gitorko/project61

Kubernetes

Rancher Desktop

Rancher Desktop allows you to run Kubernetes on your local machine. Its free and open-source.

Disable Traefik, select dockerd as container in the settings.

If you get the below error when you run kubectl, its mostly due to .kubeconfig file already present from docker desktop installation.

1I0804 20:09:34.857149   37711 versioner.go:58] Get "https://kubernetes.docker.internal:6443/version?timeout=5s": x509: certificate signed by unknown authority
2Unable to connect to the server: x509: certificate signed by unknown authority

Delete the .kube folder and restart Rancher Desktop.

1rm -rf ~/.kube

Docker Desktop

Docker Desktop allows you to run Kubernetes on your local machine. Do refer the latest licensing terms as they have changed.

Once kubernetes is running, check kubectl.

1export KUBECONFIG=~/.kube/config
2kubectl version

Kubernetes Dashboard

If you want to visualize the kubernetes infra, you can install the dashboard UI.

https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/

1kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/aio/deploy/recommended.yaml
2kubectl proxy

Open the dashboard url in a browser

http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/

To get the token to login run the below command

1kubectl -n kube-system describe secret default|grep -i 'token:'|awk '{print $2}'
2kubectl config set-credentials docker-for-desktop --token="${TOKEN}"

Now provide the token and login.

Clean up

1kubectl --namespace kube-system get all
2kubectl delete -f https://raw.githubusercontent.com/kubernetes/dashboard/master/aio/deploy/recommended.yaml

Build & Deployment

Build the project

1git clone https://github.com/gitorko/project61.git
2cd project61
3./gradlew clean build

Docker

There are 2 ways you can build the docker image, either run the docker build command or use the google jib library.

To build via docker build command

1docker build -f k8s/Dockerfile --force-rm -t project61:1.0.0 .
2docker images | grep project61

To build via jib plugin run the below command. This way building the docker image can be part of the build process

1./gradlew jibDockerBuild

Test if the docker image is working

1docker rm project61
2docker run -p 9090:9090 --name project61 project61:1.0.0

http://localhost:9090/api/time

Daemon mode

1docker run -d -p 9090:9090 --name project61 project61:1.0.0
2docker image prune 

Kubernetes Basics

Now let's deploy the project on a kubernetes cluster. Check if kubernetes commands work

1kubectl version
2kubectl config get-contexts
3kubectl config use-context docker-desktop
4kubectl config set-context --current --namespace=default
5kubectl get nodes
6kubectl get ns
7kubectl get all
8kubectl cluster-info

We will now deploy just the docker image in kubernetes without needing any yaml files and using port forwarding access the api. Very rarely you will need to do this as most k8s deployment is done via yaml.

1kubectl run project61-k8s --image project61:1.0.0 --image-pull-policy=Never --port=9090
2kubectl port-forward project61-k8s 9090:9090

http://localhost:9090/api/time

You can also create a service and access the pod. Get the port from the NodePort. Again this is to understand the fundamentals, a yaml file will be used later.

1kubectl expose pod project61-k8s --type=NodePort
2kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services project61-k8s

Change the port that you got in the last command and test this api: http://localhost:/api/time

Check the pods,services & deployments.

1kubectl get all

You can access the bash terminal of the pod

1kubectl get pods
2kubectl exec -it project61-k8s -- /bin/bash
3ls

Clean up.

1kubectl delete pod project61-k8s
2kubectl delete service project61-k8s
3kubectl get all

Kubernetes Yaml

Now we will deploy via the kubernetes yaml file.

1kubectl apply -f k8s/Deployment.yaml --dry-run=client --validate=true
2kubectl apply -f k8s/Deployment.yaml

http://localhost:9090/api/time

Scale the deployment

1kubectl scale deployment project61-k8s --replicas=3

Look at the logs

1kubectl logs -f deployment/project61-k8s --all-containers=true --since=10m

Clean up

1kubectl delete -f k8s/Deployment.yaml

Helm

Now lets deploy the same project via helm charts

1brew install helm
1helm version
2helm install project61 mychart
3helm list
4kubectl get pod,svc,deployment

Get the url and invoke the api

1curl http://$(kubectl get svc/project61-k8s -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'):9090/api/time
1http://localhost:9090/api/time

Clean up

1helm uninstall project61

Debugging

To attach a debugger to the application follow the below steps

Docker Debug

To debug the docker image start the pod with the debug port on 5005 enabled.

1docker stop project61
2docker rm project61
3docker run -p 9090:9090 -p 5005:5005 --name project61 project61:1.0.0

Enable remote JVM debug in intellij

1-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005

http://localhost:9090/api/time

Now when you request the api, the debug breakpoint in intellij is hit.

Kubernetes Debug

To debug the kubernetes pod start port forwarding to the port 5005

1kubectl get pod
2kubectl port-forward pod/<POD_NAME> 5005:5005

Now when you request the api, the debug breakpoint in intellij is hit.

Telepresence

To debug the kubernetes pod you can use telepresence. It will swap the prod running on kubernetes with a proxy pod that redirects traffic to your local setup.

Install telepresence

1sudo curl -fL https://app.getambassador.io/download/tel2/darwin/amd64/latest/telepresence -o /usr/local/bin/telepresence
2sudo chmod a+x /usr/local/bin/telepresence

Start the project61 application in debug mode in intellij, change the port to 9095 in application yaml, as we will be testing debugging locally.

Run the telepresence command, that will swap the kubernetes pod with a proxy pod and redirect all requests on 9090 to 9095.

1telepresence --namespace=default --swap-deployment project61-k8s --expose 9095:9090 --run-shell
2kubectl get pods

Note that port here is 9090 that is the kubernetes port for incoming requests. Telepresence will redirect these to 9095 port where your local instance is running.

http://localhost:9090/api/time

Now when you request the api, the debug breakpoint in intellij is hit.

JVM Monitoring

To hook jConsole or VisualVM

Docker

To connect to JMX port, start docker image with port 9095 exposed. The docker image already has the settings to enable JMX.

1docker stop project61
2docker rm project61
3docker run -p 9090:9090 -p 9095:9095 --name project61 project61:1.0.0

Kubernetes

To connect to JMX port, start port forwarding, The docker image already has the settings to enable JMX.

1kubectl get pod
2kubectl port-forward pod/<POD_NAME> 9095:9095

VisualVM

Connect to the port

1http://localhost:9095

JConsole

1jconsole 127.0.0.1:9095

Jenkins CI/CD

Fork the github project61 repo so you have your own github project to push the code & clone it.

https://github.com/gitorko/project61

Download the jenkins war file and run the below command.

https://www.jenkins.io/

1java -jar jenkins.war --httpPort='8088'

http://localhost:8088/

Follow the default steps to install plugin and configure jenkins. The default password is printed in the console log.

Goto Global Tool Configuration and add Java 17, Maven

Add the kubernetes config as a credential

1/Users/$USER/.kube/config

Install the kubernetes CLI plugin

https://plugins.jenkins.io/kubernetes-cli/

Then create a pipeline item and copy the content of Jenkinsfile, Enter the GitHub url of your forked project. Save and run the job.

 1pipeline {
 2    agent any
 3
 4    tools {
 5        jdk "jdk-17"
 6        maven "maven3"
 7    }
 8
 9    stages {
10        stage('Checkout') {
11            steps {
12                //TODO: Change to forked repo
13                git url: 'https://github.com/gitorko/project61', branch: 'master'
14            }
15        }
16        stage('Build') {
17            steps {
18                sh "./gradlew clean build"
19            }
20            post {
21                // record the test results and archive the jar file.
22                success {
23                    junit 'build/test-results/test/TEST-*.xml'
24                    archiveArtifacts 'build/libs/*.jar'
25                }
26            }
27        }
28        stage('Build Docker Image') {
29            steps {
30                sh "./gradlew jibDockerBuild -Djib.to.tags=$BUILD_NUMBER"
31            }
32            post {
33                // record the test results and archive the jar file.
34                success {
35                    junit 'build/test-results/test/TEST-*.xml'
36                    archiveArtifacts 'build/libs/*.jar'
37                }
38            }
39        }
40        stage ('Push Docker Image') {
41            steps {
42                //TODO: docker hub push
43                echo "Pushing docker image"
44            }
45        }
46        stage('Deploy') {
47            steps {
48                 withKubeConfig([credentialsId: 'kubernetes-config']) {
49                  sh '''
50cat <<EOF | kubectl apply -f -
51apiVersion: apps/v1
52kind: Deployment
53metadata:
54  name: project61-k8s
55spec:
56  selector:
57      matchLabels:
58        app: project61-k8s
59  strategy:
60    rollingUpdate:
61      maxSurge: 1
62      maxUnavailable: 1
63    type: RollingUpdate
64  replicas: 1
65  template:
66    metadata:
67      labels:
68        app: project61-k8s
69    spec:
70      containers:
71        - name: project61
72          image: project61:$BUILD_NUMBER
73          imagePullPolicy: IfNotPresent
74          ports:
75            - containerPort: 9090
76          resources:
77            limits:
78              cpu: "1"
79              memory: "500Mi"
80---
81kind: Service
82apiVersion: v1
83metadata:
84  name: project61-k8s
85spec:
86  ports:
87  - port: 9090
88    targetPort: 9090
89    name: http
90  selector:
91    app: project61-k8s
92  type: LoadBalancer
93                    '''
94                }
95            }
96        }
97    }
98}

Each jenkins job run creates a docker image version by build number, kubectl terminates the old pod and starts the new pod.

1docker images |grep project61
2kubectl get pods -w

Clean up the docker images as they consume space.

1docker rmi project61:1
2kubectl delete -f k8s/Deployment.yaml

You can configure a 'GitHub hook trigger for GITScm polling' to deploy when a commit is pushed to github.

Resources

Kubernetes Samples

Build a custom nginx image

1docker build -f k8s-manifest/Dockerfile1 --force-rm -t my-nginx:1 .
2docker build -f k8s-manifest/Dockerfile2 --force-rm -t my-nginx:2 .

Create an alias for kubectl as k

1alias k="kubectl"

https://kubernetes.io/docs/reference/kubectl/cheatsheet/

01. Create a simple pod

1k apply -f k8s-manifest/01-create-pod.yaml
2k get all
3k delete -f k8s-manifest/01-create-pod.yaml
4k logs pod/counter
5k describe pod/counter

02. Create ngnix pod, use port forward to access

Create nginx pod and enter pods bash prompt

1k apply -f k8s-manifest/02-nginx-pod.yaml
2k get all
3k port-forward pod/nginx 8080:80
4k exec -it pod/nginx -- /bin/sh
5k delete -f k8s-manifest/02-nginx-pod.yaml

http://localhost:8080/

03. Create ngnix pod with html updated by another container in same pod

1k apply -f k8s-manifest/03-nginx-pod-volume.yaml
2k get pods -w
3kubectl get -o jsonpath="{.spec.ports[0].nodePort}" service/nginx-service
4k delete -f k8s-manifest/03-nginx-pod-volume.yaml

http://localhost:31000/

04. Create job

Run once and stop. output is kept till you delete it.

1k apply -f k8s-manifest/04-job.yaml
2k get all
3k delete -f k8s-manifest/04-job.yaml

05. Liveness probe

Liveness probe determines when pod is healthy, here file is deleted after 30 seconds causing pod to restart

1k apply -f k8s-manifest/05-liveness-probe.yaml
2k get pods -w
3k delete -f k8s-manifest/05-liveness-probe.yaml

06. Readiness probe

Readiness probe determines when to send traffic

1k apply -f k8s-manifest/06-readiness-probe.yaml
2k port-forward pod/nginx 8080:80
3k delete -f k8s-manifest/06-readiness-probe.yaml

http://localhost:8080/

07. Cron Job

Cron job runs every minute

1k apply -f k8s-manifest/07-cron-job.yaml
2k get job.batch -w
3k delete -f k8s-manifest/07-cron-job.yaml

08. Config

Configure configMap and secrets.

1k apply -f k8s-manifest/08-config.yaml
2k logs pod/busybox
3k delete -f k8s-manifest/08-config.yaml

config map as volume

1k apply -f k8s-manifest/08-config-volume.yaml
2k logs -f pod/busybox
3k edit configmap app-setting
4k get configmap app-setting -o yaml
5k exec -it pod/busybox -- /bin/sh
6
7k delete -f k8s-manifest/08-config-volume.yaml

09. Deployment with Load Balancer

1k apply -f k8s-manifest/09-deployment.yaml
2k get all
3k port-forward service/nginx-service 8080:8080
4
5k scale deployment.apps/nginx --replicas=0
6k scale deployment.apps/nginx --replicas=3
7
8k delete -f k8s-manifest/09-deployment.yaml

http://localhost:8080/

10. External service

Proxies to external name

1k apply -f k8s-manifest/10-external-service.yaml
2k get services
3k delete -f k8s-manifest/10-external-service.yaml

11. Host Path Volume

1k apply -f k8s-manifest/11-volume-host-path.yaml
2k get all
3k delete -f k8s-manifest/11-volume-host-path.yaml

http://localhost:31000/

12. Persistent Volume & Persistent Volume Claim

1k apply -f k8s-manifest/12-pesistent-volume.yaml
2k get pv
3k get pvc
4k get all
5k delete -f k8s-manifest/12-pesistent-volume.yaml

http://localhost:31000/

14. Blue Green Deployment

1k apply -f k8s-manifest/14-deployment-blue-green.yaml
2k apply -f k8s-manifest/14-deployment-blue-green-flip.yaml
3
4k delete service/nginx-blue
5k delete deployment/nginx-v1 
6
7k delete -f k8s-manifest/14-deployment-blue-green.yaml

http://localhost:31000/ http://localhost:31000/

15. Canary Deployment

1k apply -f k8s-manifest/15-deployment-canary.yaml
2
3k delete deployment/nginx-v2
4
5k delete -f k8s-manifest/15-deployment-canary.yaml
1while true; do curl http://localhost:31000/; sleep 2; done

http://localhost:31000/

References

https://github.com/GoogleContainerTools/jib

https://www.docker.com/products/docker-desktop/

https://rancherdesktop.io/

https://birthday.play-with-docker.com/kubernetes-docker-desktop/

https://helm.sh/

https://www.getambassador.io/docs/telepresence/latest/quick-start/qs-java/

https://visualvm.github.io/

https://www.eclipse.org/mat/

https://www.jenkins.io/

https://plugins.jenkins.io/kubernetes/

comments powered by Disqus