<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Gene Liverman</title>
    <description>The latest articles on Forem by Gene Liverman (@genebean).</description>
    <link>https://forem.com/genebean</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F382055%2Fc2e9909b-a6cc-4c22-a268-a08ad938f98c.png</url>
      <title>Forem: Gene Liverman</title>
      <link>https://forem.com/genebean</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/genebean"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Gene Liverman</dc:creator>
      <pubDate>Mon, 16 Jun 2025 18:01:55 +0000</pubDate>
      <link>https://forem.com/genebean/-315p</link>
      <guid>https://forem.com/genebean/-315p</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/voxpupuli/an-unsupportable-path-3i5h" class="crayons-story__hidden-navigation-link"&gt;An Unsupportable Path&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/voxpupuli"&gt;
            &lt;img alt="Vox Pupuli logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F8361%2Fffdf2650-05aa-49dd-8e98-4db3b8f80f19.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/voxbot" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1282595%2F3f40ef97-8b8d-4a85-84f6-23f797d3c1ca.png" alt="voxbot profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/voxbot" class="crayons-story__secondary fw-medium m:hidden"&gt;
              VoxBot
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                VoxBot
                
              
              &lt;div id="story-author-preview-content-2597953" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/voxbot" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1282595%2F3f40ef97-8b8d-4a85-84f6-23f797d3c1ca.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;VoxBot&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/voxpupuli" class="crayons-story__secondary fw-medium"&gt;Vox Pupuli&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/voxpupuli/an-unsupportable-path-3i5h" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 16 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/voxpupuli/an-unsupportable-path-3i5h" id="article-link-2597953"&gt;
          An Unsupportable Path
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/voxpupuli/an-unsupportable-path-3i5h#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>Proxying Bitcoin Core and LND with Tailscale and Nginx</title>
      <dc:creator>Gene Liverman</dc:creator>
      <pubDate>Sat, 08 Feb 2025 09:30:00 +0000</pubDate>
      <link>https://forem.com/genebean/proxying-bitcoin-core-and-lnd-with-tailscale-and-nginx-331g</link>
      <guid>https://forem.com/genebean/proxying-bitcoin-core-and-lnd-with-tailscale-and-nginx-331g</guid>
      <description>&lt;p&gt;Recently I decided I wanted to run my own Bitcoin and Lightning node and I wanted it to be reachable on the public internet. I didn’t, however, want it to actually reside on the server that has the static public IPv4 and IPv6 addresses available. Thus, a reverse proxy was needed. This turned out to be a pretty simple thing to solve for thanks to the &lt;a href="https://nginx.org/en/docs/stream/ngx_stream_proxy_module.html" rel="noopener noreferrer"&gt;Nginx Stream Proxy module&lt;/a&gt; and &lt;a href="https://tailscale.com/linuxunplugged" rel="noopener noreferrer"&gt;Tailscale&lt;/a&gt;. Here’s the basic architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nginx on a virtual private server (VPS) at &lt;a href="https://hetzner.cloud/?ref=TYk0wCkqSS6T" rel="noopener noreferrer"&gt;Hetzner&lt;/a&gt; listens on ports 8333 &amp;amp; 9735 for TCP connections&lt;/li&gt;
&lt;li&gt;The stream proxy module forwards those connections to &lt;a href="https://bitcoin.org" rel="noopener noreferrer"&gt;bitcoind&lt;/a&gt; and &lt;a href="https://github.com/lightningnetwork/lnd" rel="noopener noreferrer"&gt;lnd&lt;/a&gt; over &lt;a href="https://tailscale.com/linuxunplugged" rel="noopener noreferrer"&gt;Tailscale&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The server running bitcoind and lnd uses the &lt;a href="https://hetzner.cloud/?ref=TYk0wCkqSS6T" rel="noopener noreferrer"&gt;Hetzner&lt;/a&gt; VPS as a Tailscale &lt;a href="https://tailscale.com/kb/1103/exit-nodes" rel="noopener noreferrer"&gt;exit node&lt;/a&gt; so that all outbound traffic is via the VPS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s a technical breakdown of how I make that happen. My configuration is done via &lt;a href="https://nixos.org/" rel="noopener noreferrer"&gt;NixOS&lt;/a&gt; flakes, but the general process would work on anything using Nginx and Tailscale.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; 
  &lt;span class="nv"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"example.com"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;private_btc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"some-host.your-domain.ts.net"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;networking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;firewall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;allowedTCPPorts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="mi"&gt;8333&lt;/span&gt; &lt;span class="c"&gt;# Bitcoin Core&lt;/span&gt;
    &lt;span class="mi"&gt;9735&lt;/span&gt; &lt;span class="c"&gt;# LND&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nv"&gt;services&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;nginx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;streamConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;        server {&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          listen 0.0.0.0:8333;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          listen [::]:8333;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          proxy_pass &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;private_btc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:8333;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;        }&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;        server {&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          listen 0.0.0.0:9735;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          listen [::]:9735;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          proxy_pass &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;private_btc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:9735;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;        }&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c"&gt;# end nginx&lt;/span&gt;
    &lt;span class="nv"&gt;tailscale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;authKeyFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;sops&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;secrets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;tailscale_key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;extraUpFlags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"--advertise-exit-node"&lt;/span&gt;
        &lt;span class="s2"&gt;"--operator"&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="s2"&gt;"--ssh"&lt;/span&gt;
      &lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="nv"&gt;useRoutingFeatures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"both"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c"&gt;# end tailscale&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c"&gt;# end services&lt;/span&gt;

  &lt;span class="nv"&gt;sops&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;age&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;keyFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;home&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.config/sops/age/keys.txt"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;defaultSopsFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;../secrets.yaml&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;secrets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;tailscale_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;restartUnits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"tailscaled-autoconnect.service"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c"&gt;# end sops&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking that down a little:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;networking.firewall.allowedTCPPorts&lt;/code&gt; opens the firewall ports needed for bitcoind and lnd&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;services.nginx&lt;/code&gt; configures two &lt;code&gt;ngx_stream_proxy_module&lt;/code&gt; instances within the &lt;code&gt;streamConfig&lt;/code&gt; section that route traffic to the backend using the dns name from &lt;a href="https://tailscale.com/linuxunplugged" rel="noopener noreferrer"&gt;Tailscale&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;services.tailscale&lt;/code&gt; enables Tailscale on the VPS and configures it as an &lt;a href="https://tailscale.com/kb/1103/exit-nodes" rel="noopener noreferrer"&gt;exit node&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sops&lt;/code&gt; configures &lt;a href="https://getsops.io/" rel="noopener noreferrer"&gt;SOPS&lt;/a&gt; to securely store secrets&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And that’s it on the VPS. For the backend, you could be running a variety of different options from &lt;a href="https://github.com/getumbrel/umbrel" rel="noopener noreferrer"&gt;Umbrel&lt;/a&gt; to &lt;a href="https://nixbitcoin.org/" rel="noopener noreferrer"&gt;Nix Bitcoin&lt;/a&gt; to the services manually configured on a variety of operating systems. Settings those up is best left to a different post, but the keys that relates to this setup are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;that where ever they run uses the VPS as an exit node&lt;/li&gt;
&lt;li&gt;the services listen on for connections incoming via Tailscale&lt;/li&gt;
&lt;li&gt;the services advertise the IP of the VPS as their public address&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Note: the links to &lt;a href="https://hetzner.cloud/?ref=TYk0wCkqSS6T" rel="noopener noreferrer"&gt;Hetzner&lt;/a&gt; and &lt;a href="https://tailscale.com/linuxunplugged" rel="noopener noreferrer"&gt;Tailscale&lt;/a&gt; in this post are referral / affiliate links. The Hetzner one is mine and the Tailscale one is from Jupiter Broadcasting’s &lt;a href="https://www.jupiterbroadcasting.com/show/linux-unplugged/" rel="noopener noreferrer"&gt;Linux Unplugged&lt;/a&gt; podcast. I’ve used the JB link because &lt;a href="https://chrislas.com/" rel="noopener noreferrer"&gt;Chris Fisher&lt;/a&gt;, &lt;a href="https://alex.ktz.me/" rel="noopener noreferrer"&gt;Alex Kretzschmar&lt;/a&gt;, &lt;a href="https://www.jupiterbroadcasting.com/hosts/brent/" rel="noopener noreferrer"&gt;Brent Gervais&lt;/a&gt;, &amp;amp; &lt;a href="https://www.jupiterbroadcasting.com/hosts/wes/" rel="noopener noreferrer"&gt;Wes Payne&lt;/a&gt; have taught me about much of what’s here through their podcasting.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>bitcoin</category>
      <category>tailscale</category>
      <category>nixos</category>
      <category>proxy</category>
    </item>
    <item>
      <title>Automated Plant Watering</title>
      <dc:creator>Gene Liverman</dc:creator>
      <pubDate>Wed, 01 May 2024 08:00:00 +0000</pubDate>
      <link>https://forem.com/genebean/automated-plant-watering-1kci</link>
      <guid>https://forem.com/genebean/automated-plant-watering-1kci</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8jpz8pvofqk4lnzwv3bq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8jpz8pvofqk4lnzwv3bq.jpg" alt="Our raised flower bed" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every spring, my wife and I get really excited about all the pretty plants and flowers that we can decorate our yard with. We also generally grow some vegetables and/or herbs. The problem with this is that we live in Georgia in the US and it gets freaking hot and humid here during the summer. The oppressive heat makes us not want to go outside to water the plants. Combine this with a little bit of traveling and you have a recipe for mostly dead plants during the latter part of the growing season. Well, this year we decided to not only acknowledge this reality, but to do something about it. You see, I’m a bit of a home automation nut and my wife knows it. She was shopping on Amazon and came across an inexpensive drip irrigation kit for gardens and decided to buy it for a raised flower bed we were already planning to setup this year. When it came in, she showed it to me and said “now I just need you to make it come on automatically.” As you might be able to guess, I was more than happy to take up that challenge. I spent a couple of days doing research to find a solution that fit within these self-imposed parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;any smart devices must not require internet access&lt;/li&gt;
&lt;li&gt;the solution had to be able to be controlled via Home Assistant&lt;/li&gt;
&lt;li&gt;all parts had to be relatively inexpensive both individually and when combined together&lt;/li&gt;
&lt;li&gt;I must be able to use multiple instances of the setup so that it can be applied to multiple places in the yard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What I came up with was a zigbee-based water valve with a built in timer and a soil moisture sensor that connects up to the weather station I already had. With these two items I can ensure that we only water the plants when they need it. I also got a 3-way manifold to go on my spigot on the side of my house so that I could connect two valves and one regular garden hose.&lt;/p&gt;

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

&lt;p&gt;The picture below shows the irrigation kit and the soil sensor. The larger tubing snaking through the middle is 1/2” diameter. The smaller tubes are 1/4” and connect to the sprinkler heads (the blue-topped things). Each sprinkler has a little spike under it to keep it in place and is adjustable so that you get just the right amount of water out of each one. The bright green thing with a white disk in the center of it is the top of the soil sensor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpwud2zb7gmbgb5kygrhf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpwud2zb7gmbgb5kygrhf.jpg" alt="Photo of flower bed with sprinklers off" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is what it looks like with the sprinklers on:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff7314gswjlrkruhso80u.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff7314gswjlrkruhso80u.jpg" alt="Photo of flower bed with sprinklers on" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A couple of things to note in the picture above are that the sprinklers each water right near the base of a plant and that the soil sensor gets watered as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parts list
&lt;/h2&gt;

&lt;p&gt;Below are links to each item I used. Many of these are affiliate links so if you happen to buy something I’ll make a few buck by you using the link.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://amzn.to/3QuPnFt" rel="noopener noreferrer"&gt;Carpathen Drip Irrigation Kit for Garden&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;I bought two of these and we ended up with parts left over.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://amzn.to/4bkXWKT" rel="noopener noreferrer"&gt;SASWELL Irrigation Timer SAS980SWT-7-Z01&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;This is, like so many devices on Amazon, made by Tuya. The zigbee connectivity works just fine with Home Assistant’s Zigbee Home Automation (ZHA) as does the on/off functionality. The other niceities, unfortunately, don’t work. Still, on/off is all that is needed for this project and does not require the use of the Tuya zigbee gateway that comes with the device.&lt;/li&gt;
&lt;li&gt;As a bonus, the kit comes with a quick disconnect coupler that can connect to the bottom of the time or to a hose and the other end goes on 1/2” drip tubing. Of the three different fittings I tried, this is the easiest to use and the only one that didn’t leak.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://amzn.to/49ULl04" rel="noopener noreferrer"&gt;Orbit 62009 3-Way Plastic Hose Faucet Valve Manifold&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;This one had great reviews and seems to work really well. As a matter of fact, it had much better reviews than the brass ones. In addition to the three connections along the bottom that can be turned on and off individually, the left and right end caps also come off and can be used.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://amzn.to/4aU4znZ" rel="noopener noreferrer"&gt;Dixon Valve TTB75 PTFE Industrial Sealant Tape&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;The other name for this is “Teflon Tape” - I used it on each of the fittings to make sure leaks are avoided.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://amzn.to/4aWRJoV" rel="noopener noreferrer"&gt;ECOWITT WH51 Soil Moisture Sensor&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;I have this connected to my Ecowitt GW2000 Wi-Fi IoT Hub that came with my &lt;a href="https://amzn.to/49ZbHhA" rel="noopener noreferrer"&gt;ECOWITT Wittboy Weather Station GW2001&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The entire kit works perfectly with Home Assistant 100% locally.&lt;/li&gt;
&lt;li&gt;It also supports up to 8 of the soil moisture sensors.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://amzn.to/44keYXx" rel="noopener noreferrer"&gt;HOUYA U Shaped Garden Stakes 4 Inch 40 Pack Drip Irrigation Stakes Galvanized Landscape Garden Staples&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;We got these to hold the hoses in place between the timers and sprinklers.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://www.homedepot.com/p/Utility-Hose-5-8-in-x-15-ft-Light-Duty-CHDUB58015CC/326978781" rel="noopener noreferrer"&gt;Utility Hose 5/8 in. x 15 ft. Light Duty&lt;/a&gt; (from Home Depot) 

&lt;ul&gt;
&lt;li&gt;I am using one of these to go between each timer and sprinkler kit.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Assembly
&lt;/h2&gt;

&lt;p&gt;The key to making all these parts into a solution that doesn’t require me to manually turn the water on or off is &lt;a href="https://www.home-assistant.io/" rel="noopener noreferrer"&gt;Home Assistant&lt;/a&gt;. Before we get to the automation part though, below is how I put those parts together and integrated them into my Home Assistant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 0: Home Assistant
&lt;/h3&gt;

&lt;p&gt;If you are reading this and don’t yet have Home Assistant then I suggest checking out &lt;a href="https://www.home-assistant.io/green" rel="noopener noreferrer"&gt;Home Assistant Green&lt;/a&gt; - here is how they describe it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ready. Set. Go. — The affordable Home Assistant Green is the easiest way you can start using Home Assistant. It’s plug-and-play and comes with Home Assistant already installed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I got one for my parents and it is quite nice and really affordable. If you are in the US, I suggest buying from &lt;a href="https://cloudfree.shop/product/home-assistant-green/" rel="noopener noreferrer"&gt;CloudFree&lt;/a&gt; if they have it in stock. It’s a small business that I have bought from several times and really like.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Weather Station &amp;amp; Soil Sensor
&lt;/h3&gt;

&lt;p&gt;For me, step 1 happend back when I got the &lt;a href="https://amzn.to/49ZbHhA" rel="noopener noreferrer"&gt;Ecowitt GW2001 Wittboy Weather Station&lt;/a&gt; as a gift. I have it connected via the &lt;a href="https://www.home-assistant.io/integrations/ecowitt/" rel="noopener noreferrer"&gt;Ecowitt integration&lt;/a&gt;. I do not use Ecowitt’s cloud at all. Under “Weather Services” in the Ecowitt’s configuration interface, I use the “Customized” to send data to Home Assistant. I also choose to send my station’s data to Wunderground, which I was able to configure on the same page.&lt;/p&gt;

&lt;p&gt;When I got the &lt;a href="https://amzn.to/4aWRJoV" rel="noopener noreferrer"&gt;Ecowitt WH51 Soil Moisture Sensor&lt;/a&gt;, I put a battery in each one and it connected right up to my station. For my own sanity, I went into both Echowitt’s interface and Home Assistant’s and renamed the sensor immediately after connecting before adding the second one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: A Zigbee Network
&lt;/h3&gt;

&lt;p&gt;Step 2’s prerequisite was me already having the &lt;a href="https://www.home-assistant.io/integrations/zha/" rel="noopener noreferrer"&gt;Zigbee Home Automation integration&lt;/a&gt; setup. Personally, I use a PoE Zigbee coordinator from &lt;a href="https://tubeszb.com/" rel="noopener noreferrer"&gt;https://tubeszb.com/&lt;/a&gt; - I am on my second one (I messed up the first one, no fault of the device) and have recommended them to everyone I know. They are really really good and provide a much more reliable setup than any of the USB based coordinators.&lt;/p&gt;

&lt;p&gt;I also use &lt;a href="https://amzn.to/3JJhj4S" rel="noopener noreferrer"&gt;Sengled Smart Plugs&lt;/a&gt; to ensure I have a very reliable Zigbee network all around my house. I have two such plugs on the side of the house that has the spigot on the outside.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: The Spigot and Manifold
&lt;/h3&gt;

&lt;p&gt;Step 3 was to take the &lt;a href="https://amzn.to/49ULl04" rel="noopener noreferrer"&gt;manifold&lt;/a&gt; outside along with the &lt;a href="https://amzn.to/4aU4znZ" rel="noopener noreferrer"&gt;teflon tape&lt;/a&gt; and connect it to my spigot. I used a set of pliers opened up wide to get a good and tight seal between the spigot and manifold after wrapping some tape around the spigot’s threads.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Water Timers / Valves, Hose, &amp;amp; Stakes
&lt;/h3&gt;

&lt;p&gt;I took each &lt;a href="https://amzn.to/4bkXWKT" rel="noopener noreferrer"&gt;Timer&lt;/a&gt; outside one at a time and put batteries in them. I then held the button down to start pairing and each showed up right away in ZHA. Once each was connected, I screwed it onto the manifold. I then turned the spigot on and made sure I didn’t have any leaks.&lt;/p&gt;

&lt;p&gt;I then attached a &lt;a href="https://www.homedepot.com/p/Utility-Hose-5-8-in-x-15-ft-Light-Duty-CHDUB58015CC/326978781" rel="noopener noreferrer"&gt;15’ long 5/8” light duty utility hose&lt;/a&gt; from Home Depot to each. I ran each hose to the bed that would be watered and then used the &lt;a href="https://amzn.to/44keYXx" rel="noopener noreferrer"&gt;U shaped garden stakes&lt;/a&gt; to keep the hose where I put it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Sprinklers
&lt;/h3&gt;

&lt;p&gt;This all started with my wife finding the &lt;a href="https://amzn.to/3QuPnFt" rel="noopener noreferrer"&gt;Carpathen Drip Irrigation Kits&lt;/a&gt;, and assembly ends with putting it to use. I attached the quick disconnect fittings from the timer kit to the utility hose and the 1/2 drip hose after applying some teflon tape. She and I then worked together to lay out the 1/2” hose and then to route all the 1/4” hoses from the junction blocks to sprinklers or to T’s that then have a pair of sprinklers attached. This part is doable solo, but was much easier with a second set of hands. One thing we learned the hard way was that we needed something that would easily cut through the hoses… if you don’t already have something for this, maybe order a &lt;a href="https://amzn.to/4dikrC9" rel="noopener noreferrer"&gt;hose cutter&lt;/a&gt; from Amazon with your other parts or grab one from a local auto parts store.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Test Flow and Adjust Sprinklers
&lt;/h3&gt;

&lt;p&gt;Last, but not least in this section was to go and turn the timer on by pressing the button on it. Once water stats flowing you can adjust each sprinkler by loosening or tightening the top of it and/or repositioning it. When finished, turn the water back off.&lt;/p&gt;

&lt;p&gt;Note: the timer seems to have a built in turn off function after roughly 10 minutes of being on. This surprised me the first time it turned off, so I thought I’d share.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automation with Home Assistant
&lt;/h2&gt;

&lt;p&gt;We are still tweaking the exact watering frequency and durations, but the automations I setup should work for just about anyone. Here’s the overview of my Home Assistant automation:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Triggers
&lt;/h3&gt;

&lt;p&gt;Here are the two things that will trigger the automation:&lt;/p&gt;

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

&lt;p&gt;The “41” is a soil moisture reading of 41%. In each trigger I have set an ID so I can reference it in the actions later.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Conditions
&lt;/h3&gt;

&lt;p&gt;These are the guard rails I have put in to control when the watering happens:&lt;/p&gt;

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

&lt;p&gt;The first condition check to see if the date is an even number. This makes it only water, at most, every other day.&lt;/p&gt;

&lt;p&gt;The second condition has two parts, only one of which has to be true.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first part checks if it is between 8am and 1pm. This is after everyone is out of bed and before the heat of the day. It also checks to see if a helper I have setup, and will describe shortly, says it is likely to rain or not.&lt;/li&gt;
&lt;li&gt;The second part simply checks it it is between 5:30pm and 7:30pm. This is after the heat of the day and before our kid goes to bed. It does not check the likelihood of rain as the forecast may or may not be right and if it hasn’t rained by 5:30pm then I am fine with doing the watering.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Actions
&lt;/h3&gt;

&lt;p&gt;If the conditions pass, here is what actually gets done:&lt;/p&gt;

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

&lt;p&gt;The first part sends me a message in Telegram with the name of the bed that is about to be watered. The ID from earlier is how it knows which one to tell me about.&lt;/p&gt;

&lt;p&gt;The second part uses the ID from earlier to turn on the sprinkler of the same name.&lt;/p&gt;

&lt;p&gt;The third part tells it to wait for 8 minutes on Sundays, which will happen roughly every other week, or for 2 minutes every other day. This makes it so that it is a good soaking once every 2 weeks and a light watering the rest of the time.&lt;/p&gt;

&lt;p&gt;The fourth part turns off the sprinkler.&lt;/p&gt;

&lt;p&gt;The fifth part sends me a notification again that tells me the watering has ended.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supplemental Automation
&lt;/h3&gt;

&lt;p&gt;Since my watering is time-boxed, I needed a way to make the sensor readings from the soil sensors update during that time window. What I came up with was simply triggering a reload of the Ecowitt integration:&lt;/p&gt;

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

&lt;p&gt;I set the triggers for this automation to be the same as the beginning of the two time windows in the conditions of the other automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it going to rain?
&lt;/h2&gt;

&lt;p&gt;When going through the conditions in the first automation, I mentioned having a helper to tell me if it is going to rain. What I have created for this is a pair of “Helpers” in Home Assistant.&lt;/p&gt;

&lt;p&gt;The first is a template sensor that gets the hourly forecast for the next 10 hours and records the max percentage chance of precipitation.&lt;/p&gt;

&lt;p&gt;The second is a toggle, aka input boolean, that I set via an automation that runs at 7:01am each morning (just after the start of the hour before my first watering time window):&lt;/p&gt;

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

&lt;p&gt;The 10 hour range mentioned just above covers the 7am hour through the 4pm hour. This means the toggle is set before the time window opens and looks ahead until just before the second time window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The code for all of this is too much to include in a blog post, so I’ve posted it all to GitHub at &lt;a href="https://github.com/genebean/home-assistant-examples/tree/main/automated-plant-watering" rel="noopener noreferrer"&gt;github.com/genebean/home-assistant-examples/tree/main/automated-plant-watering&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;This has been exciting to setup! I am really looking forward to seeing how it works this year, and maybe expanding the setup to cover a few more areas. One idea I have for the future is to use one of these timers on a rain barrel for some plants that are a little farther from my house… if that happens I’ll try to remember to post about it too.&lt;/p&gt;

</description>
      <category>homeassistant</category>
    </item>
    <item>
      <title>Custom Weather Entity</title>
      <dc:creator>Gene Liverman</dc:creator>
      <pubDate>Tue, 30 Apr 2024 08:00:00 +0000</pubDate>
      <link>https://forem.com/genebean/custom-weather-entity-1col</link>
      <guid>https://forem.com/genebean/custom-weather-entity-1col</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3wqwb5evaevdk8vcojy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3wqwb5evaevdk8vcojy.png" alt="Screenshot of weather card in Home Assistant" width="800" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Home Assistant 2024.4, this note was in the “Backward-incompatible changes” section of &lt;a href="https://www.home-assistant.io/blog/2024/04/03/release-20244/" rel="noopener noreferrer"&gt;the release announcement&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The previously deprecated &lt;code&gt;forecast&lt;/code&gt; attribute of weather entities, has now been removed. Use the &lt;a href="https://www.home-assistant.io/integrations/weather#service-weatherget_forecasts" rel="noopener noreferrer"&gt;&lt;code&gt;weather.get_forecasts&lt;/code&gt;&lt;/a&gt; service to get the forecast data instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(&lt;a href="https://github.com/gjohansson-ST" rel="noopener noreferrer"&gt;@gjohansson-ST&lt;/a&gt; - &lt;a href="https://github.com/home-assistant/core/pull/110761" rel="noopener noreferrer"&gt;#110761&lt;/a&gt;) (&lt;a href="https://www.home-assistant.io/integrations/metoffice" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;I had a heck of a time finding docs on this, so I have compiled what I did here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Base weather integration
&lt;/h2&gt;

&lt;p&gt;I am using the &lt;a href="https://www.home-assistant.io/integrations/tomorrowio/" rel="noopener noreferrer"&gt;Tomorrow.io integration&lt;/a&gt; to get forecasts, but what I have done should work with any weather provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Conditions from Weather Station
&lt;/h2&gt;

&lt;p&gt;I have made a custom weather entity that pulls current conditions from the weather station in my back yard and forecast data from the Tomorrow.io integration. The integration is known on my system as &lt;code&gt;weather.tomorrow_io_leaky_cauldron_daily&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Sensors
&lt;/h3&gt;

&lt;p&gt;To make this work, I first needed to make sure my &lt;code&gt;configuration.yaml&lt;/code&gt; contains this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;template: !include templates.yaml

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then edited &lt;code&gt;templates.yaml&lt;/code&gt; and added this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- trigger:
    - platform: time_pattern
      minutes: /15
  action:
    - service: weather.get_forecasts
      data:
        type: hourly
      target:
        entity_id: weather.tomorrow_io_leaky_cauldron_daily
      response_variable: hourly
    - service: weather.get_forecasts
      data:
        type: daily
      target:
        entity_id: weather.tomorrow_io_leaky_cauldron_daily
      response_variable: daily
  sensor:
    - name: Weather Forecast Hourly
      unique_id: weather_forecast_hourly
      state: ""
      attributes:
        forecast: ""
    - name: Weather Forecast Daily
      unique_id: weather_forecast_daily
      state: ""
      attributes:
        forecast: ""

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates two sensors: one with the hourly forecast data and one with the daily forecast data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom weather entity
&lt;/h3&gt;

&lt;p&gt;Next, I needed to return to &lt;code&gt;configuration.yaml&lt;/code&gt; and add this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;weather:
  - platform: template
    name: "Home + Tomorrow.io"
    unique_id: home_plus_forecast
    condition_template: ""
    temperature_template: ""
    temperature_unit: "°F"
    humidity_template: ""
    attribution_template: "Home weather station + Tomorrow.io"
    pressure_template: ""
    pressure_unit: "inHg"
    wind_speed_template: ""
    wind_speed_unit: "mph"
    wind_bearing_template: ""
    forecast_hourly_template: ""
    forecast_daily_template: ""

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parts of this that have &lt;code&gt;weather.tomorrow_io_leaky_cauldron_daily&lt;/code&gt; in them are pulled from the Tomorrow.io integration while the parts that start with &lt;code&gt;sensor.outdoor_weather_station&lt;/code&gt; are pulled from the Ecowitt integration that equates to my weather station.&lt;/p&gt;

&lt;p&gt;The result of this is shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fphy3zj8bypcyxdo58lxz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fphy3zj8bypcyxdo58lxz.png" alt="Screenshot of custom weather and daily forecast" width="800" height="849"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy0f8g6lyc3zdu2dkmpjf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy0f8g6lyc3zdu2dkmpjf.png" alt="Screenshot of custom weather and hourly forecast" width="800" height="986"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>homeassistant</category>
    </item>
    <item>
      <title>Crash Boom Bang: PoE &amp; Lightning Strikes</title>
      <dc:creator>Gene Liverman</dc:creator>
      <pubDate>Sat, 09 Apr 2022 13:00:00 +0000</pubDate>
      <link>https://forem.com/genebean/crash-boom-bang-poe-lightning-strikes-gnp</link>
      <guid>https://forem.com/genebean/crash-boom-bang-poe-lightning-strikes-gnp</guid>
      <description>&lt;p&gt;A couple of days ago there was a severe storm that rolled through my area. Lots of thunder that literally rattled the walls of my house, lightning strikes near by, and high winds. At one point the power blinked out too. No big deal… or at least it wasn’t after I realized what was going on. The mystery I am referring to is that when the storm finished I realized my &lt;a href="https://www.tubeszb.com/product/cc2652_poe_coordinator/21?cp=true&amp;amp;sa=false&amp;amp;sbp=false&amp;amp;q=false&amp;amp;category_id=2" rel="noopener noreferrer"&gt;PoE Zigbee Coordinator&lt;/a&gt; wasn’t back up and running.&lt;/p&gt;

&lt;p&gt;At first, it seemed that it had somehow gotten zapped (aka hit by a power surge). Upon digging a little more into the device I found that I could power it via a USB cable. Interestingly, that not only made the device boot up, but also made the ethernet connection work again. This seemed odd because I’d think that anything that fried the PoE aspect of it would have fried the entire ethernet board. With that in mind, I started poking around on my UniFi switch and noticed that there was a power setting for the port that said “off” where the others said “PoE+.” I removed the usb power cord from the coordinator, crossed my fingers, and toggled the setting back to “PoE+.” Amazingly, all was well!&lt;/p&gt;

&lt;p&gt;The really odd part to me was that my UniFi access points that are also powered by the same switch were fine. My best guess as to what happened is that the antenna on the coordinator attracted some of the electricity in the air from the nearby lightning strike and the switch protected itself (this is a total guess though). Regardless of how it happened, I know two things now that I didn’t before finding this setting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;all my equipment survived the storm&lt;/li&gt;
&lt;li&gt;if ever I have another device that won’t power up that was working via PoE, I should check this setting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s hoping I don’t have a reason to need to remember this anytime soon!&lt;/p&gt;

&lt;h2&gt;
  
  
  Just in case…
&lt;/h2&gt;

&lt;p&gt;Going on the assumption that my theory of what happened is even remotely possible, I am seriously thinking about getting a &lt;a href="https://store.ui.com/collections/operator-accessories/products/ethernet-surge-protector" rel="noopener noreferrer"&gt;Ethernet Surge Protector&lt;/a&gt; from UniFi to put in-line between my coordinator and the switch. They are only $12.50 and seem well worth it. The catch is that I am going to have to have a drain wire installed so that there is a place for any absorbed surge to go. I already have plans to have a contractor I know out soon to do some other work so I will ask him about doing this too. If it isn’t cost prohibitive I am going to move forward with the extra protection of my equipment.&lt;/p&gt;

</description>
      <category>homeassistant</category>
      <category>networking</category>
      <category>zigbee</category>
    </item>
    <item>
      <title>Starting Over with Home Assistant - Prep Time</title>
      <dc:creator>Gene Liverman</dc:creator>
      <pubDate>Sun, 06 Mar 2022 23:00:00 +0000</pubDate>
      <link>https://forem.com/genebean/starting-over-with-home-assistant-prep-time-fh6</link>
      <guid>https://forem.com/genebean/starting-over-with-home-assistant-prep-time-fh6</guid>
      <description>&lt;p&gt;The other day I &lt;a href="https://www.reddit.com/r/homeassistant/comments/t5rsg4/starting_over_maybe/" rel="noopener noreferrer"&gt;posted this question&lt;/a&gt; to Redit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I’m seriously considering redoing my Home Assistant setup from scratch now that I know what we actually use and what’s just cruft… anyone else done this?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As expected, there were a variety of opinions. Surprisingly though, there was an overwhelming consensus that redos after having used &lt;a href="https://www.home-assistant.io" rel="noopener noreferrer"&gt;Home Assistant&lt;/a&gt; for a whe were a good thing.&lt;/p&gt;

&lt;p&gt;After reading all the comments and thinking about things more, I’ve decided I want to download all my backups, copy out several bits of yaml, export some other settings, and then take the plunge. In my “&lt;a href="https://beanbag.technicalissues.us/introducing-my-home-assistant-setup/" rel="noopener noreferrer"&gt;Introducing My Home Assistant Setup&lt;/a&gt;” post I said I’d be following up with one that breaks down all my automations. This new decision is going to delay that a bit. Instead, I’m going to start by chronicling my journey through rebuilding my setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prep Time
&lt;/h2&gt;

&lt;p&gt;Before I actually wipe everything and start over I need to do some prep work. This includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;analyzing what bits we actually use&lt;/li&gt;
&lt;li&gt;downloading all my backups&lt;/li&gt;
&lt;li&gt;screenshotting everything so I can reference it later (dashboards, integrations list, add-on list, etc)&lt;/li&gt;
&lt;li&gt;exporting the yaml of each dashboard&lt;/li&gt;
&lt;li&gt;exporting the yaml of each automation and script&lt;/li&gt;
&lt;li&gt;exporting all of my config files&lt;/li&gt;
&lt;li&gt;screenshotting and exporting all of my &lt;a href="https://nodered.org" rel="noopener noreferrer"&gt;Node-RED&lt;/a&gt; flows&lt;/li&gt;
&lt;li&gt;exporting all the yaml from my ESPHome devices&lt;/li&gt;
&lt;li&gt;exporting any needed bits from my Tasmota and WLED devices&lt;/li&gt;
&lt;li&gt;exporting settings from my addons&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Naming is Hard
&lt;/h3&gt;

&lt;p&gt;I’m also going to take some time and define a new naming convention for everything while I can still see a full list of devices. I’ve found that having well named devices makes so many things simpler, especially dashboarding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;After doing all the backups, my plan is to wipe everything and reinstall Home Assistant OS. I already boot my Pi from an external drive, but the process for doing so has changed since I set things up. For this reason, I plan to double check everything to ensure I’m following current best practices, which may mean I have to utilize Raspberry Pi OS as an intermediary step for firmware updates.&lt;/p&gt;

&lt;p&gt;Once Home Assistant is reinstalled I’ll start adding devices and automations back slowly and methodically. This methodical process may well result in wanting to reset things an additional couple of times, and that’s okay. I’d much rather have a little extra down time now than be unhappy after everything has been added back in.&lt;/p&gt;

&lt;h3&gt;
  
  
  A couple of extra changes
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Changing Zigbee Software
&lt;/h4&gt;

&lt;p&gt;One planned change in particular might be the cause of some redos: I’m going to be switching from &lt;a href="https://www.home-assistant.io/integrations/zha/" rel="noopener noreferrer"&gt;ZHA (Zigbee Home Automation)&lt;/a&gt; to &lt;a href="https://www.zigbee2mqtt.io/" rel="noopener noreferrer"&gt;Zigbee2MQTT (z2m)&lt;/a&gt;. Though I’ve used both before, I didn’t discover z2m until after I’d set everything up at home. I like it a lot more and think I’ll put it to use as part of the redo.&lt;/p&gt;

&lt;h4&gt;
  
  
  Adding Z-Wave
&lt;/h4&gt;

&lt;p&gt;We’ve also been planning to start using a couple of Z-Wave devices and I think now is the time to finally do so. As part of this, I’m thinking I’ll use the &lt;a href="https://github.com/hassio-addons/addon-zwavejs2mqtt" rel="noopener noreferrer"&gt;Z-Wave JS to MQTT add-on&lt;/a&gt;. It won’t surprise me any at all if I need to try a few different times to get things just right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timing
&lt;/h2&gt;

&lt;p&gt;I’m planning to do all the prep in the next few days and the actually kick off the redo. I’m strangely looking forward to this.&lt;/p&gt;

</description>
      <category>homeassistant</category>
      <category>homeautomation</category>
      <category>hass</category>
    </item>
    <item>
      <title>That time I forgot to create alerts for a leak sensor</title>
      <dc:creator>Gene Liverman</dc:creator>
      <pubDate>Sun, 20 Feb 2022 21:00:00 +0000</pubDate>
      <link>https://forem.com/genebean/that-time-i-forgot-to-create-alerts-for-a-leak-sensor-1gck</link>
      <guid>https://forem.com/genebean/that-time-i-forgot-to-create-alerts-for-a-leak-sensor-1gck</guid>
      <description>&lt;p&gt;I bought and setup a leak sensor… but forgot to have it alert me if it detected water 🤦‍♂️ Here’s what happened and my new alerting system.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it started
&lt;/h2&gt;

&lt;p&gt;In September of last year I bought an &lt;a href="https://www.amazon.com/Aqara-MCCGQ11LM-Window-Sensor-White/dp/B07D39MSZS/" rel="noopener noreferrer"&gt;Aqara Water Leak Sensor&lt;/a&gt; off Amazon. I placed it between my washing machine and hot water heater in the garage and connected it to &lt;a href="https://www.home-assistant.io" rel="noopener noreferrer"&gt;Home Assistant&lt;/a&gt; via &lt;a href="https://www.home-assistant.io/integrations/zha/" rel="noopener noreferrer"&gt;ZHA (Zigbee Home Automation) integration&lt;/a&gt; and my &lt;a href="https://www.tubeszb.com/product/cc2652_poe_coordinator/21?cp=true&amp;amp;sa=false&amp;amp;sbp=false&amp;amp;q=false&amp;amp;category_id=2" rel="noopener noreferrer"&gt;Zigbee Coordinator&lt;/a&gt;. I then added it to a dashboard that shows me information about the garage and the status of my washer and drier. Finally, I tested that the sensor worked as advertised and that the proper state showed in Home Assistant. Everything checked out… yay for being proactive!&lt;/p&gt;

&lt;h2&gt;
  
  
  Uh-oh…
&lt;/h2&gt;

&lt;p&gt;Fast forward to the first Friday of February. My wife is on her way to her car in our garage. When she starts down the stairs from our kitchen she hears an odd noise and sees a lot of water on the floor. The water is a concern, but not totally unexpected as our garage has flooded before and it had been raining hard. The odd noise and the fact that there is steam in our garage while it’s somewhere around freezing outside is actually much more disturbing. She looks out back and sees that our sump pump is still working, which means it’s likely not the issue we’ve had before. At this point calls me and tells me what she sees and then goes back to investigating.&lt;/p&gt;

&lt;p&gt;After getting dressed appropriately, I head down and am equally confused and perplexed. We determine that the steam and noise are coming from behind the washer and drier so I decided to climb atop our drier to get a better look. It turns out both are the result of a leak in a plumbing joint near where the hot water hose connection for our laundry is: high pressure hot water is spewing from the joint. A moment or two later we find the shutoff valve for where water goes into our hot water heater and turn it off. The leak stops and we can start assessing what’s happened and the damage.&lt;/p&gt;

&lt;p&gt;Having had leaks before, one of the first things that comes to mind is “what is this going to cost us?” It’s about this time that I remember two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I have a leak detector sitting in the flooded area, which means I should be able to determine how long the water has been pouring out into the floor. This will help satisfy some the curiosity related to “is this going to be expensive.”&lt;/li&gt;
&lt;li&gt;I never set up any alerts to tell me when the sensor detected a leak… 🤦‍♂️ Yeah… oops.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I opened up the &lt;a href="https://companion.home-assistant.io/" rel="noopener noreferrer"&gt;Home Assistant app&lt;/a&gt; on my phone and pulled up the sensor. It had, indeed, worked as designed and clearly showed that the leak started at about 11:30pm the night before… it was currently about 7:30am.&lt;/p&gt;

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

&lt;p&gt;It’s at this point that I’m really wishing I hadn’t forgotten to have Home Assistant tell me if the sensor detected water. I set a reminder on my phone to rectify that later in the day and then start removing the water via a push broom (they are surprisingly good at this task, by the way). After moving some things to dry ground and pushing lots of water out the door of our garage, the immediate crisis is over. Now to figure out the root cause and get it fixed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keihan to the rescue!
&lt;/h2&gt;

&lt;p&gt;We are blessed to know an amazing gentleman named Keihan who is a General Contractor and operates his own business focused on residential repairs and upgrades. He and his crew have done almost every bit of work to our house since the day we bought it. So, naturally, my first call after getting the standing water out of our garage was to him. I left a voicemail with all the details about what was going on that morning, and included that I had noticed some sporadic high pressure in our faucets recently.&lt;/p&gt;

&lt;p&gt;After checking things out, Keihan determined that the water heater itself was the likely root cause of both the high pressure that periodically showed up in my faucets and in what caused the leak. We made a plan to replace it on Monday and to collect some data over the weekend about the water pressure in my house via a mechanical gauge he attached to the spigot where our water hose would normally be connected. The gauge had an extra needle that would record how high pressure spiked. Keihan asked me to check it periodically and let him know if it spiked much.&lt;/p&gt;

&lt;h3&gt;
  
  
  All the pressure
&lt;/h3&gt;

&lt;p&gt;A few hours later, I noticed that the pressure had spiked to about 90 PSI. Street pressure is only about 80 PSI and the regulator for the house is less than that, so this was a little concerning to us both. I dialed the observation needle back down to the current pressure so that we could see if this was a one-off spike or if there was a pattern. I checked again around 8pm and was greeted with this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy3nh0kyao9uhtbny5c6f.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy3nh0kyao9uhtbny5c6f.jpeg" alt="photo of pressure gauge showing nearly 120 PSI" width="800" height="1010"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I sent Keihan this photo showing that the pressure had spiked to nearly 120 PSI and he decided we shouldn’t wait until Monday as this much pressure could easily expose other weak areas in my house’s plumbing. He said he’d be out the following morning, Saturday, to replace the hot water heater. Did I mention that he’s awesome?&lt;/p&gt;

&lt;h3&gt;
  
  
  Water heater time
&lt;/h3&gt;

&lt;p&gt;Saturday morning came and so did Ro, a long time employee of Keihan’s. Ro got right to getting the old water heater drained and prepped for removal. A little bit later, Keihan arrived with the surprisingly hard to acquire new water heater. You see, it was supposedly in stock at the local big box home improvement store… but no one could find it. The next closest store showed to have three of them, so it was off to there. Apparently they were having some inventory difficulties too as it took them an entire hour to find even one unit. Fortunately, they did find it. At any rate, the new hot water heater was at my house and they were ready to get it installed. The old one had well exceeded its life expectancy (which is something I didn’t know I needed to watch for) and had developed significant amounts of rust that was already starting to clog the attached pressure tank. One thing was for certain after seeing all the rust and learning the age of the old water heater: it may or may not be the only problem, but it was for sure some of it and truly needed replacing.&lt;/p&gt;

&lt;h3&gt;
  
  
  More monitoring
&lt;/h3&gt;

&lt;p&gt;With the new water heater installed and all the air flushed from the lines, all that was left was to keep an eye on the gauge to see if the issue was fully fixed or just partially fixed. The following day, Sunday, I checked the gauge and, sadly, it had spiked above 120 PSI. I let Keihan know and he said Ro would be out the following day to replace the house’s pressure regulator.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monday
&lt;/h3&gt;

&lt;p&gt;Monday came and so did Ro. He let me know that water was going to be cut off to the house for a little while and then went to work. In what seemed like no time, he was back at the door letting me know he was all finished.&lt;/p&gt;

&lt;h4&gt;
  
  
  Thankful
&lt;/h4&gt;

&lt;p&gt;I have said it before, and I will say it again: I am really thankful that we have access to such a good and reliable contractor. By lunch on Monday everything had been completed and life could return to normal.&lt;/p&gt;

&lt;h2&gt;
  
  
  But what about those missing alerts?
&lt;/h2&gt;

&lt;p&gt;The last thing I want is to have a repeat of the 🤦‍♂️ moment where the reason we didn’t know about a water leak was that I had simply neglected to make Home Assistant tell us. Thus, it was time to create a new automation.&lt;/p&gt;

&lt;p&gt;I though about what I really wanted this alert to do and came up with this as a baseline: it should alert us in a way that we won’t miss, regardless of the time of day or night that it goes off, and regardless of us being home or away. There is one catch to this: it also should not terrify my toddler.&lt;/p&gt;

&lt;h3&gt;
  
  
  “SOS - Water detected in garage”
&lt;/h3&gt;

&lt;p&gt;Enter my new Home Assistant automation entitled “SOS - Water detected in garage.” This automation is triggered any time the leak sensor has been wet for at least one minute. The delay is simply to avoid false alarms and to allow enough time to quickly pick the sensor up if something is spilled near it.&lt;/p&gt;

&lt;p&gt;If triggered, the following actions are taken in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Text messages are sent to my wife and I via Twilio that simply say “Leak detected in the garage.”&lt;/li&gt;
&lt;li&gt;Turns on overhead lights. This includes pretty much every light switch in the house that is not in our toddler’s room.&lt;/li&gt;
&lt;li&gt;Turns off overhead lights&lt;/li&gt;
&lt;li&gt;Turns on overhead lights&lt;/li&gt;
&lt;li&gt;Sends a “critical” notification to my wife and I via the Home Assistant app that is installed on each of our phones. This alert contains the text “Water has been detected in the garage” and plays an audio clip with the same message. Of note here is that critical alerts are never silenced, show up on Car Play, and show at the top of your list of alerts.&lt;/li&gt;
&lt;li&gt;Sets the volume of the Sonos speakers in our bedroom, kitchen, and living room to 30% and combines them into a groups&lt;/li&gt;
&lt;li&gt;Uses text to speech (TTS) to speak “Attention, attention, attention, a sensor in the garage near the washer is wet”&lt;/li&gt;
&lt;li&gt;Wait 10 seconds&lt;/li&gt;
&lt;li&gt;Repeat steps 2-8 up to thirty times&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Right now, to make it stop you’d have to go find the automation and turn it off. I plan to improve this by making the notification to our phones “&lt;a href="https://companion.home-assistant.io/docs/notifications/actionable-notifications/" rel="noopener noreferrer"&gt;Actionable&lt;/a&gt;.” This will allow us to acknowledge the alarm from the notification. Upon acknowledgment the actions listed above will cease.&lt;/p&gt;

&lt;h2&gt;
  
  
  Another sensor and reliability enhancement
&lt;/h2&gt;

&lt;p&gt;One thing that was added as part of getting the new hot water heater was a pan beneath it that is intended to catch water in certain scenarios and route it to a safe place. This is great, but also means that the leak sensor sitting next to my washer is no longer sufficient to tell me about everything I’d like to keep an eye on. The solution: add a second leak sensor.&lt;/p&gt;

&lt;p&gt;I hopped back on Amazon and ordered another &lt;a href="https://www.amazon.com/Aqara-MCCGQ11LM-Window-Sensor-White/dp/B07D39MSZS/" rel="noopener noreferrer"&gt;Aqara Water Leak Sensor&lt;/a&gt; to place in the pan under the water heater. The only problem with this plan is that the pan is metal… and metal is great at blocking or interfering with wireless signals. To combat this, I also picked up a &lt;a href="https://www.amazon.com/gp/product/B082PSKRSP/" rel="noopener noreferrer"&gt;SONOFF S31 Lite 15A Zigbee Smart Plug&lt;/a&gt;. This plug acts as a Zigbee router, which means that if it were to be plugged in near the sensors then they’d have a strong signal even with the pan causing interference. It just so happens that I had an open place for such a plug in an outlet under my work bench about 10 feet away.&lt;/p&gt;

&lt;p&gt;Both the sensor and the plug have come in and have been added to my Home Assistant setup. The new sensor has also been added to the automation that watches for leaks. Here’s to hoping that that automation doesn’t ever actually need to be triggered.&lt;/p&gt;

</description>
      <category>homeassistant</category>
      <category>homeautomation</category>
      <category>hass</category>
    </item>
    <item>
      <title>Temperature sensing for Jupiter Garage</title>
      <dc:creator>Gene Liverman</dc:creator>
      <pubDate>Mon, 31 Jan 2022 03:34:00 +0000</pubDate>
      <link>https://forem.com/genebean/temperature-sensing-for-jupiter-garage-335e</link>
      <guid>https://forem.com/genebean/temperature-sensing-for-jupiter-garage-335e</guid>
      <description>&lt;p&gt;The other day I was listening to &lt;a href="https://linuxunplugged.com/441" rel="noopener noreferrer"&gt;Linux Unplugged 441&lt;/a&gt; and heard Chris mention how he wished he had a way to track the temperature in the garage where the server is.&lt;/p&gt;

&lt;p&gt;I decided that this was something I could help with, so I hit him up on Twitter:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hey &lt;a href="https://twitter.com/ChrisLAS" rel="noopener noreferrer"&gt;@ChrisLAS&lt;/a&gt; - I was listening to LUP today and heard you might need to monitor temperatures in your garage… DM me if you want a cloudless WiFi monitor based on ESPHome.&lt;/p&gt;

&lt;p&gt;— Technical Issues (@technicalissues) &lt;a href="https://twitter.com/technicalissues/status/1483594125832294404" rel="noopener noreferrer"&gt;January 19, 2022&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We chatted a tad via direct messages and then I built this:&lt;/p&gt;

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

&lt;p&gt;It’s modeled after one I have in my own garage with a couple of small modifications to suite his use case better. The setup is made up of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a 1/2 size prototyping board&lt;/li&gt;
&lt;li&gt;a D1 Mini (aka a small ESP8266) microcontroller&lt;/li&gt;
&lt;li&gt;a BME280 temperature, pressure, and humidity sensor&lt;/li&gt;
&lt;li&gt;a 3 port spring terminal block&lt;/li&gt;
&lt;li&gt;a Dallas 1-wire temperature sensor in a waterproof housing with a cable attached&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The idea is that &lt;a href="https://twitter.com/ChrisLAS" rel="noopener noreferrer"&gt;Chris&lt;/a&gt; will be able to mount this on, or near, the new server cabinet with the microcontroller at the top so that the heat it generates rises above the onboard sensor. The onboard sensor (the purple part) will allow him to monitor the temperature, barometric pressure, and humidity in the garage while the corded sensor will allow for monitoring the temperature inside the server rack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessing the data directly
&lt;/h2&gt;

&lt;p&gt;The microcontroller is configured to present it’s data locally in two ways: via a web page and via a Prometheus endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  The local web page
&lt;/h3&gt;

&lt;p&gt;This page is presented at &lt;a href="http://jupiter-garage-data.local" rel="noopener noreferrer"&gt;jupiter-garage-data.local&lt;/a&gt;:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The Prometheus endpoint
&lt;/h3&gt;

&lt;p&gt;This data is presented at &lt;a href="http://jupiter-garage-data.local/metrics" rel="noopener noreferrer"&gt;jupiter-garage-data.local/metrics&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#TYPE esphome_sensor_value GAUGE
esphome_sensor_value{id="jupiter_garage_data_wifi_signal",name="Jupiter Garage Data WiFi Signal",unit="dBm"} -69
esphome_sensor_value{id="jupiter_garage_data_server_rack_temperature",name="Jupiter Garage Data Server Rack Temperature",unit="°C"} 20.9
esphome_sensor_value{id="jupiter_garage_data_garage_temperature",name="Jupiter Garage Data Garage Temperature",unit="°C"} 20.9
esphome_sensor_value{id="jupiter_garage_data_garage_pressure",name="Jupiter Garage Data Garage Pressure",unit="hPa"} 980.3
esphome_sensor_value{id="jupiter_garage_data_garage_humidity",name="Jupiter Garage Data Garage Humidity",unit="%"} 31.1
#TYPE esphome_sensor_failed GAUGE
esphome_sensor_failed{id="jupiter_garage_data_wifi_signal",name="Jupiter Garage Data WiFi Signal"} 0
esphome_sensor_failed{id="jupiter_garage_data_server_rack_temperature",name="Jupiter Garage Data Server Rack Temperature"} 0
esphome_sensor_failed{id="jupiter_garage_data_garage_temperature",name="Jupiter Garage Data Garage Temperature"} 0
esphome_sensor_failed{id="jupiter_garage_data_garage_pressure",name="Jupiter Garage Data Garage Pressure"} 0
esphome_sensor_failed{id="jupiter_garage_data_garage_humidity",name="Jupiter Garage Data Garage Humidity"} 0

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A better way: Home Assistant
&lt;/h2&gt;

&lt;p&gt;Accessing the data locally is all fine and dandy when debugging or doing casual checks, but for everyday usage it is way more helpful to have the data in &lt;a href="https://www.home-assistant.io/" rel="noopener noreferrer"&gt;Home Assistant&lt;/a&gt;. &lt;a href="https://esphome.io/" rel="noopener noreferrer"&gt;ESPHome&lt;/a&gt; supports this out of the box and that’s exactly what the code for the microcontroller is built in. Speaking of which, here is the code:&lt;/p&gt;

&lt;h3&gt;
  
  
  jupiter-garage-data.yaml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;substitutions:
  name: jupiter-garage-data
  friendly_name: Jupiter Garage Data
  on_board_sensor_name: Garage
  corded_sensor_name: Server Rack

esphome:
  name: "${name}"

esp8266:
  board: d1_mini

# Enable Home Assistant API
api:
  password: "self-hosted"
  # encryption:
  # key: !secret enc_key

# Used by fallback Access Point (ap)
captive_portal:

# Enable logging
logger:

ota:
  # only use one of the two lines below
  password: "self-hosted"
  # password: !secret ota_password

prometheus:

web_server:
  port: 80
  # auth:
  # username: admin
  # password: !secret ota_password

wifi:
  # the two secret names here are now the ones used by default in ESPHome
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "${name}-setup"
    password: "self-hosted"

# Set device time to match that of Home Assistant
time:
  - platform: homeassistant
    id: esptime

# Use the onboard LED to indicate system status
status_led:
  pin:
    number: D0
    inverted: true

# Wired sensor
dallas:
  - pin: D4

# The bme280 sensor on the board uses i2c
i2c:
  sda: D2
  scl: D1

sensor:
  - platform: wifi_signal
    name: "${friendly_name} WiFi Signal"
    update_interval: 60s
  - platform: dallas
    address: 0x680000066e92ad28
    name: "${friendly_name} ${corded_sensor_name} Temperature"
  - platform: bme280
    temperature:
      name: "${friendly_name} ${on_board_sensor_name} Temperature"
      oversampling: 16x
    pressure:
      name: "${friendly_name} ${on_board_sensor_name} Pressure"
    humidity:
      name: "${friendly_name} ${on_board_sensor_name} Humidity"
    address: 0x76
    update_interval: 60s

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once this device is connected with Home Assistant all the sensors will be availabe for alerts and automations. For example, an automation could be setup to cut on an exhast fan or portable air conditioner if the temperature gets too high. Additionally, if the temperature doesn’t come down fast enough an alert could be sent to one or more members of the JB team so that someone can manually intervene before the hardware is damaged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diagrams
&lt;/h2&gt;

&lt;p&gt;Here are a couple of diagrams to help explain things further.&lt;/p&gt;

&lt;h3&gt;
  
  
  D1 Mini
&lt;/h3&gt;

&lt;p&gt;In the code above the pins &lt;code&gt;D0&lt;/code&gt;, &lt;code&gt;D1&lt;/code&gt;, &lt;code&gt;D2&lt;/code&gt;, and &lt;code&gt;D4&lt;/code&gt; are referenced. You can see exactly what those correlate to here:&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;The image above was copied from &lt;a href="https://escapequotes.net/esp8266-wemos-d1-mini-pins-and-diagram/" rel="noopener noreferrer"&gt;https://escapequotes.net/esp8266-wemos-d1-mini-pins-and-diagram/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  jupiter-garage-data
&lt;/h3&gt;

&lt;p&gt;This diagram shows how I assembled everything:&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;This diagram was created with &lt;a href="https://fritzing.org/" rel="noopener noreferrer"&gt;Fritzing&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;Here’s hoping Chris likes this device hand finds it useful.&lt;/p&gt;

</description>
      <category>esphome</category>
      <category>homeassistant</category>
      <category>esp8266</category>
    </item>
    <item>
      <title>Introducing My Home Assistant Setup</title>
      <dc:creator>Gene Liverman</dc:creator>
      <pubDate>Mon, 10 Jan 2022 04:00:00 +0000</pubDate>
      <link>https://forem.com/genebean/introducing-my-home-assistant-setup-50j4</link>
      <guid>https://forem.com/genebean/introducing-my-home-assistant-setup-50j4</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0ov0v35ht6emqfv93gp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0ov0v35ht6emqfv93gp.png" alt="Home Assistant logo with text" width="800" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A year ago today (January 9th, 2021) I deployed what I consider my first production-grade instance of &lt;a href="https://www.home-assistant.io" rel="noopener noreferrer"&gt;Home Assistant&lt;/a&gt; and couldn’t be happier. It is an amazingly powerful tool that is 100% free and open source. One of Home Assistant’s key features is the fact that it takes a local first approach to everything. By that I mean that every aspect of the project makes a concerted effort to not rely on the internet or cloud services unless they are absolutely required, such as when integrating with a vendor who does not have a local api (or won’t provide access to it to the community). This means that if the internet is out I can still control the vast majority of the devices connected to Home Assistant using either the web interface or the app on my phone… and push notifications from Home Assistant to my phone will continue to work too.&lt;/p&gt;

&lt;p&gt;This post describes what my setup looks like today in hopes that it will inspire and/or help others automate things around their home. I cannot recommend Home Assistant enough, and that’s not just for techies like myself. It can provide significant benefits for the non-technically inclined too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Home Assistant Itself
&lt;/h2&gt;

&lt;p&gt;My setup is based on a &lt;a href="https://www.raspberrypi.com/products/raspberry-pi-4-model-b/" rel="noopener noreferrer"&gt;Raspberry Pi 4&lt;/a&gt; that has 8GB of RAM. It is housed in a &lt;a href="https://www.kickstarter.com/projects/coolermaster/pi-case-40" rel="noopener noreferrer"&gt;Cooler Master Pi Case 40&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Storage wise, I am using a &lt;a href="https://www.amazon.com/gp/product/B07VYG5HQD" rel="noopener noreferrer"&gt;Kingston 250GB A2000 M.2 2280 NVMe drive&lt;/a&gt; housed in a &lt;a href="https://www.amazon.com/gp/product/B07W74BN5B" rel="noopener noreferrer"&gt;FIDECO USB C Gen 2 enclosure&lt;/a&gt;. The picture below was taken right after I put things together and just before I slid the lower parts into the black housing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5gf5ygo0hhr1nr5iur0c.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5gf5ygo0hhr1nr5iur0c.jpg" alt="NVMe drive for my Home Assistant" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I configured my Pi so that it will boot from the NVMe drive and then installed Home Assistant Operating System &lt;a href="https://www.home-assistant.io/installation/raspberrypi" rel="noopener noreferrer"&gt;per their instructions&lt;/a&gt;. There is no SD card in my Pi at all.&lt;/p&gt;

&lt;p&gt;The setup is fast and rock-solid reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nabu Casa
&lt;/h2&gt;

&lt;p&gt;The founders of Home Assistant also a company to make the project sustainable. For a measly $5 a month (1 Startbucks drink) you get a secure way to control your home when not at home… with zero technical know how required on your part. No fiddling with routers or anything like that. I love what they provide and find it to be a great value that has the bonus of supporting Home Assistant’s development. Check it out for yourself at &lt;a href="https://www.nabucasa.com" rel="noopener noreferrer"&gt;nabucasa.com&lt;/a&gt;. And, though it may sound otherwise, they have not asked me to say any of this nor am I being compensated in any way. I just really believe in their service and work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zigbee
&lt;/h2&gt;

&lt;p&gt;Over time I have accumulated nearly 30 devices that utilize the Zigbee protocol. Initially, I just had Philips Hue bulbs and one of their hubs. That all changed once I decided to start adding devices that weren’t part of the Hue product line. I transitioned from the Hue hub to a &lt;a href="https://www.amazon.com/gp/product/B07PZ7ZHG5" rel="noopener noreferrer"&gt;ConBee II&lt;/a&gt; and the &lt;a href="https://www.home-assistant.io/integrations/deconz/" rel="noopener noreferrer"&gt;deCONZ software&lt;/a&gt;. Though I was able to easily move my Hue bulbs from the Hue hub to the ConBee II, I quickly ran into problems with both the hardware and software.&lt;/p&gt;

&lt;h3&gt;
  
  
  ConBee II
&lt;/h3&gt;

&lt;p&gt;On the hardware side, the radio in the ConBee II was just too weak for use where I needed it in my house and resulted in really poor reliability. I replaced it with the &lt;a href="https://www.tubeszb.com/product/cc2652_poe_coordinator/21?cp=true&amp;amp;sa=false&amp;amp;sbp=false&amp;amp;q=false&amp;amp;category_id=2" rel="noopener noreferrer"&gt;CC2652P2 Based Zigbee to PoE Coordinator V2&lt;/a&gt; made by &lt;a href="https://twitter.com/Tubeszb" rel="noopener noreferrer"&gt;tubesZB&lt;/a&gt;. I can’t recommend this coordinator enough. Even if it is out of stock initially, it is worth waiting for.&lt;/p&gt;

&lt;h3&gt;
  
  
  deCONZ
&lt;/h3&gt;

&lt;p&gt;On the software side, I had some reliability and usability issues that I narrowed down to being caused by deCONZ. I bit the bullet and switched everything over to the in-built &lt;a href="https://www.home-assistant.io/integrations/zha/" rel="noopener noreferrer"&gt;ZHA (Zigbee Home Automation) integration&lt;/a&gt; and have been very happy since.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Second Zibee Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sonoff Zigbee Bridge
&lt;/h3&gt;

&lt;p&gt;Before switching out my coordinator at home, I had actually bought a second ConBee II for use in my office. I’ll detail that setup in some other blog post, but the relevant part is that I had issues there too. I replaced that one with a Sonoff Zigbee Bridge that I flashed with Tasmota. There are many guides out there on how to accomplish this, but you can also buy one pre-flashed from &lt;a href="https://cloudfree.shop/product/sonoff-zigbee-bridge-flashed-with-tasmota/" rel="noopener noreferrer"&gt;CloudFree&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Zigbee2MQTT
&lt;/h3&gt;

&lt;p&gt;I also ended up replacing deCONZ there too, though it was a good bit after I’d switched to ZHA at home. I ended up running &lt;a href="https://www.zigbee2mqtt.io/" rel="noopener noreferrer"&gt;Zigbee2MQTT (z2m)&lt;/a&gt; instead both for ease of use and because z2m has a web interface that I could use locally from anything with a browser. Though I have been perfectly satisfied by ZHA, I’d probably use z2m if I was starting over simply because it has a better user experience. The only reason I am not using it now is that migration is tedious and time consuming.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zigbee and Wi-Fi Coexistence
&lt;/h2&gt;

&lt;p&gt;Before moving on, I want to call out explicitly that Zigbee and Wi-Fi utilize the same frequencies. This means that to avoid having problems with both you need to plan accordingly. I found the article “&lt;a href="https://www.metageek.com/training/resources/zigbee-wifi-coexistence/" rel="noopener noreferrer"&gt;ZigBee and Wi-Fi Coexistence&lt;/a&gt;” on metageek to be supremely helpful. For me, this translated to telling my Wi-Fi gear to only use channels 1 and 6 and telling my Zigbee coordinator (by way of ZHA) to use channel 25. The image below was taken from that article and shows how this setup keeps each system from fighting with the other (I picked 25 even though 24 is shown in the image).&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Putting Home Assistant To Use
&lt;/h2&gt;

&lt;p&gt;With all that background out of the way, let’s get into my philosophies around automation and how I am actually using Home Assistant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Baseline Philosophy On Automation
&lt;/h3&gt;

&lt;p&gt;If you research the topic of home automation any at all you quickly find that there are people who want their house to basically be autonomous… that’s not me. I firmly believe that automation should make things easier, not get in the way of ANYONE in your house… visitors included. For example, lights should still have physical switches and you shouldn’t have to alter the way your automations run just because someone is staying over.&lt;/p&gt;

&lt;h4&gt;
  
  
  Switches vs Smart Bulbs
&lt;/h4&gt;

&lt;p&gt;Along those same lines, I tend to prefer smart switches over smart bulbs, where practical. Switches have two distinct advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Switches basically don’t wear out or need replacing as time goes on. They are a once-and-done upgrade. Anything can fail but, unlike bulbs, they are not designed to wear out.&lt;/li&gt;
&lt;li&gt;One switch can control any number of lights. If you have a room with a traditional switch controlling three lights you can continue controlling all three by swapping out the traditional one for a smart one. Alternatively, you could replace all three bulbs with smart ones and control them independently. The down sides to this are that bulbs wear out and you no longer have a physical switch to control the bulbs. The first costs more over the long haul and the second poses challenges for anyone visiting and any time your network isn't working perfectly. Switches still work even when the network is down.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Smartified Things
&lt;/h3&gt;

&lt;p&gt;There a few categories of things I’ve made smart:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;light switches&lt;/li&gt;
&lt;li&gt;light bulbs&lt;/li&gt;
&lt;li&gt;plugs&lt;/li&gt;
&lt;li&gt;thermostat&lt;/li&gt;
&lt;li&gt;thermometers (aka temperature sensors)&lt;/li&gt;
&lt;li&gt;the TV remote&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Smart Plugs
&lt;/h4&gt;

&lt;p&gt;I’ve progressively added more and more smart devices. My initial focus was on smart plugs as they are cheap and require next to no effort to install. They were added to our bedside lamps because the lamps were actually really hard to reach from the bed. Once they were added we could just tell the Echo to turn them on or off. I also added one to our Christmas tree. That one felt like a serious win as it not only meant I didn’t have to crawl under or wiggle behind the tree any more, but we could also have the tree come on automatically using the scheduling functions the plug provided.&lt;/p&gt;

&lt;h4&gt;
  
  
  Smart Switches
&lt;/h4&gt;

&lt;p&gt;That was quickly followed by starting to install smart switches. I felt (and still feel) like they gave me the most bang for the buck. This also allowed me to start making a noticeable impact on our day to life by simplifying little things like turning all the lights in multiple rooms off when we left home by simply saying “Alexa, good bye.” It also helped when we returned home, especially when our hands were full, because I could say “Alexa, I’m home.”&lt;/p&gt;

&lt;h4&gt;
  
  
  Smart Bulbs
&lt;/h4&gt;

&lt;p&gt;Next up was combining a &lt;a href="https://www.ikea.com/us/en/p/hemma-cord-set-white-10175810/" rel="noopener noreferrer"&gt;HEMMA cord set&lt;/a&gt;, a &lt;a href="https://www.ikea.com/us/en/p/nymoe-lamp-shade-black-brass-color-00377210/" rel="noopener noreferrer"&gt;NYMÖ Lamp shade&lt;/a&gt;, and a &lt;a href="https://www.homedepot.com/p/Philips-Hue-Soft-White-A19-75W-Equivalent-Dimmable-LED-Smart-Light-Bulb-563007/316148568" rel="noopener noreferrer"&gt;Philips Hue Soft White bulb&lt;/a&gt; to add light to places like above our couch and over the dresser in our nursery.&lt;/p&gt;

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

&lt;p&gt;The lights over the couch were, and still are, really nice because we can have smaller amounts of light in more focused locations instead of one set of central, really bright lights on the ceiling. My wife and I both find this to be much easier on our eyes and significantly more effective when reading.&lt;/p&gt;

&lt;p&gt;The light in the nursery was an inspired decision and likely one that we have gained the most benefit from since having a kid. Having this light and connecting it to an Amazon Echo gave us an effective night light, a way to do late night feedings without bright lights like from an overhead, and hands-free operation of the light and its brightness. When set at 1% it is dim enough to not interfere with sleep while still being bright enough that we were able to easy check on our kid without squinting. To further simplify things, and add a touch of automation, we added a Hue Dimmer that is mounted to the wall just inside the door. Having the dimmer, though not as nice as a wired one, allowed for easily brightening or dimming the light without saying a word.&lt;/p&gt;

&lt;h4&gt;
  
  
  Thermostats And Temperature Sensors
&lt;/h4&gt;

&lt;p&gt;I have a Nest thermostat and temerature sensors in almost every room. The sensors are a combination of &lt;a href="https://amazon.com/dp/B07D37FKGY" rel="noopener noreferrer"&gt;Aqara Temperature and Humidity Sensors&lt;/a&gt; and custom built ones. Building a sensor that is compact and nice looking turned out to be beyond my skill level so most of the ones I have are the Aqara ones. By having these sensors everywhere and having the data from them pulled into Home Assistant I can quickly see the temperature in the occupied part of the house and adjust the Nest accordingly.&lt;/p&gt;

&lt;p&gt;One thing that I want to call out here is that Home Assistant is wht makes this possible. The Aqara sensors are Zigbee, my custom ones are Wi-Fi, and the Nest is read via a remote API. Home Assistant takes these three different systems and pulls them into a single place where I can work with them as if they were all from the same vendor.&lt;/p&gt;

&lt;h4&gt;
  
  
  TV Remote
&lt;/h4&gt;

&lt;p&gt;I've got a &lt;a href="https://www.amazon.com/Logitech-Harmony-Companion-Control-Entertainment/dp/B00N3RFC4G" rel="noopener noreferrer"&gt;Logitech Harmony Companion All in One Remote Control&lt;/a&gt; that controls my living room Roku TV and all the things connected to it. This allows me to control my TV as part of automations that I'll discuss later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automations In Home Assistant
&lt;/h2&gt;

&lt;p&gt;Up to this point, Home Assistant hasn’t really been used except superficially. It has some serious automation abilities that can span all the different products from all the different vendors utilized in my house. In my next post I am going to break down all the automations I currently have setup. Each breakdown will include details on the automation, the equipment involved, and why I think it’s worth having. I am not including this here simply because it would make this post way too long.&lt;/p&gt;

</description>
      <category>homeassistant</category>
      <category>homeautomation</category>
      <category>hass</category>
    </item>
    <item>
      <title>OpenTelemetry Part 3: v0.6.0 gems and VMPooler</title>
      <dc:creator>Gene Liverman</dc:creator>
      <pubDate>Mon, 05 Oct 2020 22:20:00 +0000</pubDate>
      <link>https://forem.com/puppet/opentelemetry-part-3-v0-6-0-gems-and-vmpooler-18hj</link>
      <guid>https://forem.com/puppet/opentelemetry-part-3-v0-6-0-gems-and-vmpooler-18hj</guid>
      <description>&lt;p&gt;For part three of my journey in using OpenTelemetry (Otel) with Sinatra I am upgrading to the 0.6.0 release of the OTel gems to get many new features, adding instrumentation to VMPooler, and learning what not to do. Part 3 also includes opening several issues and making my first code contribution to opentelemetry-ruby. Lastly, I will be sharing some more complete code examples showing how all the bits are configured.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recapping current reality
&lt;/h2&gt;

&lt;p&gt;This recap is so much farther along than the last one. Our current reality at the start of part three is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ABS, CITH, and NSPooler have v0.5.x &lt;code&gt;opentelemetry-*&lt;/code&gt; gems generating traces in both test and production&lt;/li&gt;
&lt;li&gt;Traces are going from each application to a Jaeger agent&lt;/li&gt;
&lt;li&gt;Each Jaeger agent sends traces to an OTel collector&lt;/li&gt;
&lt;li&gt;Each OTel collector processes data and sends it to both a local Jaeger instance backed by Elasticsearch and to &lt;a href="https://lightstep.com/" rel="noopener noreferrer"&gt;Lightstep&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chatting in Gitter and the Ruby SIG
&lt;/h2&gt;

&lt;p&gt;The OpenTelemetry projects each have a &lt;a href="https://gitter.im" rel="noopener noreferrer"&gt;Gitter&lt;/a&gt; channel. I spent a good bit of time chatting in the &lt;a href="https://gitter.im/open-telemetry/opentelemetry-ruby" rel="noopener noreferrer"&gt;opentelemetry-ruby&lt;/a&gt; one and got some really good tips. I also joined a couple of the weekly OpenTelemetry Ruby Special Interest Groups (SIG) meetings. Those meetings provided a lot of insight into what was going on behind the scenes with the project and also offered a venue to have a real-time chat with the core maintainers about what things on the todo list were most important to me and the goals I've been working towards.&lt;/p&gt;

&lt;p&gt;The combination of Gitter and the SIG meetings on Zoom have been incredibly  helpful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tickets pre v0.6.0
&lt;/h2&gt;

&lt;p&gt;Before v0.6.0 came out I opened &lt;a href="https://github.com/open-telemetry/opentelemetry-ruby/issues/312" rel="noopener noreferrer"&gt;Unable to add tags to spans #312&lt;/a&gt;. The discussion on that ticket was very educational. It also lead to &lt;a href="https://github.com/open-telemetry/opentelemetry-ruby/pull/348" rel="noopener noreferrer"&gt;Export resources from Jaeger #348&lt;/a&gt; being done and included in the 0.6.0 release. One of the key reasons this is important is that it allows for setting both basic and custom attributes (tags) that prior to 0.6.0 had to be tacked on by the external Jaeger agent.&lt;/p&gt;

&lt;p&gt;One of the key tickets I was watching was &lt;a href="https://github.com/open-telemetry/opentelemetry-ruby/pull/277" rel="noopener noreferrer"&gt;OTLP exporter #277&lt;/a&gt; but &lt;a href="https://github.com/protocolbuffers/protobuf/issues/1594" rel="noopener noreferrer"&gt;protocolbuffers/protobuf#1594&lt;/a&gt; threw a big wrench into it being a path forward for me: the problem is that right now the gRPC used by OTLP doesn't support JRuby... most of the apps I am working on run on JRuby. Fortunately, &lt;a href="https://github.com/open-telemetry/opentelemetry-ruby/issues/231" rel="noopener noreferrer"&gt;#231&lt;/a&gt; was already scheduled to be in the 0.6.0 release. That issues called for implementing Binary Thrift over HTTP as a transport for the Jaeger exporter.&lt;/p&gt;

&lt;p&gt;The reason that, initially, I was watching #277 and then watched #231 is that either of those being done would mean I no longer had to have a local Jaeger agent; I could, instead, send traces directly from the application via a TCP-based protocol directly to the OpenTelemetry collector.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrading to v0.6.0
&lt;/h2&gt;

&lt;p&gt;Once the new release came out, it was time to start upgrading so that attributes could be set in code and Jaeger agents could be ditched. It also meant that I could start working in earnest on VMPooler since there was no longer a dependency on the agent. Both sets of work started in parallel at this point... which, in hind sight, might not have been my best plan. Read on to see what I mean.&lt;/p&gt;

&lt;h2&gt;
  
  
  VMPooler part 1: working locally
&lt;/h2&gt;

&lt;p&gt;Things started out pretty simple When I started working on adding the OTel instrumentation to VMPooler. That is, until I ran &lt;code&gt;docker-compose up&lt;/code&gt; and watched something about the new tracing code cause the app to crash with only this error as a clue:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;E, [2020-09-12T00:36:44.445784 #1] ERROR -- : unexpected error in Jaeger::CollectorExporter#export - Not enough bytes remain in buffer&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I started digging and found some Jaeger docs for how to &lt;a href="https://www.jaegertracing.io/docs/1.18/performance-tuning/#increase-in-memory-queue-size" rel="noopener noreferrer"&gt;Increase in-memory queue size&lt;/a&gt;. My initial impression after reading those docs was that I needed the Jaeger exporter to either flush faster or have a bigger buffer. I couldn't find a way to do that but I did remember seeing that there was an alternative to the &lt;code&gt;SimpleSpanProcessor&lt;/code&gt; called &lt;code&gt;BatchSpanProcessor&lt;/code&gt;. Sadly, there were not any docs saying what span processor I should use or how to use each one. Fortunately I didn't give up and poked around in the repository on GitHub and discovered enough info to try it out by reading the comments in &lt;a href="https://github.com/open-telemetry/opentelemetry-ruby/blob/master/sdk/lib/opentelemetry/sdk/trace/export/batch_span_processor.rb" rel="noopener noreferrer"&gt;batch_span_processor.rb&lt;/a&gt;. Though I didn't exactly understand why, I did find that swapping out the span processor fixed my issue.&lt;/p&gt;

&lt;p&gt;During all of this, I had been posting in a thread on Gitter. As a result, I was given this piece of advice:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I think batch span is generally the way to go for anything outside of basic tests, we should probably improve the language here a bit&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That was pretty enlightening as every single example shows using &lt;code&gt;SimpleSpanProcessor&lt;/code&gt;. I opened &lt;a href="https://github.com/open-telemetry/opentelemetry-ruby/issues/397" rel="noopener noreferrer"&gt;Span Processors are basically undocumented #397&lt;/a&gt; in hopes that this would get clarified in a formal way and am happy to report that it is currently listed as part of the 0.7.0 milestone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Span processors
&lt;/h2&gt;

&lt;p&gt;As mentioned earlier, I was working on both VMPooler's initial setup and the upgrade to 0.6.0 in the other apps at the same time. That work was going smoothly and seemed pretty simple. The problem was that I didn't make the mental connection that I should also swap out the &lt;code&gt;SimpleSpanProcessor&lt;/code&gt; for the &lt;code&gt;BatchSpanProcessor&lt;/code&gt; in ABS, CITH, and NSPooler. This turned out to be a grave oversight as we started having real problems with NSPooler - it was periodically crashing and, as a result, causing problems in our CI pipelines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tracing data helps solve the quandary
&lt;/h3&gt;

&lt;p&gt;None of us could quite put our finger on what was going on with NSPooler at first. It just didn't make any sense. Then, one of my team mates noticed in the &lt;a href="https://lightstep.com/" rel="noopener noreferrer"&gt;Lightstep&lt;/a&gt; interface that the &lt;code&gt;/status&lt;/code&gt; endpoint was taking over 9 seconds to respond. This too was confusing as there was nothing that &lt;em&gt;should&lt;/em&gt; have caused it to slow down like that. It was about this time that I remembered what I had learned a couple of days before while working on VMPooler: never use the &lt;code&gt;SimpleSpanProcessor&lt;/code&gt;. In hopes of the two being related I quickly put up a pull request with this change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  if ENV["NSPOOLER_DISABLE_TRACING"] &amp;amp;&amp;amp; ENV["NSPOOLER_DISABLE_TRACING"].eql?('true')
    puts "Exporting of traces has been disabled so the span processor has been set to a 'NoopSpanExporter'"
&lt;span class="gd"&gt;-   span_processor = OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(
-     OpenTelemetry::SDK::Trace::Export::NoopSpanExporter.new
&lt;/span&gt;&lt;span class="gi"&gt;+   span_processor = OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
+     exporter: OpenTelemetry::SDK::Trace::Export::NoopSpanExporter.new
&lt;/span&gt;    )
  else
    jaeger_host = ENV.fetch('JAEGER_HOST', 'http://localhost:14268/api/traces')
    puts "Exporting of traces will be done over HTTP in binary Thrift format to #{jaeger_host}"
&lt;span class="gd"&gt;-   span_processor = OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(
-     OpenTelemetry::Exporter::Jaeger::CollectorExporter.new(endpoint: jaeger_host)
&lt;/span&gt;&lt;span class="gi"&gt;+   span_processor = OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
+     exporter: OpenTelemetry::Exporter::Jaeger::CollectorExporter.new(endpoint: jaeger_host)
&lt;/span&gt;    )
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lo and behold, that fixed it. And by fixed, I mean that not only did NSPooler stop crashing, but also that response times to the &lt;code&gt;/status&lt;/code&gt; endpoint changed significantly:&lt;/p&gt;

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

&lt;p&gt;The image above is a screenshot from Lightstep's interface comparing the latencies on &lt;code&gt;/status&lt;/code&gt; between our 2.8.0 release and the 2.6.0 one. As you can clearly see, there is a massive difference.&lt;/p&gt;

&lt;p&gt;After seeing how big of an impact this had, I put up PRs for ABS and CITH the following morning to make the same change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tickets and pull requests post v0.6.0
&lt;/h2&gt;

&lt;p&gt;While working on the 0.6.0 upgrades and setting up the new &lt;code&gt;Jaeger::CollectorExporter&lt;/code&gt; I came across &lt;code&gt;OpenTelemetry::SDK::Resources::Constants::SERVICE_RESOURCE[:name]&lt;/code&gt; in its readme. I have not found any docs on this other than in its source code so I opened &lt;a href="https://github.com/open-telemetry/opentelemetry-ruby/issues/379" rel="noopener noreferrer"&gt;OpenTelemetry::SDK::Resources::Constants appears to be undocumented #379&lt;/a&gt;. That ticket is also slated for the 0.7.0 milestone. Besides the requested documentation update, starting a conversation on this topic resulted in a helper method being added to the configurator so that within a configuration block a user can simply call &lt;code&gt;c.service_name = 'my-service'&lt;/code&gt; instead of having to do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SDK&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Resources&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SDK&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Resources&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SERVICE_RESOURCE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I liked this so much that I duplicated the work in &lt;a href="https://github.com/open-telemetry/opentelemetry-ruby/pull/417" rel="noopener noreferrer"&gt;#417&lt;/a&gt; and submitted &lt;a href="https://github.com/open-telemetry/opentelemetry-ruby/pull/426" rel="noopener noreferrer"&gt;feat: Add service_version setter to configurator #426&lt;/a&gt; so that the same could be done for setting an application's version.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/open-telemetry/opentelemetry-ruby/pull/417" rel="noopener noreferrer"&gt;#417&lt;/a&gt; and &lt;a href="https://github.com/open-telemetry/opentelemetry-ruby/pull/426" rel="noopener noreferrer"&gt;#426&lt;/a&gt; combined will allow me to simplify my configuration block like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- c.resource = OpenTelemetry::SDK::Resources::Resource.create(
-   {
-     OpenTelemetry::SDK::Resources::Constants::SERVICE_RESOURCE[:name] =&amp;gt; service_name,
-     OpenTelemetry::SDK::Resources::Constants::SERVICE_RESOURCE[:version] =&amp;gt; version
-   }
- )
&lt;/span&gt;&lt;span class="gi"&gt;+ c.service_name = service_name
+ c.service_version = version
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Resource detectors
&lt;/h2&gt;

&lt;p&gt;Another thing I learned about by way of a chat happening in Gitter was that there is a feature called "resource detectors" that will automatically detect information about where an application is running and add related resources similar to the name and version ones mentioned above. Enabling that was as simple as adding &lt;code&gt;c.resource = OpenTelemetry::Resource::Detectors::AutoDetector.detect&lt;/code&gt; to my configuration block. Doing so allows me to automatically learn quite a bit about both the Kubernetes environment an app is running in and the Google nodes and account on which Kubernetes is running.&lt;/p&gt;

&lt;h2&gt;
  
  
  VMPooler part 2: ship it!
&lt;/h2&gt;

&lt;p&gt;After getting the other applications updated to 0.6.0 and fixing the goof of not replacing the span processors in the other applications I was able to turn my attention back to VMPooler. I got all the initil tracing code into it via &lt;a href="https://github.com/puppetlabs/vmpooler/pull/399" rel="noopener noreferrer"&gt;Add distributed tracing #399&lt;/a&gt;. I also put in a PR to &lt;a href="https://github.com/puppetlabs/vmpooler/pull/401" rel="noopener noreferrer"&gt;Add OTel resource detectors #401&lt;/a&gt; into VMPooler. All that worked locally but, it turns out, I had some missundstandings about what went where gem-wise and also didn't know what all the different Dockerfiles were used for. Both of those got fixed via &lt;a href="https://github.com/puppetlabs/vmpooler/pull/404" rel="noopener noreferrer"&gt;Fix mixup of gem placement. #404&lt;/a&gt; and &lt;a href="https://github.com/puppetlabs/vmpooler/pull/405" rel="noopener noreferrer"&gt;Adding make to the other two Dockerfiles #405&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After those four PRs we were finally able to release version 0.14.9 to both our Mesos cluster and to our staging instance in Kubernetes. When doing the release to our Mesos cluster we added these two environment variables so that tracing would be enabled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"VMPOOLER_TRACING_ENABLED"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"VMPOOLER_TRACING_JAEGER_HOST"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://otel-jthrifthttp-prod.k8s.example.net/api/traces"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first of these is used is used by the code below to effectively turn tracing on and the second maps to &lt;code&gt;tracing_jaeger_host&lt;/code&gt; in it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tracing_enabled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eql?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'false'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Exporting of traces has been disabled so the span processor has been se to a 'NoopSpanExporter'"&lt;/span&gt;
  &lt;span class="n"&gt;span_processor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SDK&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Trace&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Export&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BatchSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;exporter: &lt;/span&gt;&lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SDK&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Trace&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Export&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NoopSpanExporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Exporting of traces will be done over HTTP in binary Thrift format to &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;tracing_jaeger_host&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;span_processor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SDK&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Trace&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Export&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BatchSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;exporter: &lt;/span&gt;&lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Exporter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Jaeger&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CollectorExporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;endpoint: &lt;/span&gt;&lt;span class="n"&gt;tracing_jaeger_host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may have noticed that while I am talking about our Mesos cluster the Jaeger endpoint is set to a url containing &lt;code&gt;k8s&lt;/code&gt;: this is becaues the endpoint is a Kubernetes ingress resource that passes the traffic on to an OTel collector running in the cluster. The instance of VMPooler that we have running in Kubernetes does not need to use the ingress - it, instead, sends traffic directly to the service resource by way of its in-cluster address: &lt;code&gt;http://otel-collector.otel-collector.svc:14268/api/traces&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rolling up spans by Sinatra route
&lt;/h2&gt;

&lt;p&gt;This is all live now and working well... except for one thing: VMPooler highly utilizes url parameters that are part of the url itself such as &lt;a href="https://github.com/puppetlabs/vmpooler/blob/4e7e16e00158807b85fe23a0dabfcd1d215f0b65/lib/vmpooler/api/v1.rb#L723" rel="noopener noreferrer"&gt;&lt;code&gt;get "#{api_prefix}/token/:token/?" do&lt;/code&gt;&lt;/a&gt; and the 0.6.0 version of the Sinatra integration doesn't account for that. The result is that instead of seeing tracing data for the endpoint we actually get unique data sets for each user. Aside from this being less than ideal for seeing how a given endpoint is performing, it also means that all user tokens are exposed within our trace data.&lt;/p&gt;

&lt;p&gt;Fortunately, there is already a fix for this that will be included in 0.7.0: &lt;a href="https://github.com/open-telemetry/opentelemetry-ruby/pull/415" rel="noopener noreferrer"&gt;fix: default to sinatra.route for span name #415&lt;/a&gt;. This PR is incredibly simple on the surface - here's its entire diff:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  def call(env)
&lt;span class="gi"&gt;+   span_name = env['sinatra.route'] || env['PATH_INFO']
+
&lt;/span&gt;    tracer.in_span(
&lt;span class="gd"&gt;-     env['PATH_INFO'],
&lt;/span&gt;&lt;span class="gi"&gt;+     span_name,
&lt;/span&gt;      attributes: { 'http.method' =&amp;gt; env['REQUEST_METHOD'],
                    'http.url' =&amp;gt; env['PATH_INFO'] },
      kind: :server,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is pretty significant though as it will roll up all calls to &lt;code&gt;/api/v1/token/:token/?&lt;/code&gt; into a single data set. Furthermore, I can easily add filters on the OTel collector to redact the actual value of the token before the trace data is stored anywhere. The end result being more useful data that no longer exposes sensitive information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bring on the code samples
&lt;/h2&gt;

&lt;p&gt;At the beginning of this post I promised some real code examples that showed how all this was configured so let's wrap this post up with exactly that.&lt;/p&gt;

&lt;h3&gt;
  
  
  vmpooler.gemspec
&lt;/h3&gt;

&lt;p&gt;Here are the gems that got added to VMPooler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_dependency&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-api'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 0.6.0'&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_dependency&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-exporter-jaeger'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 0.6.0'&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_dependency&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-instrumentation-concurrent_ruby'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 0.6.0'&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_dependency&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-instrumentation-redis'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 0.6.0'&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_dependency&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-instrumentation-sinatra'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 0.6.0'&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_dependency&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-resource_detectors'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 0.6.0'&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_dependency&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-sdk'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 0.6.0'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  lib/vmpooler
&lt;/h3&gt;

&lt;p&gt;There were multiple additions to &lt;code&gt;lib/vmpooler&lt;/code&gt;. The first was to require all the needed gems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Dependencies for tracing&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-api'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-instrumentation-concurrent_ruby'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-instrumentation-redis'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-instrumentation-sinatra'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-sdk'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry/exporter/jaeger'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry/resource/detectors'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next was to add in some new configuration settings so that the needed parameters could be passed in through VMPooler's standard methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;parsed_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:tracing&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parsed_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:tracing&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;parsed_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:tracing&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'enabled'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'VMPOOLER_TRACING_ENABLED'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;parsed_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:tracing&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'enabled'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'false'&lt;/span&gt;
&lt;span class="n"&gt;parsed_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:tracing&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'jaeger_host'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'VMPOOLER_TRACING_JAEGER_HOST'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;parsed_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:tracing&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'jaeger_host'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'http://localhost:14268/api/traces'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last addition here is a helper method that can be used to configure all the tracing bits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure_tracing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;startup_args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tracing_enabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tracing_jaeger_host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;startup_args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;startup_args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'api'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;service_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'vmpooler-api'&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;startup_args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;startup_args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'manager'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;service_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'vmpooler-manager'&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;service_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'vmpooler'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;service_name&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tracing_enabled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eql?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'false'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Exporting of traces has been disabled so the span processor has been se to a 'NoopSpanExporter'"&lt;/span&gt;
    &lt;span class="n"&gt;span_processor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SDK&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Trace&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Export&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BatchSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;exporter: &lt;/span&gt;&lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SDK&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Trace&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Export&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NoopSpanExporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Exporting of traces will be done over HTTP in binary Thrift format to &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;tracing_jaeger_host&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;span_processor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SDK&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Trace&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Export&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BatchSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;exporter: &lt;/span&gt;&lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Exporter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Jaeger&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CollectorExporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;endpoint: &lt;/span&gt;&lt;span class="n"&gt;tracing_jaeger_host&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SDK&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="s1"&gt;'OpenTelemetry::Instrumentation::Sinatra'&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="s1"&gt;'OpenTelemetry::Instrumentation::ConcurrentRuby'&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="s1"&gt;'OpenTelemetry::Instrumentation::Redis'&lt;/span&gt;

    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_span_processor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;span_processor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Resource&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Detectors&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;AutoDetector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detect&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SDK&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Resources&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SDK&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Resources&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SERVICE_RESOURCE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SDK&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Resources&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SERVICE_RESOURCE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:version&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;self.configure_tracing&lt;/code&gt; method isn't quite as complex as it may look. All that code breaks down to this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;set a variable for the service name to be used. This is needed because VMPooler can run as two independent services (api and manager) or as an all-in-one.&lt;/li&gt;
&lt;li&gt;update the service name to include the defined prefix which usually maps to a instance's name such as prod, stage, or test.&lt;/li&gt;
&lt;li&gt;set the span exporter based on if tracing is enabled or not. When it's disabled the &lt;code&gt;NoopSpanExporter&lt;/code&gt; get's used so that no trace data is emmitted. When tracing is enabled the endpoint to which to send the data to is also configured.&lt;/li&gt;
&lt;li&gt;run OTel's configurator and tell it to:

&lt;ol&gt;
&lt;li&gt;enable automated instrumentation for Sinatra, ConcurrentRuby, and Redis&lt;/li&gt;
&lt;li&gt;use the selected span processor&lt;/li&gt;
&lt;li&gt;enable automatic resource detection&lt;/li&gt;
&lt;li&gt;set &lt;code&gt;service.name&lt;/code&gt; and &lt;code&gt;service.version&lt;/code&gt; (note that this is using the verbose method - I'll update it after &lt;a href="https://github.com/open-telemetry/opentelemetry-ruby/pull/426" rel="noopener noreferrer"&gt;#426&lt;/a&gt; is merged into the OTel gems)&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;h3&gt;
  
  
  bin/vmpooler
&lt;/h3&gt;

&lt;p&gt;VMPooler is actually run by calling &lt;code&gt;bin/vmpooler&lt;/code&gt; so that is where the final bit go. First, I needed to bring in a few new settings and make a few new local variables. I did that by adding these lines to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'vmpooler/version'&lt;/span&gt;

&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:config&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'prefix'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;tracing_enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:tracing&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'enabled'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;tracing_jaeger_host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:tracing&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'jaeger_host'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Vmpooler&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VERSION&lt;/span&gt;

&lt;span class="n"&gt;startup_args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ARGV&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With those in place I was able to call the helper method that was added to &lt;code&gt;lib/vmpooler&lt;/code&gt; by adding this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Vmpooler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure_tracing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;startup_args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tracing_enabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tracing_jaeger_host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's all the ruby code that was needed. Beyond that, our Dockerfiles did need the small adjustment of adding the installation of &lt;code&gt;make&lt;/code&gt; so that all the gems would properly install.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;My Sinatra journey is progressing nicely but there is still more to do. Next up is adding some manual instrumentation to VMPooler and ABS and upgrading to 0.7.0 as soon as it comes out so that I can get the changes talked about above.&lt;/p&gt;

&lt;p&gt;With regards to the manual instrumentation part, I have started working on that in &lt;a href="https://github.com/puppetlabs/vmpooler/pull/400" rel="noopener noreferrer"&gt;Add additional data to spans in api/v1.rb #400&lt;/a&gt; but want to do some manual testing before asking for it to be merged. My learnings there will directly influence how I move forward on a similar PR for ABS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: putting the OTel Java agent to use
&lt;/h2&gt;

&lt;p&gt;As a bonus tidbit for anyone who made it through this entire post, I wanted to mention that I have recently started utilizing OTel's Java agent that provides automated instrumentation of applications running on the JVM. I will be blogging about that work too in the very near future.&lt;/p&gt;

</description>
      <category>opentelemetry</category>
      <category>kubernetes</category>
      <category>ruby</category>
      <category>observability</category>
    </item>
    <item>
      <title>OpenTelemetry Part 2: Redoing Instrumentation</title>
      <dc:creator>Gene Liverman</dc:creator>
      <pubDate>Mon, 07 Sep 2020 01:30:00 +0000</pubDate>
      <link>https://forem.com/puppet/opentelemetry-part-2-redoing-instrumentation-4e9i</link>
      <guid>https://forem.com/puppet/opentelemetry-part-2-redoing-instrumentation-4e9i</guid>
      <description>&lt;p&gt;For part two of my journey in using OpenTelemetry (Otel) with Sinatra I am replacing my Lightstep instrumentation with the OTel version. Besides updating the instrumentation, I am also deploying production instances of an OTel Collector and Jaeger. The goal of part 2 is to have my first three applications shipping traces to both a local Jaeger instance and to Lightstep in both test and production and to have Jaeger included in the Docker compose workflows used during development.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Be sure to also see part 3 of this series as some limitations talked about here no longer exist. I am leaving them in this part because they are a relevant part of my journey.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Recapping current reality
&lt;/h2&gt;

&lt;p&gt;To set the stage for this phase I want to recap where things are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is a pre-existing Jaeger instance deployed to my test GKE environment that was setup over a year ago as part of another project&lt;/li&gt;
&lt;li&gt;An OTel collector has been deployed to test also and is shipping to the local Jaeger only&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hub.docker.com/r/genebean/sinatra-otel-demo" rel="noopener noreferrer"&gt;genebean/sinatra-otel-demo&lt;/a&gt; has been deployed to test and is shipping traces to the OTel collector&lt;/li&gt;
&lt;li&gt;CITH has Lightstep instrumentation setup and shipping to Lightstep in both test and prod by way of a &lt;a href="https://github.com/lightstep/reverse-proxy" rel="noopener noreferrer"&gt;Lightstep proxy&lt;/a&gt; that is deployed as part of the CITH Helm chart&lt;/li&gt;
&lt;li&gt;ABS and NSPooler have Lightstep instrumentation added in but are not yet shipping anywhere&lt;/li&gt;
&lt;li&gt;No full fledged &lt;a href="https://github.com/lightstep/lightstep-satellite-helm-chart" rel="noopener noreferrer"&gt;Lightstep satellites&lt;/a&gt; have been deployed yet&lt;/li&gt;
&lt;li&gt;No OTel collector has been deployed to production yet&lt;/li&gt;
&lt;li&gt;ABS and NSPooler are still running in Apache Mesos... they will be moving to Kubernetes soon but not before I implement tracing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  CITH
&lt;/h2&gt;

&lt;p&gt;Just as was done originally, I am starting my new round of instrumentation with our CI Triage Helper (CITH) application. I am doing this because it is the simplest of our applications and because it sits besides our CI pipeline. This makes it much safer to experiment with than one that is directly part of CI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gems
&lt;/h3&gt;

&lt;p&gt;The first step in this conversion is to replace &lt;code&gt;ls-trace&lt;/code&gt; in the &lt;code&gt;Gemfile&lt;/code&gt; with the bits from OpenTelemetry. In the case of CITH, that means adding this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-api'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 0.5.1'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-exporters-jaeger'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 0.5.0'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-instrumentation-restclient'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 0.5.0'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-instrumentation-sinatra'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 0.5.0'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-sdk'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 0.5.1'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;p&gt;The first change in &lt;code&gt;config.ru&lt;/code&gt; is updating the requires from simply being &lt;code&gt;ddtrace&lt;/code&gt; to all the OTel components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-api'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry/exporters/jaeger'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-instrumentation-restclient'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-instrumentation-sinatra'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'opentelemetry-sdk'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The change in &lt;code&gt;config.ru&lt;/code&gt; is to swap out the configuration block from Lightstep for the one needed by OTel:&lt;/p&gt;

&lt;h4&gt;
  
  
  Lightstep
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'CITH_LIGHTSTEP_TRACING_TOKEN'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"CITH_LIGHTSTEP_TRACING_TOKEN was passed so tracing will be enabled."&lt;/span&gt;
  &lt;span class="no"&gt;Datadog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="ss"&gt;:sinatra&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="ss"&gt;:mongo&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="ss"&gt;:rest_client&lt;/span&gt;


    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distributed_tracing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;propagation_inject_style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Datadog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Ext&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DistributedTracing&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PROPAGATION_STYLE_B3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distributed_tracing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;propagation_extract_style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Datadog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Ext&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DistributedTracing&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PROPAGATION_STYLE_B3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tracer&lt;/span&gt; &lt;span class="ss"&gt;tags: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s1"&gt;'lightstep.service_name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'cith-api'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s1"&gt;'lightstep.access_token'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'CITH_LIGHTSTEP_TRACING_TOKEN'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="s1"&gt;'service.version'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;service_name: &lt;/span&gt;&lt;span class="s1"&gt;'cith'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="n"&gt;jaeger_host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;port: &lt;/span&gt;&lt;span class="mi"&gt;6831&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'No CITH_LIGHTSTEP_TRACING_TOKEN passed. Tracing is disabled.'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  OTel
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;jaeger_host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'JAEGER_HOST'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'localhost'&lt;/span&gt;

&lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SDK&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="s1"&gt;'OpenTelemetry::Instrumentation::Sinatra'&lt;/span&gt;
  &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="s1"&gt;'OpenTelemetry::Instrumentation::RestClient'&lt;/span&gt;

  &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_span_processor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SDK&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Trace&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Export&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SimpleSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Exporters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Jaeger&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Exporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;service_name: &lt;/span&gt;&lt;span class="s1"&gt;'cith'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="n"&gt;jaeger_host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;port: &lt;/span&gt;&lt;span class="mi"&gt;6831&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that in this configuration block there is nothing about MongoDB... that is because its bits have not been ported over from ddtrace yet (see &lt;a href="https://github.com/open-telemetry/opentelemetry-ruby/issues/257" rel="noopener noreferrer"&gt;issue #257&lt;/a&gt;).&lt;/p&gt;

&lt;h4&gt;
  
  
  Jaeger
&lt;/h4&gt;

&lt;p&gt;Another difference here is that the spans are being output to a Jaeger agent... via UDP. As of today, this is the only exporter that OpenTelemetry for ruby has. There is active work to implement others but, in the mean time, this means that some extra steps are needed to deal with the UDP traffic since it's really designed to be sent to localhost. More on this later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local development via docker-compose.yml
&lt;/h3&gt;

&lt;p&gt;CITH, like many of our apps, comes with a &lt;code&gt;docker-compose.yml&lt;/code&gt; to facilitate local development and testing. In the case of CITH, that file needed a few changes to switch from Lightstep to OTel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the passed in list of environment variables was updated to simply be the Jaeger host's address&lt;/li&gt;
&lt;li&gt;the Lightstep proxy was replaced with a Jaeger all-in-one instance&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Done... but not really
&lt;/h3&gt;

&lt;p&gt;This wrapped up all the code changes to CITH and was pretty easy to test out locally. The problem is that this was no where near the end of the road with regards to migrating CITH over to OpenTelemetry: its Helm chart still needed updating and additional infrastructure needed to be deployed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Charts, charts, charts
&lt;/h2&gt;

&lt;p&gt;To be able to deploy the new version of CITH and validate everything worked as desired I also needed to update my OTel collector to emit traces to Lightstep and I needed to deploy a Lightstep satellite via their Helm chart. I also needed to deploy these latter two to our production environment along with a Jaeger instance. The way I decided to tackle this was to update CITH's chart so that it could get data to the OTel collector, then deploy a satellite, then update the collector for Lightstep, and finally deal with upgrading the current Jaeger and preparing for deploying an initial one to production.&lt;/p&gt;

&lt;h3&gt;
  
  
  CITH's Helm chart
&lt;/h3&gt;

&lt;p&gt;CITH's chart was actually pretty easy: I just needed to delete all the Lightstep related bits from it and add a Jaeger sidecar to the API's pod. The sidecar is able to collect the traces emitted to localhost and then send them via gRPC to the OTel collector. Here are the flags I added to the Jaeger agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--reporter.grpc.host-port={{ .Values.jaeger_host }}:14250&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--reporter.type=grpc&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--jaeger.tags=helm_chart={{ include "dio-cith.chart" . }},service.version={{ .Chart.AppVersion }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking those args entries down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the first and second ones combined send traces via gRBC to the Jaeger input of the OTel collector&lt;/li&gt;
&lt;li&gt;the third one adds some tags that get converted into OTel attributes&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Why add a sidecar?
&lt;/h4&gt;

&lt;p&gt;Remember earlier when I mentioned that the Jaeger exporter is really only intended for sending to localhost? Well, that is one reason we need a sidecar. The other is that there isn't currently a way to add tags like &lt;code&gt;service.version&lt;/code&gt; without using the sidecar. That functionality is coming per work done to fix &lt;a href="https://github.com/open-telemetry/opentelemetry-ruby/issues/312" rel="noopener noreferrer"&gt;#312&lt;/a&gt; but, in between now and then, this is what I can do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Satellite Deployment
&lt;/h3&gt;

&lt;p&gt;When I started on this phase there wasn't a repository for Lightstep's Helm chart (&lt;a href="https://github.com/lightstep/lightstep-satellite-helm-chart/issues/1" rel="noopener noreferrer"&gt;#1&lt;/a&gt;). Fortunately, the fine folks at Lightstep were willing to rectify this and it is now available at both &lt;a href="https://artifacthub.io/packages/helm/lightstepsatellite/lightstep" rel="noopener noreferrer"&gt;Artifact HUB&lt;/a&gt; and &lt;a href="https://hub.helm.sh/charts/lightstepsatellite/lightstep" rel="noopener noreferrer"&gt;Helm Hub&lt;/a&gt;. I deployed their chart and it mostly "just worked" - the exception is that I never did get the statsd metrics coming out of it to work right with a Prometheus statsd exporter. For now I have simply given up on this aspect of monitoring the satellite and, instead, am hoping they implement native Prometheus metrics. Docs for all of this can be found &lt;a href="https://docs.lightstep.com/docs/install-and-configure-satellites" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Collector outputs
&lt;/h3&gt;

&lt;p&gt;With the satellites up and running it is time to add Lightstep as a destination in my collector configuration. Doing so is as simple as adding this to my exporters section and then adding &lt;code&gt;otlp/lightstep&lt;/code&gt; to the array of locations listed in the exporters part of the pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;otlp/lightstep&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lightstep.lightstep.svc:8184"&lt;/span&gt;
  &lt;span class="na"&gt;insecure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lightstep-access-token"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Values.lightstepAccessToken&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simply sets up an exporter that sends data in OTLP format to the service named &lt;code&gt;lightstep&lt;/code&gt; in the &lt;code&gt;lightstep&lt;/code&gt; namespace on port 8184 and adds a header that includes the access token that matches the desired project in Lightstep. Fortunately, this is all that is needed to get data to Lightstep - no custom exporter or other hacks at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Jaeger redo
&lt;/h3&gt;

&lt;p&gt;I was actually dreading this step but, thanks to a tip from a coworker, it turned out to be really easy as Jaeger now provides a &lt;code&gt;jaeger&lt;/code&gt; chart for deploying their their stack via &lt;a href="https://jaegertracing.github.io/helm-charts/" rel="noopener noreferrer"&gt;https://jaegertracing.github.io/helm-charts/&lt;/a&gt;. For my setup, all I need to do is create a shallow Helm chart that has the &lt;code&gt;jaeger&lt;/code&gt; chart as a dependency and includes this &lt;code&gt;values.yaml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jaeger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;provisionDataStore&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cassandra&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;elasticsearch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;elasticsearch&lt;/span&gt;
  &lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;collector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;autoscaling&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;minReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;maxReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ingress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;certmanager.k8s.io/cluster-issuer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;letsencrypt-prod&lt;/span&gt;
        &lt;span class="na"&gt;kubernetes.io/ingress.class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
        &lt;span class="na"&gt;kubernetes.io/tls-acme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
      &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;jaeger-test.k8s.example.net&lt;/span&gt;
      &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;jaeger-test.k8s.example.net&lt;/span&gt;
          &lt;span class="na"&gt;secretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jaeger-test.k8s.example.net-tls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploying it all
&lt;/h2&gt;

&lt;p&gt;With all this in place I deployed everything to test, and then to production, and was able to see data from CITH in both Jaeger and Lightstep for both 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  ABS and NSPooler
&lt;/h2&gt;

&lt;p&gt;Getting to this point has taken longer than anticipated but has been very fruitful as it has provided what I imagine to be a good foundation for all the other things I plan to do. Getting ABS and NSPooler updated to use this is basically a rinse and repeat of CITH so I am not repeating the details here. The one exception is that they still run in our Mesos cluster so an extra step is needed: I need a place to send their traces. I solved this by taking advantage of a host we had previously setup as a static Docker host. I simply deployed a &lt;a href="https://www.jaegertracing.io/docs/1.18/architecture/#agent" rel="noopener noreferrer"&gt;Jaeger Agent&lt;/a&gt; to that host that listened on port 6831/udp and gave it basically the same startup arguments that were used in CITH's Helm chart. This was all done with Puppet code via the &lt;a href="https://forge.puppet.com/puppetlabs/docker" rel="noopener noreferrer"&gt;puppetlabs/docker module&lt;/a&gt; and the following entry in &lt;a href="https://puppet.com/docs/puppet/latest/hiera_intro.html" rel="noopener noreferrer"&gt;Hiera&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;docker::run_instance::instance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;jaeger-agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;jaegertracing/jaeger-agent:latest'&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;6831:6831/udp'&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--reporter.grpc.host-port=otel-jgrpc-prod.k8s.example.net:443&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--reporter.type=grpc&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--reporter.grpc.tls.enabled=true&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--reporter.grpc.tls.skip-host-verify=true'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make this work I also had to add an ingress resource to my OTel collector's deployment. The ingress looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- if .Values.ingress.enabled -&lt;/span&gt;&lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- $fqdn&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;= .Values.ingress.protocol.jaegerGrpc.fqdn -&lt;/span&gt;&lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- $nameSuffix&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;= .Values.ingress.protocol.jaegerGrpc.nameSuffix -&lt;/span&gt;&lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- $svcPortNumber&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;= .Values.ingress.protocol.jaegerGrpc.svcPortNumber -&lt;/span&gt;&lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;include "dio-otel-collector.fullname" .&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;-{{ $nameSuffix }}&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- include "dio-otel-collector.labels" . | nindent 4&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
  &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- with .Values.ingress.annotations&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- toYaml . | nindent 4&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
    &lt;span class="na"&gt;nginx.ingress.kubernetes.io/backend-protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GRPC"&lt;/span&gt;
  &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- end&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;$fqdn | quote&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
      &lt;span class="na"&gt;secretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;$fqdn&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;-tls&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;$fqdn | quote&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
      &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
            &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otel-collector&lt;/span&gt;
              &lt;span class="na"&gt;servicePort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;$svcPortNumber&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- end&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only thing special about this ingress is this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;nginx.ingress.kubernetes.io/backend-protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GRPC"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That ingress is paired with this entry in my &lt;code&gt;values.yaml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ingress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;certmanager.k8s.io/cluster-issuer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;letsencrypt-prod&lt;/span&gt;
    &lt;span class="na"&gt;kubernetes.io/ingress.class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
    &lt;span class="na"&gt;kubernetes.io/tls-acme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
  &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;jaegerGrpc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="na"&gt;fqdn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otel-jgrpc-test.k8s.example.net&lt;/span&gt;
      &lt;span class="na"&gt;nameSuffix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jaeger-grpc&lt;/span&gt;
      &lt;span class="na"&gt;svcPortNumber&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14250&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That line is required to make the gRPC connection actually work. Beyond that, all it's doing is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;listening for a gRPC connection over TLS on port 443,&lt;/li&gt;
&lt;li&gt;terminating the TLS connection once received, and&lt;/li&gt;
&lt;li&gt;forwarding the unencrypted traffic to the &lt;code&gt;otel-collector&lt;/code&gt; service on port 14250.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With this in place data started flowing from ABS and NSPooler too!&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;Part 3 of this series will cover how things changed, and got way better, when version 0.6.0 of the &lt;code&gt;opentelemetry-*&lt;/code&gt; gems came out. It will also talk about some additional learnings and getting VMPooler added into the mix of things sending tracing data.&lt;/p&gt;

</description>
      <category>opentelemetry</category>
      <category>kubernetes</category>
      <category>ruby</category>
      <category>observability</category>
    </item>
    <item>
      <title>Burnout sucks</title>
      <dc:creator>Gene Liverman</dc:creator>
      <pubDate>Wed, 19 Aug 2020 03:35:37 +0000</pubDate>
      <link>https://forem.com/genebean/burnout-sucks-3i49</link>
      <guid>https://forem.com/genebean/burnout-sucks-3i49</guid>
      <description>&lt;p&gt;My favorite personal project is an application called &lt;a href="https://piweatherrock.technicalissues.us" rel="noopener noreferrer"&gt;PiWeatherRock&lt;/a&gt;... or it was before I dove in head-first working to update it and create a community for it’s users. Tons of enthusiasm morphed into something else and, before I realized what was happening, I didn’t even want to touch my computer after work. Months have passed since I even opened anything related to the project and, as best as I can tell, this is that thing I’ve seen others talk about called “burnout.” &lt;/p&gt;

&lt;p&gt;I'm writing all this in hopes that someone else who's coming up on their own burnout will recognize these same signs in themselves early enough to head it off. The first thing I suggest keeping an eye out for is a project that all of a sudden is all you can think about and all you do when off from work. For me, this instant and massively intense focus was all consuming. In hindsight, I have no doubt that letting it consume me is what lead to my current state. The next thing I think I should have picked up on was slowly starting to avoid looking at the notifications related to my project. For example, if you go from being excited when someone submits a pull request to avoiding reviewing it you may be starting to burn out. For me, not only did that happen, but I also started avoiding the Gitter that I had setup as a way to bring my community of users together... it's pretty disheartening to realize that you don't even want to engage with other people excited about using something you authored or maintain. To this day, I still can't bring myself to open my own Gitter.&lt;/p&gt;

&lt;p&gt;Unfortunately, I don't have an any answers to this other than try to head it off before it consumes your enthusiasm.&lt;/p&gt;

&lt;p&gt;If you are reading this in hopes of learning what my plan is for PiWeatherRock, all I can say is that I hope to resume working on it before the version I have running on my TV every morning stops working at the end of 2021. I have friends who would like to have their own PiWeatherRock and can't do so without more work being done due to no new API keys being issued... I agree that this sucks. All I can say is that I really do think time will allow my enthusiasm to return.&lt;/p&gt;

</description>
      <category>mentalhealth</category>
    </item>
  </channel>
</rss>
