Migrating from Kubernetes Deployment to Knative Serving

When I talk about Knative, I often get questions on how to migrate an app from Kubernetes Deployment (sometimes with Istio) to Knative and what are the differences between the two setups.

First of all, everything you can do with a Knative Service, you can probably do with a pure Kubernetes + Istio setup and the right configuration. However, it’ll be much harder to get right. The whole point of Knative is to simplify and abstract away the details of Kubernetes and Istio for you.

In this blog post, I want to answer the question in a different way. I want to start with a Knative Service and show how to setup the same service with Kubernetes + Istio the ‘hard way’.

Knative Service

In my previous post, I showed how to deploy an autoscaled, gRPC enabled, ASP.NET Core service with Knative. This was the Knative service definition yaml file:

apiVersion: serving.knative.dev/v1beta1
kind: Service
metadata:
  name: grpc-greeter
  namespace: default
spec:
  template:
    spec:
      containers:
        - image: docker.io/meteatamel/grpc-greeter:v1
          ports:
          - name: h2c
            containerPort: 8080

Notice the simplicity of the yaml file. It had the container image and the port info (HTTP2/8080) and not much else. Once deployed, Knative Serving took care of all the details of deploying the container in a Kubernetes pod, exposing that pod to the outside world via Istio ingress and also setting up autoscaling.

What does it take to deploy the same service in a Kubernetes + Istio cluster without Knative? Let’s take a look.

Kubernetes Deployment

First, we need a Kubernetes Deployment to encapsulate the container in a pod. This is how the deployment yaml looks like:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: grpc-greeter
spec:
  selector:
      matchLabels:
        app: grpc-greeter
  template:
    metadata:
      labels:
        app: grpc-greeter
    spec:
      containers:
      - name: grpc-greeter
        image: docker.io/meteatamel/grpc-greeter:v1
        ports:
        - name: h2c
          containerPort: 8080

This is already more verbose than a Knative service definition. Once deployed, we’ll have a pod running the container.

Kubernetes Service

Next step is to expose the pod behind a Kubernetes Service:

apiVersion: v1
kind: Service
metadata:
  name: grpc-greeter-service
spec:
  ports:
  - name: http2
    port: 80
    targetPort: h2c
  selector:
    app: grpc-greeter

This will expose the pod behind port 80. However, it’s not publicly accessible yet until we setup networking in Istio.

Istio Gateway and VirtualService

In an Istio cluster, we need to first setup a Gateway to enable external traffic on a port/protocol. In our case, our app requires HTTP on port 80. This is the Gateway definition we need:

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: grpc-gateway
spec:
  selector:
    istio: ingressgateway # use istio default controller
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"

We now have traffic enabled on port 80 but we need to map the traffic to the Kubernetes Service we created earlier. That’s done via a VirtualService:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: grpc-virtualservice
spec:
  hosts:
  - "*"
  gateways:
  - grpc-gateway
  http:
  - route:
    - destination:
        host: grpc-greeter-service

Our pod is finally publicly accessible. You can use the GrpcGreeterClient from my previous blog to point to the Istio Ingress Gateway IP and you should see a response from our service:

> dotnet run
Greeting: Hello GreeterClient
Press any key to exit…

Phew! A lot of steps to deploy a publicly accessible container without Knative. We still need to setup autoscaling of pods to get parity with Knative Serving but I’ll leave that as an exercise to the reader.

I hope it’s clear now that Knative makes it easier to deploy autoscaled containers with much less configuration. Knative’s higher level APIs allow you to focus more on your code in a container than the underlying details of how that container is deployed and how its traffic is managed with Kubernetes and Istio.

Thanks to Matt Moore from the Knative team for giving me the idea for the blog post.

Serverless gRPC + ASP.NET Core with Knative

knativegrpc

I was recently going through the ASP.NET Core updates in .NET Core 3.0 Preview 3 post, this section got my attention: gRPC template.

Apparently, .NET Core 3.0 got a new gRPC template for easily building gRPC services with ASP.NET Core. I tested gRPC and .NET before and I have some samples in my grpc-samples-dotnet repo. Even though gRPC and .NET worked before, it wasn’t that straightforward to setup. I was curious to try out the new gRPC template and see how it helped.

Additionally, I wanted to go beyond a simple gRPC service and see what it takes to turn it into a serverless Knative service with all the benefits of Knative such as autoscaling, revisions, etc.

If you want to try this out yourself, I have a new section in my Knative tutorial: Serverless gRPC with Knative with detailed instructions. In this blog post, I want to highlight some of the main steps for a serverless, gRPC enabled, ASP.NET Core service deployed to Knative on Google Kubernetes Engine (GKE).

Create a gRPC service with the new template

Creating a HelloWorld gRPC service with .NET Core 3.0 couldn’t be any simpler with the gRPC template. This is the command:

> dotnet new grpc -o GrpcGreeter

This command does a few things in the background:

  • Creates an ASP.NET Core project with all gRPC dependencies already included.
  • Creates a gRPC service definition file named greet.proto.
  • Auto-generates all gRPC stubs based on the service definition file.
  • Creates a gRPC service (GreeterService.cs) based on the auto-generated gRPC stub.
  • Configures the gRPC pipeline in Startup.cs to map to GreeterService.cs

The end result is that you have all of gRPC details taken care of and you can simply start running the service:

> dotnet run
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: http://localhost:50051

This is much simpler than what I had to before. Great job from the .NET Core team!

Containerize the gRPC service

Next step is to containerize the gRPC service, so it can be deployed to Knative. The Dockerfile looks something like this:

FROM mcr.microsoft.com/dotnet/core/sdk:3.0

WORKDIR /app
COPY *.csproj .
RUN dotnet restore

COPY . .

RUN dotnet publish -c Release -o out

ENV PORT 8080

ENV ASPNETCORE_URLS http://*:${PORT}

CMD ["dotnet", "out/GrpcGreeter.dll"]

I’m using .NET Core 3.0 as the base image and making sure that the service runs on port 8080, instead of the default gRPC port 50051. Nothing special.

Deploy the Knative service

Once the image is built and pushed, define the Knative service in a service.yamlfile:

apiVersion: serving.knative.dev/v1beta1
kind: Service
metadata:
  name: grpc-greeter
  namespace: default
spec:
  template:
    spec:
      containers:
        # Replace {username} with your actual DockerHub
        - image: docker.io/{username}/grpc-greeter:v1
          ports:
          - name: h2c
            containerPort: 8080

This is a plain Knative service definition pointing to the image. The only special part is the ports section where we define a h2c port 8080. This tells Knative that container is expecting HTTP/2 traffic on port 8080.

Deploy the Knative service:

> kubectl apply -f service.yaml

Make sure that a pod with gRPC service is running:

> kubectl get pods

NAME
grpc-greeter-5tpwl-deployment-6fb423289c5-r5qmt

Test it out

To test out the gRPC service, you need the corresponding gRPC enabled client. You can refer to Create a gRPC client part of my tutorial on how to create a GrpcGreeterClient.cs. Once you have the client, just point to your Knative service. You should see a reply from the gRPC service running in Knative:

> dotnet run

Greeting: Hello GreeterClient
Press any key to exit...

On the surface, this looks like the response you’d get from a plain gRPC service. However, it’s a Knative managed service, meaning you have 0-n automatic scaling, revisions and all other benefits of running a service in Knative.


.NET Core 3.0 makes it really easy to get started with gRPC and Knative transforms plain gRPC into serverless-style services that autoscale and respond to events. This is a powerful combination and hopefully this blog post and the corresponding tutorial gave you a glimpse of how to use .NET Core with gRPC and Knative.