In the previous blog post we learnt how to create a customer docker image and build container from it.
Image courtesy Minikube.
In this blog post we will use the docker image built previously and deploy it using Kubernetes.
We will do the following-
- Push the image to docker hub
- Install minikube and Install kubectl
- Create deployment file and deploy the pod
- Deploy another pod and test connectivity from the pod
- Expose the pod using following methods
a. Using port-forward method
b. Create NodePort service and expose on port 80
Refer to previous post for steps to create a custom docker image and docker installation steps. My setup is an arm64 ubuntu server virtual machine running docker and minikube. So let’s start!
Push the image to Docker hub
Here I have an image called webpage. This is an nginx based webserver which serves a webpage that we created (refer to this blog post).
clitoapi@jarvis:~/docker_nginx$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
webpage latest 60b81ca16d3f About a minute ago 40.7MB
hello-world latest b038788ddb22 2 months ago 9.14kB
We can see that a container is running using this image.
clitoapi@jarvis:~/docker_nginx$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d4290dbcc26f 60b81ca16d3f "/docker-entrypoint.…" 17 seconds ago Up 16 seconds 0.0.0.0:8080->80/tcp, :::8080->80/tcp goofy_visvesvaraya
Create a docker account if you do not have one. Login to docker account.
clitoapi@jarvis:~/docker_nginx$ sudo docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to [https://hub.docker.com](https://hub.docker.com/) to create one.
Username:
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
My docker repository is called clitoapi/aclitoapi_webpage, so I will tag the image accordingly.
Here clitoapi is the namespace created on Docker hub and aclitoapi_webpage is the name of repository. If repository does not exist then push will create a new public repository, tagging will not be required.
clitoapi@jarvis:~/docker_nginx$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
webpage latest 60b81ca16d3f 15 minutes ago 40.7MB
hello-world latest b038788ddb22 2 months ago 9.14kB
clitoapi@jarvis:~/docker_nginx$ sudo docker tag webpage clitoapi/aclitoapi_webpage
Now we are ready to push the image to docker hub.
clitoapi@jarvis:~/docker_nginx$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
clitoapi/aclitoapi_webpage latest 60b81ca16d3f 16 minutes ago 40.7MB
webpage latest 60b81ca16d3f 16 minutes ago 40.7MB
hello-world latest b038788ddb22 2 months ago 9.14kB
clitoapi@jarvis:~/docker_nginx$ sudo docker push clitoapi/clitoapi_repo
Using default tag: latest
The push refers to repository [[docker.io/clitoapi/clitoapi_repo](http://docker.io/clitoapi/clitoapi_repo)]
a986c8ee190a: Pushed
e6f9fb9edcd1: Mounted from arm64v8/nginx
e3222cfd0239: Mounted from arm64v8/nginx
d5ad83434521: Mounted from arm64v8/nginx
a06f0fc55ea6: Mounted from arm64v8/nginx
b684d67dbe6f: Mounted from arm64v8/nginx
180c71c90053: Mounted from arm64v8/nginx
9386262d7a74: Mounted from arm64v8/nginx
latest: digest: sha256:771abac75928c16fc77acd27bdf0aae5d72985838417130ad69fcc1c6f11b375 size: 1988
Let’s see if we can pull this image and run container. Let’s stop the container and remove all images. Pull this image and run container.
clitoapi@jarvis:~/docker_nginx$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
clitoapi/aclitoapi_webpage latest 60b81ca16d3f 3 hours ago 40.7MB
webpage latest 60b81ca16d3f 3 hours ago 40.7MB
hello-world latest b038788ddb22 2 months ago 9.14kB
clitoapi@jarvis:~/docker_nginx$ sudo docker rmi clitoapi/aclitoapi_webpage
Untagged: clitoapi/aclitoapi_webpage:latest
Untagged: clitoapi/aclitoapi_webpage@sha256:771abac75928c16fc77acd27bdf0aae5d72985838417130ad69fcc1c6f11b375
clitoapi@jarvis:~/docker_nginx$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
webpage latest 60b81ca16d3f 3 hours ago 40.7MB
hello-world latest b038788ddb22 2 months ago 9.14kB
clitoapi@jarvis:~/docker_nginx$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
webpage latest 60b81ca16d3f 3 hours ago 40.7MB
hello-world latest b038788ddb22 2 months ago 9.14kB
clitoapi@jarvis:~/docker_nginx$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d4290dbcc26f 60b81ca16d3f "/docker-entrypoint.…" 3 hours ago Up 3 hours 0.0.0.0:8080->80/tcp, :::8080->80/tcp goofy_visvesvaraya
clitoapi@jarvis:~/docker_nginx$ sudo docker stop goofy_visvesvaraya
goofy_visvesvaraya
clitoapi@jarvis:~/docker_nginx$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d4290dbcc26f 60b81ca16d3f "/docker-entrypoint.…" 3 hours ago Exited (0) 6 seconds ago goofy_visvesvaraya
clitoapi@jarvis:~/docker_nginx$ sudo docker rm goofy_visvesvaraya
goofy_visvesvaraya
clitoapi@jarvis:~/docker_nginx$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
clitoapi@jarvis:~/docker_nginx$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
webpage latest 60b81ca16d3f 3 hours ago 40.7MB
hello-world latest b038788ddb22 2 months ago 9.14kB
clitoapi@jarvis:~/docker_nginx$ sudo docker rmi 60b81ca16d3f
Untagged: webpage:latest
Deleted: sha256:60b81ca16d3fd67b9ae88927b74b8ce6b6984048678b085852021168c62d0831
clitoapi@jarvis:~/docker_nginx$
Pull the image from docker hub.
clitoapi@jarvis:~/docker_nginx$ sudo docker pull clitoapi/aclitoapi_webpage:latest
latest: Pulling from clitoapi/aclitoapi_webpage
edb6bdbacee9: Already exists
4c7f12338fe3: Already exists
002a136ea5c5: Already exists
6d407d2ad632: Already exists
d1543f6e84d3: Already exists
ad428fb17e98: Already exists
bacb1bf71fa0: Already exists
374e6c54001d: Already exists
Digest: sha256:771abac75928c16fc77acd27bdf0aae5d72985838417130ad69fcc1c6f11b375
Status: Downloaded newer image for clitoapi/aclitoapi_webpage:latest
[docker.io/clitoapi/aclitoapi_webpage:latest](http://docker.io/clitoapi/aclitoapi_webpage:latest)
clitoapi@jarvis:~/docker_nginx$
clitoapi@jarvis:~/docker_nginx$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
clitoapi/aclitoapi_webpage latest 60b81ca16d3f 3 hours ago 40.7MB
hello-world latest b038788ddb22 2 months ago 9.14kB
We have the image. Lets run the container.
clitoapi@jarvis:~/docker_nginx$ sudo docker run -d -p 8080:80 60b81ca16d3f
a187aee82da210f2761779d3fa09f6c3c4251d8fc363ce5d2b261c5bf6fb53be
clitoapi@jarvis:~/docker_nginx$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a187aee82da2 60b81ca16d3f "/docker-entrypoint.…" 2 seconds ago Up 1 second 0.0.0.0:8080->80/tcp, :::8080->80/tcp distracted_hopper
2. Install minikube and kubctl
According to Kubernetes documentation “In order for kubectl to find and access a Kubernetes cluster, it needs a kubeconfig file, which is created automatically when you create a cluster using kube-up.sh or successfully deploy a Minikube cluster. By default, kubectl configuration is located at ~/.kube/config.”
First install minikube then install kubectl. You might have to do some troubleshooting during minikube and kubectl installation but don’t give up you will get there. I followed the steps documented here.
Steps to install minikube
https://minikube.sigs.k8s.io/docs/start/
Steps to install kubectl
https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/#install-using-other-package-management
After installation is successful, start minikube.
litoapi@jarvis:~$ minikube version
minikube version: v1.31.1
commit: fd3f3801765d093a485d255043149f92ec0a695f
clitoapi@jarvis:~$ minikube start
😄 minikube v1.31.1 on Ubuntu 22.04 (arm64)
✨ Using the docker driver based on existing profile
👍 Starting control plane node minikube in cluster minikube
🚜 Pulling base image ...
🔄 Restarting existing docker container for "minikube" ...
🧯 Docker is nearly out of disk space, which may cause deployments to fail! (91% of capacity). You can pass '--force' to skip this check.
💡 Suggestion:
Try one or more of the following to free up space on the device:
1. Run "docker system prune" to remove unused Docker data (optionally with "-a")
2. Increase the storage allocated to Docker for Desktop by clicking on:
Docker icon > Preferences > Resources > Disk Image Size
3. Run "minikube ssh -- docker system prune" if using the Docker container runtime
🍿 Related issue: https://github.com/kubernetes/minikube/issues/9024
🐳 Preparing Kubernetes v1.27.3 on Docker 24.0.4 ...
🔗 Configuring bridge CNI (Container Networking Interface) ...
🔎 Verifying Kubernetes components...
▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
▪ Using image docker.io/kubernetesui/dashboard:v2.7.0
▪ Using image docker.io/kubernetesui/metrics-scraper:v1.0.8
💡 Some dashboard features require the metrics-server addon. To enable all features please run:
minikube addons enable metrics-server
🌟 Enabled addons: default-storageclass, storage-provisioner, dashboard
🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
Check kubectl is installed properly.
clitoapi@jarvis:~$ kubectl version
WARNING: This version information is deprecated and will be replaced with the output from kubectl version --short. Use --output=yaml|json to get the full version.
Client Version: version.Info{Major:"1", Minor:"27", GitVersion:"v1.27.4", GitCommit:"fa3d7990104d7c1f16943a67f11b154b71f6a132", GitTreeState:"clean", BuildDate:"2023-07-19T12:20:54Z", GoVersion:"go1.20.6", Compiler:"gc", Platform:"linux/arm64"}
Kustomize Version: v5.0.1
Server Version: version.Info{Major:"1", Minor:"27", GitVersion:"v1.27.3", GitCommit:"25b4e43193bcda6c7328a6d147b1fb73a33f1598", GitTreeState:"clean", BuildDate:"2023-06-14T09:47:40Z", GoVersion:"go1.20.5", Compiler:"gc", Platform:"linux/arm64"}
Create deployment file and deploy the pod
We want to create a pod that uses image from our docker repository. Let’s do a dry-run to preview the config and then save the output to a yaml file.
clitoapi@jarvis:~/docker_nginx$ kubectl run nginx-webpage --image=clitoapi/aclitoapi_webpage --dry-run -o yaml
W0729 08:14:55.382908 27000 helpers.go:692] --dry-run is deprecated and can be replaced with --dry-run=client.
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: nginx-webpage
name: nginx-webpage
spec:
containers:
- image: clitoapi/aclitoapi_webpage
name: nginx-webpage
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
clitoapi@jarvis:~/docker_nginx$ kubectl run nginx-webpage --image=clitoapi/aclitoapi_webpage --dry-run -o yaml > clitoapiwebpage.yml
W0729 08:15:26.435366 27372 helpers.go:692] --dry-run is deprecated and can be replaced with --dry-run=client.
clitoapi@jarvis:~/docker_nginx$ cat clitoapiwebpage.yml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: nginx-webpage
name: nginx-webpage
spec:
containers:
- image: clitoapi/aclitoapi_webpage
name: nginx-webpage
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
clitoapi@jarvis:~/docker_nginx$
Let’s create our pod now.
clitoapi@jarvis:~/docker_nginx$ kubectl apply -f clitoapiwebpage.yml
pod/nginx-webpage created
clitoapi@jarvis:~/docker_nginx$ kubectl get all -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/nginx-webpage 1/1 Running 0 3m29s 10.244.0.11 minikube <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 13h <none>
And it is done. The pod is up. Now how do we test it?
Deploy another pod and test connectivity from the pod
Let’s deploy one more pod, this time we will use Ubuntu image and use it to test connectivity to our webserver.
clitoapi@jarvis:~/docker_nginx$ kubectl run ubuntu --image=ubuntu -- sleep 600
pod/ubuntu created
clitoapi@jarvis:~/docker_nginx$ kubectl get all -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/nginx-webpage 1/1 Running 0 5m48s 10.244.0.11 minikube <none> <none>
pod/ubuntu 1/1 Running 0 15s 10.244.0.12 minikube <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 13h <none>
We will have to login to Ubuntu and install curl. We will use these commands.
clitoapi@jarvis:~/docker_nginx$ kubectl exec -it ubuntu /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@ubuntu:/# apt-get update
root@ubuntu:/# apt-get install curl
Yay! our webserver is accessible.
root@ubuntu:/# curl http://10.244.0.11
<html>
<body>
<h1> "There's some good in this world, Mr. Frodo, and it's worth fighting for." </h1>
</body>
</html>
root@ubuntu:/#
Expose the pod and connect to the application from host Ubuntu server
We will try to connect to this application from our host Ubuntu server. We us the following methods,
a. Using port-forward
clitoapi@jarvis:~/docker_nginx$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-webpage 1/1 Running 0 3h4m
ubuntu 1/1 Running 8 (8m38s ago) 178m
clitoapi@jarvis:~/docker_nginx$ kubectl port-forward nginx-webpage 8888:80
Forwarding from 127.0.0.1:8888 -> 80
Forwarding from [::1]:8888 -> 80
Handling connection for 8888
clitoapi@jarvis:~/docker_nginx$
Now let’s test connectivity using curl. It is successful.
clitoapi@jarvis:~$ curl -v http://localhost:8888
* Trying 127.0.0.1:8888...
* Connected to localhost (127.0.0.1) port 8888 (#0)
> GET / HTTP/1.1
> Host: localhost:8888
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.24.0
< Date: Sat, 29 Jul 2023 11:21:49 GMT
< Content-Type: text/html
< Content-Length: 118
< Last-Modified: Fri, 28 Jul 2023 14:57:40 GMT
< Connection: keep-alive
< ETag: "64c3d764-76"
< Accept-Ranges: bytes
<
<html>
<body>
<h1> "There's some good in this world, Mr. Frodo, and it's worth fighting for." </h1>
</body>
</html>
* Connection #0 to host localhost left intact
b. Create NodePort service and expose on port 80
clitoapi@jarvis:~/docker_nginx$ kubectl expose pod nginx-webpage --name=nginx-svc --type=NodePort --port=80
service/nginx-svc exposed
clitoapi@jarvis:~/docker_nginx$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 16h
nginx-svc NodePort 10.98.39.170 <none> 80:31475/TCP 25s
clitoapi@jarvis:~/docker_nginx$ minikube ip
192.168.49.2
clitoapi@jarvis:~/docker_nginx$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 16h
nginx-svc NodePort 10.98.39.170 <none> 80:31475/TCP 5m37s
clitoapi@jarvis:~/docker_nginx$
clitoapi@jarvis:~/docker_nginx$ minikube service nginx-svc --url
http://192.168.49.2:31475
clitoapi@jarvis:~/docker_nginx$
Let’s test again. GET request is successful.
clitoapi@jarvis:~$ curl -v http://192.168.49.2:31475
* Trying 192.168.49.2:31475...
* Connected to 192.168.49.2 (192.168.49.2) port 31475 (#0)
> GET / HTTP/1.1
> Host: 192.168.49.2:31475
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.24.0
< Date: Sat, 29 Jul 2023 11:55:14 GMT
< Content-Type: text/html
< Content-Length: 118
< Last-Modified: Fri, 28 Jul 2023 14:57:40 GMT
< Connection: keep-alive
< ETag: "64c3d764-76"
< Accept-Ranges: bytes
<
<html>
<body>
<h1> "There's some good in this world, Mr. Frodo, and it's worth fighting for." </h1>
</body>
</html>
* Connection #0 to host 192.168.49.2 left intact
clitoapi@jarvis:~$