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
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:
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:
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:
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:
- Open
https://hub.docker.com/repositories
- Click Create Repository
- Enter Repository Name and Description (in this example I used
user-service-api-image
as name) - 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(where
tagnameis the desired tag, or
latest`), 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
- 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",
- 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:
- High availability
- Scalability
- Resilience
Basic architecture
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:
Whenever we interact with mongo-express UI (let's say, by creating a DB) the request goes this way (starting from the client browser):
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.