Deploy a gRPC Service

Python gRPC Hello World Service

The complete example can be found at Python gRPC Helloworld - Truefoundry Examples

In this example, we will deploy a simple service with a Hello world RPC. This example was adapted from the official python gRPC quickstart: https://grpc.io/docs/languages/python/quickstart/

Project structure

Our final project structure will look like this

.
├── deploy.py
├── greeter_client_with_options.py
├── greeter_server_with_reflection.py
├── helloworld.proto
├── helloworld_pb2.py
├── helloworld_pb2.pyi
├── helloworld_pb2_grpc.py
└── requirements.txt

Step 1: Install grpcio and grpc-tools

pip install grpcio==1.51.3 grpcio-reflection==1.51.3 grpcio-tools==1.51.3

Let's also create a requirements.txt and add dependencies which will be useful later.

requirements.txt

grpcio==1.51.3
grpcio-reflection==1.51.3
grpcio-tools==1.51.3
protobuf==4.22.0

Step 2: Declare message and rpc

Create a helloworld.proto with the following contents

helloworld.proto

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

Step 3: Next generate Python definitions

python -m grpc_tools.protoc -I. --python_out=. --pyi_out=. --grpc_python_out=. helloworld.proto

This will write helloworld_pb2.py, helloworld_pb2.pyi and helloworld_pb2_grpc.py to your project directory.

Step 4: Implement the rpc and gRPC Server

Next, we implement the SayHello rpc and write code to start the gRPC Server on port 50051

greeter_server_with_reflection.py

from concurrent import futures
import logging

import grpc
from grpc_reflection.v1alpha import reflection
import helloworld_pb2
import helloworld_pb2_grpc


class Greeter(helloworld_pb2_grpc.GreeterServicer):
    def SayHello(self, request, context):
        logging.info('Now greeting %s' % request.name)
        return helloworld_pb2.HelloReply(message='Hello, %s!' % request.name)


def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
    SERVICE_NAMES = (
        helloworld_pb2.DESCRIPTOR.services_by_name['Greeter'].full_name,
        reflection.SERVICE_NAME,
    )
    reflection.enable_server_reflection(SERVICE_NAMES, server)
    server.add_insecure_port('[::]:50051')
    server.start()
    logging.info('Server started!')
    server.wait_for_termination()


if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO, format=logging.BASIC_FORMAT)
    serve()

Step 5: Deploy it using Truefoundry

We can now deploy our service using Python SDK or YAML spec. Make sure you have servicefoundry installed and setup

Via Python SDK

Create a deploy.py

deploy.py

import argparse
import logging
from servicefoundry import Service, Build, PythonBuild, Resources, Port, AppProtocol

logging.basicConfig(level=logging.INFO, format=logging.BASIC_FORMAT)
parser = argparse.ArgumentParser()
parser.add_argument("--workspace_fqn", type=str, required=True,
                    help="FQN of the workspace to deploy to")
args = parser.parse_args()

service = Service(
    name="grpc-py-helloworld",
    image=Build(
        build_spec=PythonBuild(
            python_version="3.9",
            requirements_path="requirements.txt",
            command="python greeter_server_with_reflection.py"
        )
    ),
    resources=Resources(
        cpu_request=0.2,
        cpu_limit=0.5,
        memory_request=500,
        memory_limit=500,
    ),
    ports=[
        Port(
            port=50051,
            app_protocol=AppProtocol.grpc,
            host="<Provide a host value based on your configured domain>"
            # Note: Your cluster should allow subdomain based routing (*.yoursite.com) for gRPC to work correctly via public internet.
            # A host matching the wildcard base domain for the cluster can be explicitly configured by passing in `host`
        ),
    ],
)

service.deploy(workspace_fqn=args.workspace_fqn)

Now we can deploy by simply running this file with Workspace FQN

python deploy.py --workspace_fqn <YOUR WORKSPACE FQN HERE>

Via YAML Spec

Create a deploy.yaml with the following spec:

deploy.yaml

name: grpc-py-helloworld
type: service
image:
  type: build
  build_spec:
    type: tfy-python-buildpack
    command: python greeter_server_with_reflection.py
    python_version: '3.9'
    requirements_path: requirements.txt
    build_context_path: ./
  build_source:
    type: local
ports:
  - port: 50051
    expose: true
    protocol: TCP
    app_protocol: grpc
    host: <Provide a host value based on your configured domain>
replicas: 1
resources:
  cpu_limit: 0.5
  cpu_request: 0.2
  memory_limit: 500
  memory_request: 500

Now we can deploy by simply calling sfy deploy with Workspace FQN

sfy deploy --file deploy.yaml --workspace-fqn <YOUR WORKSPACE FQN HERE>

Step 6: Get the Endpoint URL

Once deployed, we can find the Service in the Deployments section of the platform and get the Endpoint

Step 7: Interacting with our Service

We can now interact with our service by passing the Endpoint from Step 6.

Create a client python file as follows:

greeter_client_with_options.py

from __future__ import print_function
import argparse

import logging

import grpc
import helloworld_pb2
import helloworld_pb2_grpc

parser = argparse.ArgumentParser()
parser.add_argument(
    "--host",
    type=str,
    required=True,
    help="Host of the deployed service",
)
parser.add_argument(
    "--name",
    type=str,
    required=False,
    default="Truefoundry User",
    help="Input name",
)
args = parser.parse_args()

def run():
    with grpc.secure_channel(
        target=args.host,
        credentials=grpc.ssl_channel_credentials(),
   ) as channel:
        stub = helloworld_pb2_grpc.GreeterStub(channel)
        response = stub.SayHello(helloworld_pb2.HelloRequest(name=args.name), timeout=10)
    print("Greeter client received: " + response.message)


if __name__ == '__main__':
    logging.basicConfig()
    run()

We can invoke our rpc by running the file

python greeter_client_with_options.py --host <YOUR ENDPOINT HERE> --name User

E.g. if Endpoint were https://grpc-py-helloworld-tfy-demo-50051.abc.xyz.example.com

python greeter_client_with_options.py --host grpc-py-helloworld-tfy-demo-50051.abc.xyz.example.com --name User

Note: Do not pass https:// in the --host option

This should get us output like

Greeter client received: Hello, User!