Skip to content

필사 모드: The Complete Guide to Emissary-ingress (Ambassador): Everything About API Gateway-style Ingress

English
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

Introduction

When exposing services from Kubernetes to the outside world, Ingress is usually the first thing that comes to mind. But once you actually run things in production, a moment arrives when you need more than simple path-based routing. You want to attach authentication, apply rate limits, route only 5 percent of traffic to a new version via a canary release, branch routing based on headers, and handle gRPC and WebSocket at the same time.

As these requirements pile up, you eventually arrive at the question: "Isn't what I actually need an API Gateway rather than an Ingress?" Emissary-ingress (formerly Ambassador) is a project that starts exactly at that point. As a CNCF incubating project, it uses Envoy Proxy as its data plane and provides API Gateway features in a Kubernetes-native way.

As of 2026, the ingress ecosystem is going through a major inflection point. The networking Ingress API is effectively frozen, meaning no new features are being added to it. Gateway API has established itself as the successor standard, and ingress-nginx, once the most widely used controller, is trending toward something close to maintenance mode due to security issues and a shortage of maintainers. In this situation, understanding a modern Envoy-based controller is increasingly important.

In this article, we will dissect the architecture of Emissary-ingress, take a deep look at its core resource the Mapping CRD, and cover authentication, rate limiting, traffic management, canary releases, Gateway API support, hands-on deployment, operational tuning, and the common pitfalls people fall into, all in one place.

What Is Emissary-ingress

Emissary-ingress is an open-source API Gateway and Ingress controller for Kubernetes. Let us first lay out its key characteristics.

- It uses Envoy Proxy as its data plane. In other words, the battle-tested Envoy handles the actual traffic.

- It declares all configuration via Kubernetes CRDs (custom resources). Mapping, Host, Listener, and AuthService are representative examples.

- Rather than annotation-based configuration, it defines routing through independent resources, so a decentralized configuration model in which each microservice team owns its own Mapping comes naturally.

- It provides API Gateway features such as gRPC, WebSocket, HTTP/2, HTTP/3, TLS termination, authentication, and rate limiting out of the box.

Historically, this project started from Ambassador, built by Datawire (now Ambassador Labs). Later, when the open-source core was donated to the CNCF, it was renamed Emissary-ingress, while the commercial version, named Ambassador Edge Stack (AES), additionally provides a developer portal, advanced rate limiting, OAuth/OIDC filters, a web application firewall, and more.

Positioning at a Glance

| Aspect | Plain Ingress controller | Emissary-ingress |

| --- | --- | --- |

| Primary use | L7 path routing, TLS termination | API Gateway, path routing, traffic management |

| Configuration style | Ingress resource plus annotations | Dedicated CRDs (Mapping, etc.) |

| Data plane | Varies by controller | Envoy Proxy |

| Authentication | Requires separate setup | Built in via AuthService |

| Rate limiting | Limited | Built in via RateLimitService |

| Canary | Difficult | Built in, weight based |

| Gateway API | Varies by controller | Supported |

Architecture: Separating the Control Plane and the Data Plane

To truly understand Emissary-ingress, you first need to grasp the separation of the control plane and the data plane. Many plain Ingress controllers render a configuration file directly and restart the proxy, but Emissary leverages Envoy's xDS (dynamic discovery) protocol.

+-----------------------------+

| Kubernetes API Server |

| (Mapping, Host, Listener...) |

+--------------+--------------+

| watch (CRD)

v

+----------------------------------------+

| Emissary Pod |

| +----------------+ +--------------+ |

| | controller | | Envoy | |

| | (CRD -> snapshot|-->| (data plane) | |

| | -> Envoy conf) |xDS| | |

| +----------------+ +------+-------+ |

+--------------------------------|--------+

| L7 traffic

client ---- :8080/:8443 ------+----> upstream services

Breaking the flow into steps looks like this.

1. The user creates CRDs such as Mapping and Host with `kubectl apply`.

2. The Emissary controller watches the Kubernetes API server and detects changes.

3. The controller gathers all resources into a single consistent snapshot and converts it into Envoy configuration.

4. The converted configuration is delivered to the Envoy data plane via the xDS (or V3 ADS) protocol.

5. Envoy applies the new configuration without downtime and handles the actual client traffic.

Thanks to this structure, you can update routing rules when configuration changes without restarting the entire proxy process. The data plane (Envoy) focuses solely on traffic handling, while the control plane focuses on translating the declared intent into a form Envoy understands.

The Core Resource Hierarchy

In Emissary 3.x, the path that incoming traffic takes is composed by the following resources working together.

- Listener: defines which port and protocol Envoy receives requests on (for example, 8080 HTTP, 8443 HTTPS).

- Host: ties together a domain (hostname), TLS settings, and certificates. Automatic ACME issuance is handled here too.

- Mapping: the actual routing rule. It defines which service a path prefix is sent to.

- AuthService / RateLimitService: define the behavior of external filters.

Thanks to this separation, "which port do we listen on (Listener)," "which domain do we handle (Host)," and "which path goes where (Mapping)" are cleanly divided.

A Deep Look at the Mapping CRD

Mapping is the heart of Emissary. Let us start with its most basic form.

apiVersion: getambassador.io/v3alpha1

kind: Mapping

metadata:

name: quote-backend

namespace: default

spec:

hostname: "*"

prefix: /backend/

service: quote

This Mapping sends every request starting with `/backend/` to the `quote` service. The `hostname: "*"` means it applies to all domains. The service name points to a Kubernetes Service in the same namespace, and you can specify other namespaces with the `quote.namespace` form.

prefix and Regex Routing

apiVersion: getambassador.io/v3alpha1

kind: Mapping

metadata:

name: regex-route

spec:

hostname: "*"

prefix: "/user/[0-9]+/profile"

prefix_regex: true

service: user-service

If you set `prefix_regex: true`, the prefix is interpreted as a regular expression. However, regex routing has a performance cost, so it is best used only when truly necessary.

Header and Query Parameter Based Routing

apiVersion: getambassador.io/v3alpha1

kind: Mapping

metadata:

name: canary-by-header

spec:

hostname: "*"

prefix: /api/

service: api-v2

headers:

x-api-version: "v2"

The Mapping above sends only requests carrying the `x-api-version: v2` header to `api-v2`. Requests without the header flow to a different Mapping handling the same prefix. This kind of header-based branching is useful for gradual migrations or for separating internal test traffic.

Path Rewriting and Host Rewriting

apiVersion: getambassador.io/v3alpha1

kind: Mapping

metadata:

name: rewrite-example

spec:

hostname: "*"

prefix: /legacy/

rewrite: /v3/

host_rewrite: internal.example.com

service: modern-service

A `/legacy/foo` request is delivered to the upstream as `/v3/foo`, and the Host header is changed to `internal.example.com`. This is a common pattern when you keep the legacy path but swap out only the backend.

Timeouts, Retries, and Circuit Breakers

apiVersion: getambassador.io/v3alpha1

kind: Mapping

metadata:

name: resilient-route

spec:

hostname: "*"

prefix: /orders/

service: orders

timeout_ms: 4000

connect_timeout_ms: 1500

retry_policy:

retry_on: "5xx"

num_retries: 3

circuit_breakers:

- max_connections: 2048

max_pending_requests: 1024

max_requests: 2048

Envoy's powerful resilience features are exposed directly as Mapping fields. `retry_on` defines under which conditions to retry (5xx, gateway-error, connect-failure, and so on), and `circuit_breakers` sets the thresholds that prevent upstream overload.

Authentication: AuthService

Emissary handles authentication through Envoy's ext_authz (external authorization) filter. When you create an AuthService resource, all (or specific) requests are first forwarded to the authentication service before they reach the upstream.

apiVersion: getambassador.io/v3alpha1

kind: AuthService

metadata:

name: authentication

namespace: default

spec:

auth_service: "auth-service:3000"

proto: http

path_prefix: "/extauth"

allowed_request_headers:

- "x-request-id"

- "authorization"

allowed_authorization_headers:

- "x-user-id"

- "x-user-role"

The behavior is as follows.

1. When a request arrives, Emissary sends an authentication request to `auth-service:3000` before processing the body.

2. If the authentication service returns 200 OK, the request passes through and the response headers (such as `x-user-id`) are forwarded to the upstream.

3. If the authentication service returns 401/403, the request is blocked right there.

To make only a specific Mapping skip authentication, add `bypass_auth: true` to that Mapping. In the commercial Edge Stack, you can declaratively attach OAuth2/OIDC as a Filter resource, so you do not have to implement a separate authentication service yourself.

Rate Limiting: RateLimitService

Rate limiting also leverages Envoy's ext filter mechanism. First, you register an external rate limit service with a RateLimitService.

apiVersion: getambassador.io/v3alpha1

kind: RateLimitService

metadata:

name: ratelimit

namespace: default

spec:

service: "ratelimit-service:8081"

protocol_version: v3

domain: emissary

Then, in the Mapping, you specify with labels which dimension (descriptor) the rate limit is applied to.

apiVersion: getambassador.io/v3alpha1

kind: Mapping

metadata:

name: rate-limited-api

spec:

hostname: "*"

prefix: /api/

service: api

labels:

emissary:

- request_label_group:

- remote_address:

key: remote_address

Here, the group under `labels` is converted into an Envoy rate limit descriptor and forwarded to the external rate limit service. The rate limit service manages a counter per descriptor and is configured to return 429 once the limit is exceeded. Open-source Emissary uses the Envoy ratelimit service (with a Redis backend) as is, while Edge Stack provides an integrated rate limiting in which limits are declared as CRDs.

Traffic Management and Canary Releases

The real value of an API Gateway-style Ingress shows up in traffic management. If you assign `weight` to two Mappings handling the same prefix, you get weight-based traffic splitting.

apiVersion: getambassador.io/v3alpha1

kind: Mapping

metadata:

name: app-stable

spec:

hostname: "*"

prefix: /app/

service: app-v1

weight: 90

apiVersion: getambassador.io/v3alpha1

kind: Mapping

metadata:

name: app-canary

spec:

hostname: "*"

prefix: /app/

service: app-v2

weight: 10

This configuration sends 90 percent of `/app/` traffic to v1 and 10 percent to v2. This is the simplest form of a canary release. If your monitoring metrics look healthy, you gradually raise the weight from 10 to 25, 50, and 100.

incoming /app/ requests

|

v

+-----------------+

| Emissary route |

| weight split |

+--+-----------+--+

| 90% | 10%

v v

app-v1 app-v2 (canary)

Combined with GitOps, you can control a gradual rollout simply by adjusting this weight value through a PR. When integrated with a tool like Argo Rollouts, metric-based automatic promotion is also possible. Combining a header-based canary (only specific internal users get the new version) with a weight-based canary (a random ratio) is a common pattern as well.

Traffic Shadowing

apiVersion: getambassador.io/v3alpha1

kind: Mapping

metadata:

name: shadow-traffic

spec:

hostname: "*"

prefix: /app/

service: app-v2-shadow

shadow: true

`shadow: true` sends a copy of the real traffic to the new version but discards the response. In other words, you can observe how the new version handles real traffic without affecting users at all. This is useful for a dark launch.

API Gateway vs Plain Ingress: When to Use Which

Let us pause here to clarify. Not every service needs an API Gateway.

A plain Ingress is sufficient in the following cases.

- When exposing an internal tool or a small service owned by a single team

- When you only need path-based routing and TLS termination

- When authentication is already handled at the application or service-mesh level

By contrast, an API Gateway-style Ingress like Emissary shines in these cases.

- When multiple teams each own their microservices and you want to manage routing configuration in a decentralized way

- When you want to apply authentication, rate limiting, and transformation uniformly at the gateway level

- When you need gradual deployments such as canary, traffic shadowing, and header-based routing

- When you need to handle diverse protocols such as gRPC, WebSocket, and HTTP/3 from a single entry point

| Requirement | Recommendation |

| --- | --- |

| Simple path routing plus TLS | Basic Ingress controller |

| Decentralized routing ownership | Emissary Mapping |

| Gateway-level authentication | Emissary AuthService |

| Gradual canary | Emissary weight or Argo Rollouts |

| Standardized, future-proof API | Gateway API (supported by Emissary) |

The Developer Portal

The commercial Ambassador Edge Stack includes a developer portal (Dev Portal). This is a feature that automatically generates an API catalog page once you attach an OpenAPI spec to a Mapping.

apiVersion: getambassador.io/v3alpha1

kind: Mapping

metadata:

name: catalog-api

labels:

docs.getambassador.io/source: "true"

spec:

hostname: "*"

prefix: /catalog/

service: catalog

docs:

path: "/openapi.json"

If you specify the path to the OpenAPI (Swagger) document in `docs.path`, the portal collects it and automatically generates documentation that external and internal developers can browse. As the number of microservices grows, automatically aggregating "which API lives where and how to call it" is a significant operational advantage. Open-source Emissary alone does not provide the portal UI, so if you need this feature you should consider Edge Stack.

Gateway API: The Flow Toward the Future Standard

As mentioned earlier, as of 2026 the networking Ingress API is frozen and Gateway API has established itself as the successor standard. The core of Gateway API is its role-oriented design.

- GatewayClass: the kind of gateway implementation defined by the infrastructure provider (for example, Emissary)

- Gateway: the actual entry point (listeners, ports) created by the cluster operator

- HTTPRoute: the routing rule created by the application developer

This separation is philosophically very similar to the Listener/Host/Mapping separation that Emissary originally pursued. That is why Emissary supports Gateway API alongside its own CRDs.

apiVersion: gateway.networking.k8s.io/v1

kind: Gateway

metadata:

name: emissary-gateway

spec:

gatewayClassName: emissary

listeners:

- name: http

protocol: HTTP

port: 8080

apiVersion: gateway.networking.k8s.io/v1

kind: HTTPRoute

metadata:

name: app-route

spec:

parentRefs:

- name: emissary-gateway

hostnames:

- "app.example.com"

rules:

- matches:

- path:

type: PathPrefix

value: /app/

backendRefs:

- name: app

port: 80

Looking at the flow, GatewayClass points to Emissary, Gateway opens a listener on port 8080, and HTTPRoute sends the `/app/` path to the `app` service. Gateway API's `backendRefs` supports multiple backends and weights, so scenarios like canary can also be expressed in a standard way.

GatewayClass(emissary)

|

v

Gateway ---- listener :8080

^

| parentRefs

HTTPRoute ---- /app/ -> Service(app)

For a new project, it is well worth actively considering starting with Gateway API instead of a vendor CRD like Mapping. Because it is a standard, even if you switch controllers later, there is a good chance you can reuse the routing definitions as is. That said, advanced features that Gateway API does not yet cover (some Envoy details) may need to be supplemented with vendor CRDs or extension fields.

Hands-on Deployment

Let us follow the most common flow for installing Emissary with Helm.

Install the CRDs first

kubectl apply -f https://app.getambassador.io/yaml/emissary/3.10.0/emissary-crds.yaml

kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system

Add the Helm repo and install

helm repo add datawire https://app.getambassador.io

helm repo update

helm install emissary-ingress datawire/emissary-ingress \

--namespace emissary \

--create-namespace

kubectl rollout status deployment/emissary-ingress -n emissary

Once installation is complete, you define a basic Listener.

apiVersion: getambassador.io/v3alpha1

kind: Listener

metadata:

name: http-listener

namespace: emissary

spec:

port: 8080

protocol: HTTP

securityModel: XFP

hostBinding:

namespace:

from: ALL

apiVersion: getambassador.io/v3alpha1

kind: Listener

metadata:

name: https-listener

namespace: emissary

spec:

port: 8443

protocol: HTTPS

securityModel: XFP

hostBinding:

namespace:

from: ALL

Next, you create a Host that ties together a domain and TLS. You can automatically issue certificates with cert-manager or Emissary's built-in ACME.

apiVersion: getambassador.io/v3alpha1

kind: Host

metadata:

name: example-host

namespace: emissary

spec:

hostname: app.example.com

acmeProvider:

authority: https://acme-v02.api.letsencrypt.org/directory

email: ops@example.com

tlsSecret:

name: app-example-com-tls

Finally, you apply a routing Mapping and verify the behavior.

kubectl apply -f mapping.yaml

Check the external IP

kubectl get service emissary-ingress -n emissary

Verify routing

EMISSARY_IP=$(kubectl get svc emissary-ingress -n emissary -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

curl -i http://$EMISSARY_IP/backend/

Check diagnostics (port forwarding)

kubectl port-forward -n emissary deploy/emissary-ingress 8877

curl http://localhost:8877/ambassador/v0/diag/?json=true | jq .

The `/ambassador/v0/diag/` diagnostics endpoint shows all the Mappings that Emissary currently recognizes and the cluster state, so it is the first place to look when checking whether your configuration has been applied correctly.

Comparison with Another Envoy-based Controller: Contour

Besides Emissary, Contour is a representative controller that also uses Envoy as its data plane. Both are CNCF projects and Envoy based, but they aim at different things.

| Item | Emissary-ingress | Contour |

| --- | --- | --- |

| Main steward | Ambassador Labs (core is CNCF) | VMware/CNCF |

| Core CRD | Mapping, Host, Listener | HTTPProxy |

| Aim | Full API Gateway set | Lightweight Ingress plus L7 routing |

| Auth / rate limit | Built in (AuthService, etc.) | External ext_authz integration |

| Delegation model | Per namespace/resource | HTTPProxy include delegation |

| Gateway API | Supported | Actively supported |

| Commercial version | Edge Stack | None (pure open source) |

A rough rule of thumb for choosing is this. If you want a full set of features such as authentication, rate limiting, and a developer portal at the gateway level, Emissary is advantageous. Conversely, if you need a lightweight, simple Envoy-based L7 router and handle authentication externally, Contour is cleaner. Contour's HTTPProxy has a clear delegation model via include, which gives it an edge in preventing path conflicts in multi-tenant environments.

Operations and Tuning

To run Emissary stably in production, it is good to take care of the following.

Resources and Scaling

Envoy's memory usage grows in proportion to the scale of the configuration (the number of routes and clusters). As Mappings grow into the thousands, the cost of the controller recomputing the snapshot also increases, so set appropriate CPU/memory requests and limits and configure horizontal scaling with an HPA.

Helm values example (partial)

replicaCount: 3

resources:

requests:

cpu: 500m

memory: 512Mi

limits:

cpu: "1"

memory: 1Gi

autoscaling:

enabled: true

minReplicas: 3

maxReplicas: 10

targetCPUUtilizationPercentage: 70

Observability

Emissary exposes Envoy's statistics in Prometheus format. Monitoring request counts, latency (p50/p90/p99), upstream 5xx ratio, active connections, and so on in a dashboard gives you the basis for canary promotion decisions. For distributed tracing, you can integrate with a Zipkin/Jaeger/OpenTelemetry collector via the TracingService resource.

apiVersion: getambassador.io/v3alpha1

kind: TracingService

metadata:

name: tracing

namespace: emissary

spec:

service: "otel-collector:9411"

driver: zipkin

sampling:

overall: 10

Zero-downtime Redeployment

When upgrading Emissary itself, place a PodDisruptionBudget, appropriate readiness/liveness probes, and enough replicas so that traffic is not interrupted even during a rolling update. If you use `externalTrafficPolicy: Local` on the LoadBalancer service, the client IP is preserved, but you have to be careful about node distribution.

Common Pitfalls and Troubleshooting

Here we summarize the problems you frequently encounter in real operations.

- You get a 404 even though the Mapping exists: the most common cause is that there is no Listener, or the Host's hostname does not match the request's Host header. First check on the `/ambassador/v0/diag/` diagnostics page whether that Mapping is actually loaded.

- Configuration is not being applied: Emissary only applies all CRDs when a consistent snapshot is formed. If even one resource fails validation, the entire snapshot may be rejected, so check the controller logs for validation errors.

- TLS certificate is not applied: check whether the Host's tlsSecret is in the same namespace and whether ACME issuance has completed, by looking at the Host's state. ACME requires that the HTTP-01 challenge over port 80 be reachable.

- gRPC does not work: people often forget to set `grpc: true` in the Mapping. Check the HTTP/2 prior knowledge setting and protocol matching together.

- The canary ratio feels inaccurate: weight is a statistical split, so the ratio can fluctuate when the request count is low. Also, if the sum of weights of the Mappings handling the same prefix is not 100, it is normalized differently than you intended.

- 503 upstream connect error: check whether the upstream service's endpoints are ready and whether the port and protocol (especially whether TLS origination is used) match. You can see the `cluster` state on the diagnostics page.

The golden rule of problem diagnosis is "diagnostics endpoint first, controller logs next, Envoy statistics last." Looking in this order quickly narrows down most routing problems.

Conclusion

Emissary-ingress is not a mere Ingress controller, but an API Gateway that unfolds the power of Envoy through Kubernetes-native CRDs. From the clear separation of control plane and data plane, decentralized routing ownership through Mapping, gateway-level authentication and rate limiting, weight-based canary and traffic shadowing, all the way to Gateway API support, it packs in almost every element you need for modern traffic management.

Viewed in the context of 2026, the direction is clear. The Ingress API is frozen, Gateway API is taking over the position of the standard, and existing controllers are facing maintenance and security burdens. In this flow, the strategy of understanding an Envoy-based controller and standardizing new routing on Gateway API significantly reduces the cost of future migrations.

If you only need simple exposure, a basic Ingress is enough. But when the moment arrives that you need multiple teams, multiple microservices, gradual deployments, and gateway-level policies, an API Gateway-style Ingress like Emissary becomes the answer. Start small, put a diagnostics endpoint and observability in place, and expand gradually with weight and Gateway API.

References

- Emissary-ingress official docs: https://www.getambassador.io/docs/emissary

- Ambassador Labs: https://www.getambassador.io

- Emissary-ingress GitHub: https://github.com/emissary-ingress/emissary

- Gateway API (SIG Network): https://gateway-api.sigs.k8s.io

- Envoy Proxy docs: https://www.envoyproxy.io/docs

- Kubernetes Ingress concepts: https://kubernetes.io/docs/concepts/services-networking/ingress

- cert-manager docs: https://cert-manager.io/docs

- Project Contour: https://projectcontour.io

- Kubernetes Gateway API concepts: https://kubernetes.io/docs/concepts/services-networking/gateway

현재 단락 (1/382)

When exposing services from Kubernetes to the outside world, Ingress is usually the first thing that...

작성 글자: 0원문 글자: 21,225작성 단락: 0/382