- read

Calling a containerized Golang function using RPC

Jack Ogina 15

Some friends & I were building a basic CI / code execution platform, the client code routines would run in a docker container with its own application kernel. We needed a way to communicate with the code runner inside the container, one idea we experimented with is RPC.

This article demonstrates how to call a function of a go program running inside a docker container from the host PC using RPC.

An image depicting how RPC works

The Demo

The demo code involves:

  • RPC server that receives & executes shell commands inside a docker container & writes results back to a client
  • A client that spawns the containerized RPC server & calls its Run function to execute shell commands in the docker container.

Even though this demo is in golang it should be possible to reproduce this in any language that has RPC support (for example gRPC) & docker engine sdk. You could write a wrapper around docker binaries if no SDK is available.

This article does not explain how RPC or docker works, you can see some links at the end of this article for those.

Project Structure / Files

  • server.go will contain our CmdExecutor RPC Server
  • client.go will spawn CmdExecutor in a container & call its Run function via RPC to execute commands inside the container
  • main.go
  • Dockerfile An ubuntu based image for our CMDExecutor RPC server. Make sure your compiled binary can run in your base image platform.
#init project
go mod init
#build go binary
go build -o cmdExecutor
#build docker image
docker build -t "cmdexecutor" .

Executing Shell Commands from Golang

We will first need to write a simple command executor that will live inside our docker container to execute client’s commands and return response, I will use golang’s OS package to execute the command in blocking mode then write the response back to the client’s struct.

#build
go build -o cmdExecutor
#rebuild docker image
docker build -t "cmdexecutor" .
#run cmdExecutor
docker run cmdexecutor

Add RPC Server

Golang has an inbuilt net RPC package that enables us to call exported functions of an object remotely.

In our case the object would be CmdExecutor that would enable a client to execute commands inside the container.

# rebuild & run RPC server
go build -o cmdExecutor
docker build -t "cmdexecutor" .
docker run cmdexecutor

Calling Run function via RPC

Now that we have our RPC server running inside the container its time we wrote the client that connects to it and calls the Run function to execute commands inside the docker container.

You will have to run the server on the host PC for now, till we have a way of getting its docker IP (hint: docker inspect <container_id>)

#build
go build -o cmdExecutor
#run server on host pc
./cmdExecutor server
# on another terminal
./cmdExecutor client

Spawning RPC container from client & getting its IP

Since our cmd executor will live in a docker container, we need a way to spawn the container & get its IP so we can be able to connect to the RPC server. For this I used docker golang’s SDK

#install docker SDK
go mod tidy
#build
go build -o cmdExecutor
#rebuild image
docker build -t "cmdexecutor" .
#spawn container & call function via RPC demo
./cmdExecutor client

I get this results on my PC

$ ./cmdExecutor client
CmdExecutorContainer IP 172.17.0.2
stdout: Hello From Container
stderr:
err: <nil>

An interesting exercise to attempt would be to execute the command in non-blocking mode then stream the results (stdout/stderr) back to client as the command executes. Or calling the function from another language (see rpcjson or grpc).

Resources & Further reading

Its important to note that a docker process is not a sandbox and should not be used to execute untrusted user code, see projects like gVisor or firecracker to see how to execute untrusted code in a container.

You can find the source here https://github.com/jakhax/myblog/tree/master/src/rpc_into_container