Kubernetes Deployments with ConfigMaps

Deploy 4 NGINX servers with custom HTML

Brandi McCall
Women in Technology
9 min readMar 19, 2023

--

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 Mac

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.

Name in Deployment YAML file
Name in ConfigMap YAML 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.

--

--