Adding a new AI feature to a polyglot microservices repo doesn’t have to be painful. In this post, I’ll show how I integrated the Nano Banana Try‑On service (tryonservice/
) into the Google Online Boutique–style demo: containerizing a Python gRPC service, exposing it on Kubernetes, wiring the Go frontend, and enabling local/dev workflows with Skaffold and Docker Compose.
What we’ll cover:
- Service design and gRPC surface
- Containerization (multi‑stage Dockerfile)
- Kubernetes Deployment/Service and env wiring
- Frontend integration (Go) via env vars + gRPC client
- Local dev with Skaffold and Docker Compose
- Observability hooks with OpenTelemetry
Architecture at a glance
The new tryonservice
is a Python gRPC service that generates a virtual try‑on image for a given user and product. The Go frontend
exposes HTTP routes and calls tryonservice
over gRPC. Everything is deployed with Kubernetes; images are built via Skaffold.
The Try‑On service (Python + gRPC)
Key entrypoint: src/tryonservice/tryon_server.py
- gRPC server implements
TryOnService
with two RPCs:TryOnProduct
andGetTryOnHistory
. - Uses Google Gemini via
google-genai
whenGEMINI_API_KEY
is present; otherwise returns a mock image so dev flows are smooth. - Emits structured logs and optional OpenTelemetry traces.
Code highlights:
58:class TryOnService(demo_pb2_grpc.TryOnServiceServicer):
71: def TryOnProduct(self, request, context):
299:def serve():
301: port = os.environ.get('PORT', '5012')
324: server.add_insecure_port(f'[::]:{port}')
327: logger.info(f"TryOn service listening on port {port}")
Observability is opt‑in via OTLP:
41:# Initialize OpenTelemetry
42:if os.environ.get('OTEL_EXPORTER_OTLP_ENDPOINT'):
43: resource = Resource(attributes={"service.name": "tryonservice"})
45: processor = BatchSpanProcessor(
46: OTLPSpanExporter(endpoint=os.environ.get('OTEL_EXPORTER_OTLP_ENDPOINT'))
Dependencies
Pinned in requirements.txt
:
1:grpcio==1.68.1
4:google-genai==0.3.0
5:Pillow==11.0.0
8:opentelemetry-api==1.29.0
11:opentelemetry-exporter-otlp==1.29.0
Containerization (multi‑stage Dockerfile)
The service uses a two‑stage build to keep the runtime image slim, exposes gRPC on 5012, and runs the Python server directly.
1:FROM python:3.12-slim AS base
9:COPY requirements.txt .
10:RUN pip install --prefix="/install" -r requirements.txt
14:WORKDIR /tryonservice
24:EXPOSE 5012
29:ENTRYPOINT ["python", "tryon_server.py"]
Kubernetes: Deployment and Service
We declare the Deployment and ClusterIP Service, and wire in env vars for port, Gemini key (as a Secret), and OTEL exporter. Probes are simple TCP checks on the gRPC port.
1:apiVersion: apps/v1
4: name: tryonservice
19: containers:
21: image: gcr.io/quiet-cider-472416-a5/tryonservice:v2
27: - name: PORT
28: value: "5012"
29: - name: GEMINI_API_KEY
31: secretKeyRef:
32: name: gemini-api-key
35: - name: OTEL_EXPORTER_OTLP_ENDPOINT
36: value: "otel-collector:4317"
56:apiVersion: v1
59: name: tryonservice
65: - name: grpc
66: port: 5012
Skaffold: build and deploy
Skaffold tracks and builds the service image and deploys manifests via kustomize.
50: - image: tryonservice
51: context: src/tryonservice
60: kustomize:
62: - kubernetes-manifests
This means skaffold dev
or skaffold run
will build src/tryonservice
and roll it out alongside other services.
Frontend wiring (Go)
The Go frontend
consumes TRYON_SERVICE_ADDR
and opens a gRPC connection; routes /tryon
and /tryon/history
proxy user actions to the service.
84: tryOnSvcAddr string
145: mustMapEnv(&svc.tryOnSvcAddr, "TRYON_SERVICE_ADDR")
155: mustConnGRPC(ctx, &svc.tryOnSvcConn, svc.tryOnSvcAddr)
168: r.HandleFunc(baseUrl + "/tryon", svc.tryOnHandler).Methods(http.MethodPost)
169: r.HandleFunc(baseUrl + "/tryon/history", svc.tryOnHistoryHandler).Methods(http.MethodGet)
In Kubernetes, the frontend gets that env var from its Deployment manifest:
86: - name: TRYON_SERVICE_ADDR
87: value: "tryonservice:5012"
For local docker‑compose and the helper script, the variable is present too:
72: - TRYON_SERVICE_ADDR=tryonservice:8080
93:TRYON_SERVICE_ADDR=tryonservice:8080
136: - TRYON_SERVICE_ADDR=tryonservice:8080
Note: In Kubernetes we expose 5012 (native gRPC port). In some local setups we route through the frontend at 8080; adjust as needed for your environment.
Running locally with Skaffold
- Prereqs: Docker, kubectl, a local or remote Kubernetes cluster, and Skaffold.
- Optional: Create the Gemini API key Secret so the service can use real generations:
kubectl create secret generic gemini-api-key \
--from-literal=key="$GEMINI_API_KEY"
- Launch dev loop:
skaffold dev
- Port‑forward the try‑on service for quick gRPC tests:
kubectl port-forward deployment/tryonservice 5012:5012
- Example gRPC smoke test with
grpcurl
:
grpcurl -plaintext localhost:5012 list
Running with docker‑compose
If you prefer Compose for a quick end‑to‑end spin‑up:
docker compose up --build
Ensure the frontend and try‑on env vars are aligned; in this repo they’re defined in docker-compose.yml
.
Observability (optional)
Set OTEL_EXPORTER_OTLP_ENDPOINT
in tryonservice
to emit traces to your collector. The frontend is already OTel‑instrumented via otelgrpc
and otelhttp
.
Gotchas and tips
- If
GEMINI_API_KEY
is not set, the service falls back to a mock image. This is great for dev environments and CI. - gRPC readiness/liveness probes use TCP checks on 5012. If you add TLS, switch probes accordingly.
- Keep image sizes small by relying on the two‑stage Dockerfile and avoiding unnecessary build tools in the runtime image.
Wrap‑up
That’s the full integration path: Python gRPC microservice → container → Kubernetes → Go frontend → local/dev workflows with Skaffold and Compose → optional OTel. You can apply the same pattern to add more AI‑backed capabilities to the demo.
Top comments (0)