Moving an existing install to Traefik
New installs serve their host through Traefik and the Kubernetes Gateway API. Earlier releases used
ingress-nginx, which the Kubernetes project has retired. This page is for an install that already runs
ingress-nginx: the move is done in steps, and the host keeps answering throughout. A cluster with no
ingress-nginx needs none of it; dcctl install puts Traefik alone on it.
What each command does to a cluster that still has ingress-nginx:
dcctl installleavesingress-nginxserving and installs Traefik and the Gateway API definitions beside it. Traefik gets a load balancer of its own, so it has its own address.dcctl upgradegives each instance a Gateway and HTTPRoutes, and keeps its Ingress objects beside them for as long asingress-nginxis on the cluster. Both serve the host.- DNS is yours to move, when you are ready.
dcctl upgrade --drop-legacy-ingressstops rendering the instance's Ingress objects.dcctl install --remove-legacy-ingressremovesingress-nginx, and only once no Ingress object names its class.
Before you start
- Know what is different at the edge. See What changes at the edge.
- If the instance gets its certificate from an ACME issuer (
ingress.tls.selfSigned: falseand aClusterIssuer), read Certificates from an ACME issuer: it needs a change after DNS has moved, not before. - Give
dcctl installthe settings the cluster was installed with, such as--ha, as well as the new--version, as for any rolling upgrade.
The steps
-
Install the new controller. This leaves
ingress-nginxserving.dcctl install <provider> --version <new-version> -
Upgrade each instance.
--dry-runfirst prints which front end the instance will have.dcctl upgrade <provider> <instance> --version <new-version> --dry-rundcctl upgrade <provider> <instance> --version <new-version>The instance now has a Gateway named
devicechainin its namespace (dci-<instance>), with anhttpand anhttpslistener, and HTTPRoutes behind it. Its Ingress objects are still there. The two have separate certificates: the Ingress objects keepdevicechain-tls, which is what DNS still reaches, so the live host never waits for a new certificate; the Gateway usesdevicechain-tls-gateway. The Gateway keeps that name for good, even after the Ingress objects are gone.The upgrade then checks the Gateway's certificate. See The certificate gate.
-
Find Traefik's address and check the instance through it, before DNS moves.
kubectl -n traefik get service traefikkubectl -n dci-<instance> get gateway devicechaincurl --resolve <host>:443:<traefik-address> https://<host>/The Service's
EXTERNAL-IP(or hostname, on AWS) is Traefik's address. The Gateway should reportProgrammed.--resolvemakescurlsend the request to Traefik while still using your host name, so the certificate and the routes are the ones a browser would see. Add-k(or--cacertwith the instance's CA, see TLS, flags and a laptop install) when the instance is self-signed. -
Move DNS. Point the host at Traefik's address. Nothing else is needed, and nothing has to be quick: the Ingress objects keep answering for as long as clients still resolve the old address.
-
Check the Gateway's certificate. On an instance that terminates TLS with its own CA (the default self-signed setup), confirm the Gateway's certificate was issued by the instance CA before you drop the Ingress objects. An instance upgraded across the issuer switch could be left with a Gateway certificate issued by the earlier self-signed issuer: it has an empty subject and is not a CA-signed leaf, so an OpenSSL-based client has nothing to anchor it to, although a browser that accepts exceptions does not show the difference.
Releases from v0.26.1 sign under a new issuer name, so a certificate issued by an upgrade cannot come out that way, and
dcctl upgrade --drop-legacy-ingressreads the certificate itself and refuses, naming the Secret, when it is self-signed or was not signed by its ownca.crt. What follows is the fallback: for an instance whose certificate was issued by an earlier release, and for when that refusal names the Secret. The rename also makes cert-manager issue every host certificate of the instance once more, from the same CA; each Secret keeps serving its current certificate until the new one is ready, so there is no outage.kubectl -n dci-<instance> get secret devicechain-tls-gateway -o jsonpath='{.data.tls\.crt}' \| base64 -d | openssl x509 -noout -subject -issuerThe subject must not be empty, and the issuer must be the instance CA,
CN=<instance> DeviceChain CA. If it is not, ask cert-manager to issue it again from the instance CA and check once more:cmctl renew -n dci-<instance> devicechain-tls-gateway# without cmctl: delete the Secret and cert-manager recreates itkubectl -n dci-<instance> delete secret devicechain-tls-gatewayAn instance whose certificate comes from an ACME or other
ClusterIssuerhas nothing to do here. -
Drop the Ingress objects. When traffic has moved, stop rendering them:
dcctl upgrade <provider> <instance> --drop-legacy-ingressDo this for every instance on the cluster.
-
Remove
ingress-nginx.dcctl install <provider> --remove-legacy-ingressPass the same settings as in step 1. Removing the controller does not remove Ingress objects, and one left behind would stop answering without any fault being reported, so the command refuses while any exists:
ingress-nginx is still serving 1 Ingress object(s), and removing the controller leaves them in place withnothing to serve them, so every host they carry stops answering:dci-beta/devicechain-apiMove each instance to the Gateway first: point DNS at the new controller, then run`dcctl upgrade <provider> <instance> --drop-legacy-ingress` for it. Nothing has been changedThe list names every Ingress served by the
ingress-nginxclass, in any namespace, including ones you made yourself. Move or delete those too. The Ingress the install made for Grafana does not count: it moves to Traefik in the same apply.
On a local cluster
A local (kind) cluster publishes a node's ports 80 and 443, and only one controller can hold them. While both controllers are installed, ingress-nginx holds the ports and Traefik is reachable only on a NodePort.
On a local cluster there is no DNS to move: the browser reaches whichever controller holds the node's ports. Steps 3 and 4 (the external address and DNS) do not apply, and a plain dcctl upgrade already moves an instance that has Ingress objects into the window. Run steps 6 and 7 back to back: the host does not answer from --drop-legacy-ingress until the removal has handed the ports to Traefik, and expect a short gap while that happens.
A fresh local cluster has no ingress-nginx, and Traefik holds the ports from the start. On a local cluster that has both, a new instance is served by ingress-nginx until the removal.
Several instances
The cluster step is shared and each instance moves on its own schedule. ingress-nginx stays until the last
instance has run --drop-legacy-ingress. A cluster whose instances use different hosts needs DNS moved for each.
The certificate gate
A Gateway with no certificate answers https with the controller's default certificate, so the move checks the Gateway's certificate at four points. The checks apply when the instance terminates TLS and the cluster has cert-manager; --no-tls skips them. A Gateway whose Secret you supply yourself (it carries no cert-manager issuer annotation and no kubernetes.io/tls-acme: "true") has nothing to wait for and passes.
- During the window,
dcctl upgradewaits 30 seconds for the certificate, then warns and continues. The Ingress objects still serve the host, and many issuers can only issue the Gateway's certificate once DNS points at Traefik. dcctl upgrade --drop-legacy-ingressrefuses, before it renders or changes anything, unless the Gateway's cert-manager Certificate isReadyand controlled by the Gateway. It also refuses when the instance has no Gateway yet. Move DNS first so the certificate can be issued, checkkubectl -n dci-<instance> get certificate, and run the command again.dcctl install --remove-legacy-ingresschecks the Gateway of every instance on the cluster, and names each one whose certificate is notReady. Removingingress-nginxdoes not change how those instances are served; the check keeps a broken Gateway certificate from going unnoticed until nothing else serves the host.- An upgrade that leaves an instance on the Gateway alone waits up to five minutes, and fails with the reason. The services are already up when it fails.
A refusal ends with "Nothing has been changed". Fix what it names and run the same command again. The common causes:
- A Certificate exists for the Secret, but the Gateway does not control it. cert-manager will not adopt a Certificate it did not create for the Gateway, so one left over from an Ingress, or made by hand, under the same Secret name leaves the Gateway with none. The message prints the command that deletes it (
kubectl -n dci-<instance> delete certificate <name>); the Gateway's own is then created. - No Certificate is created at all. cert-manager's Gateway API support is not on, or it started before the Gateway API definitions existed.
kubectl -n cert-manager logs deploy/cert-managersays. Adcctl installof this release turns it on. - The Certificate is not
Ready. The message carries the issuer's own text. With an ACME issuer, see below.
Certificates from an ACME issuer
An HTTP-01 issuer that solves through the nginx ingress class keeps working during the window, for the Gateway's certificate too: the challenge is served by ingress-nginx, where DNS still points. Leave it as it is until DNS has moved. Then, before --drop-legacy-ingress and --remove-legacy-ingress, give it a gatewayHTTPRoute solver, or its renewals fail once ingress-nginx is gone. A gatewayHTTPRoute solver set before DNS moves fails, because the challenge reaches ingress-nginx, which has no route for it. A DNS-01 solver works throughout.
The gatewayHTTPRoute solver names the http listener of the instance's Gateway, devicechain:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt
spec:
acme:
# server, email and privateKeySecretRef as before
solvers:
- http01:
gatewayHTTPRoute:
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: devicechain
sectionName: http
The parentRef has no namespace, and one covers every instance: cert-manager creates the solver's route in the namespace of the Certificate being issued, so it attaches to that instance's devicechain Gateway. The chart's redirect from http to https is a less specific match than the challenge path, so it does not intercept the challenge.
Rolling back
While ingress-nginx is still on the cluster, an instance can be put back on it:
dcctl upgrade <provider> <instance> --ingress-controller nginx
The choice is recorded on the instance and kept by every later upgrade, because an upgrade re-derives
everything it renders and a rollback that a plain upgrade undid would be no rollback. --ingress-controller gateway
records the other choice. --ingress-controller auto clears the record, and the instance is then decided from the
cluster again: the Gateway where Traefik runs.
nginxneedsingress-nginxon the cluster, andgatewayneeds Traefik. Each is refused, with nothing changed, when the controller it names is missing.--ingress-controller nginxand--drop-legacy-ingresstogether are refused: one makes the Ingress objects the only front end and the other stops rendering them.- If DNS already points at Traefik, point it back at
ingress-nginx's address: the rollback removes the Gateway. - Once
ingress-nginxhas been removed there is no rollback to it, and an install does not bring a removed controller back.
What changes at the edge
The Gateway serves the same hosts, paths and backends as the Ingress objects did. What differs is who configures the edge:
- Annotations no longer apply.
ingress.className,ingress.annotationsandingress.uploadMaxBodySizein a values overlay belong to the Ingress objects. On an instance served by the Gateway alone, a value you type for them in a run is refused, and one the instance already recorded is dropped from the render with a warning and kept in the record, so it applies again under--ingress-controller nginx. - Uploads are not capped or buffered. Traefik streams the request body, and the update service enforces its
own ceiling. Traefik's read timeout bounds the whole request, body included, so it is derived from the
largest artifact the cluster accepts: the OpenTofu variable
traefik_max_artifact_bytes, 512 MiB by default, gives a window of 2,228 seconds. Raise it before raising an instance'supdates.maxArtifactBytes. The same window is how long a client sending headers slowly can hold a connection, against 60 seconds before. - Download query strings stay out of the access log, which is off by default. The download capability travels
in
?cap=. - Paths are case-sensitive and end on a path boundary.
/API/<area>and/api/<area>Xfall through to the console instead of reaching the area. - Client addresses. Traefik writes the real peer into
X-Forwarded-Forand its Service usesexternalTrafficPolicy: Local; see Client addresses and trusted proxies. On AWS a Classic Load Balancer hides the client address.dcctl install --aws-nlb traefikgives Traefik's Service a Network Load Balancer, and it is the value to use during the window becauseingress-nginxkeeps serving on its own load balancer. Changing a Service replaces its load balancer and changes its hostname, so repoint DNS afterwards. - Rollouts drain. Behind a cloud load balancer (
externalTrafficPolicy: Local) the balancer keeps sending to a node until its health check fails it, so a controller pod that left at once dropped requests while the balancer caught up. Both controllers now keep serving for 40 seconds after they are told to stop, and a new pod must be ready for 15 seconds before an old one is removed, so a rollout or a pod moving node loses nothing. A rollout therefore takes about a minute per replica longer than before. The wait applies only behind a load balancer: on a local cluster, where a controller binds the node's ports or sits behind a NodePort, there is nothing to wait for and rollouts are as quick as before. The OpenTofu variablesdrain_secondsandmin_ready_secondsof each controller module adjust it;0turns it off.
Related
dcctl installanddcctl upgradelist every flag used here.- Releases & upgrades has the notes for the release that ships this change.