Hopefully you managed to get through the first part of this series, and get the basic C# code stored in the right places.
If this is new to you the quick recap is that we are trying to build a setup for microservices using managed Kubernetes in Azure (AKS) and Visual Studio Team Services (VSTS). So come along for the ride 🙂
As the next step it's probably best if we create some Azure components that we need to rely on before actually building and releasing the code. I would usually point you to the Azure portal for learning purposes, but you can't really escape the command line for this scenario, so if you don't have it installed already I would strongly advise you to install the Azure CLI:
(If you have an old version installed I would recommend upgrading to get the proper support for AKS along with some bugfixes.)
And while we're at it you should probably download two important Kubernetes tools as well:
Kubectl - https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.9md (Go to the newest "Client Binaries" and grab it.)
Helm - https://github.com/kubernetes/helm/releases
They are plain executables, so no installer; just paste them into a folder of your choosing like C:\k8s
Kubectl is the "control app" for Kubernetes, and Helm is the package manager (or the equivalent of NuGet in the .NET world if you like) for Kubernetes. More on the usage of these tools later.
You might ask yourself why you need Kubernetes native tools, when you are using a managed Kubernetes service, and that is a valid question. A lot of managed services put an abstraction on top of the underlying service, and hides the original in various manners. An important thing to understand about AKS is that while it certainly abstracts parts of the k8s setup away from you it does not hide the fact that it is k8s. This means that you can interact with the cluster just like if you had set it up from scratch. Which also means that if you are already a k8s ninja you can still feel at home, and vice versa, if you come from knowing nothing about k8s it's necessary to learn at least some of the tooling. (Though you don't necessarily need to aspire to ninja-level knowledge of Kubernetes. Though you need to be able to follow along as I switch between k8s for shorthand and typing Kubernetes out properly:) )
For a complete reference on what kubectl can do you can add the kubectl Cheat Sheet to your favorites:
For a lot of the things we will do now, the full-blown Visual Studio IDE is probably not what you want to use to get there. I find myself switching between Visual Studio Code, the PowerShell ISE, and the good old command line depending on what I'm doing. (So, don't get confused if I jump back and forth in my screenshots either.)
Creating the AKS cluster
Pro tip: This login process is implemented using the OAuth DeviceProfile flow. You can implement this yourself if you like:
If you have multiple subscriptions in Azure you might need to use
az account list, and
az account set --subscription xyz,
to make sure you're working on the right one.
We need a resource group to contain our AKS instance. (Technically it doesn't matter which location you deploy the resource group too, but I suggest going with one that is supported by AKS and stick with it throughout the setup.)
az group create --name csharpFiddleRG --location westeurope
Next you need to create the AKS cluster:
az aks create --name csharpFiddleAKS --resource-group csharpFiddleRG --node-count 1 --generate-ssh-keys --kubernetes-version 1.9.2 --node-vm-size Standard_DS1_v2
You will note that I chose a specific Kubernetes version, even though that seems to be a low-level detail when we're dealing with a service that should handle this for us. However, since Kubernetes is a fast-moving target you might need to be on a certain level for specific features and/or compatibility. 1.9.2 is the newest AKS supported version as I am typing this, so you can verify if there is a newer one if you like. (You can upgrade the version later of course.) If you don't specify the version you will be given the default version, which was on the 1.7.x branch when I tested. Since this is a managed service there might be a delay from a new version of Kubernetes is released until it is available in AKS, so if you want to go bleeding edge managed might not be for you.
To keep costs down in the test environment I'm only using one node, but in production you should ramp this up to at least 3 for high availability and scale. I also specified the VM size to be a DS1_v2. (This is also the default if you omit the parameter.) I tried cheaping out, and going with the cheapest SKU I could locate, but the performance was abysmal when going through the cycle of pulling and deploying images repeatedly so I upgraded.
In light of this I would like to highlight another piece of goodness with AKS. In a Kubernetes cluster you have management nodes and worker nodes. Just like you need more than one worker to distribute the load, you need multiple managers to have high availability. AKS takes care of the management, but not only do they abstract it away - you don't pay for it either. You pay for the nodes, and that's it. (Since the service is in preview I would like to add the disclaimer that I do not know if things might be adjusted at a later point in time.)
The creation of the cluster should take 5-10 minutes so have a minor break here if you like.
Kubectl also allows for connecting to the dashboard (kubectl proxy), but using the Azure CLI everything is automatically piggybacked onto the Azure session you have. You'll notice that the address is 127.0.0.1 even though it isn't local, but that's just some proxy tricks where the traffic is tunneled through to Azure.
Helm needs to be primed as well to be ready for later. Based on having a working cluster as verified in the previous step, helm will automagically work out where to apply its logic. (You can have multiple clusters so part of the point in verifying the cluster is ok is to make sure you're connected to the right one.) So apply the following:
Helm repo update
The cluster should now be more or less ready to have images deployed. (We built a Docker image locally in the previous post.) Which brings us to another topic - management of images.
Much like we refer to images when building virtual machines, Docker uses the same concept although slightly different on the implementation level. So to get running containers inside your Kubernetes cluster you need a repository for these images. The default public repo is Docker Hub, and images stored there will be entirely suited for your AKS cluster. (In fact we will acquire some third-party images from Docker Hub for completing our setup.) However we don't want to make our images available on the Internet for now, so we will want a private repository. In the Azure ecosystem this is delivered by Azure Container Registry (ACR).
You can easily create this in the portal, but for coherency let's do this through the CLI as well. You can throw this into the AKS resource group, but since a registry logically speaking is a separate entity we will create a new group for our registry. (Then it becomes more obvious to re-use across clusters too.)
Setting up Azure Container Registry (ACR)
Create a resource group:
az group create --name myACRrg --location westeurope
Create an Azure Container Registry:
az acr create --resource-group myACRrg --name myFiddleregistry --sku Basic
Since it needs to be globally unique you need to come up with your own moniker for the name parameter 🙂
While you can now browse the contents of the registry in the portal that does not mean that your cluster can do so. As is indicated by the message upon successful creation of the ACR component we need to create a service principal that will be used by Kubernetes, and we need to give this service principal access to ACR. If you're new to the concept this doesn't explain what a service principal is, but you can think of it as a user account for applications in this context. (I will stop myself from going into all the details of Azure AD, as that will quickly grow to something that could be a post of its own.)
This is one of those things I find easier to do in the PowerShell ISE:
// Get the id of the service principal configured for AKS
$AKS_RESOURCE_GROUP = "csharpFiddleRG"
$AKS_CLUSTER_NAME = "csharpFiddleAKS"
$CLIENT_ID=$(az aks show --resource-group $AKS_RESOURCE_GROUP --name $AKS_CLUSTER_NAME --query "servicePrincipalProfile.clientId" --output tsv)
// Get the ACR registry resource id
$ACR_NAME = "myFiddleregistry"
$ACR_RESOURCE_GROUP = "myACRrg"
$ACR_ID=$(az acr show --name $ACR_NAME --resource-group $ACR_RESOURCE_GROUP --query "id" --output tsv)
Alrighty, now we have everything set up for creating specifications of our pipeline from Visual Studio to a running container. We need three definitions for this:
- How VSTS should build our code.
- How VSTS should push the resulting artifacts to ACR.
- How AKS should deploy the container.
"Definition" is basically just another word for a recipe that VSTS can follow.
Sounds simple right? Well, yes, some of it is. But it turns out that the devil is in the details as he always is.
Or you can put the definition into code as well. Actually you sort of have that code already in the form of a Dockerfile. However I wasn't quite lucky with using the default Dockerfile in VSTS, and the default UI definition produced an error as well.. Ah, such is life.
It might not be apparent to you how to define this in code through VSTS due to the fact that access to viewing/creating YAML files is a preview feature. Depending on the timing of reading this versus when things go GA you might need to enable this to see it:
There's two (at least) ways you can approach handling of the building process. You can either push the code into the repo, build the code "directly", pack it into a Docker image, and push the result to ACR.
The other approach is to push the code to the repo, "inject" the code into a Docker image for the build, have the output in a new Docker image, and then push the result to ACR. This would for instance allow you to build things that aren't supported natively by VSTS. Very meta to use a container for building a container though 🙂
I seem to get the second approach to run slightly faster; don't know why. (Could be a coincidence for that matter.) But for now we will choose the first approach. We will re-visit the second approach later on in our walkthrough.
Your docker-compose.yml currently looks like this:
We'll change the image name to say myfiddleregistry.azurecr.io:apiplayground, and commit/sync to VSTS.
You can see that Restore, Build, Test, and Publish steps have been defined. You can remove the Test step if you like since we're not actually doing that currently 🙂 (It doesn't do anything if you leave it there though.) We will remove the Publish step, because we will not publish this way either.
Select the first of these Docker tasks, select your Azure subscription, and select your Azure Container Registry. You need to authorize your VSTS project to access your Azure subscription the first time you're connecting the two, which should be straightforward if you're not juggling multiple user accounts in Azure and VSTS. Select the docker-compose.yml from the root of your project (you did check-in your edit right?):
Unless the defaults have been changed in VSTS the rest of this task should be good to go already.
Moving on to the next Docker task you should configure the same Azure subscription and registry, as well as pointing it to the same docker-compose file. The action here should be "Push Service Image".
The Publish Artifact task isn't really used now either (not the same as the Publish task we removed), but we will still leave it there (because it can be used when creating releases). You can now hit "Save and queue" to kick off the build.
If something fails there should be a hint in the logs to suggest why. (Not necessarily helpful hints though.)
Status so far is that we have coded up something, and we have built and pushed an image to ACR. But there is nothing actually running yet. We need to get the image from ACR, and into the cluster. Kubernetes runs on having the recipes, (or definitions if you will), for the containers defined in yaml files. So let us define one for our service:
apiVersion: apps/v1beta1 kind: Deployment metadata: name: api-playground spec: template: metadata: labels: app: api-playground spec: containers: - name: api-playground image: myFiddleregistry.azurecr.io/apiplayground:latest ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: api-playground spec: ports: - port: 80 selector: app: api-playground
I'm not delving into the explanations here, but as you can probably figure out this defines some of the necessary things to describe the container. Be aware - yaml files want spaces, not tabs, and it expects the indent hierarchy to be as above. If you get the indentations wrong it will not work.
This should eventually go into our source repo too, but for now I actually chose to create it separately to illustrate how microservice definitions are not necessarily a part of the Visual Studio side of things.
If everything went well you should now have a running container in Azure. Let's browse to the site and see how it looks. Ehh.. Which address should we browse to?
Kubectl gives us an IP address, but it is internal to the cluster. This goes to prove "things" have happened, as k8s will not assign an address to a non-existent entity. Not that this really helps the average end-user as this IP isn't really usable for the world at large.
This is where things crosses more into the ops part than the dev part of the DevOps term. And we will cover those pieces of the puzzle in our next post.