This blogpost shows you the bare minimal steps to run .NET Core 2 Docker images in Kubernetes. Kubernetes is hosted in Azure with Azure Container Service and we are using Azure Container Registry as our private Docker Hub.
In this blogpost all steps will be executed manually. Next time we automate the whole process with VSTS.
The next items need to be installed:
- Azure CLI
- Docker for Windows
Besides this Azure Container Registry and Azure Container Service needs to be provisioned in Azure. For now create them manually within the Azure Portal.
After this make sure the ServiceAccount of Kubernetes can access Azure Container Registry.
Create an Azure Container Registry
To create an Azure Container Registry (ACR) navigate to the Azure Portal and add a new resource:
Search for Azure Container Registry and click on it.
Choose an available Registry name and Enable the Admin user. (the Admin user can be enabled later on also)
Create a Kubernetes cluster with Azure Container Services
Make sure you have SSH keys. See my blogpost for the easiest way to do this on Windows.
Create a Service Principal and make sure you have the ApplicationId and password.
Search for Azure Container Service
Give the cluster a name and use an existing (empty) resourcegroup or create a new one
Choose for Kubernetes as Orchestrator
Enter the pem public SSH key or the Single line public SSH key
Enter the ApplicationId of the Service Principal
Enter the secret of the Service Principal
Choose the number of agents that you like.
Prepare the Kubernetes cluster to have access to Azure Container Registry
When the cluster is succesfully provisioned you are going to change the configuration of the Service Account. Because Azure Container Registry (ACR) is a private Docker Registry also the Kubernetes cluster cannot access it by default.
If you haven’t logged in to Azure then you will need to login with az login
If the cluster is on a different subscription then your default, change subscription with: az account set -s “”
Login to your Kubernetes cluster with:
az acs kubernetes get-credentials –resource-group=pascalnaberacs –name=myacscluster –ssh-key-file “C:\blogpost\opensshprivatekey”
Type the password for the SSH
Add a secret to Kubernetes, this secret contains the credentials to connect to ACR.
kubectl create secret docker-registry acrconnection —docker-server=https://myacr.azurecr.io —docker-username=myacr —docker-password=r/DK=ijNIvTArT1yU1OlXxHiLMXA9UDY —firstname.lastname@example.org
The name of the secret is: acrconnection
The credentials that you have to pass can be found on the admin tab of Azure Container Registry in the Azure Portal
Get the current configuration of the ServiceAccount
kubectl get serviceaccounts default -o yaml > ./serviceaccount.yml
Add this to the end of the serviceaccount.yml file:
imagePullSecrets: - name: acrconnection
So the complete files looks like:
apiVersion: v1 kind: ServiceAccount metadata: creationTimestamp: 2017-09-12T08:01:14Z name: default namespace: default resourceVersion: "151" selfLink: /api/v1/namespaces/default/serviceaccounts/default uid: 8ca65600-9790-11e7-83bb-000d3a24e4fe secrets: - name: default-token-j5jzn imagePullSecrets: - name: acrconnection
Replace the current configuration of the ServiceAccount with this new one:
kubectl replace serviceaccount default -f ./serviceaccount.yml
Now when we deploy images located in our Azure Container Registry, the images can be pulled by Kubernetes.
Steps to run your .NET Core 2 WebAPI project as Docker Container in Kubernetes
Now we have all prerequisites in place. Let’s do the actual work to deploy a Docker Container to Kubernetes. The following picture shows the steps that need to be done. Every step is explained below.
1. Create a .NET Core 2 WebAPI project
You can use the command prompt, you can use Visual Studio Code. I’m using Visual Studio 2017:
Create a new project: choose for an ASP.NET Core Web Application
In the next step in the wizard choose for a Web API project. Note that ASP.NET Core 2.0 is selected, Enable Docker Support is enabled and create a Linux Docker Image.
2. Publish the .NET Core 2 WebAPI project
We are not going to change the behavior of the code in the Web API controller. For now we go with the default implementation.
There are multiple ways to create a Docker Image.
One way is to use the Docker compose file. To use this, build the docker-compose project in Release configuration. After building you will have a Docker image on your machine with the name of your project. Check this in the command prompt with docker images
Using Docker Compose is the easiest way when you have multiple projects in your solution that has to become a Docker Image.
Another way is to use the Dockerfile directly. The Dockerfile references the obj/Docker/publish directory by default. So we are going to publish the project to this directory.
Right click the project and choose for publish. Choose for Folder publish:
Select the same path as the Dockerfile uses. So: obj/Docker/publish
The files are published to this directory.
An alternative to publish the ASP.NET Core 2 WebAPI project is to use the command prompt:
dotnet publish ./MyWebApi.csproj -c Release -o ./obj/Docker/publish
3. Create a Docker image of the .NET Core 2 WebAPI project
This step is already done if you have used Docker compose in the previous step.
Open a command prompt in the directory of the WebApi project.
Execute (where mywebapi is the name of your project) docker build -t mywebapi .
You can see the image on your machine with the following command:
if you want to run the image on your local machine run:
docker run -it -p 8080:80 mywebapi
Where 8080 is the local port and maps to port 80 of the Docker container.
You can access the WebApi project in the browser via:
If you like, you can in the mean time open a new command prompt and see the Docker container running with: docker ps
4. Push the Docker image to the Azure Container Registry
It’s not possible to deploy a Docker container directly to Kubernetes. Kubernetes needs to get the image from a Docker repository. For example Docker Hub is a public repository. Azure offers a private Docker repository with Azure Docker Registry (ACR).
To succesfully push the Docker image of the project to ACR, you need to prepare the tags of the image. The tag needs to contain the name of the ACR. So in this sample, where the name of the ACR is myacr, the name of the image will be: myacr.azurecr.io/myservice/mywebapi
where myservice is only a name to group Container Images that belong together.
For this step multiple ways are possible also:
You can add a tag to an existing image:
docker tag mywebapi myacr.azurecr.io/myservice/mywebapi
Tag the image with an version also:
docker tag mywebapi myacr.azurecr.io/myservice/mywebapi:1
An alternative for adding a tag is to apply multiple tags while building the image.
docker build -t mywebapi -t myacr.azurecr.io/myservice/mywebapi -t myacr.azurecr.io/myservice/mywebapi:1 .
Note: it’s not needed to apply a version to the Image, but it’s a best practice to know which version you are using. Because next time when you upload the same image. That will be the latest.
Take a look at the results by executing docker images.
You can see 3 images with the same IMAGE ID. Two of them have a latest tag and one has tag 1.
Now the images are prepared with the correct tags, you can push the images to ACR.
a. Get the password of the admin user in the Azure Portal:
If you forgot to enable the admin account during the creation of ACR, you can do it now.
b. Login to ACR from the command prompt:
docker login myacr.azurecr.io -u myacr -p r/DK=ijNIvTArT1yU1OlXxHiLMXA9UDY
c. Push the image to ACR
docker push myacr.azurecr.io/myservice/mywebapi and
docker push myacr.azurecr.io/myservice/mywebapi:1
Note that uploading the second version of the Image is drastically faster because of the Layers that are already known in ACR.
d. Take a look in the Azure Portal, tab Repositories:
if you like, you can now also get the image from ACR on your local machine and run it.
Because you already have this image, delete it from your local machine first.
(the parameter is the first part of the IMAGE ID)
docker rmi fb5
if you get an exception that’s because the image has run.
Check this with docker ps -a
And remove the container with: (the parameter is the first part of the CONTAINER ID)
docker rm 67a
Check with docker images that the images are not available on your local machine anymore.
Get the image from ACR (you should be logged in already):
docker pull myacr.azurecr.io/myservice/mywebapi:1
Run the image.
5. Execute a Deployment file to instruct Kubernetes to host the Docker Container
Create a Deploymentfile which declaratively tells Kubernetes what to do. In this case we want Kubernetes to run 3 containers of the mywebapi image.
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: mywebapi-deployment spec: replicas: 3 minReadySeconds: 10 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 1 template: metadata: labels: app: mywebapi spec: containers: - name: mywebapi image: myacr.azurecr.io/myservice/mywebapi:1 ports: - containerPort: 80 imagePullPolicy: Always
Change the file according to your needs and save it as Deploy.yml
Login to your Kubernetes cluster as described in the “Prepare the Kubernetes cluster to have access to Azure Container Registry” section earlier in this blogpost.
Exectute the deployment with (–save-config is to see the history of deployments): kubectl create -f Deploy.yml –save-config
The “create” argument only have to be passed the first time you deploy. Sequential deployments should be executed with: kubectl apply -f Deploy.yml
Let’s see if the deployment is succeeded:
This can be done in the command line and also in the UI.
a. With the command line
See the deployments:
kubectl get deployments
See the pods that are available now: (you will see 3 pods)
kubectl get pods
b. With the UI
Start the UI proxy:
Open the UI in the webbrowser:
Navigate to the Deployments, the pods and you can also see the secret you created.
The pods should be running, but you can’t access the webapi right now. To make a public endpoint do the following: (this has only to be done once)
Create a new Deployment file. This time for a Service. The file looks like this:
apiVersion: v1 kind: Service metadata: name: myapiservice spec: ports: - port: 80 selector: app: mywebapi type: LoadBalancer
Make sure that the selector-app is the same as template-metadata-labels-app name in the Deploy.yml.
Save the file as DeployService.yml
Create the Deployment:
kubectl create -f DeployService.yml
The creation of the Service is fast, but to get a public IP-Address takes a while (3 minutes or so), because Kubernetes is communicating with the load balancer in Azure to get a public IP address.
Check the results in the UI or command line:
kubectl get services
The EXTERNAL-IP is <pending> for now.
The EXTERNAL-IP is available and now you can access your WebApi:
In my situation the URI is:
Now change the implementation of your WebApi project to return a different value.
Create a Docker Image with a new version tag and push the Docker Image to ACR.
Update the Deploy.yml file with the new version tag and execute it.
See the power of Kubernetes when the Rolling updates takes place.
Next time we are going to automate all this steps with VSTS