Kubernetes: Host Multi-Container ASP.NET Core app to Single Pod

Kubernetes: Host Multi-Container ASP.NET Core app to Single Pod

In this tutorial I will host a Multi-Container ASP.NET Core app to a single Pod in Kubernetes. The ASP.NET Core app will have 2 containers and these containers will also communicate with each other. I will show you how this communication will take place inside the Pod.

Multi-Container ASP.NET Core app

We will create an ASP.NET Core app that will contain 2 projects. These 2 projects will be contained in 2 separate Docker Containers. Later on these 2 Containers will run inside a single Kubernetes Pod.

These 2 projects are:

  • 1. MultiApp – a simple project that will be making an API call to get a random joke. This random joke is then displayed on the browser.
  • 2. MultiApi – a web api project which will be sending this joke to the client in json format.
Quick Pause – On my previous tutorial I covered Ingress – How to use Kubernetes Ingress on an ASP.NET Core app. Ingress is an absolute necessary when deploying kubernetes on the cloud.

Create a new Web Application in Visual Studio.

asp.net core web application

Give your app the name MultiApp, and un-check the option Place solution and project in the same directory.

Place solution and project in the same directory visual studio

Do not select Enable Docker Support. We will do it later.

Now, select the template ASP.NET Core Web App that will create an ASP.NET Core Razor Pages based app.

asp.net core razor pages template

Create a Web API project

Add a new project to the same solution. This is done by right click the solution name in Solution Explorer and select Add >> New Project.

adding project to solution

Select ASP.NET Core Web Application.

ASP.NET Core Web Application

Call it MultiApi.

new project

Select the template as ASP.NET Core Web API. Make sure to un-check the checkbox that says – Configure for HTTPS.

visual studio configure for https

This is done because there is no need for SSL for the communication between Docker Containers.

Create API Controller

In the “MultiApi” project create a new class called Joke.cs inside the Models folder with the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MultiApi.Models
{
    public class Joke
    {
        public string Name { get; set; }
        public string Text { get; set; }
        public string Category { get; set; }
    }
}

This class is for the Jokes . Jokes will be randomly sent to the client that is calling the web api.

Next add a new controller called JokeController.cs inside the Controllers folder with the following code:

using Microsoft.AspNetCore.Mvc;
using MultiApi.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MultiApi.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class JokeController : ControllerBase
    {
        [HttpGet]
        public Joke Get()
        {
            Random rand = new Random();
            int toSkip = rand.Next(0, 10);
            Joke joke = Repository().Skip(toSkip).Take(1).FirstOrDefault();
            return joke;
        }

        public List<Joke> Repository()
        {
            List<Joke> jokeList = new List<Joke> {
                new Joke {Name = "RATTLE SNAKE", Text = "Two men are hiking through the woods when one of them cries out, “Snake! Run!” His companion laughs at him. “Oh, relax. It’s only a baby,” he says. “Don’t you!", Category="Animal" },
                new Joke {Name = "HORSE RIDER", Text = "To be or not to be a horse rider, that is equestrian. —Mark Simmons, comedian", Category="Animal" },
                new Joke {Name = "POSTURE CAT", Text = "What did the grandma cat say to her grandson when she saw him slouching? A: You need to pay more attention to my pawsture.", Category="Animal" },
                new Joke {Name = "HE CAN DO IT HIMSELF", Text = "It was my first night caring for an elderly patient. When he grew sleepy, I wheeled his chair as close to the bed as possible and, using the techniques I’d...", Category="Dockor" },
                new Joke {Name = "ON THE BADGE", Text = "My 85-year-old grandfather was rushed to the hospital with a possible concussion. The doctor asked him a series of questions: “Do you know where you are?” “I’m at Rex Hospital.”...", Category="Dockor" },
                new Joke {Name = "THE NURSE HAS MY TEETH", Text = "As a brain wave technologist, I often ask postoperative patients to smile to make sure their facial nerves are intact. It always struck me as odd to be asking this...", Category="Dockor" },
                new Joke {Name = "GLUTEN ATTACK", Text = "Guy staring at an ambulance in front of Whole Foods: “Somebody must have accidentally eaten gluten.”", Category="Food" },
                new Joke {Name = "MORNING TEA", Text = "What has T in the beginning, T in the middle, and T at the end? A: A teapot.", Category="Food" },
                new Joke {Name = "MAKE ME A SANDWICH", Text = "My husband and I were daydreaming about what we would do if we won the lottery. I started: “I’d hire a cook so that I could just say, ‘Hey, make...", Category="Marriage" },
                new Joke {Name = "SELL IT", Text = "As my wife and I prepared for our garage sale, I came across a painting. Looking at the back, I discovered that I had written “To my beautiful wife on...", Category="Marriage" }
                };

            return jokeList;
        }
    }
}

There are 2 attributes on the controller which will make it API Controller. These attributes are:

[ApiController]
[Route("api/[controller]")]

This controller is inherited from ControllerBase.

The Web API controller has a HTTP GET method that will return a random joke.

[HttpGet]
public Joke Get()
{
    Random rand = new Random();
    int toSkip = rand.Next(0, 10);
    Joke joke = Repository().Skip(toSkip).Take(1).FirstOrDefault();
    return joke;
}

There are 10 Jokes contained by the Repository method.

public List<Joke> Repository()
{
…
}
Calling Web API from “MultiApp” project

Open the Index.cshtml.cs page kept inside “Pages” folder of the MultiApp project and change the OnGet() method code to:

public async Task OnGet()
{
    using (var client = new System.Net.Http.HttpClient())
    {
        var request = new System.Net.Http.HttpRequestMessage();
        request.RequestUri = new Uri("http://localhost:53205/api/Joke");
        var response = await client.SendAsync(request);
        var joke = await response.Content.ReadAsStringAsync();

        var details = JObject.Parse(joke);
        ViewData["Joke"] = details["name"] + ";;" + details["text"] + ";;" + details["category"];
    }
}   

In this method the Web API is called. Notice the url of the web api – http://localhost:53205/api/Joke. You can get this url by selecting properties of the MultiApi project, see below image.

multi api url

The “JObject” is from Newtonsoft.Json.Linq package. It converts a string containing json to JObject type. After that you can extract the individual values of the json as:

details["name"]
details["text"]
details["category"]	

When the Joke is received, it is saved to a ViewData variable so in the Index view I will show the Joke inside an HTML table.

Therefore, edit the Index.cshtml kept inside the Pages folder to include an HTML Table whose code is shown in highlighted way:

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
    <table class="table table-sm table-striped table-bordered m-2">
        <thead>
            <tr>
                <th>Name</th>
                <th>Text</th>
                <th>Category</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>@ViewData["Joke"].ToString().Split(";;")[0]</td>
                <td>@ViewData["Joke"].ToString().Split(";;")[1]</td>
                <td>@ViewData["Joke"].ToString().Split(";;")[2]</td>
            </tr>
        </tbody>
    </table>
</div>

Multiple Startup Projects

Right click the solution name in the solution explorer and select properties. Now select Multiple Startup Project. Then select “Start” for both these project as shown in the below image.

multiple startup projects

Now run the app in visual studio, you will see a joke displayed on the page. Check the below image where I have shown this thing.

joke shown on browser

Create 2 Docker Containers for the App from Docker Compose

Now I will need to create 2 Docker Containers for the ASP.NET Core app. The first container will contain “MultiApp” project and the second container will contain the “MultiApi” project. I have illustrated this in the below image.

multi container asp.net core

Next you have to add docker support 2 both these projects. The best way is to do this from Docker Compose. A YAML file will be created docker-compose.yml, it’s purpose is to tell the docker engine to run this app in 2 containers.

Right click the MultiApp project name on the solution explorer and select Add ➤ Container Orchestrator Support.

Container Orchestrator Support

The Add Container Orchestrator Support dialog appears. Select Docker Compose and click the OK button.

Add Container Orchestrator Support

A new window opens where you need to select Target OS. Here select “Linux” and click “OK” button.

docker support options

In the same way add Add Container Orchestrator Support to the other project – “MultiApi”.

A “docker-compose.yml” file will be created with the following code:

version: '3.4'

services:
  multiapp:
    image: ${DOCKER_REGISTRY-}multiapp
    build:
      context: .
      dockerfile: MultiApp/Dockerfile

  multiapi:
    image: ${DOCKER_REGISTRY-}multiapi
    build:
      context: .
      dockerfile: MultiApi/Dockerfile

Also 2 Dockerfile will be created for each of the 2 projects. Check the below image where I have shown these 2 Dockerfile:

docker file location

Now it is the time to build the Docker Images for these 2 projects.

Important: Communication between containers in a same k8s Pod

Before I build the Docker Images, I want to come to Kubernetes Pods topic. Ultimately there will be a Single Pod which will be running the 2 containers. The first container will contain the “MultiApp” image while the second container will contain the “MultiApi” image.

The containers will also need to communicate with one another. In my case it is the transfer of a Joke when the Web API is called.

Kindly use Kubernetes Volumes for storing valuable data for the Pods and this data will remain safe from even pod crashes. Check – Kubernetes Volume emptyDir and SSL Mount to volume.

Containers residing in a same Pod are accessible via “localhost”. I will be opening port 80 for the first container (this container will be running “MultiApp” image), while port 81 will be opened for the second container (this container will be running “MultiApi” image).

Therefore, this means the Web API address will be – https://localhost:81/api/Joke.

So, go to your MultiApp project and change the code which is calling the Web API. Recall this code is inside the Index.cshtml.cs file residing inside the Pages folder. I have shown this change by highlighted line, see below:

public async Task OnGet()
{
    using (var client = new System.Net.Http.HttpClient())
    {
        var request = new System.Net.Http.HttpRequestMessage();

        //request.RequestUri = new Uri("http://localhost:53205/api/Joke");
        request.RequestUri = new Uri("http://localhost:81/api/Joke");
        
        var response = await client.SendAsync(request);
        var joke = await response.Content.ReadAsStringAsync();

        var details = JObject.Parse(joke);
        ViewData["Joke"] = details["name"] + ";;" + details["text"] + ";;" + details["category"];
    }
}

Build Docker Images for the app inside Minikube VM

Let us build the Docker Images for these 2 projects. Remember the images should be build inside Minikube VM.

If you are in windows you can go inside the Minikube VM by running minikube docker-env command on your command prompt.

This command will show you environment variables created by minikube. You copy and paste the last one, which is @FOR /f "tokens=*" %i IN ('minikube -p minikube docker-env') DO @%i, on the command prompt and press enter. This will take you inside the VM created by Minikube.

minikube docker env command

For linux and macOS enter the following command on the terminal to enter minikube’s VM.

Eval $(minikube docker-env)

Now, in the command prompt, go to the directory containing this “docker-compose.yml” file and build it. The command is given below:

docker-compose build

This will create 2 images:

  • 1. multiapp
  • 2. multiapi

Run docker images command to confirm these 2 images are build. I have shown them in the below image.

docker images created

Create a Single Pod and it’s 2 Containers

I now have to create a Kubernetes Pod which will contain 2 Docker Containers. The container 1 will contain the multiapp docker image while the container 2 will contain the multiapi docker image. I have tried to explain this by the below image:

single pod containing multiple containers

So create a Kubernetes Deployment configuration file and name it like mydep.yaml. Add the following configurations to it:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: multi-container-dep
  labels:
    app: aspnet-core-multi-container-app
spec:
  replicas: 1
  selector: 
    matchLabels:
      component: multi-container
  template:
    metadata: 
      labels:
        component: multi-container
    spec:
      containers:
        - name: cmultiapp
          image: multiapp
          imagePullPolicy: Never
          ports:
            - containerPort: 80
        - name: cmultiapi
          image: multiapi
          imagePullPolicy: Never
          ports:
            - containerPort: 81
          env:
            - name: ASPNETCORE_URLS
              value: http://+:81

Important points to note:

    1. The name of this deployment is given by the metadata.name field.
  • 2. There are 2 containers specified inside the spec.containers field.
containers:
  - name: cmultiapp
    image: multiapp
    imagePullPolicy: Never
    ports:
      - containerPort: 80
  - name: cmultiapi
    image: multiapi
    imagePullPolicy: Never
    ports:
      - containerPort: 81
    env:
      - name: ASPNETCORE_URLS
        value: http://+:81

The 2 container names are “cmultiapp” and “cmultiapi”.

Notice the ports.containerPort fields for these 2 containers. For the first container I have exposed port 80 while the second container’s port 81 is exposed. The second container contains the web api and as discussed earlier it’s port 81 is exposed. So, my ASP.NET Core app on container 1 can access the ASP.NET Core app on container 2 with http://localhost:81.

It is to be noted that if the containers are contained inside a same k8s pod then you cannot open their same port numbers. This is the reason why I exposed different ports numbers for these 2 containers.

The web api app on the container 2 must also be told that it needs to allocated port 81 for it’s url. This is done by adding environment variable. See the env field which does this work:

env:
  - name: ASPNETCORE_URLS
    value: http://+:81

Let us apply the deployment. In your command prompt, navigate to the directory containing the deployment file, and run the kubectl apply command.

kubectl apply -f mydep.yaml

Next, run kubectl get pods command to confirm the pod is created inside your local k8s cluster. I have shown the Pod in the below image.

multi container pod created

Notice the ready column is showing “2/2” which means both the 2 containers are running.

Create a k8s Service for the Pod

Now I will create a service which will connect to this Pod. The service can be accessed on the browser. I will show this later on.

Create a service configuration yaml file with the following code:

apiVersion: v1
kind: Service
metadata:
  name: multi-container-service
spec:
  type: NodePort
  selector:
    component: multi-container
  ports:
    - port: 8080
      targetPort: 80

I have named this NodePort service as multi-container-service. The selector field targets the deployment we just created.

Also note the ports filed which exposes server on port 8080 and the service targets the port 80 of the pod. Recall on port 80 the MultiApp project will be executed.

ports:
  - port: 8080
    targetPort: 80

So now apply the service by running the kubectl apply -f myservice.yaml. The myservice.yaml file is the name of this service file. It can be any name.

Now open the service on the browser. So, run the following command on your command prompt window, and this will open a proxy tunnel in your browser.

minikube service multi-container-service

A new browser tab will open and you can access your multi-container app running from a single kubernetes pod. The below image shows this browser tab with the ASP.NET Core app we created before.

multi container single pod service browser

Get a Shell to the Containers

I will not go inside the containers and run some command inside the shell. The pod running the containers is named as multi-container-dep-5bdd897d64-jg25r. You can simply run the kubectl get pods command to find it’s name.

multi container pod created

Next run the describe pod command which is:

kubectl describe pod multi-container-dep-5bdd897d64-jg25r

It will describe the pod, telling all the containers it is running. See below given image.

kubectl describe pod command output

I can get a shell to the containers. I do it for the cmultiapp container. The command to run is:

kubectl exec -it multi-container-dep-5bdd897d64-jg25r -c cmultiapp -- /bin/bash
container shell

Now Install curl inside the container, you have to run the following 2 commands one by one.

apt-get update
apt-get install curl

Now run the curl command for the api located at localhost:81/api/Joke. The command will be:

curl localhost:81/api/Joke

This command will return you the json of the Joke.

curl inside docker container

If you run the curl for only localhost i.e.

curl localhost

Then it will give the page source of the index page of “MultiApp” project.

Download the source codes:

Download

Conclusion

In this Kubernetes tutorial you learnt to host a multi-container dockized ASP.NET Core app on a single Pod. Later on, I also explained how these 2 containers will communicate with one another and share jokes json. Kindly share and let this tutorial reach more audiences of dot net.

SHARE THIS ARTICLE

  • linkedin
  • reddit
yogihosting

ABOUT THE AUTHOR

I hope you enjoyed reading this tutorial. If it helped you then consider buying a cup of coffee for me. This will help me in writing more such good tutorials for the readers. Thank you. Buy Me A Coffee donate

Leave a Reply

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