Guide to add external workloads to an Istio service mesh using ServiceEntry and VirtualService while ensuring Envoy sidecar intercepts outbound traffic
This guide shows how to bring an external workload into an Istio service mesh using a ServiceEntry and a VirtualService — without changing the mesh-wide outbound policy. This pattern is useful when the external workload is outside the Kubernetes cluster (for example, an internal EC2 instance or an on-premises web server) and you want Istio to capture and control traffic to it.What you will learn:
Confirm the cluster and Istio environment
Create a ServiceEntry for an external host
Create a VirtualService to instruct Istio how to route traffic to that ServiceEntry
Ensure the client pod has an Envoy sidecar so Istio can intercept outbound traffic
Verify the external app resolves and responds from the control plane host (this demo uses an /etc/hosts override):
curl myapp.com
Example truncated HTML response:
<!DOCTYPE html><html><head><title>Welcome to nginx!</title>...<h1>Welcome to nginx!</h1><p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p>...</html>
Step 2 — Run a test client pod and try curling the external host
Start a simple pod that includes curl. The official nginx image may not include curl; use an image such as curlimages/curl and keep the pod alive with sleep:
kubectl run test --image=curlimages/curl --restart=Never --command -- sleep 3600kubectl get pods
Example:
NAME READY STATUS RESTARTS AGEtest 1/1 Running 0 30s
From inside the test pod, attempt to curl the external host:
If the test pod does not have an Istio sidecar injected (no Envoy), you might see an unexpected intermediate response such as an HTTP 302 from an upstream gateway or proxy:
Why? Although the ServiceEntry exists, Istio only applies mesh routing (VirtualService handling) when the client pod’s outbound traffic is intercepted by the Envoy sidecar. If the pod is running in a namespace without Istio automatic sidecar injection enabled, Envoy is not present and Istio cannot manage that pod’s outbound traffic.
Make sure the client pod has an Envoy sidecar injected. If the pod is in a namespace without Istio injection enabled, Istio features (VirtualService routing and ServiceEntry handling through the proxy) will not be applied to that pod’s outbound traffic.
Apply the VirtualService and list virtual services:
kubectl apply -f vs.yamlkubectl get virtualservice
Example output:
NAME GATEWAYS HOSTS AGEmyapp-vs ["myapp.com"] 4s
Although this VirtualService appears to route myapp.com to myapp.com:80 (redundant looking), it is important: it instructs Istio how to process and forward requests for myapp.com. The destination host myapp.com is then resolved by the ServiceEntry to the external IP.
Step 4 — Ensure the client pod has an Istio sidecar
If the test pod was created in a namespace without sidecar injection, Istio will analyze and warn:
istioctl analyze
Example analysis result:
Info [IST0102] (Namespace default) The namespace is not enabled for Istio injection. Run 'kubectl label namespace default istio-injection=enabled' to enable it, or 'kubectl label namespace default istio-injection=disabled' to explicitly mark it as not needing injection.
Enable automatic injection for the namespace (example shows the default namespace):
With the Envoy sidecar present, Istio will capture and route outbound traffic according to the ServiceEntry and VirtualService. Run:
kubectl exec test -- curl -sS myapp.com
You should receive the full NGINX page:
<!DOCTYPE html><html><head><title>Welcome to nginx!</title>...<h1>Welcome to nginx!</h1><p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p>...</html>
This confirms Istio is intercepting the traffic and routing the request to the external endpoint specified in the ServiceEntry.
If the external service listens on a different port (e.g., 9090), update the ServiceEntry ports and endpoints accordingly.
For DNS-resolvable external services use resolution: DNS and omit endpoints — Istio will resolve the host dynamically.
For fixed internal IPs (such as an on-premise box), resolution: STATIC with endpoints is appropriate.
If you prefer manual sidecar injection, use istioctl kube-inject or the sidecar.istio.io/inject annotation on pod spec rather than enabling namespace-wide automatic injection.
Use istioctl analyze to check for common configuration issues and hints.
Remember to replace the demo IP 192.168.121.2 with the correct IP for your environment.
This exercise is useful preparation for the Istio Certified Associate (ICA) exam and real-world scenarios where you need to bring external services under Istio control. Try it in a lab environment to reinforce the steps.