<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Zigbee on ln --help</title>
    <link>https://blog.mei-home.net/tags/zigbee/</link>
    <description>Recent content in Zigbee on ln --help</description>
    <generator>Hugo -- 0.147.2</generator>
    <language>en</language>
    <lastBuildDate>Sat, 15 Feb 2025 12:09:12 +0100</lastBuildDate>
    <atom:link href="https://blog.mei-home.net/tags/zigbee/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Nomad to k8s, Part 17: Migrating my IoT Services</title>
      <link>https://blog.mei-home.net/posts/k8s-migration-17-iot/</link>
      <pubDate>Sat, 15 Feb 2025 12:09:12 +0100</pubDate>
      <guid>https://blog.mei-home.net/posts/k8s-migration-17-iot/</guid>
      <description>Migrating Mosquitto, mqtt2prometheus and zigbee2mqtt</description>
      <content:encoded><![CDATA[<p>Wherein I migrate several IoT services over to Kubernetes.</p>
<p>This is part 18 of my <a href="https://blog.mei-home.net/tags/k8s-migration/">k8s migration series</a>.</p>
<p>This is going to be a short one. This weekend, I finished the migration of
several IoT related services to k8s.
<a href="https://mosquitto.org/">Mosquitto</a> is my MQTT broker, handling messages from
several sources. For me, it&rsquo;s only a listener - I do not have any actual home
automations.
Said mosquitto instance is scraped by <a href="https://github.com/hikhvar/mqtt2prometheus">mqtt2prometheus</a>
to get the data my smart plugs and thermometers produce into my Prometheus
instance.
Finally, I also migrated my <a href="https://www.zigbee2mqtt.io/">Zigbee2MQTT</a> instance
over to the k8s cluster. It controls my Zigbee transceiver and sends the data
from my thermometers on to mosquitto.</p>
<p>If you&rsquo;d like some more details on the power plug data gathering setup, have
a look <a href="https://blog.mei-home.net/posts/power-measurement/">here</a>.
The post on my thermometer setup is still on the large pile of blog posts I&rsquo;d
like to write at some point.</p>
<p>This will be a short(er) post, as I want to only talk about a couple of issues
I encountered along the way.</p>
<h2 id="selfmade-helm-chart">Selfmade Helm chart</h2>
<p>I decided to write my own Helm chart for these tools and manage them all in the
same namespace. Just makes the setup a bit simpler, as they don&rsquo;t really need to
talk to many other services, none of the apps needs a database for example.</p>
<p>So what does a Helm chart look like when I write it myself?
The <code>Chart.yaml</code> is kept extremely simple:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">v2</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">name</span>: <span style="color:#ae81ff">iot</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">description</span>: <span style="color:#ae81ff">The Homelab IoT services</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">type</span>: <span style="color:#ae81ff">application</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">version</span>: <span style="color:#ae81ff">0.1.0</span>
</span></span></code></pre></div><p>I don&rsquo;t need anything more. I don&rsquo;t even bother to change the Chart&rsquo;s version
when I change things.</p>
<p>The <code>values.yaml</code> file is also pretty sparse. I mostly use it for cases where
I need a value in multiple places:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">commonLabels</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">homelab/part-of</span>: <span style="color:#ae81ff">iot</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">mosquitto</span>: <span style="color:#e6db74">&#34;1883&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">pwr</span>: <span style="color:#e6db74">&#34;9641&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">temp</span>: <span style="color:#e6db74">&#34;9642&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">z2m</span>: <span style="color:#e6db74">&#34;8080&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">mqttHost</span>: <span style="color:#ae81ff">mqtt.example.com</span>
</span></span></code></pre></div><p>And that&rsquo;s it already.</p>
<h2 id="mosquitto">Mosquitto</h2>
<p>As I said, I won&rsquo;t detail every single manifest here. But one interesting part
was that MQTT isn&rsquo;t HTTP, it&rsquo;s a purely TCP based protocol. But I&rsquo;m still using
Ingress mechanisms, because Traefik does support TCP routes. In k8s, these are
configured with the <a href="https://doc.traefik.io/traefik/routing/providers/kubernetes-crd/#kind-ingressroutetcp">IngressRouteTCP</a> CRD.
Using such a router config, some things are not available. E.g. if you don&rsquo;t
configure TLS, you cannot do host-based routing. The connection simply doesn&rsquo;t
tell you what host it connected to. So when you want to use unencrypted TCP (or UDP),
your have to create a separate Traefik entrypoint with its own port just for this
route.
Here&rsquo;s the route manifest:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">traefik.io/v1alpha1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">IngressRouteTCP</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">mosquitto</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">annotations</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">external-dns.alpha.kubernetes.io/hostname</span>: <span style="color:#e6db74">&#34;{{ .Values.mqttHost }}&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">external-dns.alpha.kubernetes.io/target</span>: <span style="color:#e6db74">&#34;ingress.example.com&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">entryPoints</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">mqtt</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">routes</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">match</span>: <span style="color:#ae81ff">HostSNI(`*`)</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">services</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">mosquitto</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Service</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">port</span>: <span style="color:#ae81ff">1883</span>
</span></span></code></pre></div><p>This connects Traefik&rsquo;s 1883 port to mosquitto&rsquo;s Service. All connections
arriving on the mqtt entrypoint will be forwarded to mosquitto.</p>
<p>If you do require TLS, Traefik can make use of the <a href="https://de.wikipedia.org/wiki/Server_Name_Indication">Server Name Indication</a>,
via the <code>HostSNI</code> setting. But SNI is an extension to TLS, so not all software
implementing TLS will support it.
When TLS is enabled, you can even run pure TLS connections over the same port
Traefik is using for HTTPS.
An IngressRouteTCP would look like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">traefik.io/v1alpha1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">IngressRouteTCP</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">mosquitto-tls</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">entryPoints</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">websecure</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">routes</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">match</span>: <span style="color:#ae81ff">HostSNI(`{{ .Values.mqttHost }}`)</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">services</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">mosquitto</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Service</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">port</span>: <span style="color:#ae81ff">1883</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">tls</span>: {}
</span></span></code></pre></div><p>Here, the <code>websecure</code> entrypoint is my standard HTTPS entrypoint. This still works
as expected though, even for pure TLS connections, by using the SNI and
forwarding connections arriving for mqtt.example.com to mosquitto.
The <code>tls</code> key at the end is important, even though it is empty. This tells
Traefik to enable TLS with its default configuration, which uses my wildcard
cert.</p>
<p>The most interesting part of the mosquitto setup was the creation of users.
It uses a passwd-like file format, and I got &ldquo;creative&rdquo; when setting up the
Nomad job.
All of the users (admin user, scrapers, Zigbee2MQTT, my smart plugs) are in a
directory in Vault, looking like this:</p>
<pre tabindex="0"><code>my-secrets/iot/mqtt/users/username1
my-secrets/iot/mqtt/users/username2
[...]
</code></pre><p>Then each of those only has a single key, <code>secret</code>, which contains the user&rsquo;s
password, already encrypted with <a href="https://mosquitto.org/man/mosquitto_passwd-1.html">mosquitto_passwd</a>.
The problem now is: How to get all of those into a single passwd file for
mosquitto to use?
The resulting file should look something like this:</p>
<pre tabindex="0"><code>user1:$7$foo_encrypted==

user2:$7$bar_encrypted==
</code></pre><p>It turns out that <a href="https://external-secrets.io/latest/">external-secrets</a> has a
pretty good <a href="https://external-secrets.io/latest/guides/templating/">templating engine</a>,
so I was actually able to do this. The finished ExternalSecret looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">external-secrets.io/v1beta1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ExternalSecret</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">mosquitto-users</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">refreshInterval</span>: <span style="color:#e6db74">&#34;1m&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">secretStoreRef</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">my-secrets</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ClusterSecretStore</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">target</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">passwd</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">template</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">data</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">passwd</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {{ `{{ range $name, $pass := . }}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {{ $name }}:{{ with $pass | fromJson }}{{ .secret }}{{ end }}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {{ end }}` }}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">dataFrom</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">find</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">path</span>: <span style="color:#ae81ff">my-secrets/iot/mqtt/users</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">name</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">regexp</span>: <span style="color:#e6db74">&#34;.*&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">rewrite</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">regexp</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">source</span>: <span style="color:#e6db74">&#34;my-secrets/iot/mqtt/users/(.*)&#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">target</span>: <span style="color:#e6db74">&#34;$1&#34;</span>
</span></span></code></pre></div><p>Let&rsquo;s start with the data fetching in <code>dataFrom</code>. It returns all secrets
below the <code>users/</code> path and returns them in a map, akin to this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">resultMap</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">my-secrets/iot/mqtt/users/username1</span>: {<span style="color:#f92672">&#34;secret&#34;: </span><span style="color:#e6db74">&#34;foo&#34;</span>}
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">my-secrets/iot/mqtt/users/username2</span>: {<span style="color:#f92672">&#34;secret&#34;: </span><span style="color:#e6db74">&#34;bar&#34;</span>}
</span></span></code></pre></div><p>This is a bit unfortunate, because to get the right format, I need the username
as well. That&rsquo;s what the <code>rewrite:</code> object gives me. It does a regex match on
the whole path and gives me back only the last element, which is the username.
Then the template itself just iterates over the map and brings out the
username and password in the right format.</p>
<p>I&rsquo;m repeatedly impressed how many tight situations external-secrets has helped
me out of already. After some fiddling, this is a good enough result.</p>
<p>One thing I found rather unfortunate though: There&rsquo;s no way of defining the
owner of a Secret mapped into a pod as a volume. This means that the passwd file
sits in the container world readable. Not great. The only potential solution I
found was the introduction of an init container, to run chmod on the file.
I skipped that for now, but will have to take care about it at some point,
because mosquitto already complains about the fact that the passwd file is
world readable, noting that a setup like that will be rejected in the future.</p>
<h2 id="scraping-mqtt-data-with-prometheus">Scraping MQTT data with Prometheus</h2>
<p>I like and greatly enjoy my Prometheus data. I like looking at all of the plots
in Grafana. There&rsquo;s a reason it gets to occupy 200 GB of disk space.
So I need to get my MQTT data, meaning power consumption from the smart plugs
and thermal data from the thermometers, into Prometheus.
For this, I&rsquo;m using <a href="https://github.com/hikhvar/mqtt2prometheus">mqtt2prometheus</a>.
I&rsquo;ve currently got two instances running, one for my power plugs&rsquo; energy measurement
and one for my thermometers&rsquo; temperature and humidity. I put both of them into
one Pod, because having separate Pods for each of them seemed unnecessary.</p>
<p>The configuration of the power measurements exporter looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ConfigMap</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">pwr-exporter</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>    {{- <span style="color:#ae81ff">range $label, $value := .Values.commonLabels }}</span>
</span></span><span style="display:flex;"><span>    {{ <span style="color:#f92672">$label }}</span>: {{ <span style="color:#ae81ff">$value | quote }}</span>
</span></span><span style="display:flex;"><span>    {{- <span style="color:#ae81ff">end }}</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">data</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">config.yaml</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    mqtt:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      server: tcp://{{ .Values.mqttHost }}:{{ .Values.ports.mosquitto }}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      user: promexport
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      client_id: pwr-exporter
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      topic_path: &#34;plugs/tasmota/tele/#&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      device_id_regex: &#34;plugs/tasmota/tele/(?P&lt;deviceid&gt;.*)/.*&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    metrics:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      - prom_name: mqtt_total_power_kwh
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        mqtt_name: ENERGY.Total
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        help: &#34;Total power consumption (kWh)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        type: counter
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      - prom_name: mqtt_power
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        mqtt_name: ENERGY.Power
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        help: &#34;Current consumption (W)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        type: gauge
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      - prom_name: mqtt_current
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        mqtt_name: ENERGY.ApparentPower
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        help: &#34;Current (A)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        type: gauge
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      - prom_name: mqtt_yesterday_pwr
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        mqtt_name: ENERGY.Yesterday
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        help: &#34;Yesterdays Total Power Consumption (kWh)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        type: counter
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      - prom_name: mqtt_today_pwr
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        mqtt_name: ENERGY.Today
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        help: &#34;Todays Total Power Consumption (kWh)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        type: counter</span>
</span></span></code></pre></div><p>And the one for the thermometers looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ConfigMap</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">temp-exporter</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>    {{- <span style="color:#ae81ff">range $label, $value := .Values.commonLabels }}</span>
</span></span><span style="display:flex;"><span>    {{ <span style="color:#f92672">$label }}</span>: {{ <span style="color:#ae81ff">$value | quote }}</span>
</span></span><span style="display:flex;"><span>    {{- <span style="color:#ae81ff">end }}</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">data</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">config.yaml</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    mqtt:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      server: tcp://{{ .Values.mqttHost }}:{{ .Values.ports.mosquitto }}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      user: promexport
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      client_id: temp-exporter
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      topic_path: &#34;zigbee2mqtt/temp/sonoff/#&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      device_id_regex: &#34;zigbee2mqtt/temp/sonoff/(?P&lt;deviceid&gt;.*)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    cache:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      timeout: 24h
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    metrics:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      - prom_name: mqtt_temp_battery_percent
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        mqtt_name: battery
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        help: &#34;Current battery percentage (percent)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        type: gauge
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        omit_timestamp: true
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      - prom_name: mqtt_temp_humidity
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        mqtt_name: humidity
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        help: &#34;Current humidity (percent)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        type: gauge
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        omit_timestamp: true
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      - prom_name: mqtt_temp_temperature
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        mqtt_name: temperature
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        help: &#34;Current temperature (C)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        type: gauge
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        omit_timestamp: true</span>
</span></span></code></pre></div><p>The configurations mostly consist of the translation of MQTT topics to Prometheus
metrics.</p>
<p>Here&rsquo;s the deployment for the Pod:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">apps/v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Deployment</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">exporters</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">replicas</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">selector</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">matchLabels</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">homelab/app</span>: <span style="color:#ae81ff">exporters</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">strategy</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">type</span>: <span style="color:#e6db74">&#34;Recreate&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">template</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">homelab/app</span>: <span style="color:#ae81ff">exporters</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">annotations</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">checksum/pwr-config</span>: {{ <span style="color:#ae81ff">include (print $.Template.BasePath &#34;/pwr-exp-conf.yaml&#34;) . | sha256sum }}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">checksum/temp-config</span>: {{ <span style="color:#ae81ff">include (print $.Template.BasePath &#34;/temp-exp-conf.yaml&#34;) . | sha256sum }}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">containers</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">pwr-exporter</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">image</span>: <span style="color:#ae81ff">ghcr.io/hikhvar/mqtt2prometheus:{{ .Values.mqtt2promVersion }}</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">args</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;-config&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;/etc/mqtt2prom/config.yaml&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;-listen-port&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;{{ .Values.ports.pwr }}&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;-log-format&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;json&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">volumeMounts</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">config-pwr</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">mountPath</span>: <span style="color:#ae81ff">/etc/mqtt2prom</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">readOnly</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">env</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">MQTT2PROM_MQTT_USER</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;promexport&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">envFrom</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">secretRef</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">name</span>: <span style="color:#ae81ff">exporter-mosquitto-user</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">optional</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">pwr-exporter</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">containerPort</span>: {{ <span style="color:#ae81ff">.Values.ports.pwr }}</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">protocol</span>: <span style="color:#ae81ff">TCP</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">temp-exporter</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">image</span>: <span style="color:#ae81ff">ghcr.io/hikhvar/mqtt2prometheus:{{ .Values.mqtt2promVersion }}</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">args</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;-config&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;/etc/mqtt2prom/config.yaml&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;-listen-port&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;{{ .Values.ports.temp }}&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;-log-format&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;json&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">volumeMounts</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">config-temp</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">mountPath</span>: <span style="color:#ae81ff">/etc/mqtt2prom</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">readOnly</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">env</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">MQTT2PROM_MQTT_USER</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;promexport&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">envFrom</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">secretRef</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">name</span>: <span style="color:#ae81ff">exporter-mosquitto-user</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">optional</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">temp-exporter</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">containerPort</span>: {{ <span style="color:#ae81ff">.Values.ports.temp }}</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">protocol</span>: <span style="color:#ae81ff">TCP</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">volumes</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">config-pwr</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">configMap</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">name</span>: <span style="color:#ae81ff">pwr-exporter</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">config-temp</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">configMap</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">name</span>: <span style="color:#ae81ff">temp-exporter</span>
</span></span></code></pre></div><p>I have again cut out some unimportant pieces. Luckily, mqtt2prometheus supports
providing the credentials for MQTT access via environment variables, so I didn&rsquo;t
have to template the entire configuration file to avoid putting the credentials
into git.</p>
<p>Finally, I had to also set up the network policy to allow my Prometheus deployment
access to the Pod and its ports for scraping:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#e6db74">&#34;cilium.io/v2&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">CiliumNetworkPolicy</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;exporters&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">endpointSelector</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">matchLabels</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">homelab/app</span>: <span style="color:#ae81ff">exporters</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">ingress</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">fromEndpoints</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">matchLabels</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">io.kubernetes.pod.namespace</span>: <span style="color:#ae81ff">monitoring</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">app.kubernetes.io/name</span>: <span style="color:#ae81ff">prometheus</span>
</span></span></code></pre></div><h2 id="the-zigbee-manager">The Zigbee manager</h2>
<p>My thermometers are connected via Zigbee, so I needed some way to transform the
data to MQTT and send it to my mosquitto instance. I don&rsquo;t use HomeAssistant,
because it looks very much like overkill. I don&rsquo;t actually control anything,
I just want to gather a bit of data.
I&rsquo;m using <a href="https://www.zigbee2mqtt.io/">Zigbee2MQTT</a> for this. I&rsquo;m using a
Zigbee transceiver connected via LAN, so I didn&rsquo;t have to muck about with
mounting a USB device into the Pod.
Again, zigbee2mqtt is a good piece of software because it allows me to set some
config keys, those containing secrets, via environment variables but also allows
me to provide the non-secret config options in the configuration file.
Zigbee2MQTT requires three secrets:</p>
<ol>
<li>The MQTT credentials for access to mosquitto</li>
<li>An auth token for access to the web UI</li>
<li>A network key</li>
</ol>
<p>I&rsquo;m providing all three from my Vault instance in an ExternalSecret again:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">external-secrets.io/v1beta1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ExternalSecret</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">zigbee2mqtt</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">refreshInterval</span>: <span style="color:#e6db74">&#34;1m&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">secretStoreRef</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">my-secrets</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ClusterSecretStore</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">target</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">zigbee2mqtt</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">template</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">data</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">ZIGBEE2MQTT_CONFIG_FRONTEND_AUTH_TOKEN</span>: <span style="color:#e6db74">&#34;{{ `{{ .auth }}` }}&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">ZIGBEE2MQTT_CONFIG_MQTT_PASSWORD</span>: <span style="color:#e6db74">&#34;{{ `{{ .mqtt }}` }}&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">ZIGBEE2MQTT_CONFIG_ADVANCED_NETWORK_KEY</span>: <span style="color:#e6db74">&#34;[{{ `{{ .network }}` }}]&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">data</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">secretKey</span>: <span style="color:#ae81ff">auth</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">remoteRef</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">key</span>: <span style="color:#ae81ff">my-secrets/iot/zigbee2mqtt/auth</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">property</span>: <span style="color:#ae81ff">secret</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">secretKey</span>: <span style="color:#ae81ff">mqtt</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">remoteRef</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">key</span>: <span style="color:#ae81ff">my-secrets/iot/zigbee2mqtt/mqtt</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">property</span>: <span style="color:#ae81ff">secret</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">secretKey</span>: <span style="color:#ae81ff">network</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">remoteRef</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">key</span>: <span style="color:#ae81ff">my-secrets/iot/zigbee2mqtt/network-key</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">property</span>: <span style="color:#ae81ff">secret</span>
</span></span></code></pre></div><p>The complicated part of the Zigbee2MQTT deployment is the configuration file.
Because sadly, Zigbee2MQTT is one of those applications that need write access
to their configuration file. Which makes usage of a ConfigMap complicated,
because those are always mounted read-only. In the case of Zigbee2MQTT, I don&rsquo;t
really care about the content changes it makes, I can just deploy my original
file over it without an issue. But Zigbee2MQTT won&rsquo;t even start if it can&rsquo;t
write to the config file.</p>
<p>First, the config map itself:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ConfigMap</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">zigbee2mqtt</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">data</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">configuration.yaml</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    version: 4
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    homeassistant:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      enabled: false
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    permit_join: false
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    frontend:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      enabled: true
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    mqtt:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      base_topic: zigbee2mqtt
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      server: &#39;mqtts://{{ .Values.mqttHost }}:443&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      user: foo
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      client_id: &#34;foobar&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    # Serial settings
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    serial:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      port: &#39;tcp://my-zigbee-bridge:1234&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      adapter: zstack
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    advanced:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      channel: 23
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      log_output:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        - console
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    devices:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      &#39;0x123&#39;:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        friendly_name: &#39;temp/sonoff/thermo1&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        icon: device_icons/bdc2692122548ad0f2b0fb6c9f10a93d.png
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      &#39;0x456&#39;:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        friendly_name: &#39;temp/sonoff/thermo2&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        icon: device_icons/bdc2692122548ad0f2b0fb6c9f10a93d.png</span>
</span></span></code></pre></div><p>When new devices are connected, Zigbee2MQTT adds them to the <code>devices:</code> map, and
I then just add them to the ConfigMap manually.</p>
<p>But how to handle the fact that this config file needs to be writable?
Init containers. Because up to now, I&rsquo;ve been living in blissful ignorance of
such hacks, but that streak of good fortune had to end at some point. I just
find it so incredibly ugly. Look at it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">apps/v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Deployment</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">zigbee2mqtt</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">replicas</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">selector</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">matchLabels</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">homelab/app</span>: <span style="color:#ae81ff">zigbee2mqtt</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">strategy</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">type</span>: <span style="color:#e6db74">&#34;Recreate&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">template</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">homelab/app</span>: <span style="color:#ae81ff">zigbee2mqtt</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">annotations</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">checksum/config</span>: {{ <span style="color:#ae81ff">include (print $.Template.BasePath &#34;/z2m-config.yaml&#34;) . | sha256sum }}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">securityContext</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">fsGroup</span>: <span style="color:#ae81ff">1000</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">initContainers</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">zigbee2mqtt-init</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">image</span>: <span style="color:#ae81ff">alpine:3.21.2</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">volumeMounts</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">data</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">mountPath</span>: <span style="color:#ae81ff">/data</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">config</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">mountPath</span>: <span style="color:#ae81ff">/config</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">command</span>: [<span style="color:#e6db74">&#34;cp&#34;</span>, <span style="color:#e6db74">&#34;/config/configuration.yaml&#34;</span>, <span style="color:#e6db74">&#34;/data/configuration.yaml&#34;</span>]
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">containers</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">zigbee2mqtt</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">image</span>: <span style="color:#ae81ff">koenkk/zigbee2mqtt:{{ .Values.zigbee2mqttVersion }}</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">volumeMounts</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">data</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">mountPath</span>: <span style="color:#ae81ff">/app/data</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">resources</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">requests</span>:
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">cpu</span>: <span style="color:#ae81ff">200m</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">memory</span>: <span style="color:#ae81ff">200Mi</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">envFrom</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">secretRef</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">name</span>: <span style="color:#ae81ff">zigbee2mqtt</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">optional</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">web</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">containerPort</span>: {{ <span style="color:#ae81ff">.Values.ports.z2m }}</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">protocol</span>: <span style="color:#ae81ff">TCP</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">volumes</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">data</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">persistentVolumeClaim</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">claimName</span>: <span style="color:#ae81ff">z2m-volume</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">config</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">configMap</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">name</span>: <span style="color:#ae81ff">zigbee2mqtt</span>
</span></span></code></pre></div><p>I&rsquo;m launching an entire other container just to run a single <code>cp</code> command to
copy the mounted ConfigMap into the data volume. I wish we had some better way
to do something like this. But it seems we don&rsquo;t.</p>
<p>And that&rsquo;s it for this one. I think wherever possible, I will keep the future
migration posts in this format, not explaining every single line of every single
Yaml file anymore, but only pointing out interesting things like the issue
with the mosquitto credentials in this one. It&rsquo;s more interesting to write and
I hope more interesting to read than the umpteenth re-explanation of my CNPG
DB setup.</p>
<p>Next up will be my Jellyfin media server. The copying of my media collection
is already done, and hopefully I will get the actual migration completed today.
That one will contain a lot of Grafana plots and Ceph performance musings. &#x1f913;</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
