raulo5

Docker and Kubernetes

Based on Docker and Kubernetes - Full Course for Beginners. More that 4 hours long. All credits to freeCodeCamp, what I have below are just my own notes after watching the video.


Docker

What is Docker?

  • A tool for running applications in isolated environments (containers)
  • The app run always in same environment
  • Similar to a VM but using less resources
  • Currently a standard for application deployment

Containers vs VMs

VM
  • Abstraction at the hardware / OS Level
  • Each VM runs not a whole copy of the OS
Container
  • Abstraction at the Application level
  • Packages Code + Dependencies
  • Multiple containers can run in the same machine sharing OS kernel
  • Each container is isolated in terms of running processes and user spaces

alt text Image from Docker and Kubernetes - Full Course for Beginners


Installing and running Docker Engine

Docker Engine is what you need to run docker in a development environment. It includes at least:

  • A Docker server running a daemon process, dockerd
  • A CLI, docker

Note that the installer for Mac also includes:

  • Docker Compose: A tool to define via a YAML file a set of services and containers to run with a single command.
  • Notary: A tool and CLI to manage trusted images.
  • Kubernetes: A system to manage (deploy, maintenance, scaling) containerized applications in multiple hosts.
  • Credential Helper: A tool to securely manage Docke credentials in native storage.

To install Docker go to https://docs.docker.com/engine/install/ and download the desired OS version. You may need to register, it's free.

To verify that Docker is running properly, try getting the version:

$ docker --version
Docker version 20.10.0, build 7287ab3

Images vs Containers

Image

  • Template for creating containers
  • Can be a snapshot, so you can version them
  • Complete environment to run an App or Service

Container

  • Running instance of an Image

You can see a list of official images in Docker Hub


Basic commands to pull images and run containers

Pull nginx image:

$ docker pull nginx:latest

Checking list of currently downloaded images:

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED      SIZE
nginx        latest    ae2feff98a0c   4 days ago   133MB

Running a container from nginx image:

$ docker run nginx:latest

Note that the above container is running, so you can open a new terminal to check for running containers:

$ docker container ls
CONTAINER ID   IMAGE          COMMAND                  CREATED              STATUS              PORTS     NAMES
46af091dc5a3   nginx:latest   "/docker-entrypoint.…"   About a minute ago   Up About a minute   80/tcp    elegant_jemison

To run in detached mode use -d flag, then you can see containers running (now using ps command), also here we are mapping host port 8080 into container port 80 to expose it to the host:

$ docker run -d -p 8080:80 nginx:latest
122e8e3524cf8f4295d26ac6dc9bbd408c4547a93f33a687ae9d8f9e1fcab92a
$ docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED              STATUS              PORTS     NAMES
122e8e3524cf   nginx:latest   "/docker-entrypoint.…"   About a minute ago   Up About a minute   80/tcp    pensive_cray

Now this container is running in detached mode and exposing port 80 so you can now open http://localhost:8080 and see nginx there:

alt text

Now you can stop the running container with docker stop and container ID or container name:

$ docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                  NAMES
2aeb57320e8c   nginx:latest   "/docker-entrypoint.…"   5 minutes ago   Up 5 minutes   0.0.0.0:8080->80/tcp   bold_burnell
$ docker stop 2aeb57320e8c
2aeb57320e8c
$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

You can stop a container, and start it again:


$ docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS     NAMES
3e3b5d7cd7a9   nginx     "/docker-entrypoint.…"   4 seconds ago   Up 3 seconds   80/tcp    crazy_fermi
$ docker stop crazy_fermi
crazy_fermi
$ docker ps              
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
$ docker start crazy_fermi
crazy_fermi
$ docker ps               
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS         PORTS     NAMES
3e3b5d7cd7a9   nginx     "/docker-entrypoint.…"   38 seconds ago   Up 2 seconds   80/tcp    crazy_fermi

As $ docker ps will by default list running containers, you can also list all (including not running) containers adding --all:

$ docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS     NAMES
3e3b5d7cd7a9   nginx     "/docker-entrypoint.…"   2 minutes ago   Up 2 minutes   80/tcp    crazy_fermi
$ docker stop crazy_fermi
crazy_fermi
$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
$ docker ps --all
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS                     PORTS     NAMES
3e3b5d7cd7a9   nginx          "/docker-entrypoint.…"   3 minutes ago   Exited (0) 9 seconds ago             crazy_fermi
2aeb57320e8c   nginx:latest   "/docker-entrypoint.…"   3 hours ago     Exited (0) 3 hours ago               bold_burnell
122e8e3524cf   nginx:latest   "/docker-entrypoint.…"   3 hours ago     Exited (0) 3 hours ago               pensive_cray
46af091dc5a3   nginx:latest   "/docker-entrypoint.…"   4 hours ago     Exited (0) 3 hours ago               elegant_jemison

To remove a container (by default works for stopped containers) just call rm passing container name or id:

$ docker rm crazy_fermi 
crazy_fermi
$ docker ps --all
CONTAINER ID   IMAGE          COMMAND                  CREATED       STATUS                   PORTS     NAMES
2aeb57320e8c   nginx:latest   "/docker-entrypoint.…"   3 hours ago   Exited (0) 3 hours ago             bold_burnell
122e8e3524cf   nginx:latest   "/docker-entrypoint.…"   4 hours ago   Exited (0) 3 hours ago             pensive_cray
46af091dc5a3   nginx:latest   "/docker-entrypoint.…"   4 hours ago   Exited (0) 4 hours ago             elegant_jemison

To remove all the containers you can do:

$ docker rm -f $(docker ps -aq)
2aeb57320e8c
122e8e3524cf
46af091dc5a3

To set a custom name for a container add --name when running it:

$ docker run -d --name website nginx:latest
38923b247a24949c9f428a5b7ec6575f5c12c5a4fc2bc55bd1a6b60f57735290
$ docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS     NAMES
38923b247a24   nginx:latest   "/docker-entrypoint.…"   4 seconds ago   Up 3 seconds   80/tcp    website

To run interactive bash in nginx (as a running container named website) run this:

$ docker exec -it website bash
root@d1c3d28febb8:/# 

Volumes

Allows sharing files and folders between containers and host, and also between containers.

Sharing data between host and container

Create folder ~/Desktop/website and add there a simple index.html file containing:

<h1> Hello Docker and Volumes </h1>

Now will start an nginx container named website exposing por 80 from container into 8000 from host and sharing host folder ~/Desktop/website into container folder /usr/share/nginx/html:

$ docker run --name website -v ~/Desktop/website:/usr/share/nginx/html -d -p 8000:80 nginx

Now opening http://localhost:8000 should show:

alt text

You should also be able to write files in /usr/share/nginx/html container folder and see that reflected in ~/Desktop/website host folder, since it was mounted as a shared volume:

$ ls ~/Desktop/website 
index.html	
$ docker exec -it website bash
root@d1c3d28febb8:/# cd /usr/share/nginx/html/
root@d1c3d28febb8:/usr/share/nginx/html# touch sample.txt
root@d1c3d28febb8:/usr/share/nginx/html# exit
exit
$ ls ~/Desktop/website 
index.html	sample.txt

Sharing data between containers

Now, assuming we have already running website container running nginx with mounted volume ~/Desktop/website:/usr/share/nginx/html, we can now run a second container (website-copy) mounting volume from website container:

$ docker run --name website-copy --volumes-from website -d -p 8001:80 nginx

Now opening http://localhost:8000 and also http://localhost:8001 should show the exact same content, which is the content we have in our mounted host volume (in both website and website-copy containers) from host folder ~/Desktop/website.


Dockerfile and custom images

A dockerfile it's a file (with a series of steps) that allows you to create a docker image that you can use to run containers.

Now will create a Dockerfile containing what is in ~/Desktop/website folder, to create an image so we can run our simple website without having to mount any volume.

Here is the content for the Dockerfile (should be created inside ~/Desktop/website folder):

FROM nginx:latest
ADD . /usr/share/nginx/html

Note that Dockerfile should be in root of the folder containing the project you want to package as an image

Now it's time to build the actual image:

$ cd ~/Desktop/website
$ docker build --tag website-image:latest .
Sending build context to Docker daemon  3.584kB
Step 1/2 : FROM nginx:latest
 ---> ae2feff98a0c
Step 2/2 : ADD . /usr/share/nginx/html
 ---> 2d93b6156c91
Successfully built 2d93b6156c91
Successfully tagged website-image:latest
$ ls
Dockerfile	index.html	sample.txt
$ docker image ls
REPOSITORY      TAG       IMAGE ID       CREATED              SIZE
website-image   latest    2d93b6156c91   About a minute ago   133MB
nginx           latest    ae2feff98a0c   6 days ago           133MB

Now run a container from our created image (before will remove current website container):

$ docker rm -f website
website
$ docker run --name website -p 8000:80 -d website-image:latest
b53e675904223ffb520cd01689f83971d159970c4933c9bf38d948f53f75786d
$ docker ps
CONTAINER ID   IMAGE           COMMAND                  CREATED         STATUS         PORTS                  NAMES
b53e67590422   website-image   "/docker-entrypoint.…"   6 seconds ago   Up 6 seconds   0.0.0.0:8000->80/tcp   website

And now you should be able to browse to http://localhost:8000 and see the website up and running. Note that we are not mounting any volume since our image contains all the necessary files to run our dead simple website.


Dockerizing a simple NodeJS/ExpressJS app

Download and install NodeJS and then install ExpressJS, just press enter when asked for input from ExpressJS installer (so you can set the default values):

$ cd ~/Desktop
$ mkdir user-service-api
$ cd user-service-api
$ npm init
$ npm install --save express

This will create a ~/Desktop/user-service-api/package.json file and will install ExpressJS in node_modules folder.

Now will create a simple app in a file named app.js which will return a simple JSON array:

const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.json([{
  	name: "Bob",
  	email: "[email protected]"
  }])
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

Now will create a ~/Desktop/user-service-api/Dockerfile (based on Node official base image using tag lts-alpine since it's based on Apline which is a small linux distro) to build an image out of our simple API:

Note that WORKDIR will first create the provided folder (in case it doesn't exist yet) and then set this as the base folder for the upcoming commands in this Dockerfile.

FROM node:lts-alpine
WORKDIR /app
ADD package*.json ./
RUN npm install
ADD . .
CMD node app.js

Add also a ~/Desktop/user-service-api/.dockerignore file with the following content (to avoid packaging files and folders not needed):

node_modules
Dockerfile
.git

And now will create the image for this NodeJS app (this may take some time):

$ docker build --tag user-service-api-image:latest .

And now will run a container from our new image:

$ docker run --name user-api -d -p 3000:3000 user-service-api-image:latest

And now you should be able to browse to http://localhost:3000 and see the actual JSON:

alt text


Docker Registry

Stores public/private Docker images that can be pulled and used in CI/CD pipelines. Some of the most well known registries are:

Creating a Docker Hub repository and pushing our images

In DockerHub you can create private and public repositories. A repository can contain many image tags.

Make sure you are signed in (browse https://hub.docker.com), and follow these directions:

  1. Open https://hub.docker.com/repositories
  2. Click Create Repository
  3. Enter Repository Name and Description (in this example I used user-service-api-image as name)
  4. Click Create

Then tag your local image user-service-api-image as suggested after creating it, which in this case is raulbajales/user-service-api-image:tagname(wheretagnameis the desired tag, orlatest`), and push it.

$ docker tag user-service-api-image:latest  raulbajales/user-service-api-image:latest
$ docker login
$ docker push raulbajales/user-service-api-image:latest

After this you should see your image in https://hub.docker.com/repository/docker/raulbajales/user-service-api-image. Note that this single repository can hold many tags for this same image.

You can now pull the recently pushed image (will do this here removing first the current raulbajales/user-service-api-image:latest image:

$ docker image rm -f raulbajales/user-service-api-image:latest
$ docker pull raulbajales/user-service-api-image:latest
$ docker image ls
REPOSITORY                           TAG          IMAGE ID       CREATED          SIZE
raulbajales/user-service-api-image   latest       7f358c8f2edf   31 minutes ago   120MB

Logs

Let's start a container and check for some logs (this will actually show any log from the app running in the container out to stdout or console):

$ docker run --name user-service -d -p 3000:3000 raulbajales/user-service-api-image
98f3695407eee50c21f58ddb572f469da88ec0abb2ac2b9e3a40c7ca76f3cc94
$ docker ps
CONTAINER ID   IMAGE                                COMMAND                  CREATED          STATUS          PORTS                    NAMES
98f3695407ee   raulbajales/user-service-api-image   "docker-entrypoint.s…"   44 seconds ago   Up 43 seconds   0.0.0.0:3000->3000/tcp   user-service
$ docker logs -f 98f3695407ee                                                                     
Example app listening at http://localhost:3000

(Note that the -f flag will keep showing logs as they happen.)


Get into the container shell

  1. Use inspect the running container to know the shell command (look for the Cmd section):
$ docker inspect user-service | grep Cmd -n2
163-                "YARN_VERSION=1.22.5"
164-            ],
165:            "Cmd": [
166-                "/bin/sh",
167-                "-c",
  1. Use exec command to execute shell and get into the running container:
$ docker exec -it user-service /bin/sh
/app #

Kubernetes

Also known as K8s, It's a container orchestration tool. It ensures:

  1. High availability
  2. Scalability
  3. Resilience

Basic architecture

alt text Image from Docker and Kubernetes - Full Course for Beginners

  • Each worker node has many containers, here is where the application is running. Each worker node contains also a kubelet which is a node agent able to communicate with other nodes, for management purposes.
  • Master node runs Kubernetes processes to run and mange the cluster.
  • The whole K8s cluster (master and worker nodes) runs in a Virtual Network.

Master is also runs containers (from Kubernetes), including:

  • API Server: Entry point to the K8s cluster, clients are: UI, CLI and API
  • Controller Manager: Keeps track of all events related to the cluster
  • Scheduler: Schedules containers based on load and availability.
  • etcd key/value storage: Keep track of cluster state / snapshots.

Main concepts

  • Node: Either a Physical Server or a Virtual Machine part of a K8s Cluster, usually nodes are replicated.
  • Pod: Abstraction of a Container in K8s. Runs our Application. Each Pod (not the Container) has his own IP inside the K8s Virtual Network, and is able to communicate with other Pods via this internal IP. Also, since Pods are ephemeral, when recreated they may get a new IP.
  • Service: A Service with permanent IP address attached to a Pod. Pod IP and Service (attached to a Pod) IP are different. A Service can be Internal (accessible inside the K8s Cluster Virtual Network) or External (therefore exposing the Pod). A Service is also a Load Balancer, so when a Pod is down the Service can redirect requests to a replica node where the Pod with the same App is running,
  • Ingress: Sets up rules to route domain names and ports to internal K8s Services, to expose services in a node.
  • ConfigMap: Manages Application Configuration.
  • Secret: Manages Application Secrets (base64 encoded).
  • Volumes: Attaches a local or remote storage to a Pod. Remote means outside the K8s cluster (even in the cloud).
  • Deployment: Abstraction over Pods for Stateless Applications. It's a blueprint for a whole Application, where you can define number of replicas and also how to scale up / down. Note that State (DB data) cannot be replicated via Deployment.
  • Stateful Set: Abstraction over Pods for Stateful Applications (or DBs) (MySQL, ElasticSearch, etc.) It's not easy to configure, so a usual approach is working with Deployments and hosting/managing DBs outside the K8s Cluster.

Minikube and Kubectl

Minikube

It's a 1 node K8s Cluster running both Master and Worker processes, with Docker Container pre-installed, running in a VirtualBox or another hypervisor (like Hyperkit). Useful for Test / Local environments.

minikube (CLI tool) is used to manage the Minikube Cluster.

To intall Minikube browse https://minikube.sigs.k8s.io/docs/start/.

To install Hyperkit browse https://minikube.sigs.k8s.io/docs/drivers/hyperkit/.

Kubectl

It's a Command Line Tool to interact with the Minikube K8s Cluster. Kubectl interacts with K8s API Server running in Master process.

kubectl (CLI tool) is used to configure the Minikube Cluster.

To install Kubectl browse https://kubernetes.io/es/docs/tasks/tools/install-kubectl/.

Start Minikube using Hyperkit:

$ minikube start --vm-driver=hyperkit

Get status of the running K8s cluster:

$ kubectl get nodes
NAME       STATUS   ROLES                  AGE   VERSION
minikube   Ready    control-plane,master   25s   v1.20.0

Get status of K8s components:

$ minikube status
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured
timeToStop: Nonexistent

Get status of services in minikube:

$ kubectl get services
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   24m

Deployment: Create and debug pods

Create and nginx deployment:

$ kubectl create deployment sample-nginx-deployment --image=nginx
deployment.apps/sample-nginx-deployment created

Get status of deployment:

$ kubectl get deployment
NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
sample-nginx-deployment   1/1     1            1           60s

Get status of pod:

$ kubectl get pod
NAME                                       READY   STATUS    RESTARTS   AGE
sample-nginx-deployment-7c9c4555f9-5bvrs   1/1     Running   0          2m12s

Get logs of running pod:

$ kubectl logs sample-nginx-deployment-7c9c4555f9-5bvrs
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Configuration complete; ready for start up

Describe pod:

$ kubectl describe pod sample-nginx-deployment-7c9c4555f9-5bvrs
Name:         sample-nginx-deployment-7c9c4555f9-5bvrs
Namespace:    default
Priority:     0
Node:         minikube/192.168.64.2
Start Time:   Mon, 28 Dec 2020 12:12:37 -0300
Labels:       app=sample-nginx-deployment
              pod-template-hash=7c9c4555f9
Annotations:  <none>
Status:       Running
IP:           172.17.0.3
IPs:
  IP:           172.17.0.3
Controlled By:  ReplicaSet/sample-nginx-deployment-7c9c4555f9
Containers:
  nginx:
    Container ID:   docker://e515faaa09c1543c9d614e6d6f87df45f76cc12e18fe2886a493676b7d3f85b8
    Image:          nginx
    Image ID:       docker-pullable://nginx@sha256:4cf620a5c81390ee209398ecc18e5fb9dd0f5155cd82adcbae532fec94006fb9
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Mon, 28 Dec 2020 12:13:17 -0300
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-qtp72 (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  default-token-qtp72:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-qtp72
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                 node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age    From               Message
  ----    ------     ----   ----               -------
  Normal  Scheduled  7m24s  default-scheduler  Successfully assigned default/sample-nginx-deployment-7c9c4555f9-5bvrs to minikube
  Normal  Pulling    7m22s  kubelet            Pulling image "nginx"
  Normal  Pulled     6m44s  kubelet            Successfully pulled image "nginx" in 37.939644592s
  Normal  Created    6m44s  kubelet            Created container nginx
  Normal  Started    6m44s  kubelet            Started container nginx

Get status of nodes / replicas:

$ kubectl get replicaset
NAME                                 DESIRED   CURRENT   READY   AGE
sample-nginx-deployment-7c9c4555f9   1         1         1       15m

Get into the terminal of a running pod:

$ kubectl get pod
NAME                                       READY   STATUS    RESTARTS   AGE
sample-nginx-deployment-7c9c4555f9-5bvrs   1/1     Running   0          11m
$ kubectl exec -it sample-nginx-deployment-7c9c4555f9-5bvrs -- bin/bash
root@sample-nginx-deployment-7c9c4555f9-5bvrs:/#

Delete a deployment:

$ kubectl delete deployment sample-nginx-deployment
deployment.apps "sample-nginx-deployment" deleted

Deployment Configuration file

Create a simple Deployment Configuration File and save it as nginx-deployment.yaml containing:

apiVersion: apps/v1
kind: Deployment
metadata:
	name: nginx-deployment
	labels: 
		nginx: nginx
# Specification for the Deployment:
spec:
	replicas: 1
	selector: 
		matchLabels:
			app: nginx
	# Blueprint for the Pods:
	template:
		metadata:
			labels:
				app: nginx
		# Specification for the Pods:
		spec:
			containers:
			- name: nginx
			  image: nginx:1.16
			  ports:
			  - containerPort: 80			

Apply a Deployment Configuration file:

$ kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment created
$ kubectl get deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   0/1     1            0           95s
$ kubectl get pod
NAME                                READY   STATUS              RESTARTS   AGE
nginx-deployment-644599b9c9-xb8ph   0/1     ContainerCreating   0          13s

Since K8s knows when to create a deployment or update it, you can now change / update configuration of this deployment by making changes in nginx-deployment.yaml and the running again $ kubectl apply -f nginx-deployment.yaml.

Here first updated nginx-deployment.yaml to set replicas: 2 and then:

$ kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment configured
$ kubectl get deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   2/2     2            2           9m26s
$ kubectl get pod
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-644599b9c9-j2h9z   1/1     Running   0          2m1s
nginx-deployment-644599b9c9-xb8ph   1/1     Running   0          9m40s

Create a sample Deployment with MongoDB + MongoExpress (UI)

Start with a fresh and empty Minikube cluster

$ minikube start --vm-driver=hyperkit
$ kubectl get all
NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   5h22m

Create a Deployment file for mongoDB, save it as mongo.yaml, containing:

apiVersion: apps/v1
kind: Deployment
metadata:
	name: mongodb-deployment
	labels: 
		app: mongodb
spec:
	replicas: 1
	selector:
		matchLabels:
			app: mongodb
	template:
		metadata:
			labels:
				app: mongodb
		spec:
			containers:
			- name: mongodb
			  image: mongo
			  ports:
			  - containerPort: 27017
			  env:
			  - name: MONGO_INITDB_ROOT_USERNAME
			    value:
			  - name: MONGO_INITDB_ROOT_PASSWORD
			    value:	

Note that spec on how to use mongo image (like default port number, or env vars to set username and password) are explained in mongo image page at https://hub.docker.com/_/mongo Also note that the actual values for username/password ase empty for now, as this deployment file may be checked into a source code repository, it should not contain any sensitive data.

Creating secret values

Now will use Secret K8s service to create secret for username/password to access mongodb.

First create a mongo-secret.yaml file containing:

apiVersion: v1
kind: Secret
metadata:
	name: mongodb-secret
type: Opaque
# Custom key/value pairs section
# For secrets, value must be be base64 encoded
data:
	mongo-root-username: c29tZS11c2VybmFtZQ==
	mongo-root-password: c29tZS1wYXNzd29yZA==

To get base54 encoded text, you can do:

$ echo -n 'some-username' | base64
c29tZS11c2VybmFtZQ==
$ echo -n 'some-password' | base64
c29tZS1wYXNzd29yZA==

Now apply to get secrets created:

$ kubectl apply -f mongo-secret.yaml
secret/mongodv-secret created
$ kubectl get secret
NAME                  TYPE                                  DATA   AGE
default-token-qtp72   kubernetes.io/service-account-token   3      5h56m
mongodv-secret        Opaque                                2      46s

Now need to edit mongo.yaml to finish setting secret values for username/password:

...
			env:
			- name: MONGO_INITDB_ROOT_USERNAME
			  valueFrom:
				secretKeyRef:
					name: mongodb-secret
					key: mongo-root-username
			
			- name: MONGO_INITDB_ROOT_PASSWORD
			  valueFrom:
				secretKeyRef:
					name: mongodb-secret
					key: mongo-root-password
...

And now apply Deployment Configuration file:

$ kbectl apply -f mongo.yaml
deployment.apps/mongodb-deployment created
$ kubectl get all
NAME                                     READY   STATUS              RESTARTS   AGE
pod/mongodb-deployment-8f6675bc5-xq82b   0/1     ContainerCreating   0          16s

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   6h8m

NAME                                 READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/mongodb-deployment   0/1     1            0           16s

NAME                                           DESIRED   CURRENT   READY   AGE
replicaset.apps/mongodb-deployment-8f6675bc5   1         1         0       16s

Note that you can see above the pod, deployment and replica set.

Now will create a Service Configuration file to create a Service to be attached to the Pod defined in previous Deployment Configuration file. The file could be saved as mongo-service.yaml containing:


apiVersion: v1
kind: Service
metadata:
  name: mongodb-service
spec:
  selector:
    # This selector should match the one defined for the Pod
    # in previous Deployment Configuration file (spec/template/metadata/labels)
    app: mongodb
  # ports/port: The exposed port for this service
  # ports/targetPort: The port for the selected Pod
  ports:
    - protocol: TCP
      port: 27017
      targetPort: 27017

Now apply mongo-service.yaml to create the service:

$ kubectl apply -f mongo-service.yaml
service/mongodb-service created
$ kubectl get service
NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)     AGE
kubernetes        ClusterIP   10.96.0.1      <none>        443/TCP     6h41m
mongodb-service   ClusterIP   10.111.18.29   <none>        27017/TCP   91s

Now create a Deployment Configuration file for mongodb-express. Save it as mongo-express.yaml containing:


apiVersion: apps/v1
kind: Deployment
metadata: 
  name: mongo-express
spec:
  replicas: 1
  selector: 
    matchLabels:
      app: mongo-express
  template:
    metadata:
      labels:
        app: mongo-express
    spec:
      containers:
      - name: mongo-express
        image: mongo-express
        ports:
        - containerPort: 8081
        env:
        - name: ME_CONFIG_MONGODB_ADMINUSERNAME
          valueFrom:
            secretKeyRef:
               name: mongodb-secret
               key: mongo-root-username
        - name: ME_CONFIG_MONGODB_ADMINPASSWORD
          valueFrom:
            secretKeyRef:
               name: mongodb-secret
               key: mongo-root-password
        - name: ME_CONFIG_MONGODB_SERVER

Note that the specific env vars to set are explained in mongo-express image page here https://hub.docker.com/_/mongo-express

Also note that the above file is incomplete, we will create a ConfigMap to store mongodb server address, after that will finish editing above file (to set the value for ME_CONFIG_MONGODB_SERVER).

Now will create a ConfigMap file with name mongo-configmap.yaml containing:

apiVersion: v1
kind: ConfigMap
metadata: 
	name: mongodb-configmap
# Key/value pairs goes here:
data:
	database_url: mongodb-service

Note that we can get the internal URL of a service by just using the service name (as in database_url: mongodb-service).

Now will apply the ConfigMap file:

$ kubectl apply -f mongo-configmap.yaml
configmap/mongodb-configmap created

And now need to go back to the unfinished file mongo-express.yaml and edit it to set the value for ME_CONFIG_MONGODB_SERVER to reference the one in above applied ConfigMap:

...
          valueFrom:
            configMapKeyRef:
              name: mongodb-configmap
              key: database_url
...

And apply it to create mongo-express deployment:

$ kubectl apply -f mongo-express.yaml
deployment.apps/mongo-express created
$ kubectl get pod
NAME                                 READY   STATUS    RESTARTS   AGE
mongo-express-78fcf796b8-8zshm       1/1     Running   0          88s
mongodb-deployment-8f6675bc5-xq82b   1/1     Running   0          17h

To check if mongo-express is up and connected to mongodb check logs:

$ kubectl logs mongo-express-78fcf796b8-8zshm
Waiting for mongodb-service:27017...
Welcome to mongo-express
------------------------


Mongo Express server listening at http://0.0.0.0:8081
Server is open to allow connections from anyone (0.0.0.0)
basicAuth credentials are "admin:pass", it is recommended you change this in your config.js!
Database connected
Admin Database connected

We need now an external service to access mongo-express from a browser.

To create an external service, just need to add type: LoadBalancer and nodePort (must be a value between 30000 and 32767) to a new Service configuration file, as shown below (save it as mongo-express-service.yaml):

apiVersion: v1
kind: Service
metadata:
	name: mongo-express-service
spec:
	selector:
		app: mongo-express
	type: LoadBalancer
	# port: The internal port for this service
	# targetPort: The internal port the target pod (from mongo-express.yaml Deployment config file)
	# nodePort: The external port for this service 
	ports:
		- protocol: TCP
		  port: 8081
		  targetPort: 8081
		  nodePort: 30000 

And now apply it:

$ kubectl apply -f mongo-express-service.yaml
service/mongo-express-service created

After this, the services should appear like this:

$ kubectl get service
NAME                    TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
kubernetes              ClusterIP      10.96.0.1      <none>        443/TCP          23h
mongo-express-service   LoadBalancer   10.104.68.50   <pending>     8081:30000/TCP   2m49s
mongodb-service         ClusterIP      10.111.18.29   <none>        27017/TCP        17h

Note that mongo-express-service has TYPE: LoadBalancer since it is accessible from outside the K8s cluster, but still the EXTERNAL-IP is <pending> so we need an additional step in minikube to get this external IP, this step will actually open the browser showing mongo-express-service:

$ minikube service mongo-express-service
|-----------|-----------------------|-------------|---------------------------|
| NAMESPACE |         NAME          | TARGET PORT |            URL            |
|-----------|-----------------------|-------------|---------------------------|
| default   | mongo-express-service |        8081 | http://192.168.64.2:30000 |
|-----------|-----------------------|-------------|---------------------------|
🎉  Opening service default/mongo-express-service in default browser...

And this should be shown:

alt text

Whenever we interact with mongo-express UI (let's say, by creating a DB) the request goes this way (starting from the client browser):

alt text

Image from freeCodeCamp video

Recap about configuration files

These are the basic set of files (yaml) you need to apply ($ kubectl apply -f ...) to setup a K8s cluster:

  • Deployment config files: kind: Deployment. Defines deployment spec (replicas, etc.) and template for the pods (image, port, env vars)
  • Service config files: kind: Service. Defines protocol/port for a service attached to a pod
  • Secret config files: kind: Secret. Defines secure key/value pairs for app related credentials, referenced in Deployment config files.
  • ConfigMap service files: kind: ConfigMap. Defines app related configuration (key/value pairs), referenced in Deployment config files.