Run a Service Locally Against Cluster Traffic
ksail workload intercept lets you change one service locally while the rest of the application
keeps running in Kubernetes. KSail selects a running pod for a Deployment, injects a narrowly
privileged steering container, and redirects one inbound TCP port through a tunnel to a process on
your machine. The local process returns the response to the original in-cluster caller.
Use this when the local process should replace the selected pod for the intercepted port. If you
only want to observe traffic while the workload continues serving it, use
ksail workload mirror instead.
| Mode | Cluster workload | Local process |
|---|---|---|
intercept |
Stops serving the intercepted traffic while the session is active | Receives the request and returns the response |
mirror |
Keeps serving normally | Receives a copy for inspection or replay |
Before you start
Section titled “Before you start”You need:
- a running Kubernetes cluster and a kubeconfig context KSail can use;
- a Deployment with at least one Running pod;
- credentials allowed to read the target Deployment and its pods, update the
pods/ephemeralcontainerssubresource, and createpods/execsessions; - an admission policy that permits the injected ephemeral container’s single added Linux capability,
NET_ADMIN; - the local process listening on
127.0.0.1before the intercept starts; and - Python 3 for the local demo server below (replace it with your own application in real use).
The walkthrough uses one Deployment replica so every request has a single destination. With several replicas, KSail intercepts only the concrete pod it selects; requests routed to other replicas keep using the in-cluster application.
Walk through an intercept
Section titled “Walk through an intercept”-
Create a target and an in-cluster traffic client. Save this as
/tmp/ksail-intercept-demo.yaml:apiVersion: v1kind: Namespacemetadata:name: ksail-intercept-demo---apiVersion: v1kind: ConfigMapmetadata:name: intercept-demo-indexnamespace: ksail-intercept-demodata:index.html: |served by the cluster---apiVersion: apps/v1kind: Deploymentmetadata:name: intercept-demonamespace: ksail-intercept-demospec:replicas: 1selector:matchLabels:app: intercept-demotemplate:metadata:labels:app: intercept-demospec:containers:- name: webimage: nginx:1.27-alpineports:- name: httpcontainerPort: 80volumeMounts:- name: indexmountPath: /usr/share/nginx/html/index.htmlsubPath: index.htmlvolumes:- name: indexconfigMap:name: intercept-demo-index---apiVersion: v1kind: Servicemetadata:name: intercept-demonamespace: ksail-intercept-demospec:selector:app: intercept-demoports:- name: httpport: 80targetPort: http---apiVersion: v1kind: Podmetadata:name: traffic-clientnamespace: ksail-intercept-demospec:containers:- name: clientimage: busybox:1.36.1command: ["sh", "-c", "sleep 3600"]Apply it and wait for both pods:
Terminal window ksail workload apply -f /tmp/ksail-intercept-demo.yamlksail workload exec --namespace ksail-intercept-demo \--pod-running-timeout=2m deployment/intercept-demo -- trueksail workload exec --namespace ksail-intercept-demo \--pod-running-timeout=2m traffic-client -- trueThe two no-op
execcalls wait for a running target pod and traffic-client pod. The baseline request in the next step then verifies that the Service and nginx container are ready together. -
Confirm the remote baseline. Send a request from the traffic client to the Service:
Terminal window ksail workload exec -n ksail-intercept-demo traffic-client -- \sh -c 'attempt=0until wget -T 1 -qO- http://intercept-demo; doattempt=$((attempt + 1))if [ "$attempt" -ge 20 ]; thenecho "timed out waiting for intercept-demo" >&2exit 1fisleep 2done'This retries for about one minute so a Running pod cannot race nginx starting to listen.
The response is:
served by the cluster -
Start the local process. In a second terminal, serve a different response on loopback:
Terminal window mkdir -p /tmp/ksail-intercept-localprintf 'served by the local process\n' > /tmp/ksail-intercept-local/index.htmlpython3 -m http.server 8080 --bind 127.0.0.1 \--directory /tmp/ksail-intercept-local -
Start the intercept. In a third terminal, redirect the pod’s port
80to local port8080:Terminal window ksail workload intercept intercept-demo \--namespace ksail-intercept-demo \--service-port 80 \--local-port 8080--service-portis the port the container receives after any ServicetargetPortmapping. It is not necessarily the Service’s publicport.--local-portis the loopback port where your local process listens. -
Verify the local response. Repeat the in-cluster request:
Terminal window ksail workload exec -n ksail-intercept-demo traffic-client -- \sh -c 'attempt=0while [ "$attempt" -lt 20 ]; doresponse=$(wget -T 1 -qO- http://intercept-demo 2>/dev/null || true)case "$response" in*"served by the local process"*)printf "%s\n" "$response"exit 0;;esacattempt=$((attempt + 1))sleep 2doneecho "timed out waiting for local intercept" >&2exit 1'This bounded poll distinguishes intercept readiness from the command’s startup message and fails instead of silently accepting the original cluster response.
The same Service now returns:
served by the local process -
Stop and verify restoration. Press Ctrl-C in the intercept terminal. KSail closes the tunnel, the steering agent removes its redirect rule, and the command exits successfully. Repeating the request returns
served by the clusteragain.Stop the Python server, then remove the demo resources:
Terminal window ksail workload delete -f /tmp/ksail-intercept-demo.yaml --waitrm -rf /tmp/ksail-intercept-local /tmp/ksail-intercept-demo.yaml
Target the right workload
Section titled “Target the right workload”The example uses the current context, a dedicated namespace, and a single-container Deployment. For another environment, make the selection explicit:
ksail workload intercept checkout \ --context dev-cluster \ --namespace shop \ --container api \ --service-port 8080 \ --local-port 3000- Pass
--containerwhen the Deployment has several containers. - Use the same context and namespace for verification commands such as
workload exec. - Keep the local process running for the whole session. Intercept does not silently fall back to the cluster workload when the local listener is unavailable.
Cleanup and recovery
Section titled “Cleanup and recovery”Ctrl-C is the deterministic cleanup path: it removes the redirect before returning. A released KSail
client using its derived default command and exact version-matched steering image also negotiates a
keepalive watchdog so an unexpectedly lost client can remove the redirect. KSail deliberately does
not claim that guarantee for a development :latest image, a custom steering image or command, or a
steering container reused from another version because those agents may not understand the watchdog
protocol.
If graceful Ctrl-C was impossible and traffic remains redirected, replace the selected pod. For a Deployment, deleting that pod lets its controller create a clean replacement. Copy the selected pod name from the intercept command’s output and use the same context, namespace, and Deployment as the intercept; for example:
context=dev-clusternamespace=shopdeployment=checkoutselected_pod=checkout-7c8d9f-example
ksail workload delete --context "$context" --namespace "$namespace" \ "pod/$selected_pod" --waitksail workload exec --context "$context" --namespace "$namespace" \ --pod-running-timeout=2m "deployment/$deployment" -- trueThis recovery interrupts traffic when no other replica is Ready, so use it deliberately.
Kubernetes does not allow an injected ephemeral-container record to be removed from a live pod. After a session ends cleanly, the dormant container has no redirect rule or active steering process; a later intercept can reuse it. The recovery above replaces the pod, which removes the container record with it; a later intercept injects a new steering container into the replacement pod.
If requests fail during a session:
- verify the local process still listens on
127.0.0.1:<local-port>; - confirm
--service-portmatches the target container port, including any ServicetargetPort; - confirm the selected pod is Running and the requested container name is correct; and
- press Ctrl-C to restore remote handling before changing the command, or replace the selected pod if the client is already gone and traffic remains redirected.
Related
Section titled “Related”ksail workload interceptreference — every flag and custom steering-agent optionsksail workload mirrorreference — read-only capture and replay- Workload Management — deploy, inspect, and operate workloads
- Unmanaged Clusters — point KSail at a cluster it did not create