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