Zero to Kubernetes on Azure

Photo by Joseph Barrientos on Unsplash

Kubernetes is a highly popular container management platform. If you have just heard about it but didn’t have a chance to play with it then this post might help you to get started.

In this guide, we will create a single-node kubernetes cluster and will deploy a sample application into our cluster from our private container registry, and finally, we are going to configure our cluster to be able to serve our content with TLS certificate from a custom domain!

If this sounds interesting, then buckle up because this post is going to be really looooong post!


Before we start, make sure that you have an active Visual Studio Subscription.

While we are creating and configuring a cluster we will make use of a couple of tools.

  • Docker We will need a local docker installation to be able to build Docker images locally.
  • Azure CLI is a suite of command-line tools that we are going to use heavily to manage our Azure resources. Once you have installed it, make sure you are logged in (az login) and your Visual Studio subscription is the active one.
  • kubectl is a command-line tool that we will use to manage our kubernetes cluster.
  • helm is a command-line tool that we will use to manage deployments to our kubernetes cluster.

Create the Kubernetes Cluster

OK, let’s get started.

First, we need to create a resource group which will contain all the resources that we are going to create later on.

Next, we are going to create a single-node kubernetes cluster.

NOTE: I have chosen to create a single-node cluster purely because the cost of a multi-node cluster would exceed monthly Visual Studio Subscription credit. If you are not planning to run the cluster for a month then feel free to increase the node count in the previous command.

Creating a kubernetes cluster might easily take a while. You should see a JSON formatted cluster information printed to the console when the operation is completed.

NOTE: If you see an error message saying An RSA key file or key value must be supplied to SSH Key Value. You can use — generate-ssh-keys to let CLI generate one for you, then try appending --generate-ssh-keys option to the end of the previous command and run it again.

From now on, we are going to use kubectl CLI to interact with our cluster. kubectl can be used to work with multiple kubernetes cluster. Authentication information for each cluster is called context.

We need to provide context information of our cluster so that the commands we run points to our cluster.

We are going to use Azure CLI to handle pulling the context information of cluster and merge it with kubectl configuration.

Context information for our cluster is called kube-demo. Let's make sure that it is the default context.

Awesome! Now, every command we are going to run using kubectl is going to be executed in our kubernetes cluster.

Let’s request the list of active nodes in our cluster.

Create an Azure Container Registry (ACR)

We have created a kubernetes cluster, but it is pretty useless as it’s own because we haven’t deployed anything to it.

Ideally, we would like to deploy containers from our own application images into our cluster.

One way of doing it is to push our application’s docker image to public under our user name but this will make it publicly accessible. If this is not something you would like the alternative is to create a private container registry.

A private container registry on Azure is called Azure Container Registry (ACR).

The following command creates an ACR resource with name kubeDockerRegistry on Azure. The full address of the container registry will be

NOTE: The name of the ACR needs to be unique. If the name is taken, an error message will be printed. Don’t forget to replace ACR name in the subsequent commands with the name you have chosen.

At this point, if we knew username and password to our ACR then we would run docker login to login to the registry. As the first line of the response suggests, the admin user is disabled by default.

We are going to use Azure CLI to handle the details of logging in to our container registry through docker.

We are going to use aks-helloworld image to test deployments to our kubernetes cluster.

Docker image naming format is registry[:port]/user/repo[:tag]. When registry part not specified then it is assumed to be If we want to push an image to our private container registry then we need to tag the image accordingly. In our case the name should be

Now, when we push the image, it will be sent to our private container registry.

Associate Azure Container Registry and Kubernetes Cluster

We have set up our kubernetes cluster and a private container registry and be able to communicate with both of them.

Next step is to make them be able to talk with each other.

We need to grant acrpull permission to our kubernetes cluster service principal to be able to pull docker images from our private container registry.

In order to do this, we need two pieces of information.

First, we need to get the server principal id of the cluster which we will be referring as SERVER_PRINCIPAL_ID.

Secondly, we need resource id of the private container registry which we will be referring to as ACR_RESOURCE_ID.

Finally, we are going to grant the acrpull permission to SERVER_PRINCIPLE_ID on ACR_RESOURCE_ID.

Install Helm

Helm is the package manager for Kubernetes.

We are going to use it to deploy our applications into our cluster.

Helm has a server-side component called tiller which needs to be initialised in the cluster to be able to create resources needed for deployment of our application.

Let’s do that first.

If we run helm list now, we will get an error message because helm is running with the default service account. That means it doesn't have the required permissions to make any changes to our cluster.

We need to grant the required permissions to be able to install packages into our cluster.

NOTE: This is giving cluster-admin access to the tiller service, which is not something you should be doing in production.
Z:\>kubectl create clusterrolebinding add-on-cluster-admin — clusterrole=cluster-admin — serviceaccount=kube-system:default created

If we run helm list again, the error message should not show and we should be able to see the tiller pod running.

Deploy with helm

In Helm’s terminology, a recipe for a deployment is called a chart. A chart made of a collection of templates and a file called Values.yaml to provide the template values.

We are going to create a chart for our aks-helloworld application.

Helm is going to create a folder with the name aks-helloworld-chart. The file that we are interested in is values.yaml.

The contents of the file look like this:

We are going to change the values under the image block. These values define which and what version of the docker image to pull.

For our application;

We are not interested in ingress and tls blocks for now, but we are going to use them later.

Once we have updated these values, we can try deploying our application.

The installation has kicked off. You can watch the progress of the deployment by running the following command.

As a result of the deployment of our application, the following resources created in the kubernetes cluster:

  • Pods are a group of one or more containers running inside the cluster.
  • Services define a well-known name and a port for a set of pods inside our cluster. You might be running your application in multiple pods, but then in order to connect to these pods, you need to know IPs and ports assigned to them. Instead of connecting pods individually, you can use services.
  • Deployments are used to declare what resources you want to run inside your cluster and kubernetes handles the creating and updating the necessary resources. If a pod dies, it spins up another one. If the deployment is deleted then all the associated resources are deleted.

Install nginx ingress

Right now, we have pods and services running within the cluster. But, they are not accessible outside of the cluster.

The resource that allows external requests to map into the services within the cluster is called ingress.

We need to create an ingress resource to tell kubernetes how the requests should be mapped.

Luckily, there is a premade chart that we can just install and enable ingress.

Helm deployed all the ingress related resources. If we query the running services, we should see an ingress-controller with an external IP assigned. is the IP that we can use to connect to our cluster now.

Let’s see what we get back when we make a request!

NOTE: You can install curl if you don’t have it locally install by running scoop install curl

The response is default backend - 404 which is absolutely normal.

It means ingress is up and running but it doesn’t know how to map external requests to any of the internal services, therefore, falling back to the default backend which only returns 404.

We are going to modify the ingress block on our chart as follows:

We have enabled the ingress, which will tell helm to create an ingress resource which maps the root of our host to the internal aks-helloworld service.

It’s worth bumping up the version of the chart in Chart.yaml so that we can rollback if anything goes wrong.

Let’s deploy the new version.

Let’s test if the server is returning anything!

Create a DNS Zone

Accessing the cluster only by the IP is not ideal.

I want to get to the cluster by using a domain name. I am going to configure one of my custom domains to access the cluster.

First, we need to create a DNS Zone resource for our domain.

We are interested in the name server values that are printed as a part of the JSON response. I am going to enter these values to my domain registrar’s portal so that the domain resolves into our DNS Zone.

In my registrar’s portal, it looks like this:

NOTE: Don’t forget to include trailing dots.

DNS propagation may take many hours to complete.

You can run use nslookup to check if the operation is completed. The name of the primary name server should change to

When a user types the domain into their browser, they will be taken to the Azure DNS Zonec, but Azure doesn’t know what IP to redirect to. We need to add an A-type record-set in our DNS Zone to point our domain to the cluster's external IP.

Let’s update our aks-helloworld-chart by adding our host value.

I have added a host value to point to our custom domain.

Let’s bump the chart version and deploy again.

We should be able to navigate to our cluster by using the domain.

If you try to reach the website from a browser, you will be redirected to https because of the default HSTS policy. Majority of the browsers will refuse to load the website because it doesn't have a browser-trusted certificate.

Let’s fix this!

TLS Certificate

I am going to use to obtain a TLS certificate.

Let’s Encrypt is a well-known, non-profit certificate authority. Certificates issued by ‘let’s encrypt’ are valid for 3 months and needs to be renewed afterwards. Renewal of the certificate can be done manually or can be automated by running an ACME client.

Luckily, there is an add-on called cert-manager for kubernetes which can automate the whole process for us. We are going to install it next but before we do that there is one more thing we need to do.

We need to add CAA record-set to our DNS zone to make it clear that our certificate issuer is This record is checked as a part of baseline requirements by many CAs as they are required to do to be trusted by major browsers.

You can use to check if your CAA record is set up correctly.

Install cert-manager

cert-manager is an open source kubernetes add-on by jetstack that automates issuance and renewal of TLS certificates.

I have installed cert-manager:0.7 by following their installation guide.

Create an issuer

We have cert-manager up and running.

Next thing to do is to create an issuer resource to kick off requesting a TLS certificate from letsencrypt.

Let’s create a file with the following content and name it issuer-prod.yaml.

NOTE: Don’t forget to change the email.

Now we can enable TLS in our aks-helloworld-chart chart and configure it to use the issuer that we have just created.

We have added annotation to specify which issuer to use and also set value to http01 to match the challenge type of the issuer.

cert-manager should pick the changes and should handle the communication with letsencrypt and finally create a certificate resource with the name cert-prod.

Let’s bump the chart version and upgrade our chart once more.

It might take a while to complete the request but eventually, we should see our certificate created.

Let’s navigate to our domain and check if the HTTPS connection is secure.



That was quite a long post even though we have cut corners whenever we can.

I hope this would give an overall understanding of how various pieces of technology come together to create a kubernetes cluster that is capable of routing HTTP requests to the services inside the cluster as well as issuing a TLS certificate and keeping it up to date.

As a next step, you might create an Azure DevOps CI/CD pipeline that deploys your application straight from the git repository to the cluster.


Originally published at