Skip to content

Routes Documentation

The bb-common Helm chart provides a comprehensive routes framework that simplifies the creation and management of Istio service mesh resources for ingress traffic routing.

Table of Contents

Overview

The routes framework provides two types of routing configurations:

  • Inbound Routes (routes.inbound): Configure ingress traffic routing through Istio gateways to services within your mesh, with automatic security policy generation
  • Outbound Routes (routes.outbound): Register external services for egress traffic, enabling REGISTRY_ONLY outbound traffic policies

Inbound Routes

Inbound routes handle incoming traffic to your applications through Istio gateways.

Quick Start

Inbound routes are configured under the routes.inbound key in your values file. Here’s a basic configuration that creates a secure route with network policies:

routes:
  inbound:
    my-app:
      enabled: true                # Enable/disable the route
      gateways:                    # List of Istio gateways
        - istio-gateway/public-ingressgateway
      hosts:                       # List of host domains (supports templating)
        - myapp.example.com
      serviceEntry:                # Optional - generated inbound ServiceEntry settings
        enabled: true              # Defaults to true; set to false to suppress it
      resolution: DNS              # Optional - resolution for the generated ServiceEntry
      service: my-app-service      # Target service name (supports templating)
      port: 8080                   # Target service port (supports templating)
      containerPort: 3000          # Optional - container/pod port for NetworkPolicy (defaults to port)
      passthrough:                 # Optional - section used only if virtual service belongs to a passthrough gateway
        enabled:                   # Optional - must set to true to configure virtual service properly when used with a passthrough gateway
        gatewayPort:               # Optional - the https port of the passthrough gateway used; defaults to port 8443 if not specified 
      selector:                    # Optional - defaults to app.kubernetes.io/name: {route-key}
        app.kubernetes.io/name: my-app
      metadata:                    # Custom metadata for all generated resources
        labels: {}                 # Custom labels for all generated resources
        annotations: {}            # Custom annotations for all generated resources

This creates a VirtualService that routes traffic from myapp.example.com to the my-app-service on port 8080, plus supporting resources for security and service mesh integration.

Required fields:

  • enabled: Must be true to generate resources
  • gateways: List of Istio gateways (format: namespace/gateway-name)
  • hosts: List of hostnames for routing
  • service: Target service name
  • port: Target service port

Optional:

  • containerPort: Target container/pod port number - used for NetworkPolicy when different from service port. When omitted, defaults to port value. Supports templating.
  • selector: Pod selector labels - if omitted, defaults to app.kubernetes.io/name: {route-key}. NetworkPolicy and AuthorizationPolicy are automatically generated using this selector for enhanced security
  • serviceEntry.enabled: Generate a ServiceEntry for this inbound route. Defaults to true; set to false to suppress only the automatically generated inbound ServiceEntry
  • resolution: Generated ServiceEntry resolution strategy - DNS (default), STATIC, DNS_ROUND_ROBIN, DYNAMIC_DNS, or NONE. If any host contains a wildcard (*) and resolution is omitted, it defaults to NONE. This setting has no effect when serviceEntry.enabled is false
  • metadata: Custom labels and annotations for all generated resources

Resource Naming

By default, route resources are named without the Helm release name prefix. When deploying multiple releases to the same namespace, enable prependReleaseName to avoid naming conflicts:

routes:
  prependReleaseName: true  # e.g. "my-release-myapp" instead of "myapp"
  inbound:
    myapp:
      enabled: true
      # ... route configuration

Prerequisites

Important: Routes are conditionally rendered based on the availability of Istio Custom Resource Definitions (CRDs) in your cluster.

Routes requires the Istio CRDs to be installed in your Kubernetes cluster. Specifically, routes will only be rendered when the networking.istio.io/v1 API version is available.

To verify Istio CRDs are available:

kubectl api-versions | grep networking.istio.io/v1

You should see networking.istio.io/v1 in the output. If you don’t see this, you need to install Istio before routes can be generated.

Generated Resources

The routes framework automatically generates multiple Kubernetes resources to provide complete ingress routing functionality. Each enabled route creates a VirtualService for traffic routing and, unless serviceEntry.enabled is false, a ServiceEntry for internal service mesh communication. Additional NetworkPolicy and AuthorizationPolicy resources are automatically created to secure access to the target service using either an explicit selector or the default app.kubernetes.io/name: {route-key} selector.

For a visual representation of how these resources relate to each other, see the Resource Graph.

VirtualService

A Kubernetes VirtualService is created for each enabled route:

# Generated from basic route configuration
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: my-app
  namespace: default
spec:
  gateways:
    - istio-gateway/public-ingressgateway
  hosts:
    - myapp.example.com
  http:
    - route:
        - destination:
            host: my-app-service
            port:
              number: 8080

When passthrough.enabled is set to true on the route, the SNI is used for routing instead of the HTTP host header. This is intended to be used when a workload is associated with a gateway that is in passthrough mode as TLS is terminated at the workload in this scenario:

apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: my-app
  namespace: default
spec:
  gateways:
    - istio-gateway/passthrough-ingressgateway
  hosts:
    - myapp.example.com
  tls:
    - match:
        - port: 8443
          sniHosts:
            - myapp.example.com
      route:
        - destination:
            host: my-app-service
            port:
              number: 8080

ServiceEntry

A ServiceEntry is created by default to register the inbound route hosts in the service mesh registry. This is essential when using REGISTRY_ONLY outbound traffic policies, allowing the mesh to route traffic to external hosts defined in the VirtualService:

# Generated for service mesh registry entry
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
  name: my-app-internal
  namespace: default
spec:
  hosts:
    - myapp.example.com
  location: MESH_EXTERNAL
  resolution: DNS
  ports:
    - name: https
      number: 443
      protocol: HTTPS

Disable automatic ServiceEntry generation for an individual inbound route when registering its public hosts interferes with the environment’s internal DNS resolution:

routes:
  inbound:
    my-app:
      enabled: true
      serviceEntry:
        enabled: false
      gateways:
        - istio-gateway/public-ingressgateway
      hosts:
        - myapp.example.com
      service: my-app-service
      port: 8080

This opt-out does not disable the route’s VirtualService or generated NetworkPolicy, AuthorizationPolicy, authservice, or waypoint resources. If the mesh uses outboundTrafficPolicy.mode: REGISTRY_ONLY, ensure any required hosts are registered through another ServiceEntry or equivalent configuration.

Note: ServiceEntries are particularly important in environments with outboundTrafficPolicy.mode set to REGISTRY_ONLY, where only explicitly registered external services are allowed.

For inbound routes, resolution defaults to DNS unless a wildcard host is present (e.g. *.example.com), in which case it defaults to NONE. ServiceEntries from routes.inbound omit exportTo and therefore use Istio’s default (cluster-wide) visibility.

NetworkPolicy

A NetworkPolicy is automatically generated to allow ingress gateway access:

# Generated automatically (using explicit or default selector)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-ingress-istio-gateway-my-app
  namespace: default
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: my-app
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: istio-gateway
          podSelector:
            matchLabels:
              app.kubernetes.io/name: public-ingressgateway
              istio: ingressgateway
      ports:
        - port: 8080
          protocol: TCP

Note: By default, the NetworkPolicy uses the same port as specified in the port field (8080 in this example). If your service port differs from your container/pod port, use the containerPort field to specify the actual port the container is listening on. For example, if your service exposes port 80 but your container listens on 8080, set port: 80 and containerPort: 8080. The VirtualService will use port 80, while the NetworkPolicy will use port 8080.

AuthorizationPolicy

An AuthorizationPolicy is also automatically created for allowing gateway access to the application pod:

# Generated automatically (using explicit or default selector)
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: my-app-public-ingressgateway-authz-policy
  namespace: default
spec:
  action: ALLOW
  selector:
    matchLabels:
      app.kubernetes.io/name: my-app
  rules:
    - from:
        - source:
            namespaces:
              - istio-gateway
            principals:
              - cluster.local/ns/istio-gateway/sa/public-ingressgateway-ingressgateway-service-account

Automatic Selector Inference

When no selector is explicitly provided in the route configuration, the system automatically infers one using the Kubernetes standard labeling convention:

# Automatic inference example
routes:
  inbound:
    my-service:  # Route key
      enabled: true
      # No selector specified
      gateways:
        - istio-gateway/public-ingressgateway
      hosts:
        - myservice.example.com
      service: my-service
      port: 8080

# Automatically becomes equivalent to:
routes:
  inbound:
    my-service:
      enabled: true
      selector:
        app.kubernetes.io/name: my-service  # Inferred from route key
      # ... rest of configuration

This ensures that NetworkPolicy and AuthorizationPolicy resources are always created, even when selectors are not explicitly specified. You can override this behavior by providing an explicit selector configuration.

Protecting a Route with Authservice (OIDC ext_authz)

An inbound route can be protected with authservice by adding an authservice block. The binding is derived from the route — no separate serviceName/selector is needed. In ambient mode, enabling authservice auto-creates the shared waypoint Gateway (see Waypoint Gateway) that acts as the L7 enforcement point:

routes:
  inbound:
    podinfo:
      enabled: true
      gateways:
        - istio-gateway/public-ingressgateway
      hosts:
        - podinfo.dev.bigbang.mil
      service: podinfo          # reused as the ambient targetRefs Service
      port: 9898
      selector:                 # reused as the sidecar selector
        app.kubernetes.io/name: podinfo
      authservice:
        enabled: true
        issuer: https://keycloak.dev.bigbang.mil/auth/realms/baby-yoda
        clientId: my-client-id
        # jwksUri optional (derived: <issuer>/protocol/openid-connect/certs)
        # The ext_authz provider name ("authservice") and Service address
        # (authservice/authservice:10003) are the fixed Big Bang authservice
        # contract — not configurable.
        # ingressOnly optional (default false): scope enforcement to traffic
        # arriving via the route's ingress gateway(s); in-mesh (east-west)
        # clients are exempt from OIDC. See below.
        # In ambient mode the waypoint Gateway and the waypoint->authservice egress
        # NetworkPolicy are auto-generated (authservice/authservice:10003).

⚠️ Important: the route generates the policies and the waypoint, but nothing is enforced until your Service is enrolled onto the waypoint — see Enrolling your Service onto the waypoint.

Generated per protected route (named <route>-authservice, so multiple routes coexist):

  • RequestAuthentication — OIDC JWT validation (issuer, audiences=[clientId], jwksUri)
  • AuthorizationPolicyaction: CUSTOM, provider.name (ext_authz). Only requests without an Authorization header are sent to the provider (the browser login flow); token-bearing requests skip ext_authz and are checked by the RequestAuthentication + jwt DENY instead, so programmatic clients with a valid JWT don’t get login redirects.
  • AuthorizationPolicy<route>-authservice-jwt-deny, action: DENY. The JWT enforcement backstop: denies requests that do not carry a validated request principal from the issuer. DENY is evaluated before ALLOW, so the route’s ingress-gateway ALLOW policy cannot admit a request this rejects, even if ext_authz is bypassed.
  • AuthorizationPolicy<route>-authservice-allow-in-ns, action: ALLOW (ambient mode, gated on the allowInNamespace default toggle). Admits same-namespace callers at the waypoint, which only evaluates targetRefs-bound policies and would otherwise deny all in-mesh traffic — even with a valid JWT. Enforcement is unaffected: the CUSTOM and jwt DENY policies still apply. Cross-namespace callers need their own waypoint-bound ALLOW policy — declared k8s netpol clients get one automatically, see Waypoint Mirroring of Ingress Policies.
  • NetworkPolicy (ambient, when networkPolicies.enabled) — allow-egress-from-<route>-waypoint-to-authservice-tcp-port-10003, admitting the waypoint pod’s egress to the authservice Service on the ext_authz port and HBONE (15008). The waypoint — not the workload — makes the ext_authz call, so this egress selects the waypoint pod.
  • NetworkPolicy (ambient, when networkPolicies.enabled) — allow-ingress-to-<route>-waypoint-15008-..., admitting the route’s ingress gateway to the waypoint pod on HBONE (15008) for north-south traffic.

Generated once for the namespace (ambient mode, shared by all protected routes):

  • Gateway — the waypoint Gateway (istio.io/waypoint-for: service, istio-waypoint GatewayClass), the L7 enforcement point. Named waypoint, or <release>-waypoint when istio.prependReleaseName is set — enrollment labels (istio.io/use-waypoint) must reference the effective name.
  • NetworkPolicy (when networkPolicies.enabled, gated on the networkPolicies.ingress.defaults.enabled toggle like the sidecar-mode scrape default it replaces) — allow-ingress-to-waypoint-tcp-port-15020-from-ns-monitoring-pod-prometheus, admitting prometheus to the waypoint pod’s istio-proxy metrics port (15020).

Enrolling your Service onto the waypoint

bb-common creates the waypoint Gateway, but it does not enroll anything onto it — traffic only routes through the waypoint (and is therefore subject to the authservice policies) once the consuming package labels its Service:

metadata:
  labels:
    # Route mesh (east-west) traffic for this Service through the waypoint.
    # The value must be the EFFECTIVE waypoint Gateway name: `waypoint`, or
    # `<release>-waypoint` when `istio.prependReleaseName` is set.
    istio.io/use-waypoint: waypoint
    # Also route north-south (ingress gateway) traffic through the waypoint.
    # Without this the ingress gateway sends straight to the pods, bypassing
    # the waypoint — so authservice never sees external requests.
    istio.io/ingress-use-waypoint: "true"

Prefer labeling the Service over the namespace. Both labels can also be set on the namespace, but that routes every Service in it through the waypoint — applying authservice-style enforcement to workloads (e.g. a redis cache) that were never meant to be protected, and typically breaking them (RBAC: access denied, since only targetRefs-bound policies are honored at the waypoint). Service-level labels enroll exactly the Services your routes protect.

How the labels get onto the Service depends on who renders it: charts commonly expose a values hook (e.g. the upstream podinfo chart’s service.additionalLabels); see test-values-authservice.yaml for a complete working example of this pattern.

Waypoint Mirroring of Ingress Policies

When an inbound route enables authservice in ambient mode, its Service is expected to be waypoint-enrolled — Service-addressed traffic from in-mesh clients terminates at the shared waypoint pod, where neither the workload’s NetworkPolicy nor its ztunnel-enforced (selector-bound) AuthorizationPolicies apply. To ensure each existing ingress policy permits traffic, each ingress k8s entry whose workload is fronted by such a route is automatically mirrored onto the waypoint:

  • a NetworkPolicy admitting the client to the waypoint pod over HBONE (15008)
  • an AuthorizationPolicy with the same source, rebound via targetRefs: {kind: Service} (waypoints only evaluate targetRefs-bound policies) and matched on the route’s Service port

The entry↔route join is by convention — no extra configuration:

  • the entry’s effective pod selector equals the route’s selector (both must use the canonical app.kubernetes.io/name label, or matching explicit selectors)
  • the entry’s port(s) include the route’s workload port (containerPort, falling back to port when not set — the same precedence the route’s own NetworkPolicy uses)

Entries that don’t match both conditions are not mirrored. Rendering fails if an entry matches multiple authservice routes resolving to different services. The selector-bound originals are always kept: pod-direct traffic (e.g. endpoints-discovered scrapes) bypasses the waypoint and still relies on them. Generated mirrors carry generated.*.bigbang.dev/from-route annotations for traceability.

For example, this entry and route join on selector and port:

networkPolicies:
  ingress:
    to:
      podinfo:9898:                # local name -> selector app.kubernetes.io/name: podinfo  (= route selector)
        from:                      # local port 9898                                         (= route workload port)
          k8s:
            foo-sa@bar/baz: true   # the declared client: SA foo-sa, namespace bar, app baz

routes:
  inbound:
    podinfo:
      enabled: true
      gateways:
        - istio-gateway/public-ingressgateway
      hosts:
        - podinfo.dev.bigbang.mil
      service: podinfo
      port: 9898                   # workload port (no containerPort set, so port is used)
      selector:
        app.kubernetes.io/name: podinfo
      authservice:
        enabled: true
        issuer: https://keycloak.dev.bigbang.mil/auth/realms/baby-yoda
        clientId: my-client-id

Besides the entry’s usual workload-level pair, two mirrors are generated for the baz client:

kind: NetworkPolicy                # allow-ingress-to-podinfo-waypoint-tcp-port-15008-from-ns-bar-pod-baz
spec:
  podSelector:
    matchLabels:                   # the waypoint pod, not the workload
      gateway.networking.k8s.io/gateway-name: waypoint
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: bar
          podSelector:
            matchLabels:
              app.kubernetes.io/name: baz
      ports:
        - port: 15008              # HBONE, not the application port
          protocol: TCP
---
kind: AuthorizationPolicy          # allow-ingress-to-podinfo-tcp-port-9898-from-ns-bar-with-identity-foo-sa-waypoint
spec:
  action: ALLOW
  targetRefs:                      # bound to the Service (waypoint-evaluated),
    - group: ""                    # not a pod selector
      kind: Service
      name: podinfo
  rules:
    - from:
        - source:
            principals:
              - cluster.local/ns/bar/sa/foo-sa
      to:
        - operation:
            ports:
              - "9898"             # the route's SERVICE port (what the client addressed)

Use the <identity>@ form for waypoint-mirrored clients — entries without a service-account identity degrade to a namespace-scoped source at the waypoint.

Note: the client’s egress toward the waypoint cannot be generated from this chart (it lives in the client’s namespace). Client packages add it with a remote selector override targeting the waypoint pod’s gateway.networking.k8s.io/gateway-name label on port 15008.

ingressOnly

By default the authservice policies apply to all traffic reaching the route’s Service, so in-mesh (east-west) clients are also redirected to OIDC login. Setting authservice.ingressOnly: true scopes enforcement to north-south traffic only:

  • the CUSTOM (ext_authz) policy is scoped to the route’s public hosts (Istio forbids identity-based from.source fields with action: CUSTOM, so hostname matching is used: ingress traffic addresses the external host, in-mesh traffic addresses the cluster-local Service DNS name and never matches);
  • the jwt DENY policy is scoped to the route’s ingress-gateway principals.

In-mesh callers are admitted at the waypoint by the always-emitted <route>-authservice-allow-in-ns policy; ingressOnly additionally exempts them from ext_authz and the JWT requirement.

Binding, mirroring the route’s own AuthorizationPolicy:

  • ambienttargetRefs to the route’s Service (enforced at the auto-created shared waypoint).
  • sidecarselector.matchLabels from the route’s selector.

Authservice Prerequisites

See the Istio authservice docs: an extensionProviders entry named authservice must be registered in the Istio mesh config, and the Service must be enrolled onto the waypoint for enforcement to occur. In ambient mode the waypoint and the ext_authz egress to the provider are auto-generated (targeting the conventional authservice/authservice:10003); in sidecar mode, express the egress yourself in networkPolicies.

Outbound Routes

Outbound routes register external services in the Istio service mesh, enabling controlled egress traffic. This is particularly important when using outboundTrafficPolicy.mode: REGISTRY_ONLY, where only explicitly registered external services are accessible.

Quick Start

Outbound routes are configured under the routes.outbound key in your values file:

routes:
  outbound:
    google:
      enabled: true
      hosts:
        - www.google.com
      ports:
        - number: 443
          name: https
          protocol: HTTPS

This creates a ServiceEntry that allows pods in the mesh to communicate with www.google.com on port 443.

Required fields:

  • enabled: Must be true to generate resources
  • hosts: List of external hostnames to register

Optional:

  • ports: List of port configurations (defaults to HTTPS/443 if omitted)
  • location: Service location - MESH_EXTERNAL (default) or MESH_INTERNAL
  • resolution: DNS resolution strategy - DNS (default), STATIC, DNS_ROUND_ROBIN, DYNAMIC_DNS, or NONE
  • exportTo: ServiceEntry visibility list - ["."] (default) limits the outbound ServiceEntry to the current namespace; use ["*"] for cluster-wide visibility or namespace names for selected namespaces
  • metadata: Custom labels and annotations for the generated ServiceEntry

Generated Resources

ServiceEntry

A ServiceEntry resource is created for each enabled outbound route:

# Generated from outbound route configuration
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
  name: google-external
  namespace: default
  labels:
    service-entries.bigbang.dev/source: bb-common
  annotations:
    outbound.service-entries.generated.bigbang.dev/from-route-name: google
spec:
  hosts:
    - www.google.com
  exportTo:
    - "."
  location: MESH_EXTERNAL
  resolution: DNS
  ports:
    - name: https
      number: 443
      protocol: HTTPS

Note: The ServiceEntry name is automatically suffixed with -external or -internal based on the location setting.

Configuration Options

Location

  • MESH_EXTERNAL (default): Service is external to the mesh
  • MESH_INTERNAL: Service is internal to the mesh

Resolution

  • DNS (default): Use DNS for service discovery
  • STATIC: Use static IP addresses from the service entry
  • DNS_ROUND_ROBIN: DNS-based round-robin load balancing
  • DYNAMIC_DNS: Dynamically resolve DNS
  • NONE: No resolution - use the address as-is

Export Visibility

Outbound ServiceEntries default to namespace-local visibility:

exportTo:
  - "."

Set exportTo to ["*"] to export the ServiceEntry cluster-wide, or list specific namespaces to share it only with those namespaces:

exportTo:
  - namespace-a
  - namespace-b

This option applies only to routes.outbound ServiceEntries. When enabled, ServiceEntries generated from routes.inbound keep Istio’s default visibility of being cluster-wide.

Ports

If no ports are specified, defaults to HTTPS/443. When specifying ports, all three fields are required:

ports:
  - number: 443      # Port number (can be templated string or integer)
    name: https      # Port name
    protocol: HTTPS  # Protocol (HTTP, HTTPS, TCP, etc.)

Examples

Inbound Examples

Simple Application Routing

Basic web application exposed through a gateway:

See routes-simple-application-routing.yaml for the complete configuration.

Multiple Services

Multiple services with different routing configurations:

See routes-multiple-services.yaml for the complete configuration.

Advanced HTTP Rules

Custom routing with path-based rules:

See routes-advanced-http-rules.yaml for the complete configuration.

Custom Gateway Configuration

Using different gateways for external and internal services:

See routes-custom-gateway-configuration.yaml for the complete configuration.

Labels and Annotations

Apply custom labels and annotations to all generated resources using the metadata structure:

See routes-labels-and-annotations.yaml for the complete configuration.

Note: Custom labels and annotations specified in the route metadata configuration are applied to all generated resources (VirtualService, ServiceEntry, NetworkPolicies, AuthorizationPolicies, and the authservice RequestAuthentication). The shared waypoint Gateway is the exception: it is a namespace-level resource shared by all protected routes, so it does not carry any single route’s metadata.

Authservice-Protected Route (OIDC ext_authz)

Inbound routes protected with authservice, including an ingressOnly variant that exempts in-mesh (east-west) clients from OIDC — see Protecting a Route with Authservice for the generated resources:

See routes-authservice-protected.yaml for the complete configuration.

Service and Container Port Discrepancy

When your Kubernetes service port differs from the actual container port, use containerPort to ensure NetworkPolicies target the correct port:

routes:
  inbound:
    loki:
      enabled: true
      gateways:
        - istio-gateway/public-ingressgateway
      hosts:
        - loki.dev.bigbang.mil
      service: logging-loki-gateway.logging.svc.cluster.local
      port: 80              # Service port (used in VirtualService)
      containerPort: 8080   # Container port (used in NetworkPolicy)
      selector:
        app.kubernetes.io/name: logging-loki

This configuration creates: - A VirtualService that routes to the service on port 80 - A NetworkPolicy that allows traffic to the pods on port 8080

See routes-container-port.yaml for the complete configuration.

Outbound Examples

Basic Outbound Route

Allow access to external services with default HTTPS configuration:

routes:
  outbound:
    google:
      enabled: true
      hosts:
        - www.google.com
        - google.com

    github:
      enabled: true
      hosts:
        - api.github.com
        - github.com

Custom Ports and Protocols

Define specific ports and protocols for external services:

routes:
  outbound:
    database:
      enabled: true
      hosts:
        - db.example.com
      ports:
        - number: 5432
          name: postgres
          protocol: TCP
        - number: 5433
          name: postgres-replica
          protocol: TCP

    api-service:
      enabled: true
      hosts:
        - api.example.com
      ports:
        - number: 80
          name: http
          protocol: HTTP
        - number: 443
          name: https
          protocol: HTTPS

Mesh Internal Services

Register services internal to the mesh with custom resolution:

routes:
  outbound:
    internal-service:
      enabled: true
      hosts:
        - internal.service.local
      location: MESH_INTERNAL
      resolution: NONE
      metadata:
        labels:
          environment: production
          team: platform
        annotations:
          description: "Internal service for cross-namespace communication"

Custom Export Visibility

Share an outbound ServiceEntry with selected namespaces:

routes:
  outbound:
    shared-api:
      enabled: true
      hosts:
        - api.example.com
      exportTo:
        - namespace-a
        - namespace-b