Skip to content

필사 모드: Migrating Between Ingress Controllers — A Zero-Downtime Strategy

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

Introduction

An Ingress Controller is infrastructure you rarely change once chosen. Yet in 2026 the number of organizations evaluating a replacement has jumped. The biggest reason is that ingress-nginx, the most widely used option, has moved into maintenance mode, and operational risk grew as several security issues (CVEs) were reported in its annotation-based configuration injection (snippet). On top of that comes the large trend of the Ingress API being frozen and the flow moving toward the Gateway API.

The problem is that the Ingress Controller is the entry point for external traffic. Replace it wrongly and your entire service goes down at once. Therefore a replacement should be approached not as a "swap it all at once" but as a zero-downtime strategy of "keeping two controllers coexisting and moving traffic over bit by bit."

In this article we walk step by step through the practical procedure: clarifying the migration motivation, building an inventory, mapping annotations, coexisting via IngressClass separation, weighted DNS cutover, staged rollout and rollback, verification, common pitfalls, and a final checklist.

Clarifying the Migration Motivation

You must first clarify "why are we changing?" so that your cutover goals and verification criteria fall into place. Common motivations include:

- **Maintenance/security**: ingress-nginx moving to maintenance mode, responding to snippet-related CVEs

- **Gateway API transition**: moving to a Gateway API native implementation in line with the Ingress-frozen trend

- **Feature requirements**: needing API gateway features (auth, rate limiting, transformation) or a multitenancy delegation model

- **Performance/observability**: dynamic reload, better metrics/tracing

- **Cost**: load balancer consolidation, operational simplification

The target controller differs by motivation. If the motivation is security/maintenance, a relatively simple swap to an Ingress-compatible controller like HAProxy or Traefik; if it is a Gateway API transition, a model change to something like Envoy Gateway becomes the goal.

Building an Inventory

The first practical step of the cutover is to fully understand what is currently deployed. Collect all Ingress resources with the following commands.

List Ingresses in all namespaces

kubectl get ingress -A -o wide

Back up the full definitions including annotations

kubectl get ingress -A -o yaml > ingress-backup.yaml

Check the IngressClasses in use

kubectl get ingressclass

Tally which annotations are used

kubectl get ingress -A -o json \

| jq -r '.items[].metadata.annotations | keys[]' \

| sort | uniq -c | sort -rn

The last command tallies which annotations are used and how often across the whole cluster, revealing which features you must reproduce in the new controller. Also catalog TLS Secrets, cert-manager-issued certificates, and external DNS records.

Annotation Mapping Table

The most labor-intensive task is moving annotations into the new controller's expression. Here is the correspondence for representative ingress-nginx annotations.

| Feature | ingress-nginx | Traefik | Contour |

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

| Path rewrite | rewrite-target | Middleware (ReplacePathRegex) | pathRewritePolicy |

| SSL redirect | ssl-redirect | Middleware (RedirectScheme) | virtualhost.tls auto |

| Body size limit | proxy-body-size | Middleware (Buffering) | global config |

| Rate limit | limit-rps | Middleware (RateLimit) | global/external |

| Backend protocol | backend-protocol | serversTransport | service protocol |

| Allowlist | whitelist-source-range | Middleware (IPWhiteList) | authorization/external |

As the table shows, a single ingress-nginx annotation scatters into a separate Middleware CRD in Traefik and into HTTPProxy fields or global config in Contour. In other words, recognize that this is not a one-to-one conversion but a model conversion.

Coexistence Strategy: IngressClass Separation

The core of a zero-downtime cutover is to run both controllers at the same time and clearly distinguish, via IngressClass, which one handles which Ingress.

External DNS

┌────────────┴────────────┐

│ (distribute by weight/record)

▼ ▼

┌──────────────┐ ┌──────────────┐

│ LB (old) │ │ LB (new) │

│ nginx ctrl │ │ traefik ctrl │

└──────┬───────┘ └──────┬───────┘

│ class: nginx │ class: traefik

▼ ▼

┌───────────────────────────────────────────┐

│ Same backend Service/Pod │

└───────────────────────────────────────────┘

Install the new controller with a separate IngressClass.

apiVersion: networking.k8s.io/v1

kind: IngressClass

metadata:

name: traefik

spec:

controller: traefik.io/ingress-controller

With this, existing Ingresses (ingressClassName: nginx) continue to be handled by the nginx controller, and only newly created resources (ingressClassName: traefik) are handled by the new controller. The two do not interfere with each other, so you can run them in parallel safely.

Converting Annotations to CRDs/Middlewares

If you are moving to Traefik, convert the ingress-nginx rewrite annotation into a Middleware.

apiVersion: traefik.io/v1alpha1

kind: Middleware

metadata:

name: strip-api-prefix

spec:

replacePathRegex:

regex: ^/api/(.*)

replacement: /$1

apiVersion: traefik.io/v1alpha1

kind: IngressRoute

metadata:

name: web

spec:

entryPoints:

- websecure

routes:

- match: Host(`app.example.com`) && PathPrefix(`/api`)

kind: Rule

services:

- name: api

port: 80

middlewares:

- name: strip-api-prefix

If you are moving to Contour, convert it into an HTTPProxy.

apiVersion: projectcontour.io/v1

kind: HTTPProxy

metadata:

name: web

spec:

virtualhost:

fqdn: app.example.com

tls:

secretName: app-tls

routes:

- conditions:

- prefix: /api

pathRewritePolicy:

replacePrefix:

- replacement: /

services:

- name: api

port: 80

The key is to create the converted resources under the new IngressClass and verify them first, isolated from existing traffic.

Weighted DNS Cutover

Once you have replicated resources to the new controller and verified them, it is time to move actual traffic. Because the two controllers each have their own LoadBalancer, you shift traffic gradually by adjusting weights in DNS.

Stage 1: new 0% - synthetic traffic only to new controller (internal verify)

Stage 2: new 5% - canary. Compare error rate / latency

Stage 3: new 25% - confirm metrics are stable

Stage 4: new 50% - even split, compare load

Stage 5: new 100% - full cutover

Stage 6: remove old - clean up old controller after a stabilization period

You can use weighted DNS (such as a routing policy) or place a shared entry point in front of the two LBs. Allow a sufficient observation period between each stage, and immediately revert to the previous weight if something is off.

Staged Rollout and Rollback

The cutover procedure at the command level looks like this.

1) Install the new controller (separate IngressClass)

helm install traefik traefik/traefik \

--namespace traefik --create-namespace \

--set ingressClass.name=traefik

2) Apply the converted resources (new class)

kubectl apply -f converted-routes/

3) Verify directly against the new LB endpoint

curl -H "Host: app.example.com" http://<NEW_LB_IP>/healthz

4) Raise the DNS weight in stages (5 -> 25 -> 50 -> 100)

Observe metrics at each stage

5) If rollback is needed, set the DNS weight to 0 immediately

Resources stay in place, so only traffic returns to the old controller

The core of rollback design is "never delete the old controller and its Ingress until a stabilization period after the cutover is complete." It must be built so that reverting only the DNS weight instantly restores the previous state.

Verification: Traffic Comparison and Synthetic Monitors

At each cutover stage, verify the following by comparison.

- **Status code distribution**: Is the 2xx/4xx/5xx ratio the same as the old controller?

- **Latency**: Have the p50/p95/p99 percentile latencies not degraded?

- **TLS behavior**: Are the certificate chain, SNI, and redirects identical?

- **Path matching**: Does the rewrite-result URL match the backend's expectation?

With a synthetic check, throwing key paths at both controllers simultaneously and diffing the responses lets you catch behavioral differences early.

Send the same request to both LBs and compare responses

for path in / /api/users /login /static/app.js; do

old=$(curl -s -o /dev/null -w "%{http_code}" -H "Host: app.example.com" http://<OLD_LB_IP>$path)

new=$(curl -s -o /dev/null -w "%{http_code}" -H "Host: app.example.com" http://<NEW_LB_IP>$path)

echo "$path old=$old new=$new"

done

Pitfalls: Behavioral Differences

Even with seemingly identical configuration, subtle behavioral differences between controllers cause incidents. The representative ones:

- **Rewrite regex differences**: ingress-nginx's rewrite-target and capture-group behavior differ from Traefik/Contour. Always check slash handling and trailing-slash presence.

- **Path-matching priority**: Prefix vs Exact and longest-match-wins rules may differ per implementation.

- **Default timeouts**: Backend response timeout and idle timeout defaults differ. A long request may be cut off only on the new controller.

- **Header handling**: Differences in policy for adding/overwriting X-Forwarded-* headers.

- **Body size limit**: Differing defaults can block uploads only on the new controller.

- **Regex host matching**: Differences in how wildcard hosts are handled.

These differences are hard to catch by spec comparison alone; they surface only when you run real traffic through the synthetic-monitor diff above.

Migration Checklist

The items to check before and after the cutover.

[ ] Back up all Ingress inventory (kubectl get ingress -A -o yaml)

[ ] Fully tally annotations in use and build a mapping table

[ ] Catalog TLS Secrets / cert-manager-issued certificates

[ ] Install new controller with a separate IngressClass (coexist)

[ ] Author annotation -> CRD/Middleware converted resources

[ ] Verify the new LB endpoint directly (with Host header)

[ ] Diff both responses with a synthetic monitor

[ ] Stage DNS weight cutover (5->25->50->100), observe each stage

[ ] Pass status-code/latency/TLS/path-matching comparison

[ ] Rehearse rollback scenario (revert weight to 0)

[ ] Clean up old controller/old Ingress after stabilization period

[ ] Plan a Gateway API transition path for the long term

Conclusion

Replacing an Ingress Controller is a risky operation that changes your external entry point, but by coexisting two controllers via IngressClass separation and migrating gradually with DNS weights, you can do it safely with zero downtime. The core points are three. First, an accurate inventory and annotation mapping. Second, the recognition that this is a model conversion rather than a one-to-one conversion, plus synthetic-monitor verification of behavioral differences. Third, a rollback design where reverting only the DNS weight instantly restores the previous state.

Finally, since you are replacing the controller anyway, planning the transition path to the Gateway API for the long term — in line with the Ingress-frozen trend — means you won't have to go through one more big cutover next time.

References

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

- Kubernetes IngressClass docs: https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class

- ingress-nginx annotations docs: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/

- Traefik Kubernetes Ingress docs: https://doc.traefik.io/traefik/providers/kubernetes-ingress/

- Traefik Middleware docs: https://doc.traefik.io/traefik/middlewares/overview/

- Project Contour HTTPProxy docs: https://projectcontour.io/docs/main/config/fundamentals/

- HAProxy Kubernetes Ingress docs: https://www.haproxy.com/documentation/kubernetes-ingress/

- Gateway API migration (ingress2gateway): https://gateway-api.sigs.k8s.io/guides/migrating-from-ingress/

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

현재 단락 (1/153)

An Ingress Controller is infrastructure you rarely change once chosen. Yet in 2026 the number of org...

작성 글자: 0원문 글자: 10,422작성 단락: 0/153