My Skills

Kubernetes

Deploying containerized Python AI services on Kubernetes — Helm values for autoscaling, probes, ingress and resource budgets on a shared platform chart, an authored Helm chart for an open-source MCP server, and staged GitLab CI release pipelines.

Details & related links

My Kubernetes work is the deployment half of the services I build. The retrieval platform and the agent-facing MCP tooling I work on at Spotware run as containerized Python services on Kubernetes, and I own the values and the release pipelines that put them there.

Those services deploy onto a shared internal platform chart, so what I author is the configuration that makes each one behave correctly under it: horizontal pod autoscaling with tuned scale-up and scale-down policies and stabilization windows, readiness and liveness probes — plus a startup probe on the service that is slow to warm up — CPU and memory requests and limits, a termination grace period set above the application's own graceful-shutdown timeout, ingress with cert-manager-issued TLS, and DNS wired declaratively through external-dns annotations. Where I depart from a platform default I write down why: one service scales at 75% CPU rather than the documented 80%, because bursty ONNX inference produces sharp spikes that average out and would otherwise scale too late. The same values carry the scheduled CronJobs that re-ingest the knowledge sources twice a day, with their own resource budgets kept separate from the serving pods.

Releases run through a staged GitLab CI pipeline — checks, dependency and image builds, then deploy. Pre-commit, the pytest suite, and an integration test against a live server run on every merge request; a tagged commit on main is what builds the image with Kaniko and hands it to the deploy job, which renders a helm diff and then runs an atomic helm upgrade --install into the service's own namespace, so a failed rollout rolls itself back instead of leaving half a release behind. On the agent-facing MCP service the test run is a hard floor rather than a report: it fails below 90% line coverage.

Container-side detail matters as much as chart-side detail. The Gunicorn keep-alive in the retrieval platform's image is deliberately set above the idle timeout of the load balancer in front of the cluster, because a mismatch there never presents as a bug — it presents as intermittent, unattributable connection resets that cost far more to diagnose than to prevent.

I also write charts from scratch when the software is mine to package. MCP Context Server ships its own Helm chart — deployment, service, ingress, config map, secret, persistent volume claim, and service account templates, with separate SQLite and PostgreSQL values files — alongside its Docker Compose and stdio deployment shapes, so the same server runs as a local process, a container, or a cluster workload without a line of code changing. A third kind of work is putting someone else's software into a cluster to the same standard: I am building the corporate MCP gateway that fronts our internal MCP servers as declarative Kubernetes custom resources on ToolHive, each endpoint with its own OAuth 2.1 sign-in federated to corporate identity, per-user and per-tool authorization evaluated before any call reaches the server behind it, and a default-deny NetworkPolicy written by hand because the operator creates none. It rolls out under GitOps, through a pipeline that diffs the desired manifests against the live cluster before applying and refuses to touch a resource it does not own, and the boundary is explicit: the platform team owns the cluster and the operator, I own the application layer on it. And I have written the practice down rather than rediscovering it per service: the chart, values, ingress, secrets, and troubleshooting conventions I use live as a reference skill inside my own agentic development environment, so the deployment decisions get made the same way every time.