This project is a small HTTP server that returns the incoming request with metadata in the response, including the sender's IP address, all request parameters, and headers. The server's configuration can be modified using environment variables. Additionally, the server supports CPU and memory load testing and provides metrics in Prometheus format. The running server's parameters can be modified via API.
The server can be run as a Docker container or Kubernetes resources for x86 or ARM platforms. It can also be run as compiled binary files or compiled from source code.
- Usage Scenarios
- run http ping-pong server
- run http ping-pong server with custom server Name
- run http ping-pong server with serverName = podName
- run http ping-pong server with MEMORY_USAGE
- emulate crash app
- run http ping-pong server with response delay 3 seconds and 10 workers
- run http ping-pong server with additional response size in Kb
- Run
- Configuration
- gRPC
- API
kubectl run test --image viktoruj/ping_pong
kubectl run test --image viktoruj/ping_pong --env SERVER_NAME=pingPongServer
kubectl run test --image viktoruj/ping_pong --env ENABLE_DEFAULT_HOSTNAME=false
kubectl run test --image viktoruj/ping_pong --env MEMORY_USAGE_PROFILE='5=60 20=60 1024=360 ' --env ENABLE_LOAD_MEMORY=true
run http ping-pong server with MEMORY_USAGE and CPU_USAGE and enable cpu and memory loging . It uses CPU_MAXPROC =2 .
kubectl run test --image viktoruj/ping_pong --env CPU_USAGE_PROFILE='1=400=1=60 1=4=1=60 10=1=4=60' --env ENABLE_LOAD_CPU=true --env ENABLE_LOG_LOAD_CPU=true --env CPU_MAXPROC=2 --env MEMORY_USAGE_PROFILE='5=60 20=60 1024=360 ' --env ENABLE_LOAD_MEMORY=true --env ENABLE_LOG_LOAD_MEMORY=true
kubectl run test --image viktoruj/ping_pong --env DELAY_START=10
kubectl run test --image viktoruj/ping_pong --env RESPONSE_DELAY=3000 --env MAX_RESPONSE_WORKER=10
kubectl run test --image viktoruj/ping_pong --env ADDITIONAL_RESPONSE_SIZE=1024
| ENV VAR | APP VAR | set via API | Description |
|---|---|---|---|
SERVER_NAME |
serverName |
Yes | The name of the server. Default is ping_pong_server. |
ENABLE_DEFAULT_HOSTNAME |
enableDefaultHostName |
Yes | Use the default hostname (default = "true"). |
SRV_PORT |
serverPort |
No | The port on which the server is running. |
METRIC_PORT |
metricPort |
No | The port where server metrics are available in Prometheus format. You can view the current metrics at {server_address}:{METRIC_PORT}/metrics. |
ENABLE_OUTPUT |
enableOutput |
Yes | Log to stdout (default = "true"). |
LOG_PATH |
logPath |
Yes | Write logs to file ${LOG_PATH} (default="" without file output). |
ENABLE_LOAD_MEMORY |
enableLoadMemory |
Yes | Enable memory usage (default=false). |
MEMORY_USAGE_PROFILE |
memoryProfileStr |
Yes | Additional memory usage and time for usage. Example: 5=10 7=60 1024=360. |
ENABLE_LOG_LOAD_MEMORY |
enableLogLoadMemory |
Yes | Logs LOAD_MEMORY function. |
ENABLE_LOAD_CPU |
enableLoadCpu |
Yes | Enable CPU usage (default=false). |
ENABLE_LOG_LOAD_CPU |
enableLogLoadCpu |
Yes | Logs LOAD_CPU function. |
CPU_USAGE_PROFILE |
cpuProfileStr |
Yes | Additional CPU usage iteraction_milion=wait_msec=gorutins=time_sec. Example: 10=1=1=30 1=400=1=60. |
CPU_MAXPROC |
cpuMaxProc |
Yes | GOMAXPROCS (default = 1). How many cores to use. |
DELAY_START |
parsedDelay |
No | Delay start in seconds before start app (default = 0). Useful for testing slow start of app and test startup probes. |
RESPONSE_DELAY |
responseDelay |
Yes | Response delay in milliseconds. |
MAX_RESPONSE_WORKER |
maxResponseWorker |
Yes | Maximum number of response workers. |
ADDITIONAL_RESPONSE_SIZE |
additionalResponseSize |
Yes | additional response size in Kb , default = 0 |
ENABLE_GRPC |
- | No | Start the gRPC server (default = "true"; set "false" to disable). |
GRPC_PORT |
- | No | Port for the gRPC server (default = 8079). |
Besides the HTTP server, ping_pong also runs a gRPC server (over HTTP/2) on a
separate port (default 8079). It is enabled by default; the same binary can also act
as a gRPC client / load generator, so one image covers both the server and the client
side of a demo.
Control it with two environment variables (see also the Configuration table):
| ENV VAR | Default | Description |
|---|---|---|
ENABLE_GRPC |
true |
Start the gRPC server. Set false to disable. |
GRPC_PORT |
8079 |
Port the gRPC server listens on. |
The HTTP server (8080) and metrics (9090) keep working alongside gRPC.
pingpong.PingPong/Echo- returns the request payload plus the serving pod identity:message,server_name,hostname, and a per-podcount. Because every reply carries the pod hostname, a client can see which pod answered each call - the direct way to prove per-request load balancing across replicas.grpc.health.v1.Health- the standard gRPC health service. ReturnsSERVING, sofortio load -grpc,grpc_health_probeand Istio/Kubernetes gRPC readiness probes work out of the box.- server reflection - so tools like
grpcurlcan call the server without any.protofiles.
# Docker: expose the gRPC port
docker run -d -p 8079:8079 viktoruj/ping_pong
# Kubernetes: gRPC on by default; make each pod report its real hostname
kubectl run grpc --image viktoruj/ping_pong --env ENABLE_DEFAULT_HOSTNAME=false
# disable gRPC / change the port
docker run -d -e ENABLE_GRPC=false -p 8080:8080 viktoruj/ping_pong
docker run -d -e GRPC_PORT=50051 -p 50051:50051 viktoruj/ping_pongTip: set
ENABLE_DEFAULT_HOSTNAME=falseon the server soEchoreturns the real pod hostname instead of the fixed defaultping_pong_server. This is what makes the per-request balancing demo readable.
The binary switches to client mode when the first argument is -grpc-client; it sends
Echo requests, prints which pod answered each one, then prints a summary and exits.
# from any pod/host that has the binary or the image
/app -grpc-client -target grpc-server:8079 -n 180 -c 4
# inside Kubernetes (exec into a pod running the image)
kubectl exec -it deploy/grpc-client -- /app -grpc-client -target grpc-server:8079 -n 180Client flags:
| Flag | Default | Description |
|---|---|---|
-target |
localhost:8079 |
gRPC server host:port. |
-n |
100 |
Number of Echo requests to send. |
-c |
1 |
Parallel workers sharing the channel. |
-message |
ping |
Payload string echoed back. |
-timeout |
30s |
Overall deadline for the run. |
-quiet |
false |
Print only the summary (no per-request lines). |
Example output:
req 1 from host=grpc-server-6d4f-aaaaa server=grpc-server-6d4f-aaaaa count=1
req 2 from host=grpc-server-6d4f-bbbbb server=grpc-server-6d4f-bbbbb count=1
...
--- summary ---
requests: 180 ok: 180 errors: 0
distinct servers: 3
host grpc-server-6d4f-aaaaa: 60
host grpc-server-6d4f-bbbbb: 60
host grpc-server-6d4f-ccccc: 60
distinct servers: N is the key line - it tells you how many backend pods actually
answered. The process exits 0 if at least one request succeeded, otherwise 1.
Server reflection is enabled, so no .proto files are needed:
# list services and methods
grpcurl -plaintext localhost:8079 list
grpcurl -plaintext localhost:8079 describe pingpong.PingPong
# call Echo
grpcurl -plaintext -d '{"message":"hello"}' localhost:8079 pingpong.PingPong/Echo
# -> { "message": "hello", "serverName": "...", "hostname": "...", "count": "1" }
# health check
grpcurl -plaintext localhost:8079 grpc.health.v1.Health/Check
# -> { "status": "SERVING" }# fortio uses the standard gRPC health service
fortio load -grpc -n 50 localhost:8079 # reports "Health SERVING : 50"
# grpc_health_probe (handy as a Kubernetes probe)
grpc_health_probe -addr=localhost:8079 # -> status: SERVINGKubernetes gRPC readiness probe example:
readinessProbe:
grpc:
port: 8079
initialDelaySeconds: 2gRPC is HTTP/2, not raw TCP. In a mesh (e.g. Istio) the service port must be named
grpc / grpc-* or carry appProtocol: grpc, otherwise the proxy treats it as L4 TCP,
pins the single long-lived connection to one pod, and the client sees distinct servers: 1. With the port declared as gRPC, the proxy balances each request and the client
sees distinct servers: 3. A full worked example is ICA
Lab 32.
The service is defined in app/proto/pingpong.proto. The
generated Go code (*.pb.go) is committed, so the Docker build stays a plain go build.
Regenerate it only when you change the .proto:
cd docker/ping_pong/app
# needs: buf, protoc-gen-go, protoc-gen-go-grpc on PATH
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
buf generate
# generated files land next to buf.yaml; keep them under app/proto/docker run -d -p 8080:8080 viktoruj/ping_pong
The images work on x86 and ARM platforms.
latest- from scratch image with only binary. Dockerfilealpine- alpine image with binary and utils. Dockerfile_alpinedebug- amazon/aws-cli image with utils. Dockerfile_debug_arm and Dockerfile_debug_x86 .
sudo xattr -d com.apple.quarantine ping-pong-darwin-amd64
sudo chmod +x ping-pong-darwin-amd64
./ping-pong-darwin-amd64
# or
SERVER_NAME=myCustomServer ./ping-pong-linux-amd64
# check server
curl 127.0.0.1:8080
sudo chmod +x ping-pong-linux-amd64
./ping-pong-linux-amd64
# or
SERVER_NAME=myCustomServer ./ping-pong-linux-amd64
# check server
curl 127.0.0.1:8080
- use Termux
copy ping-pong-android-arm64 to /data/data/com.termux/files/usr/bin/
chmod +x /data/data/com.termux/files/usr/bin/ping-pong-android-arm64
ping-pong-android-arm64 cd docker/ping_pong/app
go mod tidy
go run . # or go build .
/ping-pong-api/getVar- get all ping-pong variables/ping-pong-api/setVar- set ping-pong variables/ping-pong-api/osInfo- get os info/ping-pong-api/panic- emulate crash app/ping-pong-api/getMetric- get metric
curl {ping-pong ip}:8080/ping-pong-api/getVar -s | jq
{
"cpuMaxProc": 2,
"cpuProfileStr": "1000=1=1=30",
"delayStart": 0,
"enableDefaultHostName": "true",
"enableLoadCpu": "true",
"enableLoadMemory": "false",
"enableLogLoadCpu": "true",
"enableLogLoadMemory": "false",
"enableOutput": "true",
"hostName": "ping_pong_server",
"logPath": "",
"memoryProfiles": null,
"serverName": "ping_pong_server"
}
curl -X POST "http://{ping-pong ip}:8080/ping-pong-api/setVar" -H "Content-Type: application/json" -d '{"enableLoadCpu":"true", "cpuProfileStr":"1000=1=1=30","cpuMaxProc":2}' -s | jq
{
"changes": {
"cpuMaxProc": {
"new": "2",
"old": "1"
},
"cpuProfileStr": {
"new": "1000=1=1=30",
"old": "1000=1=2=30"
},
"enableLoadCpu": {
"new": "true",
"old": "false"
}
},
"status": "Variables updated. Handlers reloaded."
}
curl {ping-pong ip}:8080/ping-pong-api/osInfo -s | jq
{
"architecture": {
"architecture": "amd64",
"goamd64": "v4",
"goarm": ""
},
"cpu": {
"cores": [
{
"core": 1,
"unit": "percentage",
"usage": 13.7
},
{
"core": 2,
"unit": "percentage",
"usage": 7.6
},
.............
curl {ping-pong ip}:8080/ping-pong-api/getMetric -s | jq
{
"goroutines": 6,
"requests_per_minute": 0,
"requests_per_second": 0,
"requests_total": 0
}
curl {ping-pong ip}:8080/ping-pong-api/panic -s
# in app log
Panic occurred: Test panic
exit status 1
