<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Algo7</title>
    <description>The latest articles on DEV Community by Algo7 (@algo7).</description>
    <link>https://dev.to/algo7</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1249350%2Fc16f62e3-1ec1-4e17-a96c-d78dbfd1df68.png</url>
      <title>DEV Community: Algo7</title>
      <link>https://dev.to/algo7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/algo7"/>
    <language>en</language>
    <item>
      <title>Podman Quadlet Deep Dive: How a .container Becomes a systemd Service</title>
      <dc:creator>Algo7</dc:creator>
      <pubDate>Tue, 23 Jun 2026 00:48:23 +0000</pubDate>
      <link>https://dev.to/algo7/podman-quadlet-deep-dive-how-a-container-becomes-a-systemd-service-1c27</link>
      <guid>https://dev.to/algo7/podman-quadlet-deep-dive-how-a-container-becomes-a-systemd-service-1c27</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you have a simple container service that doesn't justify an orchestrator (k8s / Docker Swarm), but &lt;code&gt;docker compose up -d&lt;/code&gt; feels too non-production, &lt;a href="https://docs.podman.io/en/latest/markdown/podman-quadlet.1.html#description" rel="noopener noreferrer"&gt;Podman Quadlet&lt;/a&gt; is worth a check. Quadlet lets you manage your container resources as &lt;a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html" rel="noopener noreferrer"&gt;systemd unit files&lt;/a&gt;; you write a declarative &lt;code&gt;.container&lt;/code&gt; file, and systemd runs it like any other service.&lt;/p&gt;

&lt;p&gt;That's probably all you will find if you don't take a deep dive into the official docs. This post investigates what &lt;em&gt;actually&lt;/em&gt; happens when you drop that file in place. We'll trace how Podman turns a &lt;code&gt;.container&lt;/code&gt; into a real systemd &lt;code&gt;.service&lt;/code&gt;, where that generated unit lives, why it's transient, and why those mechanics dictate how you start it and keep it running.&lt;/p&gt;






&lt;ul&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Today's Scope&lt;/li&gt;
&lt;li&gt;
How It Works

&lt;ul&gt;
&lt;li&gt;
Quadlet Files to systemd Unit Files

&lt;ul&gt;
&lt;li&gt;User Manager (rootless)&lt;/li&gt;
&lt;li&gt;System Manager (rootful)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Creating Our Quadlet File&lt;/li&gt;
&lt;li&gt;Running the Service&lt;/li&gt;
&lt;li&gt;The Big Gotcha&lt;/li&gt;
&lt;/ul&gt;






&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Linux-based VM (this post uses Ubuntu)&lt;/li&gt;
&lt;li&gt;Podman &amp;gt;=v4.4.0 installed&lt;/li&gt;
&lt;li&gt;A container image to run&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Today's Scope
&lt;/h2&gt;

&lt;p&gt;There are many types of Quadlet files, but today we are only covering the rootless/user &lt;code&gt;.container&lt;/code&gt; type.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs0aeki76smxlu79htlf2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs0aeki76smxlu79htlf2.png" alt="Type of Quadlet Files" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above image is taken from the &lt;a href="https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html#synopsis" rel="noopener noreferrer"&gt;official doc&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;.container&lt;/code&gt; file looks like a systemd unit, but, well, it's not. The gap between "the file you write" and "the unit systemd runs" is our focus today, so let's walk it before reaching for copy-and-paste.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quadlet Files to systemd Unit Files
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html" rel="noopener noreferrer"&gt;Quadlet files&lt;/a&gt; use the same format as &lt;a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html" rel="noopener noreferrer"&gt;systemd unit files&lt;/a&gt; but they aren't valid units on their own. Keys such as &lt;code&gt;Image=&lt;/code&gt; and &lt;code&gt;PublishPort=&lt;/code&gt; are Podman specific. Podman uses a &lt;a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.generator.html" rel="noopener noreferrer"&gt;systemd unit generator&lt;/a&gt; to convert the Quadlet files into standard systemd &lt;code&gt;.service&lt;/code&gt; files.&lt;/p&gt;

&lt;h4&gt;
  
  
  User Manager (rootless)
&lt;/h4&gt;

&lt;p&gt;When you use &lt;code&gt;systemctl --user&lt;/code&gt;, the per-user manager runs &lt;code&gt;/usr/lib/systemd/user-generators/podman-user-generator&lt;/code&gt;, which converts your Quadlet files into &lt;code&gt;.service&lt;/code&gt; units and drops them in &lt;code&gt;$XDG_RUNTIME_DIR/systemd/generator/&lt;/code&gt;. Because this manager runs as your UID, the containers it launches are rootless.&lt;/p&gt;

&lt;h4&gt;
  
  
  System Manager (rootful)
&lt;/h4&gt;

&lt;p&gt;When you use &lt;code&gt;systemctl&lt;/code&gt; (no &lt;code&gt;--user&lt;/code&gt;), the system manager (PID 1) runs &lt;code&gt;/usr/lib/systemd/system-generators/podman-system-generator&lt;/code&gt;, with output in &lt;code&gt;/run/systemd/generator/&lt;/code&gt;. Because this manager runs as root, the containers it launches are rootful.&lt;/p&gt;

&lt;p&gt;Note: Both generators are symlinks to &lt;code&gt;/usr/libexec/podman/quadlet&lt;/code&gt; (path may vary by distro)&lt;/p&gt;

&lt;p&gt;Read more about &lt;a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.generator.html#Output%20directories" rel="noopener noreferrer"&gt;systemd generator output directories&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Creating Our Quadlet File
&lt;/h2&gt;

&lt;p&gt;With the mechanics out of the way, let's create a &lt;code&gt;.container&lt;/code&gt; Quadlet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Container]&lt;/span&gt;

&lt;span class="c"&gt;# the container name shown when running podman ps
# Ref: https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html#containername
&lt;/span&gt;&lt;span class="py"&gt;ContainerName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;my-container&lt;/span&gt;

&lt;span class="c"&gt;# the file that contains all the needed env vars
# Ref: https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html#environmentfile
&lt;/span&gt;&lt;span class="py"&gt;EnvironmentFile&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;my-env-file&lt;/span&gt;

&lt;span class="c"&gt;# The image to run
# Ref: https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html#image
&lt;/span&gt;&lt;span class="py"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;my-image:latest&lt;/span&gt;

&lt;span class="c"&gt;# port mapping (hostPort:containerPort)
# note that rootless units can't bind to privilege ports
# Ref: https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html#id30
&lt;/span&gt;&lt;span class="py"&gt;PublishPort&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;3000:3000/tcp&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="c"&gt;# restart policy
&lt;/span&gt;&lt;span class="py"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="c"&gt;# starts on boot
&lt;/span&gt;&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;default.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the complete list of keys for &lt;code&gt;.container&lt;/code&gt; unit files please refer to the &lt;a href="https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html#container-units-container" rel="noopener noreferrer"&gt;official doc&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For rootless/user units, the podman systemd generator reads from &lt;code&gt;$XDG_CONFIG_HOME/containers/systemd/&lt;/code&gt; or &lt;code&gt;~/.config/containers/systemd/&lt;/code&gt; if &lt;code&gt;$XDG_CONFIG_HOME&lt;/code&gt; is unset. We can save the above file as &lt;code&gt;~/.config/containers/systemd/my-service.container&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Please refer to the &lt;a href="https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html#podman-rootless-unit-search-path" rel="noopener noreferrer"&gt;official doc&lt;/a&gt; for the comprehensive list of rootless unit search paths.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running the Service
&lt;/h2&gt;

&lt;p&gt;Since it's a new unit, we need to tell our user systemd manager to reload. For a Quadlet unit, &lt;code&gt;daemon-reload&lt;/code&gt; re-runs the generators (regenerating the &lt;code&gt;.service&lt;/code&gt; from our &lt;code&gt;.container&lt;/code&gt;) and reloads units from disk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; daemon-reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--user&lt;/code&gt; flag points &lt;code&gt;systemctl&lt;/code&gt; at the per-user systemd manager which is running as your UID, not the system manager (PID 1). Note this picks which manager runs the unit; it's a separate thing from whether the container itself is rootless.&lt;/p&gt;

&lt;p&gt;Start the service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; start my-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why systemctl &lt;code&gt;start&lt;/code&gt; and not &lt;code&gt;enable&lt;/code&gt; like normal systemd units? The systemd units Quadlet generates are transient; they live in &lt;code&gt;$XDG_RUNTIME_DIR/systemd/generator/&lt;/code&gt; (user) or &lt;code&gt;/run/systemd/generator&lt;/code&gt; (system), and both are tmpfs. The generator regenerates them every time it runs (at boot and each &lt;code&gt;daemon-reload&lt;/code&gt;). systemd won't let you &lt;code&gt;systemctl enable&lt;/code&gt; a transient/generated unit, so you can't enable it the usual way. Instead, the generator reads the &lt;code&gt;[Install]&lt;/code&gt; section from your &lt;code&gt;.container&lt;/code&gt; and creates a symlink in &lt;code&gt;$XDG_RUNTIME_DIR/systemd/generator/default.target.wants/&lt;/code&gt; (user) or &lt;code&gt;/run/systemd/generator/default.target.wants/&lt;/code&gt; (system) as part of that same generation pass. That's why &lt;code&gt;WantedBy=default.target&lt;/code&gt; is what makes it start on boot, not &lt;code&gt;enable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you try &lt;code&gt;systemctl --user enable my-service&lt;/code&gt; you will get&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Failed to &lt;span class="nb"&gt;enable &lt;/span&gt;unit: Unit /run/user/&amp;lt;uid&amp;gt;/systemd/generator/my-service.service is transient or generated.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;systemd captures the service's stdout/stderr to the journal (via journald). To follow the logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;journalctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; my-service &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can open &lt;code&gt;$XDG_RUNTIME_DIR/systemd/generator/my-service.service&lt;/code&gt; to check the generated systemd unit file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Automatically generated by /usr/lib/systemd/user-generators/podman-user-generator&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# my-service.container&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;X-Container]
&lt;span class="nv"&gt;ContainerName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-container
&lt;span class="nv"&gt;EnvironmentFile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-env-file
&lt;span class="nv"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-image:latest
&lt;span class="nv"&gt;PublishPort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3000:3000/tcp

&lt;span class="o"&gt;[&lt;/span&gt;Service]
&lt;span class="nv"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;always
&lt;span class="nv"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;PODMAN_SYSTEMD_UNIT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;%n
&lt;span class="nv"&gt;KillMode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mixed
&lt;span class="nv"&gt;ExecStop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/podman &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;--cidfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;%t/%N.cid
&lt;span class="nv"&gt;ExecStopPost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-/usr/bin/podman &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;--cidfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;%t/%N.cid
&lt;span class="nv"&gt;Delegate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes
&lt;/span&gt;&lt;span class="nv"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;notify
&lt;span class="nv"&gt;NotifyAccess&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;all
&lt;span class="nv"&gt;SyslogIdentifier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;%N
&lt;span class="nv"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/podman run &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-container &lt;span class="nt"&gt;--cidfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;%t/%N.cid &lt;span class="nt"&gt;--replace&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--cgroups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;split&lt;/span&gt; &lt;span class="nt"&gt;--sdnotify&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;conmon &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--publish&lt;/span&gt; 3000:3000/tcp &lt;span class="nt"&gt;--env-file&lt;/span&gt; /home/ubuntu/.config/containers/systemd/my-env-file my-image:latest

&lt;span class="o"&gt;[&lt;/span&gt;Install]
&lt;span class="nv"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default.target

&lt;span class="o"&gt;[&lt;/span&gt;Unit]
&lt;span class="nv"&gt;SourcePath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/ubuntu/.config/containers/systemd/my-service.container
&lt;span class="nv"&gt;RequiresMountsFor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;%t/containers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that an &lt;code&gt;X-&lt;/code&gt; is prefixed to the Podman-specific &lt;code&gt;[Container]&lt;/code&gt; section. That's systemd's way of saying ignore it. &lt;/p&gt;




&lt;h2&gt;
  
  
  The Big Gotcha
&lt;/h2&gt;

&lt;p&gt;Here's the part that bites people on a remote VM: your &lt;code&gt;systemd --user&lt;/code&gt; manager only exists while you have an active session. It starts on your first login and stops when your &lt;strong&gt;last&lt;/strong&gt; session ends. When it does, your user services go with it. So if you start the service over SSH and then close the SSH session, the service dies with it.&lt;br&gt;
To keep the user manager alive at boot with no session attached, you must enable &lt;strong&gt;user lingering&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# enable lingering for the current user&lt;/span&gt;
loginctl enable-linger

&lt;span class="c"&gt;# verify&lt;/span&gt;
loginctl show-user &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Linger
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lingering is recorded under &lt;code&gt;/var/lib/systemd/linger/&lt;/code&gt; and survives reboots. Note that lingering doesn't make you "logged in"; it only controls whether your per-user &lt;code&gt;systemd --user&lt;/code&gt; manager runs without an active session.  For the service to actually come up on boot you still need the &lt;code&gt;[Install] WantedBy=default.target&lt;/code&gt; section in your Quadlet file as noted in Running the Service and the &lt;a href="https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html#enabling-unit-files" rel="noopener noreferrer"&gt;official doc&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For details, see the &lt;a href="https://www.freedesktop.org/software/systemd/man/latest/loginctl.html#enable-linger%20USER%E2%80%A6" rel="noopener noreferrer"&gt;loginctl docs&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>containers</category>
      <category>podman</category>
      <category>docker</category>
    </item>
    <item>
      <title>Rename a Kubernetes PVC Without Losing Your Data: PersistentVolume Rebinding</title>
      <dc:creator>Algo7</dc:creator>
      <pubDate>Thu, 28 May 2026 16:04:29 +0000</pubDate>
      <link>https://dev.to/algo7/rename-a-pvc-without-losing-your-data-persistentvolume-rebinding-24a7</link>
      <guid>https://dev.to/algo7/rename-a-pvc-without-losing-your-data-persistentvolume-rebinding-24a7</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As an SRE working with Kubernetes, you'll occasionally need to rename a PersistentVolumeClaim (PVC) without losing the PersistentVolume (PV) and the data behind it. It comes up when you rename a StatefulSet, or just rename a Helm release for cosmetic reasons, like changing &lt;code&gt;myservice&lt;/code&gt; to &lt;code&gt;my-service&lt;/code&gt;. Because the PVC name is derived from that name in both cases, a simple redeploy provisions a new PVC, and with it a new, empty PV. If your old PV's &lt;code&gt;persistentVolumeReclaimPolicy&lt;/code&gt; is &lt;code&gt;Retain&lt;/code&gt;, your data stays unreferenced on the old PV (if it's &lt;code&gt;Delete&lt;/code&gt;, the volume is gone the moment you delete the PVC).&lt;/p&gt;

&lt;p&gt;To keep the data, your first instinct might be a migration: taking a VolumeSnapshot, running &lt;code&gt;rsync&lt;/code&gt; between two mounts, or copying the data out to your local machine and back into the PV. But, in this very specific case, there's an easier way: a PV rebind. Instead of moving any data, you keep the PV as is and just re-point it at the new PVC.&lt;/p&gt;

&lt;p&gt;Worth getting the vocabulary straight: this is a rebind, not a migration. Not a single byte moves. For block storage the volume ID stays the same; for NFS the same access point remains.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Important Notes&lt;/li&gt;
&lt;li&gt;Step 1: Set the persistentVolumeReclaimPolicy to Retain&lt;/li&gt;
&lt;li&gt;Step 2: Scale Down the StatefulSet / Deployment&lt;/li&gt;
&lt;li&gt;Step 3: Delete the PVC&lt;/li&gt;
&lt;li&gt;Step 4: Modify the Existing PV&lt;/li&gt;
&lt;li&gt;Step 5: Apply the New PVC&lt;/li&gt;
&lt;li&gt;Note on block-storage based CSIs on Managed Kubernetes&lt;/li&gt;
&lt;li&gt;Automated Tooling&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Important Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;For &lt;a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/#dynamic" rel="noopener noreferrer"&gt;dynamic PV provisioning&lt;/a&gt;: don't create the new PVC (or a StatefulSet with &lt;code&gt;volumeClaimTemplates&lt;/code&gt;) until &lt;em&gt;after&lt;/em&gt; you've re-pointed the existing PV in Step 4, or the provisioner races in and hands you a new, empty PV. If you already created the news resources, delete them along with the associated PVs and PVCs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For &lt;a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/#static" rel="noopener noreferrer"&gt;static PV provisioning&lt;/a&gt;: well, just don't create a new PV. The whole point is to reuse the existing one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you're on ArgoCD, turn off &lt;a href="https://argo-cd.readthedocs.io/en/latest/user-guide/auto_sync/" rel="noopener noreferrer"&gt;auto-sync&lt;/a&gt; or it will keep reverting the changes you're making.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Set the persistentVolumeReclaimPolicy to Retain
&lt;/h2&gt;

&lt;p&gt;A PV provisioned by a StorageClass (SC) inherits that SC's &lt;code&gt;reclaimPolicy&lt;/code&gt; at creation time. So if the SC's &lt;code&gt;reclaimPolicy&lt;/code&gt; is &lt;code&gt;Delete&lt;/code&gt;, the PV's &lt;code&gt;persistentVolumeReclaimPolicy&lt;/code&gt; will be &lt;code&gt;Delete&lt;/code&gt; too, and the moment you delete the PVC the underlying volume and your data are gone.&lt;/p&gt;

&lt;p&gt;An SC's &lt;code&gt;reclaimPolicy&lt;/code&gt; is immutable: even if you delete and recreate the SC, the change only applies to PVs provisioned afterward. The PV's own &lt;code&gt;persistentVolumeReclaimPolicy&lt;/code&gt;, however, is mutable, which is exactly what we are going to change.&lt;/p&gt;

&lt;p&gt;First, check the reclaim policy of your existing PV:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pv &amp;lt;your-pv-name&amp;gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.spec.persistentVolumeReclaimPolicy}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it prints &lt;code&gt;Retain&lt;/code&gt;, you're safe to move on. If it prints &lt;code&gt;Delete&lt;/code&gt;, change it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl patch pv &amp;lt;your-pv-name&amp;gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s1"&gt;'{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'&lt;/span&gt;

&lt;span class="c"&gt;# OR edit the same field interactively:&lt;/span&gt;
kubectl edit pv &amp;lt;your-pv-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: Scale Down the StatefulSet / Deployment
&lt;/h2&gt;

&lt;p&gt;You can't delete a PVC while a pod is still using it: the &lt;code&gt;pvc-protection&lt;/code&gt; finalizer blocks it. Scale the workload down first.&lt;/p&gt;

&lt;p&gt;For Deployments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl scale deployment &amp;lt;deployment-name&amp;gt; &lt;span class="nt"&gt;--replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;your-namespace&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For StatefulSets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl scale statefulset &amp;lt;statefulset-name&amp;gt; &lt;span class="nt"&gt;--replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;your-namespace&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3: Delete the PVC
&lt;/h2&gt;

&lt;p&gt;Delete your existing PVC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete pvc &amp;lt;your-pvc-name&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;your-namespace&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;Retain&lt;/code&gt; set in Step 1, the PV state changes to &lt;code&gt;Released&lt;/code&gt;, keeping your data instead of reclaiming it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Modify the Existing PV
&lt;/h2&gt;

&lt;p&gt;Delete two fields under &lt;code&gt;spec.claimRef&lt;/code&gt; on the existing PV:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;resourceVersion&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;uid&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl patch pv &amp;lt;your-pv-name&amp;gt; &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'merge'&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s1"&gt;'{"spec":{"claimRef":{"resourceVersion":null,"uid":null}}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;uid&lt;/code&gt; binds the PV to the old PVC's specific instance; clearing it and the &lt;code&gt;resourceVersion&lt;/code&gt; flips the PV from &lt;code&gt;Released&lt;/code&gt; to &lt;code&gt;Available&lt;/code&gt; and thus free to bind a new claim. You then point &lt;code&gt;claimRef&lt;/code&gt; at the new PVC by changing the &lt;code&gt;name&lt;/code&gt; (and &lt;code&gt;namespace&lt;/code&gt;, if it moved):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl patch pv &amp;lt;your-pv-name&amp;gt; &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'merge'&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s1"&gt;'{"spec":{"claimRef":{"name":"new-claim-name","namespace":"new-namespace-name"}}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also do this interactively with &lt;code&gt;kubectl edit pv &amp;lt;your-pv-name&amp;gt;&lt;/code&gt;, but becareful only touch the fields &lt;em&gt;under&lt;/em&gt; &lt;code&gt;claimRef&lt;/code&gt;. If you delete the PV's top-level &lt;code&gt;metadata.uid&lt;/code&gt;, which is what I accidentally did, the &lt;code&gt;kubectl edit&lt;/code&gt; will throw &lt;code&gt;error: no original object found&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Apply the New PVC
&lt;/h2&gt;

&lt;p&gt;Now create the new PVC using &lt;code&gt;kubectl&lt;/code&gt;, Helm, or whatever you use. Give it the same &lt;code&gt;storageClassName&lt;/code&gt; and &lt;code&gt;accessModes&lt;/code&gt; as the PV, a size no larger than the PV's capacity, and leave &lt;code&gt;volumeName&lt;/code&gt; unset. Because the PV is already reserved for this exact name, the binder grabs it instead of provisioning a fresh one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note on Personal Experience:&lt;/strong&gt; if your new PVC requests a larger size, the rebinding won't work and a new PV will provisioned by your SC. Smaller size will work, but you still get whatever the old PV size is which is misleading; so keep the manifest exactly the same as before.&lt;/p&gt;




&lt;h2&gt;
  
  
  Note on block-storage based CSIs on Managed Kubernetes
&lt;/h2&gt;

&lt;p&gt;Block storage on managed Kubernetes, such as EKS with the EBS CSI driver, is usually AZ-bound. If the PV lives in &lt;code&gt;availability-zone-a&lt;/code&gt;, you can't attach it to a pod scheduled in &lt;code&gt;availability-zone-b&lt;/code&gt;. There you'd have to take the full migration path, which we won't cover today.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automated Tooling
&lt;/h2&gt;

&lt;p&gt;This process can also be automated using tools such as &lt;a href="https://github.com/stackitcloud/rename-pvc" rel="noopener noreferrer"&gt;rename-pvc&lt;/a&gt; from &lt;a href="https://github.com/stackitcloud" rel="noopener noreferrer"&gt;STACKIT&lt;/a&gt;. It follows the same PV rebind procedure.&lt;/p&gt;

&lt;p&gt;However, you still need to remember to scale down the workload and pause any GitOps reconciliation (e.g. ArgoCD auto-sync) during the operation.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>k8s</category>
      <category>sre</category>
    </item>
    <item>
      <title>K3s Update TLS SANs</title>
      <dc:creator>Algo7</dc:creator>
      <pubDate>Sat, 09 Mar 2024 21:02:44 +0000</pubDate>
      <link>https://dev.to/algo7/k3s-update-tls-sans-2kg3</link>
      <guid>https://dev.to/algo7/k3s-update-tls-sans-2kg3</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When I was following the &lt;a href="https://docs.k3s.io/datastore/cluster-loadbalancer#setup-load-balancer"&gt;official guide of adding an external cluster load balancer&lt;/a&gt; for K3s using HAProxy to have some HAs when connecting to the cluster. I ran into a problem: &lt;code&gt;kubectl&lt;/code&gt; refuses to connect to the cluster due to some certificate issues:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;couldn't get current server API group list: Get "https://balancer_ip:6443/api?timeout=32s": tls: failed to verify certificate: x509: certificate is valid for [list of control plan ips and cluster + Kube API server service ip], ::1, not load_balancer_ip&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is due to my load balancer's IP not being in the cluster's API server certificate. During cluster initialization and when adding new control plane node to the cluster, K3s automatically update this certificate to include the new control plane node. However, this is not the case when adding an external cluster load balancer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;



&lt;ul&gt;
&lt;li&gt;Solution 1: Reinitialize the Cluster With --tls-san&lt;/li&gt;
&lt;li&gt;
Solution 2: Editing K3s Configuration File

&lt;ul&gt;
&lt;li&gt;Step 1: Edit the Config File&lt;/li&gt;
&lt;li&gt;Step 2: Delete the Existing Kube API Server Cert&lt;/li&gt;
&lt;li&gt;Step 3: Restart K3s&lt;/li&gt;
&lt;li&gt;
Step 4: Inform the DynamicListener About the Change

&lt;ul&gt;
&lt;li&gt;Alternative Method&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Worker / Control Plane Node Reconfiguration (Optional)&lt;/li&gt;
&lt;/ul&gt;






&lt;h2&gt;
  
  
  Solution 1: Reinitialize the Cluster With --tls-san
&lt;/h2&gt;

&lt;p&gt;According to the &lt;a href="https://docs.k3s.io/cli/server#listeners"&gt;K3s official documentation&lt;/a&gt;, you can pass the &lt;code&gt;--tls-san&lt;/code&gt; flag when initializing the cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sh k3s_install.sh --tls-san additional_ip_1 --tls-san additional_ip_1 --cluster-init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, my cluster is already running and I don't want to reinstall it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Solution 2: Editing K3s Configuration File
&lt;/h2&gt;

&lt;p&gt;The K3s configuration file is located under &lt;code&gt;/etc/rancher/k3s/config.yaml&lt;/code&gt; on all control plane nodes. If the file doesn't exist, you have to create it manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Edit the Config File
&lt;/h3&gt;

&lt;p&gt;In the config file, you can simply add the following entries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;tls-san&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ip_1&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;hostname_1&lt;/span&gt;
  &lt;span class="s"&gt;// additional entries&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can be done on any control plane node. &lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Delete the Existing Kube API Server Cert
&lt;/h3&gt;

&lt;p&gt;On any control plane node run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system delete secrets/k3s-serving
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to delete the existing Kubernetes API server certificate.&lt;/p&gt;

&lt;p&gt;This can be done anywhere as long as you can access the cluster's API server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Restart K3s
&lt;/h3&gt;

&lt;p&gt;Restart K3s on the same control plane node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart k3s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Inform the DynamicListener About the Change
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/rancher/dynamiclistener"&gt;DynamicListener&lt;/a&gt; is a component of K3s that handles automatic updates/renewal of the API server certificate, including when new control plan nodes join the cluster. &lt;/p&gt;

&lt;p&gt;According to one of the K3s contributor, &lt;a href="https://github.com/brandond"&gt;Brad Davidson&lt;/a&gt; in one of the &lt;a href="https://github.com/k3s-io/k3s/issues/7312#issuecomment-1652536728"&gt;GitHub issue&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dynamiclistener adds SANs for any hostname or IP address requested via a HTTP Host header or TLS SNI handshake. It is designed to allow you to add or remove servers without having to manually regenerate the certificate. The downside (as you noted) is that this allows for SAN stuffing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The documentation about it is not very clear and the official README is still in progress. This is as far as I can go about it.&lt;/p&gt;

&lt;p&gt;For each new hostname / IP you added to the config file, run the following command on the control plan node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="nt"&gt;--resolve&lt;/span&gt; your_new_hostname_or_ip:6443:127.0.0.1  https://your_new_hostname_or_ip:6443/ping
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method is also mentioned in another GitHub issue comment &lt;a href="https://github.com/k3s-io/k3s/issues/3369#issuecomment-849005179"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To confirm that the API server certificate has been updated, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get secret/k3s-serving &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system &lt;span class="nt"&gt;-o&lt;/span&gt; yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can be done anywhere as long as you have access to the cluster's API server.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;metadata.annotations&lt;/code&gt; part of the output, you should see your newly added hostname/ip as one of the annotations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;listener.cattle.io/cn-ur_hostnameor__ip: ur_hostname_or_ip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Alternative Method
&lt;/h4&gt;

&lt;p&gt;I found one Medium post &lt;a href="https://taozhi.medium.com/k3s-apiserver-unable-to-connect-to-the-server-x509-certificate-is-valid-for-10-43-0-1-8ec1f8c2097f"&gt;here&lt;/a&gt; that uses another method to update the DynamicListener.&lt;/p&gt;

&lt;p&gt;After you delete the existing API server certificate on a control plane node using&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system delete secrets/k3s-serving
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can move or delete a file called &lt;code&gt;dynamic-cert.json&lt;/code&gt; located under &lt;code&gt;/var/lib/rancher/k3s/server/tls&lt;/code&gt; on the same control plane node&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mv&lt;/span&gt; /var/lib/rancher/k3s/server/tls/dynamic-cert.json /tmp/dynamic-cert.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;BEFORE&lt;/strong&gt; restarting K3s on the same control plane node.&lt;/p&gt;

&lt;p&gt;I have tested both methods and they all worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worker / Control Plane Node Reconfiguration (Optional)
&lt;/h2&gt;

&lt;p&gt;Now we have to update our worker / control plane nodes to use the new endpoint. &lt;/p&gt;

&lt;p&gt;The fastest way is to update the K3s systemd service file.&lt;/p&gt;

&lt;p&gt;You will have to do this on each node that you want to reconfigure.&lt;/p&gt;

&lt;p&gt;For control plane nodes the file is located at&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/etc/systemd/system/k3s.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For worker nodes the file is located at&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/systemd/system/k3s-agent.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the value of the &lt;code&gt;--server&lt;/code&gt; flag in the file to use the new hostname / IP of the balancer.&lt;/p&gt;

&lt;p&gt;After making the changes, run &lt;code&gt;sudo systemctl daemon-reload&lt;/code&gt; to reload the changes.&lt;/p&gt;

&lt;p&gt;Finally run &lt;code&gt;sudo systemctl restart k3s&lt;/code&gt; to restart the K3s daemon for the changes to take effect.&lt;/p&gt;

</description>
      <category>k3s</category>
      <category>kubernetes</category>
      <category>homelab</category>
      <category>tls</category>
    </item>
    <item>
      <title>Install K3s on Proxmox Using Ansible</title>
      <dc:creator>Algo7</dc:creator>
      <pubDate>Tue, 30 Jan 2024 12:50:23 +0000</pubDate>
      <link>https://dev.to/algo7/install-k3s-on-proxmox-using-ansible-20j1</link>
      <guid>https://dev.to/algo7/install-k3s-on-proxmox-using-ansible-20j1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This article will guide you through how to install K3s with Flannel VXLAN backend on Proxmox using Ansible. It is assumed that you have basic knowledge about Kubernetes, Proxmox, SSH, and Ansible.&lt;/p&gt;

&lt;p&gt;This is my first post on dev.to, any constructive feedback is welcomed. &lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;



&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Table Of Contents&lt;/li&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;
Proxmox Firewall Configuration

&lt;ul&gt;
&lt;li&gt;Creating Proxmox Security Group&lt;/li&gt;
&lt;li&gt;Applying the Security Groups to your VMs&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Ansible Setup

&lt;ul&gt;
&lt;li&gt;Update your SSH config&lt;/li&gt;
&lt;li&gt;Directory Structure&lt;/li&gt;
&lt;li&gt;Ansible Config File&lt;/li&gt;
&lt;li&gt;Ansible Inventory&lt;/li&gt;
&lt;li&gt;The Enrollment Token&lt;/li&gt;
&lt;li&gt;The K3s Ansible Role &lt;/li&gt;
&lt;li&gt;The Playbook&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Run the Playbook&lt;/li&gt;

&lt;li&gt;Cluster Access&lt;/li&gt;

&lt;li&gt;Resources&lt;/li&gt;

&lt;/ul&gt;



&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Promox installed and running&lt;/li&gt;
&lt;li&gt;Ansible installed on your local machine&lt;/li&gt;
&lt;li&gt;At least 4 VMs (3 control plane nodes + 1 worker node) 

&lt;ul&gt;
&lt;li&gt;OS: Debian-based OSes, preferably the latest Ubuntu LTS&lt;/li&gt;
&lt;li&gt;SSH installed and configured using Public Key Auth&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;kubectl installed locally&lt;/li&gt;
&lt;li&gt;SSH installed locally&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Proxmox Firewall Configuration
&lt;/h2&gt;

&lt;p&gt;When you create VMs on Proxmox, the firewall is disabled by default. If you want to use the VM firewall, please make sure you opened the required ports.&lt;/p&gt;

&lt;p&gt;Below are the inbound rules for K3s nodes from the official documentation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fld5ee9b09aa4b2ko5owf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fld5ee9b09aa4b2ko5owf.png" alt="K3s Ports" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For more information: &lt;a href="https://docs.k3s.io/installation/requirements#inbound-rules-for-k3s-nodes" rel="noopener noreferrer"&gt;https://docs.k3s.io/installation/requirements#inbound-rules-for-k3s-nodes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We don't need all of these ports opened because we are not using the Flannel Wireguard backend and Spegel registry.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating Proxmox Security Group
&lt;/h3&gt;

&lt;p&gt;As we will be applying the same firewall rules to multiple VMs, it will be easier if we create a security group in Proxmox and apply the security group to all the target VMs.&lt;/p&gt;

&lt;p&gt;We will be creating 2 Security Groups in Proxmox:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;k3s: Ports that need to be opened on all nodes&lt;/li&gt;
&lt;li&gt;k3s_server: Ports that only need to be opened on the control plane nodes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To create a security group in Proxmox:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Login to the Proxmox UI&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Datacenter&lt;/strong&gt; on the left menu&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Firewall&lt;/strong&gt; and then &lt;strong&gt;Security Group&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Create&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select the security group you just created and click on &lt;strong&gt;Add&lt;/strong&gt; to start adding rules&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the &lt;strong&gt;k3s&lt;/strong&gt; security group:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;TCP 10250 for Kubelet metrics&lt;/li&gt;
&lt;li&gt;UDP 8472 for Flannel VXLAN&lt;/li&gt;
&lt;li&gt;TCP 22 for SSH&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the &lt;strong&gt;k3s_server&lt;/strong&gt; security group:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;TCP 6443 for K3s supervisor and Kubernetes API Server&lt;/li&gt;
&lt;li&gt;TCP 2479 for HA with embedded etcd&lt;/li&gt;
&lt;li&gt;TCP 2380 for HA with embedded etcd&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Applying the Security Groups to your VMs
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Click on your VM on from the left menu&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Firewall&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click on the &lt;strong&gt;Insert: Security Group&lt;/strong&gt; button&lt;/li&gt;
&lt;li&gt;Select the security groups we just created and enable them&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Ansible Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Update your SSH config
&lt;/h3&gt;

&lt;p&gt;To streamline our process of working with Ansible, it is recommended that you add the target hosts to your SSH config so we can reference them using hostnames in the Ansible inventory file. I am using Ubuntu on my local machine, so the SSH config is located at &lt;code&gt;~/.ssh/config&lt;/code&gt;. The same should apply to all Linux/Unix OSes.&lt;/p&gt;

&lt;p&gt;Example configuration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Control plane 1&lt;/span&gt;
Host k3s-m1
  User ubuntu
  HostName 10.0.0.30
  IdentityFile ~/.ssh/your_pk

&lt;span class="c"&gt;# Control plane 2&lt;/span&gt;
Host k3s-m2
  User ubuntu
  HostName 10.0.0.31
  IdentityFile ~/.ssh/your_pk

&lt;span class="c"&gt;# Control plane 3&lt;/span&gt;
Host k3s-m3
  User ubuntu
  HostName 10.0.0.32
  IdentityFile ~/.ssh/your_pk

&lt;span class="c"&gt;# Worker node 1&lt;/span&gt;
Host k3s-w1
  User ubuntu
  HostName 10.0.0.33
  IdentityFile ~/.ssh/your_pk

&lt;span class="c"&gt;# Add more if needed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Directory Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
├── ansible.cfg &lt;span class="c"&gt;# project specific Ansible configuration&lt;/span&gt;
├── inventory
│   └── inventories.ini &lt;span class="c"&gt;# Inventory information&lt;/span&gt;
├── k3s.yml &lt;span class="c"&gt;# The playbook to install K3s&lt;/span&gt;
└── roles
    └── k3s
        ├── tasks
        │   └── main.yml &lt;span class="c"&gt;# Ansible role to install K3s&lt;/span&gt;
        └── vars
            └── k3s-secrets.yml &lt;span class="c"&gt;# K3s enrollment token&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ansible Config File
&lt;/h3&gt;

&lt;p&gt;Path: &lt;code&gt;project_root/ansible.cfg&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We will create a custom Ansible configuration file named &lt;code&gt;ansible.cfg&lt;/code&gt; at the root of the project directory, which will reference the inventory file.&lt;/p&gt;

&lt;p&gt;Example configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[defaults]
inventory = ./inventory/inventories.ini
# If you haven't connected to the K3s VMs before and they are not in
# your SSH `know_hosts` file, you will have to uncomment the
# following option for the playbook to not thrown an error:
# host_key_checking = False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ansible Inventory
&lt;/h3&gt;

&lt;p&gt;Path: &lt;code&gt;project_root/inventory/inventories.ini&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We will organize the target VMs using an Ansible inventory file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# Initial master node setup for bootstrapping the K3S cluster.
# 'node_type' is used in the Ansible roles to identify and execute specific tasks for this node (see the role section).
&lt;/span&gt;&lt;span class="nn"&gt;[k3s_initial_master]&lt;/span&gt;
&lt;span class="c"&gt;# k3s-m1 is the hostname we defined in our SSH config
&lt;/span&gt;&lt;span class="err"&gt;k3s-m1&lt;/span&gt; &lt;span class="py"&gt;node_type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;k3s_initial_master&lt;/span&gt;

&lt;span class="c"&gt;# Additional master nodes for the K3S cluster.
&lt;/span&gt;&lt;span class="nn"&gt;[k3s_masters]&lt;/span&gt;
&lt;span class="err"&gt;k3s-m2&lt;/span&gt; &lt;span class="py"&gt;node_type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;k3s_master&lt;/span&gt;
&lt;span class="err"&gt;k3s-m3&lt;/span&gt; &lt;span class="py"&gt;node_type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;k3s_master&lt;/span&gt;
&lt;span class="c"&gt;# Additional workers...
&lt;/span&gt;
&lt;span class="c"&gt;# Worker nodes for running containerized applications.
&lt;/span&gt;&lt;span class="nn"&gt;[k3s_workers]&lt;/span&gt;
&lt;span class="err"&gt;k3s-w1&lt;/span&gt; &lt;span class="py"&gt;node_type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;k3s_worker&lt;/span&gt;
&lt;span class="err"&gt;k3s-w2&lt;/span&gt; &lt;span class="py"&gt;node_type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;k3s_worker&lt;/span&gt;
&lt;span class="err"&gt;k3s-w3&lt;/span&gt; &lt;span class="py"&gt;node_type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;k3s_worker&lt;/span&gt;
&lt;span class="c"&gt;# Additional workers...
&lt;/span&gt;
&lt;span class="c"&gt;# Group definition for simplified playbook targeting.
&lt;/span&gt;&lt;span class="nn"&gt;[k3s:children]&lt;/span&gt;
&lt;span class="err"&gt;k3s_initial_master&lt;/span&gt;
&lt;span class="err"&gt;k3s_masters&lt;/span&gt;
&lt;span class="err"&gt;k3s_workers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Enrollment Token
&lt;/h3&gt;

&lt;p&gt;Path: &lt;code&gt;project_root/roles/k3s/vars/k3s-secrets.yml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can optionally encrypt the file using the &lt;code&gt;ansible-vault encrypt path_to_file&lt;/code&gt; command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# You have to create a token yourself&lt;/span&gt;
&lt;span class="na"&gt;k3s_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_token"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The K3s Ansible Role
&lt;/h3&gt;

&lt;p&gt;Path: &lt;code&gt;project_root/roles/k3s/tasks/main.yml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# code: language=ansible&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install K3S Requirements&lt;/span&gt;
  &lt;span class="na"&gt;ansible.builtin.apt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;update_cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;pkg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;policycoreutils&lt;/span&gt;
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;

&lt;span class="c1"&gt;# To make sure the the role is idempotent. The tasks after this will only be executed if K3S hasn't been installed already.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check if K3S is already installed&lt;/span&gt;
  &lt;span class="na"&gt;ansible.builtin.shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;test&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-f&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/usr/local/bin/k3s'&lt;/span&gt;
  &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k3s_installed&lt;/span&gt;
  &lt;span class="na"&gt;failed_when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download K3s installation script&lt;/span&gt;
  &lt;span class="na"&gt;ansible.builtin.uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://get.k3s.io'&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GET&lt;/span&gt;
    &lt;span class="na"&gt;return_content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
    &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/tmp/k3s_install.sh'&lt;/span&gt;
  &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k3s_installed.rc != &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Import K3S Token&lt;/span&gt;
  &lt;span class="na"&gt;ansible.builtin.include_vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k3s-secrets.yml.vault&lt;/span&gt;
  &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k3s_installed.rc != &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;

&lt;span class="c1"&gt;# Note that the node_type variable is set in the inventory file&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Execute K3s installation script [Initial Master Node]&lt;/span&gt;
  &lt;span class="na"&gt;ansible.builtin.shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sh&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/tmp/k3s_install.sh&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--token&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;k3s_token&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--disable=traefik&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--flannel-backend=vxlan&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--cluster-init'&lt;/span&gt;
  &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;k3s_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;k3s_token&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;executable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bin/bash&lt;/span&gt;
  &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node_type | default('undefined') == 'k3s_initial_master' and k3s_installed.rc != &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Execute K3s installation script [Master Nodes]&lt;/span&gt;
  &lt;span class="na"&gt;ansible.builtin.shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sh&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/tmp/k3s_install.sh&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--token&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;k3s_token&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--disable=traefik&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--flannel-backend=vxlan&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--server&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;https://{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;hostvars["k3s-m1"]["ansible_default_ipv4"]["address"]&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}:6443'&lt;/span&gt;
  &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;k3s_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;k3s_token&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;executable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bin/bash&lt;/span&gt;
  &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node_type | default('undefined') == 'k3s_master' and k3s_installed.rc != &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Execute K3s installation script [Worker Nodes]&lt;/span&gt;
  &lt;span class="na"&gt;ansible.builtin.shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sh&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/tmp/k3s_install.sh&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;agent&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--token&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;k3s_token&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--server&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;https://{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;hostvars["k3s-m1"]["ansible_default_ipv4"]["address"]&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}:6443'&lt;/span&gt;
  &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;k3s_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;k3s_token&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;executable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bin/bash&lt;/span&gt;
  &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node_type | default('undefined') == 'k3s_worker' and k3s_installed.rc != &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Playbook
&lt;/h3&gt;

&lt;p&gt;Path: &lt;code&gt;project_root/k3s.yml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# code: language=ansible&lt;/span&gt;
&lt;span class="c1"&gt;# K3S Ansible Playbook&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;K3S&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k3s&lt;/span&gt;
  &lt;span class="na"&gt;gather_facts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k3s&lt;/span&gt;
      &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run the Playbook
&lt;/h2&gt;

&lt;p&gt;In your terminal, run &lt;code&gt;ansible-playbook k3s.yml&lt;/code&gt;. If you have encrypted the &lt;code&gt;k3s-secret.yml&lt;/code&gt; then you should run &lt;code&gt;ansible-playbook k3s.yml --ask-vault-pass&lt;/code&gt; and enter the password.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cluster Access
&lt;/h2&gt;

&lt;p&gt;After the playbook has finished running. You can obtain the content of the cluster's kubeconfig by SSH into one of the master node and reading the content of &lt;code&gt;/etc/rancher/k3s/k3s.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;More information: &lt;a href="https://docs.k3s.io/cluster-access" rel="noopener noreferrer"&gt;https://docs.k3s.io/cluster-access&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To use the config locally, please remember to change the &lt;code&gt;server: https://127.0.0.1:6443&lt;/code&gt; property in the file to point to one of the master node.&lt;/p&gt;

&lt;p&gt;Once you have copied the kubeconfig to your local machine, you can run &lt;code&gt;kubectl get ns&lt;/code&gt; to list all the namespaces and test the connection. If you have multiple clusters locally, make sure you are selecting the the correct context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;The playbook and role configuration can be found here on my GitHub repo: &lt;a href="https://github.com/algo7/homelab_ansible/tree/main/roles/k3s" rel="noopener noreferrer"&gt;https://github.com/algo7/homelab_ansible/tree/main/roles/k3s&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>k3s</category>
      <category>ansible</category>
      <category>proxmox</category>
    </item>
  </channel>
</rss>
