<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/feeds.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://chameth.com/">
    <title>Chameth.com - posts like debugging-beyond-the-debugger, intro-to-containers, monitoring-power-with-wemo, why-you-should-be-using-https</title>
    <subtitle>Personal homepage of Chris Smith</subtitle>
    <link href="https://chameth.com/feeds/posts/like/debugging-beyond-the-debugger,intro-to-containers,monitoring-power-with-wemo,why-you-should-be-using-https/" rel="self"/>
    <link href="https://chameth.com/"/>
    <icon>https://chameth.com/favicon.png</icon>
    <updated>2026-04-09T00:00:00Z</updated>
    <id>https://chameth.com/</id>
    <author>
        <name>Chris Smith</name>
    </author>
    <entry>
        <title>The Case of the Unchanging Config</title>
        <link href="https://chameth.com/the-case-of-the-unchanging-config/"/>
        <updated>2026-04-09T00:00:00Z</updated>
        <id>https://chameth.com/the-case-of-the-unchanging-config/</id>
        <content xml:lang="en" type="html">&lt;p&gt;Last week I was attempting to make it so I could share pictures on IRC directly from my client. This sounds simple, but it involves a bouncer&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:1&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; that proxies the request to a standalone image hosting service that I had to modify to be compatible. At one point my testing loop was:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Make a change to the hosting service&lt;/li&gt;
&lt;li&gt;Commit it&lt;/li&gt;
&lt;li&gt;Tag a new release&lt;/li&gt;
&lt;li&gt;Wait for it to build&lt;/li&gt;
&lt;li&gt;Update the version I’m running on my server&lt;/li&gt;
&lt;li&gt;Reconfigure the bouncer to pass the new parameter or change the URL or whatever&lt;/li&gt;
&lt;li&gt;Try uploading a photo from my phone&lt;/li&gt;
&lt;li&gt;Realise I’ve overlooked something and go back to step one&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I &lt;em&gt;could&lt;/em&gt; have set up a local copy of everything and tested it sensibly. I &lt;em&gt;should&lt;/em&gt; have set up a local copy of everything and tested it sensibly. But it seemed like such a trivial change, and setting up the whole environment seemed like such a pain. After the third or so iteration of failure I was pretty annoyed with myself, computers, and basically everything.&lt;/p&gt;
&lt;p&gt;My biggest annoyance was that my bouncer would not pick up the new URL from the config when I changed it. It’s meant to reload the config when it receives a &lt;code&gt;SIGHUP&lt;/code&gt;, and it claimed to in the logs, but I could clearly see it was still hitting the old URL. Restarting the bouncer to update the config is a pain, as it disconnects me from all the IRC networks, and has to reconnect to them all, reauthenticate, etc. It also mildly spams everyone who shares a channel with me&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:2&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;When I finally got everything working I had a look at the bouncer source, and thought I’d spotted the issue. I raised a bug report, ending in this remark:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It looks like the config is reloaded properly, but the handler for uploads is created once at startup and has its own copy of the uploader, so effectively snapshots the config to whatever it is at startup:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nx&#34;&gt;fileUploadHandler&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;http&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nf&#34;&gt;HandlerFunc&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;http&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;ResponseWriter&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;http&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;Request&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;cfg&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;srv&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nf&#34;&gt;Config&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;h&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;fileupload&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;Handler&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;Uploader&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;cfg&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;FileUploader&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;DB&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;db&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;Auth&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;cfg&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;Auth&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;HTTPOrigins&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;cfg&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;HTTPOrigins&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;h&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nf&#34;&gt;ServeHTTP&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;})&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I’m not quite sure what I thought I saw there, but that’s not right. I want to call it a hallucination, but that feels like a very overloaded word these days. I guess I was just seeing what I wanted to see, and that was a good excuse to stop investigating. Still, even if I misidentified the cause, the bug was still present, right? … Right?&lt;/p&gt;
&lt;h3 id=&#34;the-science-of-deduction&#34;&gt;The Science of Deduction&lt;/h3&gt;
&lt;p&gt;When the developer got back and said he couldn’t reproduce my issue, I went back to my install and immediately reproduced it. With a calmer head, I figured it was probably something with my particular set up. First thing to check: am I actually running the version I think I am?&lt;/p&gt;
&lt;p&gt;Unfortunately the bouncer doesn’t actually seem to expose the version anywhere that I can see. It’s not in the logs, it doesn’t have a &lt;code&gt;-version&lt;/code&gt; flag, and none of the IRC-based status commands seem to include it. But I know it’s a Go app, and I know Go embeds the version information. &lt;code&gt;go version -m &amp;lt;binary&amp;gt;&lt;/code&gt; will dump it all out, but the binary is inside a Docker image, and the Docker image is one of my &lt;a href=&#34;https://chameth.com/artisanal-docker-images/&#34;&gt;nice, minimal, artisanal ones&lt;/a&gt; so doesn’t ship a &lt;code&gt;go&lt;/code&gt; binary. No problem, &lt;code&gt;docker compose cp bouncer:/bnc ./bnc&lt;/code&gt; yoinks the binary out of the container, and then dumping the version shows that, yes, I am running the version I thought I was. Hmm.&lt;/p&gt;
&lt;p&gt;The next tool I reached for in &lt;a href=&#34;https://chameth.com/debugging-beyond-the-debugger/&#34;&gt;my toolbox&lt;/a&gt; was &lt;code&gt;strace&lt;/code&gt;. Maybe it’s not actually reading the file for some reason? I immediately executed &lt;code&gt;strace -p &amp;lt;pid&amp;gt; -e trace=openat,open,read,pread64 -f&lt;/code&gt; without having to look any part of that up. Yep. Definitely. Then I edited the config, &lt;code&gt;HUP&lt;/code&gt;’d the bouncer, and saw that it was… reading the config file. As it’s meant to. By default &lt;code&gt;strace&lt;/code&gt; truncates strings to 32 bytes, so I couldn’t actually see the line I’d changed. Some more definitely-not-RTFMing later, and rerunning it with an extra &lt;code&gt;-s 65536&lt;/code&gt; let me see the full config. Surprise! The config hadn’t changed!&lt;/p&gt;
&lt;p&gt;To confirm my findings, I used &lt;code&gt;docker compose cp&lt;/code&gt; again, this time yoinking the config file from inside the container. The inside config file was definitely different to the outside config file. What? My hypothesis at this point was “something something Docker nonsense”. I mount the config as read-only, and was wondering if that meant that Docker was doing something &lt;em&gt;weird&lt;/em&gt; instead of just bind mounting it. A quick trip to &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/mounts&lt;/code&gt; showed that it was, in fact, not doing anything weird, and was just bind mounting it.&lt;/p&gt;
&lt;p&gt;If the file is bind mounted, then surely it’s the same file? I ran &lt;code&gt;stat&lt;/code&gt; on the file on the host, noted the inode number, then pondered how to actually run &lt;code&gt;stat&lt;/code&gt; on the file inside the container, given the aforementioned awkwardly minimal image. The solution was easy: access it via &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/root/&lt;/code&gt;. I could’ve saved myself a bunch of &lt;code&gt;docker compose cp&lt;/code&gt; if I’d thought about that earlier. Oh well. The inode of that file was different. What?&lt;/p&gt;
&lt;p&gt;The answer was DNS. It’s always DNS. Oh, sorry, force of habit. I meant the answer was &lt;em&gt;vim&lt;/em&gt;. I was editing the config in vim, and when it saves files, by default, it writes the new content to a temporary file and does an atomic rename. That’s normally a good thing: it prevents corruption if the write fails midway through&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:3&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;. In this case, though, that means the new file has a new inode. Bind mounting a file binds to the inode, so the container just keeps clutching onto the original config from when it was started, blissfully unaware that the party has relocated down the street.&lt;/p&gt;
&lt;p&gt;This problem is likely to happen whenever you bind mount a file into a container. When I mentioned this to a friend, he immediately responded “oh yeah, never do that”, and went on to describe the horrible hacks he’s had to add to Ansible to sidestep the issue. The nicer solution is to just bind mount an entire directory if you can, as then it doesn’t matter what happens to the files within it. I really like having the config files sat alongside the Docker compose files, though; having to create a directory just to work around some bind mount weirdness upsets me.&lt;/p&gt;
&lt;p&gt;Now I knew what the problem was, I found there was an issue &lt;a href=&#34;https://github.com/moby/moby/issues/6011&#34;&gt;raised against Docker&lt;/a&gt; twelve years ago&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:4&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;. The first response was “that’s expected”, and I fully agree with the author: “respectfully, that might be expected by you, but it was not expected by me”!&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:5&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt; As a result of the issue they &lt;a href=&#34;https://github.com/moby/moby/pull/6854/changes&#34;&gt;added a nice note to the docs&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;
Many tools used to edit files including &lt;code&gt;vi&lt;/code&gt; and &lt;code&gt;sed --in-place&lt;/code&gt; may result
in an inode change. Since Docker v1.1.0, this will produce an error such as
“&lt;em&gt;sed: cannot rename ./sedKdJ9Dy: Device or resource busy&lt;/em&gt;”. In the case where
you want to edit the mounted file, it is often easiest to instead mount the
parent directory.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But in the intervening twelve years, both the note and the functionality described have gone missing. Ho-hum.&lt;/p&gt;
&lt;p&gt;I found it interesting how I’ve only just hit this problem, given how long I’ve used Docker. But I realised that almost everything I run I’m happy to just restart. Cattle, not pets, and so on. My IRC bouncer is one of the few exceptions to that. The only other thing I regularly hot reloaded was &lt;a href=&#34;https://github.com/csmith/centauri&#34;&gt;Centauri&lt;/a&gt;, my reverse proxy, but that had a whole config directory mounted because it was shared between containers, so nicely sidestepped the foot-gun.&lt;/p&gt;
&lt;p&gt;So lesson learnt: check for weird bind mount issues before raising issues about config hot reloading. It’ll join the esteemed company of “maybe the drive is full and causing completely unrelated problems?”, “perhaps everything is dog slow because the kernel ran out of entropy?”, and “did systemd sneakily take over that functionality while you weren’t looking?” in the troubleshooting checklist.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;An IRC bouncer is basically an always-on proxy. It connects to the IRC networks for you, then your clients connect to your bouncer. The bouncer can then send incoming messages to all your different clients, cache them when you’re offline and replay them later, and lots of other nice things people take for granted in their chat apps these days. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:1&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;Join/part/quit spam is part of IRC, and clients have ways of handling it, but I still &lt;em&gt;feel&lt;/em&gt; bad about doing it excessively. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:2&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;You can disable this in vim by setting &lt;code&gt;backupcopy=yes&lt;/code&gt;. Good luck redoing all the debugging if you ever accidentally remove that from your &lt;code&gt;vimrc&lt;/code&gt;, though! &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:3&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;Which is weird, because Docker can’t possibly be that old. That would make me much older than I’m prepared to accept. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:4&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34;&gt;
&lt;p&gt;If you read the thread they didn’t actually mean to sound so dismissive, but it’s still pretty funny. “Yes, it’s expected that the foot-gun causes your foot to hurt. Duh. What did you expect?” &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:5&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</content>
    </entry>
    <entry>
        <title>An interesting Tailscale + Docker gotcha</title>
        <link href="https://chameth.com/tailscale-docker-gotcha/"/>
        <updated>2026-01-29T00:00:00Z</updated>
        <id>https://chameth.com/tailscale-docker-gotcha/</id>
        <content xml:lang="en" type="html">&lt;p&gt;As I’ve &lt;a href=&#34;https://chameth.com/how-i-use-tailscale/&#34;&gt;written&lt;/a&gt; &lt;a href=&#34;https://chameth.com/avoiding-the-consequences-of-dumb-laws-with-tailscale/&#34;&gt;about&lt;/a&gt; &lt;a href=&#34;https://chameth.com/exposing-game-servers-over-tailscale/&#34;&gt;before&lt;/a&gt;, I use &lt;a href=&#34;https://tailscale.com/&#34;&gt;Tailscale&lt;/a&gt; for a lot of things. I thought I had it set up in a reasonably secure manner, but I recently noticed a problem.&lt;/p&gt;
&lt;p&gt;I use Tailscale’s ACLs to limit what each node can access, based on the tags I apply to it. So an &lt;code&gt;app&lt;/code&gt; node can’t access anything via Tailscale, while an &lt;code&gt;integration&lt;/code&gt; or &lt;code&gt;server&lt;/code&gt; node can access things tagged with either &lt;code&gt;app&lt;/code&gt; or &lt;code&gt;integration&lt;/code&gt;. This is expressed pretty simply in the Tailscale ACL JSON:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;	&lt;span class=&#34;chroma-nt&#34;&gt;&amp;#34;tagOwners&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;		&lt;span class=&#34;chroma-c1&#34;&gt;// Servers that can be SSH&amp;#39;d into
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;		&lt;span class=&#34;chroma-nt&#34;&gt;&amp;#34;tag:server&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;[],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;		&lt;span class=&#34;chroma-c1&#34;&gt;// Applications that are exposed on tailscale but never connect out
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;		&lt;span class=&#34;chroma-nt&#34;&gt;&amp;#34;tag:app&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;[],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;		&lt;span class=&#34;chroma-c1&#34;&gt;// Things which talk to other services over tailscale (connecting to apps etc)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;		&lt;span class=&#34;chroma-nt&#34;&gt;&amp;#34;tag:integration&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;[],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;	&lt;span class=&#34;chroma-p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;	&lt;span class=&#34;chroma-nt&#34;&gt;&amp;#34;grants&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;		&lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;			&lt;span class=&#34;chroma-c1&#34;&gt;// Users can access everything
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;			&lt;span class=&#34;chroma-nt&#34;&gt;&amp;#34;src&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;autogroup:member&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;			&lt;span class=&#34;chroma-nt&#34;&gt;&amp;#34;dst&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;			&lt;span class=&#34;chroma-nt&#34;&gt;&amp;#34;ip&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt;  &lt;span class=&#34;chroma-p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;		&lt;span class=&#34;chroma-p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;		&lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;			&lt;span class=&#34;chroma-c1&#34;&gt;// Servers and integrations can access integrations and apps
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;			&lt;span class=&#34;chroma-nt&#34;&gt;&amp;#34;src&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;tag:server&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;tag:integration&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;			&lt;span class=&#34;chroma-nt&#34;&gt;&amp;#34;dst&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;tag:integration&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;tag:app&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;			&lt;span class=&#34;chroma-nt&#34;&gt;&amp;#34;ip&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt;  &lt;span class=&#34;chroma-p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;		&lt;span class=&#34;chroma-p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;	&lt;span class=&#34;chroma-p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Over the past week I’ve been setting up a private &lt;a href=&#34;https://forgejo.org/&#34;&gt;Forgejo&lt;/a&gt; instance behind Tailscale, complete with an actions runner that runs things using a docker-in-docker container. I didn’t want the runner knowing anything about Tailscale, so I had it configured to speak to Forgejo direct over HTTP (&lt;code&gt;forgejo:3000&lt;/code&gt;) instead of using full Tailscale HTTPS URL that I use when accessing it (&lt;code&gt;http://git.example-net.ts.net/&lt;/code&gt;)&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:1&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Everything was going fine, until I forgot to do that translation… and it worked. My Forgejo action runners could access anything on my tailnet. I run several things on Tailscale that just have authentication turned off, on the basis that only authorised devices can access them. Things like the admin interface for this website. I definitely didn’t intend for any workflow I run on my git server to have access to edit my website!&lt;/p&gt;
&lt;p&gt;This wasn’t just limited to Forgejo, either. Any docker container I was running could access the tailnet. In hindsight it’s fairly obvious why: the host is running Tailscale, connected as a node tagged with my &lt;code&gt;server&lt;/code&gt; tag. That creates a &lt;code&gt;tailscale0&lt;/code&gt; interface, and automagically sets up iptables rules to route Tailscale traffic over the interface. Docker also automagically sets up iptables rules to bridge traffic, and apparently these two sets of rules interact in such a way that traffic from Docker containers is allowed to route via the &lt;code&gt;tailscale0&lt;/code&gt; interface.&lt;/p&gt;
&lt;p&gt;I say it’s fairly obvious in hindsight — there’s no reason why Docker would special case any particular host interface after all — but it still feels pretty surprising. Because both bits of software inject their own iptables rules, I never really had a good mental model for how they interact. The host Tailscale node was a completely separate building block to Docker. It would be a pain to use either of them if they didn’t do these rules, but it’s also one of the reasons I don’t really like “magical” things&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:2&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;There are lots of ways to fix this, but none of them feel particularly great. You can configure both Tailscale and Docker to not automatically fiddle with iptables and handle the rules yourself, but I really hate dealing with iptables&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:3&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;. As a stopgap I did hold my nose and add some iptables rules to drop traffic to the &lt;code&gt;tailscale0&lt;/code&gt; interface if it originated from the IP ranges that Docker was configured to use:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;iptables -I DOCKER-USER -s 192.168.0.0/16 -o tailscale0 -j DROP
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;iptables -I DOCKER-USER -s 172.17.0.0/12 -o tailscale0 -j DROP
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;DOCKER-USER&lt;/code&gt; chain is a nice little escape hatch; it comes before the main auto-generated &lt;code&gt;DOCKER&lt;/code&gt; chain, and Docker leaves the rules in it alone.&lt;/p&gt;
&lt;p&gt;So I added these rules, and felt pretty good about myself, and then… everything started breaking in weird ways. After some debugging I realised the problem was DNS&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:4&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;. The server’s DNS resolver is &lt;code&gt;100.100.100.100&lt;/code&gt;, a special Tailscale address. This is so that it can resolve tailnet hostnames via &lt;a href=&#34;https://tailscale.com/kb/1081/magicdns&#34;&gt;MagicDNS&lt;/a&gt; (and so I don’t have to configure my custom DNS servers manually on each device; Tailscale does it for me). My new iptables rules inadvertently dropped all the DNS packets coming from docker containers. D’oh.&lt;/p&gt;
&lt;p&gt;Obviously the solution here is to double down and add MORE iptables rules:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;iptables -I DOCKER-USER -s 192.168.0.0/16 -d 100.100.100.100 -o tailscale0 -j ACCEPT
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;iptables -I DOCKER-USER -s 172.17.0.0/12 -d 100.100.100.100 -o tailscale0 -j ACCEPT
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This got DNS working again, but it all feels a bit gross. Not least because to actually make these persist you need to use &lt;code&gt;iptables-save&lt;/code&gt;, and then you also get all the rules that both Docker and Tailscale have inserted. I came across &lt;a href=&#34;https://github.com/giangi/iptables-docker-filter&#34;&gt;a script&lt;/a&gt; to filter out the Docker ones, but… yuck&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:5&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;I realised a cleaner idea might just be to lock down what &lt;code&gt;server&lt;/code&gt; tagged nodes could access within Tailscale itself. That way I can avoid touching iptables at all. It’s handy to be able to &lt;code&gt;curl&lt;/code&gt; services from the host when debugging, but it’s not really necessary. So I removed that access, and… stuff broke again. I’m using my Forgejo instance as a registry for some of the docker images I run, so the docker daemon needs to be able to reach it. I ended up making a new tag for &lt;code&gt;infrastructure&lt;/code&gt;, which can be accessed from &lt;code&gt;server&lt;/code&gt; devices. This does still allow all the docker containers to reach Forgejo, but I already have it set up with appropriate access controls and public/private repository splits. Forgejo is a service designed to run publicly, so this seems a reasonable trade-off for convenience. I used the &lt;code&gt;tests&lt;/code&gt; feature of Tailscale’s ACL config to make sure I’d got the rules right:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;	&lt;span class=&#34;chroma-nt&#34;&gt;&amp;#34;tests&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;		&lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;			&lt;span class=&#34;chroma-c1&#34;&gt;// Servers can only access infrastructure
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;			&lt;span class=&#34;chroma-nt&#34;&gt;&amp;#34;src&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt;   &lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;tag:server&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;			&lt;span class=&#34;chroma-nt&#34;&gt;&amp;#34;proto&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;tcp&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;			&lt;span class=&#34;chroma-nt&#34;&gt;&amp;#34;allow&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;				&lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;tag:infrastructure:8080&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;			&lt;span class=&#34;chroma-p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;			&lt;span class=&#34;chroma-nt&#34;&gt;&amp;#34;deny&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;				&lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;100.84.16.43:8080&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;				&lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;me@example.com:8080&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;				&lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;tag:server:8080&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;				&lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;tag:app:8080&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;				&lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;tag:integration:8080&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;			&lt;span class=&#34;chroma-p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;		&lt;span class=&#34;chroma-p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;	&lt;span class=&#34;chroma-p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The whole situation still feels a bit messy. If I ever get around to switching to nftables I might loop back and manually craft some rules for routing traffic, instead of leaving Tailscale and Docker to do their own thing.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;I discovered later on that logging into the container registry at &lt;code&gt;forgejo:3000&lt;/code&gt; actually issued a redirect to &lt;code&gt;https://git.example-net.ts.net/&lt;/code&gt; so this was all basically for naught… &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:1&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;I didn’t use Tailscale’s MagicDNS for a long time just because the word “magic” put me off. Only when I eventually got around to learning how it worked, and seeing that it wasn’t really that magical under the hood, did I change my mind. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:2&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;nftables seems far better in lots of ways, but I can’t really be bothered migrating. Maybe next time I reimage the server for whatever reason… &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:3&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;It’s always DNS… &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:4&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34;&gt;
&lt;p&gt;Again, nftables would almost certainly help here. It actually has (gasp) configuration files. But again, I really didn’t want to spend the time migrating. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:5&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</content>
    </entry>
    <entry>
        <title>Home Automation Without the Megacorps</title>
        <link href="https://chameth.com/home-automation-without-megacorps/"/>
        <updated>2025-05-21T00:00:00Z</updated>
        <id>https://chameth.com/home-automation-without-megacorps/</id>
        <content xml:lang="en" type="html">&lt;p&gt;I first experimented with home automation in 2016, by picking up a Samsung
“SmartThings” hub. It was terrible. The UI to configure things was slow and
clunky, firmware updates were applied whether you wanted them or not, and
everything stopped working if their cloud services stopped. You were also locked
into whatever integrations they deigned to support, of course. After that broke
for the umpteenth time I scaled back and for years the closest I got to home
automation was a couple of Hue bulbs.&lt;/p&gt;
&lt;p&gt;Recently I’ve been building it out again, though. This time using off-the-shelf
components that interop using Zigbee, open-source software, and some code I
wrote myself. It’s great; it runs entirely locally and has had basically zero
downtime. The Zigbee ecosystem lets me integrate all sorts of things without
having to spend lots of money on “smart” alternatives. I think I’ve spent less
on this incarnation than I did on the original SmartThings hub all those years
ago (even without adjusting for inflation!).&lt;/p&gt;
&lt;h3 id=&#34;my-current-setup&#34;&gt;My current setup&lt;/h3&gt;
&lt;p&gt;I run everything on a Raspberry Pi 4, with a Sonoff USB Zigbee adapter based
on the CC2652P chipset&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:1&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. Interfacing with the Zigbee stack is handled by
&lt;a href=&#34;https://www.zigbee2mqtt.io/&#34;&gt;zigbee2mqtt&lt;/a&gt; (z2m for short), an open-source project that
basically bridges your devices to an MQTT broker&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:2&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. When a device reports some
data, it will send a new message over MQTT; when you want to make a device do
something you just post a message back. It’s incredibly lightweight, but
supports a huge array of devices out of the box. And as it’s just using MQTT,
it’s trivial to integrate with other software or build on top of.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;I know a lot of people building out this kind of home automation use
&lt;a href=&#34;https://www.home-assistant.io/&#34;&gt;Home Assistant&lt;/a&gt;, but I don’t get on with it
terribly well. It felt incredibly sluggish, and the entire project is just a
bit… much. I want a binary or package or docker image I can just run, not an
entire operating system. That’s not how computers are meant to work! In contrast,
z2m is simple to set up, super light weight and responsive.&lt;/p&gt;
&lt;p&gt;Anyway. z2m exposes Zigbee devices over MQTT, so I wrote some code in Go to
connect to the MQTT broker, and listen to the messages. It’s grown a bit beyond
this now, and I’m skipping some boring bits like error handling and JSON
parsing, but at first I had something like:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;message&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;topic&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nf&#34;&gt;ReadSlices&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;chroma-k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nb&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;topic&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-s&#34;&gt;&amp;#34;zigbee2mqtt/desk-button&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;chroma-k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;strings&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nf&#34;&gt;Contains&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;message&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-s&#34;&gt;&amp;#34;single&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nf&#34;&gt;Publish&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;chroma-nb&#34;&gt;byte&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-s&#34;&gt;&amp;#34;{\&amp;#34;state\&amp;#34;: \&amp;#34;TOGGLE\&amp;#34;}&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-s&#34;&gt;&amp;#34;zigbee2mqtt/room-lights/set&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I think I actually made this harder to read by trying to simplify it here, but
hopefully you can follow that it was relatively straight forward to listen
for a particular action to happen (in this case me “single”-pressing on the
device called “desk-button”) and then make another device do something in
response (toggling the state of the “room-lights” device).&lt;/p&gt;
&lt;p&gt;So what actually are these devices? At present in z2m I have the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Some generic buttons&lt;/li&gt;
&lt;li&gt;An air quality sensor (USB powered), and a separate temperature sensor (battery powered)&lt;/li&gt;
&lt;li&gt;A light switch&lt;/li&gt;
&lt;li&gt;Some 240V relay modules, that I use to turn non-smart devices on and off&lt;/li&gt;
&lt;li&gt;Some USB relay modules, for the same purpose&lt;/li&gt;
&lt;li&gt;Some “smart” plugs that I mostly use to monitor power usage&lt;/li&gt;
&lt;li&gt;A motorised blind roller&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I think all of these are either Tuya or Moes branded, and were all sourced from
AliExpress. You can get the same things from Amazon or elsewhere, but they tend
to be 3-4x more expensive for the same product.&lt;/p&gt;
&lt;p&gt;One of the cool things about Zigbee devices is that the powered ones work
together to create a mesh network, so you don’t have to worry about network
repeaters or signal strength like you do with Wi-Fi networks. z2m even makes
a map showing the connections:&lt;/p&gt;
&lt;figure class=&#34;image full&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/home-automation-without-megacorps/zigbee-map.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/home-automation-without-megacorps/zigbee-map.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/home-automation-without-megacorps/zigbee-map.png&#34; alt=&#34;A &amp;#34;map&amp;#34; showing how Zigbee devices connect to one another&#34; loading=&#34;lazy&#34; width=&#34;1093&#34; height=&#34;651&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;The network map produced by z2m. You can see how all the powered devices form a mesh that the lower power ones can connect to.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&#34;some-interesting-automations&#34;&gt;Some interesting automations&lt;/h3&gt;
&lt;figure class=&#34;image right&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/home-automation-without-megacorps/relay.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/home-automation-without-megacorps/relay.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/home-automation-without-megacorps/relay.jpg&#34; alt=&#34;A Zigbee relay and some wago connectors wired up in a project box&#34; loading=&#34;lazy&#34; width=&#34;325&#34; height=&#34;500&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;A Zigbee relay spliced into the power cable for the fan&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;So what do I actually do with all of this? Some of it is just what I’ll call
“laziness automation”: I want to be able to turn on the lights without getting
up and walking to the light switch. So there’s a Zigbee button on my desk that
does it. Then there are some less manual automations: my blinds are
automatically closed at sunset&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:3&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;, for example. It feels a bit trivial, but it’s
surprisingly nice just not to have to think about that at all.&lt;/p&gt;
&lt;p&gt;Another nice quality of life automation is the light I have above my monitor.
It’s USB powered, and I now have it going through a Zigbee USB relay that lets
it be toggled on and off. Then I have a small agent running on my desktop that
turns the light on when the computer is unlocked, and off when it locks or
shuts down. I keep meaning to make a “film mode” that detects when I’m watching
a film, and turning all the lights and blinds down, but I haven’t got around
to it yet.&lt;/p&gt;
&lt;p&gt;The most complex automation is probably for a window fan. It’s not smart in
any way, so I cut the power cable and inserted a Zigbee relay. The whole thing
is housed in a little project box to keep it secure. The relay basically acts
as a switch: the live wire leading to the fan runs to the “normally open”&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:4&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;
contact, while the upstream power goes to “common”. When the relay is told to
turn the fan on, the two get connected and the fan gets power. Oh, it’s also
not actually a window fan, but I designed and 3D printed some adapters so that
it sits right in the window.&lt;/p&gt;
&lt;p&gt;So now we have a window fan that can be turned off and on automatically. But I don’t
want to have to press a button to do that. I’m lazy, remember? Instead, I made
it so that the Go code constantly monitors the temperature reported by the air
quality sensor, and queries the free &lt;a href=&#34;https://openweathermap.org/&#34;&gt;OpenWeatherMap API&lt;/a&gt;
to get the rough temperature outside. It can then turn the fan on if the room is
too hot, and outside is cool enough to make a difference (there’s no point in
blowing hotter air in!).&lt;/p&gt;
&lt;figure class=&#34;image left&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/home-automation-without-megacorps/fan-graph.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/home-automation-without-megacorps/fan-graph.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/home-automation-without-megacorps/fan-graph.png&#34; alt=&#34;A graph of room temperature, slowly rising to 23.5 degrees, then sharply dropping to 19, rising to 20, dropping to 19 again, etc&#34; loading=&#34;lazy&#34; width=&#34;540&#34; height=&#34;500&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;A graph of room temperature, showing the effect of the fan being turned on and off&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I don’t want it to do that all the time though. The room doesn’t need to be
cooled if I’m not around. There are various Zigbee presence sensors you can get,
but the cheaper ones seem to be of dubious quality. Instead, I wrote some code
to guess whether I’m present. It uses the state of the monitor light
as a proxy for “is the computer in use”, and then makes some guesses based on
the last time the computer was used and the current time (if I turn the computer
off at midnight it probably means I’m going to sleep; if I turn it off at 2pm
it probably means I’m going out somewhere).&lt;/p&gt;
&lt;p&gt;You can see in the graph that the fan has a decent impact on temperature. I
coded it to cool the room to 19 degrees, but then not turn on again until it
was above 20. This prevents it flip-flopping on and off constantly. You can
clearly see the pattern in the graph, as it actively cools and then slowly
the room warms back up. This arrangement is much better than being woken up at
5am because it’s painfully cold. Trust me.&lt;/p&gt;
&lt;h3 id=&#34;bonuses-metrics-and-3d-printer&#34;&gt;Bonuses: metrics and 3D printer&lt;/h3&gt;
&lt;p&gt;One of the things my custom Go code does is collate all the various stats
reported by the Zigbee devices, and inserts them into a &lt;a href=&#34;https://victoriametrics.com/&#34;&gt;VictoriaMetrics&lt;/a&gt;
database. I originally hosted this on the Pi itself, and it performed fine, but
I’ve since moved it onto a server so that I can use it for some other things
as well.&lt;/p&gt;
&lt;p&gt;I set up Grafana to point to VM, and can create dashboards showing power usage,
what devices are turned on when, and a bunch of environmental conditions. This
also makes it easy to spot how good the data coming from the devices are. For
example, the air quality sensor reports a figure for the amount of Carbon Dioxide
in the air, as well as the amount of Volatile Organic Compounds (VOCs). The
graphs are basically identical, but on a different scale. It turns out this
particular device has no actual way of detecting CO₂, so it just “calculates”
it from the VOCs figure. Useful to know if you want to actually use that data.&lt;/p&gt;
&lt;p&gt;Recently I came across &lt;a href=&#34;https://github.com/torbenconto/bambulabs_api&#34;&gt;a go library for interacting with Bambu Lab printers&lt;/a&gt;,
so I’ve also hooked that into my automation. It exports metrics about the
printer, so I now have a way of seeing what’s going on when I’m not physically
present&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:5&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;. I even added a HTTP endpoint (exposed over &lt;a href=&#34;https://tailscale.com/&#34;&gt;Tailscale&lt;/a&gt;) that
shows pictures from the built-in camera.&lt;/p&gt;
&lt;p&gt;On the automation front, I’ve made it automatically turn off the printer’s light
when it finishes a print, and also added push notifications via &lt;a href=&#34;https://pushover.net/&#34;&gt;PushOver&lt;/a&gt;
whenever the state changes. No longer will I be sat in another room blissfully
unaware it ran out of filament seconds after I walked out the door!&lt;/p&gt;
&lt;h3 id=&#34;was-it-worth-it&#34;&gt;Was it worth it?&lt;/h3&gt;
&lt;p&gt;These days you could probably just buy some kind of ‘smart home’ hub that works
well enough, and do most of what I’ve done with a lot less effort and no coding.
I’m still of the opinion that for something so essentially &lt;em&gt;local&lt;/em&gt;, it should
itself be managed entirely locally. I don’t trust companies like Google or
Amazon not to kill their products, or change or remove an API I rely on.&lt;/p&gt;
&lt;p&gt;Obviously the “Not Invented Here” approach of coding everything yourself doesn’t
suit everyone, but as someone who enjoys coding and enjoys having things work
&lt;em&gt;just so&lt;/em&gt; it works very well for me.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;It turns out the chipset is important. I initially used a similar Sonoff
dongle that used a CC2531 chipset, and its performance wasn’t great. It often
failed to pair with new devices, and dropped links to existing ones. The
&lt;a href=&#34;https://www.zigbee2mqtt.io/advanced/zigbee/02_improve_network_range_and_stability.html&#34;&gt;zigbee2mqtt docs&lt;/a&gt;
do explicitly advise against the CC2531 chips for that reason. At the time they
recommended CC2652Ps, so that’s what I went with. If you’re starting new I’d
go with whatever their latest recommendation was. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:1&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;MQTT is basically a network protocol for publishing and subscribing to
arbitrary messages. A broker is the thing that sits in the middle and routes
the messages. I use &lt;a href=&#34;https://mosquitto.org/&#34;&gt;Mosquitto&lt;/a&gt; but any will do. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:2&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;They originally also re-opened at sunrise but that was a terrible
mistake. Who knew the sun rose so early?! &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:3&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;“open” meaning “there’s a gap so it doesn’t work” not
“open for business”. This confusion in terminology also extends to drawbridges. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:4&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34;&gt;
&lt;p&gt;I use the printer in LAN mode, which means there’s no way to monitor it
from a phone, even if you’re connected to the same network still. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:5&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</content>
    </entry>
    <entry>
        <title>An introduction to containers</title>
        <link href="https://chameth.com/intro-to-containers/"/>
        <updated>2020-03-01T00:00:00Z</updated>
        <id>https://chameth.com/intro-to-containers/</id>
        <content xml:lang="en" type="html">&lt;figure class=&#34;image right&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/intro-to-containers/containers.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/intro-to-containers/containers.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/intro-to-containers/containers.jpg&#34; alt=&#34;Containers in port&#34; loading=&#34;lazy&#34; width=&#34;300&#34; height=&#34;396&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;So. Many. Containers.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I’m a huge fan of (software) containers. Most people I know fall in to one of two camps:
either they also use, and are fans of, containers, or they haven’t yet really figured them
out and view them as some kind of voodoo that they don’t really want or need.&lt;/p&gt;
&lt;p&gt;I’m writing this short guide to explain a little how containers work - and how running
something in a container isn’t really that much different to running it normally -
to hopefully enable more people in that second group to give them a try. It’s aimed at
people who have a fairly good grasp of how Linux works.&lt;/p&gt;
&lt;p&gt;Containers are often mentioned in the same breath as VMs, which is not a helpful comparison
or analogy. Think of containers as standard units of software, much like how
&lt;a href=&#34;https://en.wikipedia.org/wiki/Intermodal_container&#34;&gt;Intermodal containers&lt;/a&gt; are standard
units of freight transport across the world. When a company internationally ships goods in
volume there isn’t a question about how they’re packaged - they go in an intermodal container.
The same container can be deployed on a freight train, a lorry, or a ship. The haulage company
doesn’t need to care what’s in the container because they’re completely standardised.
Likewise, with software containers you don’t really need to care about what’s inside: the
software you’re deploying could be written in Go, Python2, Python3, Bash, PHP, LOLCODE, or
anything&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:1&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3 id=&#34;what-does-a-running-container-look-like&#34;&gt;What does a running container look like?&lt;/h3&gt;
&lt;p&gt;When you run a container, you’re just running a process. In a lot of ways it’s not
very different to what happens if you were to start the same process on the host
computer.&lt;/p&gt;
&lt;p&gt;For example I have a container that just runs &lt;code&gt;cat(1)&lt;/code&gt;. With no arguments, &lt;code&gt;cat&lt;/code&gt;
will read from stdin until it receives an EOF, so it’s handy to test with.
If I run &lt;code&gt;ps a&lt;/code&gt; on my computer, I can see the cat process in amongst everything
else I’m currently running:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    PID TTY      STAT   TIME COMMAND
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;   7199 pts/1    Ss     0:01 /usr/bin/zsh
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt; 323806 pts/0    Ss+    0:00 /bin/cat
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt; 324120 pts/4    R+     0:00 ps a
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;/bin/cat&lt;/code&gt; process is in a container, and the &lt;code&gt;ps a&lt;/code&gt; underneath it is just running
like normal on my desktop. They look very similar, right? If I look under &lt;code&gt;/proc/323806&lt;/code&gt;
I can see all the usual attributes, the same as any other process running.&lt;/p&gt;
&lt;p&gt;If I run &lt;code&gt;ps&lt;/code&gt; in a container, though, it’s a different story:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    PID TTY      STAT   TIME COMMAND
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;      1 pts/0    Rs+    0:00 ps a
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So inside the container it looks like there’s only one process running. It can’t see
anything running “outside” on my desktop. The secret here is that this isn’t a special
container trick: this is just a feature of the Linux kernel called &lt;em&gt;namespacing&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;If we go back to procfs and look at the &lt;code&gt;ns/pid&lt;/code&gt; node we can see the process in the
container is in a separate PID (process ID) namespace to the one on my desktop:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;# readlink /proc/323806/ns/pid  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;pid:[4026534564]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;# readlink /proc/7199/ns/pid
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;pid:[4026531836]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Almost all&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:2&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; processes running ’normally’ on my desktop have the same PID namespace,
whereas each container gets their own by default. PID namespaces are hierarchical:
a new process is assigned a PID in its own namespace, and the parent namespace, and
the grandparent namespace, and so on. That’s why I can see the process running in
the container from my normal shell - the container’s namespace is a child of the
main namespace all of my desktop software is running in.&lt;/p&gt;
&lt;p&gt;Linux supports - and container software makes use of - a bunch of other namespaces too:
mount points, network, UTS&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:3&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;, cgroups, and more. These all play a part in isolating
a container from the system it is running on.&lt;/p&gt;
&lt;p&gt;You can manually run a process with &lt;code&gt;unshare(1)&lt;/code&gt; to “unshare” some namespaces from
the parent process. For example if I run &lt;code&gt;unshare -fp --mount-proc ps a&lt;/code&gt;, it
looks very similar to running ps instead the container:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    PID TTY      STAT   TIME COMMAND
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;      1 pts/4    R+     0:00 ps a
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So: a process running inside a container is just a heavily namespaced process running
otherwise normally in the operating system. No voodoo magic here!&lt;/p&gt;
&lt;h3 id=&#34;what-about-the-filesystem-what-are-images&#34;&gt;What about the filesystem? What are ‘images’?&lt;/h3&gt;
&lt;p&gt;Containers run in their own mount namespace meaning mount points can be different
inside the container to those on the host. This means the container can have a different
&lt;code&gt;/&lt;/code&gt; mounted to the host, effectively giving it its own filesystem.&lt;/p&gt;
&lt;p&gt;The root filesystem of the container is defined in the container’s image. If I
use Docker to run a container using the Ubuntu image (&lt;code&gt;docker run ubuntu&lt;/code&gt;), the root
filesystem inside that container will resemble a minimal ubuntu install. Note
that this is just the filesystem: the container doesn’t have its own kernel.&lt;/p&gt;
&lt;p&gt;You might be thinking that sounds pretty inefficient. Downloading Ubuntu is definitely
not instant, and doing it for every application you run would be insane! Quite.
Containers solve this by using filesystem &lt;strong&gt;layers&lt;/strong&gt;. These are stacked on top of
one another to create the final filesystem. Each layer can be retrieved and
cached independently of all others.&lt;/p&gt;
&lt;p&gt;Say (for simplicity) that the Ubuntu image is a single layer. If I run one container
with that image, then the layer will be downloaded and cached once. If I run three
hundred containers with that image then the layer will be downloaded and cached once.
Even better, if I use another image that’s based on Ubuntu but adds some software
on top, only the “on top” layer will be downloaded if I already have the relevant
“Ubuntu” layer cached.&lt;/p&gt;
&lt;p&gt;If all the layers are cached, what happens when you change a file? This is dealt
with using the copy-on-write technique: when you modify a file it is copied from
the source layer and the changes are only made in a new layer. This is handled
by the OverlayFS filesystem which is part of the mainline kernel.&lt;/p&gt;
&lt;p&gt;When a container is running, changes made to its filesystem are temporary, and
do not persist across container restarts. To persist data - or introduce new
data to a container - you can mount volumes. How this works varies depending
on how you’re running your container, but at the basic level it is pretty much
the same as bind-mounting (&lt;code&gt;mount -o bind&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;You may be familiar with using &lt;code&gt;chroots&lt;/code&gt; to change the apparent root directory
of processes, perhaps with full-blown “jails” built on top. Containers offer
much better isolation thanks to the use of namespaces. Instead of being
constrained to a portion of the host’s filesystem, they don’t even have
it mounted! Containers also get to specify their environment - if they
expect in certain places, for example - instead of the sysadmin having to
manually set up the chroot. Finally, containers offer much more fine-grained
control over what processes can do (if you want it), and allow much more
advanced use-cases such as inter-container networking.&lt;/p&gt;
&lt;p&gt;Images and filesystems employ a little magic to ensure that layers are reusable
and cacheable, but again there’s nothing terribly special about them: a container
has a filesystem that appears to it to work the same way as a filesystem on
the host, and it’s using a standard filesystem shipped with the kernel.&lt;/p&gt;
&lt;h3 id=&#34;how-about-networking&#34;&gt;How about networking?&lt;/h3&gt;
&lt;p&gt;Again, networking is namespaced, so a container has its own network stack,
its own virtual network interface, its own IP address and so on. How that network
interacts with your real network depends on how you’re running the container.
Docker, for example, can add iptables rules to NAT traffic between containers&amp;#39;
networks and the outside world.&lt;/p&gt;
&lt;p&gt;Containers can generally be connected into networks, and can communicate
amongst themselves without the traffic actually leaving the host machine.
This allows you to, for example, run a SQL database and connect it to
a web application without ever exposing the database to the outside world.
Moreover, as well as being isolated from the outside world, it’s isolated
from other containers in other networks. If one of your applications has a crazy bug
or is compromised, this significantly limits the damage it can do.&lt;/p&gt;
&lt;p&gt;You have to explicitly opt in to “publishing” ports from a container, which
exposes them to the outside world (either directly, or via a load balancer
or some other middle-man, depending on how you’re running the container).
This means you can pick and chose how the outside world sees the app you’re
deploying: if it’s a web service that listens on both port 443 and port 80,
you can chose to only expose the encrypted port.&lt;/p&gt;
&lt;p&gt;If you run some containers and create some networks, you can see the
interfaces and bridges on the host:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;$ ip l
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;6: br-2405a8cc0445: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc noqueue state UP mode DEFAULT group default 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    link/ether 02:42:3e:fa:23:62 brd ff:ff:ff:ff:ff:ff
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;8: veth8ed0735@if7: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc noqueue master br-2405a8cc0445 state UP mode DEFAULT group default 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    link/ether b2:c1:5d:55:26:9b brd ff:ff:ff:ff:ff:ff link-netnsid 2
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;10: veth541d84b@if9: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc noqueue master br-9d7bc4024c1a state UP mode DEFAULT group default 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    link/ether 86:aa:5f:ee:da:1a brd ff:ff:ff:ff:ff:ff link-netnsid 1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This shows a bridge and two virtual NICs, just the same as if you’d manually created
them (albeit with seemingly random names). So: as before, nothing special here.&lt;/p&gt;
&lt;h3 id=&#34;docker-compose-k8s-floccinaucinihilipilification&#34;&gt;Docker? Compose? K8s? Floccinaucinihilipilification?&lt;/h3&gt;
&lt;p&gt;(OK, Floccinaucinihilipilification isn’t actually a container technology, as far
as I’m aware.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Docker&lt;/strong&gt; is the most popular &lt;em&gt;container engine&lt;/em&gt; - that is, the bit of software
that actually runs containers. It’s responsible for setting up all those namespaces
we found, downloading and caching image layers, and actually starting and stopping
the processes. Docker runs as a system-wide daemon - when you run a command like
&lt;code&gt;docker run ubuntu&lt;/code&gt; it actually just instructs the daemon to do the work.&lt;/p&gt;
&lt;p&gt;There are several alternatives to Docker for running containers; one interesting one
is &lt;a href=&#34;https://podman.io/&#34;&gt;Podman&lt;/a&gt; which runs containers without a daemon. Container
engines have all standardised around the same image format looked after by the
&lt;a href=&#34;https://www.opencontainers.org/&#34;&gt;Open Container Initiative&lt;/a&gt;, so an image you build
in Docker can be used in Podman, or pretty much any other engine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Docker compose&lt;/strong&gt; is a tool for defining and running multiple-container applications.
I mentioned earlier running a database alongside a webapp - in practice to do this
you’re going to have to configure a network for them, configure a mount point for
the database to persist its data on, pass credentials in to both the database and
the application, and so on. Doing all that by hand is tedious and error prone.&lt;/p&gt;
&lt;p&gt;Docker compose lets you write “compose files”, which are simple yaml descriptions
of the containers you wish to run, their properties, and details about any volumes
or networks you may want. Out of the box, docker-compose will create a default
network for each compose file you run so the containers within it can communicate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kubernetes&lt;/strong&gt;  is a container &lt;em&gt;orchestrator&lt;/em&gt;, designed to automate deployment
and management of large numbers of containers. It works with Docker under the
hood, but provides a huge amount of tooling on top to allow you to deploy
applications and manage their dependencies. It runs across multiple physical
(or virtual) machines (while still allowing containers to communicate privately),
and can support massive workloads by scaling out services (running multiple copies
of a container on different hosts) and load balancing. Kubernetes is sometimes
shorted to &lt;strong&gt;k8s&lt;/strong&gt; (as in &lt;code&gt;K&lt;/code&gt; - 8 elided letters - &lt;code&gt;s&lt;/code&gt;) because computer people
don’t like long words.&lt;/p&gt;
&lt;h3 id=&#34;ok-they-make-sense-now-but-why-bother&#34;&gt;OK, they make sense now. But why bother?&lt;/h3&gt;
&lt;p&gt;Hopefully if you’ve read this far you’ve already picked up on some of the potential
benefits, but this is my personal list:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Isolation&lt;/strong&gt;. If I run some software in a container, there is very little it
can do to upset me. It’s not going to leave bits of itself all over my filesystem,
it can’t steal all of the secrets in my home directory, I can even limit its CPU
and memory resources if I want. If I decide to stop running it, I just delete
the container and it is completely gone: no trace remains.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ease of use&lt;/strong&gt;. If you give me a container image I have a very good idea of how
to run it already. I might need to do some minor configuration to expose ports
or mount volumes, but there’s no question about how to run it, how to make it
automatically start, and there’s no “installation” procedure. If I want to
then swap it with an alternative (say, move from MySQL to MariaDB), it’s
potentially just a case of changing the name of the image I pull.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dependencies included&lt;/strong&gt;. Containers just &lt;em&gt;run&lt;/em&gt;. Python 2 software includes
Python 2 and just work. Python 3 software includes Python 3 and just work.
I don’t have a massive headache trying to run both at the same time, because
they take care of their own messes. Similarly I’m not going to have to install
&lt;code&gt;npm&lt;/code&gt; or &lt;code&gt;cargo&lt;/code&gt; or &lt;code&gt;composer&lt;/code&gt; to pull in dependencies for an application:
that’s going to have been done in the build process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reproducibility&lt;/strong&gt;. As a fallout from having dependencies included and being
isolated from everything else, containers give you amazing reproducibility.
If it “works on your machine” in a container, it’ll almost certainly work in
production because it’s the exact same environment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Standardisation&lt;/strong&gt;. At the start of this article I called containers standard
units of software. One of my favourite advantages of containers is that you
basically get an API to list all the software you’re running. Most container
engines let you supply labels attached to containers as well, so you can add
your own annotations. I use this to annotate services which expose HTTP
endpoints, and I have a tool that automatically generates SSL certificates
for them and configures haproxy to route traffic to them. I can’t imagine
how I’d do this without containers - I imagine it’d involve a lot of
manual work.&lt;/p&gt;
&lt;p&gt;Hopefully this has helped demystify containers a little. If you feel like
I’ve missed something important out, or I’ve left you more confused than
when you started, feel free to drop me a note using the feedback form below.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;OK, maybe you should care if you’re deploying something written in crazy languages like PHP. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:1&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;Some multi-process apps, such as web browsers, are starting to use namespaces to enhance security,
as do certain package systems like Flatpak &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:2&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;“Unix timesharing system”; in practice this means having a separate hostname &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:3&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</content>
    </entry>
    <entry>
        <title>Debugging beyond the debugger</title>
        <link href="https://chameth.com/debugging-beyond-the-debugger/"/>
        <updated>2019-05-08T00:00:00Z</updated>
        <id>https://chameth.com/debugging-beyond-the-debugger/</id>
        <content xml:lang="en" type="html">&lt;figure class=&#34;image right&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/debugging-beyond-the-debugger/tools.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/debugging-beyond-the-debugger/tools.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/debugging-beyond-the-debugger/tools.jpg&#34; alt=&#34;Collection of tools hanging on a wall&#34; loading=&#34;lazy&#34; width=&#34;300&#34; height=&#34;396&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;Real-life debugging tools&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Most programming — and sysadmin — problems can be debugged in a
fairly straight forward manner using logs, print statements,
educated guesses, or an actual debugger. Sometimes, though, the
problem is more elusive. There’s a wider box of tricks that can
be employed in these cases but I’ve not managed to find a nice
overview of them, so here’s mine. I’m mainly focusing on Linux
and similar systems, but there tend to be alternatives available
for other Operating Systems or VMs if you seek them out.&lt;/p&gt;
&lt;h3 id=&#34;networking&#34;&gt;Networking&lt;/h3&gt;
&lt;h4 id=&#34;tcpdump&#34;&gt;tcpdump&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;tcpdump&lt;/code&gt; prints out descriptions of packets on a network interface. You can
apply filters to limit which packets are displayed, chose to dump the entire
content of the packet, and so forth.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;Typical usage might look something like:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;# tcpdump -nSi eth0 port 80
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;16:03:35.577781 IP6 2001:db8::1.54742 &amp;gt; 2001:db8::2.80: Flags [S], seq 2815779044, win 64800, options [mss 1440,sackOK,TS val 2378811665 ecr 0,nop,wscale 7], length 0
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;16:03:35.586853 IP6 2001:db8::2.80 &amp;gt; 2001:db8::1.54742: Flags [S.], seq 1522609102, ack 2815779045, win 28560, options [mss 1440,sackOK,TS val 3063610173 ecr 2378811665,nop,wscale 7], length 0
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;16:03:35.586877 IP6 2001:db8::1.54742 &amp;gt; 2001:db8::2.80: Flags [.], ack 1522609103, win 507, options [nop,nop,TS val 2378811674 ecr 3063610173], length 0
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;16:03:35.620678 IP6 2001:db8::1.54742 &amp;gt; 2001:db8::2.80: Flags [P.], seq 2815779045:2815779399, ack 1522609103, win 507, options [nop,nop,TS val 2378811708 ecr 3063610173], length 354: HTTP: GET / HTTP/1.1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here you can see the start of a plaintext HTTP request: the three-way
handshake as the TCP connection is established followed by a GET request.
Even if the data is encrypted as it will be in most cases, it’s often useful
to see the “shape” of the transmissions: did the client start sending data
when it connected, did the server ever respond, etc.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://danielmiessler.com/study/tcpdump/&#34;&gt;Daniel Miessler has a good tutorial on tcpdump&lt;/a&gt;
if you’re not familiar with it and don’t want to jump straight into the man
page.&lt;/p&gt;
&lt;h5 id=&#34;-with-docker&#34;&gt;… with Docker&lt;/h5&gt;
&lt;p&gt;Docker sets up separate network namespaces for each container. To see the
traffic across the interfaces of a single container you can &lt;code&gt;nsenter&lt;/code&gt; the
container’s network namespace:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;# nsenter -t $(docker inspect --format &amp;#39;{{.State.Pid}}&amp;#39; my_container) -n tcpdump -nS port 80
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This retrieves the PID for the container, and tells &lt;code&gt;nsenter&lt;/code&gt; to enter the
network (&lt;code&gt;-n&lt;/code&gt;) namespace from the given target (&lt;code&gt;-t&lt;/code&gt;) PID, and then run the
given command (in this case &lt;code&gt;tcpdump ...&lt;/code&gt;).&lt;/p&gt;
&lt;h4 id=&#34;openssl-s-client--s-server&#34;&gt;openssl s_client / s_server&lt;/h4&gt;
&lt;p&gt;When a connection is using TLS it’s often useful to try connecting to the
server and see what certificate it presents, algorithms it negotiates, and
so forth. OpenSSL offers two useful subcommands which can help with this:
&lt;code&gt;s_client&lt;/code&gt; for connecting as a client, and &lt;code&gt;s_server&lt;/code&gt; for listening to
connections.&lt;/p&gt;
&lt;p&gt;For example, using &lt;code&gt;s_client&lt;/code&gt; to connect to &lt;code&gt;google.com&lt;/code&gt; on the standard
HTTPS port shows us details about the server cert and its verification
status:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;$ openssl s_client -connect google.com:443
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;CONNECTED(00000003)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;depth=2 OU = GlobalSign Root CA - R2, O = GlobalSign, CN = GlobalSign
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;verify return:1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;depth=1 C = US, O = Google Trust Services, CN = Google Internet Authority G3
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;verify return:1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;depth=0 C = US, ST = California, L = Mountain View, O = Google LLC, CN = *.google.com
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;verify return:1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;Certificate chain
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt; 0 s:C = US, ST = California, L = Mountain View, O = Google LLC, CN = *.google.com
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;   i:C = US, O = Google Trust Services, CN = Google Internet Authority G3
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt; 1 s:C = US, O = Google Trust Services, CN = Google Internet Authority G3
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;   i:OU = GlobalSign Root CA - R2, O = GlobalSign, CN = GlobalSign
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;# ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Whereas connecting to my webserver and providing an unknown host in the SNI
field results in an SSL alert 112 (“The server name sent was not recognized”)
and no server certificate is sent:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;$ openssl s_client -connect chameth.com:443 -servername example.com
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;CONNECTED(00000003)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;140384831313024:error:14094458:SSL routines:ssl3_read_bytes:tlsv1 unrecognized name:../ssl/record/rec_layer_s3.c:1536:SSL alert number 112
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;no peer certificate available
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;# ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Often if you hit this kind of alert in an application the exact error will be
lost somewhere in the many layers between the SSL library and the logs, so
being able to directly connect and test can help diagnose a lot of issues.&lt;/p&gt;
&lt;p&gt;Once a connection is established you can read and write plain text and it
will be encrypted and decrypted automatically.&lt;/p&gt;
&lt;h4 id=&#34;java-apps&#34;&gt;Java apps&lt;/h4&gt;
&lt;p&gt;If a Java app is involved in the connection, you can enable a lot of built-in
debugging with a simple JVM property: &lt;code&gt;javax.net.debug&lt;/code&gt;. You can tweak
what exactly gets logged, but the easiest thing to do is just set the property
to &lt;code&gt;all&lt;/code&gt; and you’ll see information about certificate chains, verification,
and packet dumps:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;$ java -Djavax.net.debug=all -jar ....
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;# ...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;found key for : duke
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;chain [0] = [
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;[
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;  Version: V1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;  Subject: CN=Duke, OU=Java Software, O=&amp;#34;Sun Microsystems, Inc.&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;  L=Cupertino, ST=CA, C=US
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;# ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;More information about Java’s debugging options is available on
&lt;a href=&#34;https://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/ReadDebug.html&#34;&gt;docs.oracle.com&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;thread-and-core-dumps&#34;&gt;Thread and core dumps&lt;/h3&gt;
&lt;p&gt;Higher-level languages frequently provide an interactive way to dump the
current execution state of all of their threads (a “thread dump”). This
is useful to spot deadlocks, some types of race conditions, and as a
quick and dirty method of investigating hangs or excessive CPU usage.&lt;/p&gt;
&lt;p&gt;With both Java and Go applications you can send a QUIT signal to have a
thread dump printed out; Go applications will quit after doing so, Java
ones will carry on running. At most terminals you can hit &lt;code&gt;Ctrl&lt;/code&gt; and &lt;code&gt;\&lt;/code&gt; to
send a QUIT signal.&lt;/p&gt;
&lt;p&gt;For Java you can also use the &lt;code&gt;jstack&lt;/code&gt; tool from the JDK to dump threads
by PID; this can be useful if the application is running in the background
or has redirected sysout:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;$ jstack 8321
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;Attaching to process ID 8321, please wait...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;Debugger attached successfully.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;Client compiler detected.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;Thread t@5: (state = BLOCKED)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt; - java.lang.Object.wait(long) @bci=-1107318896 (Interpreted frame)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt; - java.lang.Object.wait(long) @bci=0 (Interpreted frame)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt; - java.lang.ref.ReferenceQueue.remove(long) @bci=44, line=116 (Interpreted frame)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt; - java.lang.ref.ReferenceQueue.remove() @bci=2, line=132 (Interpreted frame)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt; - java.lang.ref.Finalizer$FinalizerThread.run() @bci=3, line=159 (Interpreted frame)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;# ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A core dump provides more complete information about the state of a process,
but is often more complex to interpret. The &lt;code&gt;gcore&lt;/code&gt; utility from GDB will
create a core dump of a process with a given PID. You can then generally
load the core file using your normal debugger, depending on the language
in question.&lt;/p&gt;
&lt;h3 id=&#34;system-calls&#34;&gt;System calls&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;strace&lt;/code&gt; is the swiss army knife for seeing what a process is doing. It
details each system call made by a program (you can filter them down, of
course). For example:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;$ strace -e read curl https://google.com/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;read(3, &amp;#34;\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0&amp;gt;\0\1\0\0\0 \236\0\0\0\0\0\0&amp;#34;..., 832) = 832
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;read(3, &amp;#34;\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0&amp;gt;\0\1\0\0\0P!\0\0\0\0\0\0&amp;#34;..., 832) = 832
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;read(3, &amp;#34;\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0&amp;gt;\0\1\0\0\0\200l\2\0\0\0\0\0&amp;#34;..., 832) = 832
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;read(3, &amp;#34;\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0&amp;gt;\0\1\0\0\0\20Q\0\0\0\0\0\0&amp;#34;..., 832) = 832
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;# ...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;read(3, &amp;#34;\0\0\0\0\0\0\0\4\25\345\366\302\273sE6\365wI\225\321|\3435Z\362\216\372\215\251aO&amp;#34;..., 253) = 253
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&amp;lt;HTML&amp;gt;&amp;lt;HEAD&amp;gt;&amp;lt;meta http-equiv=&amp;#34;content-type&amp;#34; content=&amp;#34;text/html;charset=utf-8&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&amp;lt;TITLE&amp;gt;301 Moved&amp;lt;/TITLE&amp;gt;&amp;lt;/HEAD&amp;gt;&amp;lt;BODY&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&amp;lt;H1&amp;gt;301 Moved&amp;lt;/H1&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;The document has moved
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&amp;lt;A HREF=&amp;#34;https://www.google.com/&amp;#34;&amp;gt;here&amp;lt;/A&amp;gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&amp;lt;/BODY&amp;gt;&amp;lt;/HTML&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;read(3, &amp;#34;\27\3\3\0!&amp;#34;, 5)                = 5
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;# ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&#34;http://www.brendangregg.com/blog/2014-05-12/strace-wow-much-syscall.html&#34;&gt;Brendan Gregg&lt;/a&gt;
has a nice guide on &lt;code&gt;strace&lt;/code&gt; and alternatives.&lt;/p&gt;
&lt;h4 id=&#34;-with-docker-1&#34;&gt;… with docker&lt;/h4&gt;
&lt;p&gt;When the application is running in docker you can usually just &lt;code&gt;strace&lt;/code&gt; it
from the host with the correct PID
(from e.g. &lt;code&gt;docker inspect --format &amp;#39;{{.State.Pid}}&amp;#39; my_container&lt;/code&gt;).
Sometimes you may need to trace the startup of an application though, which is
a bit trickier. Instead you can run a new container using the same PID
namespace as your target, and the permissions needed to &lt;code&gt;strace&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;$ docker run --rm -it --pid=container:my_container \
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;  --net=container:my_container \
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;  --cap-add sys_admin \
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;  --cap-add sys_ptrace \
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;  alpine
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From within the new container you can install strace, and trace any running
program within the target container using &lt;code&gt;strace -p&lt;/code&gt; as normal. To start a
new program you need access to the target container’s filesystem, which you
can get to via &lt;code&gt;/proc/1/root&lt;/code&gt; (PID &lt;code&gt;1&lt;/code&gt; being the main process that docker
started in the target container).&lt;/p&gt;
&lt;h3 id=&#34;files&#34;&gt;Files&lt;/h3&gt;
&lt;p&gt;Sometimes the problem might relate to file access. There are a couple of
straight forward — but nonetheless useful — tools which might help here.
&lt;code&gt;inotifywait&lt;/code&gt; uses the Linux &lt;code&gt;inotify&lt;/code&gt; subsystem to watch files or directories
for operations. For example:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;$ inotifywait -mr site/content
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;Setting up watches.  Beware: since -r was given, this may take a while!
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;Watches established.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;site/content/post/ MODIFY 2019-05-08-debugging-beyond-the-debugger.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;site/content/post/ OPEN 2019-05-08-debugging-beyond-the-debugger.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;site/content/post/ MODIFY 2019-05-08-debugging-beyond-the-debugger.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;site/content/post/ MODIFY 2019-05-08-debugging-beyond-the-debugger.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;site/content/post/ CLOSE_WRITE,CLOSE 2019-05-08-debugging-beyond-the-debugger.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;# ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here the &lt;code&gt;-m&lt;/code&gt; switch makes &lt;code&gt;inotifywait&lt;/code&gt; monitor the files forever (instead
of exiting on the first modification, which is the normal behaviour) and &lt;code&gt;r&lt;/code&gt;
makes it recurse into the directory and monitor each file and subdirectory in
there.&lt;/p&gt;
&lt;p&gt;If you want to see what processes currently have a file open, &lt;code&gt;fuser&lt;/code&gt; is the
go-to tool. For example:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;$ fuser -v /
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;                     USER PID ACCESS COMMAND
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;/:                   root     kernel mount /
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;                     chris      2961 .rc.. systemd
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;                     chris      2986 .r... gdm-x-session
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;                     chris      2994 .r... dbus-daemon
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;                     chris      3001 .r... gnome-session-b
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;# ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;honourable-mentions&#34;&gt;Honourable mentions&lt;/h3&gt;
&lt;p&gt;These aren’t really debugging tools, but I feel it’s worth mentioning as
they often feature somewhere along the debugging-of-weird-problems journey.&lt;/p&gt;
&lt;p&gt;I’ve seen some weird and wonderful problems happen
because a disk is full, so a quick &lt;code&gt;df&lt;/code&gt; early on in the debugging process
never hurts. Some apps may hang, some may corrupt their config, some may
fall over and die; sometimes the manner in which they fail doesn’t obviously
point to a disk space issue.&lt;/p&gt;
&lt;p&gt;Another issue that comes up now and then — especially inside VMs or
other environment that don’t have a decent amount of “noise” happening —
is entropy exhaustion. A quick look at &lt;code&gt;/proc/sys/kernel/random/entropy_avail&lt;/code&gt;
should be enough to confirm that everything is ticking along nicely. If it’s
exceedingly low then you may find that anything involving random number
generation stalls (TLS connections for example).&lt;/p&gt;
</content>
    </entry>
</feed>
