Istio ServiceEntry Explained: External Services, DNS, and Traffic Control
What Is a ServiceEntry
Istio maintains an internal service registry that merges Kubernetes Services with additional entries you declare. When a sidecar proxy needs to route a request, it consults this registry. Services inside the mesh are automatically registered, but external services require a ServiceEntry to be added to the registry.
A ServiceEntry is a custom resource that registers external services in the mesh's service registry. Once registered, external services become first-class citizens with access to Istio features including metrics, access logs, distributed traces, mTLS origination, retries, timeouts, and circuit breaking.
ServiceEntry Anatomy: All Fields Explained
hosts
A list of hostnames associated with the service. For external services, this is typically the DNS name your application uses (e.g., api.stripe.com). For HTTP protocols, the hosts field is matched against the HTTP Host header. For non-HTTP protocols, you can use synthetic hostnames paired with addresses or static endpoints.
addresses
Optional virtual IP addresses associated with the service. Useful for TCP services where you want to assign a VIP that the sidecar will intercept. Not required for HTTP/HTTPS services that use hostname-based routing.
ports
The ports on which the external service is exposed. Each port needs a number, name, and protocol. The protocol setting determines how Envoy handles the connection—TLS for pass-through without termination, HTTPS for HTTP over TLS, and TCP for database connections.
location
Either MESH_EXTERNAL or MESH_INTERNAL. Use MESH_EXTERNAL for services outside your cluster (third-party APIs, managed databases). Use MESH_INTERNAL for services inside your infrastructure without a sidecar, such as VMs in the same VPC or unmeshed Kubernetes Services. This affects mTLS application and metrics labeling.
resolution
How the sidecar resolves endpoint addresses. Options include NONE, STATIC, DNS, and DNS_ROUND_ROBIN. This is the most critical field for ServiceEntry configuration.
endpoints
An explicit list of network endpoints. Required when resolution is STATIC. Each endpoint can have an address, ports, labels, network, locality, and weight.
exportTo
Controls visibility across namespaces. Use "." for the current namespace only, "*" for all namespaces. In multi-team clusters, restrict exports to avoid namespace pollution.
Resolution Types: NONE vs STATIC vs DNS vs DNS_ROUND_ROBIN
The resolution field determines how Envoy discovers IP addresses behind the service.
| Resolution | How It Works | Best For |
|---|---|---|
NONE |
Envoy uses the original destination IP from the connection. No DNS lookup by the proxy. | Wildcard entries, pass-through scenarios, services where the application already resolved the IP. |
STATIC |
Envoy routes to the IPs listed in the endpoints field. No DNS involved. |
Services with stable, known IPs (e.g., on-prem databases, VMs with fixed IPs). |
DNS |
Envoy resolves the hostname at connection time and creates an endpoint per returned IP. Uses async DNS with health checking per IP. | External APIs behind load balancers, managed databases with DNS endpoints (RDS, CloudSQL). |
DNS_ROUND_ROBIN |
Envoy resolves the hostname and uses a single logical endpoint, rotating across returned IPs. No per-IP health checking. | Simple external services, services where you do not need per-endpoint circuit breaking. |
When to Use NONE
Use NONE when registering a range of external IPs or wildcard hosts without Envoy performing address resolution. This is common for broad egress policies like "allow traffic to *.googleapis.com on port 443." The downside is that Envoy has limited ability to apply per-endpoint policies.
When to Use STATIC
Use STATIC when the external service has known, stable IP addresses that rarely change. This avoids DNS dependencies entirely. Classic use case: a legacy Oracle database on a fixed IP in your data center.
When to Use DNS
Use DNS for most external API integrations. Envoy performs asynchronous DNS resolution and creates a cluster endpoint for each returned IP address. This enables per-endpoint health checking and circuit breaking—critical for production reliability.
When to Use DNS_ROUND_ROBIN
Use DNS_ROUND_ROBIN when the external hostname returns many IPs and you do not need per-IP circuit breaking. Envoy treats all resolved IPs as a single logical endpoint and round-robins across them, which is lighter weight than DNS mode.
Practical Patterns
Pattern 1: External HTTP API (api.stripe.com)
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: stripe-api
namespace: payments
spec:
hosts:
- api.stripe.com
location: MESH_EXTERNAL
ports:
- number: 443
name: tls
protocol: TLS
resolution: DNS
The protocol is TLS, not HTTPS, because the application initiates the TLS handshake directly. Envoy handles this as opaque TLS using SNI-based routing.
Pattern 2: External Managed Database (RDS / CloudSQL)
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: orders-database
namespace: orders
spec:
hosts:
- orders-db.abc123.us-east-1.rds.amazonaws.com
location: MESH_EXTERNAL
ports:
- number: 5432
name: postgres
protocol: TCP
resolution: DNS
For TCP services, the DNS resolution mode ensures Envoy periodically re-resolves the hostname and updates its endpoint list, which is critical for RDS multi-AZ failover scenarios.
Pattern 3: Legacy Internal Service Not in the Mesh
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: legacy-monitoring
namespace: observability
spec:
hosts:
- legacy-monitoring.internal
location: MESH_INTERNAL
ports:
- number: 8080
name: http
protocol: HTTP
resolution: STATIC
endpoints:
- address: 10.0.5.10
- address: 10.0.5.11
- address: 10.0.5.12
The location is MESH_INTERNAL because the service lives inside your network, and resolution is STATIC because the IPs are known. The hostname is synthetic—your application uses it, and Istio's DNS proxy resolves it to one of the listed endpoints.
Pattern 4: TCP Services with Multiple Ports
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: external-elasticsearch
namespace: search
spec:
hosts:
- es.example.com
location: MESH_EXTERNAL
ports:
- number: 9200
name: http
protocol: HTTP
- number: 9300
name: transport
protocol: TCP
resolution: DNS
Each port gets its own Envoy listener configuration. The HTTP port benefits from full Layer 7 telemetry, while the TCP port gets Layer 4 metrics and connection-level policies.
Combining ServiceEntry with DestinationRule
A ServiceEntry alone registers the external service. To apply traffic policies—connection pooling, circuit breaking, TLS origination, load balancing—pair it with a DestinationRule.
Connection Pooling and Circuit Breaking
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: stripe-api
namespace: payments
spec:
hosts:
- api.stripe.com
location: MESH_EXTERNAL
ports:
- number: 443
name: tls
protocol: TLS
resolution: DNS
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: stripe-api-dr
namespace: payments
spec:
host: api.stripe.com
trafficPolicy:
connectionPool:
tcp:
maxConnections: 50
connectTimeout: 5s
http:
h2UpgradePolicy: DO_NOT_UPGRADE
maxRequestsPerConnection: 100
outlierDetection:
consecutive5xxErrors: 3
interval: 30s
baseEjectionTime: 60s
maxEjectionPercent: 100
This configuration caps outbound connections at 50, sets a 5-second connection timeout, and ejects endpoints that return 3 consecutive 5xx errors, preventing a degraded external API from consuming all connection slots.
TLS Origination
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: external-api
namespace: default
spec:
hosts:
- api.external-service.com
location: MESH_EXTERNAL
ports:
- number: 80
name: http
protocol: HTTP
- number: 443
name: https
protocol: TLS
resolution: DNS
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: external-api-tls
namespace: default
spec:
host: api.external-service.com
trafficPolicy:
portLevelSettings:
- port:
number: 443
tls:
mode: SIMPLE
The application sends HTTP to port 80. A VirtualService redirects that to port 443. The DestinationRule initiates TLS to the external endpoint. The application never knows TLS happened.
Combining ServiceEntry with VirtualService
VirtualService provides Layer 7 traffic management for external services: retries, timeouts, fault injection, header-based routing, and traffic shifting.
Retries and Timeouts
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: stripe-api-vs
namespace: payments
spec:
hosts:
- api.stripe.com
http:
- route:
- destination:
host: api.stripe.com
port:
number: 443
timeout: 10s
retries:
attempts: 3
perTryTimeout: 3s
retryOn: connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes
retryRemoteLocalities: true
This applies a 10-second overall timeout with up to 3 retry attempts (3 seconds each) for specific failure conditions. This only works for HTTP-protocol ServiceEntries. For TLS-protocol entries, you are limited to TCP-level connection retries via the DestinationRule.
Traffic Shifting Between External Providers
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: geocoding-primary
namespace: geo
spec:
hosts:
- geocoding.internal
location: MESH_EXTERNAL
ports:
- number: 443
name: tls
protocol: TLS
resolution: STATIC
endpoints:
- address: api.old-geocoding-provider.com
labels:
provider: old
- address: api.new-geocoding-provider.com
labels:
provider: new
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: geocoding-dr
namespace: geo
spec:
host: geocoding.internal
trafficPolicy:
tls:
mode: SIMPLE
subsets:
- name: old-provider
labels:
provider: old
- name: new-provider
labels:
provider: new
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: geocoding-vs
namespace: geo
spec:
hosts:
- geocoding.internal
http:
- route:
- destination:
host: geocoding.internal
subset: old-provider
weight: 80
- destination:
host: geocoding.internal
subset: new-provider
weight: 20
This sends 80% of geocoding traffic to the old provider and 20% to the new one. Adjust weights as you gain confidence.
DNS Resolution Patterns: Istio DNS Proxy vs kube-dns
Istio DNS resolution involves two layers: how your application resolves the hostname (kube-dns / CoreDNS) and how the sidecar resolves the hostname (Envoy's async DNS or Istio's DNS proxy).
Default Flow (Without Istio DNS Proxy)
Your application calls api.stripe.com. kube-dns resolves it to an IP. The application opens a connection to that IP. The sidecar intercepts the connection and—if the ServiceEntry uses DNS resolution—Envoy independently resolves api.stripe.com. Two separate DNS lookups happen, which can lead to inconsistencies if DNS records change between the two resolutions.
With Istio DNS Proxy (dns.istio.io)
Istio's sidecar includes a DNS proxy that intercepts DNS queries from the application. When enabled, the proxy can:
- Auto-allocate virtual IPs for ServiceEntry hosts that do not have addresses defined, which is critical for TCP ServiceEntries.
- Resolve ServiceEntry hosts directly, avoiding the round-trip to kube-dns for known mesh services.
- Ensure consistency between the application's DNS resolution and the sidecar's endpoint resolution.
In modern Istio installations (1.18+), DNS capture is enabled by default.
When DNS Proxy Matters Most
The DNS proxy is especially important for TCP ServiceEntries without an explicit addresses field. Without a VIP, Envoy cannot match an incoming TCP connection to the correct ServiceEntry. The DNS proxy solves this by auto-allocating a VIP from the 240.240.0.0/16 range and returning that VIP when the application resolves the hostname.
Sticky Sessions with ServiceEntry
Some external services require session affinity. Istio supports sticky sessions for external services through consistent hashing in a DestinationRule.
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: legacy-session-service
namespace: default
spec:
hosts:
- legacy-session.internal
location: MESH_INTERNAL
ports:
- number: 8080
name: http
protocol: HTTP
resolution: STATIC
endpoints:
- address: 10.0.1.10
- address: 10.0.1.11
- address: 10.0.1.12
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: legacy-session-dr
namespace: default
spec:
host: legacy-session.internal
trafficPolicy:
loadBalancer:
consistentHash:
httpCookie:
name: SERVERID
ttl: 3600s
This configuration hashes on an HTTP cookie named SERVERID. If the cookie does not exist, Envoy generates one and sets it on the response. You can also hash on:
-
HTTP header:
consistentHash.httpHeaderName: "x-user-id"— useful when your application sends a user identifier in every request. -
Source IP:
consistentHash.useSourceIp: true— simplest option but breaks in environments with NAT or shared egress IPs. -
Query parameter:
consistentHash.httpQueryParameterName: "session_id"— for REST APIs that include a session identifier in the URL.
The ServiceEntry must use STATIC or DNS resolution for sticky sessions to work. With DNS_ROUND_ROBIN, there is only one logical endpoint, so consistent hashing has no effect.
Troubleshooting Common Issues
503 Errors When Calling External Services
Start with this diagnostic sequence:
# Check if the ServiceEntry is applied and visible to the proxy
istioctl proxy-config cluster -n | grep
# Check the listeners
istioctl proxy-config listener -n --port
# Look at Envoy access logs for the specific request
kubectl logs -n -c istio-proxy | grep
Common causes of 503 errors:
-
Wrong protocol: Setting
protocol: HTTPSwhen your application initiates TLS. UseTLSfor pass-through. - Missing ServiceEntry in REGISTRY_ONLY mode: Any host without a ServiceEntry is blocked.
-
exportTo restriction: The ServiceEntry is in namespace A, exported only to
".", and the calling pod is in namespace B. - DNS resolution failure: Envoy cannot resolve the hostname. Check that DNS servers are reachable from the pod.
DNS Resolution Failures
When Envoy's async DNS resolver fails, you will see UH (upstream unhealthy) or UF (upstream connection failure) flags in access logs.
# Verify DNS works from inside the sidecar
kubectl exec -n -c istio-proxy -- \
pilot-agent request GET /dns_resolve?proxyID=.&host=api.stripe.com
# Check Envoy cluster health
istioctl proxy-config endpoint -n | grep
If the endpoint shows UNHEALTHY, Envoy resolved the DNS but outlier detection ejected the host. If no endpoint appears, DNS resolution is failing. Ensure your pods can reach an external DNS server, or that CoreDNS is configured to forward queries for the external domain.
TLS Origination Not Working
If you configured TLS origination via a DestinationRule but traffic still fails:
- Ensure the ServiceEntry port protocol is
HTTP, notTLS. - Verify the DestinationRule's
hostfield exactly matches the ServiceEntry'shostsentry. - Check that the VirtualService routes to the correct port number.
TCP ServiceEntry Not Intercepting Traffic
For TCP-protocol ServiceEntries without the DNS proxy, Envoy cannot match traffic by hostname. You must either:
- Set an explicit
addressesfield with a VIP that your application targets. - Enable Istio's DNS proxy to auto-allocate VIPs.
- Ensure the destination IP matches what the ServiceEntry resolves to.
Without one of these, TCP traffic goes through the PassthroughCluster and bypasses your ServiceEntry entirely.
Frequently Asked Questions
Do I need a ServiceEntry if outboundTrafficPolicy is set to ALLOW_ANY?
You do not need one for connectivity. But you should create ServiceEntries anyway. Without them, outbound traffic goes through the PassthroughCluster, which means no detailed metrics per destination, no access logging with the external hostname, no circuit breaking, no retries, and no timeout policies.
What is the difference between protocol TLS and HTTPS in a ServiceEntry port?
TLS tells Envoy to treat the connection as opaque TLS. Envoy reads the SNI header to determine routing but does not decrypt the payload. Use this when your application initiates TLS directly. HTTPS tells Envoy the protocol is HTTP over TLS. In practice, for external services where the application manages its own TLS, use TLS.
Can I use wildcards in ServiceEntry hosts?
Yes, but with limitations. You can use *.example.com to match any subdomain of example.com. However, wildcard entries only work with resolution: NONE because Envoy cannot perform DNS lookups for wildcard hostnames. Wildcard ServiceEntries are best used for broad egress access control rather than fine-grained traffic management.
How do I configure sticky sessions for an external service behind a ServiceEntry?
Create a ServiceEntry with STATIC or DNS resolution so Envoy has multiple endpoints. Pair it with a DestinationRule that configures consistentHash under trafficPolicy.loadBalancer. You can hash on an HTTP cookie, header, source IP, or query parameter.
How does ServiceEntry interact with NetworkPolicy and Istio AuthorizationPolicy?
A ServiceEntry does not bypass Kubernetes NetworkPolicy. If a NetworkPolicy blocks egress to the external IP, traffic will be dropped at the CNI level before Envoy can route it. Istio AuthorizationPolicy can also restrict which workloads are allowed to call specific ServiceEntry hosts. Use ServiceEntry for traffic management and observability, AuthorizationPolicy for workload-level access control, and NetworkPolicy for network-level enforcement.
Wrapping Up
ServiceEntry transforms opaque outbound connections into managed, observable, policy-controlled traffic without requiring changes to your application code. Start with the basics: create a ServiceEntry for each external dependency, set the correct resolution type, and pair it with a DestinationRule for connection limits and circuit breaking. As you mature, add VirtualServices for retries and timeouts, configure sticky sessions where needed, and enable the DNS proxy for seamless TCP service integration. Every external dependency you formalize with a ServiceEntry is one fewer blind spot in your production mesh.
Originally published at alexandre-vazquez.com/istio-serviceentry-explained
Top comments (0)