{"id":3529,"date":"2026-06-09T10:26:00","date_gmt":"2026-06-09T16:26:00","guid":{"rendered":"https:\/\/yyc.ninja\/index.php\/2026\/10\/20\/upgrading-rke2-rancher-cert-manager-with-no-spare-capacity\/"},"modified":"2026-10-20T13:00:21","modified_gmt":"2026-10-20T19:00:21","slug":"upgrading-rke2-rancher-cert-manager-with-no-spare-capacity","status":"publish","type":"post","link":"https:\/\/yyc.ninja\/index.php\/2026\/06\/09\/upgrading-rke2-rancher-cert-manager-with-no-spare-capacity\/","title":{"rendered":"Upgrading Kubernetes, Rancher and cert-manager with no spare capacity"},"content":{"rendered":"<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1200\" height=\"630\" src=\"https:\/\/yyc.ninja\/wp-content\/uploads\/2026\/10\/cg-upgrading-rke2-rancher-cert-manager-with-no-spare-capacity-cover.png\" alt=\"Upgrading Kubernetes, Rancher and cert-manager with no spare capacity\" class=\"wp-image-3528\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\" srcset=\"https:\/\/yyc.ninja\/wp-content\/uploads\/2026\/10\/cg-upgrading-rke2-rancher-cert-manager-with-no-spare-capacity-cover.png 1200w, https:\/\/yyc.ninja\/wp-content\/uploads\/2026\/10\/cg-upgrading-rke2-rancher-cert-manager-with-no-spare-capacity-cover-300x158.png 300w, https:\/\/yyc.ninja\/wp-content\/uploads\/2026\/10\/cg-upgrading-rke2-rancher-cert-manager-with-no-spare-capacity-cover-1024x538.png 1024w, https:\/\/yyc.ninja\/wp-content\/uploads\/2026\/10\/cg-upgrading-rke2-rancher-cert-manager-with-no-spare-capacity-cover-768x403.png 768w, https:\/\/yyc.ninja\/wp-content\/uploads\/2026\/10\/cg-upgrading-rke2-rancher-cert-manager-with-no-spare-capacity-cover-100x53.png 100w\" \/><\/figure>\n<p class=\"wp-block-paragraph\"><em>Part 4 of the data-e2e series. Issue numbers refer to the deployment log.<\/em><\/p>\n<p class=\"wp-block-paragraph\">The cluster came back from a two-month shutdown running <strong>Kubernetes 1.33<\/strong> (end of life), <strong>Rancher 2.13<\/strong> and\n<strong>cert-manager 1.14<\/strong>. Two days later it ran <strong>RKE2 1.36.5, Rancher 2.15.2 and cert-manager 1.21.2<\/strong>, with\ningress-nginx replaced by Gateway API on the way. This post covers how that went on a cluster with no spare node,\nno spare RAM and a single slow disk.<\/p>\n<h3 class=\"wp-block-heading\">The order was dictated by Rancher<\/h3>\n<p class=\"wp-block-paragraph\">Kubernetes minor versions must be upgraded one at a time (1.33 \u2192 1.34 \u2192 1.35 \u2192 1.36). Rancher only supports a\nwindow of Kubernetes versions, and <strong>Rancher 2.13 stops at 1.34<\/strong> (#8). The sequence was therefore:<\/p>\n<ol class=\"wp-block-list\">\n<li>RKE2 1.33 \u2192 1.34, the most Rancher 2.13 allows.<\/li>\n<li>Rancher 2.13 \u2192 2.14.3 \u2192 2.15.2, one minor version per hop.<\/li>\n<li>cert-manager 1.14 \u2192 1.21.2, the current release.<\/li>\n<li>Gateway API cut-over: Traefik in, ingress-nginx out. RKE2 1.37 removes ingress-nginx, so this had to happen\n before going any further.<\/li>\n<li>RKE2 1.34 \u2192 1.35 \u2192 1.36.<\/li>\n<\/ol>\n<p class=\"wp-block-paragraph\">An <strong>etcd snapshot before every step<\/strong>, named for the step (<code>pre-v1.36.5<\/code>, <code>pre-gateway-api<\/code>, <code>pre-rancher-2.15<\/code>).\nOn this cluster the rollback for a bad control-plane upgrade is &#8220;restore the snapshot&#8221;, so the snapshot is the\nupgrade&#8217;s undo button.<\/p>\n<h3 class=\"wp-block-heading\">Upgrading nodes you can&#8217;t drain<\/h3>\n<p class=\"wp-block-paragraph\">The textbook node upgrade is cordon \u2192 drain \u2192 upgrade \u2192 uncordon. Here that fails twice over:<\/p>\n<ul class=\"wp-block-list\">\n<li><strong>There&#8217;s no room.<\/strong> The workers sat at 91-99 % of memory requests, so a drained node&#8217;s pods have nowhere to go.<\/li>\n<li><strong>The pods can&#8217;t move anyway.<\/strong> local-path volumes are pinned to the node that holds the directory.<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\">The opposite approach (just restart the agent on the new version) had already gone wrong once (#38). The agent\ntook 9 minutes to come back on the busy disk. After 5 minutes NotReady, Kubernetes evicted every pod on the node.<\/p>\n<p class=\"wp-block-paragraph\">The script that worked (<code>rke2-upgrade-node.sh<\/code>, #92) is built around <strong>keeping the restart under the 5-minute\neviction window<\/strong>:<\/p>\n<ol class=\"wp-block-list\">\n<li>Take an etcd snapshot (server only).<\/li>\n<li><strong>Pre-pull the new release&#8217;s system images while the old version is still running.<\/strong> Image pulls were most of\n the downtime; doing them early takes them out of the critical path.<\/li>\n<li>Pin settings whose <em>defaults<\/em> change in the new version. RKE2 1.36 defaults new clusters to Traefik, so\n <code>ingress-controller<\/code> was written explicitly into the config before upgrading.<\/li>\n<li>Run the install script, <code>systemctl restart --no-block<\/code>, and wait for Ready.<\/li>\n<\/ol>\n<p class=\"wp-block-paragraph\">The result: nodes were back in <strong>357\/272\/182 s<\/strong> (1.35) and <strong>145\/123\/181 s<\/strong> (1.36). Every one was under 300 s, and\nno pod was evicted. Pre-pulling turned a 10-minute risk into a 2-3 minute blip.<\/p>\n<h3 class=\"wp-block-heading\">Rancher: upgrades that take an hour<\/h3>\n<p class=\"wp-block-paragraph\">Rancher was the slowest component by far, in ways that are worth knowing:<\/p>\n<ul class=\"wp-block-list\">\n<li><strong><code>--reuse-values<\/code> breaks across minor versions (#82).<\/strong> Rancher 2.14 added a value (<code>networkExposure<\/code>) that the\n old release&#8217;s stored values don&#8217;t have, and the template dereferenced it: <code>nil pointer evaluating\n .Values.networkExposure.type<\/code>. Use <code>--reset-then-reuse-values<\/code> (Helm \u2265 3.14). It applies the <em>new<\/em> chart&#8217;s\n defaults and then your overrides.<\/li>\n<li><strong>Startup probe vs. reality (#83).<\/strong> The new pod pulled a 913 MB image (11 min) and then restored its chart\n repositories for over 15 minutes. It was killed twice by its startup probe. The fix was a 60-minute\n <code>startupProbe<\/code>, and <code>helm upgrade<\/code> <em>without<\/em> <code>--wait<\/code>. The old pod keeps serving until the new one is Ready.<\/li>\n<li><strong>The &#8220;jail&#8221; timeout (#86).<\/strong> At start Rancher copies binaries into a jail with a 60 s budget. On the HDD: <code>[FATAL]\n timed out<\/code>. Any Rancher setting can be overridden with an env var, so <code>CATTLE_JAILER_TIMEOUT=900<\/code> fixed it.<\/li>\n<li><strong>System charts that never upgraded (#91).<\/strong> After the upgrade, Fleet, the webhook and Turtles were still on the old\n chart line. Rancher had tried to install them before its chart cache loaded and never retried. Patching the\n <code>rancher-charts<\/code> ClusterRepo with a new <code>forceUpdate<\/code> timestamp re-ran the installs. Since then, I run\n <code>helm list -A<\/code> after every Rancher hop.<\/li>\n<\/ul>\n<h3 class=\"wp-block-heading\">Gateway API cut-over without downtime<\/h3>\n<p class=\"wp-block-paragraph\">ingress-nginx and Traefik ran <strong>side by side<\/strong> during the switch: RKE2&#8217;s <code>ingress-controller: [ingress-nginx,\ntraefik]<\/code>, with Traefik on its own MetalLB IP and no hostPorts, so nothing clashed on 80\/443.<\/p>\n<p class=\"wp-block-paragraph\">Each app got an HTTPRoute and was tested on the new IP. DNS was flipped, and then ingress-nginx was removed by\ndropping it from the list. Rancher has a native Gateway mode (<code>networkExposure.type: gateway<\/code>), which replaced its\nIngress with its own Gateway and routes.<\/p>\n<p class=\"wp-block-paragraph\">One surprise: Traefik&#8217;s chart takes the Service type from <code>service.spec.type<\/code>, not <code>service.type<\/code> (#94). The Service\nstayed ClusterIP until that was fixed.<\/p>\n<h3 class=\"wp-block-heading\">A repeatable upgrade, in short<\/h3>\n<ol class=\"wp-block-list\">\n<li>Read the support matrix for <strong>every<\/strong> component that pins the Kubernetes version (Rancher, the CNI,\n cert-manager), and plan the hops.<\/li>\n<li><strong>Take an etcd snapshot per step<\/strong>, named after the step, and copy it off the host.<\/li>\n<li><strong>Pre-pull images<\/strong> before restarting anything. Measure node downtime and keep it under the eviction timeout.<\/li>\n<li>Pin settings whose defaults change: read the &#8220;changed defaults&#8221; section of each release note.<\/li>\n<li>Use <code>--reset-then-reuse-values<\/code>, never <code>--reuse-values<\/code>, across chart minor versions.<\/li>\n<li>For slow apps, upgrade without <code>--wait<\/code> and watch the new pod; size startup probes for the worst case.<\/li>\n<li>After the last hop, <strong>audit, not just verify<\/strong>: run the functional test suite (79 checks here) <em>and<\/em> look for\n inherited state, labels and credentials.<\/li>\n<li>Next time: system-upgrade-controller Plans in Git instead of a script.<\/li>\n<\/ol>\n<p class=\"wp-block-paragraph\"><em>Previous: Part 3 (GitOps adoption \u00b7 Next: Part 5). From VMs and Docker Compose to\nRKE2.<\/em><\/p>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Get the next one in your inbox<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Enjoyed this? I write about local AI, self-hosting, and cloud cost ninjutsu. Join the Ninja Dispatch for occasional, high-signal notes. No fluff.<\/p>\n\n\n\n\t\t\t<form id=\"sib_signup_form_2\" method=\"post\" class=\"sib_signup_form\">\n\t\t\t\t<div class=\"sib_loader\" style=\"display:none;\"><img\n\t\t\t\t\t\t\tsrc=\"https:\/\/yyc.ninja\/wp-includes\/images\/spinner.gif\" alt=\"loader\"><\/div>\n\t\t\t\t<input type=\"hidden\" name=\"sib_form_action\" value=\"subscribe_form_submit\">\n\t\t\t\t<input type=\"hidden\" name=\"sib_form_id\" value=\"2\">\n                <input type=\"hidden\" name=\"sib_form_alert_notice\" value=\"Please fill out all required fields.\">\n                <input type=\"hidden\" name=\"sib_form_invalid_email_notice\" value=\"Please enter a valid email address.\">\n                <input type=\"hidden\" name=\"sib_security\" value=\"e492cd529a\">\n\t\t\t\t<div class=\"sib_signup_box_inside_2\">\n\t\t\t\t\t<div style=\"\/*display:none*\/\" class=\"sib_msg_disp\">\n\t\t\t\t\t<\/div>\n                    \t\t\t\t\t<p class=\"sib-email-area\">\n    <input type=\"email\" class=\"sib-email-area\" name=\"email\" placeholder=\"you@example.com\" required=\"required\">\n<\/p>\n<p class=\"sib-FIRSTNAME-area\">\n    <input type=\"text\" class=\"sib-FIRSTNAME-area\" name=\"FIRSTNAME\" placeholder=\"First name (optional)\">\n<\/p>\n<input type=\"hidden\" class=\"sib-SOURCE_DOMAIN-area\" name=\"SOURCE_DOMAIN\" value=\"yyc.ninja\">\n<input type=\"hidden\" class=\"sib-SIGNUP_SOURCE-area\" name=\"SIGNUP_SOURCE\" value=\"post\">\n<p>\n    <input type=\"submit\" class=\"sib-default-btn\" value=\"Join the Ninja Dispatch\">\n<\/p>\n<p class=\"ninja-fineprint\">No spam. Unsubscribe anytime.<\/p>\t\t\t\t<\/div>\n\t\t\t<\/form>\n\t\t\t<style>\n\t\t\t\tform#sib_signup_form_2{background:#192a3d;border-radius:10px;padding:16px;font-family:inherit}form#sib_signup_form_2 input[type=text],form#sib_signup_form_2 input[type=email],form#sib_signup_form_2 select{width:100%;box-sizing:border-box;margin:0 0 10px 0;padding:10px 12px;border:1px solid #33465c;border-radius:8px;background:#0f1c29;color:#e8eef5;font-size:14px}form#sib_signup_form_2 input::placeholder{color:#8a9bb0}form#sib_signup_form_2 .sib-default-btn{width:100%;margin:2px 0 0 0;padding:11px 14px;background:#e63946;color:#ffffff;border:0;border-radius:8px;font-size:15px;font-weight:600;cursor:pointer}form#sib_signup_form_2 .sib-default-btn:hover{background:#d02f3c}form#sib_signup_form_2 p{margin:0 0 10px 0}form#sib_signup_form_2 .ninja-fineprint{margin:8px 0 0;font-size:11px;color:#8a9bb0;text-align:center}form#sib_signup_form_2 p.sib-alert-message {\n    padding: 6px 12px;\n    margin-bottom: 20px;\n    border: 1px solid transparent;\n    border-radius: 4px;\n    -webkit-box-sizing: border-box;\n    -moz-box-sizing: border-box;\n    box-sizing: border-box;\n}\nform#sib_signup_form_2 p.sib-alert-message-error {\n    background-color: #f2dede;\n    border-color: #ebccd1;\n    color: #a94442;\n}\nform#sib_signup_form_2 p.sib-alert-message-success {\n    background-color: #dff0d8;\n    border-color: #d6e9c6;\n    color: #3c763d;\n}\nform#sib_signup_form_2 p.sib-alert-message-warning {\n    background-color: #fcf8e3;\n    border-color: #faebcc;\n    color: #8a6d3b;\n}\n\t\t\t<\/style>\n\t\t\t\n","protected":false},"excerpt":{"rendered":"<p>The cluster came back from a two-month shutdown running Kubernetes 1.33 (end of life), Rancher 2.13 and<\/p>\n","protected":false},"author":1,"featured_media":3528,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12],"tags":[44,46,45],"class_list":["post-3529","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-how-to","tag-data-engineering","tag-mlops","tag-streaming"],"featured_image_urls_v2":{"full":["https:\/\/yyc.ninja\/wp-content\/uploads\/2026\/10\/cg-upgrading-rke2-rancher-cert-manager-with-no-spare-capacity-cover.png",1200,630,false],"thumbnail":["https:\/\/yyc.ninja\/wp-content\/uploads\/2026\/10\/cg-upgrading-rke2-rancher-cert-manager-with-no-spare-capacity-cover-150x150.png",150,150,true],"medium":["https:\/\/yyc.ninja\/wp-content\/uploads\/2026\/10\/cg-upgrading-rke2-rancher-cert-manager-with-no-spare-capacity-cover-300x158.png",300,158,true],"medium_large":["https:\/\/yyc.ninja\/wp-content\/uploads\/2026\/10\/cg-upgrading-rke2-rancher-cert-manager-with-no-spare-capacity-cover-768x403.png",768,403,true],"large":["https:\/\/yyc.ninja\/wp-content\/uploads\/2026\/10\/cg-upgrading-rke2-rancher-cert-manager-with-no-spare-capacity-cover-1024x538.png",1024,538,true],"RoboGalleryMansoryImagesCenter":["https:\/\/yyc.ninja\/wp-content\/uploads\/2026\/10\/cg-upgrading-rke2-rancher-cert-manager-with-no-spare-capacity-cover-600x630.png",600,630,true],"RoboGalleryPreload":["https:\/\/yyc.ninja\/wp-content\/uploads\/2026\/10\/cg-upgrading-rke2-rancher-cert-manager-with-no-spare-capacity-cover-100x53.png",100,53,true],"1536x1536":["https:\/\/yyc.ninja\/wp-content\/uploads\/2026\/10\/cg-upgrading-rke2-rancher-cert-manager-with-no-spare-capacity-cover.png",1200,630,false],"2048x2048":["https:\/\/yyc.ninja\/wp-content\/uploads\/2026\/10\/cg-upgrading-rke2-rancher-cert-manager-with-no-spare-capacity-cover.png",1200,630,false]},"post_excerpt_stackable_v2":"<p>The cluster came back from a two-month shutdown running Kubernetes 1.33 (end of life), Rancher 2.13 and<\/p>\n","category_list_v2":"<a href=\"https:\/\/yyc.ninja\/index.php\/category\/how-to\/\" rel=\"category tag\">How To Ninja<\/a>","author_info_v2":{"name":"YYC Ninja","url":"https:\/\/yyc.ninja\/index.php\/author\/admin\/"},"comments_num_v2":"0 comments","_links":{"self":[{"href":"https:\/\/yyc.ninja\/index.php\/wp-json\/wp\/v2\/posts\/3529","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/yyc.ninja\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/yyc.ninja\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/yyc.ninja\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/yyc.ninja\/index.php\/wp-json\/wp\/v2\/comments?post=3529"}],"version-history":[{"count":1,"href":"https:\/\/yyc.ninja\/index.php\/wp-json\/wp\/v2\/posts\/3529\/revisions"}],"predecessor-version":[{"id":3539,"href":"https:\/\/yyc.ninja\/index.php\/wp-json\/wp\/v2\/posts\/3529\/revisions\/3539"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/yyc.ninja\/index.php\/wp-json\/wp\/v2\/media\/3528"}],"wp:attachment":[{"href":"https:\/\/yyc.ninja\/index.php\/wp-json\/wp\/v2\/media?parent=3529"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/yyc.ninja\/index.php\/wp-json\/wp\/v2\/categories?post=3529"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/yyc.ninja\/index.php\/wp-json\/wp\/v2\/tags?post=3529"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}