DEV Community

loading...

Streaming on Cloud with C#

Nishant Singh
・14 min read

In this workshop, we will create a gRPC streaming server, consume it using a web service. We will use following :

  • Dot net Core 3.1
  • Docker
  • gRPC
  • SignalR
  • Minikube
  • Azure CLI
  • Azure Kubernetes Service

Alt Text

This will be helpful for anyone who is just getting started with gRPC in a new project. It assumes basic knowledge of Docker, Kubernetes, C#, Dotnet core.

If you are a complete beginner to above, please consider doing below exercises first :

This is a purely hands-on workshop and does not talk about gRPC or protobuf in depth.

Contents

Shell, Dot Net Core 3.1, BloomRPC, Docker

Create service using dotnet new grpc

Test a gRPC endpoint using BloomRPC

Create a console app using dotnet new console and get response from a gRPC endpoint

Add streaming endpoint to our grpc server

Consume our streaming endpoint in the client

Create a streaming web app with SIgnalR

Consume data from gRPC server in the webapp and stream to end client

Run our web streaming service as a docker container

Create Kubernetes configuration for our app and services

Create a cluster on Azure Kubernetes Service using Azure CLI and deploy app on cloud.

Pre-requisites

  • Shell

All commands are written for bash on MacOS, but you can use them in a GitBash or Powershell. We have used CLI instead of Visual Studio as it gives better view of what is going on underneath.

  • DotNet Core 3.1

We have used 3.1 as DotNet 5 is still in beta. Though everything should work the same with 5. If it does not, please help us all by reporting it.

  • BloomRPC

Download from here: https://appimage.github.io/BloomRPC/. We use it to tests our gRPC server, before we start writing a client ourselves.

  • Docker

  • Minikube

  • Azure Account

  • Azure CLI

Step1 - Creating a gRPC service

# Create a folder for our project
mkdir dotnet-docker-grpc-stream

# Create a new project named as Service
dotnet new grpc -o Service

# Generate a gitignore file
dotnet new gitignore

# Start our server
cd Service
dotnet run

# Commit your code
git add --all
git commit -m "Created a web service"
Enter fullscreen mode Exit fullscreen mode

If you are using MacOS, you will get error. Kestrel doesn't support HTTP/2 with TLS on macOS and older Windows versions such as Windows 7.

You will be able to run the server after next step in which we will run service inside docker container.

If you still want to fix this, read https://docs.microsoft.com/en-us/aspnet/core/grpc/troubleshoot?view=aspnetcore-3.1#unable-to-start-aspnet-core-grpc-app-on-macos

Create a file Service/Dockerfile

# Service/Dockerfile

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /source

COPY ./*.csproj .
RUN dotnet restore

COPY . .
RUN dotnet publish -c release -o /app --no-restore

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
WORKDIR /app

COPY --from=build /app .

EXPOSE 80  
ENTRYPOINT ["dotnet", "Service.dll"]
Enter fullscreen mode Exit fullscreen mode

Create another file Service/.dockerignore to tell docker which files to ignore

# Service/.dockerignore

**/.dockerignore
**/.project
**/.vs
**/.idea
**/.vscode
**/*.*proj.user
**/bin
**/Dockerfile*
**/obj
Enter fullscreen mode Exit fullscreen mode

Now run the server as docker container:

# Build docker image
docker build -t server .

# Run service as a container
docker run -p 5000:80 server
Enter fullscreen mode Exit fullscreen mode

Checkout a protobuf created at dotnet-docker-grpc-stream/Service/Protos/greep.proto. We will use it in next step to test our server.

Step 2 - Testing a gRPC endpoint

Just like we can use postman to test an HTTP endpoint, we will use BloomRPC to test our gRPC endpoint.

Download and open the app from here.

  • select the file from our project : dotnet-docker-grpc-stream/Service/Protos/greep.proto

  • enter the address for our gRPC server in address bar as localhost:5000

  • If the server is not running, start it as a container :

docker run -p 5000:80 server
Enter fullscreen mode Exit fullscreen mode

Now click on the play button to test our endpoint.

Alt Text

Step - 3 Create a client app

In this step we will create a .netcore console app and hit our server to read from gRPC endpoint.

Create a console app in the root directory of our project

# Go to our project's root (assuming you are in Service directory)
cd ../
dotnet new console -o Client
Enter fullscreen mode Exit fullscreen mode

We need the following libraries to make our client work :

  • Grpc.Net.Client, which contains the .NET Core client.
  • Google.Protobuf, which contains protobuf message APIs for C#.
  • Grpc.Tools, which contains C# tooling support for protobuf files. The tooling package isn't required at runtime, so the dependency is marked with PrivateAssets="All".
dotnet add package Grpc.Net.Client
dotnet add package Google.Protobuf
dotnet add package Grpc.Tools
Enter fullscreen mode Exit fullscreen mode

Add the proto files from our server to client project at Client/Protos/greet.proto

mkdir Protos
cp ../Service/Protos/greet.proto ./Protos/
Enter fullscreen mode Exit fullscreen mode

Modify the Client/Client.csproj file to add a reference to the proto file

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>
Enter fullscreen mode Exit fullscreen mode

Now create the Program.cs as

using System;
using System.Threading.Tasks;
using Grpc.Net.Client;
using Service;

namespace Client
{
  class Program
  {
    static void Main()
    {
      // Ignore this for now
      AppContext.SetSwitch(
        "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);

      SayHello();
    }

    private static void SayHello()
    {
      using var channel = GrpcChannel.ForAddress("http://localhost:5000");

      // Greeter service is defined in hello.proto
      // <service-name>.<service-name>Client is auto-created
      var client = new Greeter.GreeterClient(channel);

      // HelloRequest is defined in hello.proto
      var request = new HelloRequest();
      request.Name = "Nishant";

      // SayHello method is defined in hello.proto
      var response = client.SayHello(request);

      // HelloResponse.Message is defined in hello.proto
      Console.WriteLine(response.Message);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
# Run the server if not already running
docker run -p 5000:80 server

# Run client (in Client dir)
dotnet run
Enter fullscreen mode Exit fullscreen mode

Step 4 - Create a streaming endpoint

Now we will create a protobuf file for a streaming interface and take a look at the protobuf syntax.

We will create a server that can take a financial instrument and stream its price in real-time.

Define protobuf

It is very much like defining a class in C#. Though there is one big difference that there is a number associated with each field in message. This number is used to identify the field in binary message data. Unlike REST, the messages in gRPC are sent and received in binary. Which makes is much more efficient.

Create our prices.proto file in Server/Protos/prices.proto:

// Server/Protos/prices.proto

syntax = "proto3";
package prices;

// Code will be generated in this namespace
option csharp_namespace = "Service";

// The gRPC interface definition
service Pricing {
  // Note that it returns "stream" of PriceResponse
  rpc Subscribe (PriceRequest) returns (stream PriceResponse);
}

// The request message
message PriceRequest {
  string uic = 1;
  string assetType = 2;
}

// The response message
message PriceResponse {
  string quote = 1;
}
Enter fullscreen mode Exit fullscreen mode

Take a note of returns statement here. The return type is a stream of PriceResponse instead of a single instance of PriceResponse.

Here we define the interface for our service. It is very much like an interface in C#. This interface is known by both client and server. The nugets on client and server generate code based on this protobuf file.

Now we will include the file in our Service/Service.csproj.

<!-- Service/Service.csproj --> 

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
  <Protobuf Include="Protos\prices.proto" GrpcServices="Server"/>
</ItemGroup>
Enter fullscreen mode Exit fullscreen mode

Notice that it already has a greet.proto defined that was created by default and that we used in first part of the workshop.

Create implementation of the endpoint

Create file Services/PricingService.cs:

using System;
using System.Threading.Tasks;
using Grpc.Core;

namespace Service
{
  // Pricing is the name or interface in prices.proto
  // <service-name>.<service-name>Base class is auto generated
  public class PricingService : Pricing.PricingBase
  {

    public override async Task Subscribe(
      PriceRequest request, 
      IServerStreamWriter<PriceResponse> responseStream, 
      ServerCallContext context
    ){
      var i = 0;
      while (true)
      {
        // At every second, keep sending a fake response
        await Task.Delay(TimeSpan.FromSeconds(1));
        var quote = $"Quote#{++i} for {request.Uic}-{request.AssetType}";
        Console.WriteLine($"Sent: {quote}");
        var response = new PriceResponse{Quote = quote};
        await responseStream.WriteAsync(response);
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Now register our PricingService in Startup.cs

 app.UseEndpoints(endpoints =>{
   endpoints.MapGrpcService<GreeterService>();
+  endpoints.MapGrpcService<PricingService>();
}
Enter fullscreen mode Exit fullscreen mode

Notice that the GreeterService was created by default with the project.

Now let's build and run our service as a docker image:

docker build -t server .
docker run -p 5000:80 server
Enter fullscreen mode Exit fullscreen mode

Now in BloomRPC, click on + icon and add our Service/Protos/prices.proto

Alt Text

Step 5: Create a streaming client

Now that our server has a streaming endpoint, let us create a client code that can listen to the stream.

Copy the Service/Protos/prices.proto to Client/Protos/prices.proto

cd Client
cp ../Service/Protos/prices.proto ./Protos/
Enter fullscreen mode Exit fullscreen mode

Remember we can not consume a gRPC endpoint unless we have the proto file for the interface.

Add the prices.proto to Client/Client.csproj

 <ItemGroup>
     <Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
+    <Protobuf Include="Protos\prices.proto" GrpcServices="Client" />
 </ItemGroup>
Enter fullscreen mode Exit fullscreen mode

Add another method in our

private static async Task SubscribeToStream()
{
  using var channel = GrpcChannel.ForAddress("http://localhost:5000");

  var client = new Pricing.PricingClient(channel);
  var request = new PriceRequest{Uic = "211", AssetType = "Stock"};

  var streamReader = client.Subscribe(request).ResponseStream;

  while (await streamReader.MoveNext())
  {
    Console.WriteLine($"Received: {streamReader.Current}");
  }

  Console.WriteLine("Gracefully ended.");
}
Enter fullscreen mode Exit fullscreen mode

Now call this method in the main function :

- static void Main()
+ static async Task Main()
 {
   AppContext.SetSwitch(
     "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", 
     true);

   SayHello();
+  await SubscribeToStream();
 }
Enter fullscreen mode Exit fullscreen mode

And add this import on top:

using Grpc.Net.Client;
Enter fullscreen mode Exit fullscreen mode

Now lets test our client

# Run the server if not running
docker run -p 5000:80 server

# Run client (in Client dir)
dotnet run
Enter fullscreen mode Exit fullscreen mode

Expected output :

Received: { "quote": "Quote#1 for 211-Stock" }
Received: { "quote": "Quote#2 for 211-Stock" }
Received: { "quote": "Quote#3 for 211-Stock" }
Received: { "quote": "Quote#4 for 211-Stock" }
Enter fullscreen mode Exit fullscreen mode

Our gRPC streaming client-server is working. You should keep seeing a new message on console till you kill the client.

Step 6 - Create a streaming web server

In this part, we will create a web server that will have an html page and will stream data to the client.

For now, we will just stream some random data before we make it consume our gRPC service.

Create streaming web service

# Creat a webapp for streaming to client
dotnet new webapp -o StreamWebService

cd StreamWebService

# Create dockerignore for the project
dotnet new gitignore

# Run the app (make sure gRPC server is not running at 5000)
dotnet run
Enter fullscreen mode Exit fullscreen mode

Now open http://localhost:5000/ and check the app

Alt Text

In StreamWebService/Startup.cs, initialize SignalR on server side by adding this to ConfigureServices

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
+       services.AddSignalR();
    }
Enter fullscreen mode Exit fullscreen mode

Create a file StreamWebService/PricingHub.cs:

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;

namespace StreamWebService
{
  public class PricingHub : Hub
  {

    public async IAsyncEnumerable<string> Subscribe(
            string uic,
            string assetType,
            [EnumeratorCancellation]
            CancellationToken cancellationToken)
    {
      for (var i = 0; i < 10; i++)
      {
        // Check the cancellation token regularly so that the server will stop
        // producing items if the client disconnects.
        cancellationToken.ThrowIfCancellationRequested();

        yield return $"{i} : {uic}-{assetType}";

        // Use the cancellationToken in other APIs that accept cancellation
        // tokens so the cancellation can flow down to them.
        await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Not add the PricingHub to endpoints in StreamWebService/Startup.cs

  app.UseEndpoints(endpoints =>
  {
    endpoints.MapRazorPages();
+   endpoints.MapHub<PricingHub>("/subscribe/infoprice");
  });
Enter fullscreen mode Exit fullscreen mode

Add SignalR client-side library with libman

# Install libman
dotnet tool install -g Microsoft.Web.LibraryManager.Cli

# Add SignalR client using libman
libman install @microsoft/signalr@latest -p unpkg -d wwwroot/js/signalr --files dist/browser/signalr.js --files dist/browser/signalr.min.js
# wwwroot/js/signalr/dist/browser/signalr.js written to disk
# wwwroot/js/signalr/dist/browser/signalr.min.js written to disk
# Installed library "@microsoft/signalr@latest" to "wwwroot/js/signalr"
Enter fullscreen mode Exit fullscreen mode

Edit Pages\Index.cshtml

@page
<div class="container">
    <div class="row">
        <form>
            <div class="form-row">
                <div class="form-group col-md-6">
                    <input type="text" 
                           id="uic" class="form-control" 
                           placeholder="uic"/>
                </div>
                <div class="form-group col-md-6">
                    <input class="form-control" 
                           type="text" 
                           id="assetType" 
                           placeholder="Asset Type"/>
                </div>
            </div>
            <button type="button" 
                    id="startStreaming" 
                    class="btn btn-primary" 
                    disabled="true">Start Streaming</button>
        </form>
    </div>
    <hr/>
    <div class="row">
        <div class="col-6">
            <ul id="messagesList" style="list-style: none; padding:  0; margin:  0"></ul>
        </div>
    </div>
</div>

<script src="~/js/signalr/dist/browser/signalr.js"></script>
<script src="~/js/pricing.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
Enter fullscreen mode Exit fullscreen mode

Create file StreamWebService/wwwroot/js/pricing.js

// wwwroot/js/pricing.js
"use strict";

const pricingConnection = new signalR.HubConnectionBuilder()
    .withUrl("/subscribe/infoprice")
    .build();

const showMessage = (content) => {
    var li = document.createElement("li");
    li.textContent = content;
    document.getElementById("messagesList").prepend(li);
};

const setButtonEnabled = status => 
    document.getElementById("startStreaming").disabled = !status;

pricingConnection.start().then( ()=> {
    setButtonEnabled(true);
    showMessage("Conncted with server");
}).catch((err) => {
    showMessage("Failed to connect to server" + err.toString());
});

document
    .getElementById("startStreaming")
    .addEventListener("click", () => {
        setButtonEnabled(false);
        const uic = document.getElementById("uic").value;
        const assetType = document.getElementById("assetType").value;

        pricingConnection.stream("Subscribe", uic, assetType)
            .subscribe({
                next: showMessage,
                complete: () => {
                    showMessage("Stream completed");
                    setButtonEnabled(true);
                },
                error: showMessage,
            });
        event.preventDefault();
    });
Enter fullscreen mode Exit fullscreen mode

Step 6 - Add gRPC Client to webapp

This step is almost the same as the Create gRPC client step. Instead of a console app, this time we will add gRPC client to a web app.

Add required nugets:

dotnet add package Grpc.Net.Client
dotnet add package Google.Protobuf
dotnet add package Grpc.Tools
Enter fullscreen mode Exit fullscreen mode

Add the proto files from our server to this project at StreamWebService/Protos/prices.proto

mkdir Protos
cp ../Service/Protos/prices.proto ./Protos/
Enter fullscreen mode Exit fullscreen mode

Add prices.proto to out StreamWebService/StreamWebService.csproj

  <ItemGroup>
    <Protobuf Include="Protos\prices.proto" GrpcServices="Client" />
  </ItemGroup>
Enter fullscreen mode Exit fullscreen mode

Update StreamWebService/Program.cs :

  public static void Main(string[] args)
  {
+   AppContext.SetSwitch(
+    "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", 
+       true
+   );
    CreateHostBuilder(args).Build().Run();
  }
Enter fullscreen mode Exit fullscreen mode

This is required as our gRPC server serves on HTTP port, so lets enable insecure HTTP2 support (gRPC uses HTTP2 under the hood).

Update StreamWebService/PricingHub.cs to stream data from our gRPC service :

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using Grpc.Core;
using Grpc.Net.Client;
using Microsoft.AspNetCore.SignalR;
using Service;

namespace StreamWebService
{
  public class PricingHub : Hub
  {

    public async IAsyncEnumerable<string> Subscribe(
            string uic,
            string assetType,
            [EnumeratorCancellation]
            CancellationToken cancellationToken)
    {
      var url = "http://localhost:5000";
      using var channel = GrpcChannel.ForAddress(url);

      yield return $"Info: Openening channel to : {url}";

      var client = new Pricing.PricingClient(channel);
      var request = new PriceRequest{Uic = uic, AssetType = assetType};

      var streamReader = client.Subscribe(request).ResponseStream;

      yield return "Info: Invoking stream..";

      while (await streamReader.MoveNext())
      {
        cancellationToken.ThrowIfCancellationRequested();
        yield return $"Data: {streamReader.Current}";
      }

      Console.WriteLine("Gracefully ended.");
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Now let us consume the gRPC streaming endpoint server in web app

# Run the server on 5000
docker run -p 5000:80 server

# Run on port 300 (in StreamWebService dir)
dotnet run --urls=http://localhost:3000/
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:3000/ to check our page.

Alt Text

Step 7 - Run web app as a container

Before we containerize our web app, we need to make sure we can pass the URL to gRPC endpoint as a runtime variable.

Edit the StreamWebService/PricingHub.cs

- var url = "http://localhost:5000";
+ var url = Environment.GetEnvironmentVariable("PRICING_STREAM_ENDPOINT");
  using var channel = GrpcChannel.ForAddress(url);
Enter fullscreen mode Exit fullscreen mode

Create a file StreamWebService/Dockerfile

#StreamWebService/Dockerfile

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /source

COPY ./*.csproj .
RUN dotnet restore

COPY . .
RUN dotnet publish -c release -o /app --no-restore

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
WORKDIR /app

COPY --from=build /app .

EXPOSE 80  
ENTRYPOINT ["dotnet", "StreamWebService.dll"]
Enter fullscreen mode Exit fullscreen mode

Create StreamWebService/.dockerignore

**/.dockerignore
**/.project
**/.vs
**/.idea
**/.vscode
**/*.*proj.user
**/bin
**/Dockerfile*
**/obj
Enter fullscreen mode Exit fullscreen mode

Run gRPC server and web app as docker containers

# Build docker container for web service (in StreamWebService dir)
docker build -t stream-web-app .

# Run the server on 5000 (if not already running)
docker run -p 5000:80 server

# Run on port 300 
docker run -p 3000:80 -e PRICING_STREAM_ENDPOINT="http://host.docker.internal:5000" stream-web-app

Enter fullscreen mode Exit fullscreen mode

Open http://localhost:3000/ to check our page.

Step 8 - Create Kubernetes definition

Before creating our Kubernetes configuration, we will push our docker images to docker hub.

docker login

# User your own username instead of nishants
docker tag server:latest nishants/grpc-pricing-server:v0.1 
docker push nishants/grpc-pricing-server:v0.1 

# User your own username instead of nishants
docker tag stream-web-app:latest nishants/stream-web-app:v0.1 
docker push nishants/stream-web-app:v0.1 
Enter fullscreen mode Exit fullscreen mode

Open docker hub and make sure your image is publicly accessible:

Alt Text

Create a StreamWebService/k8s/web.yml

# This part creates a load balancer pod that receives traffic from
# internet and load-balances to our pods
apiVersion: v1
kind: Service
metadata:
  name: stream-web-app-service
spec:
  selector:
    app: stream-web-app     # This makes load balancer point to stream-web-app deployment
  ports:
    - port: 80        
      targetPort: 80  # The port our container(in pods) listens to
  type: LoadBalancer
---
# This part creates a pod that runs our docker image
apiVersion: apps/v1
kind: Deployment
metadata:
  name: stream-web-app
spec:
  # Keep two replicas of our app
  replicas: 2  
  selector:
    matchLabels:
      app: stream-web-app
  template:
    metadata:
      labels:
        app: stream-web-app
    spec:
      containers:
        - name: stream-web-app
          image: nishants/stream-web-app:v0.1  # Our docker image on docker hub
          ports:
            - containerPort: 80           # Port that our app listens to
          env:
            - name: PRICING_STREAM_ENDPOINT
              value: http://pricing-grpc-service:5000
          imagePullPolicy: Always
Enter fullscreen mode Exit fullscreen mode

Now we will create a Kubernetes cluster locally to test our manifest file. You can use Minikube or kind for this.

# Start a single node cluster locally
minikube start

# Check our local cluster
kubectl cluster-info

# Deploy our service to local cluster
kubectl apply -f StreamWebService/k8s/web.yml
# service/stream-web-app-service created
# deployment.apps/stream-web-app created

# Check our service in browser
minkube service stream-web-app-service
Enter fullscreen mode Exit fullscreen mode

Our service will fail to connect to gRPC server, even if gRPC service is running as a docker container. We will need to deploy our gRPC server in the Kubernetes cluster.

Create a Service/k8s/pricing-service.yml

apiVersion: v1
kind: Service
metadata:
  name: pricing-grpc-service   # Name of service (we use this as domain in todo-app config)
spec:
  selector:
    app: pricing-grpc       # Exposes stream-web-app as service
  ports:
    - port: 5000
      targetPort: 80 # Map service port to container port
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pricing-deployment     # Name of deployment, we wil refer this in service
spec:
  replicas: 2  
  selector:
    matchLabels:
      app: pricing-grpc
  template:
    metadata:
      labels:
        app: pricing-grpc
    spec:
      containers:
        - name: pricing-grpc
          image: nishants/grpc-pricing-server:v0.1   # Image name for stream-web-app container
          ports:
            - containerPort: 80
Enter fullscreen mode Exit fullscreen mode

Deploy our gRPC server in Kubernetes cluster

kubectl apply -f Service/k8s/pricing-service.yml

# Check our service in browser
minkube service stream-web-app-service
Enter fullscreen mode Exit fullscreen mode

This time our service should be able to get data from gRPC service.

Step 9 - Deploy app on Azure Kubernetes Service

Login to Azure CLI and create a Kubernetes cluster

# login using azure cli
az login

RESOURCE_GROUP=pricing-streaming-demo
CLUSTER_NAME=pricing-streaming-demo
REGION=westeurope

# Create resource
az group create --name $RESOURCE_GROUP --location $REGION

# Create cluster on AKS with 1 node
az aks create --resource-group $RESOURCE_GROUP \
--name $CLUSTER_NAME \
--node-count 1 \
--enable-addons monitoring \
--generate-ssh-keys

# Allow kubectl to connect and manage our AKS clustuer
az aks get-credentials \
--resource-group $RESOURCE_GROUP \
--name $CLUSTER_NAME

# Check if our node is up and running
kubectl get nodes
# NAME                                STATUS   ROLES   AGE     VERSION
# aks-nodepool1-36600731-vmss000000   Ready    agent   2m58s   v1.17.10

# Deploy our app 
kubectl apply -f Service/k8s/pricing-service.yml -f StreamWebService/k8s/web.yml

# View the external ip adress of our web app
kubectl get service/stream-web-app-service
# NAME                     TYPE           CLUSTER-IP     EXTERNAL-IP     
# stream-web-app-service   LoadBalancer   10.0.105.141   51.105.150.87   
Enter fullscreen mode Exit fullscreen mode

Opent the external ip of your endpoint in browser. For e.g. for above output it will be http://51.105.150.87

Alt Text

Delete your cluster

# delete cluster
az group delete --name $RESOURCE_GROUP --yes --no-wait
Enter fullscreen mode Exit fullscreen mode

This is it !

Alt Text

Discussion (0)