Kubernetes Deployments with ConfigMaps
Deploy 4 NGINX servers with custom HTML
Prerequisites:
- Basic Linux commands
- Basic knowledge of Docker CLI commands
- Basic knowledge of Kubernetes
- Docker Desktop installed
- Minikube installed in CLI
Objectives:
- Create a single-node cluster using minikube
- Create a Kubernetes deployment that contains 2 pods running the NGINX image
- Include a ConfigMap that points to a custom index.html page that contains the line “This is Deployment One”
- Create a second Kubernetes deployment that contains 2 pods running the NGINX image
- Include a ConfigMap that points to a custom index.html page that contains the line “This is Deployment Two”
- Create a service that points to both deployments
- Access both deployments (should be able to use same IP address and port number)
- Use the curl command to validate that you can see the index.html pages from both Deployment 1 and Deployment 2
This tutorial builds on my last two tutorials Creating a Kubernetes Cluster Using Docker Desktop and Create a 3 Node Kubernetes Cluster with Minikube. In these tutorials you can find detailed information about the basic architecture of Kubernetes including details of the control plane and components of a Kubernetes cluster. If you are not familiar with these terms, please review these first.
In this tutorial, we will create a single node cluster using minikube, create config maps with custom HTML, create two NGINX deployments with a total of four replicas, and create a load balancer service so the deployments can reach an external IP address. Let’s get started!
Setting Up A Kubernetes Environment
To get started with creating a cluster, we first need to set up our environment, which involves installing Docker Desktop and minikube. If these are not already installed, follow the tutorials below:
Install Docker Desktop
Install Docker Desktop on Linux
Install Docker Desktop on Windows
Enable Kubernetes:
Enable Kubernetes on Docker Desktop
Install Minikube:
Install minikube on Linux, Mac, or Windows
Start a Single-Node Cluster with Minikube
Once you have your environment set up, use the following command to create a single-node cluster using minikube:
minikube start
This will take a few minutes to create, so go get a sip of water, do a few pushups, and come back.
To see the node that was created, use the following command:
kubectl get nodes
You can also see your cluster in your Docker Desktop dashboard.
Create a Deployment with 2 pods Running NGINX
As you probably already know, to deploy pods on a Kubernetes cluster, you need to create a YAML file. For details on how to create a Kubernetes YAML file, go here. To get started creating the file, change into the directory you want to work in and either Vim, Vi, or Nano to make a new file, or use your favorite code editor. I will be using VS Code.
The first file we will create is the NGINX deployment file and we can give it the name deployment1.yml
. In the YAML below, you can see that the kind is Deployment, we’ve given the deployment a name of “nginx1”, and we are creating 2 replicas or pods. The template defines our containers and tells Kubernetes which image to run, on which port, and we are volume mounting our custom NGINX html file. We are referencing the name of the config map that we will create next.
# Deployment with 2 pods running NGINX image
# Include a ConfigMap that points to a custom index.html page that contains
# the line "This is Deployment One"
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx1
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: nginx-index-file-1
mountPath: /usr/share/nginx/html
volumes:
- name: nginx-index-file-1
configMap:
name: html-configmap1
items:
- key: index.html
path: index.html
Include a ConfigMap that Points to a Custom index.html Page
To meet the objectives above, we want our first NGINX landing page to have a custom greeting of “This is Deployment One” and our second NGINX landing page to have a custom greeting of “This is Deployment Two”. We can do this using a ConfigMap. A ConfigMap is a type of API object that is used to store non-confidential information in the form of key:value pairs. We want to keep our source code (in this case our custom index.html file) separate from our deployments. To do this, we will create a ConfigMap in a separate file. Create a new file in the same directory as your Deployment file, and name it html-configmap1.yml
. The ConfigMap kind uses a different API version, so notice this has been changed to v1
. Under data our key is index.html
and our value is the actual HTML we want. You can see the custom greeting of “Welcome” and “This is Deployment One”.
apiVersion: v1
kind: ConfigMap
metadata:
name: html-configmap1
data:
index.html: |
<html>
<h1>Welcome</h1>
</br>
<h1>This is Deployment One</h1>
</html
Make sure that your reference to the config map in your Deployment file is the exact same as the name in your ConfigMap metadata, and that the key in your Deployment file is the same as your data in your ConfigMap file.
Now that you have your deployment1.yml
file and your html-configmap1.yml
file, we need to repeat the above steps for Deployment 2. The files will be very similar, but we can change the name of the deployment and create a separate config map file for Deployment 2.
Here is the second deployment file. Note the name of the deployment has changed to “nginx2” and the name of the volumeMounts and configMap have changed slightly. The labels under template metadata are the same for both deployment files.
# Deployment with 2 pods running NGINX image
# Include a ConfigMap that points to a custom index.html page that contains the
# line "This is Deployment Two"
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx2
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: nginx-index-file-2
mountPath: /usr/share/nginx/html/
volumes:
- name: nginx-index-file-2
configMap:
name: html-configmap2
items:
- key: index.html
path: index.html
Here is the second ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: html-configmap2
namespace: default
data:
index.html: |
<html>
<h1>Welcome</h1>
</br>
<h1>This is Deployment Two</h1>
</html
At this point, you should have four YAML files: deployment1.yml, deployment2.yml, html-configmap1.yml, and html-configmap2.yml.
Once all of these are created, move on to the next section.
Create Two ConfigMaps
We have created our two config map files, but now we need to apply them to our Kubernetes cluster. In the CLI, use the following command:
kubectl apply -f html-configmap1.yml
Repeat this step for the second config file:
kubectl apply -f html-configmap2.yml
Deploy the Pods
Earlier we created our deployment YAML files for Deployment 1 and Deployment 2, but didn’t actually apply or deploy those files, thus we haven’t yet created our pods. To deploy the pods (two replicas for Deployment 1 and two replicas for Deployment 2), use the following command for both of your deployment files:
kubectl apply -f <deployment_file_name.yml>
To see our pods that were created, use the following command:
kubectl get pods -o wide
Create a Service that Points to Both Deployment 1 and Deployment 2
A Kubernetes service allows you to expose a network application to make it available on the internet, and serves as a consistent endpoint that allows external communication. There are four service types:
- ClusterIP — Default service type, only available inside the cluster
- NodePort — Designed to talk outside the cluster through the IP addresses on the nodes themselves
- LoadBalancer — Controls a load balancer endpoint outside the cluster; used for traffic coming into your cluster from an external source
- ExternalName — Used when your cluster needs to talk to outbound services or when you want to create DNS names in the core DNS system so your cluster can resolve external names that you may not have control over
The ClusterIP service is automatically created for every cluster and allows communication between pods within the cluster. If you are using Linux, a NodePort service is a great way for pods to communicate outside of the cluster to the external world. Unfortunately, NodePort is much more difficult to configure with a Mac and Docker Desktop. Since I am using a Mac, we will use a LoadBalancer instead to help our pods communicate outside of our cluster.
If you do kubectl get all
you will see that a ClusterIP service has already been created by default.
Create a new file in the same directory as your others, and name it loadbalancer.yml
. The apiVersion for a load balancer service is v1. The kind is Service, and the spec defines the label selector, ports and protocols desired. Note that the label selector is the key that will allow us to point the service to both deployments. Both deployments have the same key:value label of “app:nginx”. By including this shared label in the service YAML document, the load balancer service will point to both deployments. Here is the YAML for the loadbalancer.yml
file:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
So far we have deployed our two config map files and our two deployment files. Now let’s deploy our service file.
kubectl apply -f <service_file.yml>
Do kubectl get all
to see all of the running pods and services.
Access Both Deployments Using Curl
Notice in the screenshot above that the LoadBalancer has an external IP that is pending. How do we fix this? This external IP will remain in the pending status until you run a minikube tunnel in a separate terminal window. A minikube tunnel is a running process that creates a network route on the host to the service CIDR of the cluster using the cluster’s IP address as a gateway. To open the tunnel, first open a new terminal window using ctrl t. In the new terminal window (and keeping your other terminal window that has your cluster running open), use the following command:
minikube tunnel
If you get an error that looks like this:
then it means that you need to start a minikube cluster in this second terminal window before running the tunnel command. To do that, use the following command:
minikube start
After the minikube cluster is started, try this command again:
minikube tunnel
You will have to enter your computer’s password.
Keep this terminal window open and go back to your first terminal window where your four pods are running. Run the following command again:
kubectl get all
You should now see an external IP for your load balancer.
Test connectivity from your cluster to the internet using the curl command:
curl <external_IP>:<listening_port>
Run the same command again (or a few times), and you should eventually see both the HTML for deployment 1 and deployment 2.
Cleanup
To delete these pods and services we can simply delete the cluster using the following command:
minikube delete
Use the following command to confirm the cluster was deleted:
minikube profile list
Thank you so much for following along with me for another tutorial. This tutorial reinforced basic Kubernetes concepts like creating a cluster and launching deployments. Keep watching for new tutorials as I continue to learn about containerization with Docker and Kubernetes.