필사 모드: The Complete Guide to Emissary-ingress (Ambassador): Everything About API Gateway-style Ingress
EnglishIntroduction
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...