External services
Declare the hosts outside the cluster that your application connects to, and know what the platform does with them.
spec.networking.outbound.external on a PlatformApplication lists the hosts
outside the cluster that your application talks to, such as a managed database,
a cache, or a third-party API. For each entry, the platform registers the host
with the service mesh as an Istio ServiceEntry.
The field doesn't open or close any network path, and it doesn't change what a hostname resolves to. The What this field doesn't do section explains both.
outbound.external takes effect from Platform Application Operator 0.1.429. On
an earlier version, the field is accepted but generates nothing. To find out
which version your cluster runs, ask the platform team.
Declare an external host
Add one entry per host and port to your PlatformApplication. This example
connects to an Azure Database for MySQL server through its private endpoint:
# .platform/kubernetes/base/application.yaml
spec:
networking:
outbound:
external:
- host: orders-db.mysql.database.azure.com
port: 3306
protocol: TCP
Each entry has the following fields:
| Field | Required | Description |
|---|---|---|
host | Yes | The hostname your application connects to. Use the name the provider gives you, not an IP address. |
port | Yes | The port your application connects to, from 1 to 65535. |
protocol | No | How the mesh treats the connection. Set it for every entry. For the accepted values, see Choose a protocol. |
To reach the same host on two ports, add two entries. To stop declaring a host,
remove its entry. The operator deletes the ServiceEntry on its next reconcile.
In your application, connect to the host by name, exactly as you declared it. You
don't need IP addresses, hostAliases, or /etc/hosts entries.
For the full field reference, see spec.networking.outbound.external in the PlatformApplication reference.
Choose a protocol
The protocol tells the mesh how to treat the connection. When you leave it out,
the generated port is named HTTP-<port> and has no protocol set.
Set protocol to the value that matches your connection:
| Your connection | Set protocol to |
|---|---|
| MySQL, PostgreSQL, Redis, MongoDB, or any other database or non-HTTP protocol | TCP |
| An HTTPS API | HTTPS |
| Plain HTTP/1.1 | HTTP |
| HTTP/2 without TLS | HTTP2 |
| gRPC | GRPC |
| Another protocol over TLS | TLS |
The field also accepts MONGO. It only adds MongoDB metrics for pods with a
sidecar, and only on unencrypted connections, so use TCP instead.
A database connection declared without protocol: TCP gets an HTTP port name.
Set TCP explicitly so the mesh doesn't treat it as HTTP.
What the operator generates
For each entry, the operator creates one ServiceEntry in your application's
namespace with these settings:
| Setting | Value | What it means |
|---|---|---|
| Name | <application>-<host>-<port> | Shortened with a hash suffix if it exceeds 253 characters. |
hosts | Your host | One host per entry. |
ports | Your port, named <PROTOCOL>-<port> | For example, TCP-3306. |
location | MESH_EXTERNAL | The host is outside the mesh. |
resolution | DNS | The mesh resolves host through cluster DNS each time it connects. |
exportTo | . | Visible only in your application's namespace. |
The operator also adds entries that you don't declare, based on your
application's resources:
- A
crdbresource addsdefault-public.cockroachdb-systemon port26257. - Any other resource adds
*.amazonaws.comon port443.
How private endpoints resolve
A private endpoint gives a managed Azure service a private IP address in your
cluster's network. Its public hostname, such as
orders-db.mysql.database.azure.com, returns that private IP only when the
query reaches Azure DNS, which holds the privatelink record.
On Azure clusters, cluster DNS forwards the following zones to Azure DNS:
mysql.database.azure.com(Azure Database for MySQL)redis.azure.net(Azure Managed Redis)
Forwarding gets the query to Azure DNS. For Azure DNS to answer with the private IP, two things also have to be in place on the Azure side:
- The endpoint's
privatelinkDNS zone, such asprivatelink.mysql.database.azure.com, is linked to your cluster's virtual network. Without the link, Azure DNS returns the public IP. - The zone holds an
Arecord for the endpoint. The endpoint's DNS zone group normally writes it when the endpoint is created.
When all three are in place, cluster DNS returns the private IP for the host, and
the ServiceEntry resolves to it too. When you set up a private endpoint,
confirm the zone link and the record with whoever manages the endpoint.
For a private endpoint in any other zone, such as
postgres.database.azure.com or redis.cache.windows.net, ask the platform
team to add the zone to your cluster's DNS forwarding. Until they do, the name
resolves to the service's public IP, and the connection either fails or leaves
the private network.
What this field doesn't do
- It isn't an egress firewall. The field generates no
NetworkPolicyand blocks nothing: leaving a host out doesn't stop your application reaching it, and listing a host doesn't grant access to it. Don't rely on it as a security boundary. - It doesn't fix DNS. Because every entry uses
resolution: DNS, the mesh connects to whatever cluster DNS returns for the host. If cluster DNS returns the wrong address, theServiceEntryuses the wrong address too. Fix the DNS record or the forwarding, as described in the preceding section. - It doesn't pin an IP address. You can't set a fixed IP for a host.
Troubleshoot
If your namespace runs in Istio ambient mode, DNS looks different from inside a pod. Before you trust a DNS result from your pod, check the following.
nslookup names a server but the answer doesn't come from it. In an ambient
pod, the mesh's node proxy (ztunnel) captures every DNS query and resolves it
through cluster DNS, whichever server you pass:
nslookup orders-db.mysql.database.azure.com 168.63.129.16
This command doesn't query Azure DNS from an ambient pod. To test what a specific DNS server returns, run the query from a pod outside the mesh. Ask the platform team if you can't start one.
The host resolves to 240.240.x.x. This is expected. The mesh gives each
ServiceEntry host a virtual address in 240.240.0.0/16 and routes connections
to it to the real destination. Your application connects to the virtual
address, and the mesh resolves the host when it opens the connection.
An IPv6 address such as 2001:2::1 is unreachable. The mesh also allocates
an IPv6 virtual address. Pods without IPv6 fail on it at once with Network unreachable, and most clients then try the IPv4 address. If your client tries
only the first address it gets, configure it to prefer IPv4.
The host resolves to a public IP. Azure DNS didn't give cluster DNS a private answer. There are two likely causes, and the fix differs, so ask the platform team to check both before changing anything:
- The host's zone isn't forwarded to Azure DNS.
- The endpoint's
privatelinkzone isn't linked to your cluster's virtual network, so Azure DNS returns the public record even though forwarding works.
For more information, see How private endpoints resolve.
A pod outside the mesh gets NXDOMAIN from Azure DNS. The private
endpoint's record is missing from its privatelink DNS zone. This can happen
when another private endpoint for the same server is deleted. Ask the platform
team to restore the record.
No ServiceEntry exists. If you have read access to your namespace, list
the entries:
kubectl get serviceentries -n NAMESPACE
Replace NAMESPACE with your application's namespace. If the list is empty,
your cluster may run an operator version earlier than 0.1.429. Ask the platform
team.