First, build a docker container that contains all required packages. In this example, I choose ubuntu:bionic as the basic image, and I installed other packages onto it.

FROM ubuntu:bionic
RUN apt-get update
RUN apt-get install python3 -y
RUN apt-get install python3-pip -y
RUN pip3 install grpcio

ADD app /app/

EXPOSE 22222

The Dockerfile is shown above. In the app file, it contains 4 files. They are:

client.py, server.py, test_pb2.py, test_pb2_grpc.py

test_pb2.py and test_pb2_grpc.py is generated by compiling the test.protofile. The content for test.proto is shown below.

syntax = "proto3";

package lmjwtest;

// service, encode a plain text 
service EncodeService {
    // request a service of encode
    rpc GetEncode(plaintext) returns (encodetext) {}
}


message plaintext {
    string pttransactionID = 1;
    string ptproperties = 2;
    string ptsenderID = 3;
}

message encodetext {
    string enctransactionID = 1;
    string encproperties = 2;
    string encsenderID = 3;
}

By using the grpcio-tools to compile the test.protofile, we can get the test_pb2.py and test_pb2_grpc.py two files.

The compile command is:

python3 -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. test.proto

Then we create the server and client using the grpc generated python file.

The server.py file:

from concurrent import futures
import base64
import time 

import test_pb2
import test_pb2_grpc

import grpc

def encoding(msg):
    return base64.a85encode(msg.encode())

class EService(test_pb2_grpc.EncodeServiceServicer):
    
    def GetEncode(self, request, context):
        return test_pb2.encodetext(enctransactionID = encoding(request.pttransactionID),
                                            encproperties = encoding(request.ptproperties),
                                            encsenderID = request.ptsenderID)

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=2))
    test_pb2_grpc.add_EncodeServiceServicer_to_server(EService(),server)
    server.add_insecure_port('[::]:22222')
    server.start()
    try:
        while True:
            time.sleep(60*60*24)
    except KeyboardInterrupt:
        server.stop(0)

if __name__ == '__main__':
    serve()

the client.py:

import grpc

import test_pb2
import test_pb2_grpc

def run():
    channel = grpc.insecure_channel('server:22222')
    stub = test_pb2_grpc.EncodeServiceStub(channel)
    response = stub.GetEncode(test_pb2.plaintext(pttransactionID = 'abcde',
    ptproperties = 'This is a plain text transaction',
    ptsenderID = 'Will smith'))
    print("Encdded service received:\n EnctransactionID:%s\n,Encproperties:%s\n,EncsenderID:%s\n"%(response.enctransactionID,response.encproperties,response.encsenderID))

if __name__ == "__main__":
    run()

To test the grpc on the local host, we can open two terminal. The first terminal runs the server.py and the second runs the client.py. If the client.py can return the encoded message, it proves the grpc is working properly on the local host.


The Next step is to deploy this simple application on docker containers and the client and server need to be on different containers. So the encoding is considered as a microservice. To do this, we need to run two containers.

Using command docker build . to generate the docker images from the dockerfile. Note that you need to create an app folder and copy the “client.py, server.py, test_pb2.py, test_pb2_grpc.py” into this folder.

the file tree should look like this

-somename
	-Dockerfile
	-app/
		-client.py
		-server.py
		-test_pb2.py
		-test_pb2_grpc.py

run docker build . in the directory “somename”. This should create a docker image. Copy the created image ID image-id.

Using docker run -it image-id, replace the image-id with your image ID that was created by docker build. You need to do this twice in two different command line so that you have two different container. Find the two container ID. In the following code, I use container1 and container2 to identify two different containers id.

The next step is to link this two containers via an network. We can use docker network command to achieve this.

open a third terminal and type:

docker network create testnet
docker network connect testnet container1 --alias client
docker network connect testnet container2 --alias server

If you have gone through the code carefully, you may wondering where was the ‘‘server:22222" come from in client.py code. Well, here is it. The fact is we name the container2 using the network alias as “server” so all the containers in this network can use “server” to find this container. You can also use the container ID to replace “server”.

So now, you can run the command python3 app/server.py on the container2 and run python3 app/client.py on the container1. You should be able to see the client side successfully get the encoded message from server.

WELL DONE! Now, we have realized the comunication between two containers with grpc!


That’s all about this post. Let me know if you have any questions.