<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
 <title>Gluecode Journal</title>
 <description>Journal, Blog, Brain Dump, etc</description>
 <link>https://www.gluecode.net/blog/</link>
 <atom:link href="https://www.gluecode.net/blog/rss.xml" rel="self" type="application/rss+xml" />
 <lastBuildDate>Wed, 20 May 2026 22:03:23 +0000</lastBuildDate>
<item>
          <title>Consider No</title>
          <description><![CDATA[<p>I know I've written a lot, at length, about <a
href="https://www.gluecode.net/blog/2017/08/15/its-all-politics.html">politics</a>
and <a
href="https://www.gluecode.net/blog/2017/08/22/ignorance-costs-us-everything.html">lessons</a>
<a
href="https://www.gluecode.net/blog/2017/08/03/your-ops-team-is-dying.html">learned</a>.
My <a
href="https://www.gluecode.net/blog/2026/04/20/sequences-of-ai.html">most
recent post</a> is inherently political.</p>
<p>I thought maybe white supremacy would be extinquished. I thought
extensive antitrust enforcement would change company behavior. I thought
maybe, just maybe, customer sentiment would get companies to back off on
shoving more ads, tracking, genAI, down our throats. I thought maybe a
collective upset would make companies see we don't want the death of
generalized, cheap computing.</p>
<p>None of that seems to have happened, or worked.</p>
<p>None of this is a matter of progression, or evolution, or people
learning. The information is there. People were loud about it. Those who
we keep thinking we can "change their mind" or change their behavior,
don't.</p>
<p>We keep giving chances, and exceptions, and <em>trust</em> to those
who have literally abused every opportunity they've been given.
Companies, republicans, anyone with power -- they take the stance of
"fuck you, I do what I want."</p>
<p>So what are we to do?</p>
<p>At some point, any person needs to be told "no." If
corporations-are-people (ugh), then we need to tell corporations,
government, anybody who's starting down the path of ruining what's good
for the rest of us the full and complete sentence:</p>
<pre><code>No.</code></pre>]]></description>
          <link>https://gluecode.net/blog/2026/05/13/looking-back.html</link>
          <guid>https://gluecode.net/blog/2026/05/13/looking-back.html</guid>
          <pubDate>Wed, 13 May 2026 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>How to handle AI inputs as a culture</title>
          <description><![CDATA[<p>As much as I <em>hate</em> generative-LLM output in systems, and how
quickly it's being pushed as the cure-all for business problems, I have
to admit that a broken clock can be "right" every so often. LLM outputs
are rapidly becoming as useful as boilerplate generators, or fuzzers, or
the <a
href="https://en.wikipedia.org/wiki/Rapid_application_development">RAD</a>
IDE environments.</p>
<p>How we as a society are reacting to "AI" outputs reminded me of the
immune response of the human body -- namely, some of us are okay with it
(largely because the use case doesn't matter) and some of us are VERY
MUCH NOT OKAY with it, to the point where we have violent reactions to
its existence in our spaces.</p>
<h2 id="generative-llm-virus">Generative LLM = Virus</h2>
<p>As one of my favorite authors put it:</p>
<blockquote>
<p>Imagine that you encounter a signal. It is structured, and dense with
information. It meets all the criteria of an intelligent transmission.
Evolution and experience offer a variety of paths to follow,
branch-points in the flowcharts that handle such input. Sometimes these
signals come from conspecifics who have useful information to share,
whose lives you'll defend according to the rules of kin selection.
Sometimes they come from competitors or predators or other inimical
entities that must be avoided or destroyed; in those cases, the
information may prove of significant tactical value. Some signals may
even arise from entities which, while not kin, can still serve as allies
or symbionts in mutually beneficial pursuits. You can derive appropriate
responses for any of these eventualities, and many others.</p>
<p>You decode the signals, and stumble</p>
<p>[...]</p>
<p>There are no meaningful translations for these terms. They are
needlessly recursive. They contain no usable intelligence, yet they are
structured intelligently; there is no chance they could have arisen by
chance.</p>
<p>The only explanation is that something has coded nonsense in a way
that poses as a useful message; only after wasting time and effort does
the deception becomes apparent. The signal functions to consume the
resources of a recipient for zero payoff and reduced fitness. The signal
is a virus.</p>
<p>The signal is an attack.</p>
</blockquote>
<p>We aren't wrong to treat the invasion of plausibly-looking bad data
like we would any virus. The problem is that our "programming immune
systems" haven't had to deal with such an onslaught of plausible-but-bad
data before. As Paracelsus said, "the dose makes the poison."</p>
<p>So, extending the metaphor of the human body, let's look at how our
bodies have <em>actually</em> dealt with previous invasions,
specifically of retroviruses -- the kind that want to persist their
sequences into our bodies and proliferate forever.</p>
<h2 id="retroviral-response">Retroviral Response</h2>
<p><a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC228468/">A Survey
of Human Genes of Retroviral Origin</a> shows us that ~8% of our tissue
sequences have retroviral components. Eight percent of anything isn't
all that much, but what the document shows is the sequences are not
evenly distributed. Places like the placenta, the testes, or even the
skin would have 10-100x the retroviral sequences as tissues of the
thymuss, or the bone marrow. And there were almost <em>no</em> sequences
to be found in the heart or the liver.</p>
<figure>
<img
src="https://www.gluecode.net/images/reference/retroviral_table_fig4.jpg"
alt="Figure 4 of the paper, showing a greyscale table of relevant sequences and their prevalence across 19 tissues sampled" />
<figcaption aria-hidden="true">Figure 4 of the paper, showing a
greyscale table of relevant sequences and their prevalence across 19
tissues sampled</figcaption>
</figure>
<p>This tells us that, where having some unexpected coding sequences or
errors is low-risk or even expected (the placenta has to deal with a
literal foreign-body with its own DNA for 40 weeks), introduction of
viral material isn't all that important. In some cases, it might have <a
href="https://pmc.ncbi.nlm.nih.gov/articles/PMC6177113/">even been
helpful</a> to the reproductive outcomes of our species.</p>
<p>Conversely, when you have a very critical system like the liver or
the heart, nothing can be tweaked or modified or replaced. Every part of
that system needs to function as-intended, or the host
<em>dies</em>.</p>
<p>The human body has responded and set up a procedure to fight the
invasion of viral content -- terminate the invaders, sure, but also,
leave the battlefield alone when it's done.</p>
<p>We're going to have <em>some</em> volume of AI-generated content in
our software, in our libraries, our videos, etc, because it's not
critical for the things we do to be 100% perfect every time. If we treat
the presence of AI content as a "kill it with fire" response every time,
we'll end up in the equivalent of a <a
href="https://en.wikipedia.org/wiki/Cytokine_storm">cytokine storm</a>
and burn ourselves out (or remove ourselves entirely) just trying to
fight it.</p>
<p>If only we'd fought the nazis with such fervor.</p>
<hr />
<p>Citations for relevant papers:</p>
<blockquote>
<p>de Parseval N, Lazar V, Casella JF, Benit L, Heidmann T. Survey of
human genes of retroviral origin: identification and transcriptome of
the genes with coding capacity for complete envelope proteins. J Virol.
2003 Oct;77(19):10414-22. doi: 10.1128/jvi.77.19.10414-10422.2003. PMID:
12970426; PMCID: PMC228468.</p>
</blockquote>
<blockquote>
<p>Chuong EB. The placenta goes viral: Retroviruses control gene
expression in pregnancy. PLoS Biol. 2018 Oct 9;16(10):e3000028. doi:
10.1371/journal.pbio.3000028. PMID: 30300353; PMCID: PMC6177113.</p>
</blockquote>]]></description>
          <link>https://gluecode.net/blog/2026/04/20/sequences-of-ai.html</link>
          <guid>https://gluecode.net/blog/2026/04/20/sequences-of-ai.html</guid>
          <pubDate>Mon, 20 Apr 2026 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Neovim and the Terminal</title>
          <description><![CDATA[<p>I've been using "alternative Terminal emulators" on MacOS for...
quite a while. It's one of the few things <a
href="https://neovim.io/doc/user/faq.html#_how-can-i-use-">I
<em>must</em> do for Neovim</a> with all its colors and syntax
highlighting, because otherwise why would I have a true-color scheme and
all that syntax highlighting?</p>
<p>The reality is I don't use a lot of special colors in my editor
anyway. My default <a
href="https://git.gluecode.net/active/dotfiles.git/tree/.vimrc">.vimrc</a>
even turns off syntax highlighting and colors. So now I've got a <a
href="https://www.lazyvim.org">LazyVim</a> enabled setup with true-color
using <a href="https://ghostty.org">GhosTTY</a> as my emulator and... I
do all that for a <a
href="https://neovim.io/doc/user/faq.html#_how-to-change-cursor-color-in-the-terminal?">cursor-color
change</a> when in various modes. I even set up terminal color schemes
so that it would take advantage of the <a
href="https://git.gluecode.net/active/dotfiles.git/tree/.config/ghostty/config">terminal-provided
palette</a>.</p>
<p>I don't think MacOS terminal is actually unable to do any of this,
since I'm able to "see" the cursor with alternating contrast colors in
native vim... so what am I missing?</p>
<p>Turns out, it was my neovim colorscheme. I had already switched over
MacOS to the "Developer Colors" palette, but I was still seeing "wrong"
colors inside neovim. By switching to the <a
href="https://github.com/neovim/neovim/blob/master/runtime/colors/quiet.vim">quiet</a>
colorscheme instead of <a
href="https://github.com/nvim-mini/mini.nvim?tab=readme-ov-file#plugin-color-schemes">minischeme</a>,
I'm now able to get nice, terminal-color-highlighted selections and
alternating cursor colors.</p>
<h2 id="solution">Solution</h2>
<ol type="1">
<li>New MacOS Terminal Profile --&gt; Change color palette to
"Developer" <img
src="https://www.gluecode.net/images/reference/macos_term_developer.png"
alt="Picture showing where to select the &quot;Developer&quot; color palette" /></li>
<li>Neovim --&gt; use the colorscheme "quiet"</li>
<li>Do a happy dance</li>
</ol>]]></description>
          <link>https://gluecode.net/blog/2026/02/16/neovim-and-terminals.html</link>
          <guid>https://gluecode.net/blog/2026/02/16/neovim-and-terminals.html</guid>
          <pubDate>Mon, 16 Feb 2026 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>How DHCP nerfed my NIC</title>
          <description><![CDATA[<p>Bought a new <a
href="https://www.amazon.com/dp/B07DHDW59N">10GbE-capable NIC using the
intel 550 chipset</a>. I had purchased one <a
href="https://www.amazon.com/dp/B077VDKBL1">previously</a> but that was
for when I had an Eero Pro unit with a 10Gbit port on the unit.</p>
<p>Now that I've "downgraded" my network to a 2.5gbit switch, I needed a
unit that could do the intermediate steps between gigabit and 10GbE. And
yes, I've heard much moaning about how the 2.5 and 5GbE are just hobbled
steps on the way to full 10GbE -- doesn't matter, it's what we have and
we need it to work at line speed.</p>
<p>Problem is, the DHCP auto-negotiation speeds only allow 10GbE and
1GbE; nothing else in-between will autonegotiate. Turns out <a
href="https://git.gluecode.net/mirrors/freebsd-src.git/tree/sys/dev/ixgbe/if_ix.c#n1813">this
is by design</a>!</p>
<pre><code> * 2.5G and 5G autonegotiation speeds on X550
 * are disabled by default due to reported
 * interoperability issues with some switches.</code></pre>
<h2 id="how-do-i-fix-it">How do I fix it?</h2>
<p>First, accept that you're gonna have to let go of DHCP for a minute.
If you can't do without DHCP on your device, that is... for a future
post! Yeah! &gt;_&gt;</p>
<p>My unit is an X550-T2 unit, meaning it has two ports:
<code>ix0</code> and <code>ix1</code>, so I have to double-up my
commands here. The <em>active</em> port that I selected is
<code>ix1</code> if you want the absolute must-do lines.</p>
<p>First we have to add some overrides to the <a
href="https://man.freebsd.org/cgi/man.cgi?query=loader.conf&amp;manpath=FreeBSD+15.0-RELEASE+and+Ports">loader.conf</a>
because the <a
href="https://man.freebsd.org/cgi/man.cgi?query=ix&amp;sektion=4&amp;manpath=FreeBSD+15.0-RELEASE+and+Ports">ix</a>
driver does not allow changes to advertised speeds after boot.</p>
<pre><code>[/boot/loader.conf]

if_ix_load=&quot;YES&quot;
hw.ix.0.advertise_speed=0x10
hw.ix.1.advertise_speed=0x10
dev.ix.0.advertise_speed=0x10
dev.ix.1.advertise_speed=0x10</code></pre>
<p>The <code>0x10</code> is special because the (as said before) those
speeds are <a
href="https://git.gluecode.net/mirrors/freebsd-src.git/tree/sys/dev/ixgbe/if_ix.c#n5192">disabled
by default</a>:</p>
<pre><code>/************************************************************************
 * ixgbe_get_default_advertise - Get default advertised speed settings
 *
 *   Formatted for sysctl usage.
 *   Flags:
 *     0x1 - advertise 100 Mb
 *     0x2 - advertise 1G
 *     0x4 - advertise 10G
 *     0x8 - advertise 10 Mb (yes, Mb)
 *     0x10 - advertise 2.5G (disabled by default)
 *     0x20 - advertise 5G (disabled by default)
 ************************************************************************/</code></pre>
<p>Then we reboot. This <em>should have</em> let DHCP autonegotiate to a
2.5Gbit line speed, but it didn't. I went through a few more options and
finally said "I must tell the machine what to do."</p>
<p>Thus I changed the following lines in my <a
href="https://man.freebsd.org/cgi/man.cgi?query=rc.conf&amp;sektion=5&amp;manpath=FreeBSD+15.0-RELEASE+and+Ports">rc.conf</a>:</p>
<pre><code>[/etc/rc.conf]

#ifconfig_ix1=&quot;DHCP&quot;
ifconfig_ix1=&quot;inet xxx.xxx.xxx.xxx netmask 0xffffff00 media 2500Base-T&quot;
defaultrouter=&quot;xxx.xxx.xxx.xxx&quot;</code></pre>
<p>Now it works! And it's pretty snappy. Don't forget to add the
<code>defaultrouter</code> line or you won't be able to reach
anything.</p>]]></description>
          <link>https://gluecode.net/blog/2026/02/14/gigabit_autoselect.html</link>
          <guid>https://gluecode.net/blog/2026/02/14/gigabit_autoselect.html</guid>
          <pubDate>Sat, 14 Feb 2026 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>GoToSocial instance shut down</title>
          <description><![CDATA[<p>For reasons I still don't understand, my GoToSocial instance (at
gts.gluecode.net) stopped receiving updates from federated instances.
This is now the second time it's happened and I still can't sort out
<em>why</em>.</p>
<p>As such, as of today I am reverting my account from
wavefunction@gts.gluecode.net to <a
href="https://mastodon.sdf.org/@wavefunction">wavefunction@mastodon.sdf.org</a>.</p>
<p>I'm rather upset. This was supposed to be sigificantly easier and
we've made it difficult. Again.</p>
<p>I'll set up a native host in the future. Until then, I leave the
social hosting to someone else.</p>
<p>[ Update ] Surprising no one, GTS has a problem redirecting accounts
<em>away</em> to the new instance. Cool cool. Time to just shut it the
hell down.</p>]]></description>
          <link>https://gluecode.net/blog/2026/01/27/social.html</link>
          <guid>https://gluecode.net/blog/2026/01/27/social.html</guid>
          <pubDate>Tue, 27 Jan 2026 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>We made some mistakes</title>
          <description><![CDATA[<p>There's so much that's gone wrong with our (currently unfunded,
non-functioning) federal government and its policies, but "when we get
back to normal" there's still a lot of work to do.</p>
<p>We, the People, have made some serious mistakes. If we want to fix
them, there are clear paths to addressing them, and the only question is
"how fast"? *</p>
<blockquote>
<ul>
<li>Considering the reality of our legislative environment, we probably
need to move as fast as possible, with the fewest possible means of
removing said changes to prevent assholes from fucking it all up.</li>
</ul>
</blockquote>
<p>This post kept getting longer and longer... it's time to publish.</p>
<h2 id="procedural-changes">Procedural Changes</h2>
<p>Let's start with a few things:</p>
<h3 id="voting">Voting</h3>
<ul>
<li>In an extension of the constitutional amendments, prohibit all
"tests" for polling places. The maximum set of requirements is to show
some form of ID <em>the first time</em> you go to vote. (As an example,
here's <a
href="https://www.sos.ca.gov/elections/voting-resources/voting-california/what-bring">California's
set of requirements</a>.) Otherwise, no restrictions can be added.</li>
<li>Prohibit gerrymandering using validation mechanisms already tested
and verified by subject experts (not politicians or judges).</li>
<li>While the Federal government can't <em>force</em> states to change,
push for ranked-choice voting.
<ul>
<li>Enable ranked-choice voting within federal systems where
possible.</li>
</ul></li>
</ul>
<h3 id="judicial-system">Judicial System</h3>
<ul>
<li>Staff 23 justices in SCOTUS. Pack them with progressives (the kind
that make socialists squee) but also throw in some others. Do judicial
rotations instead of en-banc panels. No first-time judges.</li>
<li>Set term limits for SCOTUS. (18 years seems like a good
number.)</li>
<li>Enforceable ethics rules for circuit judges, etc must also apply to
SCOTUS, with full legal consequences.</li>
<li>Staff the <a
href="https://www.uscourts.gov/data-news/judicial-vacancies/current-judicial-vacancies">40+</a>
vacant federal judgships and add another 400 necessary to process the
backlog of asylum and immigration cases. (Also, to do more judging! We
need courts to get to [necessary] hearings faster.)
<ul>
<li>Absent the above change, open up our borders.</li>
</ul></li>
<li>Reinterpret the <a
href="https://en.wikipedia.org/wiki/Second_Amendment_to_the_United_States_Constitution">second
amendment</a> such that individual gun ownership is only allowed with
court order or restraining order ("proof of need").</li>
</ul>
<h3 id="legislature">Legislature</h3>
<ul>
<li>Eliminate <a
href="https://en.wikipedia.org/wiki/Qualified_immunity">Qualified
Immunity</a> at the federal level. It was a bad decision in 1967, still
a bad decision today.</li>
<li>Expand exceptions to all systems and software, or remove the
prohibitions, of anticircumvention laws. That's Section 103 and <a
href="https://www.copyright.gov/1201/2018/">Section 1201</a> of the
DMCA. If someone can do the job better, let them hack the thing.
<ul>
<li>More broadly, remove the DMCA in its entirety. Yes, it has been used
to <a
href="https://web.archive.org/web/20110723083915/http://www.decoderpro.net/k/docket/395.pdf">prevent
removal of licenses</a> for open source projects, but the harms are more
numerous than the good.</li>
</ul></li>
<li>Remove the safe-harbor provision of SEC <a
href="https://www.sec.gov/rules-regulations/2002/12/rule-10b-18-purchases-certain-equity-securities-issuer-others">Rule
10b-18</a>. Make Stock Buybacks unambiguously illegal again.</li>
<li>Reinstate <a
href="https://en.wikipedia.org/wiki/Chevron_U.S.A.,_Inc._v._Natural_Resources_Defense_Council,_Inc.">deference
doctrine</a>.</li>
<li>Failure to pass a budget results in an immediate election for the
body that failed to authorize the budget.</li>
<li>Require all congress-persons and judges to put their stocks and
retirement into a blind trust / management group while in office. Maybe
spouses too.</li>
<li>Pass a universal healthcare / medicare expansion for all. ("Public
option")</li>
<li>Pass a universal basic income system, with a wealth/asset tax to
support it initially. ("Universal welfare"?)</li>
<li>Public Housing (not Section 8) with both initial investment and
ongoing funding for maintenance. Raise the cost-cap to meet "modern"
costs.</li>
</ul>
<h3 id="executive-agencies-fcc-ftc-epa-irs-etc">Executive Agencies (FCC,
FTC, EPA, IRS, etc)</h3>
<ul>
<li>Work with the legislature to legally support and motivate the
Agencies to investigate and punish-as-needed <strong>every single
company</strong> over 100 people in size. Start with the biggest and
push through verdicts (ideally with the enlarged staff of judges).
<ul>
<li>IRS audits, particularly of individuals with high wealth.</li>
<li>NLRB audits</li>
<li>FCC review of telcos</li>
<li>FTC and SEC investigations of every FinTech company</li>
<li>EPA review of every datacenter and local manufacturing/power
plant</li>
</ul></li>
<li>Special note: leverage the Antitrust division to take on Sinclair
Broadcasting Group and break up their ownership of local newsrooms
across the US.</li>
</ul>]]></description>
          <link>https://gluecode.net/blog/2026/01/14/government.html</link>
          <guid>https://gluecode.net/blog/2026/01/14/government.html</guid>
          <pubDate>Wed, 14 Jan 2026 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Observability is a scale problem</title>
          <description><![CDATA[<p>Warning: Hot Take</p>
<p>Observability (with "arbitrarily wide log events") is a solution to a
problem that most fields and businesses don't have.</p>
<h2 id="measuring-from-first-principles">Measuring from First
Principles</h2>
<p>Most businesses are not pushing the boundaries of science or
research. Most businesses are built to solve a particular problem and
focus on providing services or products at a lower-cost or
higher-satisfaction than their competitors.</p>
<p>This matters because it means the questions you will be asking of
your data are <em>already known or knowable</em> before you begin.</p>
<p>When you provide a service to a customer, you're probably going to
want to know about errors in service, latency or delays in processing,
overall throughput of the service to customers, and your cost in
resources to support it. There will be <em>other</em> additional
questions you'll want to ask, but you'll want those baseline questions
answered in a meaningful way before you find yourself able to answer
higher-order questions.</p>
<p>This applies whether you're talking about a restaurant, a car
manufacturer, or a software shop. Your key performance indicators (KPIs)
are a well-known set of values and you don't have to look very far to
apply them to your current process. That's not to say you aren't doing
something unique in your business -- you probably are! But what you are
<em>not</em> doing is something beyond the realm of process and
procedure.</p>
<h2 id="why-structured-events-though">Why structured events though</h2>
<p>Structured events allow for storing data with an often-arbitrary
organization, of unlimited <a
href="https://en.wikipedia.org/wiki/Cardinality">cardinality</a>, and
without necessarily needing to act on the input on receipt.</p>
<p>These are all <strong>wonderful</strong> properties. We like this; we
want this; it is an unmitigated good for a business to manage its events
in this way.</p>
<h3 id="but...-you-said-structured-events-are-bad">But... you said
structured events are Bad</h3>
<p>When looking at telemetry, you must split the relevant information
into two categories: Business Events, and Operational Telemetry. The
former is <em>always</em> high-value data and can/should be stored as
structured events. Operatinal telemetry should <em>Not</em>.</p>
<p>Business events are things like "Person A bought X, Y, and Z" or
"Customer R modified a deployment in zone Q" -- these are things your
Business operations, turned into Events. These Events are part of your
business history and are always worth the time and money to retain.</p>
<p>What <em>isn't</em> worth retaining are the Operational events. The
fact that a particular operation took 16ms instead of 23ms on the
backend service is <em>not</em> something your business is going to care
about months or years from now. The fact that a particular operation
took three retries to complete is <em>not</em> something the business
will care about in six weeks. If they do care about arbitrary questions
about those particular operations, then they need to be willing to spend
exponentially-increasing volumes of money to retain and process said
data.</p>
<h3 id="separate-your-business-and-operational-data-then-what">Separate
your business and operational data, then what?</h3>
<p>This will take time and effort, but once you do successfully
categorize your data into "operational" and "business" buckets, you can
treat them differently. When new services are built, they can cleanly
flag events generated for each.</p>
<p>Business events get long, expensive storage options. They get schemas
and requirements, with clear reasons for the data to exist.</p>
<p>Operational events get weeks or months of lifetime before being
compacted or deleted.</p>
<p>No need for expensive historical Splunk queries to figure out why
your service is slow -- collapse your data into point-metrics and do
that. Even if cardinality is high, you're storing a <em>small</em>
dataset and adding more points for tracking is a small addition to the
set.</p>
<h2 id="my-datset-is-already-small...">My datset is already
small...</h2>
<p>Then literally anything will work for what you have. You don't need
honeycomb, Splunk, DataDog, or any of that -- you need <code>grep</code>
and a handful of <a
href="https://serverpartdeals.com/products/seagate-exos-st26000nm000c-26tb-7-2k-rpm-sata-6gb-s-512e-cmr-3-5-recertified-hard-drive">refurbished
26TB disk drives</a> for storage.</p>]]></description>
          <link>https://gluecode.net/blog/2026/01/07/honeycomb.html</link>
          <guid>https://gluecode.net/blog/2026/01/07/honeycomb.html</guid>
          <pubDate>Wed, 07 Jan 2026 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>GoToSocial v0.20</title>
          <description><![CDATA[<p>I want to run a fediverse server on my FreeBSD host. I don't want to
bother setting up Jails and I don't really need to run ruby or nodejs on
my server when <a
href="https://www.w3.org/TR/activitypub/">ActivityPub</a> is a
standardized protocol. I decided to look around and found <a
href="https://codeberg.org/superseriousbusiness/gotosocial">GoToSocial</a>.
It runs as a single Go binary and, if you don't want to install
postgresql, can even host a low number of users on sqlite.</p>
<p>What's not to love?</p>
<p>Well, there are a very few things not to love. The most recent issue
was that my sqlite-backed instance decided to stop updating / receiving
updates from peer instances. I can't quite pin down why that was an
issue except that, for some reason, the instance started declaring that
my proxy no longer passed on the real IP address of my client to the
instance.</p>
<p>As a bit of background, I had to change over my network hardware due
to frequent restarts and failures with the Eero Pro kit. Now, I have a
Dream Router 7, an 8-port 2.5Gbps switch, and new access points in the
house. Because my switch is only a Layer-2 switch, I wouldn't expect it
to be able to short-circuit the routing table and forward my request
directly to the server... but the DR7 could. That is easily remedied by
adding an exception for my local IPv4 network range, but I still find it
weird that I had to do it in the first place.</p>
<p>Still, that did not fix my no-update problem, so I elected to migrate
from Sqlite to PostreSQL as the backing database. At the very least, I
could poke around in the DB without risking a strange multi-writer issue
or some other problem. Setting it up was easy -- migrating my older data
is what caused problems. The export/import function explicitly doesn't
support moving over favorites, saved posts, and a half dozen other
objects. Suffice to say, until GoToSocial actively supports
export/import of <em>those particular</em> functions, I will recommend
everyone just install and use postgres as the backing DB. It's not worth
losing data to try sqlite.</p>
<hr />
<p>Since I know Go, I should be able to write my own fixes and propose
them in the main repo, so this is my way of complaining publicly while I
look at what I can do fix it privately. I won't be a hypocrite --
OpenSource means "you got fingers, you go fix it."</p>]]></description>
          <link>https://gluecode.net/blog/2025/12/23/mastodon.html</link>
          <guid>https://gluecode.net/blog/2025/12/23/mastodon.html</guid>
          <pubDate>Tue, 23 Dec 2025 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Big Drive Is Here</title>
          <description><![CDATA[<p><a href="https://www.gluecode.net/videos/TheBigOne.mp4">It
came!</a></p>
<p><code>&lt;ST24000NM000C-3WD103 SN02&gt;</code> -- The certified
refurbished Samsung Exos 24TB drive has arrived.</p>
<p>After doing a partition create on it, it showed the drive had only
22TB of counted, usable space:</p>
<pre><code>$ gpt list ada1
...
first: 40
entries: 128
scheme: GPT
Providers:
1. Name: ada1p1
   Mediasize: 24000277209088 (22T)
   Sectorsize: 512
   Stripesize: 4096
   Stripeoffset: 0
...</code></pre>
<p>and once I created a single-disk pool for it, that usable space
dropped to 21.8TB:</p>
<pre><code>$ zpool list bigdisk
NAME      SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP    HEALTH  ALTROOT
bigdisk  21.8T  2.47T  19.3T        -         -     0%    11%  1.00x    ONLINE  -</code></pre>
<p>But... it's here, and populated with data I was carefully mirroring
on external platter drives.</p>
<p>I think now I can start looking at what it takes to build my backup /
offline service collection...</p>]]></description>
          <link>https://gluecode.net/blog/2025/10/22/big-drive-with-zfs.html</link>
          <guid>https://gluecode.net/blog/2025/10/22/big-drive-with-zfs.html</guid>
          <pubDate>Wed, 22 Oct 2025 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Mirroring zroot on FreeBSD 14</title>
          <description><![CDATA[<p>Followed the directions from: <a
href="https://blog.frankleonhardt.com/2025/add-mirror-to-single-zfs-disk/"
class="uri">https://blog.frankleonhardt.com/2025/add-mirror-to-single-zfs-disk/</a>
but I don't want to lose the notes, so I'm copying what I did here.</p>
<p>Move all data off the 4TB ssd (<code>ada1</code>)</p>
<p>Run the gpart function to move bootcode over:</p>
<pre><code>gpart destroy -F ada1 ;
gpart backup ada0 | gpart restore ada1 ;
gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 ada1 ;</code></pre>
<p>Took the "ssd" zpool offline, and then attached as a mirror:</p>
<pre><code>zpool destroy ssd;
zpool attach zroot ada0p4 ada1p4;</code></pre>
<p>Because the partition of the 1TB is not as large as the new 4TB
drive, I waited until the resilvering completed, and then updated the
end partition from 800GB -&gt; 3.5TB.</p>
<pre><code>gpart show ada1;

# Resize without a &quot;size&quot; flag takes all remaining space
gpart resize -i 4 ada1 ;

gpart show ada1;</code></pre>
<p>Now I need to sort out how to break the mirror and use only the 4TB
disk.</p>
<h2 id="i-cant-facepalm-hard-enough">I can't facepalm hard enough</h2>
<p>Turns out efiboot is a royal pain and <em>for some reason</em> kept
referencing a device that didn't exist in <code>/etc/fstab</code> when I
booted using the new drive.</p>
<p>To make edits to the root filesystem, I had to turn that on for
ZFS:</p>
<pre><code>zfs readonly=off zroot/ROOT/default

# Since we&#39;re here, let&#39;s make it autoexpand the pool
zpool set autoexpand=on zroot</code></pre>
<p>For fstab, I had to change the values from:</p>
<pre><code># Device        Mountpoint  FStype  Options     Dump    Pass#
/dev/gpt/efiboot0    /boot/efi  msdosfs rw      2   2</code></pre>
<p>into:</p>
<pre><code># Device        Mountpoint  FStype  Options     Dump    Pass#
/dev/ada0p1     /boot/efi   msdosfs rw      2   2</code></pre>
<p>Once that was over, I was able to boot into a normal environment with
all my usual stuff.</p>
<p>That lead to me seeing the (original sized) 800GB zfs root
partition:</p>
<pre><code>$ zpool list
NAME      SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP    HEALTH  ALTROOT
archive  4.55T  2.44T  2.10T        -         -     0%    53%  1.00x    ONLINE  -
zroot    800G   29.8G  737G         -     ~2.7T     5%     9%  1.00x    ONLINE  -</code></pre>
<p>What?! I thought this was supposd to automatically grow the
space!</p>
<p><code>zpool online -e zroot ada0p4</code> and then we have:</p>
<pre><code>NAME      SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP    HEALTH  ALTROOT
archive  4.55T  2.44T  2.10T        -         -     0%    53%  1.00x    ONLINE  -
zroot    3.51T  80.8G  3.43T        -         -     1%     2%  1.00x    ONLINE  -</code></pre>]]></description>
          <link>https://gluecode.net/blog/2025/10/18/zroot-mirror.html</link>
          <guid>https://gluecode.net/blog/2025/10/18/zroot-mirror.html</guid>
          <pubDate>Sat, 18 Oct 2025 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Successful generation of RSS in shell</title>
          <description><![CDATA[<p>Finally got RSS generation working in <a
href="https://git.gluecode.net/active/journal.git/commit/?id=79d02603ad0191577e04e47ed181fa0e52b18eff">79d0260</a>,
then <a
href="https://git.gluecode.net/active/journal.git/commit/?id=c736893bb77d2b8686231b5abc32a4ef4e618a60">c736893</a>
added a <code>pubDate</code> tag to propertly update/sort articles.</p>
<p>I got here after trying to shove yaml and metadata into pandoc and
get it to generate the RSS xml. When I tried building a yaml file with
the full text in the description, it definitely didn't work.</p>
<p>After trying to make the metadata file as json instead of yaml, then
getting parser errors as I tried to put strings in the json file, I
finally realized "This is XML. Why don't I just build it by hand?"</p>
<p>And that's where I got to. (Almost) Entirely shell constructing a
clean RSS feed. (Pandoc was necessary to translate the markdown to
html.)</p>]]></description>
          <link>https://gluecode.net/blog/2025/09/30/rss-works.html</link>
          <guid>https://gluecode.net/blog/2025/09/30/rss-works.html</guid>
          <pubDate>Tue, 30 Sep 2025 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>But what of new smartphones?</title>
          <description><![CDATA[<p>Apple gave its big "here's all the new iPhone stuff we're releasing"
announcement on September 10. Part of $DAYJOB is to watch those, and it
had me thinking: what do I really do with my personal phone?</p>
<p>When I need a phone For Work Purposes, the iPhone stands out as the
winner. A company can do literally anything to provision one of those
highly-capable units and it will rise to the task every time. Fantastic
performance, (usually) multi-day battery, and a plethora of accessories
and compatible bits. If you hate tinkering, don't want to think about it
too hard, and need something that Just Works for almost any occasion,
iPhone is the answer.</p>
<p>My $DAYJOB-provided phone works spectacularly well. It has full
Mobile Device Management (MDM) installed on it, a VPN the office
manages, and plenty of "you will use only [these] apps" in the
management panel. It carries my digital keys for accessing the office,
provides me ~60% of the access and capability that my laptop would, and
ensures I'm always reachable during my on-call rotations. (Yes, I'm
on-call for things. That part kinda sucks.)</p>
<p>This is good news! Having a company-managed and company-provided
handset keeps me from doing "personal stuff" on my work phone and work
stuff on my could-be-compromised-by-random-goons personal phone. I love
this arrangement and request it of every company that wants to get ahold
of me in an official way "off the clock." When I look over at my
personal phone, I start questioning whether I need the latest iPhone Pro
model. There are almost zero days where I'm doing anything more
strenuous than watching content (YouTube, Netflix, etc). Most of what I
do is listen to music, navigate with mapping software, and... browse the
internet. Oh! <a href="https://signal.org/download/">And use
signal</a>.</p>
<p>So... what then do I need a super-powerful phone in my personal life
for? I don't find myself carrying <a
href="https://www.samsung.com/us/apps/dex/">around a dock like with
Samsung Dex</a>. I have used a bluetooth keyboard and mouse with my
phone for international travel, but if I'm traveling for work, that's
what the laptop is for. Typing on-phone while I'm flying somewhere is a
limited luxury and it <a href="https://mosh.org/">still requires an
internet connection</a>.</p>
<p>Let's lay out the requirements then, and see what I really
<em>need</em> out of a phone, rather than guessing:</p>
<h2 id="personal-phone-needs">Personal Phone Needs</h2>
<p>I'll organize the following lists in terms of frequency / importance
for "normal" daily functioning.</p>
<h3 id="must-have">Must Have</h3>
<ul>
<li>Conversation (Text, Video calls, Audio calls)</li>
<li>Email</li>
<li>Maps (Navigation, searching for locations)</li>
<li>Photo management (iCloud, Google Drive, self-hosted?)</li>
<li>VPN</li>
</ul>
<h3 id="pretty-important">Pretty Important</h3>
<ul>
<li>Fi-collar (Dogs)</li>
<li>Garmin Sync (Watch)</li>
<li>Home Management (Eero Wifi, Smart Lights, iRobot, etc)</li>
<li>Banking/Credit card stuff</li>
<li>Wallet functions (Apple Card? Tap to pay? Insurance, airplane
passes, etc)</li>
</ul>
<h3 id="nice-to-have">Nice to have</h3>
<ul>
<li>Socials (Mastodon, Bluesky, Discord, etc)</li>
<li>Streaming (Netflix, Paramount, Prime Video...)</li>
<li>Car Apps (MyChevrolet, MySubaru, Triumph connectivity)</li>
</ul>
<hr />
<p>Now that we have the list, let's consider what the options are.</p>
<ul>
<li>Conversation: <a href="https://signal.org/download/">Signal</a> does
it all</li>
<li>Email: thank goodness for standardized imap clients.</li>
<li><em>Maps</em>: Magic Earth (<a href="https://www.magicearth.com/"
class="uri">https://www.magicearth.com/</a>) seems to be Murena's
third-party solution to the "Private Maps" issue. ** See notes
below.</li>
<li>Photos: (TBD self-hosted option) Considering internet-advice of <a
href="https://syncthing.net/"
class="uri">https://syncthing.net/</a></li>
<li>VPN: <a href="https://mullvad.net/"
class="uri">https://mullvad.net/</a> - enough said.</li>
</ul>
<p>That... actually seems to handle the core services I <em>absolutely
need</em> on my phone. I can use the mobile web browser to do stuff like
my banking and credit card apps, access socials, etc.</p>
<p>The big question remains about <em>maps</em>. Maps are now
<em>also</em> a mix of business directories and locations. I could try
using <a href="https://f-droid.org/packages/net.osmand.plus/">OsmAnd</a>
for community maps, but the issue is that those are always behind.
Google and Apple at least have monetary incentives to keep their mapping
software updated and active.</p>
<h2 id="how-much-data-do-i-have-really">How much data do I have,
really?</h2>
<p>I currently have ~800GB of combined/family data stored in iCloud.
Looking at the <em>rest</em> of my data, I've got quite a bit of music
downloaded, so I'll have to see what that looks like.</p>
<p>...</p>
<p>About 14GB of downloaded music. That's not terrible.</p>
<p>iCloud drive says I have ~5GB of "files". Mail is around 200MB (and I
already have that moved elsewhere). My personal photos are approximately
178GB... and Apple-Messages content is 26GB.</p>
<p>Looking finally at offline maps, I have about 3GB of the PNW saved,
but that will absolutely vary when I need to travel to other
locations.</p>
<p>This is starting to look... possible. Like, really possible.</p>
<h2 id="next-steps">Next steps</h2>
<p>It looks like I have three things that I really need to replace:
Photos, Maps, and Music.</p>
<p>To research:</p>
<ul>
<li><a href="https://immich.app/">Immich</a> for photos</li>
<li><a href="https://osmand.net/">OsmAnd</a> and <a
href="https://organicmaps.app/">Organic Maps</a> for mapping/navigation
and reviews</li>
<li>Music ownership options. Not streaming but actual downloads.</li>
</ul>]]></description>
          <link>https://gluecode.net/blog/2025/09/29/2-phone-discussion.html</link>
          <guid>https://gluecode.net/blog/2025/09/29/2-phone-discussion.html</guid>
          <pubDate>Mon, 29 Sep 2025 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>More blog construction</title>
          <description><![CDATA[<p>The blog continues to evolve in structure and design to make it
easier to generate the blog (and maybe, finally, enable rss).</p>
<p>While trying to figure out what I need to generate <a
href="https://www.rssboard.org/rss-specification">RSS</a>, I realized
that I had a bunch of publish-date data inside the files (i.e.,
"<em>Published: YYYY-MM-DD</em>") that I would have to parse and extract
to emit the files in correct order. Similarly, I had no way to generate
the index page when I add new posts. I couldn't use filesystem access or
creation metadata because these files move around a lot. I also couldn't
use git data for the same reasons.</p>
<p>The fix was to move all posts into a Y/M/D path organization and
treat the path as the sorting key for the index. Full details of my
changes start at <a
href="https://git.gluecode.net/active/journal.git/commit/?id=cdf2896f4bfbce86016ccedcb1b6f9073395b817">cdf2896</a>
and end around <a
href="https://git.gluecode.net/active/journal.git/commit/?id=671a4d74e0eb48ca34468aa82f85e96c1ec4ce8c">671a4d7</a>.
If I post more than once per day, I can add filenames like "1_foo",
"2_bar", and so on to retain proper sorting.</p>
<blockquote>
<p>Update: I, in fact, did this today. It works!</p>
</blockquote>
<p>Now that the files are organized by publish date, I'm investigating
what it takes to reprocess the posts into a full-text rss feed.</p>
<p>More analysis and data to come.</p>]]></description>
          <link>https://gluecode.net/blog/2025/09/29/1-rework-blog.html</link>
          <guid>https://gluecode.net/blog/2025/09/29/1-rework-blog.html</guid>
          <pubDate>Mon, 29 Sep 2025 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Pandoc to the Rescue</title>
          <description><![CDATA[<blockquote>
<p>Published 2025-08-20</p>
</blockquote>
<p>When I set up this blog on BSD, I started out using <a
href="https://github.com/commonmark/cmark">cmark</a>. I didn't want to
invest in jekyll, hugo, or any of the other whole-static-site
generators. I wanted to invest in small, focused tools, surrounded by
shell scripts. For a short while I even entertained <a
href="https://github.com/cfenollosa/bashblog">using bashblog</a>, but I
couldn't quite make it work the way I wanted.</p>
<p>I tried poking around with <a
href="https://kristaps.bsd.lv/lowdown/">lowdown</a> and thought it could
help me build an rss feed but it turned out to be no better than
cmark.</p>
<p>Finally, I realized <a href="https://pandoc.org/">pandoc</a> was
sitting right there. It would take in most formats I wanted and produce
any number of results I wanted, so I decided to adopt it instead.</p>
<p>Pandoc produces very nice standalone HTML documents, but some of it
was... missing something... when I'd manage the headers and footers
manually, only relying on pandoc to handle the inner markdown. For
instance, my block quotes were "only" indented -- they had no vertical
bar or other indicator it was a separate reference. That made me suspect
I was doing something wrong, especially when I could see nicer
formatting on <a
href="https://joydeep.dev/building-a-website.html">websites like
this</a>.</p>
<p>Turns out, I needed to build my own template. Pandoc keeps all of its
default templates as internal representations, so you can't just edit a
file and change the defaults. However, pandoc very helpfully allows you
to emit those default templates for yourself:</p>
<pre><code>pandoc --print-default-template html &gt; html.template</code></pre>
<p>Once I did that, I was able to add <a
href="https://git.gluecode.net/active/journal.git/tree/html.template">my
own edits</a> and <a
href="https://git.gluecode.net/active/journal.git/commit/?id=eb697bc027023a605fd2ed70cc209664a0e4e619">modify
my script</a> accordingly. Now it looks much cleaner and has plenty of
(useful) added style.</p>
<p>P.S. -- Tweaking templates in pandoc has me considering what it would
take to generate an RSS feed from the data, with XSLT styling too.</p>]]></description>
          <link>https://gluecode.net/blog/2025/08/19/pandoc-to-the-rescue.html</link>
          <guid>https://gluecode.net/blog/2025/08/19/pandoc-to-the-rescue.html</guid>
          <pubDate>Tue, 19 Aug 2025 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Self-hosting is not hard</title>
          <description><![CDATA[<blockquote>
<p>Published 2025-08-14</p>
</blockquote>
<p>Drew Lyton wrote a blog post titled "<a
href="https://www.drewlyton.com/story/the-future-is-not-self-hosted/">The
Future is NOT Self-Hosted</a>" and while I can agree with some of what
he says, his premise is flawed and the conclusion is off-the-mark.</p>
<p>Let's start with the argument's foundation.</p>
<h2 id="content-is-licensed-not-owned-now">Content is licensed, not
owned, now</h2>
<blockquote>
<p>Companies like Amazon have been playing dirty with Digital Rights
Management (DRM) since the Internet's inception. Purchased digital goods
have always been licenses more than owned assets.</p>
</blockquote>
<p>It's not hard to accept that between streaming, DRM, and (regional)
lock-in for devices, companies and "license holders" have their entire
business plan hinging on the fact that consumers <em>do not own</em> the
product. "Subscriptions" makes Wall Street happy even though it doesn't
make sense for most products and solutions.</p>
<p>No argument with Drew here.</p>
<h2 id="self-hosting-definition">Self-Hosting Definition</h2>
<blockquote>
<p><em>Self</em>-hosting is when you have a computer in your house do
those same things [as Cloud providers], but on a much smaller scale. You
maintain the hardware. You set up the servers. You manage the
applications. You store and backup your data. You troubleshoot issues
when things (inevitably) go wrong.</p>
<p>If that sounds like a lot of work, <em>it is</em>.</p>
</blockquote>
<p>This is where I start to call bullshit. Yes, the idea is that you run
services that replicate the <em>function</em> of what you need.</p>
<p>The key word there is <strong>need</strong>.</p>
<p>Do you <em>need</em> a live-editing collaborative document system
where you and N-number of other people are actively updating a single
document? Probably not. It is <em>neat</em> to have two people
concurrently editing a document but it is absolutely not required to
have a useful, multi-user editing experience. If you <strong>really
do</strong> need concurrently collaborative editing, there's always <a
href="https://etherpad.org/">etherpad</a> or multiple <a
href="https://www.synology.com/en-global/dsm/feature/drive">"Google
Drive"-like options</a> literally designed for self-hosting-scale.</p>
<p>The same could be said for having a "hosted streaming platform."
Streaming of media is a matter of downloading data to the device but
preventing the user from accessing or saving the already-downloaded copy
of the data. When you use a client that permits turning your "stream"
into a persistent file (like <a
href="https://github.com/yt-dlp/yt-dlp">yt-dlp</a>), all that
"streaming" turns immediately into downloads you can replay or share to
your heart's content.</p>
<p>If you shift your thinking such that your device will get a
<em>download</em> of the data, then what you're looking for is a library
or catalog manager, not a live streaming and/or transcoding
platform.</p>
<p>(If this sounds familiar, that's because we have had good, polished,
workable solutions to the above problems since the 2000s.)</p>
<h2 id="so-whats-on-my-home-server">So, what's on my home server?</h2>
<p>This section just... feels designed to overwhelm the reader with
unnecessary complexity.</p>
<blockquote>
<p>That's kind of the point. All of that took 138 words to describe but
took me the better part of two weeks to actually do.</p>
</blockquote>
<p>Okay, bro. Way to make your point by <em>sounding</em> scary. You can
also <a href="https://www.synology.com/en-us/products/DS425+">buy a
synology NAS</a> (or <a
href="https://www.synology.com/en-us/products/DS1621+">this one</a>)
with plenty of CPU and storage to do the job you had to cobble machine
parts together to do.</p>
<blockquote>
<p>But in the end, I had four open-source alternatives to popular
cloud-based apps running on my home server:</p>
</blockquote>
<p>And now we get to the point: run a couple of applications on a
network-storage-capable computer and you have a <em>pretty good
approximation</em> of what you spend between zero-and-thirty USD a month
to have from Apple/Google/Microsoft/Netflix/Paramount/etc.</p>
<p>To add to the evidence, here's what the author said <a
href="https://news.ycombinator.com/item?id=44685340">on the ycombinator
thread</a> for his post (<a
href="https://news.ycombinator.com/item?id=44682175">full thread
here</a>):</p>
<blockquote>
<p>Yeah, at one point in writing this article I had a brief aside about
more "off-the-shelf", accessible solutions to self-hosting like
Synology. But I cut it because I honestly don't think they make the
process that much easier. They help with hardware, but the software
setup I think is still pretty difficult. Thanks for reading!</p>
</blockquote>
<p>Your entire point is to bias folks against self-hosting so you can
make your next point which is...</p>
<blockquote>
<p>Right now, I'm self-hosting a private Google Photos alternative. It's
fully owned and only accessible to me and my wife.</p>
<p>So, how do I create a shared photo album with my friends where we can
all upload pictures from our latest trip?</p>
<p>[. . .] without exposing our services to the public internet and
forcing our friends to signup for our weird app</p>
</blockquote>
<p>You... <em>expose the services to the internet</em> and you <em>set
up accounts for your friends and family</em>. This part is <em>not
hard</em>. The solution is right there in front of you. Maybe you're
hesitant because you serve your stuff over plain HTTP (as evidenced by
your screenshots) but we have <a
href="https://letsencrypt.org/">Let'sEncrypt</a>, so... why?</p>
<p>But okay, your point is that you want us to think sharing like this
is hard...</p>
<h2 id="his-vision">His Vision</h2>
<blockquote>
<p>But just like the suburbs, this vision is incredibly inefficient and
detrimental to creating vibrant, interconnected communities. It
necessitates mass amounts of duplicate, unused infrastructure and
requires each household to be individually responsible for maintaining
that infrastructure. It silos us and makes it harder to share
resources.</p>
<p>And what do we get in return? <em>A worse experience than cloud-based
services.</em></p>
</blockquote>
<p>This is just exasperating. You're not "duplicating infrastructure,"
you're establishing infrastructure for you and those who you can
support. You have the capability and capacity to set something up for
you and your immediate family. What would happen if you offered hosting
to your friends and broader family?</p>
<p>And, again, it's <em>really not</em> duplicating infrastructure to
have a storage-box for your data. That's like saying "the county and the
IRS have all my important records, so I'm not going to get a file
cabinet (for my important documents)."</p>
<p>At some point, everyone is required to manage their irreplaceable
document "stuff." Those with a permanent address will do things like buy
a safe or filing cabinet and cart it around every time they move. That's
the equivalent of the digital self-hosting solution. Nobody says they're
"wasting space" or "duplicating infrastructure" -- it's seen as a smart
investment to avoid catastrophic loss.</p>
<p>Those who don't have a permanent home address seek out alternative
solutions:</p>
<ol type="1">
<li>Storing their important paperwork with family (parents, siblings,
children, close relatives, etc) or friends.</li>
<li>Renting a safety deposit box from a bank (i.e. equivalent to the
Public Cloud VPS)</li>
</ol>
<p>These are well-known solutions to RV-ers and Van-lifers without a
permanent address. We've had these solutions for <em>decades</em> and
they work pretty well. What you have built right now is a really good
"fire-proof safe"-equivalent as option-1 for your friends and
family.</p>
<blockquote>
<p>It empowers individuals, but it doesn't reject the power dynamic
itself. It creates a system where everyone has to provide for themselves
instead of a system that provides for everyone.</p>
</blockquote>
<p>Comically missing the point.</p>
<blockquote>
<p>But if we want to live in a world where we are not bent at the knee
to corporate lords and also don't fall victim to the myth of
self-reliance and rugged individualism, we need to think radically
differently about how we create communal, shared internet
infrastructure.</p>
</blockquote>
<p>Uh... wow, you keep leaning into that false dilemma (perpetuated by
<a
href="https://www.gartner.com/en/newsroom/press-releases/2024-11-19-gartner-forecasts-worldwide-public-cloud-end-user-spending-to-total-723-billion-dollars-in-2025">corporate</a>
<a
href="https://my.idc.com/getdoc.jsp?containerId=prUS52343224">interests</a>)
that there are only two outcomes here... what's your angle?</p>
<blockquote>
<p>Instead of building our own clouds, I want us to own the cloud. Keep
all of the great parts about this feat of technical infrastructure, but
put it in the hands of the people rather than corporations.</p>
<p>I'm talking publicly funded, accessible, at cost cloud-services.</p>
<p>. . .</p>
<p>The devil is in the details. But I know this world where we are all
free from our corporate landlords through solidarity, mutual aid, and
shared, community-owned, privacy-focused, internet infrastructure is
possible.</p>
<p>. . .</p>
<p>"How do I build my own cloud?" is the question that inspired this
journey. But, "How do we build a better cloud?" is the one that I'm left
wondering as it comes to an end.</p>
</blockquote>
<p>Comically missing the point, part 2.</p>
<p>You're literally <em>employee number one</em> of your future Co-Op,
owning this infrastructure. You went far enough to mostly solve your own
problem -- why not take the last step and start truly sharing?</p>
<h2 id="conclusions">Conclusions</h2>
<p>Drew staked out a position on something that got everyone talking
about it. If this is a false-flag operation, then, applause - what a
success.</p>
<p>Ultimately, yes, I agree - I'd love to have municipalities and
governments provide "public cloud" like infrastructure. I think that's a
viable way forward for a lot of "bigger" issues that governments are
currently paying large sums of money to private entities for.</p>
<p>That said, Drew specifically points out that his proposed communal
system needs to work together with private systems:</p>
<blockquote>
<p>Even with this technical solution, I think it makes sense to envision
a world with private, market-provided options.</p>
</blockquote>
<p>My friend, I'll say it again: <em>that is you</em>. Be the change you
seek in the world. Provide services and leverage open protocols (like <a
href="https://activitypub.rocks/">ActivityPub</a>, IMAP/SMTP, RSS, etc)
so that your customers aren't locked-in to your solutions. If you have
the skills and the desire, <a
href="https://arstechnica.com/tech-policy/2022/10/comcast-wanted-210000-for-internet-so-this-man-helped-expand-a-co-op-fiber-isp/">you
start up the co-op</a> and build the solution.</p>
<p>Self-hosting <strong>is</strong> the start of this entire municipal /
co-op owned infrastructure you're hoping for... you're just so focused
on yourself and the false-dilemma of trying to provide "cloud like"
solutions that you can't see it.</p>
<figure>
<img src="/images/BashScripts.jpg"
alt="A person carrying a small box labeled &quot;Bash Scripts&quot; walking alongside another person with an overflowing cart full of product emblems who asks them &quot;Is that all you need?&quot; and they reply &quot;yes.&quot;" />
<figcaption aria-hidden="true">A person carrying a small box labeled
"Bash Scripts" walking alongside another person with an overflowing cart
full of product emblems who asks them "Is that all you need?" and they
reply "yes."</figcaption>
</figure>]]></description>
          <link>https://gluecode.net/blog/2025/08/14/self-hosting-is-hard.html</link>
          <guid>https://gluecode.net/blog/2025/08/14/self-hosting-is-hard.html</guid>
          <pubDate>Thu, 14 Aug 2025 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Anubis on git.gluecode.net</title>
          <description><![CDATA[<p><em>Posted 2025-06-23</em></p>
<p>TL;DR - Anubis is now protecting <a
href="https://git.gluecode.net">git.gluecode.net</a></p>
<p>I keep a couple of local mirrors of big, public repositories so I can
do various git things locally on my server. Repos like <a
href="https://gerrit.wikimedia.org/g/mediawiki/core">mediawiki-core</a>
or <a
href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/">the
linux kernel</a> or <a href="https://github.com/servo/servo">servo</a>
are fairly large and pulling them regularly as a full mirror means I
don't have to worry about whether or not upstream is available.</p>
<figure>
<img src="https://gluecode.net/images/LargeRepos.jpg"
alt="Meme of Ralph saying &quot;I&#39;m in danger&quot; captioned with &quot;When you host large repos&quot;" />
<figcaption aria-hidden="true">Meme of Ralph saying "I'm in danger"
captioned with "When you host large repos"</figcaption>
</figure>
<p><em>Somehow</em> AmazonBot, Claude, ChatGPT, and others found my
server. They decided it was a good idea to click on every comparison
link (repeatedly). They refused to honor robots.txt, so I put a
403-redirect for all "bot" user strings in nginx. That took out the
"polite" bots that modified their user-agent string, like AmazonBot and
ChatGPT, but there was a non-zero contingent of bots that did not
<em>care</em> and proceeded to keep hitting my server.</p>
<h2 id="anubis-saves-the-day">Anubis saves the day</h2>
<p>I did the somewhat-smarter thing back when I <a
href="/blog/posts/2025/freebsd.html">configured nginx</a> and made cgit
run within fcgiwrap, instead of the original apache/bozohttpd method of
direct CGI invocation.</p>
<p>By running in fcgiwrap, I was already prepared to inject <a
href="https://anubis.techaro.lol/">Anubis</a> into the workflow. (Thank
goodness for <a
href="https://xeiaso.net/talks/2025/bsdcan-anubis/">Xe</a> caring about
getting Anubis functioning <a
href="https://xeiaso.net/talks/2025/bsdcan-anubis/">on FreeBSD</a>!)
After following <a
href="https://anubis.techaro.lol/docs/admin/environments/nginx">a few
instructions</a> to move my nginx-server directive for the cgit instance
over to a socket-only listener, adding anubis was... easy. Nginx is both
the TLS frontend and the backend for anubis to do its thing, so I get
full protection for my computationally-expensive subdomain.</p>
<hr />
<p>I've since "enhanced" the policy definiton for anubis to be a bit
more brutal with bots, but the answer to every bot-like client will be
"Absolutely Not". I'll work on allow-listing groups like the Internet
Archive, but <a
href="https://wiki.archiveteam.org/index.php?title=Robots.txt">they
don't seem to play by <em>any</em> rules</a>, and all of this code will
be backed up separately anyway.</p>
<p>Thanks again to <a href="https://xeiaso.net/">Xe</a>. I made sure to
increase my donations, as this is more than worth the money for an
otherwise free product.</p>
<p>If you want to use this for commercial purposes, <a
href="https://github.com/sponsors/Xe">make sure to sponsor
development</a>. Your company is worth spending some money to make sure
you survive the current/oncoming LLM crawler onslaught.</p>]]></description>
          <link>https://gluecode.net/blog/2025/06/24/anubis-installed.html</link>
          <guid>https://gluecode.net/blog/2025/06/24/anubis-installed.html</guid>
          <pubDate>Tue, 24 Jun 2025 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Systems Self-Defense Part 3</title>
          <description><![CDATA[<blockquote>
<p>Published 2025-06-05</p>
</blockquote>
<blockquote>
<p>See <a
href="https://gluecode.net/blog/2025/04/16/systems-self-defense.html">Part
1</a> and <a
href="https://gluecode.net/blog/2025/06/02/systems-self-defense-2.html">Part
2</a></p>
</blockquote>
<h2 id="no-no-wait-hold-on">No, no, wait, hold on</h2>
<p>So y'all may have been a bit surprised I brought up <a
href="https://en.wikipedia.org/wiki/Redis">redis</a> (or <a
href="https://en.wikipedia.org/wiki/Valkey">valkey</a>) as a
data-persistence option and then say "nah, we're not doing that."</p>
<p>"But wait," I hear you say. "Redis and Valkey <em>persist</em> their
data, just like postgres and sqlite... why are you giving that up?
They're fast, keep their data in-memory. What's not to love?"</p>
<p>True, as an SRE, I <em>prefer</em> to over-build things (to a point).
I go for twisted pair wrapped in insulating plastic, not <a
href="https://www.copper.org/applications/telecomm/consumer/evolution.html">dry-core,
paper wrapped</a> lines. You might say "ah, that's overkill" but I like
to plan for a little bit of future-proofing.</p>
<p>So what's wrong with valkey, or any other in-memory sharded solution?
<em>Durability and Scale</em>, at precisely the point where you do not
want scaling problems or questions about your persistence.</p>
<h2 id="first-steps">First steps</h2>
<p>Let's look back at <a
href="https://gluecode.net/blog/2025/04/16/systems-self-defense.html">Part
1</a>. In it, we had a single service instance that could only write as
fast as the underlying filesystem (and operating system) could
handle.</p>
<p>For a 7200rpm spindle drive, the disk can handle 50-100 IOPS. For an
older, middle-rank SATA SSD (like a Samsung EVO 870), that hits around
100k IOPS. SATA supports <a
href="https://en.wikipedia.org/wiki/SATA">upwards of 6 Gbits/s</a>, but
unless you've got over 10Gbit network interfaces (a step-up from most
common consumer hardware, which caps out at 2.5 or 5Gbit), the service
will hit a bottleneck at the network card before it hits the disk limit.
Let's not even consider the (basic) NVMe drives that can easily hit 10x
the SATA SSD IOPS.</p>
<p>Even from there, the operating system, the CPU, memory, or the
service implementation in code can all provide hard limits to what can
be handled at maximum speed and, ultimately, those limits are
constrained to a single instance, a single machine.</p>
<p>Running a simple stress-test against an HTTP service, replying
in-memory, we can get anywhere from 7k RPS to 40k RPS depending on how
the implementation is optimized. That's about the limit of a single host
-- we can do more than ~100k RPS with some clever tweaking, or reducing
the amount of work, but let's consider that a single instance doing
meaningful work will theoretically max-out at 100k RPS.</p>
<p>Once we start spawning multiple instances, across multiple machines,
our limits are now (theoretically) unconstrained by a single disk or a
single network adapter. That "100k RPS" can be multiplied by
<code>N</code> number of additional instances. We're now limited by how
quickly those instances can communicate with each other or some other,
external system.</p>
<h2 id="durability-with-valkey-redis">Durability with Valkey /
Redis</h2>
<p>This brings us to Redis/Valkey. Note, I'm going to refer to Valkey as
"redis", but I do understand the distinction both in implememntation and
licensing. Let's set aside the licensing problem and look at the
available functions.</p>
<p>You can read directly from the Redis team about benchmarking redis
and pitfalls of the service <a
href="https://redis.io/docs/latest/operate/oss_and_stack/management/optimization/benchmarks/">here</a>.
If we review some of the risks/issues associated with redis, <a
href="https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/#ok-so-what-should-i-use">it
says</a> (emphasis mine):</p>
<blockquote>
<p>The general indication you should use both persistence methods is if
you want a degree of data safety <em>comparable to what PostgreSQL can
provide you.</em></p>
</blockquote>
<p>This statement alone should make us all pause for a moment: if
postgres is safer than redis, what is redis <em>doing</em> for
persistence?</p>
<p>It has a point-in-time snapshot (RDB) that requires duplicating the
instance and writing the dataset to disk, and an append-only-file (AOF)
that writes the operations to disk as they are sent to the server. Both
of these persist a recovery-style file to the filesystem. If your data
is important, the combination of RDB and AOF is necessary to ensure
complete recovery of your persisted data.</p>
<p>We have to ask ourselves, "is this data critical to our service?"
Considering our <a
href="https://gluecode.net/blog/2025/04/16/systems-self-defense.html">only
function</a> is to accept a value and return a key that corresponds to
the value, yes, this data is very critical to our service.</p>
<h2 id="durability-with-postgres">Durability with Postgres</h2>
<p>Postgres may be looked at as "just another database", but it is
well-known for being customizable, flexible, and highly reliable,
unencumbered by licenses or restrictions on its use.</p>
<p>Postgres is a <em>disk-first</em> storage system -- while it will
hold data temporarily in memory, its first priority is persisting the
relevant bits <em>to disk</em>. This means that your maximum dataset
size is not limited to how much memory your host has but instead is
limited by the filesystem.</p>
<p>Due to the SQL-nature of its implementation, Postgres is also able to
handle complex data objects in a way the other systems cannot. While
that is not necessarily relevant to our current system
(<code>text -&gt; key</code>), allowing more complexity through adding
fields/columns, ownership, access controls, etc, would require
extensions in other environments. With any SQL-based relational
database, such a request is usually "just another column/table."</p>
<p>That additional complexity often comes at a performance cost, but
what we lose in performance is also buoyed by multiple levels of
validation and write-durability. In the case of Postgres, it prioritizes
accuracy in a query over performance, such that a single
<code>INSERT</code> (or even a <code>SELECT</code>) can <a
href="https://blog.okmeter.io/postgresql-exploring-how-select-queries-can-produce-disk-writes-f36c8bee6b6f">trigger
multiple writes to disk</a>. Over time, Postgres will optimize
files-in-memory to ensure speedy delivery of commonly requested data
and, at any time, can provide analysis of where indexes or tables are
consulted via the <code>EXPLAIN</code> directive. Anecdotally, postgres
handles 10TB+ databases with the same speed and efficiency as it would a
10GB database.</p>
<h2 id="durability-in-redis">Durability in Redis</h2>
<p>By comparison, redis with AOF writes every command to disk and can
support some complex structures such as <a
href="https://redis.io/docs/latest/develop/interact/transactions/">transactions</a>.
It even has a <a
href="https://redis.io/kb/doc/2m9sgdzior/how-to-read-the-explain-execution-plan-and-output-of-profile">ft.explain</a>
directive, yet the operations are expected to occur entirely in
memory.</p>
<p>Compared to disk, memory is both expensive and limited in size. The
largest memory capacities (as of 2025) are <a
href="https://thetechylife.com/what-is-the-biggest-stick-of-ram/">512GB
or 1TB per chip</a>, at approximately $15,000 USD per chip. Compare this
to a <a
href="https://serverpartdeals.com/products/seagate-exos-st26000nm000c-26tb-7-2k-rpm-sata-6gb-s-512e-cmr-3-5-hard-drive">refurbished
26TB hard drive</a> that costs $290 USD (around $11/TB).</p>
<p>Let's also consider this separate from cost: should a dataset exceed
the size of available system memory, not only will redis be unable to
hold more data, it won't be able to persist said data to our AOF or RDB
files.</p>
<p>Durability with redis, therefore, is limited to what memory your
system supports and <a
href="https://gluecode.net/blog/2025/04/16/systems-self-defense.html">per
discussion in part-1</a> is a limited-availability resource.</p>
<h3 id="benching-valkey">Benching Valkey</h3>
<p>If we stick to single-item <a
href="https://redis.io/docs/latest/commands/get/">GET</a> and SET, redis
can handle 120k RPS on a multi-core machine with gigabytes of memory
allocated. The documentation also flags these as <code>O(1)</code>
operations, indicating that they can be completed in constant time.</p>
<p>If we start using the mSET and <a
href="https://redis.io/docs/latest/commands/mget/">mGET</a>, the
throughput drops by at least a third (to 70% original throughput) and
only gets worse as the set size increases. If we try and mitigate this
by switching to the hash-oriented operations, we run into the same <a
href="https://redis.io/docs/latest/commands/hget/">single-</a> vs <a
href="https://redis.io/docs/latest/commands/hmset/">multiple-key</a>
drop in performance.</p>
<p>Testing Valkey locally on a relatively unloaded dual-xeon machine,
the system supported ~135k RPS using SET and GET, but only up to 95k RPS
(30% drop!) as soon as "only 10 keys" were done using MSET:</p>
<pre><code>$ valkey-benchmark | grep -e &quot;======&quot; -e &quot;throughput summary&quot;
====== PING_INLINE ======
  throughput summary: 131406.05 requests per second
====== PING_MBULK ======
  throughput summary: 135135.14 requests per second
====== SET ======
  throughput summary: 135501.36 requests per second &lt;--
====== GET ======
  throughput summary: 134589.50 requests per second &lt;--
...
====== HSET ======
  throughput summary: 136239.78 requests per second &lt;--
...
====== LPUSH (needed to benchmark LRANGE) ======
  throughput summary: 134408.59 requests per second
====== LRANGE_100 (first 100 elements) ======
  throughput summary: 73800.73 requests per second
====== LRANGE_300 (first 300 elements) ======
  throughput summary: 30646.64 requests per second
====== LRANGE_500 (first 500 elements) ======
  throughput summary: 18910.74 requests per second
====== LRANGE_600 (first 600 elements) ======
  throughput summary: 15933.72 requests per second
====== MSET (10 keys) ======
  throughput summary: 95969.28 requests per second &lt;--
...
====== FCALL ======
  throughput summary: 136798.91 requests per second</code></pre>
<p>You can also see a precipitous drop in throughput as the LRANGE
operators grow in size. None of this is exactly a problem -- it tells us
where <em>the boundary lines are</em> for our solution!</p>
<p>If redis can handle up to 135k RPS for small values, then so long as
we limit our throughput to around 115k RPS (85% of max), we're
great!</p>
<p>While 115k RPS seems like a lot, consider that you have to divide
that up across <em>all</em> of your service instances. So if you have
ten instances concurrently accessing the redis server, an even
distribution would be: <code>115_000 / 10 = 11_500/instance</code></p>
<p>Your instances might only be able to sustain around 12_000 operations
per second individually, if they're all working continuously.</p>
<h3 id="benching-postgres">Benching Postgres</h3>
<pre><code>(baseline)
$ pgbench -c 10 -j 2 -t 10000
query mode: simple
number of clients: 10
number of threads: 2
maximum number of tries: 1
number of transactions per client: 10000
latency average = 5.271 ms
initial connection time = 16.112 ms
tps = 1897.185198 (without initial connection time)
--
$ pgbench -c 100 -j 12 -t 10000
...
number of clients: 100
latency average = 16.658 ms
tps = 6003.288229 (without initial connection time)
--
$ pgbench -c 1000 -j 12 -t 10000
...
number of clients: 1000
latency average = 171.768 ms
tps = 5821.795094 (without initial connection time)</code></pre>
<p>Postgres looks like it can handle about 6k transactions* per second
(TPS) across a multithreaded system. Anecdotally, I've seen systems hit
upwards of 8k TPS, but let's assume a relatively unoptimized machine is
our first attempt.</p>
<p>6k is definitely below 115k, but we're talking about a
transaction-based unoptimized benchmark as well. Depending on how our
dataset grows and what <a
href="https://www.postgresql.org/docs/current/using-explain.html#USING-EXPLAIN-ANALYZE">the
<code>EXPLAIN</code> command</a> tells us, we can add indices, make
optimizations to how we query the data, or even <a
href="https://www.postgresql.org/docs/current/runtime-config-replication.html">read
replicas</a> to offload read-only queries to a separate instance.</p>
<blockquote>
<p>* Note: While we refer to "transactions" as opposed to "requests",
this is due to Postgres handling many operations through a (potentially)
multi-statement transaction, rather than the simplicity of a "GET/SET"
statement in redis.</p>
</blockquote>
<h2 id="boundary-conditions-and-data-volumes">Boundary Conditions and
Data Volumes</h2>
<p>One thing we've hinted at but not developed is <em>how much</em> data
our service is going to need. The way our design is constructed, we
"accept text" but never defined how much text is "too much" -- what if
someone sends us the entire body of <a
href="https://en.wikipedia.org/wiki/War_and_Peace">War and Peace</a> as
a single text string?</p>
<p>While intermediary systems might reject that particular example
before it ever gets to us, we should be in a position to set an
upper-limit on the bytes we are handling per-request, per-transaction,
with our service.</p>
<p>Let's say we want to accept a maximum length of 100 characters in a
single request, and we'll return at most a 64-bit unsigned number (i.e.,
<code>18_446_744_073_709_551_615</code>). Because <a
href="https://en.wikipedia.org/wiki/Comparison_of_Unicode_encodings#Efficiency">UTF8
encoding can use up to 4 bytes</a> per "character", we need to account
for <code>4 x 100</code> bytes (plus some padding for encoding, wraps,
etc) for the text we are to receive. Because very few implementations
will allocate "400 bytes" for an object, we should probably round-up our
measurement to 1kb (1024 bytes) per received object. (This also let's us
raise our acceptable length limit in the future, should we choose.)</p>
<p>If every line used the maximum size value, <a
href="https://en.wikipedia.org/wiki/Back-of-the-envelope_calculation">napkin
math</a> can give us some early "breaking points" for various
solutions.</p>
<p>Redis is limited to memory and let's say the "average" server <a
href="https://www.servermonkey.com/blog/servers-101-how-much-ram-do-you-need.html">has
at least 64GB of memory</a> to dedicate to the process. If we save ~4GB
for the operating system, that leaves us with 60GB to allocate. If we
use ~1kb per entry, that leaves us with <code>62_914_560</code> or
around 60M records before we start to exhaust redis' ability to store
data. We can spend significantly more money and get a machine with 256GB
of memory, but the added cost only buys us up to ~250M records (a 4x
increase).</p>
<p>After that, in order to scale, we need to talk about <a
href="https://redis.io/docs/latest/operate/oss_and_stack/management/scaling/">sharding</a>
the dataset and running multiple instances of redis, and coordinating
their operations and backup.</p>
<p>For Postgres, as long as our operations per second don't exceed
~5500, we can persist as much data as our server-disks can hold. The <a
href="https://www.dell.com/en-us/shop/dell-poweredge-servers/poweredge-r750-rack-server/spd/poweredge-r750/pe_r750_tm_vi_vp_sb">Dell
R750 rack server</a> comes with a single, default disk size of 600GB. If
we carve out 100GB for the operating system and other things, that
leaves us with 500GB for the database, which provides us a minimum of
500M records before we start looking for additional storage options.
Since we know this will be a database server, we can purchase six or
seven <a
href="https://serverpartdeals.com/collections/hard-drives/products/seagate-lenovo-exos-x16-st16000nm002g-16tb-7-2k-rpm-sas-12gb-s-512e-3-5-hard-drive">third-party
SAS drives</a> for ~$14/TB and immediately add 100TB of usable
storage.</p>
<p>(If you want to use AWS or some cloud provider to manage your
machines, you can always <a
href="https://docs.aws.amazon.com/ebs/latest/userguide/what-is-ebs.html">use
the EBS-equivalent</a> and scale a single disk up to 64TiB, according to
their documentation as of 2025. That gives you
<strong>63-billion</strong> records without changing anything about your
database processor or memory.)</p>
<h2 id="conclusions">Conclusions</h2>
<p>Ultimately, what we have in the choice of postgres, valkey/redis, or
any other data storage solution, is a set of trade-offs. While redis may
be extremely high-performance, it has a limited storage capacity without
expensive scaling options. Postgres has significantly higher storage
capacity, but is limited to much lower rates of execution.</p>
<p>Each one of the solutions can be scaled, but <em>when</em> it needs
to scale matters just as much as how it can be scaled. This leads us to
a set of directives we can apply to any storage or execution
question:</p>
<p>If scaling requires more compute capacity, that is more expensive
than adding storage capacity. If we observe significantly more
throughput demand than we do storage, lean in to the higher-performance
option (redis). If we observe low-throughput, long-term data storage,
and/or need flexibility in our future storage systems, take the
higher-durability option (postgres).</p>]]></description>
          <link>https://gluecode.net/blog/2025/06/05/systems-self-defense-3.html</link>
          <guid>https://gluecode.net/blog/2025/06/05/systems-self-defense-3.html</guid>
          <pubDate>Thu, 05 Jun 2025 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Systems Self-Defense Part 2</title>
          <description><![CDATA[<blockquote>
<p>Published 2025-06-02</p>
</blockquote>
<p>Part of an <a
href="https://gluecode.net/blog/2025/04/16/systems-self-defense.html">ongoing
series</a>. (See <a
href="https://gluecode.net/blog/2025/06/05/systems-self-defense-3.html">Part
3 here</a>)</p>
<p>Okay, you've built your service, you've set in-flight requests to 90%
of what you know it can handle (for a given amount of CPU, memory, and
network). It's a great service.</p>
<p>And now it's time to integrate with another service!</p>
<h2 id="oh-no">Oh no</h2>
<p>So your service accepts client requests, does useful work, and then
replies to the client. And now you want to add a network-call to that
procedure.</p>
<p>... Why?</p>
<p>Let's set aside that line of questioning for a moment and assume you
<em>MUST</em> integrate with a non-local service/datastore/cache for
$DAYJOB reasons.</p>
<h2 id="persistence">Persistence</h2>
<p>Our service is popular, so we've deployed it across multiple servers.
$DAYJOB SREs have put a load balancer (like <a
href="https://www.haproxy.org">HAProxy</a>) in front of our services to
spread request equally across them all. Because our service handles its
own work and doesn't need things like TLS termination (almost always a
bad idea) the SREs could do this without our involvement.</p>
<p>Now, $DAYJOB is worried the servers are doing repetitive work and
they can't track of user patterns to show value to their investors.</p>
<p>Again, let's set aside the ickiness of the whole concept of tracking
users -- it's gross and, depending how you do it, against the law in
some jurisdictions -- but let's say persisting repeat requests is
useful, and maybe users are asking to save some kind of operation
history. Sure, yeah... <em>the users are asking for it</em>...</p>
<h3 id="caching">Caching?</h3>
<p>If we're <em>just</em> looking to handle repeat requests, we could
cache responses in-memory. This is a time-tested solution and often the
first step in <a href="https://en.wikipedia.org/wiki/Memoization">making
algorithms faster</a> if you find yourself doing repeat work.</p>
<p>As a service, we can do things like store the request parameters as
keys to a cache, and set the response as a value. If they keys are the
same, we just return the value right out of cache -- no additional
computation necessary!</p>
<p>But what if the service restarts? We'll lose all that cached
data!</p>
<p>There are many ways to store in-memory objects as a file, but if
we're considering flexibility in our implementation we probably want to
stick with a <a
href="https://en.wikipedia.org/wiki/Language_interoperability">more
portable</a> solution than just splatting a cache object on disk.</p>
<p>There's the option of building a custom <code>key=value</code>
format, or converting to JSON, but one of the <a
href="https://sqlite.org/fasterthanfs.html">fastest options</a> is using
<a href="https://www.sqlite.org">sqlite</a>. As developers, we get
binary integration (it's a library you include with your application),
portability with the application, and the flexibility of <a
href="https://en.wikipedia.org/wiki/SQL">SQL</a> and its associated data
structures.</p>
<h3 id="data-persistence">Data persistence</h3>
<p>With sqlite, we can persist our cache data to a table (maybe with a
table design like <code>key, value</code>). Because we're talking about
SQL, we can have multiple tables to store any kind of data we want.</p>
<p>Sqlite gives us fast and flexible data storage, but it is only usable
in a single instance. There are <a
href="https://github.com/benbjohnson/litestream">some options</a> to
stream transactions across multiple instances, but sqlite has <a
href="https://sqlite.org/isolation.html">a single-writer
paradigm</a>.</p>
<h2 id="shared-data-persistence">Shared Data Persistence</h2>
<p>If we want our service instances to do work "only once" across
multiple instances, we can't use a shared file -- we need an external
service.</p>
<p>As the joke goes, "now you have <em>two</em> problems."</p>
<p>Sqlite is often compared to <a
href="https://www.postgresql.org">postgresql</a> since they're both
free-and-open-source (unlike <a
href="https://en.wikipedia.org/wiki/Redis">redis</a> as others might
suggest), so let's have our service work with postgres.</p>
<h3 id="the-two-problems">The Two Problems</h3>
<p>In order to write data to postgres, postgres has to accept the
request, (ideally) ensure the operation doesn't conflict with anything
else it's doing, do the thing, and then respond to the client.</p>
<p>Doesn't this <a
href="https://gluecode.net/blog/2025/04/16/systems-self-defense.html">sound
familiar</a>?</p>
<p>In other words, now that we've decided we're going to have multiple
instances talking to a database, we are dealing with <em>two
services.</em></p>
<p><em>Note: We won't go into transaction isolation and other
SQL-specific parts here.</em></p>
<p>With two services talking to each other, our model looks like this
(with some liberties taken around the query request):</p>
<pre><code>(Step 1) Client  --   &quot;foo&quot;   --&gt; &lt;Service1&gt; --&gt; &quot;Get/Insert: foo&quot; --&gt; [Postgres]
(Step 2) Client  --   &quot;1&quot;     &lt;-- &lt;Service1&gt; &lt;-- &quot;1&quot;               &lt;-- [Postgres]
(Step 3) Client  --   &quot;foo&quot;   --&gt; &lt;Service2&gt; --&gt; &quot;Get/Insert: foo&quot; --&gt; [Postgres]
(Step 4) Client  --   &quot;1&quot;     &lt;-- &lt;Service2&gt; &lt;-- &quot;1&quot;               &lt;-- [Postgres]
(Step 5) Client  --   &quot;bar&quot;   --&gt; &lt;Service2&gt; --&gt; &quot;Get/Insert: bar&quot; --&gt; [Postgres]
(Step 6) Client  --   &quot;2&quot;     &lt;-- &lt;Service2&gt; &lt;-- &quot;2&quot;               &lt;-- [Postgres]</code></pre>
<p>Instead of having our service write and serialize to disk, we now
"write and serialize" to Postgres, which means that if we try and write
the same value more than once, Postgres just returns the original
result. Work is saved at the cost of two services and we have services
which can operate in parallel.</p>
<p>We're web scale! &#x1F92E; <!--- Vomiting emoji ---></p>
<h3 id="postgres-backpressure">Postgres backpressure</h3>
<p>Postgres will give us feedback when it is under load. Queries can
take longer to complete, connections to the database are of limited
quantity, and we can run bad queries. For a single service or client
talking to the database, this isn't a significant problem.</p>
<p>Let's say we have hundreds of service copies talking to our single
Postgres server. This replicates our <a
href="https://gluecode.net/blog/2025/04/16/systems-self-defense.html">original
client-service issue</a> but now our service is <em>also</em> a client.
We must manage our use of postgres while still providing our clients the
mechanisms they expect, which means we get to talk about retries, the
"thundering herd" problem, and "race conditions."</p>
<h2 id="retries">Retries</h2>
<p>As an intermediate service, our processor needs to determine what to
do when Postgres has a failure during a transaction.</p>
<p>In this example, we leverage transactions to ensure our writes to the
database only meet the conditions we provide (namely, that the value
doesn't already exist somewhere). If instance-1 and instance-2 of our
service try to write "foo" to our database simultaneously, one instance
will succeed, the other will fail. For the succeeding instance, the
answer is easy: send the response to the client.</p>
<p>But what about the instance that failed?</p>
<p>Let's say instance-2 failed -- there are at least two options: we can
retry at the instance-level, or we can pass the error back to the
client.</p>
<h3 id="service-handles-retries">Service handles retries</h3>
<p>So we want the service to handle retries -- after all, the client
already submitted the request. We accepted their request and so we feel
responsible for making it happen.</p>
<p>(This seems to be a common feeling among many.)</p>
<p>If we follow this path, client response time is dependent on how
quickly we can perform the "get or put" function described above.</p>
<p>In the case of a race condition, we'd see the client response time
for a request that "lost" the race take two or three times longer than
the one(s) which succeeded. As a more concrete example, if the
successful operation takes ~50 milliseconds, a failed operation could
take up to 150ms.</p>
<h3 id="client-handles-retries">Client handles retries</h3>
<p>Let's say that either a successful or unsuccessful response will be
returned to the client within ~50 milliseconds. The successful operator
gets a return value in their reply, while the "losers" of the race
condition get an error code within the same time frame.</p>
<p>So, upon encountering the error, we pass back an error code to the
client such as HTTP <a
href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/409">409
- Conflict</a> or <a
href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/412">412
- Precondition failed</a>. One could even "misuse" the status code and
send a <a
href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/429">429
- Too Many Requests</a> to encourage the client to retry their
operation.</p>
<blockquote>
<p>I say "misuse" the 429 code because it's really not the case that the
server couldn't handle more requests, but the client doesn't know
that.</p>
</blockquote>
<p>The client can either abandon its request or try it again. If it
tries again, with the same content, it gets a successful return from the
server (as the information is already present, rather than being newly
written to the storage system). This is a rather simple implementation
both from the server side and on the client side, however, it does "make
the client do more work."</p>
<p>Every time I bring up "let the client retry", people seem to get
worried, like clients doing <em>anything</em> beyond asking for things
exactly once is bad. I refer objectors to the <a
href="https://web.stanford.edu/class/archive/cs/cs240/cs240.1236/old/sp2014/readings/worse-is-better.html">MIT
vs New Jersey school</a> reference article and point out that <em>if the
client knows what's happening</em> then they can <em>make the choice for
you</em> instead of requiring the developer to handle every single retry
/ reproduce case.</p>
<h2 id="backpressure-and-the-thundering-herd-of-requests">Backpressure
and the Thundering Herd of Requests</h2>
<p>Whichever solution we chose above, we've defined a
<code>backpressure mechanism</code> for our system -- the mechanism
controls the inflow of information and prevents overload of any one
component. Now to test it.</p>
<p>Let's say we become extraordinarily popular: our service has hundreds
of instances, and multiple hundreds of active clients. This is great
news! Popularity means we are useful and/or generating revenue -- good
things for us as operators.</p>
<p>But then <em>Something Happens</em> and our database has a momentary
outage. It comes right back online and can resume servicing requests,
but our clients never stopped submitting data -- we're about to receive
hundreds of concurrent, possibly duplicative, requests and Postgres
might not be able to handle it.</p>
<p>If our <em>service</em> is handling retries, for each request to the
service, we will:</p>
<ol type="1">
<li>Hold open a client connection</li>
<li>Make <code>N</code> number of requests to Postgres to fulfill the
request</li>
</ol>
<p>If our <em>clients</em> are handling retries, then for each request
to the service, we will:</p>
<ol type="1">
<li>Make 1 request (possibly 2 depending on transaction limitations) to
Postgres.</li>
</ol>
<p>In the first case, our service <em>amplifies</em> every client
request to the database, where the second does not -- each transaction
is a fixed multiple (1x or 2x) of each client request. If a service
amplifies request volumes, we're facing a situation where a denial of
service (DoS) is achieved from within our service, with "normal" request
traffic.</p>
<p>If we have a flood of requests coming from <em>outside</em> our
service arrangement (i.e., from our clients directly), then we can
either scale-up our service and postgres to handle more traffic, or we
can <em>not</em> scale-up and allow the excessive-use responses to push
back on clients to handle retries. (And, if any one client is being
abusive, we can talk about rejecting that one client instead of
disrupting the whole service.)</p>
<h2 id="consequences">Consequences</h2>
<p>As shown above, when systems degrade or fail, the way the system
handles loss of function directly affects both the system itself and the
clients using the system. By making design decisions that limit the load
on each component, we build our system for resilience in the face of
degradations and failures.</p>]]></description>
          <link>https://gluecode.net/blog/2025/06/02/systems-self-defense-2.html</link>
          <guid>https://gluecode.net/blog/2025/06/02/systems-self-defense-2.html</guid>
          <pubDate>Mon, 02 Jun 2025 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Defaults are the only answer</title>
          <description><![CDATA[<blockquote>
<p>Published 2025-05-05</p>
</blockquote>
<p>This post might sound like a "hot take," but I promise it's not --
it's backed by over a decade of working across startups and some of the
biggest engineering organizations on the planet.</p>
<h2 id="tldr">TL;DR</h2>
<p>The only solution to maintainability is the default, from upstream or
otherwise.</p>
<h2 id="wat">Wat?</h2>
<p>Whenever I get a new phone or laptop, I like to customize the system.
I change the wallpaper, the colors, sometimes switch the theme to
dark/light/custom. Then I start customizing the terminal/shell, adding a
<a href="https://www.jetbrains.com/lp/mono/">different font</a>, usually
using something like <code>git</code> to clone my <a
href="https://git.gluecode.net/dotfiles.git/">dotfiles repo</a>, and
then I start adding packages for utilities I prefer over the <a
href="/blog/ancillary/shellhaters.html">posix shell</a>.</p>
<p><strong>All of this customization is contained to my personal
system.</strong> It's not something you, the reader, have to deal with.
It's not something anyone who's not hands-on-keyboard or remotely
shelled-in to my system has to care about.</p>
<p>I want to emphasize this point:</p>
<blockquote>
<p>The customizations only affect <em>me</em></p>
</blockquote>
<p>This is why defaults matter. From a platform engineering and SRE
perspective, you may want to <em>offer</em> customizations for those
"rare" exceptions to the default, but a <a
href="https://en.wikipedia.org/wiki/Supermajority">supermajority</a>
(the higher, the better) of customers <em>must</em> be satisfied with
the defaults.</p>
<p>This leads us to two axioms:</p>
<ul>
<li><p>If a setting captures a supermajority of users but is not a
default, it must be made a default.</p></li>
<li><p>If a default cannot capture a supermajority of use-cases, you
must remove the concept of a default entirely, forcing each user to
specify the setting each time.</p></li>
</ul>
<h2 id="consequences">Consequences</h2>
<p>We now have two key points:</p>
<ul>
<li>"Default": a setting suitable for most (60%+, 75%+, etc) use-cases
in a cohort.</li>
<li>Absent a single-setting Default (above), specify nothing for the
user.</li>
</ul>
<p>This type of exercise is often done once and then forever left alone.
When it comes to "release to the public" utilities and services, then
that type of one-off decision making is acceptable, if only because
trying to "fix it" with the global population of users can be
impossible. (See OpenSSL and default ciphers versus
libressl/boringssl.)</p>
<p>When corporations build their own internal platforms, utilities, and
software for corporate consumption, one-off decision making is a
terrible design decision which leads to unmitigable tech-debt,
increasing development and maintenance costs across all sectors which
use said tool.</p>
<p>Take an example of a certificate management platform. Let's say you
have two runtime targets, A and B, and you can generate certificate
types of X, Y, and Z. Among those six possible combination options, if a
majority are <code>A:X</code> and <code>B:Y</code>, you're in a position
to say "All runtime on A <em>by default</em> uses Certificate Type X and
all runtime on B <em>by default</em> uses Certificate Type Y."</p>
<p>If, however, you're not able to provide a single solution for those
environments, any kind of default will need to be <em>constantly</em>
overridden by your users. If you provide a default that matches only 20%
of environments, why is that the default and not "nothing"?</p>
<h2 id="objections">Objections</h2>
<h3 id="its-always-good-to-have-a-default.">It's always good to have a
default.</h3>
<p>Defaults make a <em>decision</em> that the user either has to accept
or modify for their particular use case. If you <em>don't</em> choose a
default, the user must make the decision each time.</p>
<p>Defaults save time only for those to whom the default applies. This
ends up being a penalty for everyone to whom the default does not
apply.</p>
<h3 id="why-not-have-different-things-for-different-people">Why not have
different things for different people?</h3>
<p>Teams building solutions for a company need to make decisions that
affect that solution for its entire lifetime. Defining a default means
establishing an <a
href="https://en.wikipedia.org/wiki/Invariant_(mathematics)">invariant</a>
in the code.</p>
<p>When a different team, or a different engineer, starts using the
service or system, they're going to expect the <em>unconfigured</em>
system to meet their needs. People don't immediately sit down and try to
customize and configure a system they are entirely unfamiliar with; they
will try the defaults first and then customize as they go.</p>
<h2 id="okay-what-now">Okay, what now?</h2>
<p>You've got a service or a system with defaults already shipped. Well,
that's going to be nearly impossible to change user habits if it's
already in-use.</p>
<p>If you have the authority to do so, suggest removing defaults in
favor of "required options" or change the default to meet the true
majority of users (whatever that aligns to).</p>
<p>If you are building a new service, please <em>do not</em> specify a
default. If you <em>do</em> know exactly what your users want and can
show it with data, feel free to establish a default.</p>]]></description>
          <link>https://gluecode.net/blog/2025/05/05/defaults-are-the-only-answer.html</link>
          <guid>https://gluecode.net/blog/2025/05/05/defaults-are-the-only-answer.html</guid>
          <pubDate>Mon, 05 May 2025 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Systems and Self-Defense</title>
          <description><![CDATA[<blockquote>
<p>Published 2025-04-16</p>
</blockquote>
<p>Despite what the first Avengers movie would tell us, a system can
protect itself <em>from itself</em> if built intentionally. To
understand how, let's start with the following:</p>
<p><strong>Tyler&#x2019;s Law</strong>:</p>
<blockquote>
<p>&#x201C;Any system will inevitably be used to 100% of its authorized
capacity.&#x201D;</p>
</blockquote>
<p><strong>Tyler's Corollary</strong>:</p>
<blockquote>
<p>"If your authorized capacity is equal to your available capacity,
your system will fail."</p>
</blockquote>
<p>"Authorized" is a term different from "available" capacity and they
are not interchangeable.</p>
<h2 id="denial-of-service-dos">Denial of Service (DoS)</h2>
<p>Authorized capacity for a computer is not generally controllable by
the end user (unless you've got <code>root</code> access), so a single
process can consume as much in resources as the computer has available
(with very few limits). If one runs a command to duplicate a movie file
of "Plan 9 from Outer Space" fifty thousand times, like</p>
<p><code>seq 50000 | xargs -I{} cp plan9.mov plan9copy{}.mov</code></p>
<p>the computer will dutifully use all its resources to accomplish that
objective until the job completes or the disk is full.</p>
<p>Note, there is <em>no interactivity</em> once executed -- the command
accepts one set of instructions, stops taking input, then executes
without indications of progress until the job is done.</p>
<p>A service, on the other hand, accepts input from another source and
<em>persists.</em> As the service does work on a job, it may be coded to
accept more inputs, and reply to the requestor with already-completed
work.</p>
<p>This poses a problem for managing resources: how many resources are
to be used for in-flight operations? Does the service have enough
resources to accept new work while processing a current job? How does
the service tell the requestor it's not ready yet?</p>
<p>If the system is using all of its <em>available</em> resources to do
work, then there is no resource left to respond to a client/user, to
process added <a
href="https://www.man7.org/linux/man-pages/man7/signal.7.html"><code>signals</code></a>
(like <code>kill</code>/<code>term</code>), or even provide telemetry to
an observer.</p>
<p>If a service or a computer "goes silent," how are we sure it is
functioning correctly, if at all?</p>
<h2 id="the-problem">The Problem</h2>
<p>The key point to the previous section is this:</p>
<blockquote>
<p>If we are able to give a service enough work that it uses all of its
available resources, then we've achieved a <a
href="https://en.wikipedia.org/wiki/Denial-of-service_attack">Denial of
Service</a> condition.</p>
</blockquote>
<p>This is bad. To allow a program to "cancel" an erroneous command or
be triggered to produce telemetry / feedback, the program must be able
to listen for <a
href="https://www.man7.org/linux/man-pages/man7/signal.7.html"><code>signals</code></a>
from the operating system and act accordingly. The only exception is <a
href="https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html"><code>SIGKILL</code></a>,
which cannot be blocked or handled.</p>
<p>The goal of the operating system / kernel is to ensure that
"authorized resources" never exceed "avialable resources", or <em>the
system will crash</em>. This is why <code>SIGKILL</code> is unblockable
-- it's an action of last resort by the operating system (OS) to protect
itself.</p>
<p>But what if we have an interactive <em>service</em>? We don't want to
terminate the process if it gets stuck -- we want it to keep running. So
how do we protect it?</p>
<h2 id="self-defense">Self-defense</h2>
<p>All programs and services practice a form of self-defense known as
response-codes or error-codes. They provide signals to an operator or
requestor that vary from "I'm still here" to "Please try again later" or
"this broke something" or even "your request is broken and I won't do
it."</p>
<p>What does this look like in practice though?</p>
<h3 id="service-example">Service Example</h3>
<p>Let's say I have a web server that accepts text, appends it to a
file, and returns a line number to the client.</p>
<pre><code>(Step 1) ClientA  --  &quot;foo&quot;  --&gt; &lt;Service&gt; --&gt; [write to disk]
(Step 2) ClientA &lt;--   &quot;1&quot;   --  &lt;Service&gt;</code></pre>
<p>Because this service has to write values sequentially, while it is
performing work for Client-A, it cannot do anything else. This means
that if Client-A gives sufficient work to Service, it can't do anything
else.</p>
<p>So let's look at what happens when we introduce Client-B:</p>
<pre><code>(Step 1) ClientA  --   &quot;foo&quot;   --&gt; &lt;Service&gt; --&gt; [write to disk]
(Step 2) ClientB  --   &quot;bar&quot;   --&gt; &lt;Service&gt; --&gt; [BLOCKED]
(Step 3) ClientB &lt;-- &quot;TIMEOUT&quot; --  &lt;Service&gt;
(Step 4) ClientA &lt;--    &quot;1&quot;    --  &lt;Service&gt; --  [write completes]</code></pre>
<p>Because this service has to write values sequentially, while it is
performing work for Client-A, if Client-B wants send "bar" to the
service, we must tell Client-B to wait or come back later.</p>
<p>By default, a TCP connection to a service will <code>CONNECT</code>,
send data, and then receive a response. The operating system can
multiplex TCP <code>CONNECT</code> requests, so if one is already
active, it will tell the underlying network hardware to "wait" until it
can open up another port.</p>
<p>Your application, however, can't see this "wait" condition -- it just
goes silent until either the kernel accepts your connection, or you
time-out and the kernel evicts you from the queue.</p>
<p>This is not a great solution. What could we do instead?</p>
<h3 id="do-nothing">Do Nothing?</h3>
<p>No really, what if we do nothing?</p>
<p>That's a pretty great situation for the developer -- zero work needed
and the kernel/OS does the multiplexing. This, however, is a
<em>horrible</em> experience for clients and service operators. Before
specific solutions like running a dedicated HTTP service or providing an
http stack in-process, the answer to making a service network-available
was using <a href="https://en.wikipedia.org/wiki/Inetd">inetd</a>. It
was (and still is) incredibly slow and does not scale beyond very low
traffic rates.</p>
<p>So, in doing nothing, the service appears inconsistent, with
periodically high latency, and does not actually fix the problem (see
"Denial of Service" above).</p>
<h3 id="application-layer-defense">Application Layer Defense</h3>
<p>Most network services operate on HTTP, even ones that <a
href="https://aweirdimagination.net/2024/04/07/http-over-unix-sockets/">bind
to unix sockets</a>. Even <a
href="https://grpc.io/blog/grpc-on-http2/">grpc operates on http2</a>,
so I think it's safe to say I can leverage HTTP status codes as an
example of how to respond to a client without requiring <em>too
much</em> translation to other stacks.</p>
<p>When an HTTP server is "busy", <a
href="https://www.rfc-editor.org/rfc/rfc6585#section-4">rfc6585</a>
suggests responding with a <a
href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/429">code
<code>429</code></a> which maps to "Too Many Requests."</p>
<p>Of note is this paragraph:</p>
<blockquote>
<p>Note that this specification does not define how the origin server
identifies the user, nor how it counts requests. For example, an origin
server that is limiting request rates can do so based upon counts of
requests on a per-resource basis, across the entire server, or even
among a set of servers. Likewise, it might identify the user by its
authentication credentials, or a stateful cookie.</p>
</blockquote>
<p>Let's refer back to our Server example. If we have Client-A handling
a request, then any <em>additional</em> requests during the time
Client-A's request is being processed would be "too many requests" for
the server to handle.</p>
<p>So what we should so is configure the server to do two things at once
(or, at least, two things concurrently). As pseudocode:</p>
<pre><code>var locked bool

fn writeLineToFile(s string, f file) -&gt; (success bool, l int) {
  locked = true;
  n, err = os.Write(s, f);
  locked = false;

  if err != nil{
    return false, 0; // We failed
  }
  return true, n;
}

main(){
  f = open(&quot;filename&quot;)
  requests = http.Listen(port)

  for r in requests {
    if locked {
      r.reply(http-429) // Too Many Requests
    } else {
      ok, line = writeLineToFile(r.payload, f)
      if ok {
        r.reply(line)
      } else {
        r.reply(http-500) // Error
      }
    }
}</code></pre>
<p>Let's take a second to look at this -- the main code opens a file to
persist the data and listens for requests on the HTTP port. All seems
normal, until we get to the <code>if locked</code> section. If the lock
is active, immediately stop what you're doing and reply with a <a
href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/429">HTTP-429</a>.
(For those using GRPC, you can swap "HTTP-429" out for metadata <a
href="https://grpc.io/docs/guides/status-codes/">status code</a>
<code>UNAVAILABLE(14)</code>.)</p>
<p>You'll notice that we haven't done any additional code checking -- no
computation, no querying the file, just check the boolean value, then
act.</p>
<p>This is, computationally speaking, very cheap to do. The benefit of
the positioning also means that we short-circuit the operation
<em>before we even start down the computationally-expensive path</em> of
accepting the payload and doing anything with the file.</p>
<p>Looking further into the <code>writeLineToFile</code> function,
you'll also see that we "lock" the file for the minimum part of the
operation -- the part where we actually write to the file. We don't lock
the error-checking portion of the code and we don't lock the reply to
the client. This means that while the code is doing
things-not-writing-to-files, we can go as fast and as concurrently as we
want.</p>
<p>If we increase our activity to three clients (A,B,C), our new
operation graph looks something like this:</p>
<pre><code>(Step 1)  ClientA  --  &quot;foo&quot;   --&gt; &lt;Service&gt; --&gt; [write to disk]
(Step 2)  ClientB  --  &quot;bar&quot;   --&gt; &lt;Service&gt; --&gt; [BLOCKED]
(Step 3)  ClientC  --  &quot;quux&quot;  --&gt; &lt;Service&gt; --&gt; [BLOCKED]
(Step 4)  ClientB &lt;-- &quot;E: 429&quot; --  &lt;Service&gt;
(Step 5)  ClientC &lt;-- &quot;E: 429&quot; --  &lt;Service&gt;
(Step 6)  ClientA &lt;--   &quot;1&quot;    --  &lt;Service&gt; --  [write completes]
(Step 7)  ClientC  --  &quot;quux&quot;  --&gt; &lt;Service&gt; --&gt; [write to disk]
(Step 8)  ClientB  --  &quot;bar&quot;   --&gt; &lt;Service&gt; --&gt; [BLOCKED]
(Step 9)  ClientB &lt;-- &quot;E: 429&quot; --  &lt;Service&gt;
(Step 10) ClientC &lt;--   &quot;2&quot;    --  &lt;Service&gt; --  [write completes]</code></pre>
<p>You'll notice at Step-7, ClientC tried again, faster than ClientB,
and they won the race! As we reply to clients, they can decide when to
try again, and since ClientA's request was fulfilled in Step-6 the lock
was lifted, allowing ClientC to make a write.</p>
<p>Regardless of who wins the race to the next write, the service can
only handle one active request at a time, and <strong>it protects
itself</strong> from being forced to handle additional work by telling
clients to try again.</p>
<h2 id="authorized-capacity-available-capacity">"Authorized Capacity" !=
"Available Capacity"</h2>
<p>The service described previously has an "Authorized Capacity" of "1
in-flight request." If the computer running this service was a
single-core tiny computer, it might not have enough power to serve
additional requests (even the HTTP-429 replies) but most computing
devices will have either sufficient speed (allownig for concurrent
processing) or additional cores to handle concurrent requests. As a
result, authorized capacity is less than the total available capacity of
the server.</p>
<p>When running an unmodified, uncontained process in a computer, the
process is "authorized" to use <em>nearly</em> the entire set of
resources available to the OS.</p>
<p>In a cloud environment, a process can run inside a virtual machine
(VM) where the constraints are similar to a bare-metal computer, but
(with few exceptions) are executing within even higher capacity
bare-metal hardware. <em>This means the VM is authorized to operate in a
higher-availability environment.</em></p>
<p>If running in a container or bsd-jail, the constraints are set by the
container manager (sometimes just a command line argument!) then the
kernel grants (and constrains) those resources to the process
inside.</p>
<p>If a process exceeds its authorized limits in any of these
environments, the supervisor process (OS, container manager, VM, etc)
will forcibly end the offending process ("kill"/"terminate") and reclaim
its resources. It doesn't matter whether or not <strong>additional
resources exist</strong> that the process could use -- the supervisory
system will act.</p>
<h2 id="conclusions">Conclusions</h2>
<p>All of this leads to a few heuristics when defining the runtime
environment for an application:</p>
<ul>
<li>Determine the maximum resource usage for a given process, then add
excess capacity to ensure the system has more than it needs by an
appreciable margin. A heuristic is a minimum of 25-15% at lower values,
and as little as 5% for larger environments (to avoid wasting
significant capacity).</li>
<li>Stress-test / Load-test your application to its "maximum" throughput
for the resources you want to use, then set your maximum in-flight to
90% of that value. This will ensure that your instance is always capable
of processing the maximum in-flight within limits.</li>
</ul>
<p>Both of these will ensure your <em>authorized</em> resources (for the
application, your container, and your process) will never exceed your
<em>available</em> capacity and keep your service functioning at maximum
throughput in the face of overwhelming requests.</p>
<blockquote>
<p>Check out <a
href="https://www.gluecode.net/blog/2025/06/02/systems-self-defense-2.html">Part
2 here</a></p>
</blockquote>]]></description>
          <link>https://gluecode.net/blog/2025/04/16/systems-self-defense.html</link>
          <guid>https://gluecode.net/blog/2025/04/16/systems-self-defense.html</guid>
          <pubDate>Wed, 16 Apr 2025 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Consequences of Deviance</title>
          <description><![CDATA[<blockquote>
<p>Published 2025-03-18</p>
</blockquote>
<p>Wikipedia has a comprehensive <a
href="https://en.wikipedia.org/wiki/Deviance_%28sociology%29">definition
of deviance</a>:</p>
<blockquote>
<p>Deviance or the sociology of deviance explores the actions or
behaviors that violate social norms across formally enacted rules (e.g.,
crime) as well as informal violations of social norms (e.g., rejecting
folkways and mores).</p>
</blockquote>
<p>There's a <em>lot</em> of that happening, all the time, across the
world. Everything, from something as small as someone being "rude" in a
social situation, to someone violating the constitution of a country,
counts as "deviance."</p>
<p>Where we have our key differences is in how we respond to it:</p>
<blockquote>
<p>Although deviance may have a negative connotation, the violation of
social norms is not always a negative action; positive deviation exists
in some situations. Although a norm is violated, a behavior can still be
classified as positive or acceptable.</p>
</blockquote>
<p>Deviance is foundational to a good, well-functioning society. As Rise
Against put it in their song lyrics, "You have to cross the line just to
remember where it lays." If a law is written, or a rule proclaimed, and
no one violates it, is the rule good? Does it <em>do</em> anything
worthwhile for the society? If no one challenges a rule, does that make
it a good rule? Is it useful?</p>
<p>No, any rule defined but unchallenged is an inconsequential rule. We
<em>need</em> deviance at various levels to trigger our social
immune-response to a violation. That response to violation of a rule is
what defines the <em>consequences</em> of the rule and without
consequences the rule doesn't <em>matter</em>. This is why people get
upset when others hold them to account for their behavior - it's not the
<em>behavior</em> that holds power, it's the response it elicits.</p>
<p>Similarly, if someone breaks a rule, and everyone says "yeah, good
job" or just doesn't react to it... the rule is also inconsequential.
The action-without-consequences means the norm doesn't apply any
more.</p>
<h2 id="so-what">So what?</h2>
<p>Why does this matter? It's key to literally everything we've seen in
the news, politics, and your local school/coffee/corporate-office
discussions for the last forty years.</p>
<p>When people collectively decide "doing X is wrong" or "doing X is
uncool" or "doing X <em>isn't</em> wrong any more", you're talking about
modifying the social contract and moving the "bright lines" on what is
considered "good behavior." This is completely normal and regular
feedback mechanisms (reactions, opposition, enforcement, etc) are all
signs of a well-oiled society.</p>
<p>Where it goes wrong is when a group (or even an individual) deviates
from "normal" behavior and gets <em>no feedback</em>. As stated above,
"no feedback" is "approval" when it comes to deviance. Or, in a more
pithy way "silence is consent."</p>
<h2 id="again-so-what">Again, so <em>what</em>?</h2>
<p>One cannot ever be silent to deviations from norms. Reactions to such
deviance depend on the severity of the violation.</p>
<ol type="1">
<li>Polite verbal response: "You probably shouldn't do that", "That's
not polite."</li>
<li>Direct verbal response: "That is unacceptable", "Cut that out",
"Have you no decency?"</li>
<li>Polite physical response: slapping someone, shoving them, as an
attempt to "disrupt" their actions.</li>
<li>Direct physical responses: unambiguous violence, beatings,
murder.</li>
</ol>
<p>When people do not respond to the first or second level of
"correction," violence <em>is</em> the only acceptable solution to
violation of norms. We see this constantly in cases where the Talking
Heads do and say things that are continuously violating norms, and yet,
no one is able to give them corrections in real time. This is inherently
degrading to our society and removes foundational social feedback from
where it is most needed.</p>
<p>Conversely, when people <em>do</em> have the opportunity to give
feedback and corrections in real time, you get <a
href="https://youtube.com/shorts/Piidt2JEHO8">situations like this</a>
(<a
href="https://gluecode.net/videos/GOPTownHall_Feedback_2025.mp4">archive
link</a>). You can hear the cheers and boos as challenges are leveled at
the representative. The representative also says "can you give me a
chance to answer the question?" -- a clear call for the audience to
adhere to Question-and-Answer norms, allowing the respondent to
speak.</p>
<p>In other parts of <a href="https://youtu.be/lUtySVmgb2E">the
meeting</a> (<a
href="https://gluecode.net/videos/GOPTownHallNorthCarolina_2025.mp4">archive
link</a>), you can see attendees that "violate" the norms of behavior
are being escorted out by law enforcement.</p>
<p>The problem with this situation is the power imbalance: attendees are
indviduals (just like the representative) and yet their behavior is
policed with force (i.e., physical removal) without actually reaching
level 3/4 of "corrective action" toward the representative. The
representative could have given a verbal response, but we never see
whether that was effective. And, if the representative is
<em>wrong</em>, they're still in a position to enforce their position
against the corrective actions of an attendee.</p>
<h2 id="why-we-fight">Why We Fight</h2>
<p>This is why real world, physical fighting is so effective. We can
call physical-corrective-actions "riots" or other names to try and
delegitimize the action, but ultimately it is the <em>strongest</em>
<strong>legitimate</strong> corrective action available to a people when
norms and rules are being violated.</p>
<p>So, when people tell you "violence is never the answer"? They haven't
ever been punched in the face.</p>]]></description>
          <link>https://gluecode.net/blog/2025/03/18/pushing-the-limits.html</link>
          <guid>https://gluecode.net/blog/2025/03/18/pushing-the-limits.html</guid>
          <pubDate>Tue, 18 Mar 2025 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Wealth, Capitalism, and The Economy</title>
          <description><![CDATA[<blockquote>
<p>Published 2025-03-04</p>
</blockquote>
<p>Warning: Spicy Take to follow</p>
<p>I've only been alive <em>after</em> Reagan took office and
systematically began dismantling the Federal government, so my
lived-experiences are already fairly biased before I can even start
talking about how "things used to be." Others have <a
href="https://www.youtube.com/shorts/IX1vw0V2MY0">explained this</a> (<a
href="https://www.gluecode.net/videos/MagaResentmentReaganTrump.mp4">archive
link</a>) at length, so I'll defer to them on the background.</p>
<p>What I think is missing from today's <em>understanding</em> is that
modern conservatives aren't "conservative" anymore. The republicans --
the group that is currently in power -- are deeply weird, unserious
people, who couldn't be bothered to "perform their own research" if
their lives depended on it. (And yes, actually, their lives <em>do</em>
depend on it.)</p>
<p>What they've done instead is adopt the stance of your stereotypical
15-year-old American boy:</p>
<ul>
<li>Remove all measures of transparency and openness</li>
<li>Claim as much authority and unilateral power as possible</li>
<li>Be deliberately, absurdly reductive and obtuse about things you
don't like</li>
<li>Argue for nuance and empathy for things you do like, especially
yourself</li>
<li>Act without regard for feedback, opinions, or justification</li>
</ul>
<p>This isn't conservative or liberal. This isn't democrat or
republican. This is willingly-ignorant, stupid even versus... every one
and everything else.</p>
<p>How did we get here? Again, see the background link above, but I
would like to take it one step further:</p>
<blockquote>
<p>Every illiberal, non-progressive government and political entity is
the cause of economic, bodily, and social harm.</p>
</blockquote>
<p>You can't be a conservative and "do good" because the conservative
ethos goes against the definition of "doing good things for people." To
borrow a quote from someone else, the response to the Daily Wire and all
these provocateur mouthpieces should have been:</p>
<blockquote>
<p>"What is a woman?" Don't know and don't really care -- let's act in
the way that causes the least suffering.</p>
</blockquote>
<p>If that's not your stance, you aren't worth the skin you're in.</p>]]></description>
          <link>https://gluecode.net/blog/2025/03/03/recession-wealth.html</link>
          <guid>https://gluecode.net/blog/2025/03/03/recession-wealth.html</guid>
          <pubDate>Mon, 03 Mar 2025 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Switching to FreeBSD 14.2</title>
          <description><![CDATA[<blockquote>
<p>Published 2025-02-26</p>
</blockquote>
<p>Brief update from <a
href="https://gluecode.net/blog/2024/11/08/tech-updates.html">previous</a>:
the website is now being hosted on FreeBSD 14.2 (RELEASE) and Nginx
(1.26+), instead of NetBSD 10.1 and Caddy (v2.9.1). Here's <a
href="https://gluecode.net/backups/freebsd-T7810-dmesg.txt">the
dmesg</a></p>
<p>Why make such a change? I finally got to test the network and
hardware performance of my system against a worth opponent (thanks, Ben)
and found the server lacking. FreeBSD has received a lot of Netflix
contributions to directly improve x86_64 performance, so, it stands to
reason that FreeBSD will do better and be a bit more consistent.</p>
<p>Don't get me wrong, I love NetBSD and found it very clean, simple,
and fully understandable, but I went off the deep end with packages and
had too many ongoing performance quirks to <em>not</em> spend the time
upgrading and fixing it.</p>]]></description>
          <link>https://gluecode.net/blog/2025/02/26/freebsd.html</link>
          <guid>https://gluecode.net/blog/2025/02/26/freebsd.html</guid>
          <pubDate>Wed, 26 Feb 2025 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Building "bulletproof" reliability</title>
          <description><![CDATA[<blockquote>
<p>Published 2025-01-14</p>
</blockquote>
<h2 id="tldr">TL;DR</h2>
<ul>
<li>Automate your (personal) operations where you can. Don't rely on
human memory or documents -- commit it to code and source control.</li>
<li><strong>Always</strong> keep the resulting code <a
href="https://gluecode.net/blog/2016/08/31/redefining-stability.html">up
to date</a></li>
<li>Do not customize configuration unless such customization is expected
for <strong>everyone</strong> who uses the tool. (e.g., SREs need a
particular k8s cli config)</li>
<li>Rely exclusively on <a
href="https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/mindex.html">POSIX</a>
or tools you can test for (e.g., git) for maximum reliability.</li>
</ul>
<h2 id="context">Context</h2>
<p>281 lines. All it took was 281 lines of code (329 if you include
comments) to automate and make repeatable a process that originally took
1-2 days to complete each time it happened.</p>
<p>Provisioning and configuring your operating environment from a clean
OS install is a very personal and custom experience for a person. You
want to use your favorite tools, maybe set up your own custom keymaps
(e.g., Capslock to Control), and make sure your favorite wallpaper or
color scheme is available. Those parts are what make your experience
"good" for you.</p>
<p>What the 281 lines of code did was <em>not</em> these things. The
code took the most tedious parts of a 5-page-long new-hire onboarding
document and automate the installation and configuration of required
tooling. The script checks for existing installations and configurations
and skips any work already done, so its execution is <a
href="https://en.wikipedia.org/wiki/Idempotence">idempotent</a>. It
bootstraps the install environment (e.g. installing <code>brew</code>),
installs required ancillary tools (like a kubernetes client
configurator, git, java, etc.), ensures all $COMPANY internal
configurations are present, and then starts executing additional package
installations by using app-URLs to launch and install things through
<code>jamf</code> and other managed platforms.</p>
<p>You still got to update your own tools, add your own aliases and
shell scripts, but the basic bootstrapping needed for every SRE? "We
took care of that for you."</p>
<h2 id="time-and-effort-saved">Time and Effort Saved</h2>
<p>When a new-hire joined the team, they used the script. It ensured
they had the "basics" already available when they were asked to do a
particular task. When a veteran team member had a broken configuration
and was unable to do work, the script restored their "good working"
tokens and configurations within a minute.</p>
<figure>
<img src="https://www.gluecode.net/images/is_it_worth_the_time.png"
alt="Diagram of time saved versus frequency of execution" />
<figcaption aria-hidden="true">Diagram of time saved versus frequency of
execution</figcaption>
</figure>
<p>You might say that the time invested-and-saved is "worth it", or you
might say that having to keep any of these documents or code up to date
is "not worth it," and now you're stuck maintaining something you could
just do on-demand before.</p>
<p>The problem is, this whole dichotomy of "[not] worth it" is
<strong>completely untrue.</strong> Every company, every team, every
individual, <em>must</em> operate and maintain their working
environment.</p>
<p>You might argue "no, that's someone else's job." Fine, you've now
externalized your needs. Doesn't change the fact that <em>someone</em>
has to maintain it.</p>
<figure>
<img src="https://www.gluecode.net/images/BuildAndOwn_Scale.png"
alt="Building and Ownership reliability incentives scale" />
<figcaption aria-hidden="true">Building and Ownership reliability
incentives scale</figcaption>
</figure>
<h2 id="maintenance">Maintenance</h2>
<p>So if you <em>have</em> to maintain an environment, what's the most
reliable option?</p>
<p>You could scribble down a document explaining how to install every
tool. You could make every new-member work directly with a veteran and
let the knowledge transfer happen orally and organically. You could have
documents for every operation include "tools required," and leave it up
to the executor to acquire them as-needed.</p>
<p>You could also write a $LANGUAGE program that digs deep and can
provision your environment from nothing -- where the code is in full
control. The problems with this approach include:</p>
<ul>
<li>(Possibly) Learning a new-to-you language for
implementation/bugfixes/etc.</li>
<li>Bootstrapping the language on a new platform (see: Rust, Clang,
JVMs...)</li>
<li>Unique implementation details tied to language choice</li>
<li>A powerful programming environment may require otherwise optional
libraries or functions your initial environment may not have</li>
</ul>
<p>I argue none of these options are good, correct, or appropriate. The
right solution is a <a
href="https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/mindex.html">POSIX</a>
shell script, because it's always there. You'll never be without a POSIX
environment on Unix/Linux or MacOS. (Windows hates file-based
configuration -- largely because they built APIs for mutation instead of
file-oriented design.)</p>
<p>Writing a posix-exclusive shell script gets your environment and
configuration set, regardless of how messed-up things might be.</p>]]></description>
          <link>https://gluecode.net/blog/2025/01/15/bulletproof.html</link>
          <guid>https://gluecode.net/blog/2025/01/15/bulletproof.html</guid>
          <pubDate>Wed, 15 Jan 2025 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Promised Updates</title>
          <description><![CDATA[<blockquote>
<p>Published 2024-11-08</p>
</blockquote>
<p>As I had promised almost a full year ago, here are some updates to
how I'm using my <a
href="https://gluecode.net/blog/2023/11/29/newhardware.html">T7810
workstation</a>.</p>
<p>A few facts before I get into the opinions:</p>
<ul>
<li>I run <a
href="https://www.netbsd.org/releases/formal-10/NetBSD-10.0.html">NetBSD
(10-Stable)</a> on this host. Here's the <a
href="https://gluecode.net/backups/netbsd10-T7810-dmesg.txt">dmesg</a>.</li>
<li>I have three drives on the host: two internal SSDs (Samsung EVO 870,
1TB and 4TB capacity), one external platter drive (WDPassport,
5TB).</li>
<li>I have added a <a
href="https://www.amazon.com/gp/product/B077VDKBL1/">10Gbit
Intel-compatible X550-T1 network card</a>.</li>
<li>I have added a <a
href="https://www.amazon.com/gp/product/B0BZNHCG71/">small usb-powered
7-inch display</a> for diagnostic / crash-cart purposes.</li>
</ul>
<p>Now, with all that said, having 24 cores and 48 threads even on a <a
href="https://ark.intel.com/content/www/us/en/ark/products/81908/intel-xeon-processor-e5-2680-v3-30m-cache-2-50-ghz.html">10-year
old chip</a> has been more than fine. I don't need blazing
single-threaded speed -- I need something where I can put a job and
ensure that it will get done in a reasonable amonut of time. That could
mean a <code>git gc --aggressive</code> on a multi-GB repository. That
could mean doing a transcoding job on a video I recorded in 4k and
intend to post. Even rebuilding the netbsd kernels and userland takes
about 20 minutes from a standing-start. (If I'm building all the tooling
from scratch as well, make it 40 minutes.)</p>
<p>Services active on this host include:</p>
<ul>
<li>This website, backed by Caddy and PHP-FPM (for the RSS
subdomain)</li>
<li>FreshRSS</li>
<li>mail sync (mbsync/isync)</li>
<li>Full cloud backups</li>
<li>IRC</li>
</ul>
<p>I'm adding more <a
href="https://github.com/awesome-selfhosted/awesome-selfhosted">self-hosted
options</a> as I get time and room to add things (like writing my blog
publishing scripts).</p>
<h2 id="to-do">To Do</h2>
<p>For this host I am considering adding:</p>
<ul>
<li>Archive In A Box</li>
<li>Samba / Bonjour / Media sharing to my home network</li>
<li>Mastodon hosting/node</li>
</ul>
<p>Archive-in-a-box is to give me a "personal archive.org" so I don't
lose websites or data if it gets rugpulled. I'm too worried about data
getting lost for me to not spend the time and effort to capture what's
available. On the plus side, I think the service lets me upload to the
real <a href="https://archive.org/">archive.org</a> as well, preserving
an off-site backup.</p>
<p>Sharing on my network is just so I'm not beholden to Apple-iCloud or
any other off-site service with my file-based data. I have plenty of
storage I pay for and I can always add more at home for "less critical"
temporary storage, like video files before they get reprocessed/cut.</p>
<p>Finally, might be fun to have a mastodon node/host on my own domain
for myself and my friends. Not sure how much effort that will be.</p>]]></description>
          <link>https://gluecode.net/blog/2024/11/08/tech-updates.html</link>
          <guid>https://gluecode.net/blog/2024/11/08/tech-updates.html</guid>
          <pubDate>Fri, 08 Nov 2024 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>November sucks</title>
          <description><![CDATA[<blockquote>
<p>Published 2024-11-06</p>
</blockquote>
<p>I'll just say it -- November, as a month, sucks approximately every
3-4 years. Doesn't matter who gets elected, we end up with "winners" and
"losers" and a full half (sometimes more, sometimes less) of the US
feels like the entire world is going to collapse.</p>
<p>Yes, our world is in dire peril. Yes, we have leaders who are making
<em>extremely</em> short-sighted decisions that affect individuals'
life-and-death stakes daily. When we do get bills and laws passed to
make changes, the supreme court... makes incredibly opaque rulings based
on very little established law and perspectives that make law professors
cringe or, at the least, scratch their heads.</p>
<p>All of this matters. All of this is important. What I'm trying to
remind myself of, is that we all cannot vote once every four years and
expect things to Just Work. We <em>must</em> be politically active, all
the time. We must constantly fight for the betterment of our jobs, our
personal rights, our local, state, and federal representation -- all of
it. It's exhausting, but it's the only way to make it <em>work.</em>
Looking at leaders like Jennifer Briney, or Molly White, or the 5-4
podcast, I'm going to start spending more time reading and understanding
laws-as-written, writing websites to track these sorts of things, and
generally investing more in building awareness for myself (and, if I
can, everyone else).</p>
<p>All that said, you might lose friends over your politics. You might
have to re-evaluate who you work for. You may need to re-evalute what
party / parties you belong to, where you live, what roles you hold in
your groups, all of it.</p>
<p>None of this is a Bad Thing. We are social creatures and, twisting
the 1960s second-wave-feminism quote, "the political is personal." For
all that people want to "keep politics out of it," when your
<em>existence</em> and your <em>health</em> and your <em>freedom</em>
are on the line, there cannot be a divide between the personal and the
political.</p>
<hr />
<p>Relevant quotes for inspiration:</p>
<p>Richard K. Morgan in the Altered Carbon series:</p>
<blockquote>
<p>The personal, as everyone&#x2019;s so fucking fond of saying, is political.
So if some idiot politician, some power player, tries to execute
policies that harm you or those you care about, take it personally. Get
angry. The Machinery of Justice will not serve you here &#x2013; it is slow and
cold, and it is theirs, hardware and soft-. Only the little people
suffer at the hands of Justice; the creatures of power slide from under
it with a wink and a grin. If you want justice, you will have to claw it
from them. Make it personal. Do as much damage as you can. Get your
message across. That way, you stand a better chance of being taken
seriously next time. Of being considered dangerous. And make no mistake
about this: being taken seriously, being considered dangerous marks the
difference - the only difference in their eyes - between players and
little people. Players they will make deals with. Little people they
liquidate. And time and again they cream your liquidation, your
displacement, your torture and brutal execution with the ultimate insult
that it&#x2019;s just business, it&#x2019;s politics, it&#x2019;s the way of the world, it&#x2019;s
a tough life and that it&#x2019;s nothing personal. Well, fuck them. Make it
personal.</p>
</blockquote>
<blockquote>
<p>Face the facts. Then act on them. It&#x2019;s the only mantra I know, the
only doctrine I have to offer you, and it&#x2019;s harder than you&#x2019;d think,
because I swear humans seem hardwired to do anything but. Face the
facts. Don&#x2019;t pray, don&#x2019;t wish, don&#x2019;t buy into centuries-old dogma and
dead rhetoric. Don&#x2019;t give in to your conditioning or your visions or
your fucked-up sense of ... whatever. Face the facts. Then act.</p>
</blockquote>
<blockquote>
<p>Every previous revolutionary movement in human history has made the
same basic mistake. They&#x2019;ve all seen power as a static apparatus, as a
structure. And it&#x2019;s not. It&#x2019;s a dynamic, a flow system with two possible
tendencies. Power either accumulates, or it diffuses through the system.
In most societies, it&#x2019;s in accumulative mode, and most revolutionary
movements are only really interested in reconstituting the accumulation
in a new location. A genuine revolution has to reverse the flow. And no
one ever does that, because they&#x2019;re all too fucking scared of losing
their conning tower moment in the historical process. If you tear down
one agglutinative power dynamic and put another one in its place, you&#x2019;ve
changed nothing. You&#x2019;re not going to solve any of that society&#x2019;s
problems, they&#x2019;ll just reemerge at a new angle. You&#x2019;ve got to set up the
nanotech that will deal with the problems on its own. You&#x2019;ve got to
build the structures that allow for diffusion of power, not re-grouping.
Accountability, democratic access, systems of constituted rights,
education in the use of political infrastructure.</p>
</blockquote>
<p>From Peter Watts' blog:</p>
<blockquote>
<p>Edmund Burke once said that the only thing necessary for the triumph
of evil is for good men to do nothing. I think that begs a question.</p>
<p>If you do nothing, what makes you any fucking good?</p>
</blockquote>]]></description>
          <link>https://gluecode.net/blog/2024/11/06/november.html</link>
          <guid>https://gluecode.net/blog/2024/11/06/november.html</guid>
          <pubDate>Wed, 06 Nov 2024 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>New Hardware</title>
          <description><![CDATA[<blockquote>
<p>Published 2023-11-29</p>
</blockquote>
<p>Intentions of "writing more" aside, I have ended up buying a few new
pieces of hardware. A while back my 16GB M1 Macbook Air threw multiple
"out of memory" errors in a week, so I traded it in for a M2 Max Macbook
Pro with 96GB of RAM (the most it supports). Since then, the extra power
of the laptop has made the coding projects where I use my i5-CoffeLake
Dell 3070 (a SFF box with 32G of RAM) seem pretty small. I've been
reading up on buying a quarter-rack (12U) and looking in to how
expensive some Dell R710 and R6XX series 1U servers would be, but
between the cost of the rack ($600+) and the noise of the 1U and 2U
servers, it seemed I was going to be "stuck" with either using my
laptop, living with an underpowered machine, or building my own out of
new parts at significant cost.</p>
<p>Then I found this <a href="https://www.amazon.com/dp/B07V4GWNJ1">Dell
T7810</a> refurbished on Amazon's Renewed store for $370. It has 2 x <a
href="https://www.intel.com/content/www/us/en/products/sku/81908/intel-xeon-processor-e52680-v3-30m-cache-2-50-ghz/specifications.html">E5-2680v3
Xeon</a> processors for a total of 24 cores and 48 threads, 128GB of ECC
RAM (with eight slots total, <a
href="https://www.amazon.com/Crucial-PC4-21300-2666MHz-CT32G4RFD4266-Registered/dp/B07X1TMZBS">up
to 256GB</a> for around $410), and comes with a 480GB SSD and an Nvidia
Quadro card. Considering my main issue with the SFF machine was CPU
power, I'd say this resolves many of my concerns around having "enough"
crunching capacity on the side.</p>
<p>If I find myself in need of some added storage, the board supports
PCI-E NVME drives, so I can throw in a <a
href="https://www.amazon.com/dp/B0B25M8FXX/">4TB disk</a> and <a
href="https://www.amazon.com/dp/B09JM5FVC7/">an adapter</a> for ~$200
total.</p>
<p>It is scheduled to arrive between today and Friday (Dec 1), so I'll
be putting up specs and notes after it gets booted.</p>]]></description>
          <link>https://gluecode.net/blog/2023/11/29/newhardware.html</link>
          <guid>https://gluecode.net/blog/2023/11/29/newhardware.html</guid>
          <pubDate>Wed, 29 Nov 2023 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Reboot</title>
          <description><![CDATA[<blockquote>
<p>Published 2022-05-13</p>
</blockquote>
<p>This journal stopped because I felt like I didn't need to write any
more. I joined a new company and decided to wait before writing any
technical entries. Then, I was granted a management position at my
company and began to remove myself from technical contributions. Most of
what I had to deal with was related to personnel matters and so could
not be shared in a public journal anyway. By the time I "had the time"
as a manager, I'd lost all interest in writing.</p>
<p>Now, I've moved to a new company. My job includes writing technical
decision documents, architecting solutions, and giving opinions to guide
engineering and business teams. So why not resurrect the journal?</p>
<p>I've got a few topics already queued and will be publishing in the
coming weeks.</p>]]></description>
          <link>https://gluecode.net/blog/2022/05/13/reboot.html</link>
          <guid>https://gluecode.net/blog/2022/05/13/reboot.html</guid>
          <pubDate>Fri, 13 May 2022 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>The Pace of Change</title>
          <description><![CDATA[<blockquote>
<p>Published 2018-07-22</p>
</blockquote>
<p>One of the first subjects my law professor discussed with the class
is the nature of change. We, as a society, expect ever faster change in
our world through additions like cars, cell phones, the internet, and
ever continuing development of purpose and meaning around those things:
privacy laws, seat-belt laws, regulation and testing, etc. This leads to
the "<a
href="https://www.google.com/search?q=Dictionary#dobs=superstructure">superstructure</a>"
changing faster than the infrastructure.</p>
<p>The problem with this ever-faster pace of evolution is that the law,
our norms, and our generational preferences are part of the
<em>infrastrucutre</em>. While technology is unmoored from the limits of
certain things like social convention, we still have people who will
physically assault someone for wearing <a
href="http://www.businessinsider.com/i-was-assaulted-for-wearing-google-glass-2014-4">Google
Glass</a> or recording them via phone. While this may have been an issue
as early as 2008/2009, we <a
href="https://en.wikipedia.org/wiki/Legality_of_recording_by_civilians">have
states establishing two-party consent</a> requirements as early as 2010,
and by 2017 we have a <a
href="https://www.theatlantic.com/politics/archive/2017/07/a-major-victory-for-the-right-to-record-police/533031/">constitutional
rights to record police</a>. It took ten <em>years</em> for the law to
catch up to the proliferation of portable, high-quality recording
devices, and even then our <em>societal</em> norms haven't caught up.
Fifty years ago, <a
href="https://en.wikipedia.org/wiki/Future_Shock">Future Shock</a> shook
the world with its predictions of radical change and evolving
technology, which is also addressed in <a
href="https://www.npr.org/2016/06/30/484215904/encore-future-shock-40-years-later">this
NPR article</a>.</p>
<p>As technology and our world evolves, <strong>much will change and we
must change with it.</strong> One cannot stick their head in the sand,
say "I disagree," and simply expect the world to adapt. Neither nature,
expanding geopolitics, nor technology will care, but with time and
effort maybe we can enshrine more cosmopolitan understanding and norms
into our laws.</p>]]></description>
          <link>https://gluecode.net/blog/2018/07/22/pace-of-change.html</link>
          <guid>https://gluecode.net/blog/2018/07/22/pace-of-change.html</guid>
          <pubDate>Sun, 22 Jul 2018 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Google Follow-up</title>
          <description><![CDATA[<blockquote>
<p>Published 2018-03-16</p>
</blockquote>
<p>A lot of people with whom I've shared my journal entry were confused
as to why I would rage-quit Google entirely when I was upset with only
one piece of the company.</p>
<p>Normally I wouldn't. Normally, I would just pivot my usage over to
another provider, stop using the offending service, and get on with
life. What's different now is that rage-quitting is often the only way
to get traction on much of anything these days. If I didn't rage-quit,
nobody would respond, nobody would notice.</p>
<p>Google's divisions care about their bottom line and their publicity.
By publicly <a
href="https://twitter.com/tydavis313/status/964328720210132992">saying
they are terrible</a>, I get the opportunity to get <em>some</em> kind
of response from Google. That didn't happen here, but then again, Google
doesn't really care about individuals unless they're huge influencers,
in which case they'd also like to hire you...</p>
<p>The short of it is, we still have enough alternatives to choose a
different option. Until we get to the point where <a
href="https://en.wikipedia.org/wiki/Presidency_of_Theodore_Roosevelt#Trust_busting_and_regulation">companies
aren't allowed</a> to have such <a
href="http://www.ushistory.org/us/43b.asp">significant market power
(again)</a>, companies will end up reaching a size where they don't care
about their detractors and stop innovating. Hence Google.</p>
<p>I'll leave you with a quote. It's more political, but everything is
getting political these days:</p>
<blockquote>
<blockquote>
<p>&#x201C;The personal, as everyone&#x2019;s so fucking fond of saying, is political.
So if some idiot politician, some power player, tries to execute
policies that harm you or those you care about, take it personally. Get
angry. The Machinery of Justice will not serve you here &#x2013; it is slow and
cold, and it is theirs, hardware and soft-. Only the little people
suffer at the hands of Justice; the creatures of power slide from under
it with a wink and a grin. If you want justice, you will have to claw it
from them. Make it personal. Do as much damage as you can. Get your
message across. That way, you stand a better chance of being taken
seriously next time. Of being considered dangerous. And make no mistake
about this: being taken seriously, being considered dangerous marks the
difference - the only difference in their eyes - between players and
little people. Players they will make deals with. Little people they
liquidate. And time and again they cream your liquidation, your
displacement, your torture and brutal execution with the ultimate insult
that it&#x2019;s just business, it&#x2019;s politics, it&#x2019;s the way of the world, it&#x2019;s
a tough life and that it&#x2019;s nothing personal. Well, fuck them. Make it
personal.</p>
<p>-Quellcrist Falconer Things I Should Have Learned by Now, Volume
II&#x201D;</p>
</blockquote>
<p><a href="https://www.goodreads.com/quotes/tag/takeshi-kovacs">-
Richard K. Morgan</a></p>
</blockquote>]]></description>
          <link>https://gluecode.net/blog/2018/03/16/google-follow-up.html</link>
          <guid>https://gluecode.net/blog/2018/03/16/google-follow-up.html</guid>
          <pubDate>Fri, 16 Mar 2018 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Google is terrible</title>
          <description><![CDATA[<blockquote>
<p>Published 2018-02-26</p>
</blockquote>
<p>I'll admit it -- I've been a Google fanboy for a long while. They
have really good web-interfaces, they work at providing consistent APIs
for their products (I'm lookin' at you, AWS), and their products
generally work as-advertised. They even had that motto: "Don't be
evil"</p>
<p>My main complaint now is with their utter lack of customer service
and support. I had heard anecdotes that they weren't the best about
supporting their customers (even when paid for said support). I had
experienced some of that "bad support" myself, but I dismissed those
reports and my own mistreatment with the assertion that one doesn't need
support with good documentation. Well, I was wrong.</p>
<p>Google is a <a href="https://fi.google.com/">phone service
provider</a>. They are registered with the other TelCos as a "prepaid"
system, in that you pay ahead for each month of service. They also have
a "device protection" plan to permit replacing a broken phone at a
reduced cost, available <a
href="https://support.google.com/fi/answer/6309809?hl=en">within 30
days</a> of device purchase.</p>
<p>The problem with Google is that, even when you <a
href="https://support.google.com/fi/answer/7535199?hl=en&amp;ref_topic=4596407">trade
in</a> or purchase a new phone, <em>you are still charged for device
protection on the old phone.</em> There is no notice that you will
continue paying $5 per month ($60/year) on a service that you can't use,
nor is there any option to transfer said coverage to your new phone,
<em>even if you've been paying on a different device up to this
moment.</em> Never mind the cost to the customer is the same (five US
dollars), you've been paying on the wrong device, so you don't get
device protection.</p>
<p>For what it's worth, I would have shrugged this off if it weren't for
the Fi Support team being inconsistent in their own messaging. They
can't even agree with themselves!</p>
<p>This is the last straw. Google was supposed to be better. Google
<em>was</em> better ten years ago. Since then, they've turned into the
worst parts of themselves and pursued a business venture that lags
<em>far</em> behind companies that actually want to retain their
customers.</p>
<p>Now, I'm migrating my customers and personal hosting to other
providers like <a href="https://azure.microsoft.com/en-us/">Azure</a>,
moving my phone plans to <a href="https://tmobile.com/">T-Mobile</a>,
and doing my dead-level best to get off Google's products in their
entirety (including Gmail and Drive). In a professional context, I'm
advocating for using cloud providers like Azure or AWS, mail from
Outlook.com, and Office365.</p>
<p>Google, you've failed at being a company we can root for.</p>]]></description>
          <link>https://gluecode.net/blog/2018/02/27/google-is-terrible.html</link>
          <guid>https://gluecode.net/blog/2018/02/27/google-is-terrible.html</guid>
          <pubDate>Tue, 27 Feb 2018 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Docker Shenanigans</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-11-03</p>
</blockquote>
<p>I've been working with docker since <a
href="https://github.com/moby/moby/releases?after=v1.7.0-rc2">v1.6.0 was
"new."</a> I like docker. I think it has many good qualities. What I
don't appreciate is how badly we developers, devops, and ops-persons
abuse it.</p>
<h2 id="docker-is-not-a-vm">Docker is not a VM</h2>
<p>A virtual machine has virtual hardware, an internal operating system
(which can be entirely different from the host), and runs <em>the entire
Operating System</em> including its own memory management, software
updates, user accounts, etc.</p>
<p>Docker containers, by contrast, share a kernel and otherwise acts
like a FreeBSD/Solaris <a
href="https://en.wikipedia.org/wiki/FreeBSD_jail">jail</a> for the
process(es) inside, with a single user: root.</p>
<p>Docker leverages multiple technologies, acting as a frontend: <a
href="https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt">kernel
control groups</a> (cgroups), <a
href="https://docs.docker.com/engine/userguide/storagedriver/">Overlay</a>
(<a href="https://en.wikipedia.org/wiki/Copy-on-write">COW</a>)
Filesystems, and <a
href="https://en.wikipedia.org/wiki/Linux_namespaces">kernel
namespaces</a>. As such, the host configuration (e.g. kernel version)
matters significantly more to a docker host than a VM host. This means
Docker can pack more containers on a single host than most hypervisors
can pack VMs, with dramatically less overhead per container.</p>
<h2 id="stop-making-oversized-docker-images">Stop making oversized
docker images</h2>
<p>Docker containers are designed such that the application defined in
the CMD or ENTRYPOINT directive is the <a
href="https://en.wikipedia.org/wiki/Init">init process</a> for the
container. The technology behind docker (above) means you need <em>just
enough</em> to run the intended application. For most applications, that
"just enough" can be as little as a single binary, or a dozen supporting
libraries, or a JVM installation.</p>
<p>In order to make this "just enough" container, there's a specific
method called <a
href="https://gluecode.net/blog/2017/11/02/docker-builder-pattern.html">the
Builder Pattern</a> that leverages container impermanence to build an
application with minimal dependencies in the final product image. It's
so powerful, you don't even need the language tools installed on your
machine to build!</p>
<p>Make your production (deployment) containers as small as you
reasonably can. It makes <em>everything</em> easier.</p>
<h2 id="stop-trying-to-hack-docker-to-make-it-go-faster">Stop trying to
"hack" docker to make it go faster</h2>
<blockquote>
<p>Fix your architecture / code instead</p>
</blockquote>
<p>Docker is not a panacea. It enables some very useful and secure
optimizations for developers and operations/infrastructure teams alike.
Its general-purpose design means it works "by default" for most
architectures and applications as well.</p>
<p>If you've done everything to optimize the container, and moving an
application into docker causes it to perform <em>worse</em> than when
running natively, take a long look at the code and its architecture.
Even traditional monoliths like <a
href="https://hub.docker.com/_/mysql/">mysql</a> &amp; <a
href="https://hub.docker.com/_/postgres/">postgresql</a> perform well in
containers.</p>]]></description>
          <link>https://gluecode.net/blog/2017/11/03/docker-shenanigans.html</link>
          <guid>https://gluecode.net/blog/2017/11/03/docker-shenanigans.html</guid>
          <pubDate>Fri, 03 Nov 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Docker Builder Pattern</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-11-02</p>
</blockquote>
<p>The Docker Builder Pattern is a highly useful pattern for leveraging
docker containers to generate artifacts and then package those artifacts
in a runtime-only image. For languages which produce binaries or
single-data archives (e.g. Go, Java/JVM languages, Rust, etc), this
pattern minimizes production container sizes, accelerating deployment
while reducing incidents of broken dependencies, conflicting build
libraries, and permits centralized control of build tools.</p>
<p>Note, this does not apply to interpreted languages such as Python,
Ruby, or NodeJS, though similar results can be achieved in NodeJS with
<a
href="https://medium.com/@andrejsabrickis/modern-approach-of-javascript-bundling-with-webpack-3b7b3e5f4e7">webpack</a>
and other preprocessing measures.</p>
<blockquote>
<p>Examples below include both Windows and Linux/OSX equivalent
commands.</p>
</blockquote>
<h2 id="example-problem">Example Problem</h2>
<p>Check out the code from <a
href="https://github.com/tydavis/hello-world-docker">https://github.com/tydavis/hello-world-docker</a>
and make sure you <a href="https://www.docker.com/get">have Docker
installed</a> and running.</p>
<h3 id="build-the-binary">Build the Binary</h3>
<p>If you don't have the Go compiler installed, don't worry! <em>You're
not going to need it.</em></p>
<p>Make sure your shell is in the <em>hello-world-docker</em> directory
and execute the following command:</p>
<blockquote>
<p>For both Linux and Windows (Powershell)</p>
</blockquote>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">docker</span> run <span class="at">-v</span> <span class="va">${PWD}</span>:/go/src/github.com/tydavis/hello-world-docker <span class="dt">\</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>-w /go/src/github.com/tydavis/hello-world-docker <span class="at">-it</span> golang:alpine <span class="dt">\</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>/bin/sh <span class="at">-c</span> <span class="st">&quot;CGO_ENABLED=0 go build &quot;</span></span></code></pre></div>
<p>Let me walk you through this command:</p>
<ol type="1">
<li>"-v &#x2026;" binds <a
href="https://docs.docker.com/engine/reference/commandline/run/#mount-volume--v-read-only">the
current working directory</a> to the relevant location inside the
container (the part after the colon : )</li>
<li>"-w &#x2026;" <a
href="https://docs.docker.com/engine/reference/commandline/run/#set-working-directory--w">sets
the container's current working directory</a> to your mounted
directory</li>
<li>"-it" grants you an <a
href="https://docs.docker.com/engine/reference/commandline/run/#examples">interactive
terminal connection</a></li>
<li>"golang:alpine" is the latest set of Go compiler and utilities built
on top of <a
href="http://containertutorials.com/alpine/get_started.html">Alpine
Linux</a>. Since we don't need extra utilities like the race detector or
other glibc-exclusives, it's a safe choice.</li>
<li><code>/bin/sh -c "CGO\_ENABLED=0 go build"</code> -- this command
disables dynamic linking, which creates a statically-linked binary.</li>
</ol>
<p>What did we actually do? We just created a Go binary without
installing <em>anything</em> to our machine.</p>
<h3 id="build-the-docker-image">Build the Docker image</h3>
<p>Now, with the binary created, it's just another file to docker. We
can build our "production" image with:</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ex">docker</span> build <span class="at">-t</span> hello-world-docker:1 .</span></code></pre></div>
<p>If you dig into the Dockerfile, you'll see that we start with <a
href="https://docs.docker.com/develop/develop-images/baseimages/">the
scratch layer</a>. That means the only thing in this container is the
binary. Let's look at the image size:</p>
<pre class="text"><code>tydavis@utils:~/go/src/github.com/tydavis/hello-world-docker$ docker images
REPOSITORY           TAG                 IMAGE ID            CREATED         SIZE
hello-world-docker   1                   fc2081dda00b        10 seconds ago  2.03MB
golang               alpine              6e8378057093        7 days ago      269MB
tydavis@utils:~/go/src/github.com/tydavis/hello-world-docker$ du -h hello-world-docker
2.0M    hello-world-docker</code></pre>
<h2 id="what-about-multi-stage-builds8">What about <a
href="https://docs.docker.com/develop/develop-images/multistage-build/">Multi-Stage
Builds</a></h2>
<p>Multi-stage builds take the wrong approach.</p>
<p>Without using mount-points, users have been ADDing or COPYing their
entire codebase into the container image via a <em>docker build</em>
command, then using <em>docker cp</em> to extract the resulting
artifacts. For languages which emit artifacts (binaries, or single
archives like JARs), copying code into a container is fundamentally
flawed.</p>
<p>The multi-stage concept takes this further down the "wrong" path,
encouraging this same copy-code-into-image mindset and providing an
unnecessary function to discard the image inline during build process.
As demonstrated above, one does not have to build or modify the
tools/compiler container every time, meaning artifact-build-time is
significantly faster than the multi-stage process, even with build layer
caching.</p>
<h2 id="conclusion">Conclusion</h2>
<p>If one is using a language that permits generating artifacts
(Go[lang], Java/JVM languages, C/++, etc) then copying code into the
image will unnecessarily bloat the result. One should be using the
builder pattern instead.</p>
<p>Conversely, if using something like Python, Ruby, or another
interpreted language, then copying into the image may be the only
solution due to runtime environment requirements.</p>]]></description>
          <link>https://gluecode.net/blog/2017/11/02/docker-builder-pattern.html</link>
          <guid>https://gluecode.net/blog/2017/11/02/docker-builder-pattern.html</guid>
          <pubDate>Thu, 02 Nov 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Learning Rust</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-10-24</p>
</blockquote>
<p>I'm in the process of <a href="https://www.rust-lang.org/">learning
Rust</a> to build my systems-language skills. While at first my brain
outright rejected the format and design of rust, I am now working my way
through <a href="https://rustbyexample.com/">Rust By Example</a> and the
results are dramatically different. Finally, it's starting to make
sense, and the benefits of the language design are becoming more
apparent (especially from my perspective as an SRE).</p>
<p>I've been learning Go for the last few years, which makes Rust an
immensely foreign design at first. <em>Let? Mut? Traits? So much
syntactic sugar&#x2026; <strong>where are my goroutines</strong>?!</em> Go was
designed to fix long compile times, threading problems, and in general
provide an "easy onboarding experience" for developers through simple
design and having a lot of "batteries included" libraries. Rust, on the
other hand, is trying to fix the problems with C/C++ as a systems
language, focusing on code safety as a top concern.</p>
<p>I haven't yet built anything using Rust -- I still fall back to Go
first -- but I do find <a
href="https://github.com/BurntSushi/ripgrep">ripgrep</a> and <a
href="https://github.com/sharkdp/fd">fd</a> are two incredibly helpful
(and crazy-fast) programs worth installing everywhere. Once I start
building my own solutions in Rust, I'll write a follow-up post with
better insights.</p>]]></description>
          <link>https://gluecode.net/blog/2017/10/24/learning-rust.html</link>
          <guid>https://gluecode.net/blog/2017/10/24/learning-rust.html</guid>
          <pubDate>Tue, 24 Oct 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Why clean up git branches</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-09-07</p>
</blockquote>
<h2 id="problem">Problem</h2>
<p>When encountering issues like:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">Cloning</span> repository...</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="ex">Cloning</span> into <span class="st">&#39;/builds/username/repo_name&#39;</span>...</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="ex">fatal:</span> pack is corrupted <span class="er">(</span><span class="ex">SHA1</span> mismatch<span class="kw">)</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="ex">fatal:</span> index-pack failed</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="ex">ERROR:</span> Job failed: exit code 1</span></code></pre></div>
<h2 id="solution">Solution</h2>
<p>Run <code>git gc --aggressive</code> and/or
<code>git repack -a -d -f --depth=50 --window=250</code> on the origin
repository. If you are using a system like Gitlab / GitlabCI, manually
trigger <a
href="https://docs.gitlab.com/ee/administration/housekeeping.html">the
Housekeeping routine</a>.</p>
<h2 id="background">Background</h2>
<p>Central repositories can have git index corruption and other slowness
operations due to fragmentation and high numbers of objects in a git
repository. By deleting merged branches, using only lightweight tags for
references (instead of, say release branches), and squashing merges into
a single commit, we reduce the total number of objects git is required
to manage.</p>
<p>Normally, git will automatically collect unused references, but the
incremental GC operations don't regularly go back and rebuild or repack
the repository data. These manual invocations of git gc/repack will
resolve the issue.</p>]]></description>
          <link>https://gluecode.net/blog/2017/09/07/why-clean-up-git-branches.html</link>
          <guid>https://gluecode.net/blog/2017/09/07/why-clean-up-git-branches.html</guid>
          <pubDate>Thu, 07 Sep 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Ignorance costs us everything</title>
          <description><![CDATA[<p>=============================</p>
<blockquote>
<p>Published 2017-08-22</p>
</blockquote>
<p>There are a great deal of things I wish I had known when I was
younger. What's worse is when I forget lessons I've already
learned...</p>
<blockquote>
<p>Manners cost you nothing but ignorance will cost you everything.</p>
<p>-- Ryan Swain</p>
</blockquote>
<p>Being polite to others is easy when you prioritize it. Rudeness ruins
me if I am not mentally present, when I let my brain stem take over my
reactions, and this has hurt me ever since I thought I was <em>free to
act without thinking</em>.</p>
<p>Somewhere around the time I went off to college, I thought I had
"found my people," that I was able to relax and "just be myself." The
truth is, I hadn't. I had found people who weren't willing to hold me to
a higher standard of interaction, and expected me to relate at their
level. So I did.</p>
<p>Years passed before reality was shoved in my face: my emotions, my
unthinking actions, the <em>inertia</em> I was carrying from those
college years forward was getting in my way. I was too concerned with
what I thought was <em>right</em>, with "following the rules," and
<strong>ignored</strong> the truth of customer service, which caused me
to be passed over for a promotion. After wrestling with the source of my
failure, I firmly sat on my ego and made my focus the customer - damn
the rules. Not long after that I was rapidly, suddenly successful -- I
had my promotion, with top marks from customers, and was regarded as an
expert in my specialty. I would repeat this kind of mistake whenever I
thought my main contributions were technical and not social.</p>
<p>I moved on to another company where they had significant technical
challenges, but where I <em>delivered</em> was when I provided work
which a customer needed badly. I'll never forget the day I reworked an
otherwise neglected script and turned over the result to our account
manager, with instructions on how the script could be adapted for other
clients -- last I heard, it was in use two years later by every one of
the top 20 clients.</p>
<p>Moving on to yet another company, I was thrown in the deep end with
technology I had never worked with or seen. After six months of focused
work on Kubernetes and our use-cases, I had nearly every technical
aspect dialed-in and backed by research. Yet my role fell apart in
relating to others.</p>
<p>I delivered some changes by fiat, argued emotionally, vilified any
opposition to my plans, and refused to examine those across the table
from me&#x2026; These were not my proudest moments and I was at least partly
aware of how bad the situation was. So, I took opportunities to attend
Meetups, to <a href="https://youtu.be/JDUV3fjhFEI">deliver
presentations</a>, and <a href="https://youtu.be/w-snFo0pPJE">share
stories of our success</a>, thinking that would force a better change in
myself. It didn't, but I wouldn't be aware of that until my next
job.</p>
<p>Today, I am in a position where my job is, unofficially, an
experiment. This role has no power unto itself. I don't even work on the
same Operations tickets as my peers, despite being an SRE in title. I'm
basically <a
href="https://en.wikipedia.org/wiki/Swiss_neutrality">Switzerland</a>,
mediating between and negotiating with two groups that sometimes treat
each other aggressively, and sometimes don't even agree on the same use
of language.</p>
<p>Here is where the social aspects are always apparent. Here is where
my standout contributions are, again, social rather than technical. I
cannot forget how important that is.</p>
<p>Businesses are inherently social. The solitary, asocial
Creator-Person is a <strong>myth</strong> and the mythology is powerful
in the tech sector. We have been misled by the mythology by perpetuating
it, and I have forgotten, repeatedly, that the technical parts are the
<em>least</em> of what we have to contribute.</p>
<p>I am never free to act without thinking. Manners, politeness, and a
helping of <a
href="http://www.businessinsider.com/former-fbi-hostage-negotiator-chris-voss-how-tactical-empathy-can-help-you-get-what-you-want-2016-5">tactical
empathy</a> make the impossible possible. I must <em>remember</em> the
lessons of the past, think before I speak or act, and focus on what
works.</p>]]></description>
          <link>https://gluecode.net/blog/2017/08/22/ignorance-costs-us-everything.html</link>
          <guid>https://gluecode.net/blog/2017/08/22/ignorance-costs-us-everything.html</guid>
          <pubDate>Tue, 22 Aug 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>It's all politics</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-08-15</p>
</blockquote>
<p>It's really hard to watch the news unfold lately. I find the
Charlottesville protests (is it too soon to call them hate
groups/crimes?) absolutely abhorrent. I've talked about the <a
href="encrypt-everything.md">right to privacy on the internet</a>, but
in <a
href="https://www.nytimes.com/2017/08/14/us/charlottesville-doxxing.html?_r=0">cases
like these</a>, it's entirely different: your right to bigoted, racist
beliefs ends when you pick up a sign, gather publicly, or otherwise
impinge on the rights of others. In short, it's the <strong>Act</strong>
that is censured, not the belief. And when it comes to <a
href="https://www.washingtonpost.com/news/post-politics/wp/2017/08/15/after-charlottesville-trump-retweets-then-deletes-image-of-train-running-over-cnn-reporter/">speaking
publicly</a>, that includes Twitter/Facebook.</p>
<p>We tend to dismiss the harsh <a
href="https://en.wikipedia.org/wiki/Ingroups_and_outgroups">words of an
outgroup</a> because "they are not us," and rely more on what we see
with our own ingroups. If we don't <em>see</em> someone in our group
being an asshole, and our ingroup doesn't confirm it, we tend to not
believe it. That impulse has served us well for most of our history,
minimizing enemy propaganda efforts and promoting group cohesion, but
this resistance to turning on one's own also gives some measure of
protection to the deviants of any social group: as long as they don't
deviate around their ingroup, they have more freedom to deviate among
those outside it.</p>
<p>During my studies in sociology, we reviewed the case of two groups of
teenagers in a small town: the skaters and the prep-boys. The skaters
were seen as a nuisance and deviants by the townspeople because they
would hang around parks, occasionally vandalize a business or part of
their school, and would "exhibit deviant behavior" as teenagers often
do. On the other hand, the prep-boys were seen as "good kids" by the
townspeople, despite causing <em>more</em> damage and vandalism. What
was the difference? The "good kids" had cars and so would drive to
another town, isolating their vandalism to a place 30-plus minutes away,
before returning home. There was no difference in the group members
except that one had the means to travel and be relatively anonymous,
while the other did not.</p>
<p>This ability to be anonymous is also not new in society, as one could
always manage to travel when motivated, but <em>temporary</em> anonymity
only came about during the <a
href="https://en.wikipedia.org/wiki/Baby_boomers">Boomer generation</a>
with cheap cars and nationwide infrastructure. When considered together,
participating in a protest hundreds or thousands of miles away
<em>seems</em> like a relatively anonymous activity. One might think "<a
href="https://en.wikipedia.org/wiki/What_happens_on_tour,_stays_on_tour">what
happens here stays here</a>" <em>i.e.</em> nothing I do abroad will
affect my life at home, so I can do what I want and not suffer the long
term consequences. Thanks to the ubiquity of cellphones-with-cameras and
the rapid propagation of information on social media networks, deviant
behavior <em>anywhere</em> is <strong>no longer an anonymous
activity</strong>.</p>
<p>Having lived in small(er) towns for most of my childhood and
young-adult life, I know very well how disruptive, invasive, and
anxiety-inducing it can be when <strong>everyone you see</strong> knows
your business. You have to be constantly aware of the group mindset,
policing your words and actions accordingly, or your livelihood is
<strong>gone</strong>. What simultaneously scares and excites me is that
this small-town mindset is now becoming a reality for the <strong>entire
nation</strong>.</p>
<p>White supremacy and other antisocial, intolerable mindsets are being
exposed because Trump and his administration have emboldened them. They
now feel that they don't have to hide. That's not reality, and the fact
that these people are <a
href="https://en.wikipedia.org/wiki/What_happens_on_tour,_stays_on_tour">being
identified and fired</a> is evidence of that. As powerful as this
activity is, it's also the definition of "<a
href="https://www.washingtonpost.com/news/the-intersect/wp/2017/08/14/a-twitter-campaign-is-outing-people-who-marched-with-white-nationalists-in-charlottesville/">tyranny
of the masses</a>." In this case, I argue its actions are morally and
socially just -- they are attacking those who are breaking <a
href="https://en.wikiquote.org/wiki/Tyranny_of_the_majority">the social
contract of tolerance</a> -- but when this mechanism doesn't have a
ready-made target on which to focus, it can (and likely will) go
devastatingly wrong. Unchecked, people who have merely
<em>different</em> views will be subject to the same scrutiny and
pressure as those who have <em>completely antisocial</em> views, and
their lives will be ruined just as badly. A great deal of self-control
will be needed and, thankfully, I see that already affecting subsequent
identification activities. It seems the "masses" are only so vicious
because of the subject, and it is awe-inspiring.</p>
<p>At best, this "rise of white supremacy" should be seen as an <a
href="https://www.goodreads.com/work/quotes/6492090-the-open-society-and-its-enemies">extinction
burst</a>, a final gasp as society purges the groups which actively
violate the social contract before turning to a socially-enforced state
of progressive liberalism. This is my hope and dream. The next question
will be whether or not our elections will bear out a change of seats for
our legislature <a
href="https://en.wikipedia.org/wiki/Extinction_(psychology)">in the
midterms</a>.</p>
<h2 id="update">Update</h2>
<p>Eugene Volokh wrote an article about the limitations of employers,
and specifically how employees can't be fired for acting politically
outside of work (in some states): <a
href="https://www.washingtonpost.com/news/volokh-conspiracy/wp/2017/08/16/can-private-employers-fire-employees-for-going-to-a-white-supremacist-rally/">Can
private employers fire employees for going to a white supremacist
rally?</a></p>
<p>For those who don't have a subscription, <a
href="https://gluecode.net/web/articles/CanPrivateEmployersFireEmployeesForGoingToWhiteSupremacistRally-WaPo.pdf">here's
a PDF</a>.</p>]]></description>
          <link>https://gluecode.net/blog/2017/08/15/its-all-politics.html</link>
          <guid>https://gluecode.net/blog/2017/08/15/its-all-politics.html</guid>
          <pubDate>Tue, 15 Aug 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Your Ops team is dying</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-08-03</p>
</blockquote>
<p>Operations teams which don't evolve are dying off, and that's a good
thing.</p>
<h2
id="infrastructure-is-a-matter-of-services-service-providers-and-your-budget">Infrastructure
is a matter of services, service providers, and your budget</h2>
<p>Most companies don't buy actual hardware servers these days. Cloud
Providers make it easy for services to leverage the provider's economies
of scale and rent compute time and services. As such, your Operations
team isn't building networks, hooking up servers, or doing anything
except laying down the services on which your business will run. Also,
teams will take advantage of managed services like on-event functions
(Cloud Functions, AWS Lambda), or databases, meaning there will be less
for your operations team to worry about.</p>
<h2
id="developers-must-be-responsible-for-managing-and-deploying-their-code">Developers
must be responsible for managing and deploying their code</h2>
<p>Developers know their code, and how to scale it. Without a lot of
documentation or runbooks, the Operations team knows little beyond "turn
it off and on again" to resolve a 3am pager alert.</p>
<p>When you empower your Developers, they also take those
responsibilities seriously. <a
href="https://medium.com/@copyconstruct/the-death-of-ops-is-greatly-exaggerated-ff3bd4a67f24">This
article makes some really good points</a> about this very issue, and how
Operations can't necessarily handle the code-related bug in a
service.</p>
<h2
id="focus-on-simplicity-and-small-teams-for-fast-feature-deployment">Focus
on Simplicity and small teams for fast feature deployment</h2>
<p>Operations usually scales with the rate of Developers, but it's a
matter of ratios: some shops have a 10:1 rate of Developers to
Operations personnel, while others reach 50:1 or higher.</p>
<p>I've personally worked on a team where the ratio was 60:1. After six
months of work at (re)building the infrastructure, providing
documentation, and campaigning for developers to be their own tier-1
on-call, I was moved to Tier-2 on-call. For the next year, I didn't get
a single pager duty alert, despite being on-call for most of it.</p>
<p>If teams can be <a
href="http://firstround.com/review/Why-Yammer-believes-the-traditional-engineering-organizational-structure-is-dead/">kept
small</a> and <a
href="http://www.effectiveengineer.com/blog/hidden-costs-that-engineers-ignore">the
architecture kept as simple as possible</a>, shipping features and
resolving bugs will be inherently faster than any large-scale
organization structure. The (possibly apocryphal) Amazon "two pizza"
limit applies here: if you can't feed the <em>entire</em> team with an
order of two large pizzas (16 slices), the team is too big.</p>
<h3 id="recap">Recap</h3>
<p>Operations teams need to focus on infrastructure and applications
which let Developers create, deploy, and scale their designs without
involvement from the Operations team. At best, the Operations team will
be invisible -- no one interacts with them, the infrastructure doesn't
fail, and the architecture is simple enough to understand and exploit
without calling them in.</p>
<p>When the Operations team builds the right kind of infrastructure, you
don't need a ratio of 10:1 -- you have a team small enough to be fed by
<strong>one</strong> pizza or, ideally, <strong>no pizza</strong>. Your
developer teams leverage provider-optimized services, keep the
architecture simple, and understand the weak points of the architecture
just like your Operations team did...</p>
<h3 id="post-script">Post Script</h3>
<p>For those who might think this is alarmist, or oversimplifying the
role of an Operations team in an organization, let me clarify: this is
my job. I've been working in an operations role for over a decade and
seen radical efficiency and delivery (regardless of Product success)
where companies follow this mindset. When companies have large-scale
operations and IT organizations, that's when inefficiencies, delays, and
the "ops vs devs" culture wars start. The future is having a group of
Engineers, a focus on simplicity, and a problem to solve.</p>]]></description>
          <link>https://gluecode.net/blog/2017/08/03/your-ops-team-is-dying.html</link>
          <guid>https://gluecode.net/blog/2017/08/03/your-ops-team-is-dying.html</guid>
          <pubDate>Thu, 03 Aug 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Google Sites</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-08-01</p>
</blockquote>
<p>I've made <a
href="https://gluecode.net/blog/2017/03/05/shift-blogging-engines.html">so</a>
<a
href="https://gluecode.net/blog/2017/05/16/blog-on-a-diet.html">many</a>
changes to this blog/website that this should scarcely be a surprise for
anyone any more.</p>
<p>While normally I would be serving off of my "naked domain" (<a
href="https://gluecode.net/">gluecode.net</a>) I instead elected to
redirect the root of my blog to Google Sites and forego <a
href="https://gsuite.google.com/">purchasing the GSuite</a> for my
domain. Reasons for this include:</p>
<ul>
<li>Transferring data and information from <a
href="mailto:tydavis@gmail.com">tydavis@gmail.com</a> would have been
difficult or impossible (including Play Store, purchased storage space,
and ~20GB of data migration).</li>
<li><a href="https://support.google.com/a/answer/2518318">Naked
domain</a> setup might be possible using basic domains.google.com
records.</li>
</ul>
<p>While I said I'd much rather "roll up my sleeves" and present exactly
what I want, this isn't markedly worse than my original work. It adapts
to mobile display without issue. I can embed maps, calendars, and
youtube links at will. I can add Google objects (Docs, Slides, etc) and
regardless it all reads cleanly on the screen.</p>
<p>I'm sure that I'll find something wrong with Sites in the future, but
for now, I'm going to keep watching Gophercon videos like the one
below.</p>]]></description>
          <link>https://gluecode.net/blog/2017/08/01/google-sites.html</link>
          <guid>https://gluecode.net/blog/2017/08/01/google-sites.html</guid>
          <pubDate>Tue, 01 Aug 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Gophercon 2017 and other stuff</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-07-24</p>
</blockquote>
<p>Keeping this post a bit short. Gophercon 2017 has <a
href="https://www.youtube.com/playlist?list=PL2ntRZ1ySWBdD9bru6IR-_WXUgJqvrtx9">released
its videos on Youtube</a> in a convenient playlist. I'm about 7 videos
in and I have to say the talks are fantastic. Definitely worth
watching.</p>
<p>Also, in the interest of keeping private things private, I have moved
this blog and other repositories into my private <a
href="https://gitlab.com/tydavis/">Gitlab account</a>. I'll be leaving
only my most public and reasonably curated things on Github.</p>
<p>Finally, don't underestimate the power of taking a break. This last
weekend my wife and I spent an overnight in <a
href="http://parks.state.wa.us/292/Twin-Harbors">Twin Harbors State
Park</a>, with easy access to the beach. While my back and sides have
protested the mat on which I slept, my brain was <em>on</em> when I got
back to the house. From that I've been able to write better, code more
clearly and directly, and exert more willpower.</p>]]></description>
          <link>https://gluecode.net/blog/2017/07/24/gophercon-and-stuff.html</link>
          <guid>https://gluecode.net/blog/2017/07/24/gophercon-and-stuff.html</guid>
          <pubDate>Mon, 24 Jul 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>PADI Scuba Open Water certified</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-07-10</p>
</blockquote>
<p>I finally got <a
href="https://www.padi.com/padi-courses/open-water-diver">Open Water
Certified</a> with <a
href="http://www.seattlescuba.com/seattle.html">Seattle Scuba</a>. The
instructors and divemasters were amazing, and I highly recommend that
people who love to explore and adventure give it a try. At the very
least, you have a whole new respect for those divers you see in the
movies!</p>
<p>For what it's worth, I've been bitten by the diving bug. All it took
was the final dive. After doing all the skills and confirming I can
handle myself through all the basic scenarios, we buddied-up and
investigated the bottom of Cove 1 in West Seattle (just off to the left
of <a href="http://saltys.com/">Salty's</a> on Alki beach). It's crab
season, and there is <em>so much</em> down there to see...</p>
<p>After having a chat with my wife, we've agreed to get our <a
href="https://www.padi.com/padi-courses/night-diver">Night Diver</a>, <a
href="https://www.padi.com/padi-courses/dry-suit-diver">Dry Suit</a>,
and <a
href="https://www.padi.com/padi-courses/advanced-open-water-diver-course">Advanced
Open Water</a> training. It's just a matter of "when!"</p>]]></description>
          <link>https://gluecode.net/blog/2017/07/10/padi-scuba-cert.html</link>
          <guid>https://gluecode.net/blog/2017/07/10/padi-scuba-cert.html</guid>
          <pubDate>Mon, 10 Jul 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Chromebook has arrived</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-06-14</p>
</blockquote>
<p>With what I can only describe as an <em>obscene</em> amount of
excitement, I've unwrapped my new <a
href="https://www.amazon.com/Samsung-Chromebook-Convertible-Laptop-XE513C24-K01US/dp/B01LZ6XKS6/">Samsung
Chromebook Plus</a> and it's everything I could have asked for. Typing
is fluid and feels like it will retain that crispness, the display is
ridiculously bright, and even my weird "use the search key as a control
key" setting has migrated over to this baby.</p>
<p>Oh yes, I'm writing this new post on the device. It's definitely in a
position to become my daily-driver (minus the times when work requires
that I use their hardware).</p>
<p>Some negatives at this time include:</p>
<ol type="1">
<li>The keyboard is ever so slightly cramped compared to the rMBP, but
that's due to the 3:2 aspect ratio.</li>
<li>The screen is just so damned BRIGHT it burns my eyeballs a
little</li>
<li>Chunky webpages like Inbox and Slack are taking just a few seconds
to load and "get smooth," but admittedly this happens to my quad-core i7
laptop, and to my rMBP, so no real change there. (Ok, I was hoping for a
little more out of this OP1 processor, but who am I kidding...)</li>
</ol>
<p>Overall I believe I am and will remain extremely pleased with this
purchase, especially once I finish my Coursera classes and can get to
work on more development projects!</p>]]></description>
          <link>https://gluecode.net/blog/2017/06/14/chromebook-has-arrived.html</link>
          <guid>https://gluecode.net/blog/2017/06/14/chromebook-has-arrived.html</guid>
          <pubDate>Wed, 14 Jun 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Final Chromebook Prep</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-06-13</p>
</blockquote>
<p>In preparation for the arrival of my <a
href="https://www.amazon.com/Samsung-Chromebook-Convertible-Laptop-XE513C24-K01US/dp/B01LZ6XKS6/">new
chromebook</a> I'm doing a final review and backup of my data. Not
surprisingly, it's all been backed up and current for months.</p>
<p>The downside to this is that I need to sell my existing laptop
<em>and</em> start removing my personal items from my work laptop. Some
could argue that I shouldn't have had <em>personal data</em> on my
<em>work-provided laptop</em> anyway, but the reality is that the lines
between work and home have been blurred by companies and employees alike
for decades.</p>
<p>That means it's time to sit down, take a long, deep dive into my
machines, and see what needs to go where. I think the hardest part is
going to be building up another server with utilities -- in my case, I
need Libreoffice (or something like it) that can reliably read and
reformat DOCX files into Word97-compatible DOC formats. Why? Because
Google Drive appears to have lost its touted and much-valued
Word-conversion abilities, and my online courses don't know how to
build/write documents in a more compatible format.</p>
<p>Alright, enough yapping. Time to get to work.</p>]]></description>
          <link>https://gluecode.net/blog/2017/06/13/chromebook-prep.html</link>
          <guid>https://gluecode.net/blog/2017/06/13/chromebook-prep.html</guid>
          <pubDate>Tue, 13 Jun 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Ranting about these Whole30 articles</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-05-17</p>
</blockquote>
<p>I am sick and tired of articles <a
href="http://www.glamour.com/story/quitting-whole30-essay">like this
one</a> from <a href="https://www.glamour.com/">Glamour.com</a>.</p>
<blockquote>
<p>"I did Whole30, but I really didn't because I half-assed the program
and then completely gave up when my family visited. Now I've invented
the Whole15!"</p>
</blockquote>
<p>Ok, that doesn't quite convey the reality of the issue, so let me
share with you her own words:</p>
<blockquote>
<p>... If you half-ass it, they say, it doesn't count.</p>
<p>But I'm going to be honest with you now: I half-assed it here and
there. Almond milk is very gross, and I quickly returned to soy as my
preferred morning latte ingredient, bean ban be damned. I didn't ask at
restaurants whether my piece of fish was cooked in butter. And if a
lentil found its way into my veggie mix, who was I to remove it? I
really did leave dairy, sugar (including my beloved Diet Coke), and
gluten behind, but I refused to stick to a "healthy" eating plan that
didn't permit rice cakes and peanut butter. If rice cakes and peanut
butter are wrong, I do not wish to be right.</p>
</blockquote>
<p>Let's get a few things straight:</p>
<ol type="1">
<li>Eating "approximately 1,000 Larabars at every meal" fails the
spirit/intent of Whole30. That alone fails you out of the program.</li>
<li>Eating rice, peanut butter, or any amount of soy
<strong>automatically fails the program.</strong></li>
<li>Bottom line: You failed the <a href="http://whole30.com">Whole30</a>
and you did not complete a <a href="http://whole9life.com/">Whole9</a>
or even a Whole15.</li>
<li>You didn't "invent" the Whole15. That's <a
href="http://www.maydae.com/paleo/doing-a-whole-15/">been around for a
while</a>, and <em>you didn't do that either.</em></li>
</ol>
<p>Why am I emphasizing the fact that you failed, despite your admission
of failure in your very first paragraph? Because you were <em>still</em>
incorrect when you said you made it 15 days. Your program ended the
<strong>second</strong> you put soy creamer in your coffee, or ate a
buttered fish, or let a little lentil into your salad. When did
<em>those</em> things happen? What was it, eight or nine days in? You
never said so in the article.</p>
<p>When it comes to doing a Whole15, you <em>plan</em> to end your
program at the appropriate time, then you follow through. Even Melissa
Hartwig herself said you can complete shortened versions of whatever
length you think appropriate:</p>
<blockquote>
<p>"If you've done at least three full Whole30 programs, been working
hard to maintain your new healthy habits, and just need a little
reminder/pick me up/kick in the pants, a shortened version of the
Whole30 may be just the ticket to help you accomplish some short-term
health and habit goals." -Melissa Hartwig</p>
</blockquote>
<p>Instead, you decided to rationalize the program failure and claim you
did a "Whole 15." Bullshit. Anybody who "modifies" the program or
decides they can have "just a little" <strong>isn't doing the
program.</strong> There are no cheats, there are no exceptions, and you
don't get to say you "did it halfway" if you violate those rules. You
aren't allowed to claim you did anything remotely "Whole 30" if you
<em>change the program.</em></p>
<p><a
href="http://www.glamour.com/story/contributor/elizabeth-logan">Elizabeth
Logan</a>, you and those like you have lied and are deliberately
deceiving the rest of the world with your lie.</p>
<p><strong>Stop it.</strong></p>]]></description>
          <link>https://gluecode.net/blog/2017/05/17/whole30-rant.html</link>
          <guid>https://gluecode.net/blog/2017/05/17/whole30-rant.html</guid>
          <pubDate>Wed, 17 May 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Blog on a diet</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-05-05</p>
</blockquote>
<p>Every time I think I've found a suitable means of running my blog
(e.g. <a href="https://gohugo.io/">Hugo</a>, <a
href="https://ghost.org/">Ghost</a>, etc), I end up reading or reviewing
<em>something else</em> compelling enough to warrant implementation.</p>
<p>I'm an optimizer by trade and by desire -- I love efficiency and get
a thrill out of playing the <a
href="http://idlewords.com/talks/website_obesity.htm">bicycle-riding
hippie to everyone else's SUV-driver</a>. When it comes to this blog,
I've been looking for the simple, easy, and fast implementation that
also delivers <em>just</em> what I want to me (and any incidental
readers -- Thank you!). Since I started <a
href="life-in-chrome.md">moving to a Chromebook</a>, I've had to rule
out local static site generators like Hugo and Pelican, and because I
want to use my own domain with TLS certificates (particularly those from
<a href="https://letsencrypt.org/">Let's Encrypt</a>) and the latest
HTTP2 technology, I have to run my own server with software like <a
href="https://caddyserver.com/">Caddy</a>.</p>
<p>Practically everyone I know swears by <a
href="https://en.wikipedia.org/wiki/Markdown">Markdown</a> format (which
requires a parser/generator to create the real HTML) but it looked easy.
Even the uncool Enterprise kids were doing it, and so I tried it. The
hard part was that I knew HTML already and ended up googling syntax more
than I wanted, and nothing ever came out 100% as I imagined it would in
my head.</p>
<p>Fast-forward a couple of years. About a month ago, I read <a
href="http://idlewords.com/talks/website_obesity.htm">this talk</a> by
<a href="http://idlewords.com/about.htm">Maciej Ceg&#x142;owski</a>. This was
it -- I had found my answer, and it was "roll up your sleeves and get to
work."</p>
<p>Much like my use of Golang, Perl, and Linux, I'm a fan of anything
you can produce or make better with mere effort. In this case, writing
HTML was a little work, but it got me exactly what I wanted.</p>]]></description>
          <link>https://gluecode.net/blog/2017/05/16/blog-on-a-diet.html</link>
          <guid>https://gluecode.net/blog/2017/05/16/blog-on-a-diet.html</guid>
          <pubDate>Tue, 16 May 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Life in Chrome (Part 2)</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-04-04</p>
</blockquote>
<p>It has been over a month since I've switched to using Chrome for all
of my work and I think I've found my happy place.</p>
<p><a href="https://c9.io/">Cloud9</a> is a fantastic editor and, by <a
href="https://github.com/c9/core/blob/master/README.md">following C9's
official directions</a>, I've been able to set up the C9 IDE for both
work and home. <em>No, I am not giving c9.io access to my work
machine.</em> Yes, it is awesome to be able to call up a complete
editor+terminal without a bunch of SSH keys. And the vim/emacs
keybindings are spot-on.</p>
<p>I still haven't resolved my <a href="encrypt-everything.md">DNS
concerns</a> yet, but <a
href="https://www.eff.org/HTTPS-EVERYWHERE">HTTPSEverywhere</a> and <a
href="https://www.ublock.org/">uBlock Origin</a> are doing a bang-up job
of keeping my connections secured and cutting out the ads. I think what
I'll need to do for my home is set up a <a
href="https://www.amazon.com/dp/B01C6EQNNK/">RaspberryPI 3</a> for my
home DNS instead. I've had some issues getting the <a
href="http://internethelp.centurylink.com/internethelp/modem-c2000t.html">C2000T</a>
to honor my settings and <em>not</em> add itself as the DNS resolver,
but I have time on my side. If it comes to the worst situation, I'll buy
my own gigabit-capable wired router.</p>
<p>Finally, after testing <a
href="https://twitter.com/tydavis313/status/847181947486916608">each of
the streaming music services</a> out there, I have settled on Google
Play/YouTube. They are the <em>only</em> services that managed to work
reliably as HTTPS-only. I'm aware that Google is an Ad company by
itself, but there's only so much I can do about protecting myself there.
I'm already a <a href="https://fi.google.com/">Project Fi</a>
subscriber, <a href="https://gmail.com/">GMail</a> user, and a <a
href="https://cloud.google.com/">Google Cloud Platform</a> subscriber.
They already have most of my data. My opposition is with <a
href="http://www.centurylink.com/">CenturyLink</a> deciding that they
have a right to my data.</p>]]></description>
          <link>https://gluecode.net/blog/2017/04/04/life-in-chrome-part2.html</link>
          <guid>https://gluecode.net/blog/2017/04/04/life-in-chrome-part2.html</guid>
          <pubDate>Tue, 04 Apr 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Why encrypting everything on the internet makes sense</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-03-30</p>
</blockquote>
<h2 id="tldr">TL;DR</h2>
<p>Start using <a
href="https://www.eff.org/https-everywhere">HTTPSEverywhere</a> and
support the websites that make a point of delivering your data in a way
that protects <strong>you</strong>.</p>
<hr />
<p>Since <a
href="https://arstechnica.com/tech-policy/2017/03/isps-and-fcc-chair-ajit-pai-celebrate-death-of-online-privacy-rules/">the
House voted to destroy Privacy rules governing ISPs</a>, I've been
reading a lot of discussion on the subject and trying to find ways to
keep me and mine under a nice veil of privacy. I have also <a
href="https://arstechnica.com/security/2015/04/it-wasnt-easy-but-netflix-will-soon-use-https-to-secure-video-streams/">encountered
commentary</a> about how streaming media (like Netflix)
<em>shouldn't</em> be secured, because:</p>
<blockquote>
<p>There are things which don't need encryption and movie streaming is
one of [them]. We don't need the extra power wasted in our world as
datacenters are power hungry monsters. Use encryption for what its
designed for. Protecting confidential data.</p>
<p>In the end every Netflix user is going to pay the extra bill for this
and this is a waste of resources in every possible way.</p>
</blockquote>
<p><strong>It's not.</strong> All of your activity should be encrypted
in ways that cannot be decrypted or tracked and here's why:</p>
<p>Radio and other broadcast media are sent into the ether and
broadcasters have no idea exactly <em>who</em> is listening. You can
build your own crystal radio kit, listen to a station, and the
broadcaster <em>has no idea</em>.</p>
<p>Contrast this with the Internet: every single device on the internet
must <em>request</em> data in order to be <em>sent</em> data. Even fancy
things like <a
href="https://en.wikipedia.org/wiki/Multicast">multicasting</a> still
require nodes to Join or Leave the network. Each system on the internet
is in <em>constant</em>, <strong>identifiable</strong> communication
with other computers in its network.</p>
<p>As such, due to the way Internet Service Providers (ISPs) work, they
have the potential to completely track and control your communications
unless they're 100% encrypted. The only way to end such invasive,
dangerous, and <em>wrong</em> actions by ISPs and other entities is to
use encryption from end-to-end.</p>
<p>I'll leave you with a quote from <a
href="https://www.schneier.com/blog/archives/2006/05/the_value_of_pr.html">Bruce
Schneier</a> (emphasis mine):</p>
<blockquote>
<p>Last week, revelation of yet another NSA surveillance effort against
the American people has rekindled the privacy debate. Those in favor of
these programs have trotted out the same rhetorical question we hear
every time privacy advocates oppose ID checks, video cameras, massive
databases, data mining, and other wholesale surveillance measures: "If
you aren't doing anything wrong, what do you have to hide?"</p>
<p>Some clever answers: "If I'm not doing anything wrong, then you have
no cause to watch me." "Because the government gets to define what's
wrong, and they keep changing the definition." "Because you might do
something wrong with my information." My problem with quips like these
-- as right as they are -- is that they accept the premise that privacy
is about hiding a wrong. It's not. <strong>Privacy is an inherent human
right, and a requirement for maintaining the human condition with
dignity and respect.</strong></p>
<p>[ . . . ]</p>
<p>Watch someone long enough, and you'll find something to arrest -- or
just blackmail -- with. Privacy is important because without it,
<strong>surveillance information will be abused:</strong> to peep,
<strong>to sell to marketers</strong> and to spy on political enemies --
whoever they happen to be at the time.</p>
<p>Privacy protects us from abuses by those in power, even if we're
doing nothing wrong at the time of surveillance.</p>
</blockquote>]]></description>
          <link>https://gluecode.net/blog/2017/03/30/encrypt-everything.html</link>
          <guid>https://gluecode.net/blog/2017/03/30/encrypt-everything.html</guid>
          <pubDate>Thu, 30 Mar 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Ongoing encryption efforts</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-03-23</p>
</blockquote>
<p>I have been using a <a href="http://someonewhocares.org/hosts/">hosts
file override</a> for years to cut the ads and "crap" out of my internet
browsing experience. I recently discovered a <a
href="https://github.com/StevenBlack/hosts">better list</a> and have
been using it for a few weeks. I'm getting far more "<em>you have an
adblocker installed</em>" warnings from websites I frequent, so it's
definitely working better.</p>
<p>Now the Senate has passed <a
href="https://arstechnica.com/tech-policy/2017/03/senate-votes-to-let-isps-sell-your-web-browsing-history-to-advertisers/">laws
that permit ISPs to sell my data to advertisers</a> and I'm ready to
call <a href="https://youtu.be/dsx2vdn7gpY">Game Over [warning:
explicit]</a> on my internet access.</p>
<p>I'm looking at purchasing a <a
href="https://www.amazon.com/Samsung-Chromebook-Convertible-Laptop-XE513C24-K01US/dp/B01LZ6XKS6/">Samsung
Chromebook Plus</a> but with the Chromebook comes the inability to
install an <a href="https://dnscrypt.org/">encrypting DNS proxy</a> and
Google doesn't <a
href="https://groups.google.com/forum/#!topic/public-dns-discuss/rmZTtPAV430">seem
interested</a> in supporting DnsCrypt with their public servers.</p>
<p>What they do provide is <a
href="https://developers.google.com/speed/public-dns/docs/dns-over-https">DNS
over HTTPS</a> which can be somewhat useful if we're running our own DNS
servers at home, but by providing an HTTPS endpoint, <strong>Chrome and
its extensions</strong> could bypass local DNS and make HTTPS-based DNS
requests on their own in order to avoid being spoofed (or to hide
additional DNS requests).</p>
<p>Many things to research here.</p>]]></description>
          <link>https://gluecode.net/blog/2017/03/23/ongoing-encryption-efforts.html</link>
          <guid>https://gluecode.net/blog/2017/03/23/ongoing-encryption-efforts.html</guid>
          <pubDate>Thu, 23 Mar 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Life in Chrome</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-03-08</p>
</blockquote>
<p>My day-to-day computer activities are mostly through my work-provided
laptop. Installing custom compilers or other untested software is
expected for my role, and I've been granted administrative rights to my
laptop, but we also have corporate antivirus and collective host
configuration management. After a chat with the security team, I
reminded myself that the security of our endpoints is more important,
and deserves my attention.</p>
<p>Being firmly in the middle between Dev and Ops, I have a VM
provisioned for my work requirements and it does the job. I don't
actually need my local host's terminal to get my job done.</p>
<p>As an experiment, I created a cheap machine in <a
href="https://cloud.google.com/compute/docs/">Google Cloud Platform</a>
and set it up with my non-work environment, then wiped-and-restored my
work laptop, only installing Chrome (and one piece of videoconference
software we use) to my user's Applications folder.</p>
<p>Magic! Chrome can SSH to other hosts <a
href="https://chrome.google.com/webstore/detail/secure-shell/pnhechapfaindjhompbnflcldabbghjo">via
an extension</a>, we use Google Apps for Office purposes, and my VM at
work took care of the rest. There's even a <a
href="https://chrome.google.com/webstore/detail/ssh-for-google-cloud-plat/ojilllmhjhibplnppnamldakhpmdnibd">chrome
extension</a> that makes the GCP SSH window work better. Best of all,
any "personal projects" I work on are isolated and run little-to-no risk
of infecting my work environment and vice versa.</p>
<p>Now if I ever need to wipe the laptop or have it replaced, I can be
back up and running in a matter of minutes. Given our upcoming security
compliance audits, I can work within every security change because my
host does nothing but act as a dumb terminal.</p>
<p><del>If it weren't for my need to have a self-contained laptop at
home when the internet needs repair, I could</del> <a
href="https://www.amazon.com/dp/B011DDXGVC/">I can</a> get away with
working exclusively on a Chromebook, even when I break my router/access
point.</p>]]></description>
          <link>https://gluecode.net/blog/2017/03/08/life-in-chrome.html</link>
          <guid>https://gluecode.net/blog/2017/03/08/life-in-chrome.html</guid>
          <pubDate>Wed, 08 Mar 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Get on the Upgrade Treadmill</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-03-07</p>
</blockquote>
<p>Always upgrade your software. Upgrading now means less pain
later.</p>
<p>Recently my company upgraded from <a
href="https://kafka.apache.org/">Apache Kafka</a> v0.8 to the latest
available version (v0.10.2). With it came wire protocol changes, two
separate clients were consolidated into only one, Kafka now stores
offsets in itself rather than <a
href="https://zookeeper.apache.org/">Apache Zookeeper</a>, and a dozen
other changes that also needed to be adjusted in our golang
services.</p>
<p>One solid outage-into-the-evening later, I've vowed two things:</p>
<h2 id="always-upgrade-your-software">Always Upgrade Your Software</h2>
<p>If you use the latest libraries, the latest software, and constantly
keep your systems up-to-date, the incremental changes end up becoming
smaller and smaller as new point-versions come out. This get easier as
everyone learns to "ride the wave" and make systems smaller. Constant
upgrades also suggest (though they do not <em>require</em>) a trend
toward smaller services or <a
href="https://martinfowler.com/articles/microservices.html">microservice
architectures</a> in order to keep the upgrades small in scope.</p>
<h2 id="test-everything">Test Everything</h2>
<ul>
<li>If there isn't a test for a block of code, make one. If that code
operates against a remote service, build a mock with expectations (or <a
href="https://en.wikipedia.org/wiki/Design_by_contract">contracts</a>)
defined. If that's too much, and you have sufficient resources, run a
stripped-down copy of the service locally. If you can't do that, at
least consult the documentation and generate a mock out of supposed API
docs.</li>
<li>Always generate your own test data. Don't expect someone else to
make the test data for you.</li>
</ul>]]></description>
          <link>https://gluecode.net/blog/2017/03/07/upgrade-treadmill.html</link>
          <guid>https://gluecode.net/blog/2017/03/07/upgrade-treadmill.html</guid>
          <pubDate>Tue, 07 Mar 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Shift back to blogger/ghost</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-03-05</p>
</blockquote>
<p>I've been migrating this blog between various systems for years. I've
used, Jekyll, most recently, and each time I've thought the new
generator would give me something I needed or wanted.</p>
<p>In the end what <em>really</em> matters is the content, and I feel
that in my pursuit of these technologies, I lost that.</p>
<p>I've migrated off of my old domain and, due to a lack of import
options in Blogger, had to copy-paste my posts and formatting in to the
blogger editor. For the record, the Blogger editor kinda sucks, but when
the editor sucks, you just learn how to work with it -- <a
href="https://gluecode.net/blog/2015/12/18/software-has-boundaries.html">practicing
what I preach!</a></p>
<p>Update: This blog is now running through <a
href="https://ghost.org/">Ghost</a> &amp; <a
href="https://caddyserver.com/">Caddy</a>, on one of my hosts in Google
Cloud Platform, which provides automatic TLS and http2 support.</p>]]></description>
          <link>https://gluecode.net/blog/2017/03/05/shift-blogging-engines.html</link>
          <guid>https://gluecode.net/blog/2017/03/05/shift-blogging-engines.html</guid>
          <pubDate>Sun, 05 Mar 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Code Less</title>
          <description><![CDATA[<blockquote>
<p>Published 2017-03-04</p>
</blockquote>
<p>There are a few core pillars of action and design which I've followed
to great success over the years. They are as follows...</p>
<h2 id="always-question-your-instinct-to-abstract">Always question your
instinct to abstract</h2>
<p>If you are in a SysAdmins/DevOps role, you need to constantly
question the systems you are building. Question <strong>every
single</strong> abstraction or proxy you design. Do you really need to
store secrets in Vault, or can you use instance roles/tags/built-in
functions of your provider? Do you really need Terraform, or Puppet,
when you can accomplish the same with a single Ansible playbook YAML
file? (Or better yet, let the application(s) do the configuration!)</p>
<p>This is a common refrain in development circles -- a lot of those
I've worked with don't like frameworks and abstractions unless they're
absolutely necessary. IT/DevOps/Ops/SysAdmins need to start adopting the
same approaches, and when you do, you end up with a simpler and more
reasonable system.</p>
<p>As an anecdote, I came across a program that wraps docker and
docker-compose, injecting credentials as an added layer directly into
the image build process, because developers aren't allowed to have
non-dev environment credentials. The better solution is to have each
environment inject the credentials into containers <strong>at
runtime</strong> and remove any option to embed credentials into the
image at all. In this way, the docker image remains the same across
environments (facilitating troubleshooting and reliable testing), yet
credentials never cross boundaries.</p>
<h2 id="do-everything-in-your-power-to-remove-abstractions">Do
everything in your power to remove abstractions</h2>
<p>Do you really need a load-balancer in front of a proxy that
communicates with an application? What if I told you <a
href="https://gluecode.net/blog/2015/12/14/no-nat-use-firewalls.html">you
don't need a VPN</a>? As a sysadmin, every extra piece of infrastructure
in your environment is another point of failure. Every wrapper shell
script, every time you use docker-compose instead of plain docker
commands, every instance of make instead of a POSIX-compliant shell ( <a
href="https://en.wikipedia.org/wiki/Almquist_shell">ash</a>) script ends
up being one more barrier to a new-hire onboarding process, or one more
hurdle for migrating to a new platform.</p>
<h2 id="prefer-static-programs-to-interpreteddynamic-systems">Prefer
static programs to interpreted/dynamic systems</h2>
<p>When possible, use systems that have no external dependencies, no
local dependencies, and/or are statically-linked (at compile time). So,
given C vs a Java/Python/Ruby program, prefer the C program. When
possible, build Go (golang) programs as statically linked rather than
relying on dynamic libraries.</p>
<p>By building static packages of software, one can guarantee (or have a
high degree of confidence) that the program will still be useful when
the system is in a broken state.</p>
<p>Anecdotally, I recently ruined the installation of on my CentOS 7
host because I performed a system-wide
<code>pip install --upgrade ...</code></p>
<h2 id="static-systems-continued">Static Systems (Continued)</h2>
<p>While we're talking about static binaries, let's also consider <a
href="https://martinfowler.com/bliki/ImmutableServer.html">immutable
servers</a>. Building your environment so that you do not change things
in-place, but instead build entirely new infrastructure to support the
new deployment, while keeping the old one unchanged will sidestep a lot
of issues with updating/upgrading any part of the environment.</p>
<h2 id="document-your-research-and-decisions">Document your research and
decisions</h2>
<p>This is probably the most important pillar, because without it,
everything you do is subject to decay. By writing down your experiences,
decisions, difficulties and (especially) the business decisions
surrounding the final implementation, you not only present a consistent
and reliable narrative, but you have the ability to point out changes in
circumstance when promoting a new solution to an old/existing
problem.</p>
<p>When faced with a problem and you have potential solutions, narrate
the problem, present the options, and send that to the stakeholders. If
you "have no stakeholders," then <em>You and everyone who may possibly
come after you</em> are the stakeholders.</p>
<h2 id="who-is-this-guy">Who is this guy</h2>
<p>I'm a SysAdmin in an SRE/DevOps role. I've been in this kind of role
for my entire career (10+ years); since before "devops" was a term. As
such, I understand software engineering, and can communicate with
developers "on their level," but my bread-and-butter is managing systems
and infrastructure. I like writing code, and I love developing smart
systems. What makes me happiest is simplifying, teaching, and automating
myself out of work. I hope that these pillars give you a solid framework
to build your own successful career.</p>]]></description>
          <link>https://gluecode.net/blog/2017/03/04/code-less.html</link>
          <guid>https://gluecode.net/blog/2017/03/04/code-less.html</guid>
          <pubDate>Sat, 04 Mar 2017 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Redefining Stability</title>
          <description><![CDATA[<blockquote>
<p>Published 2016-08-31</p>
</blockquote>
<p>Defining stability is a hard problem due to many factors. I propose
the use of some guidelines and modifiers to describe various "stable"
systems to better describe their true state.</p>
<h2 id="common-points">Common Points</h2>
<ul>
<li>The software runs within its defined memory/cpu parameters</li>
<li>The software does not crash under "normal" load</li>
</ul>
<h2 id="understable">Understable</h2>
<ul>
<li>There is some risk in using the service. Not all edge cases for the
API have been tested/bugfixed</li>
<li>Deployments need monitoring by "that one person" who understands the
service deeply</li>
<li>Subject to large or frequent code / API updates</li>
<li>Using "new" or untested libraries, repeatedly updated (subject to
frequent deployments)</li>
</ul>
<h2 id="stable">Stable</h2>
<ul>
<li>Most edge-cases have been fixed for the API</li>
<li>Engineering teams maintain and update the code regularly (as needed
or monthly)</li>
<li>Relatively few updates to the business-logic code. The API is
versioned and/or tested for backwards compatibility</li>
<li>Libraries are updated and the app is refactored to keep pace with
security updates and Engineering progress outside the app (e.g. database
version changes)</li>
</ul>
<h2 id="overstable">Overstable</h2>
<ul>
<li>So "rock solid" that no one wants to touch it -- it has "hairs" on
it in the form of bugfixes and all edge-cases have been handled in
code.</li>
<li>Code is reviewed / updated only when absolutely necessary to prevent
catastrophic failures. Library and code updates are avoided.</li>
<li>Would be considered "abandonware" in other contexts</li>
<li>(Like <em>understable</em>) requires "that one person" who
understands the code to maintain it</li>
</ul>
<h2 id="conclusions-and-reasoning">Conclusions and Reasoning</h2>
<p>Both under- and over-stable systems are in a "bad" state and are
something we should avoid.</p>
<p>Having truly Stable code means regularly reviewing or refactoring
code to account for new Engineering requirements or practices, fixing or
adding pieces as needed, and keeping the team's understanding of the
code fresh.</p>]]></description>
          <link>https://gluecode.net/blog/2016/08/31/redefining-stability.html</link>
          <guid>https://gluecode.net/blog/2016/08/31/redefining-stability.html</guid>
          <pubDate>Wed, 31 Aug 2016 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Gitlab Follow-Up</title>
          <description><![CDATA[<blockquote>
<p>Published 2016-08-29</p>
</blockquote>
<p>The team which tested Gitlab for the duration of their sprint had
such good feedback during and after the sprint, word-of-mouth prompted
other teams to migrate ahead of schedule and start testing it for
themselves. This brought up a few new data points and some
lessons-learned.</p>
<h2 id="nodejs-and-alpine">NodeJS and Alpine</h2>
<p>Running NodeJS 4.4.7 and npm 3.10.5, somehow our particular
installations are incurring an approximately 70-second delay in <em>npm
install</em> times when running on Alpine 3.4 instead of Debian Jessie.
We still haven't narrowed down where this slowdown occurs yet, but my
feeling is we are "doing something wrong" as other NodeJS shops have
reported <a
href="http://odino.org/minimal-docker-run-your-nodejs-app-in-25mb-of-an-image/">significant</a>
<a
href="https://blog.risingstack.com/minimal-docker-containers-for-node-js/">gains</a>
when running on Alpine.</p>
<p>Also, Java and other languages work fine in Alpine. NodeJS is our
only difficulty right now.</p>
<h2 id="cache-server-needs-a-delete-button">Cache server needs a Delete
button</h2>
<p>While this has more to do with the <a
href="https://www.minio.io/">Minio project</a> than anything, we've had
some incidents where the cache zip archive was either corrupted and
"killed" the build or it grew so large (4GB+) that it doubled our build
time. Especially in the case of a corrupted zip, the build did
<em>not</em> continue without the cache files and fully stopped the
build with an error message.</p>
<p><strong>To the Gitlab CI folks:</strong> this is <em>not</em> how you
treat a cache!</p>
<p>The only fix we could come up with was to delete the cache zip and
perform a fresh build, but this was not easily accomplished by the
developers, especially since they weren't able to issue a delete command
either from Gitlab or on the Minio web interface. Instead, we had to go
in to the host itself and remove the cache file directly from the
disk.</p>
<h2 id="versioning-is-an-interesting-discussion">Versioning is an
interesting discussion</h2>
<p>For a very long time we have used <a
href="http://semver.org/">semantic versioning</a> at my company, and the
developers wanted a way to tag builds when successful, but the fact is
that runner's git checkouts are read-only, there are no arbitrary
parameters which can be passed to a build, and builds can happen in
various orders (with various build numbers/iterations) decided by the
runner based on available resources. And then there's the whole argument
about how to <em>enforce</em> SemVer changes, because people
inadvertently break the <code>major.minor.patch</code> protocols all the
time and there is no way to correct or confirm it in the code.</p>
<p>As such, we had to take a long look at how we versioned our software
and our artifacts. The first thing we noted is that we don't have a lot
of shared libraries which <em>require</em> semantic versioning. Instead,
we just need a way to determine a (fuzzy) magnitude of change and a
means of sorting newer vs older artifacts.</p>
<p>(We do this all the time in Golang!) So with a bit of discussion we
stumbled on a particularly easy and human-readable solution:</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="va">BUILD_VERSION</span><span class="op">=</span><span class="kw">`</span><span class="fu">git</span> log <span class="at">--oneline</span> <span class="kw">|</span> <span class="fu">wc</span> <span class="at">-l</span><span class="kw">|</span><span class="fu">tr</span> <span class="at">-d</span> <span class="st">&#39; &#39;</span><span class="kw">`</span>-<span class="kw">`</span><span class="fu">git</span> rev-parse HEAD<span class="kw">|</span><span class="fu">head</span> <span class="at">-c</span> 10<span class="kw">`</span></span></code></pre></div>
<p>This produces a version of the format
<code>&lt;commit count&gt;-&lt;unique hash&gt;</code> satisfying the
(again, fuzzy) magnitude of change, and providing an easy way of
determining newness (barring any incidents of force-push rebase
operations). Version <code>4123-2f1df15079</code> is going to be newer
than <code>4007-fc036c4ee0</code>, sorts properly when using most sorts,
and (due to the hash value) guarantees that if versions are produced
independently (say by two different developers) they will not have the
same version.</p>
<p>I have yet to get a lot of traction with this scheme, as the Cult of
SemVer is strong, but for offices where the product is not a code
library it doesn't make a lot of sense to use SemVer.</p>]]></description>
          <link>https://gluecode.net/blog/2016/08/29/gitlab-follow-up.html</link>
          <guid>https://gluecode.net/blog/2016/08/29/gitlab-follow-up.html</guid>
          <pubDate>Mon, 29 Aug 2016 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Gitlab and GitlabCI</title>
          <description><![CDATA[<blockquote>
<p>Published 2016-08-16</p>
</blockquote>
<p>My company currently uses Github and Jenkins to handle our source
code and build our artifacts/docker images, respectively. We've had to
adjust our build system over time as we grow, now running a total of
three build agents with 8 cores per agent, and ~20GB of RAM per
host.</p>
<p>As a result, we can have as many as 20 builds and/or tests running
simultaneously and there is very little "rhyme or reason" to our build
schedule save that they almost always happen between 9am and 5pm
PST.</p>
<p>Seeing that we were spending $$$$ to run these always-on build-agents
and store a <em>limited</em> number of repositories, I decided to look
at what it would take to create a dynamic build system.</p>
<h2 id="the-runners-up">The Runners Up</h2>
<ul>
<li>Jenkins plugins for docker, kubernetes, etc</li>
<li>Travis CI (and Cirlce CI, Drone.io, ...) and other
build-as-a-service platforms</li>
<li>TeamCity</li>
<li>Atlassian Bamboo</li>
</ul>
<p>Jenkins plugins for docker-based scaling including builds via
Kubernetes were all in their <em>alpha</em> state or otherwise
unsuitable for use with our environment. The only other alternatiave
with Jenkins would be for me to write my own plugin to do exactly what
we wanted for dynamic builder creation.</p>
<p>TravisCI and such are great for free/open-source systems, but for a
private company with our concurrent build requirements, they were a
no-go. Too costly with not enough concurrency for our day-to-day
operations.</p>
<p>Similarly, TeamCity and Atlassian Bamboo (the commercial offerings)
made all their money around additional build agents. This would
encourage us to keep our number of agents small, which is the same
situation we were in with Jenkins.</p>
<h2 id="gitlab">Gitlab</h2>
<p>Gitlab CE (Community Edition) came out ahead by having:</p>
<ul>
<li>Git hosting for unlimited private repos and users (saving us the
$450/month we were spending on Github private repositories)</li>
<li>A CI system where the configuration is stored in the repository
itself (giving Developers control and revision history)</li>
<li>GitlabCI's configuration is in standard YAML (rather than a weird
DSL like Jenkins)</li>
<li>GitlabCI allows for docker-machine operation, creating machines as
needed to accomplish its build objectives, then tearing them down (re:
deleting them from our cloud provider) when idle. Also all operations
happen within docker containers, meaning provisioning operations are not
required for the runners.</li>
</ul>
<p>Needless to say, I was impressed with it.</p>
<p>I had to patch docker-machine due to some non-standard configuration
issues we had in Google Compute Platform, but that only took a day to
figure out. Once I had the patched docker-machine installed on the
Gitlab host, I configured the runner to use docker-machine and launch a
system with privleged containers. After all that, we were in
business!</p>
<p>The configuration YAMLs for most of our services are less than 50
lines, so it didn't take long to get a few services migrated over.</p>
<h2 id="caveats-and-issues">Caveats and Issues</h2>
<p>Using git to commit results back into the repository is not possible
(by default) in a build. Runners have a read-only copy of the
repository, but Artifacts can be saved to the master (and are available
for download after the build). If you want to commit the results back in
to the git repo, you'll have to create a docker image with credentials
built in.</p>
<p>Since runners are created and destroyed regularly, docker images
should be optimized for size (i.e. use Alpine linux as a base instead of
Debian) and one should have a mirror if the docker registry isn't
on-network. Reducing the transfer requirements will greatly accelerate
builds.</p>
<h2 id="dogfooding-and-impressions">Dogfooding and Impressions</h2>
<p>One of the teams nearby is almost always willing to try the "new
stuff" first and give feedback / input, so I have them using Gitlab for
their current sprint. So far, they are happy with it and excited about
the automatic testing and build operations.</p>]]></description>
          <link>https://gluecode.net/blog/2016/08/16/gitlab-and-gitlabci.html</link>
          <guid>https://gluecode.net/blog/2016/08/16/gitlab-and-gitlabci.html</guid>
          <pubDate>Tue, 16 Aug 2016 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Tech Choices and Pitfalls for DevOps</title>
          <description><![CDATA[<blockquote>
<p>Published 2016-03-16</p>
</blockquote>
<p>DevOps encompasses ideas which fly in the face of the last few
decades of systems design. Similarly, adoption of cloud-based
architectures also disrupt the last few decades of intuition and "common
sense."</p>
<p>Below is a clarification of technologies, reasons for their adoption
and usage, why their avoidance is preferable. Interspersed are snippets
regarding pitfalls with respect to purely virtual (cloud) architectures
and their usage.</p>
<hr />
<h2 id="containers---docker">Containers - Docker</h2>
<p>Let's talk about <a href="https://www.docker.com/">docker</a>.
Actually, let's talk about <em>containerization</em>.</p>
<p>Containers are a godsend to engineering as a whole. With a container,
all code, library dependencies, and other "must be set up" requirements
are handled in a single, atomic, blob of data for deployment. The only
things that a container <em>can't</em> handle are dependent services
(e.g. external databases).</p>
<p>As a consequence, those containers are nearly everything one needs to
deploy new software. If docker (or a container runtime) is installed,
use the facilities provided to download the container and start it.
Suddenly, your Java/NodeJS/Go/Python/Rust/C++/Wordpress machination is
up-and-running without having to install anything.</p>
<p>Docker is the de-facto format and runtime for managing containers,
though there are new contenders for container creation and management,
like <a href="https://coreos.com/rkt/">rkt</a>. Considering rkt uses the
open-container-initiative's <a
href="https://www.opencontainers.org/about">specifications</a>, whereas
Docker has already started to diverge from that, it's a question about
whether rkt may be a better choice going forward.</p>
<p>If you aren't using containers of some type yet, you are missing
out.</p>
<blockquote>
<p>UPDATE -- 2016/04/17 -- Docker v1.11 uses the OCI contianer
specification.</p>
</blockquote>
<h2 id="configuration-management---ansible">Configuration Management -
Ansible</h2>
<p>There is a lot of discussion across the internet about <a
href="https://puppetlabs.com/">Puppet</a>, <a
href="https://www.chef.io/chef/">Chef</a>, and <a
href="http://saltstack.com/">SaltStack</a>, but the real winner is <a
href="https://www.ansible.com/">Ansible</a>. Ansible does everything
that the docker container <em>can't</em> handle, like basic systems
provisioning.</p>
<p>What makes Ansible the winner here? Puppet, Chef, and even Salt
require bootstrapping of some kind -- some kind of Agent installed on
minions that let them communicate with their Master server. Ansible has
none of that. Working purely over python &amp; ssh, Ansible's default is
to connect to a box using SSH and perform actions without leaving a
trace that it was there.</p>
<p>What about code changes? What about half-completed operations? Those
are handled with Assertions in an ansible playbook. When things aren't
the way your playbook expects them to be, Ansible will complain, LOUDLY,
and refuse to do it. No automatic attempts at resolving the issue or
starting things in the background (<em>unless you programmed it that
way</em>), just a not-so-quiet failure and the reasons for said
failure.</p>
<p>You might ask "what about audit trails and other security functions."
Since Ansible runs over SSH, whoever has Ansible access has SSH access,
meaning they are limited by what they could do if they were to remotely
connect to the machine and run the commands themselves. Thus Ansible
does not provide an <em>additional</em> route into a box, but automation
over an existing access method. To avoid granting all and sundry SSH
access, find ways to push data away from the box and feed it into things
like centralized logging platforms, provide web access to diagnostic
systems, and so on. And if you want an audit trail for the changes to
playbooks, follow <a
href="http://docs.ansible.com/ansible/playbooks_best_practices.html">the
best practices</a> and build your infrastructure immutably.</p>
<h2 id="scripting-and-development---go-and-others">Scripting and
Development - Go (and others)</h2>
<p>It really doesn't matter so much what language you choose, so long as
you understand said language. If, however, you're in a "green field"
situation or have the room to really dig in and adopt a new language,
give a shot. It's very much a "<a
href="http://blog.paralleluniverse.co/2014/05/01/modern-java/">blue-collar
language</a>" in the same vein as Java -- one can write themselves out
of a problem, without needing to be "clever." This is the explicit
design of the language:</p>
<blockquote>
<p>The key point here is our programmers are Googlers, they're not
researchers. They're typically, fairly young, fresh out of school,
probably learned Java, maybe learned C or C++, probably learned Python.
They're not capable of understanding a brilliant language but we want to
use them to build good software. So, the language that we give them has
to be easy for them to understand and easy to adopt. -- Rob Pike</p>
</blockquote>
<p>And...</p>
<blockquote>
<p>It must be familiar, roughly C-like. Programmers working at Google
are early in their careers and are most familiar with procedural
languages, particularly from the C family. The need to get programmers
productive quickly in a new language means that the language cannot be
too radical. -- Rob Pike</p>
</blockquote>
<p>Bash, python, perl, scala, java, clojure, ruby, or any other language
will do just fine. Go wins this contest by being built for productivity
and by permitting statically compiled executables (and cross-compilation
without a new toolchain) for great portability.</p>
<h2 id="cloud-provider---google-compute-platform-digital-ocean">Cloud
Provider - Google Compute Platform (Digital Ocean)</h2>
<p>Cloud providers are expensive compared to buying one's own hardware
for simple things, but the benefits of adaptability and no need to
negotiate colocation contracts. Based on the latest information I can
gather from the time of this post (March 2016), Google Compute Platform
provides approximately ~20% savings over other providers for
sufficiently large deployments. Depending on your use case, you can save
more (or none at all) depending on your needs.</p>
<p>Google Compute Platform is also undeniably Google, meaning you can be
forced into doing things The Google Way, but there is great simplicity
in its design and many provided services make for a compelling place to
put your startup.</p>
<p>For small, personal, or green-field projects, <a
href="https://www.digitalocean.com/">Digital Ocean</a> has lately been
the best-of-breed among providers and integrates nicely with many
provisioning systems (<a
href="http://docs.ansible.com/ansible/digital_ocean_module.html">like
Ansible</a>).</p>
<h2 id="changes-when-working-with-cloud-providers">Changes when working
with cloud providers</h2>
<p>One important change to note is that Engineers, when considering
things like logging or caching information, must recognize that the "X
is cheap" philosophies don't necessarily apply. As an example, yes, disk
is cheap, but SSD-scale attached storage costs over four times the
spindle-disk equivalent on Google Compute Platform. More so, both
storage types have a maximum throughput where adding more storage
<em>does not</em> grant ever more throughput or IOPS. So this is with
all other resources in any given cloud environment.</p>
<p>Similarly, one must build applications with an eye toward recovery
and adaptation. Network reliability is not 100% and is usually around a
<a href="http://uptime.is/98">98</a>-<a
href="http://uptime.is/99">99</a>% SLA. By definition, this means that a
network (or only part of it) could be unreachable for well over ten
minutes of cumulative time in a day. To combat this, one must do things
like permit retries when submitting information to an API, or include in
your code, and things like reconnecting to databases. Always <a
href="https://blog.gopheracademy.com/advent-2014/backoff/">try to
implement backoff code</a> to avoid the "thundering herd" problem when
those systems do reconnect.</p>
<p>Finally, if a system is built with all of these aspects in mind, use
(free!) <a href="https://letsencrypt.org/">encryption</a> and
authentication for all endpoints. Single-factor authentication
<em>might</em> be sufficient when communicating between servers, but for
user-level access, adopt 2FA if you mean to do anything remotely
sensitive. Yes, this includes something as simple as storing a user's
address.</p>
<h2 id="more-to-come">More to come</h2>
<p>I sincerely hope I haven't put anyone off with this, and I am anxious
to have more suggestions and feedback as I am exposed to more of the
"soft bits" toward being in DevOps. To mangle a friend's phrases,
"technology choices are a very <em>junior</em> concern. No one cares.
Focus on the business objectives, because that's all that really
matters."</p>]]></description>
          <link>https://gluecode.net/blog/2016/03/16/tech-choices-pitfalls-devops.html</link>
          <guid>https://gluecode.net/blog/2016/03/16/tech-choices-pitfalls-devops.html</guid>
          <pubDate>Wed, 16 Mar 2016 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Software has boundaries; respect them</title>
          <description><![CDATA[<blockquote>
<p>Published 2015-12-18</p>
</blockquote>
<p>This post should be classified as a rant, and it's been brewing for a
while.</p>
<p>All software has boundaries. When a system is built, the boundaries
are defined and the end user (often) cannot change them. This doesn't
mean we can't have dynamic software that does a great many flexible
things (especially with embedded scripting languages like Lua) but it
does mean that there are invariants in that program.</p>
<p>What I cannot understand is why a great many engineers look at a
system which they themselves didn't build and expect it to do exactly
what they want. "It should work <em>this</em> way" is often heard and
that statement is entirely unhelpful. Systems don't change just because
you want them to. Computers are dumb and do exactly what they are told
and those programs may not allow you any form of customization. That
said, if you didn't write the code for a given system, then you need to
either adapt yourself to the system or find something else.</p>
<p>End Users understand this. They know they are given a
box-within-a-box, full of constraints, and they will work day and night
to find ways to make that box sing. What makes it any different for
Engineers?</p>
<p>None of this is to say that You, the engineer / software developer /
DevOps person / IT guy, can't write your own version of the software
sitting in front of you. Until you do, <em>especially</em> if you're
dealing with a must-use system at work, take the software/platform/thing
for what it is and learn it. The more you fight it, the harder it is for
the rest of us, to no one's benefit.</p>]]></description>
          <link>https://gluecode.net/blog/2015/12/18/software-has-boundaries.html</link>
          <guid>https://gluecode.net/blog/2015/12/18/software-has-boundaries.html</guid>
          <pubDate>Fri, 18 Dec 2015 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Don't worry about NAT, just use firewalls</title>
          <description><![CDATA[<blockquote>
<p>Published 2015-12-14</p>
</blockquote>
<p>I hear again and again about how NAT is a "best practice" and should
be used as the base of network design. When working in the Google Cloud
(and Amazon EC2), publicly routeable IPv4 addresses are provided to each
machine "for free" within Google's IPv4 allocation block. When this is
first brought up, everyone reacts defensively and cautiously (good job,
security teams -- you finally got that message out). Once it is fully
explained that having an external IP address does not inherently reduce
the security of a machine, people tend to see it as a benefit.</p>
<p>Stack Overflow threads actually addressed my points much better than
I had myself, so here is one snippet and one full-length response
regarding NAT...</p>
<p>While <a
href="http://www.internetsociety.org/deploy360/blog/2015/01/ipv6-security-myth-3-no-ipv6-nat-means-less-security/">this
page</a> is addressing IPv6, the same information applies:</p>
<blockquote>
<p>We can argue the merits of NAT, the end-to-end principle, and
security until we're blue in the face -- and many have -- but the
reality is that NAT does not provide any real network security. Worse
yet, it actually prevents many security measures and provides an
additional attack surface for your network.</p>
</blockquote>
<p><a href="http://serverfault.com/a/184535">And this ServerFault
page</a> regarding IPv6 and "NAT being a thing of the past" actually
addresses a network with all IPv4 addresses publicly accessible:</p>
<blockquote>
<p>First and foremost, there is nothing to fear from being on a public
IP allocation, so long as your security devices are configured
right.</p>
<blockquote>
<p>What should I be replacing NAT with, if we don't have physically
separate networks?</p>
</blockquote>
<p>The same thing we've been physically separating them with since the
1980's, routers and firewalls. The one big security gain you get with
NAT is that it forces you into a default-deny configuration. In order to
get any service through it, you have to explicitly punch holes. The
fancier devices even allow you to apply IP-based ACLs to those holes,
just like a firewall. Probably because they have 'Firewall' on the box,
actually. A correctly configured firewall provides exactly the same
service as a NAT gateway. NAT gateways are frequently used because
they're easier to get into a secure config than most firewalls.</p>
<blockquote>
<p>I hear that IPv6 and IPSEC are supposed to make all this secure
somehow, but without physically separated networks that make these
devices invisible to the Internet, I really can't see how.</p>
</blockquote>
<p>This is a misconception. I work for a University that has a /16 IPv4
allocation, and the vast, vast majority of our IP address consumption is
on that public allocation. Certainly all of our end-user workstations
and printers. Our RFC1918 consumption is limited to network devices and
certain specific servers where such addresses are required. I would not
be surprised if you just shivered just now, because I certainly did when
I showed up on my first day and saw the post-it on my monitor with my IP
address. And yet, we survive. Why? Because we have an exterior firewall
configured for default-deny with limited ICMP throughput. Just because
140.160.123.45 is theoretically routeable, does not mean you can get
there from wherever you are on the public internet. This is what
firewalls were designed to do. Given the right router configs, and
different subnets in our allocation can be completely unreachable from
each other. You do can do this in router tables or firewalls. This is a
separate network and has satisfied our security auditors in the
past.</p>
<blockquote>
<p>There's no way in hell I'll put our billing database (With lots of
credit card information!) on the internet for everyone to see.</p>
</blockquote>
<p>Our billing database is on a public IPv4 address, and has been for
its entire existence, but we have proof you can't get there from here.
Just because an address is on the public v4 routeable list does not mean
it is guaranteed to be delivered. The two firewalls between the evils of
the Internet and the actual database ports filter out the evil. Even
from my desk, behind the first firewall, I can't get to that database.
Credit-card information is one special case. That's subject to the
PCI-DSS standards, and the standards state directly that servers that
contain such data have to be behind a NAT gateway[1]. Ours are, and
these three servers represent our total server usage of RFC1918
addresses. It doesn't add any security, just a layer of complexity, but
we need to get that checkbox checked for audits.</p>
<p>The original "IPv6 makes NAT a thing of the past" idea was put
forward before the Internet boom really hit full mainstream. In 1995 NAT
was a workaround for getting around a small IP allocation. In 2005 it
was enshrined in many Security Best Practices document, and at least one
major standard (PCI-DSS to be specific). The only concrete benefit NAT
gives is that an external entity performing recon on the network doesn't
know what the IP landscape looks like behind the NAT device (though
thanks to RFC1918 they have a good guess), and on NAT-free IPv4 (such as
my work) that isn't the case. It's a small step in defense-in-depth, not
a big one. The replacement for RFC1918 addresses are what are called
Unique Local Addresses. Like RFC1918, they don't route unless peers
specifically agree to let them route. Unlike RFC1918, they are
(probably) globally unique. IPv6 address translators that translate a
ULA to a Global IP do exist in the higher range perimeter gear,
definitely not in the SOHO gear yet. You can survive just fine with a
public IP address. Just keep in mind that 'public' does not guarantee
'reachable', and you'll be fine.</p>
<p>1: The PCI-DSS standards changed in October 2010, the statement
mandating RFC1918 addresses was removed, and 'network isolation'
replaced it.</p>
</blockquote>
<p>Key Points here: "The two firewalls between the evils of the Internet
and the actual database ports filter out the evil." Also "just keep in
mind that 'public' does not guarantee 'reachable' and you'll be
fine."</p>
<p>To reiterate the response, having a default-deny firewall (which the
major cloud-provider firewalls are) and a cautious approach to opening
ports or whitelisting traffic is sufficient to accomplish the same goals
as NAT, with the only exception of "getting around a small network
allocation."</p>
<p>If you have a small fixed number of IP addresses, NAT is your only
easy way of getting more addresses than you have. Otherwise, it's
unnecessary.</p>]]></description>
          <link>https://gluecode.net/blog/2015/12/14/no-nat-use-firewalls.html</link>
          <guid>https://gluecode.net/blog/2015/12/14/no-nat-use-firewalls.html</guid>
          <pubDate>Mon, 14 Dec 2015 01:00:00 +0000</pubDate>
         </item>
<item>
          <title>Reasons why VPN in the Google Cloud environment is superfluous</title>
          <description><![CDATA[<blockquote>
<p>Published 2015-12-10</p>
</blockquote>
<h2
id="vpns-main-functions-are-two-fold-encapsulation-and-encryption">VPN's
main functions are two-fold: encapsulation and encryption</h2>
<p>VPN encapsulates the intended transmission from one network to
another. Encryption is used to hide the data from any systems
in-flight.</p>
<p>Delivering data from one network to another has nothing to do with
addresses -- routers will push packets regardless. Encryption is usable
at all levels, can exist in multiple layers, and can be strengthened or
improved independently of the service itself (e.g. use <a
href="http://security.stackexchange.com/questions/50878/ecdsa-vs-ecdh-vs-ed25519-vs-curve25519">ed25519
keys</a> instead of RSA). VPN is an unnecessary addition to the security
model because the requisite pieces are already provided via firewalls
and encryption services like SSH and TLS.</p>
<h2 id="vpn-requires-turning-it-on-when-accessing-resources">VPN
requires "turning it on" when accessing resources</h2>
<p>VPN adds another "switch" to flip when performing "real work,"
especially when working from home or unfamiliar environments. Employees
can, and will, forget to enable the VPN when working, or will have
trouble establishing a VPN tunnel from a Starbucks when on-call. This
can and does increase troubleshooting load, including maintenance and
support load from IT, even when everything is functioning normally
because the VPN adds cognitive overhead to an Engineer's debugging
process. Additionally, during high-stress situations employees
(including Management) may circumvent the VPN for the sake of
expediency, which removes its protection.The VPN provides a single point
of failure -- accessing systems over the office-bound VPN failed
repeatedly while we tried to resolve networking issues. For several days
there was intermittent connectivity to internal addresses in GCP,
greatly hampering developer productivity.Using external IP address
access for Engineering operations means connections are always the same,
work successfully over the public internet, and can be verified, every
time. No one has to remember to turn on an External Interface in order
to access a service, they simply connect and are authenticated through.
When paired with multifactor authentication like and using hardware
tokens like the thoroughly-vetted-and-reviewed <a
href="https://www.yubico.com/products/yubikey-hardware/yubikey4/">Yubikey</a>,
systems are often more secure than when accessed using an authenticated
VPN connection.</p>
<h2 id="vpn-provides-a-false-sense-of-security">VPN provides a false
sense of security</h2>
<p>VPNs are usually evangelized as a reason for everyone to "rest easy"
knowing that they are operating within a perceived-controlled
environment. This cannot be further from the truth -- the networked
environment is just as vulnerable (if not more so) than one's own
computer. Even worse, should any one employee's laptop be compromised,
VPN access is at the attacker's control and once through the VPN they
have free reign among the unencrypted internal services.<br />
In contrast, using multiple layers of authorization and encryption (RE:
Duo Security and TLS/SSH) mean that the connections are always secured
and can be limited in their scope. Should one find that a given app or
host is misbehaving, they can change authentication credentials and
audit the offending part instead.</p>
<h2
id="specifying-multiple-layers-of-firewalls-is-more-restrictive-by-default-than-vpn">Specifying
multiple layers of firewalls is more restrictive by default than
VPN</h2>
<p>VPNs tend to be set up such that each endpoint provides access to the
rest of the network as a sort of "high ground." There are no real limits
or tests of traffic from the VPN into the rest of the network. Some VPNs
provide VLAN isolation, but these VPNs are meant to allow engineering
work to happen, and so often provide access directly to the most
desirable targets.Using multiple layers of firewalls instead means that
traffic is constantly checked, both on initial ingress and further
attempts to contact / connect with other instances. This means that a
person or system which isn't supposed to contact the database cannot
contact the database despite otherwise having access.</p>
<h2 id="responses">Responses</h2>
<h3 id="isnt-using-a-vpn-considered-a-best-practice">Isn't using a VPN
considered a best-practice</h3>
<p>It is, but only due to historical issues which have nothing to do
with current technology.The reasons for using VPNs are tied to
old-school business models (i.e. datacenter-to-datacenter and
office-to-office connectivity), old software which does not support
encryption, IPv4 limitations, and enshrined "Security Best
Practices":</p>
<ul>
<li>NAT helped reduce the issue of publicly routable IPv4 address
scarcity/cost, which meant leveraging more private networks (<a
href="https://en.wikipedia.org/wiki/Private_network">RFC1918</a>)</li>
<li>Offices would have a server-closet, or a datacenter location,
storing all of their data. That system would live in one centralized
location, prompting the question of how to access data from another
branch office without using the public internet.</li>
<li><a href="https://en.wikipedia.org/wiki/Timeline_of_file_sharing"
class="uri">https://en.wikipedia.org/wiki/Timeline_of_file_sharing</a></li>
<li>Encryption was either expensive, slow, or <a
href="https://en.wikipedia.org/wiki/Export_of_cryptography_from_the_United_States">expressly
forbidden from leaving the country</a>. This meant that being able to
account for secured transmissions between two points within the country
was extremely valuable. (This is a guarantee that public internet routes
<a
href="http://www.networkworld.com/article/2272520/lan-wan/six-worst-internet-routing-attacks.html">could
not necessarily enforce</a>.)</li>
<li>Multifactor authentication was seen as unnecessary for all but "Top
Secret Government stuff" and when software companies provided things
like "dongles" using their own authentication methods, they were
circumvented or exploited, or usable on a single type of system (no
interoperability).</li>
</ul>
<p>None of these issues apply today:</p>
<ul>
<li>IPv4 addresses are still scarce, but Google Compute Platform and
other cloud providers will provide public IPs to hosts for free. One is
not charged for their existence or use (unless an address is
specifically reserved and goes unused).</li>
<li>Modern systems use a variety of strong encryption methods to secure
transfers like TLS and SSH.</li>
<li><a
href="https://en.wikipedia.org/wiki/AES_instruction_set">https://en.wikipedia.org/wiki/AES_instruction_set</a></li>
<li>TLS provides strong encryption and can work over any TCP connection,
cheaply. Network transfer is also equally cheap.</li>
<li>Multifactor authentication is easily added to most systems and is
implemented using publicly tested implementations, interoperable with
any platform.</li>
</ul>
<p>For all of these reasons, we don't need VPN if we can encrypt and
authenticate our services. None of the technology is stopping us.</p>
<h3
id="google-provides-vpn-services-so-why-would-they-provide-it-if-it-isnt-needed">Google
provides VPN services, so why would they provide it if it isn't
needed</h3>
<p>Google works with many companies which have security requirements
that mandate the use of a VPN. (See reasons above.) If they did not
provide "feature parity" with those legacy systems, they wouldn't be
able to work with the larger companies that give them lots of money in
hosting costs. In short, it costs them almost nothing to offer it, and
they give their clients the ability to check a box in their requirements
documentation for security audits. <a
href="https://cloud.google.com/security/">Details here.</a></p>
<p>Google uses things like the <a
href="https://www.yubico.com/">Yubikey</a> and authenticating-gateways
to secure their networks since <a href="https://beyondcorp.com/">they
transitioned to a zero-trust design</a> after the detection of an <a
href="https://en.wikipedia.org/wiki/Operation_Aurora">APT</a> in 2009.
If one of the largest and most-exposed companies is using a zero-trust
model, one would assume it succeeds where a perimeter-defense design
failed.</p>
<h3
id="what-about-risk-of-exposure-what-about-the-additional-attack-surface">What
about risk of exposure? What about the additional "attack surface"</h3>
<p>Port 22 (SSH) is open to the world on all GCP instances, as SSH is
their only means of access. Botnets are constantly trying to access
those systems with common accounts (e.g. admin, cisco, etc) and yet none
of them get in. They are refused precisely because password-based logins
are not used (unless someone explicitly does so). In the past, using SSH
keys was believed considerably more effort than it was worth -- "just
use a password" -- until enough systems were attacked due to weak
passwords. During a recent port scan of an in-use production network,
the only ports publicly accessible to an outside IP address were the
following:</p>
<ul>
<li>port 22 -- for SSH.</li>
<li>port 80, 443 -- for 14 specifically identified webservers.</li>
</ul>
<p>Otherwise, the firewall denied anything not explicitly approved. The
added fact that the only hosts which responded to cleartext requests
were explicitly and specifically designed to do so should say something
about the security of the setup.</p>]]></description>
          <link>https://gluecode.net/blog/2015/12/10/why-vpn-is-superfluous.html</link>
          <guid>https://gluecode.net/blog/2015/12/10/why-vpn-is-superfluous.html</guid>
          <pubDate>Thu, 10 Dec 2015 01:00:00 +0000</pubDate>
         </item>
</channel> </rss>
