Managed Kubernetes in Azure

Managed Kubernetes in Azure

Kubernetes is awesome! However, setting up a cluster is really difficult. You must think twice before you are going this direction. Consider using a managed cluster instead. For example the Azure Kubernetes services, aka AKS, a Managed Kubernetes in Azure

I’ve pushed all code for this article here:
https://github.com/FolkertJo/Dibbus.Math.Azure.K8S

Maybe one of the biggest advantages of being a software architect is the possibility of meeting lots of different people and getting in touch of new/other technology stacks.

Last year I’ve been working for one of the largest energy companies in The Netherlands. A team member suggested to run the application in containers, which was different then we had planned (Azure WebApps). After the switch to use containers for the applications, I was asked to run these containers in Kubernetes.
Problem was that there was no Kubernetes cluster, so within the organization, some people took a deep dive in to the Kubernetes stuff and after some weeks, we had a cluster. Since then, we’ve deployed hundreds of images to the cluster. And I think Kubernetes is really awesome!

Expertise layers

From my point of view 3 layers of experts are needed in order to manage a cluster:

  1. System layer
  2. Network layer
  3. Application layer

Disclaimer: I’m far from a Kubernetes expert. This is my opinion of Kubernetes after a year working with a cluster. Past year I’ve been working only within the application layer, and touched some Networking Layer stuff.
To really understand these layers you must really get some additional trainings, because this is some really heavy stuff (System Layer)!

Setting up a Kubernetes cluster is not really doable for the mortal souls. Thankfully Google, AWS and Azure offers managed services . This article is about the service provided by Azure, called Azure Kubernetes Services, aka AKS.

I will try to explain how to setup your own cluster and how to ‘promote’ a simple Docker image to a full running application in a Kubernetes cluster.

Requirements

To run an application in an Azure Kubernetes cluster you will need:

  1. A docker image of an application. This can be any type of webapp. Try to create a Docker image of a simple webapp.
  2. A Kubernetes cluster!
  3. A Container Registry, required because Kubernetes needs to pull the images from somewhere.

So, that’s not too bad! We only need a new cluster, a registry and we’re ready! So first lets setup a Kubernetes cluster using powershell:

az aks create `
   --resource-group $azureResoureGroupName `
   --name $azureKubernetesClusterName `
   --node-vm-size $azureKubernetesVmSize `
   --node-count $azureKubernetesNodeCount  `
   --generate-ssh-keys

This is al you need to create a managed Kubernetes cluster!

In Azure, you’ll need a resourcegroup, this can be an existing group, a cluster name, a vm-size, and a node count. The ssh key is generated for you in order to access the AKS VM (which is a Linux VM) using SSH.

To understand this, you must understand some basics of Kubernetes. Kubernetes is actually just a service which is responsible for orchestrating VM’s. This service is provided by all cloud providers.

Kubernetes uses Docker images to run the application. To run all these images(Containers), you can add services, Loadbalancers, storage, configmaps etc. So, a Kubernetes cluster is nothing without the actual Docker images, it’s only doing what you are telling the cluster to do, how to handle the images, how to scale, where to put data, which services must be exposed etc. also called the Desired State.

Desired State

The Desired state is your definition of your setup. And Kubernetes is doing nothing more then just follow the desired state, continuously! Over and over again. If your application needs to be running on at least 3 instances, Kubernetes will do anything to keep at least 3 instances alive. If you remove an instance, Kubernetes will start a new one instantly, just because of the desired state.

The Desired state is your definition of your setup. And Kubernetes is doing nothing more then just follow the desired state, continuously! Over and over again. If your application needs to be running on at least 3 instances, Kubernetes will do anything to keep at least 3 instances alive. If you remove an instance, Kubernetes will start a new one instantly, just because of the desired state.

Okay, now we’re all Kubernetes experts ๐Ÿ˜œ, on-topic again!

We do have a cluster using the az aks create command. Now we need to make some applications run on the cluster. My suggestion is to pick an easy application yourself to make it work. I’ve containerized one of my testing projects which is just a small math application for my son, written in. Net Core 2.2.

I’ve added a Dockerfile to make a docker image from the application:

FROM microsoft/dotnet:sdk AS build-env
WORKDIR /app

# Copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore

# Copy everything else and build
COPY . ./
RUN dotnet publish -c Release -o out

# Build runtime image
FROM microsoft/dotnet:aspnetcore-runtime
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "Dibbus.Math.Web.dll"]

๐Ÿ’ก Test the Docker image first to make sure the application can run.

Container registry

To make Kubernetes run the image, we need to push the image to an repository (Kubernetes needs to pull the image from an accessible Docker repo). Do we have a Docker repo? Nope! So we need to create one first using the az acr create command (acr = Azure Container Registry)

az acr create `
   --resource-group $azureResoureGroupName `
   --name $azureContainerRegisteryName `
   --sku Basic

This command only creates a container registry. Now we need to push our image to the registry:

First we need to tag the image using the registry endpoint:

docker tag ${dockerImage}:${dockerTag} ${azureContainerRegisteryEndpoint}/${dockerImage}:${dockerTag}

This command is just adding the endpoint to the tag. Now, when we do a Docker push, the image is pushed to the registry included in the tag name:

az acr login --name megamathcontainerregistry

docker push ${azureContainerRegisteryEndpoint}/${dockerImage}:${dockerTag}

Make sure to use correct versioning for your Docker images. Have a look at https://semver.org if you need some assistance.

Now, everything is in place to start with Kubernetes and Docker images. We have cluster, an Image registry and a Docker image. Only thing what we need to do is setting the correct permissions in the registry in order to allow Kubernetes pull images from the registry. The cluster needs to have pull permissions in our registry resource. I order to make this work, we need to gather some information first.

Permissions

The actual command to set the correct permissions is:

az role assignment create --assignee $appId --scope $acrId --role acrpull

So, we need an assignee, the scope and the role. We know the role, which is acrpull. For the other two properties we need to make some requests firsts:

Getting the assignee

When a Kubernetes cluster is created, a new principal is created for you by Azure. We need to get the clientID of the assigned service principal:

$appId = az aks list --resource-group $azureResoureGroupName --query "[0].servicePrincipalProfile.clientId" --output tsv

This will output the clientID of the service principal.

Now we need to get the scope of our resource, which is a full path of the container registry:

$acrId = az acr show --resource-group $azureResoureGroupName --name $azureContainerRegisteryName --query "id" --output tsv

Now we have the appId and the acrId. Using these values in our command sets the correct permissions on our container registry.

Kubectl

You can interact with the cluster using the Kubernetes cli: kubectl. This is installed locally if you’ve installed Docker for Desktop. You can also download the latest version from the Google Repository here:

https://kubernetes.io/docs/tasks/tools/install-kubectl

The desired state, your wish is my command!

Everything is in place, however, Kubernetes is waiting to receive ‘the Desired State’. The best way to define the desired state is to put this state into manifest files. These files are posted to the Kubernetes CLI (=kubectl).

In order to run the app, we need a 2 manifest files:

  • Deployment manifest
  • Service manifest

The Deployment manifest houses all information about the image itself, while the service manifest defines the actual loadbalancer to expose our container.

To group everything (deployments, pods, services etc )in Kubernetes, we need to create a namespace in the cluster. This is just a groupname. Everything we will create in the cluster, is created using this namespace. When we need to clear everything, we just can remove the namespace and all resources created in the namespace will be removed too.

The kubctl can allready be used, keep in mind to use the correct context:

Setting Kubernetes context

This can be done using the Docker Desktop App.
When the context is set to the cluster in Azure we can fire our first kubectl command to create the namespace:

kubectl create namespace $azureKubernetesNamespace

๐Ÿ’ก Now, all additional command must be executed using the our-namespace parameter. It is possible to include the namespace in your manifest file but in order to keep the manifest files flexible I would suggest not to include it in the file itself. Use the command instead.

At this point we are ready to start telling Kubernetes how we want Kubernetes to serve our application, aka the desired state. For this we need to have a deployment.yaml and a service.yaml.

๐Ÿ’ก These file can be combined in the same file which gives you a better overview. Combining these files is done by separating the blocks by ‘—‘ (3 dashes) in between

Deployment

Let’s have a look at the deployment.yaml

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: deployment-megamath
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: app-megamath
    spec:
      containers:
        - image: "megamathcontainerregistry.azurecr.io/megamath-web:4.0.0"
          imagePullPolicy: Always
          name: container-megamath
          ports:
          - containerPort: 80

Keep in mind that Kubernetes is evolving fast, take a note at the apiVersion. Some version might not work, while other versions offers great toolings. You can read more here:
https://kubernetes.io/docs/reference/federation/extensions/v1beta1/definitions/

๐Ÿ’ก Use a proper naming convention for you resources, resources in Kubernetes are linked to each other using these names or labels. Don’t create a mess by naming everything the same

This deployment.yaml is telling Kubernetes to run a Docker container based upon the megamath-web image, using version 4.0.0 which can be pulled from the megamathcontainerregistry.azurecr.io registry.
This instance must have only 1 instance (=replica). This can be raised ofcourse. If you want multiple replicas, Kubernetes will add multiple pods running this instance. Of course, you want to add multiple nodes in a real world production scenario to handle the load. For this case, I’ve only added a single node in Azure, running 1 replica of my app.
Also import is the container port. This is the port which our application is running on (remember, the .Net Core app). This needs to be defined so our service can expose this port to make the app accessible on this port.

Service

Next step is to create a service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: svc-megamath
spec:
  type: LoadBalancer
  ports:
  - port: 80
  selector:
    app: app-megamath

Import in this yaml is the selector which maps the service to the deployment! And of course the port number.

Combined

As mentioned before, these file can be combined which results in this file, for example kubernetes-manifest.yaml:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: deployment-megamath
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: app-megamath
    spec:
      containers:
        - image: "megamathcontainerregistry.azurecr.io/megamath-web:4.0.0"
          imagePullPolicy: Always
          name: container-megamath
          ports:
          - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: svc-megamath
spec:
  type: LoadBalancer
  ports:
  - port: 80
  selector:
    app: app-megamath

Now, we can simply apply this ‘definition’ on the cluster:

kubectl apply -f kubernetes-manifest.yaml --namespace=$azureKubernetesNamespace

Remember the namespace? we’re using it here.

We’ve just added a deployment and a service to the cluster, exposed our application using a service type Loadbalancer on port 80.

As a result of the service deployment, Aks is adding a new IP address to the service, using this IP address is opening the app in our browser. I’ve created a powershell script to get the service IP and opens this in a browser window:

try {
   Write-Host $date "Getting External IP Address" -ForegroundColor Green
   
   $serviceName = "svc-$azureKubernetesAppName"
   $json = kubectl get service $serviceName -n $azureKubernetesNamespace -ojson

   $jsonObject = $json | ConvertFrom-Json
   $ip = $jsonObject.status.loadBalancer.ingress[0].ip

   # View IP
   Write-Host $date "External IP Address: $ip"

   # Open browser
   Start-Process "http://$ip"
}
catch {
   $ErrorMessage = $_.Exception.Message
   Write-Output $ErrorMessage
}

Dashboard

Aks offers a command to view the dashboard too, you can use:

az aks browse --resource-group $azureResoureGroupName  --name $azureKubernetesClusterName

Done!

When you’ve completes al the steps and your app is running, you can just clear the resourcegroup in Azure using this command:

az group delete -n $azureResoureGroupName -yes

Or you can just remove the namespace if you want to keep the cluster:

kubectl delete namespace $azureKubernetesNamespace

More info about the commands can be found here:

https://kubernetes.io/docs/reference/kubectl/cheatsheet/

Note about the code examples

๐Ÿ“Œ As you can see in this article, I’ve replaces all values by powershell variables. This is done to make is easier to configure. All variables can be defined in a single file which is read by the script.
Read the info in the repo how to handle this file:
https://github.com/FolkertJo/Dibbus.Math.Azure.K8S/blob/master/0-variables.ps1
I’m parsing the settings using this file:
https://github.com/FolkertJo/Dibbus.Math.Azure.K8S/blob/master/0-variables.ps1
By including this file in every subsequent file, you can use the variables defined in the variables.ps1 script.

Folkert

I'm a webdeveloper, looking for the best experience, working between development and design. Just a creative programmer. When I'm getting tired of programming C#, i'd love to create 3D images in 3D Studio Max, play the guitar, create an app for Android or crush some plastics on a climbing wall or try to stay alive when iยดm descending some nice white powdered snowy mountains on my snowboard.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.