<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Devopsian</title><link>https://devopsian.net/</link><description>Recent content on Devopsian</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Tue, 04 Mar 2025 22:50:00 +0200</lastBuildDate><atom:link href="https://devopsian.net/index.xml" rel="self" type="application/rss+xml"/><item><title>Understanding Go’s Supercharged Map in v1.24</title><link>https://devopsian.net/p/understanding-gos-supercharged-map-in-v1.24/</link><pubDate>Tue, 04 Mar 2025 22:50:00 +0200</pubDate><guid>https://devopsian.net/p/understanding-gos-supercharged-map-in-v1.24/</guid><description>&lt;img src="https://devopsian.net/p/understanding-gos-supercharged-map-in-v1.24/cover.jpg" alt="Featured image of post Understanding Go’s Supercharged Map in v1.24" />&lt;p>Go 1.24 introduces a new map implementation, inspired by &lt;a class="link" href="https://abseil.io/about/design/swisstables" target="_blank" rel="noopener"
>Google&amp;rsquo;s Swiss Tables&lt;/a>, which brings significant optimizations and performance enhancements to the language&amp;rsquo;s built-in map type. While Go&amp;rsquo;s previous map implementation was already efficient, the new design takes it a step further by introducing a clever approach to data organization and access.&lt;/p>
&lt;p>To help digest this complex change, let&amp;rsquo;s use an analogy that illustrates how the new map works and how it differs from the previous implementation.
Let&amp;rsquo;s use a relatable analogy: a library. Just as a library organizes books in a way that makes them easy to find and access, a map organizes data for efficient retrieval.&lt;/p>
&lt;p>This analogy will provide a high-level understanding of the key improvements without delving too deeply into technical details. For those interested in a more in-depth exploration, we&amp;rsquo;ll reference additional resources throughout the explanation.&lt;/p>
&lt;hr>
&lt;h2 id="the-library-analogy">&lt;strong>The Library Analogy&lt;/strong>&lt;/h2>
&lt;p>Think of Go’s map as a library designed to store books. Here’s how it works:&lt;/p>
&lt;h3 id="1-tables-are-library-sections">&lt;strong>1. Tables Are Library Sections&lt;/strong>&lt;/h3>
&lt;p>The map starts with one &lt;strong>table&lt;/strong>, which is like a section of the library. If this section gets too crowded, the library adds another section. Each table is divided into smaller units called &lt;strong>groups&lt;/strong>.&lt;/p>
&lt;h3 id="2-groups-are-bookshelves">&lt;strong>2. Groups Are Bookshelves&lt;/strong>&lt;/h3>
&lt;p>Each table is made up of multiple &lt;strong>groups&lt;/strong>, which are like bookshelves in the library. A group can hold up to 8 books (key-value pairs). These groups are the fundamental storage units in Go maps.&lt;/p>
&lt;h3 id="3-control-word-the-librarians-cheat-sheet">&lt;strong>3. Control Word: The Librarian&amp;rsquo;s Cheat Sheet&lt;/strong>&lt;/h3>
&lt;p>Each bookshelf has a label that summarizes key information taped to it, called the &lt;strong>control word&lt;/strong>. This cheat sheet contains metadata about the books on that shelf:&lt;/p>
&lt;ul>
&lt;li>It stores tiny &amp;ldquo;fingerprints&amp;rdquo; of each book&amp;rsquo;s ID (derived from its hash).&lt;/li>
&lt;li>It marks whether slots on the shelf are empty, occupied, or deleted.&lt;/li>
&lt;/ul>
&lt;p>This cheat sheet helps librarians quickly locate books without flipping through every slot.&lt;/p>
&lt;p>It can be pictured as follows:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">+---------------------+ +---------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Map | | Library |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------------------+ +---------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Table 0 | | Section 1 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------------------+ +---------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Control Word (64b) | &amp;lt;----&amp;gt; | Shelf label |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------------------+ +---------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Key 0 | Value 0 | | Book 1 | Location 1 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Key 1 | Value 1 | | Book 2 | Location 2 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| ... | | ... |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Key 7 | Value 7 | | Book 8 | Location 8 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------------------+ +---------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Control Word (64b) | &amp;lt;----&amp;gt; | Shelf label |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------------------+ +---------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Key 0 | Value 0 | | Book 1 | Location 1 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Key 1 | Value 1 | | Book 2 | Location 2 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| ... | | ... |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Key 7 | Value 7 | | Book 8 | Location 8 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------------------+ +---------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| ... | | ... |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------------------+ +---------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| Table 1 | | Section 2 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---------------------+ +---------------------+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| ... | | ... |
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;hr>
&lt;h2 id="how-it-works-storing-and-retrieving-books">&lt;strong>How It Works: Storing and Retrieving Books&lt;/strong>&lt;/h2>
&lt;p>Let’s walk through an example of storing and retrieving a book in this library.&lt;/p>
&lt;h3 id="storing-a-book">&lt;strong>Storing a Book&lt;/strong>&lt;/h3>
&lt;p>Say you want to store &amp;ldquo;The Great Gatsby&amp;rdquo; by F. Scott Fitzgerald in the map.&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Generate a Hash&lt;/strong>
The librarian generates a unique ID for &lt;code>&amp;quot;The Great Gatsby&amp;quot;&lt;/code> using a hash function, e.g., &lt;code>0xf83c6f3a3c&lt;/code>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Find the Section and Bookshelf&lt;/strong>
The hash is split into two parts:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>H1&lt;/strong>: Determines which section (table) and bookshelf (group) the book belongs to. (57 bits)&lt;/li>
&lt;li>&lt;strong>H2&lt;/strong>: A small fingerprint stored in the control word for quick identification. (7 bits)&lt;/li>
&lt;/ul>
&lt;p>For example:&lt;/p>
&lt;ul>
&lt;li>H1 says: &amp;ldquo;Go to Section 1, Bookshelf 3.&amp;rdquo;&lt;/li>
&lt;li>H2 says: &amp;ldquo;Fingerprint is &lt;code>3c&lt;/code>.&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Place the Book&lt;/strong>
The librarian places &lt;code>&amp;quot;The Great Gatsby&amp;quot;&lt;/code> into an available slot on Bookshelf 3 and updates the control word with &lt;code>3c&lt;/code>.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h3 id="retrieving-a-book">&lt;strong>Retrieving a Book&lt;/strong>&lt;/h3>
&lt;p>Now you want to retrieve the book from the map.&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Find the Section and Bookshelf&lt;/strong>
The librarian uses H1 from the hash of &lt;code>&amp;quot;The Great Gatsby&amp;quot;&lt;/code> to go directly to Section 1, Bookshelf 3.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Check the Cheat Sheet (Control Word)&lt;/strong>
The librarian looks at the control word (&lt;code>3c&lt;/code>) to see if any slots match &lt;code>&amp;quot;The Great Gatsby&amp;quot;&lt;/code>&amp;rsquo;s fingerprint. If the fingerprint does not match, they know that slot doesn’t hold the desired book, saving time.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Confirm and Return&lt;/strong>
If there’s a match, they compare keys directly to confirm it’s &lt;code>&amp;quot;The Great Gatsby&amp;quot;&lt;/code>. Once confirmed, they return its value (&lt;code>10101..&lt;/code>).&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>This process avoids unnecessary checks and minimizes memory lookups, making retrieval lightning-fast.&lt;/p>
&lt;hr>
&lt;h2 id="handling-collisions">&lt;strong>Handling Collisions&lt;/strong>&lt;/h2>
&lt;p>What happens if multiple books generate the same H1 (i.e., they hash to the same group)? This scenario is known as a collision.&lt;/p>
&lt;p>In our library analogy:&lt;/p>
&lt;ul>
&lt;li>If a bookshelf is full, the librarian moves to the next available shelf in the same section.&lt;/li>
&lt;li>This is called &lt;strong>linear probing&lt;/strong>, where nearby groups are checked for free slots.&lt;/li>
&lt;/ul>
&lt;p>To ensure efficiency, if all shelves in a section are full, a new section (table) is added, and some books are redistributed between sections based on updated hash calculations to maintain optimal access.&lt;/p>
&lt;hr>
&lt;h2 id="why-this-design-is-brilliant">&lt;strong>Why This Design is Brilliant&lt;/strong>&lt;/h2>
&lt;p>The new map implementation in Go 1.24 introduces several optimizations inspired by Swiss Table design:&lt;/p>
&lt;h3 id="1-cache-friendly-layout">&lt;strong>1. Cache-Friendly Layout&lt;/strong>&lt;/h3>
&lt;p>Keys and values are stored together in groups, improving cache locality (storing related items close together takes advantage of how memory access works).
When looking for an item, both its key and value are likely loaded into memory at once.&lt;/p>
&lt;h3 id="2-fast-probing-with-metadata">&lt;strong>2. Fast Probing with Metadata&lt;/strong>&lt;/h3>
&lt;p>The control word allows fast rejection of irrelevant slots using SIMD (Single Instruction, Multiple Data allows processing multiple data points with a single instruction, thus boosting performance during lookups) operations.
This means multiple slots can be checked simultaneously, speeding up lookups significantly.&lt;/p>
&lt;p>While pre 1.24 use &lt;code>tophash&lt;/code> which reminds this strategy, it was still required to pointer chasing when overflow buckets were involved, reducing cache efficiency.&lt;/p>
&lt;hr>
&lt;h2 id="memory-layout-example">&lt;strong>Memory Layout Example&lt;/strong>&lt;/h2>
&lt;p>Here’s a simplified version of how this might look in memory for a single group:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Control Word&lt;/th>
&lt;th>Key0&lt;/th>
&lt;th>Value0&lt;/th>
&lt;th>Key1&lt;/th>
&lt;th>Value1&lt;/th>
&lt;th>&amp;hellip;&lt;/th>
&lt;th>Key7&lt;/th>
&lt;th>Value7&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>3c&lt;/code>&lt;/td>
&lt;td>&lt;code>&amp;quot;The Great Gatsby&amp;quot;&lt;/code>&lt;/td>
&lt;td>&lt;code>10101..&lt;/code>&lt;/td>
&lt;td>&amp;hellip;&lt;/td>
&lt;td>&amp;hellip;&lt;/td>
&lt;td>&amp;hellip;&lt;/td>
&lt;td>&amp;hellip;&lt;/td>
&lt;td>&amp;hellip;&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;ul>
&lt;li>The control word (&lt;code>3c&lt;/code>) stores fingerprints for all 8 slots. (We have only one element in the group)&lt;/li>
&lt;li>Keys (&lt;code>&amp;quot;The Great Gatsby&amp;quot;&lt;/code>) and values (&lt;code>10101..&lt;/code>) are stored adjacently within each group for better performance.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="performance-gains">&lt;strong>Performance Gains&lt;/strong>&lt;/h2>
&lt;p>The redesign brings significant improvements over older implementations:&lt;/p>
&lt;ul>
&lt;li>Faster lookups: Metadata allows skipping irrelevant slots quickly.&lt;/li>
&lt;li>Reduced memory overhead: Group storage eliminates extra pointers, and has better load-factor.&lt;/li>
&lt;li>Better scalability: Incremental resizing avoids performance bottlenecks during growth.&lt;/li>
&lt;/ul>
&lt;p>For example:&lt;/p>
&lt;ul>
&lt;li>With the optimizations, lookups are up to ~30% faster compared to previous versions.&lt;/li>
&lt;li>Memory usage is reduced by as much as ~28% compared to older versions of Go maps.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="conclusion">&lt;strong>Conclusion&lt;/strong>&lt;/h2>
&lt;p>Go 1.24’s map is like a librarian who gets smarter with every update—finding books faster, using space more efficiently where:&lt;/p>
&lt;ul>
&lt;li>Sections (tables) expand as needed.&lt;/li>
&lt;li>Bookshelves (groups) keep related items close together.&lt;/li>
&lt;li>Cheat sheets (control words) help librarians find books faster without flipping through every slot.&lt;/li>
&lt;/ul>
&lt;p>This design balances speed, memory efficiency, and scalability beautifully—making Go maps one of the most optimized hash table implementations out there!&lt;/p>
&lt;p>Whether you&amp;rsquo;re building high-performance systems or just curious about how things work under the hood, understanding these concepts can help you appreciate Go&amp;rsquo;s thoughtful engineering even more.&lt;/p>
&lt;p>For more detailed post about this implementation, check the official Go&amp;rsquo;s blog post &lt;a class="link" href="https://go.dev/blog/swisstable?utm_source=devopsian" target="_blank" rel="noopener"
>Faster Go maps with Swiss Tables&lt;/a>
or ByteSizeGo &lt;a class="link" href="https://www.bytesizego.com/blog/go-124-swiss-table-maps" target="_blank" rel="noopener"
>Swiss Table Maps&lt;/a>.&lt;/p>
&lt;p>&lt;strong>References&lt;/strong> to very good explanations of maps prior 1.24&lt;/p>
&lt;ul>
&lt;li>&lt;a class="link" href="https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics?utm_source=devopsian" target="_blank" rel="noopener"
>How the Go runtime implements maps efficiently&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://victoriametrics.com/blog/go-map/index.html?utm_source=devopsian" target="_blank" rel="noopener"
>Go Maps Explained: How Key-Value Pairs Are Actually Stored&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Cover image by &lt;a href="https://unsplash.com/@itfeelslikefilm?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Janko Ferlič&lt;/a> on &lt;a href="https://unsplash.com/photos/photo-of-library-with-turned-on-lights-sfL_QOnmy00?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash&lt;/a>&lt;/p></description></item><item><title>Handling Retries When Sending Files in Go: Lessons Learned</title><link>https://devopsian.net/p/handling-retries-when-sending-files-in-go-lessons-learned/</link><pubDate>Thu, 26 Dec 2024 22:00:00 +0200</pubDate><guid>https://devopsian.net/p/handling-retries-when-sending-files-in-go-lessons-learned/</guid><description>&lt;p>If you’ve ever implemented a file upload feature in Go, you might have run into a peculiar issue when retrying HTTP requests. I certainly did. This blog post is a deep dive into the problem I faced, why it happened, and what I learned about the io.Reader interface.&lt;/p>
&lt;h3 id="the-problem-retrying-sends-an-empty-file">The Problem: Retrying Sends an Empty File&lt;/h3>
&lt;p>In my Go application, one part of the functionality involves reading a file from disk and sending it to a remote server via an HTTP POST request. Here’s a simplified version of what my initial implementation looked like:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">file&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;path/to/file.txt&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">defer&lt;/span> &lt;span class="nx">file&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewRequest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;POST&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;https://example.com/upload&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">file&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">client&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Client&lt;/span>&lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">resp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">client&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Do&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">defer&lt;/span> &lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Body&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>My files are small, so loading the content onto memory is not an issue.&lt;/p>
&lt;p>All seemed fine until I added a retry mechanism to handle server failures. With retries, the client would attempt to resend the file if the server responded with an error or was unavailable. This is what the retry logic looked like:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">++&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">client&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Do&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusCode&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusOK&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Retrying... (%d/3)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>During testing, I simulated a server failure, and on the second attempt, the server received a 0-byte file. &lt;strong>What happened?&lt;/strong>&lt;/p>
&lt;h3 id="understanding-ioreader-and-why-this-happens">Understanding &lt;code>io.Reader&lt;/code> and Why This Happens&lt;/h3>
&lt;p>The root of the problem lies in how Go’s &lt;code>io.Reader&lt;/code> interface works. An &lt;code>io.Reader&lt;/code> provides a stream of data that can be read sequentially. When you pass a file to &lt;code>http.NewRequest&lt;/code> as the request body, the HTTP client reads from the file until it reaches the end. This process advances an internal pointer in the &lt;code>io.Reader&lt;/code>.&lt;/p>
&lt;p>Once the &lt;code>io.Reader&lt;/code> has been fully read, its internal pointer remains at the end of the stream. Subsequent reads will return no data, effectively making the &lt;code>io.Reader&lt;/code> empty unless explicitly reset. When the retry logic kicked in, the second attempt reused the same &lt;code>io.Reader&lt;/code> that had already been read. At this point, there was nothing left to read, so an empty file was sent.&lt;/p>
&lt;hr>
&lt;h3 id="the-solution-using-a-buffer-bytesbuffer">The Solution: Using a buffer (&lt;code>bytes.Buffer&lt;/code>)&lt;/h3>
&lt;p>To fix this, I needed a data structure that could be read multiple times, e.g.&lt;code>bytes.Buffer&lt;/code>. This is a memory-backed buffer that implements the &lt;code>io.Reader&lt;/code> interface, making it perfect for this scenario. Here’s how I updated the code:&lt;/p>
&lt;ol>
&lt;li>Read the file’s content into a &lt;code>bytes.Buffer&lt;/code>.&lt;/li>
&lt;li>Use the buffer as the request body.&lt;/li>
&lt;/ol>
&lt;p>Here’s the updated implementation:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">file&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/path/to/file.txt&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">defer&lt;/span> &lt;span class="nx">file&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Read file into a buffer first
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">var&lt;/span> &lt;span class="nx">buf&lt;/span> &lt;span class="nx">bytes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Buffer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">io&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Copy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">buf&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">file&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Create a request with the buffer instead of passing the original io.Reader
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">for&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">++&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewRequest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;POST&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;https://example.com/upload&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">buf&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">client&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Client&lt;/span>&lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">client&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Do&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusCode&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusOK&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;File uploaded successfully.&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Retrying... (%d/3)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>By reading the file into a &lt;code>bytes.Buffer&lt;/code>, I ensured that the content remained accessible for each retry. The &lt;code>io.Copy&lt;/code> function reads from the file and writes its contents to the buffer. Since &lt;code>bytes.Buffer&lt;/code> allows multiple reads, the retry mechanism works flawlessly.&lt;/p>
&lt;h3 id="exploring-alternatives-to-bytesbuffer">Exploring Alternatives to &lt;code>bytes.Buffer&lt;/code>&lt;/h3>
&lt;p>While &lt;code>bytes.Buffer&lt;/code> is a great choice for many cases, it may not be ideal for very large files due to memory constraints. Here are some alternatives:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>io.ReadSeeker&lt;/strong> If the file is large but can be stored on disk, using an &lt;code>io.ReadSeeker&lt;/code> allows resetting the read pointer to the beginning without needing to load the entire file into memory.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Chunked Uploads:&lt;/strong> For extremely large files, consider splitting the file into smaller chunks and uploading each chunk separately. This method reduces memory usage and improves reliability for large transfers.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>Each approach has trade-offs, so choose based on your specific use case.&lt;/p>
&lt;h3 id="memory-considerations">Memory Considerations&lt;/h3>
&lt;p>Using a memory-backed buffer like &lt;code>bytes.Buffer&lt;/code> works well for small to moderately sized files. Keep in mind that the entire file content will reside in memory. For large files, this can lead to significant memory consumption and potentially out-of-memory errors.&lt;/p>
&lt;p>If handling large files is a requirement, consider using disk-backed solutions like temporary files with &lt;code>os.CreateTemp&lt;/code>, which allow you to read the data sequentially from the disk without consuming much memory. Alternatively, implement streaming by breaking the file into manageable chunks and sending each chunk separately, ensuring memory efficiency even for very large files.&lt;/p>
&lt;h3 id="best-practices-for-retry-logic">Best Practices for Retry Logic&lt;/h3>
&lt;p>When implementing retry mechanisms, keep the following practices in mind:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Use Exponential Backoff:&lt;/strong> Avoid flooding the server with retries by adding a delay that increases after each failed attempt.&lt;/li>
&lt;li>&lt;strong>Set a Retry Limit:&lt;/strong> Always limit the number of retries to prevent infinite loops and resource exhaustion.&lt;/li>
&lt;li>&lt;strong>Log Errors:&lt;/strong> Keep track of what caused retries to occur, as this can help debug issues or provide insight into server-side problems.&lt;/li>
&lt;/ol>
&lt;p>Incorporating these strategies ensures that your retry logic is robust and doesn’t inadvertently worsen issues. For my use-case, I chose a simple strategy of a retry limit with a short sleep between intervals.&lt;/p>
&lt;h3 id="key-takeaways">Key Takeaways&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>Understand &lt;code>io.Reader&lt;/code>:&lt;/strong> Once an &lt;code>io.Reader&lt;/code> is read, it can’t be reused unless reset.&lt;/li>
&lt;li>&lt;strong>Use a reusable buffer:&lt;/strong> For retry mechanisms, use a structure like &lt;code>bytes.Buffer&lt;/code> or &lt;code>io.ReadSeeker&lt;/code> to ensure the data remains accessible.&lt;/li>
&lt;li>&lt;strong>Think about retries early:&lt;/strong> If retries are a possibility, design with this in mind from the start - and be sure to test it.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h3 id="wrapping-up">Wrapping Up&lt;/h3>
&lt;p>This was a great reminder of how important it is to understand the tools we use. Go’s &lt;code>io.Reader&lt;/code> is simple yet powerful, but it’s essential to know its limitations. By leveraging &lt;code>bytes.Buffer&lt;/code>, I was able to build a robust retry mechanism that handles file upload errors gracefully.&lt;/p>
&lt;p>Have you faced a similar issue in Go? Share your experiences or solutions in the comments below!&lt;/p></description></item><item><title>Why I Switched from Makefile to Taskfile</title><link>https://devopsian.net/p/why-i-switched-from-makefile-to-taskfile/</link><pubDate>Thu, 24 Oct 2024 22:00:00 +0300</pubDate><guid>https://devopsian.net/p/why-i-switched-from-makefile-to-taskfile/</guid><description>&lt;img src="https://devopsian.net/p/why-i-switched-from-makefile-to-taskfile/cover.jpg" alt="Featured image of post Why I Switched from Makefile to Taskfile" />&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>Software projects involve several phases, including building, testing, and deploying code.
For instance, compiling Go source code results in an executable, while frontend frameworks compile into HTML, CSS, and JavaScript files.
Testing is crucial before merging changes or releasing new versions. Deployment scripts often ship software to production.
Each phase requires different tools, typically command-line utilities with various flags and parameters.
Automation tools simplify these processes, enhancing efficiency in daily workflows.&lt;/p>
&lt;h2 id="makefile">Makefile&lt;/h2>
&lt;p>Makefiles are powerful tools that automate software project workflows. Initially developed for C programs, they now support diverse tasks like website generation and data processing.&lt;/p>
&lt;p>A Makefile contains directives for the &lt;code>make&lt;/code> utility to build or maintain programs and files. It defines tasks and their dependencies, ensuring efficient and reproducible builds.&lt;/p>
&lt;p>I won’t dive into Makefiles in this blog post as I’m assuming the reader is familiar with the concept. If not, there is plenty of information over the internet (like this &lt;a class="link" href="https://makefiletutorial.com/" target="_blank" rel="noopener"
>tutorial&lt;/a> for example or it’s &lt;a class="link" href="https://en.wikipedia.org/wiki/Make_%28software%29" target="_blank" rel="noopener"
>wikipedia page&lt;/a>).&lt;/p>
&lt;p>&lt;strong>Advantages of Makefile:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Integrated with the &lt;code>make&lt;/code> utility, available on most Linux/MacOS systems.&lt;/li>
&lt;li>A well-established tool with nearly 50 years of history.&lt;/li>
&lt;/ul>
&lt;p>These are the &lt;em>main&lt;/em> advantages I think Makefile have. However, Makefiles have limitations, particularly their syntax, which can be cumbersome for complex tasks.&lt;/p>
&lt;h2 id="why-i-switched">Why I Switched&lt;/h2>
&lt;p>In one of my projects, I used a Makefile for tasks like running frontend/backend services and database migrations. Here&amp;rsquo;s an example of a migration task:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-make" data-lang="make">&lt;span class="line">&lt;span class="cl">&lt;span class="nf">migrate-up&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">GOOSE_DRIVER&lt;/span>&lt;span class="o">=&lt;/span>postgres &lt;span class="nv">GOOSE_DBSTRING&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;user=app host=localhost port=5432 dbname=my-app sslmode=disable user=app&amp;#34;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> goose -dir database/migrations up
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>I wanted to load environment variables from a &lt;code>.env&lt;/code> file by default but allow overrides with &lt;code>ENV_FILE=.env.production&lt;/code>. After struggling with Makefile syntax and solutions that didn&amp;rsquo;t work, I sought alternatives.&lt;/p>
&lt;h2 id="introducing-taskfile">Introducing Taskfile&lt;/h2>
&lt;p>&lt;a class="link" href="https://taskfile.dev" target="_blank" rel="noopener"
>Taskfile&lt;/a> is a Go-based task runner using YAML syntax for defining tasks. It simplifies project workflows by automating repetitive tasks like building, testing, and deploying code.&lt;/p>
&lt;p>&lt;strong>Benefits of Taskfile:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Readable YAML Syntax:&lt;/strong> Easier to understand than Makefiles.&lt;/li>
&lt;li>&lt;strong>Single Binary:&lt;/strong> No dependencies beyond the Go runtime.&lt;/li>
&lt;li>&lt;strong>Cross-Platform Support:&lt;/strong> Works on Linux, macOS, and Windows.&lt;/li>
&lt;/ul>
&lt;p>Here&amp;rsquo;s how I solved my problem using Taskfile:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;3&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">dotenv&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s1">&amp;#39;.env&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">tasks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">migrate-up&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cmds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">goose -dir database/migrations up&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">migrate-up-prod&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">dotenv&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">.env.production&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cmds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">echo executing DB migration on PRODUCTION ..&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">sleep 2&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># allow time to cancel&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">goose -dir database/migrations up&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Taskfile&amp;rsquo;s intuitive API allowed me to quickly implement a solution that was both functional and readable.&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>Choosing the right tool can significantly impact productivity. While Makefile served its purpose initially, Taskfile offered a more elegant solution for my needs. Transitioning took less than 30 minutes and simplified my build process considerably.&lt;/p>
&lt;p>If you&amp;rsquo;re seeking an easy-to-use build tool, consider giving Taskfile a try.&lt;/p></description></item><item><title>The value of API-First design on side-projects</title><link>https://devopsian.net/p/the-value-of-api-first-design-on-side-projects/</link><pubDate>Fri, 12 Jul 2024 16:00:00 +0300</pubDate><guid>https://devopsian.net/p/the-value-of-api-first-design-on-side-projects/</guid><description>&lt;img src="https://devopsian.net/p/the-value-of-api-first-design-on-side-projects/cover.jpg" alt="Featured image of post The value of API-First design on side-projects" />&lt;h2 id="intro">Intro&lt;/h2>
&lt;p>Lately, I had a chance to try out the API-First design approach. I had never written an OpenAPI document before, so I had no real knowledge of its benefits. It always seemed like too much prep work.&lt;/p>
&lt;p>As developers, we often prefer writing code to writing documentation. We dive straight into coding, eager to see our project in action. However, I recently discovered a game-changing approach that has transformed my development process: API-First design. In this post, I&amp;rsquo;ll share my experience implementing this method in a full-stack hobby project, highlighting how it streamlined my workflow and why it&amp;rsquo;s worth considering for your next side project.&lt;/p>
&lt;blockquote>
&lt;p>tl;dr: &lt;strong>It will force you to think about your users and how they use your API before writing any code.&lt;/strong>&lt;/p>
&lt;/blockquote>
&lt;p>I’ve been working on a full-stack hobby project where my backend and frontend use different languages (Go and SvelteKit). I decided to give this approach a try and had my &lt;strong>“aha” moment&lt;/strong>. I wish I had done it before.&lt;/p>
&lt;h2 id="prioritizing-your-applications-foundation">Prioritizing Your Application&amp;rsquo;s Foundation&lt;/h2>
&lt;p>The API is how we are going to expose our app functionality. An API-first design approach prioritizes the development of APIs before implementing other parts of a software system (or writing code). This method focuses on creating a well-designed, consistent, and user-friendly API that is the foundation for the entire application.&lt;/p>
&lt;p>This methodology places the API at the center of the development process, treating it as a first-class citizen rather than an afterthought. Your API comes first, then the implementation.&lt;/p>
&lt;p>With a written API specification, we can leverage code generation tools to create some boilerplate code. By defining objects in the specification, code-gen tools can generate the relevant structs, for both the frontend and backend (yes, even when the language used is different). This is a big time saver and it helps us to be consistent.&lt;/p>
&lt;h2 id="what-is-open-api">What is Open API?&lt;/h2>
&lt;p>“&lt;em>&lt;strong>The OpenAPI Specification&lt;/strong>&lt;/em> &lt;em>(OAS) defines a standard, language-agnostic interface to RESTful APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection&lt;/em>.”&lt;/p>
&lt;p>Simply put, it’s a &lt;strong>contract&lt;/strong> that describes your API types and endpoints. You list all your API endpoints, their HTTP methods, what they possibly return, and some description of what they do.&lt;/p>
&lt;p>&lt;em>Now, what if I told you, you can use this document to improve and accelerate your dev experience?&lt;/em>&lt;/p>
&lt;p>Once I had this document, that describes the contract between my API server and its clients, these are the things I could do:&lt;/p>
&lt;ol>
&lt;li>Generate my backend types (Go)&lt;/li>
&lt;li>Generate my frontend types (Typescript)&lt;/li>
&lt;li>Generate a client code for my server (also Typescript)&lt;/li>
&lt;li>Generate a testing client with Insomnia or Postman&lt;/li>
&lt;/ol>
&lt;p>This is a lot of boilerplate code I could save myself from writing. It ensures the frontend and backend types are synchronized since both are generated.&lt;/p>
&lt;p>Grab a 🍺, and let’s walk through an example.&lt;/p>
&lt;h2 id="the-project-structure">The Project Structure&lt;/h2>
&lt;p>We will be using Go for the backend and some JS framework for the frontend, and a simple structure would look like:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">app/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├─ api/ -- the place &lt;span class="k">for&lt;/span> the Swagger OpenAPI document
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├─ client/ -- the client-side code
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├─ cmd/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├─ app.go -- thin main func that runs our API server
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├─ internal/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├─ api/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ ├─ main.go -- &lt;span class="k">for&lt;/span> code-gen
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├─ users/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ ├─ handlers.go -- implements the API contract
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="generate-the-apis">Generate The APIs&lt;/h2>
&lt;p>Let&amp;rsquo;s create our API specification. It includes two endpoints and two structs: User and Error.
Place this file under your &lt;code>/api&lt;/code> directory&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;span class="lnt">63
&lt;/span>&lt;span class="lnt">64
&lt;/span>&lt;span class="lnt">65
&lt;/span>&lt;span class="lnt">66
&lt;/span>&lt;span class="lnt">67
&lt;/span>&lt;span class="lnt">68
&lt;/span>&lt;span class="lnt">69
&lt;/span>&lt;span class="lnt">70
&lt;/span>&lt;span class="lnt">71
&lt;/span>&lt;span class="lnt">72
&lt;/span>&lt;span class="lnt">73
&lt;/span>&lt;span class="lnt">74
&lt;/span>&lt;span class="lnt">75
&lt;/span>&lt;span class="lnt">76
&lt;/span>&lt;span class="lnt">77
&lt;/span>&lt;span class="lnt">78
&lt;/span>&lt;span class="lnt">79
&lt;/span>&lt;span class="lnt">80
&lt;/span>&lt;span class="lnt">81
&lt;/span>&lt;span class="lnt">82
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">openapi&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">3.0.3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">info&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">title&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Devopsian OpenAPI Example&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.1.0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">contact&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dev&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">url&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">https://devopsian.net&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">servers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">url&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;http://localhost/v1&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">components&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">schemas&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">Error&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">object&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">required&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">code&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">message&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">properties&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">code&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">integer&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">format&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">int32&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">message&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">User&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">object&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">required&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">id&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">name&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">email&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">properties&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">id&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">email&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">format&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">email&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">paths&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">/user&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">get&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">description&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Get the current logged-in user&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">responses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">200&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">description&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">user response&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">content&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">application/json&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">schema&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">$ref&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;#/components/schemas/User&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">default&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">description&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">error&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">content&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">application/json&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">schema&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">$ref&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;#/components/schemas/Error&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">/signup&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">post&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">description&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Creates a new user&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">responses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">200&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">description&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Creates a user&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">content&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">application/json&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">schema&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">$ref&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;#/components/schemas/User&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">default&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">description&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">error&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">content&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">application/json&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">schema&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">$ref&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;#/components/schemas/Error&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">requestBody&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">required&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">content&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">application/json&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">schema&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">object&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">properties&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">email&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">format&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">email&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="generate-server-side-code">Generate Server-Side Code&lt;/h2>
&lt;p>To generate the server-side code, we need some library. I found &lt;a class="link" href="https://github.com/oapi-codegen/oapi-codegen" target="_blank" rel="noopener"
>oapi-codegen&lt;/a> for that. It supports many popular HTTP libraries (echo, gin, etc.) At the time of writing, I used &lt;code>oapi-codegen@v2.3.0&lt;/code>&lt;/p>
&lt;p>Add the following files to your &lt;code>/internal/api&lt;/code> directory&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// /internal/api/main.go
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//go:generate oapi-codegen --config cfg.yaml ../api/openapi3.yaml
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kn">package&lt;/span> &lt;span class="nx">api&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// make sure to install:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.3.0
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># /internal/api/cfg.yaml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">package&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">api&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">output&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">server.gen.go&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">generate&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">models&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">echo-server&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>I’m using the echo web framework, you can browse the library documentation to use other frameworks. Now run &lt;code>go generate ./...&lt;/code> and it will generate the interfaces (handlers) your web server has to implement to &lt;strong>fulfill&lt;/strong> this contract, including the &lt;strong>types&lt;/strong>.&lt;/p>
&lt;h3 id="interface-implementation">Interface Implementation&lt;/h3>
&lt;p>Now it’s time to write the implementation. We create a &lt;code>users&lt;/code> package where all the user&amp;rsquo;s API handlers, business logic, storage, etc. are defined. We will keep it simple and implement the handlers with static content.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">UsersHandler&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">DB&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">sql&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">DB&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">u&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">UsersHandler&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">GetUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">echo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">error&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// load the user from the database and return it to the caller
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nx">ctx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">JSON&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusOK&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">api&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">User&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Email&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Email&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;demo@devopsian.net&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;DemoUser&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Id&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">u&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">UsersHandler&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">PostSignup&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">echo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">error&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">body&lt;/span> &lt;span class="nx">api&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">PostSignupJSONBody&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">ctx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Bind&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">body&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">ctx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">JSON&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusBadRequest&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">api&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Error&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Code&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusBadRequest&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Message&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;invalid request&amp;#34;&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// save user in database
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">ctx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NoContent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusOK&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">New&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">db&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">sql&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">DB&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">UsersHandler&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">UsersHandler&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">DB&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Next, we need to create our web server entry point, we define that at &lt;code>cmd/server.go&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">Server&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">users&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">UsersHandler&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">e&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">echo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">New&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">s&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">Server&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">UsersHandler&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">users&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">New&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">nil&lt;/span>&lt;span class="p">)}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">api&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">RegisterHandlers&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">s&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Logger&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Start&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;:8080&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Note I explicitly pass in &lt;code>nil&lt;/code> as DB implementation for this example, because we don’t use it.&lt;/p>
&lt;p>That’s it. If the code compiles, &lt;em>our server implements the API contract.&lt;/em> All the API endpoints are handled by the spec. If I had missed something, &lt;strong>it would have broken at compile time.&lt;/strong>&lt;/p>
&lt;p>How nice is that?&lt;/p>
&lt;h2 id="generate-a-typescript-client">Generate a Typescript Client&lt;/h2>
&lt;p>It’s time to generate a client for our API. We use the same openapi schema file to generate a JS client. I won’t include a full frontend project in this example, but rather show how you can generate a client to an existing one.&lt;/p>
&lt;p>In the &lt;code>client/&lt;/code> directory, install the &lt;a class="link" href="https://www.npmjs.com/package/openapi-typescript-codegen" target="_blank" rel="noopener"
>code-generation&lt;/a> tool for JS:&lt;/p>
&lt;p>&lt;code>npm install openapi-typescript-codegen --save-dev&lt;/code>. (This post was tested with v0.29.0)&lt;/p>
&lt;p>Create a &lt;code>client/api/&lt;/code> directory, and let’s run the tool:&lt;/p>
&lt;p>&lt;code>npx openapi-typescript-codegen --input ../api/openapi3.yaml --output api/ --name ApiClient&lt;/code>.&lt;/p>
&lt;p>This will generate a bunch of typescript files. To use our client we need to create an instance of it.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// api.ts
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">ApiClient&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;./api/ApiClient&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">client&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">ApiClient&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="k">default&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// client has all the methods of our API:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// - getUser()
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// - postSignup(requestBody: {name?: string, email?: string})
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>That&amp;rsquo;s it.&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>API-First design isn&amp;rsquo;t just another development buzzword—it&amp;rsquo;s a powerful approach that can significantly enhance your side projects.&lt;/p>
&lt;p>By prioritizing your API design before implementation, you gain clarity, consistency, and efficiency.&lt;/p>
&lt;p>The OpenAPI specification is a &lt;strong>contract&lt;/strong> between your frontend and backend, &lt;strong>enabling automatic code generation&lt;/strong> for types, clients, and even testing tools.&lt;/p>
&lt;p>This approach not only saves time but also ensures better synchronization between different parts of your application.&lt;/p>
&lt;p>While it may seem like extra work upfront, the long-term benefits—including improved development speed, reduced errors, and better API documentation—make it a valuable investment for any side project.&lt;/p>
&lt;p>If you haven&amp;rsquo;t tried it yet, now might be the perfect time to give it a shot and experience these benefits firsthand.&lt;/p></description></item><item><title>Inside EKS Networking: Decoding the Service IP Journey</title><link>https://devopsian.net/p/inside-eks-networking-decoding-the-service-ip-journey/</link><pubDate>Sun, 17 Mar 2024 21:30:00 +0300</pubDate><guid>https://devopsian.net/p/inside-eks-networking-decoding-the-service-ip-journey/</guid><description>&lt;img src="https://devopsian.net/p/inside-eks-networking-decoding-the-service-ip-journey/cover.jpg" alt="Featured image of post Inside EKS Networking: Decoding the Service IP Journey" />&lt;h2 id="intro">Intro&lt;/h2>
&lt;p>Have you ever found yourself deep in the trenches of Kubernetes networking, only to be surprised by a hidden quirk that challenges your understanding? In this blog post, I unravel the mysteries behind Kubernetes networking in Amazon EKS, shedding light on the intricate journey of a packet from the client through the NLB to an ingress controller pod.&lt;/p>
&lt;p>This blog is a story about a change in perception. While I was debugging a problem the other day, it got me to question what I was certain I knew. When you’re sure you understand how Kubernetes works, you encounter another road bump that challenges your understanding and makes you do some research, this article is the output of this research.&lt;/p>
&lt;h2 id="the-setup">The Setup&lt;/h2>
&lt;p>The minimal setup required to examine what’s presented here. You would need an EKS cluster with NGINX ingress-controller deployed with NLB as the entry to your cluster.&lt;/p>
&lt;p>&lt;img src="https://devopsian.net/p/inside-eks-networking-decoding-the-service-ip-journey/setup.png"
width="678"
height="852"
srcset="https://devopsian.net/p/inside-eks-networking-decoding-the-service-ip-journey/setup_hu09e7f09dcdd4847400ca1beda7bbc616_63655_480x0_resize_box_3.png 480w, https://devopsian.net/p/inside-eks-networking-decoding-the-service-ip-journey/setup_hu09e7f09dcdd4847400ca1beda7bbc616_63655_1024x0_resize_box_3.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="79"
data-flex-basis="190px"
>&lt;/p>
&lt;h2 id="the-problem">The Problem&lt;/h2>
&lt;p>I was debugging a service with an ingress resource. I had incoming traffic from NLB to my ingress controller. I was surprised to see my ingress controller &lt;em>wasn’t listening on that port&lt;/em>. The AWS console shows those endpoints as ‘healthy’, meaning they respond to health check samples. But how is that even possible if there is &lt;em>no process listening on that port&lt;/em>?&lt;/p>
&lt;p>I gotta say this drove me nuts. I had looked online for similar issues, but nobody mentioned this problem. So I started doing some research, looking for an answer to the question &lt;em>“How does Kubernetes handle Service IP under the hood, on EKS?”&lt;/em>&lt;/p>
&lt;h2 id="exploring-kubernetes-networking-magic">Exploring Kubernetes Networking Magic&lt;/h2>
&lt;p>Before I present the network flow of a packet, here are some assumptions I take.
Kubernetes is a modular platform; this post is relevant for &lt;strong>EKS&lt;/strong> with the &lt;strong>VPC-CNI&lt;/strong> add-on running on v1.27.
It was checked with &lt;strong>NLB&lt;/strong>, other load-balancer types might behave differently.&lt;/p>
&lt;blockquote>
&lt;p>The next part is low-level. You should be familiar with &lt;strong>iptables&lt;/strong>. Two blog posts that cover this topic greatly are — &lt;a class="link" href="https://iximiuz.com/en/posts/laymans-iptables-101/" target="_blank" rel="noopener"
>Laymans iptables 101&lt;/a> and the &lt;a class="link" href="https://sudamtm.medium.com/iptables-a-comprehensive-guide-276b8604eff1" target="_blank" rel="noopener"
>iptables — a comprehensive guide&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>When a packet arrives, it first goes through the iptables &lt;em>&lt;strong>PREROUTING&lt;/strong>&lt;/em> chain. (That’s a builtin one):&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>root@...&lt;span class="o">]&lt;/span> &lt;span class="c1"># iptables -t nat -nvL PREROUTING&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Chain PREROUTING &lt;span class="o">(&lt;/span>policy ACCEPT &lt;span class="m">1987&lt;/span> packets, 119K bytes&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pkts bytes target prot opt in out &lt;span class="nb">source&lt;/span> destination
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 756M 5G KUBE-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This rule captures every incoming packet and forwards it to the “service portals&amp;quot;, the &lt;em>&lt;strong>KUBE-SERVICES,&lt;/strong>&lt;/em> (that’s a &lt;em>custom&lt;/em> chain) where it&amp;rsquo;s being matched and designated to the &lt;strong>relevant Kubernetes Service IP&lt;/strong> (nginx, in our case).&lt;/p>
&lt;p>Incoming packets on a matching port are &lt;em>DNAT&amp;rsquo;ed&lt;/em> to a Service IP. There’s a matching rule for our nginx instance. Incoming packets on its listening port &lt;strong>32443&lt;/strong> are sent to its Service IP:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">iptables -t nat -nvL KUBE-SERVICES &lt;span class="p">|&lt;/span> grep nginx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> KUBE-SVC-I66WCJWOLI45ORGK tcp -- * * 0.0.0.0/0 172.20.152.36 /* ingress-nginx/ingress-nginx-controller:https-32443 cluster IP */ tcp dpt:32443
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now that our packet has a destination Service IP in the cluster. But service objects in Kubernetes are just a layer of abstraction; there are no actual Pods with such IP. It’s &lt;em>synthetic&lt;/em>. Kubernetes networking layer needs to translate this IP to the relevant Pods. This process is done by transforming Service IP to &lt;em>Endpoints&lt;/em>. This is also done by iptables.&lt;/p>
&lt;p>We have a specific &lt;em>&lt;strong>KUBE-SVC-&lt;/strong>&lt;/em>* chain, which is constructed for every &lt;em>service&lt;/em> object we have. The purpose of this chain is to &lt;strong>translate Service IP to Endpoints (the actual pods behind it)&lt;/strong>. This chain has an entry for every pod alive. This is where the kernel performs &amp;rsquo;load balancing&amp;rsquo; between the pods.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">iptables -t nat -nvL KUBE-SVC-I66WCJWOLI45ORGK
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Chain KUBE-SVC-I66WCJWOLI45ORGK &lt;span class="o">(&lt;/span>&lt;span class="m">2&lt;/span> references&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pkts bytes target prot opt in out &lt;span class="nb">source&lt;/span> destination
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> KUBE-SEP-TQZ3GYVANOOYIJMO all -- * * 0.0.0.0/0 0.0.0.0/0 /* ingress-nginx/ingress-nginx-controller:https-32443 -&amp;gt; 10.1.21.61:443 */ statistic mode random probability 0.50000000000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> KUBE-SEP-NEOTPBRCKXF3UWGX all -- * * 0.0.0.0/0 0.0.0.0/0 /* ingress-nginx/ingress-nginx-controller:https-32443 -&amp;gt; 10.1.36.6:443 */
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>We’re not done with iptables just yet. As you can see from the output, there’s another &lt;em>chain&lt;/em> our packet has to go through. We’re getting close.
Each chain corresponds to a Pod, with a &lt;em>&lt;strong>KUBE-SEP-&lt;/strong>&lt;/em>* chain. If we check the rules of this chain:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">iptables -t nat -nvL KUBE-SEP-TQZ3GYVANOOYIJMO
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Chain KUBE-SEP-TQZ3GYVANOOYIJMO &lt;span class="o">(&lt;/span>&lt;span class="m">2&lt;/span> references&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pkts bytes target prot opt in out &lt;span class="nb">source&lt;/span> destination
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> KUBE-MARK-MASQ all -- * * 10.1.21.61 0.0.0.0/0 /* ingress-nginx/ingress-nginx-controller:https-32443 */
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">64&lt;/span> &lt;span class="m">3840&lt;/span> DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* ingress-nginx/ingress-nginx-controller:https-32443 */ tcp to:10.1.21.61:443
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The first rule is for outgoing packets (SNAT). Incoming traffic matches the second rule. This rule has a &lt;strong>DNAT&lt;/strong> target, which is &lt;em>Destination Network Address Translation&lt;/em>. This rule &lt;strong>rewrite&lt;/strong> the TCP packet destination IP to the Pod’s IP: 10.1.21.61:443
Now routing continues as normal, reaching the Pod on the relevant &lt;em>port&lt;/em>.&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>When a packet arrives, it first goes through iptables &lt;em>PREROUTING&lt;/em>, where it&amp;rsquo;s forwarded to the &lt;em>KUBE-SERVICES&lt;/em> chain. This chain directs packets to the relevant Kubernetes &lt;em>Service IP&lt;/em>. From there, iptables translates the Service IP to &lt;em>Endpoints&lt;/em> using the &lt;em>KUBE-SVC-&lt;/em> chains, ultimately reaching the correct pod.&lt;/p>
&lt;p>In conclusion, understanding Kubernetes networking in EKS requires delving into iptables manipulations and Kubernetes Service abstractions. I hope this post sheds light on these concepts.&lt;/p></description></item><item><title>How to Structure a Go Project: Start Simple, Refactor Later</title><link>https://devopsian.net/p/how-to-structure-a-go-project-start-simple-refactor-later/</link><pubDate>Mon, 05 Feb 2024 22:00:00 +0300</pubDate><guid>https://devopsian.net/p/how-to-structure-a-go-project-start-simple-refactor-later/</guid><description>&lt;img src="https://devopsian.net/p/how-to-structure-a-go-project-start-simple-refactor-later/cover.jpeg" alt="Featured image of post How to Structure a Go Project: Start Simple, Refactor Later" />&lt;p>Once upon a codebase, in a kingdom of endless debates, a lone developer pondered the age-old question: “What&amp;rsquo;s the perfect project structure?” Spoiler alert: there isn&amp;rsquo;t one. Let&amp;rsquo;s embark on a quest to discover the charm of simplicity and the art of Go project structuring.&lt;/p>
&lt;p>The perennial question about what directory structure should I use echoes across various social platforms now and then. This subject was discussed many times. I’ve been programming in Go for a couple of years now and asked myself this question every time I had to start a new project. If you’re asking it yourself, here are my 2 cents for you.&lt;/p>
&lt;h2 id="why-is-project-structure-important">Why is project structure important?&lt;/h2>
&lt;blockquote>
&lt;p>&amp;ldquo;A well-organized directory structure is the scaffolding upon which a robust codebase stands tall.&amp;rdquo;&lt;/p>
&lt;/blockquote>
&lt;p>A project structure is important because it affects &lt;em>readability &amp;amp; maintainability.&lt;/em> Think about your future self, six months from now reading your code when it’s not fresh in your memory. If you have structured it right (and there are many ways to do it) it would be easier to jump in. It would also be easier for &lt;em>collaboration&lt;/em> as other developers can navigate through it more easily. Most of the time we read and maintain existing code, not writing it from scratch.&lt;/p>
&lt;h2 id="what-are-the-common-pitfalls-of-project-structure">What are the common pitfalls of project structure?&lt;/h2>
&lt;p>There is no such thing as a perfect, one-size-fits-all project structure, so stop looking. It’s like asking for the perfect car. It depends on who you ask, and what are their standards and requirements. &lt;em>You won’t find a single answer&lt;/em>, because like many things in computer science — it depends. While there’s no one-size-fits-all solution, some general guidelines can be followed.&lt;/p>
&lt;p>Some of the common pitfalls:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Over-engineering:&lt;/strong> Trying to anticipate all the possible scenarios and use-cases before writing any code. This leads to unnecessary complexity and abstractions, which makes your code harder to read and maintain&lt;/li>
&lt;li>&lt;strong>Under-engineering:&lt;/strong> Writing all the code in a single package, without any structure or organization. This leads to spaghetti code base, which makes it harder to test and debug&lt;/li>
&lt;li>&lt;strong>Copy-pasta:&lt;/strong> Blindly following the structure of another project, without putting any thoughts or understanding the trade-offs behind it.&lt;/li>
&lt;/ul>
&lt;h2 id="how-to-start-simple-and-refactor-later">How to start simple and refactor later?&lt;/h2>
&lt;p>My advice is to start simple and let the packages grow organically as you’re writing the code. Don’t worry about creating the perfect packages and abstractions at this point. Just start by writing your code in a single package — main and see how it works. A simple directory tree is &lt;strong>easier&lt;/strong> to read, which reflects that it is easier to &lt;strong>maintain&lt;/strong> and collaborate with a team or individuals.&lt;/p>
&lt;p>As you write more code, you’ll notice some patterns emerge and repetitions that you can extract to a different package. This will make your code more readable, maintainable, and testable.&lt;/p>
&lt;p>I find it much harder to do the right abstraction before writing code. How do you know which packages do you need? Start writing your business logic. It is easier to refactor code &lt;em>when you see it in front of you.&lt;/em> It’s usually easier to abstract some logic from a package, rather than refactoring multiple packages because you got it wrong the first time.&lt;/p>
&lt;p>The key is to write code and refactor it later.&lt;/p>
&lt;h2 id="the-cmd-package-pattern">The cmd package pattern&lt;/h2>
&lt;blockquote>
&lt;p>A powerful pattern but make sure to ask yourself if you need it&lt;/p>
&lt;/blockquote>
&lt;p>This is a great pattern when &lt;em>necessary.&lt;/em> Do you write a library? Or an app with a single binary? Then you probably don’t need to use the &lt;code>cmd&lt;/code> package. Ask yourself what this abstraction allows you. Remember — keep it simple.
This pattern is useful when your code base is compiled to multiple binaries, for example, if you have a Server and a CLI.&lt;/p>
&lt;p>Inside this package, you put the sub-directories that contain your &lt;code>main.go&lt;/code> files. Each sub-directory is compiled into its own binary. This is the entry point of your app. You should look into &lt;a class="link" href="https://github.com/spf13/cobra" target="_blank" rel="noopener"
>spf13/cobra: A Commander for modern Go CLI interactions&lt;/a> project, which is a widely used library for creating powerful modern CLI applications. It blends well with &lt;a class="link" href="https://github.com/spf13/viper" target="_blank" rel="noopener"
>spf13/viper: Go configuration with fangs&lt;/a> project, which handles loading configurations nicely.&lt;/p>
&lt;h2 id="the-internal-package">The internal package&lt;/h2>
&lt;p>The &lt;code>internal&lt;/code> package has a special meaning in Go. Packages that reside under &lt;code>internal/&lt;/code> may not be imported by packages outside the source subtree in which they reside. Therefore, these are said to be &lt;em>internal packages.&lt;/em> The code placed here is internal to the project, and can’t be used outside of it.&lt;/p>
&lt;p>There used to be times people grouped external packages under &lt;code>pkg/&lt;/code>, but I find it meaningless. &lt;a class="link" href="https://dave.cheney.net/2019/10/06/use-internal-packages-to-reduce-your-public-api-surface" target="_blank" rel="noopener"
>A directory that exists only to hold other packages is a potential code smell.&lt;/a>
Instead, give your packages descriptive names. Try to describe &lt;strong>what&lt;/strong> they do, while &lt;a class="link" href="https://dave.cheney.net/2019/01/08/avoid-package-names-like-base-util-or-common" target="_blank" rel="noopener"
>avoiding ambiguous names like utils or common.&lt;/a>&lt;/p>
&lt;h2 id="the-config-package">The config package&lt;/h2>
&lt;p>The config package is responsible for creating and managing the configuration object that your app depends on. It simplifies the process of importing configurations from a single source and accessing them from anywhere in your code base. It also avoids the circular dependency problem that can arise from importing configurations from multiple places.&lt;/p>
&lt;p>Let the config package handle your app&amp;rsquo;s settings, harmonizing inputs from flags, environment variables, or files into a cohesive configuration object.&lt;/p>
&lt;h2 id="the-api-package">The API package&lt;/h2>
&lt;p>The API package defines the interface of your app with the outside world. It contains the schema definition (openapi) and the models (which can be generated from schema) that represent the data types and endpoints of your app. Unless a struct is internal to a specific package and is not used outside of it, it should be placed here. The API package should include any struct that is used by more than one package, or that is exposed to the client or the storage.&lt;/p>
&lt;p>The API package is the protocol that enables communication between different components of your app.&lt;/p>
&lt;h2 id="controllers-handlers-and-storage">Controllers, handlers, and storage&lt;/h2>
&lt;p>Now, let&amp;rsquo;s delve into structuring controllers, handlers, and storage. Two approaches stand out:&lt;/p>
&lt;h4 id="approach-1-storage-and-handlers-or-controllers-packages">Approach 1: Storage and Handlers (or controllers) packages&lt;/h4>
&lt;p>With the first approach, you write your storage and service/controller layers in separate packages where the service imports the storage. This allows you to decouple your business logic from your data access layer and use different storage implementations (postgres, sqlite, redis, etc.) without changing your service code. Here’s a simple view of this approach:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── project/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── api/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├── models.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └── openapi3.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── cmd/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├── server/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ └── main.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └── cli/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └── main.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── internal/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── config/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └── config.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── storage/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └── postgres/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├── users.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └── ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── handlers/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├── users.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├── users_test.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├── storage.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └── ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── server/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── server.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── server_test.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── ..
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="approach-2-domain-entity-package">Approach 2: Domain Entity Package&lt;/h4>
&lt;p>With this approach, you encapsulate &lt;em>logic by functionality&lt;/em> so that both your service and storage code reside in the same package. This follows the principle of &lt;em>domain-driven design&lt;/em>, where you model your code around the business domain and its entities. Each package represents a domain entity (such as &lt;em>user, post, comment,&lt;/em> etc.) and contains all the code related to it (such as internal structs, methods, handlers, queries, etc.)&lt;/p>
&lt;p>This is a topic for a separate blog post I might do later on, but in the meantime for more details about this type of architecture check out &lt;a class="link" href="https://threedots.tech/post/ddd-lite-in-go-introduction/" target="_blank" rel="noopener"
>introduction to DDD&lt;/a> or watch &lt;a class="link" href="https://www.youtube.com/watch?v=oL6JBUk6tj0" target="_blank" rel="noopener"
>Kat Zien great talk from GopherCon 2018.&lt;/a>. Here’s a simple view of this approach:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── project/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── api/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├── models.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └── openapi3.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── cmd/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├── server/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ └── main.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └── cli/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └── main.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── internal/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── config/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └── config.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── users/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├── postgres/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ └── db.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├── handlers.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├── handlers_test.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├── storage.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └── ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── server/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── server.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── server_test.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── ..
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>In conclusion, there is no single ‘perfect’ way to structure your Go project. There isn’t a one-size-fits-all. There are multiple ways, and it depends on your use-case, preferences, and the trade-offs you’re willing to take. Experiment based on your needs and requirements.&lt;/p>
&lt;p>Embrace simplicity initially. Write code in a single package, observe emerging patterns, and refactor as needed. The key here is to write code first, and ‘perfect’ structure later.&lt;/p>
&lt;p>I hope this post gave you some insights and ideas on how to structure your next project. Happy coding!&lt;/p></description></item><item><title>This is how you want to manage your Terraform modules</title><link>https://devopsian.net/p/this-is-how-you-want-to-manage-your-terraform-modules/</link><pubDate>Tue, 03 Jan 2023 13:00:00 +0200</pubDate><guid>https://devopsian.net/p/this-is-how-you-want-to-manage-your-terraform-modules/</guid><description>&lt;img src="https://devopsian.net/p/this-is-how-you-want-to-manage-your-terraform-modules/cover.jpg" alt="Featured image of post This is how you want to manage your Terraform modules" />&lt;p>What if I told you there is a way to manage all your private terraform modules, in a mono-repo,
with independent versioning, without using git tags? After researching for a proper open-source tool,
I found the right one for the job.&lt;/p>
&lt;h2 id="intro">Intro&lt;/h2>
&lt;p>&lt;em>A private registry is needed.&lt;/em>&lt;/p>
&lt;p>I looked for a way to manage private terraform modules like public ones.
In my company, we write tailor-maid modules that describe our infrastructure. These modules have to be private.
Terraform &lt;a class="link" href="https://developer.hashicorp.com/terraform/registry/modules/use#module-versions" target="_blank" rel="noopener"
>recommends&lt;/a> each module has its own git repository, yet, this has the burden of managing and syncing multiple repositories.&lt;/p>
&lt;p>The second option suggested is to use a mono-repo, and reference the module&amp;rsquo;s version using &lt;a class="link" href="https://developer.hashicorp.com/terraform/language/modules/sources#selecting-a-revision" target="_blank" rel="noopener"
>git tags&lt;/a>. At first, this seemed alright,
but there was a drawback — you had to give up the terraform version syntax. You no longer can use the convenient &lt;code>version = ~&amp;gt; 1.0.0&lt;/code> module parameter.&lt;/p>
&lt;p>It seemed to me that there had to be a better way.&lt;/p>
&lt;h2 id="the-problem">The problem&lt;/h2>
&lt;p>When it comes down to managing my infrastructure, I prefer the mono-repo approach. Since our terraform modules are small configuration blocks,
it makes sense. It is much easier to find a module in a single repository rather than searching multiple repositories for a module.
Our module release workflow looked something like this&lt;/p>
&lt;ul>
&lt;li>Our repository was structured with sub-directories per module&lt;/li>
&lt;li>We used Git tags for module versioning in the form of &lt;em>&lt;strong>moduleName-vX.Y.Z&lt;/strong>&lt;/em>&lt;/li>
&lt;li>Updates to module versions were &lt;em>hand-delivered&lt;/em> to clients (even minor patches)&lt;/li>
&lt;/ul>
&lt;p>Our root modules (where we execute terraform plan and apply) reference modules using git tags:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-v" data-lang="v">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">terraform&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="err">“&lt;/span>&lt;span class="nv">https&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="c1">//github.com/my-org/my-repo?ref=s3-v1.0.0&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">..&lt;/span> &lt;span class="kn">module&lt;/span> &lt;span class="nv">inputs&lt;/span> &lt;span class="o">..&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This has worked well for a while. But once we had released a new module version and wanted to use it, there was no convenient way to apply that.&lt;/p>
&lt;p>We had to go through our root modules using this module and update their references. (We could write a simple script, but we decided not to. More on that later)&lt;/p>
&lt;p>The problem we had with this approach is, you can’t use the &lt;code>version&lt;/code> argument in the terraform block.
This argument lets you specify a &lt;a class="link" href="https://developer.hashicorp.com/terraform/language/expressions/version-constraints" target="_blank" rel="noopener"
>range of acceptable versions&lt;/a> instead of a hard-coded one.&lt;/p>
&lt;p>For example, you can provide version constraints such as &lt;code>version = “~&amp;gt; 1.2.0, &amp;lt; 2.0”&lt;/code> which allows incrementing the “patch” automatically every time you run &lt;code>terraform init&lt;/code>.
No need to manually update a patch release, and no need to write a bash script. Terraform can manage that reliably for us,
with a better API, if we can only discover a way to use this feature.&lt;/p>
&lt;p>Manually managing our tags was &lt;strong>chaotic&lt;/strong>. It is not an easy task to manage independent module versions in a mono-repo using the Git tags approach.&lt;/p>
&lt;p>A simpler approach is to release a version for the whole repository. That means every new release includes all the modules together.
This couples the modules into a single artifact, which is easier to manage, but at the cost of development velocity.
You cannot release minor patches just for your module, no matter how small the code change is.&lt;/p>
&lt;h2 id="towards-a-better-future">Towards a better future&lt;/h2>
&lt;p>The structure and workflows we had in place worked; just as not as well or easily as we wanted to.
What we needed was to be able to use terraform versioning syntax, while maintaining our mono-repo.
To achieve that, we need to treat our terraform &lt;em>modules as artifacts&lt;/em>; something we can archive, version, and release independently.&lt;/p>
&lt;p>After doing some research, we found an open-source project called &lt;strong>&lt;a class="link" href="https://github.com/valentindeaconu/terralist" target="_blank" rel="noopener"
>Terralist.&lt;/a>&lt;/strong>&lt;/p>
&lt;p>Terralist is a private Terraform registry for providers and modules following the published HashiCorp protocols.
It provides a secure way to distribute your confidential modules and providers. That looked like a project that might help us to solve our problem, so we decided to try it out.&lt;/p>
&lt;p>So we maintain our mono-repo as it is. We created a job in our CI system that archives a single terraform module and uploads it to Terralist.
Since it&amp;rsquo;s a private registry, clients need to authenticate to be able to download the modules. After authentication (using terraform login &amp;lt;terralist-url&amp;gt;)
we could use our private modules just as we use the public modules. Yes, it means we can use the versioning syntax I was talking about.&lt;/p>
&lt;p>Here is how we use our modules nowadays&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-v" data-lang="v">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">terraform&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="err">“&lt;/span>&lt;span class="nv">https&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="c1">//my-terralist.com/my-org/s3/aws”
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nv">version&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="err">“&lt;/span>&lt;span class="o">~&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="mf">1.0.0&lt;/span>&lt;span class="err">”&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">..&lt;/span> &lt;span class="kn">module&lt;/span> &lt;span class="nv">inputs&lt;/span> &lt;span class="o">..&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This client code will retrieve automatic patch updates on every execution from our CI system (if you execute terraform locally, you would need to re-run &lt;code>terraform init&lt;/code>).
If we make larger code changes to one of our modules, for example, something that might break the API
we would increment the &lt;strong>major or minor&lt;/strong> version, so it wouldn&amp;rsquo;t impact our clients.&lt;/p>
&lt;h3 id="terralist">Terralist&lt;/h3>
&lt;p>&lt;em>Terralist&lt;/em> has an API that lets you upload a module from a git repository. It means you don&amp;rsquo;t need to archive the module yourself,
just point Terralist to its location.&lt;/p>
&lt;p>Terralist will clone the repository and create the artifact for you. Let&amp;rsquo;s walk through an example.&lt;/p>
&lt;p>I have a &lt;code>demo&lt;/code> module, which resides in my &lt;code>terraform.git&lt;/code> repository under the &lt;code>modules/demo&lt;/code> directory. In the module itself, we keep a &lt;code>version.tf&lt;/code>
with the &lt;strong>major and minor&lt;/strong> versions of the module. These values change only &lt;em>when we introduce a change that breaks our existing API&lt;/em>
(e.g, adding a new mandatory parameter without defaults). In such case we update the major or minor &lt;strong>manually&lt;/strong>.&lt;/p>
&lt;p>A &lt;strong>patch&lt;/strong> number in the semver represents a safe change, such as a bug fix or non-breaking changes to the module.
This value is &lt;em>calculated based on the build number&lt;/em> of our CI. We don&amp;rsquo;t really care about its value, because the version constraint
applied is in the form of &lt;code>version = &amp;quot;~&amp;gt; 1.0.0&amp;quot;&lt;/code>. This automatically updates to the latest patch on every execution.&lt;/p>
&lt;p>As part of our module build process, we &lt;em>read&lt;/em> the values of &lt;code>version.tf&lt;/code> and append the patch. Then we upload the module to Terralist
using this API call:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">curl -X POST registry.example.com/v1/api/modules/demo/aws/1.0.10/upload &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -H &lt;span class="s2">&amp;#34;Authorization: Bearer x-api-key:&lt;/span>&lt;span class="nv">$TERRALIST_API_KEY&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -d &lt;span class="s1">&amp;#39;{ &amp;#34;download_url&amp;#34;: &amp;#34;https://github.com/example-org/terraform/archive/refs/heads/master.zip//modules/demo&amp;#34; }&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The module version is composed of &lt;code>major.minor&lt;/code> coming from the &lt;code>version.tf&lt;/code> file, and the &lt;code>.patch&lt;/code> is the build ID, which is incremented with every run and guaranteed to be unique.&lt;/p>
&lt;p>Pay close attention to the &lt;strong>double //&lt;/strong> &amp;ndash; this instruct Terralist to:&lt;/p>
&lt;ol>
&lt;li>Download an archived repository&lt;/li>
&lt;li>Extract it locally&lt;/li>
&lt;li>Make an archive only from the path &lt;code>modules/demo&lt;/code>&lt;/li>
&lt;li>Upload it to the registry (basically upload it to S3 and update the registry database)&lt;/li>
&lt;/ol>
&lt;p>It&amp;rsquo;s an elegant solution: I tell Terralist where my module is, and what version it is tagged with, and it takes care for everything else.&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>There are various ways to manage your infrastructure code.&lt;/p>
&lt;p>It depends on multiple factors, such as team size, how much you are willing to spend on 3rd party tools, and your company policy, to name a few.
As of today, I&amp;rsquo;ve been using Terraform for more than 3 years, relying solely on the open-source ecosystem. That means my team and I manage everything related to our infrastructure (it might change soon, as our infrastructure size has grown).&lt;/p>
&lt;p>&lt;em>Terralist&lt;/em> was a helpful addition to our stack; it solved a problem we had in our existing workflow, with minimal effort and no code changes to our existing modules.
We just had to upload and version them to the new registry.
Now, we can release each module independently and decide if we want our clients to automatically upgrade their module version.&lt;/p>
&lt;p>&lt;strong>Happy Terraforming.&lt;/strong>&lt;/p>
&lt;p>For further reading, HashiCorp documentation contains a lot of good information. Check out the links below&lt;/p>
&lt;ul>
&lt;li>&lt;a class="link" href="https://developer.hashicorp.com/terraform/language/modules/syntax" target="_blank" rel="noopener"
>Terraform Module Blocks&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://developer.hashicorp.com/terraform/registry/modules" target="_blank" rel="noopener"
>Finding and using modules&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://developer.hashicorp.com/terraform/tutorials/modules" target="_blank" rel="noopener"
>Reuse configuration with modules (tutorial)&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Go templates: customize your output using templates</title><link>https://devopsian.net/p/go-templates-customize-your-output-using-templates/</link><pubDate>Sat, 14 May 2022 11:54:30 +0300</pubDate><guid>https://devopsian.net/p/go-templates-customize-your-output-using-templates/</guid><description>&lt;img src="https://devopsian.net/p/go-templates-customize-your-output-using-templates/cover.jpg" alt="Featured image of post Go templates: customize your output using templates" />&lt;p>Go templates are a powerful tool to customize output the way you want it. It’s a &lt;a class="link" href="https://pkg.go.dev/text/template" target="_blank" rel="noopener"
>builtin package&lt;/a> implements data-driven templates. Templates are executed by applying them to a data structure.&lt;/p>
&lt;p>While there are articles covering the basics, I had a hard time findings material on more advanced use-cases, such as looping over complex structs or using a function in the template. This post aims to distill these advanced use-cases with examples.&lt;/p>
&lt;blockquote>
&lt;p>If you’re unfamiliar with templates, this &lt;a class="link" href="https://blog.gopheracademy.com/advent-2017/using-go-templates/" target="_blank" rel="noopener"
>blog post&lt;/a> covers a great introduction to it.&lt;/p>
&lt;/blockquote>
&lt;p>This is our first use-case: I need to provide configuration to Thanos about the sidecars.
It’s basically a list of servers Thanos needs to communicate with. I have hundreds of servers.
If you’re not familiar with Thanos, that’s okay. It’s not important for this post. I use it just to show a real-world example.&lt;/p>
&lt;h2 id="the-template-and-data">The template and data&lt;/h2>
&lt;p>We start by creating a template, then the data it’s gonna execute with. Our output would be a YAML configuration file.&lt;/p>
&lt;p>Here is an example of how to &lt;strong>iterate a slice and use it&lt;/strong> in a template:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;os&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;text/template&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Locations&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">mydata&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Locations&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#34;NY&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;London&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Tokyo&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">tmplSrc&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">`---
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">- targets:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&lt;/span>&lt;span class="cp">{{-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">range&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="na">.Locations&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - &lt;/span>&lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="na">.&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&lt;/span>&lt;span class="cp">{{-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">end&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">tmpl&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">template&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Must&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">template&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">New&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;test&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">tmplSrc&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">tmpl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Execute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Stdout&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">mydata&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="nx">https&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="c1">//go.dev/play/p/hlZhU40wc0C
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="err">#&lt;/span> &lt;span class="nx">output&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">---&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="nx">targets&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="nx">NY&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="nx">London&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="nx">Tokyo&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Using the &lt;code>range&lt;/code> action we iterate an object. In my case, it&amp;rsquo;s a slice of strings. Inside the &lt;code>{{ range .. }}&lt;/code> block we refer to items
as &lt;code>.&lt;/code>&lt;/p>
&lt;h2 id="passing-data-to-the-template">Passing data to the template&lt;/h2>
&lt;p>In the previous example, we could simply pass in the slice to the template. But I used a &lt;em>struct&lt;/em>.
This allows me to &lt;em>extend&lt;/em> my template with minimal changes to the code.&lt;/p>
&lt;blockquote>
&lt;p>At the time of writing, the template engine supports &lt;strong>up to 1 argument&lt;/strong>. If you need to provide multiple values, &lt;em>use a struct.&lt;/em>&lt;/p>
&lt;p>Another (more complex) alternative is this &lt;a class="link" href="https://stackoverflow.com/a/18276968/840582" target="_blank" rel="noopener"
>answer on Stackoverflow&lt;/a>.&lt;/p>
&lt;/blockquote>
&lt;p>Let’s say instead of templating the location names, we want their respective IPs. This requires a few changes in our code.
We would need to update our &lt;em>data struct&lt;/em> to a map, of locations to IPs, and the template code too:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Locations&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">IPAddrs&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">mydata&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Locations&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#34;NY&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;London&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Tokyo&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">IPAddrs&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;NY&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;10.0.0.1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;London&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;20.0.0.1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Tokyo&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;30.0.0.1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">tmplSrc&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">`---
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">- targets:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&lt;/span>&lt;span class="cp">{{-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">range&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$location&lt;/span>&lt;span class="o">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$ip&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">:=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="na">.IPAddrs&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - &lt;/span>&lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$ip&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s"> # &lt;/span>&lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$location&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&lt;/span>&lt;span class="cp">{{-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">end&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="nx">output&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">---&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="nx">targets&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mf">20.0.0.1&lt;/span> &lt;span class="err">#&lt;/span> &lt;span class="nx">London&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mf">10.0.0.1&lt;/span> &lt;span class="err">#&lt;/span> &lt;span class="nx">NY&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mf">30.0.0.1&lt;/span> &lt;span class="err">#&lt;/span> &lt;span class="nx">Tokyo&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Here &lt;strong>I loop over a map and extract&lt;/strong> the relevant values. Cool.&lt;/p>
&lt;p>Important thing to note is, we define &lt;code>$location&lt;/code> and &lt;code>$ip&lt;/code> variables in the &lt;code>{{ range .. }}&lt;/code> statement.
&lt;em>They will be available in the template &lt;strong>only in the context of the range block&lt;/strong>.&lt;/em> You can’t use them outside this scope:&lt;/p>
&lt;blockquote>
&lt;p>A variable&amp;rsquo;s scope extends to the &amp;ldquo;end&amp;rdquo; action of the control structure (&amp;ldquo;if&amp;rdquo;, &amp;ldquo;with&amp;rdquo;, or &amp;ldquo;range&amp;rdquo;) in which it is declared,
or to the end of the template if there is no such control structure.&lt;/p>
&lt;p>A template invocation does not inherit variables from the point of its invocation.&lt;/p>
&lt;p>When execution begins, $ is set to the data argument passed to Execute, that is, to the starting value of dot.&lt;/p>
&lt;/blockquote>
&lt;h2 id="access-multiple-fields-of-a-data-structure">Access multiple fields of a data structure&lt;/h2>
&lt;p>Our requirements have changed. Now we don’t want to template all the locations of the &lt;code>IPAddrs&lt;/code> map, but only the selected ones (enabled).&lt;/p>
&lt;p>We need to modify our data. Our data struct contains all the locations (slice) and their relevant IP addresses (map).
A better name for our &lt;code>Locations&lt;/code> field would be &lt;code>EnabledLocations&lt;/code>. We change that accordingly.&lt;/p>
&lt;p>Now we want to template an IP address only if the location it resides is enabled.
We would need to:&lt;/p>
&lt;ul>
&lt;li>Iterate a slice (&lt;em>EnabledLocations&lt;/em>)&lt;/li>
&lt;li>Fetch relevant data from the map (&lt;em>IPAddrs&lt;/em>)&lt;/li>
&lt;li>Template the data in our template&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">EnabledLocations&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">IPAddrs&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">mydata&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">EnabledLocations&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#34;NY&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Tokyo&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">IPAddrs&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;NY&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;10.0.0.1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;London&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;20.0.0.1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Tokyo&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;30.0.0.1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">tmplSrc&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">`---
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">- targets:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&lt;/span>&lt;span class="cp">{{-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">range&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$location&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">:=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="na">.EnabledLocations&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&lt;/span>&lt;span class="cp">{{-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$ip&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">:=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">index&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="na">$.IPAddrs&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$location&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - &lt;/span>&lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$ip&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s"> # &lt;/span>&lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$location&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&lt;/span>&lt;span class="cp">{{-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">end&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="nx">https&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="c1">//go.dev/play/p/zOhRXGxoOMV
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="err">#&lt;/span> &lt;span class="nx">output&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">---&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="nx">targets&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mf">10.0.0.1&lt;/span> &lt;span class="err">#&lt;/span> &lt;span class="nx">NY&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mf">30.0.0.1&lt;/span> &lt;span class="err">#&lt;/span> &lt;span class="nx">Tokyo&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>I have done a few things here:&lt;/p>
&lt;ul>
&lt;li>Remove &lt;code>London&lt;/code> from the &lt;code>EnabledLocations&lt;/code> slice&lt;/li>
&lt;li>Iterate &lt;code>.EnabledLocations&lt;/code> slice with &lt;code>range&lt;/code> action and save current element to a variable &lt;code>$location&lt;/code>&lt;/li>
&lt;li>Define a new variable &lt;code>$ip&lt;/code> with the &lt;em>value&lt;/em> from a map with a &lt;em>key&lt;/em> &lt;code>$location&lt;/code>
&lt;ul>
&lt;li>Inside the &lt;code>{{ range .. }}&lt;/code> clause the scope is changed. &lt;code>.&lt;/code> cursor now reference the current item in the loop (in this example, it is equal to &lt;code>$location&lt;/code>)&lt;/li>
&lt;li>To access the outer scope, I use &lt;code>$.&lt;/code> — that way I can access the &lt;code>IPAddrs&lt;/code> map&lt;/li>
&lt;li>&lt;a class="link" href="https://pkg.go.dev/text/template#hdr-Functions" target="_blank" rel="noopener"
>index&lt;/a> function returns the result of indexing its first argument by the following arguments. In other words, the first argument is the key, and the latter is a map, slice, or array&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>Let’s complicate things a little more.
Let&amp;rsquo;s say our setup grew with more servers per location. We need our template to support multiple IPs per location.
This requires changes to our data structure and template once again.&lt;/p>
&lt;p>&lt;em>How can we use a dynamic key to fetch values from our map?&lt;/em>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">EnabledLocations&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">IPAddrs&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">][]&lt;/span>&lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">mydata&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">EnabledLocations&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#34;NY&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Tokyo&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">IPAddrs&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">][]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;NY&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#34;10.0.0.1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;10.0.0.2&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;London&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#34;20.0.0.1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;20.0.0.2&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Tokyo&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#34;30.0.0.1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;30.0.0.2&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">tmplSrc&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">`---
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">- targets:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&lt;/span>&lt;span class="cp">{{-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">range&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$location&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">:=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="na">.EnabledLocations&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&lt;/span>&lt;span class="cp">{{-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$ipList&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">:=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">index&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="na">$.IPAddrs&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$location&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &lt;/span>&lt;span class="cp">{{-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">range&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$ip&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">:=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$ipList&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - &lt;/span>&lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$ip&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s"> # &lt;/span>&lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$location&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &lt;/span>&lt;span class="cp">{{-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">end&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&lt;/span>&lt;span class="cp">{{-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">end&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="nx">output&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">---&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="nx">targets&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mf">10.0.0.1&lt;/span> &lt;span class="err">#&lt;/span> &lt;span class="nx">NY&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mf">10.0.0.2&lt;/span> &lt;span class="err">#&lt;/span> &lt;span class="nx">NY&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mf">30.0.0.1&lt;/span> &lt;span class="err">#&lt;/span> &lt;span class="nx">Tokyo&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mf">30.0.0.2&lt;/span> &lt;span class="err">#&lt;/span> &lt;span class="nx">Tokyo&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Here is what changed:&lt;/p>
&lt;ul>
&lt;li>&lt;code>IPAddrs&lt;/code> is a map of string to slice of strings&lt;/li>
&lt;li>Define the &lt;code>$ipList&lt;/code> variable which contains the relevant string slice (using the &lt;code>index&lt;/code> function)&lt;/li>
&lt;li>I got 2 range loops now: one loop the locations, the other loop each location’s IP list&lt;/li>
&lt;/ul>
&lt;p>You can also &lt;em>call methods inside a template&lt;/em>, here’s a clear &lt;a class="link" href="https://stackoverflow.com/a/10200273/840582" target="_blank" rel="noopener"
>example from StackOverflow&lt;/a> how to do it.&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>There are multiple ways to template data. Go provides a rich builtin library worth exploring.
Make sure the read the docs before banging your head over syntax, or other builtin capabilities such as &lt;code>index&lt;/code>, &lt;code>range&lt;/code>, and many more I haven&amp;rsquo;t covered in this post.
Actually, the library is much richer than what I present here. Here are the takeaways:&lt;/p>
&lt;ul>
&lt;li>&lt;code>range&lt;/code> action is used to iterate data structures&lt;/li>
&lt;li>&lt;code>index&lt;/code> function is used to extract data from a map, slice, or array&lt;/li>
&lt;li>&lt;code>$var :=&lt;/code> is used to define a variable and use it inside a template&lt;/li>
&lt;li>functions can be executed inside templates&lt;/li>
&lt;/ul></description></item><item><title>Archives</title><link>https://devopsian.net/archives/</link><pubDate>Sun, 06 Mar 2022 00:00:00 +0000</pubDate><guid>https://devopsian.net/archives/</guid><description/></item><item><title>A deep dive to Canary Deployments with Flagger, NGINX and Linkerd on Kubernetes</title><link>https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/</link><pubDate>Sat, 05 Mar 2022 21:45:00 +0200</pubDate><guid>https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/</guid><description>&lt;img src="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/cover.jpg" alt="Featured image of post A deep dive to Canary Deployments with Flagger, NGINX and Linkerd on Kubernetes" />&lt;h2 id="intro">Intro&lt;/h2>
&lt;p>Lately, I’ve been checking on progressive delivery tools. The two stars are &lt;a class="link" href="https://argoproj.github.io/argo-rollouts/" target="_blank" rel="noopener"
>Argo Rollouts&lt;/a> and &lt;a class="link" href="https://docs.flagger.app/" target="_blank" rel="noopener"
>Flagger&lt;/a>. Both projects are pretty mature and widely used.&lt;/p>
&lt;p>After researching the two for a few hours, I found out that — like most things in Kubernetes — there is more than one way of doing it.&lt;/p>
&lt;p>You can enable it with an ingress controller. Or a ServiceMesh. Or both.&lt;/p>
&lt;p>As of the time of writing this blog post, I found all the online tutorials were missing some &lt;strong>crucial&lt;/strong> pieces of information. I’m gonna save you a lot of time here, so bear with me.&lt;/p>
&lt;h2 id="why-flagger">Why Flagger&lt;/h2>
&lt;p>Flagger is a progressive delivery tool that automates the release process for apps on Kubernetes. It can gradually shift traffic to the new version while measuring metrics and running conformance tests.&lt;/p>
&lt;p>I prefer &lt;em>flagger&lt;/em> because of two main points:&lt;/p>
&lt;ul>
&lt;li>It integrates natively: it watches &lt;em>Deployment&lt;/em> resources, while Argo uses its own CRD &lt;em>Rollout&lt;/em>&lt;/li>
&lt;li>It is highly extendible and comes with batteries included: it provides a load-tester to run basic, or complex scenarios&lt;/li>
&lt;/ul>
&lt;p>When you create a deployment, Flagger &lt;em>generates duplicate&lt;/em> resources of your app (including configmaps and secrets). It creates Kubernetes objects with &lt;code>&amp;lt;targetRef.name&amp;gt;-primary&lt;/code> and a &lt;em>service&lt;/em> endpoint to the primary deployment.&lt;/p>
&lt;p>Flagger is &lt;strong>triggered by&lt;/strong> changes to the target &lt;strong>deployment&lt;/strong> (including secrets and configmaps) and performs a &lt;em>canary&lt;/em> rollout and analysis before promoting the new version as the primary.&lt;/p>
&lt;p>Read &lt;a class="link" href="https://docs.flagger.app/usage/how-it-works" target="_blank" rel="noopener"
>“How Flagger works”&lt;/a> to better understand this flow.&lt;/p>
&lt;p>There are multiple techniques of &lt;em>Progressive Delivery&lt;/em>:&lt;/p>
&lt;ul>
&lt;li>&lt;a class="link" href="https://martinfowler.com/bliki/CanaryRelease.html" target="_blank" rel="noopener"
>Canary&lt;/a> - is a deployment strategy introducing a new software version in production by slowly rolling out the change to a small subset of users before rolling it out to the entire infrastructure and making it available to all&lt;/li>
&lt;li>&lt;a class="link" href="https://docs.openshift.com/container-platform/4.7/applications/deployments/route-based-deployment-strategies.html#deployments-ab-testing_route-based-deployment-strategies" target="_blank" rel="noopener"
>A/B testing&lt;/a> - is a deployment strategy that lets you try a new version of the application in a limited way in the production environment&lt;/li>
&lt;li>&lt;a class="link" href="https://harness.io/blog/blue-green-canary-deployment-strategies/" target="_blank" rel="noopener"
>Blue/Green&lt;/a> - is a deployment strategy that utilizes two identical environments, a “blue” (aka staging) and a “green” (aka production) environment with different versions of an application or service. The new version can be tested, approved, then supersede the stable (green) production environment&lt;/li>
&lt;/ul>
&lt;p>In this blog post, I focus on &lt;em>Canary&lt;/em>. Canary covers simple and sophisticated use-cases. I will dive into how this actually works, and fill in the missing pieces I had to solve myself.&lt;/p>
&lt;h2 id="prerequisites">Prerequisites&lt;/h2>
&lt;p>If you wanna try this out, you’ll need:&lt;/p>
&lt;ul>
&lt;li>A Kubernetes cluster &lt;strong>v1.19+&lt;/strong> and NGINX &lt;strong>v1.0.2+&lt;/strong> (I use aws EKS here)&lt;/li>
&lt;li>Linkerd &lt;strong>v2.10+&lt;/strong> (optional, only if you go for the service mesh solution)&lt;/li>
&lt;/ul>
&lt;h2 id="nginx-ingress-canary-deployment">NGINX (Ingress) Canary Deployment&lt;/h2>
&lt;p>NGINX provides Canary deployment using &lt;em>annotations.&lt;/em> With the proper configuration, you can control and increment the number of requests to a different service than the production one.&lt;/p>
&lt;h3 id="deploy-nginx">Deploy nginx&lt;/h3>
&lt;p>Deploy NGINX ingress controller if you don’t have one already. It has to be monitored by Promethues, hence the podAnnotations:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl create ns ingress-nginx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">helm upgrade -i ingress-nginx ingress-nginx/ingress-nginx &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--namespace ingress-nginx &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--set controller.metrics.enabled&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--set controller.podAnnotations.&lt;span class="s2">&amp;#34;prometheus\.io/scrape&amp;#34;&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--set controller.podAnnotations.&lt;span class="s2">&amp;#34;prometheus\.io/port&amp;#34;&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">10254&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Install Flagger and set it with &lt;em>nginx&lt;/em> provider. Flagger can bring Prometheus with it, if you don’t have one installed:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Install w/ Prometheus to collect metrics from the ingress controller&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">helm upgrade -i flagger flagger/flagger &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--namespace ingress-nginx &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--set crd.create&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--set prometheus.install&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--set &lt;span class="nv">meshProvider&lt;/span>&lt;span class="o">=&lt;/span>nginx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Or point Flagger to an existing Prometheus instance&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">helm upgrade -i flagger flagger/flagger &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--namespace ingress-nginx &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--set crd.create&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--set &lt;span class="nv">metricsServer&lt;/span>&lt;span class="o">=&lt;/span>http://prometheus.monitoring:9090 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--set &lt;span class="nv">meshProvider&lt;/span>&lt;span class="o">=&lt;/span>nginx
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;blockquote>
&lt;p>&lt;strong>Gotcha&lt;/strong>: If you are using an existing Prometheus instance, and it is running in a different namespace,
&lt;em>you can&amp;rsquo;t use the prebuilt metrics&lt;/em>. You&amp;rsquo;ll encounter &amp;ldquo;no values found for nginx metric request-success-rate&amp;rdquo; issue.
You need to create your own template, check this &lt;a class="link" href="https://github.com/fluxcd/flagger/issues/421" target="_blank" rel="noopener"
>issue&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>Create a test namespace and install load testing tool to generate traffic during &lt;em>canary&lt;/em> analysis:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl create ns &lt;span class="nb">test&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Install the load testing tool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl apply -k https://github.com/fluxcd/flagger//kustomize/tester?ref&lt;span class="o">=&lt;/span>main
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Deploy our example app — &lt;em>podinfo.&lt;/em> Create an &lt;em>ingress&lt;/em> resource too:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;span class="lnt">63
&lt;/span>&lt;span class="lnt">64
&lt;/span>&lt;span class="lnt">65
&lt;/span>&lt;span class="lnt">66
&lt;/span>&lt;span class="lnt">67
&lt;/span>&lt;span class="lnt">68
&lt;/span>&lt;span class="lnt">69
&lt;/span>&lt;span class="lnt">70
&lt;/span>&lt;span class="lnt">71
&lt;/span>&lt;span class="lnt">72
&lt;/span>&lt;span class="lnt">73
&lt;/span>&lt;span class="lnt">74
&lt;/span>&lt;span class="lnt">75
&lt;/span>&lt;span class="lnt">76
&lt;/span>&lt;span class="lnt">77
&lt;/span>&lt;span class="lnt">78
&lt;/span>&lt;span class="lnt">79
&lt;/span>&lt;span class="lnt">80
&lt;/span>&lt;span class="lnt">81
&lt;/span>&lt;span class="lnt">82
&lt;/span>&lt;span class="lnt">83
&lt;/span>&lt;span class="lnt">84
&lt;/span>&lt;span class="lnt">85
&lt;/span>&lt;span class="lnt">86
&lt;/span>&lt;span class="lnt">87
&lt;/span>&lt;span class="lnt">88
&lt;/span>&lt;span class="lnt">89
&lt;/span>&lt;span class="lnt">90
&lt;/span>&lt;span class="lnt">91
&lt;/span>&lt;span class="lnt">92
&lt;/span>&lt;span class="lnt">93
&lt;/span>&lt;span class="lnt">94
&lt;/span>&lt;span class="lnt">95
&lt;/span>&lt;span class="lnt">96
&lt;/span>&lt;span class="lnt">97
&lt;/span>&lt;span class="lnt">98
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># deployment.yaml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">apps/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Deployment&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">minReadySeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">revisionHistoryLimit&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">progressDeadlineSeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">60&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">strategy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">rollingUpdate&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">maxUnavailable&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">RollingUpdate&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">template&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">annotations&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">prometheus.io/scrape&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;true&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">prometheus.io/port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;9797&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfod&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">stefanprodan/podinfo:3.1.0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">imagePullPolicy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">IfNotPresent&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containerPort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">9898&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">protocol&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">TCP&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http-metrics&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containerPort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">9797&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">protocol&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">TCP&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">grpc&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containerPort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">9999&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">protocol&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">TCP&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">port=9898&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">port-metrics=9797&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">grpc-port=9999&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">grpc-service-name=podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">level=info&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">random-delay=false&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">random-error=false&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">PODINFO_UI_COLOR&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">value&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;#34577c&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">livenessProbe&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">exec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">podcli&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">check&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">http&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">localhost:9898/healthz&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">initialDelaySeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timeoutSeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">readinessProbe&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">exec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">podcli&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">check&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">http&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">localhost:9898/readyz&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">initialDelaySeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timeoutSeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">limits&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">2000m&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">memory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">512Mi&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">requests&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">100m&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">memory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">64Mi&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># ingress.yaml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">networking.k8s.io/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Ingress&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">annotations&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ingressClassName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;nginx&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">rules&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">host&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo.local&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">http&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">paths&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">backend&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">service&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">number&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">9898&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Note that I use &lt;code>http://podinfo.local&lt;/code> as the URL for this service. Use it or change it. You can’t use the &lt;code>kubectl port-forward&lt;/code> **to access it.&lt;/p>
&lt;p>Next we create the Canary resource. This defines how we roll out a new version, how Flagger performs its analysis and optionally run tests on the new version:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">flagger.app/v1beta1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Canary&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">provider&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">nginx&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># deployment reference&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">targetRef&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">apps/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Deployment&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># ingress reference&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ingressRef&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">networking.k8s.io/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Ingress&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># the maximum time in seconds for the canary deployment&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># to make progress before it is rollback (default 600s)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">progressDeadlineSeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">60&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">service&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># ClusterIP port number&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">80&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># container port number or name&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">targetPort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">9898&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">analysis&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># schedule interval (default 60s)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">10s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># max number of failed metric checks before rollback&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">threshold&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">10&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># max traffic percentage routed to canary&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># percentage (0-100)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">maxWeight&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">50&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># canary increment step&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># percentage (0-100)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">stepWeight&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># NGINX Prometheus checks&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metrics&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">request-success-rate&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># minimum req success rate (non 5xx responses)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># percentage (0-100)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">thresholdRange&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">min&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">99&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">1m&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># testing (optional)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">webhooks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">acceptance-test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pre-rollout&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">url&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http://flagger-loadtester.test/&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timeout&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">30s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">bash&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cmd&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;curl -sd &amp;#39;test&amp;#39; http://podinfo-canary/token | grep token&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">load-test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">url&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http://flagger-loadtester.test/&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timeout&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">5s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cmd&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;hey -z 1m -q 10 -c 2 http://podinfo-canary/&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>For details on the settings defined here, read &lt;a class="link" href="https://docs.flagger.app/usage/how-it-works#canary-analysis" target="_blank" rel="noopener"
>this&lt;/a>.&lt;/p>
&lt;p>In short, during a rollout of a new version, we do &lt;em>acceptance-test&lt;/em> and &lt;em>load-test.&lt;/em> Based on the &lt;em>metrics&lt;/em>, Flagger decides if it should keep rolling out the new version, halt, or rollback.&lt;/p>
&lt;p>&lt;a class="link" href="https://github.com/benc-uk/kubeview" target="_blank" rel="noopener"
>KubeView&lt;/a> is a Kubernetes cluster visualizer. It displays and maps out the API objects and how they are interconnected. This is how our Kubernetes test namespace looks like:&lt;/p>
&lt;p>&lt;img src="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/kubeview0.png"
width="556"
height="815"
srcset="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/kubeview0_hu956cd458cb6563fa5c1d8564e8d5290b_100300_480x0_resize_box_3.png 480w, https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/kubeview0_hu956cd458cb6563fa5c1d8564e8d5290b_100300_1024x0_resize_box_3.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="68"
data-flex-basis="163px"
>&lt;/p>
&lt;p>Flagger created the &lt;em>service&lt;/em> resources and another ingress — &lt;em>podinfo-canary&lt;/em>. The special thing about that ingress is it is annotated with &lt;em>canary properties:&lt;/em>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="l">➜ kubectl describe ingress/podinfo-canary&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">Name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo-canary&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">Namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">Address&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">xxx&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">Default backend: default-http-backend:80 (&amp;lt;error&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">endpoints &amp;#34;default-http-backend&amp;#34; not found&amp;gt;)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">Rules&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="l">Host Path Backends&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>---- &lt;span class="w"> &lt;/span>---- &lt;span class="w"> &lt;/span>--------&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo.local&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="l">/ podinfo-canary:9898 (&amp;lt;none&amp;gt;)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">Annotations: nginx.ingress.kubernetes.io/canary&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nginx.ingress.kubernetes.io/canary-weight&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>We have no deployment going on, so the canary-weight is 0.&lt;/p>
&lt;p>Accessing our app shows:&lt;/p>
&lt;p>&lt;img src="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/podinfo0.png"
width="581"
height="765"
srcset="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/podinfo0_hudfbf4747c3a5870b122d75bb4a056d3f_58794_480x0_resize_box_3.png 480w, https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/podinfo0_hudfbf4747c3a5870b122d75bb4a056d3f_58794_1024x0_resize_box_3.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="182px"
>&lt;/p>
&lt;p>OK — Let’s deploy a new version of our app and see how it rolls:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl -n &lt;span class="nb">test&lt;/span> &lt;span class="nb">set&lt;/span> image deployment/podinfo &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>&lt;span class="nv">podinfod&lt;/span>&lt;span class="o">=&lt;/span>stefanprodan/podinfo:3.1.3
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This updates a deployment, which triggers Flagger, which updates our Canary and Ingress resources:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="l">➜ kubectl get canaries -A&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="l">NAMESPACE NAME STATUS WEIGHT LASTTRANSITIONTIME&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="l">test podinfo Progressing 0 2022-03-04T16:18:05Z&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="l">➜ kubectl describe ingress/podinfo-canary&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">Name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo-canary&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">Namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">Address&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">xxx&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">Default backend: default-http-backend:80 (&amp;lt;error&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">endpoints &amp;#34;default-http-backend&amp;#34; not found&amp;gt;)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">Rules&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="l">Host Path Backends&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>---- &lt;span class="w"> &lt;/span>---- &lt;span class="w"> &lt;/span>--------&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo.local&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="l">/ podinfo-canary:9898 ()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">Annotations: nginx.ingress.kubernetes.io/canary&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nginx.ingress.kubernetes.io/canary-weight&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">&amp;lt;-- this was changed by Flagger&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>It brought up a new version of &lt;code>deploy/podinfo&lt;/code> with &lt;code>podinfo-canary&lt;/code> Ingress that points to a service with the same name. (unfortunately, the &lt;code>podinfo-canary&lt;/code> isn’t mapped to the service in the picture). The setup looks like this:&lt;/p>
&lt;p>&lt;img src="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/kubeview1.png"
width="599"
height="712"
srcset="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/kubeview1_hu537bf1b071fd7b73449639862b8a3438_103602_480x0_resize_box_3.png 480w, https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/kubeview1_hu537bf1b071fd7b73449639862b8a3438_103602_1024x0_resize_box_3.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="84"
data-flex-basis="201px"
>&lt;/p>
&lt;p>We can see some of our requests being served by the new version:&lt;/p>
&lt;p>&lt;img src="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/podinfo1.png"
width="625"
height="767"
srcset="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/podinfo1_hu2a05bb78dbd605044b1e03d563f2a7af_59607_480x0_resize_box_3.png 480w, https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/podinfo1_hu2a05bb78dbd605044b1e03d563f2a7af_59607_1024x0_resize_box_3.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="81"
data-flex-basis="195px"
>&lt;/p>
&lt;p>Flagger slowly shifts more traffic to the Canary, until it reaches the promotion stage&lt;/p>
&lt;p>&lt;img src="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/flagger-canary.png"
width="800"
height="415"
srcset="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/flagger-canary_hubd4ffbba7f2c459205b638cd1321e76d_105358_480x0_resize_box_3.png 480w, https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/flagger-canary_hubd4ffbba7f2c459205b638cd1321e76d_105358_1024x0_resize_box_3.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="192"
data-flex-basis="462px"
>&lt;/p>
&lt;p>It then &lt;em>updates&lt;/em> the &lt;em>&lt;code>deployment/podinfo-primary&lt;/code>&lt;/em> to mark the Canary as the primary, or stable version:&lt;/p>
&lt;p>&lt;img src="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/kubeview2.png"
width="591"
height="729"
srcset="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/kubeview2_hu3809fd7a760cfe0ce2d99cbf667acfcf_106609_480x0_resize_box_3.png 480w, https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/kubeview2_hu3809fd7a760cfe0ce2d99cbf667acfcf_106609_1024x0_resize_box_3.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="81"
data-flex-basis="194px"
>&lt;/p>
&lt;p>Once the promote step is done, Flagger scales down &lt;code>podinfo&lt;/code> deployment.&lt;/p>
&lt;p>For reference, you can read more about &lt;a class="link" href="https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#canary" target="_blank" rel="noopener"
>NGINX Canary annotations&lt;/a> and the queries source code Flagger uses to check the NGINX &lt;a class="link" href="https://github.com/fluxcd/flagger/blob/main/pkg/metrics/observers/nginx.go" target="_blank" rel="noopener"
>metrics&lt;/a>.&lt;/p>
&lt;p>NGINX has advanced configurations for Canary, such as &lt;code>nginx.ingress.kubernetes.io/canary-by-header&lt;/code> and &lt;code>nginx.ingress.kubernetes.io/canary-by-cookie&lt;/code> annotations for more fine-grained control over the traffic reaches to Canary. Check out the documentation.&lt;/p>
&lt;blockquote>
&lt;p>Using NGINX for Canary controls only traffic coming from an Ingress (outside your cluster).
It means &lt;em>service-to-service communication&lt;/em> is never going to reach the Canary version during the rollout. If that’s a requirement, check the Linkerd solution below&lt;/p>
&lt;/blockquote>
&lt;h2 id="linkerd-servicemesh-canary-deployment-with-ingress-support">Linkerd (ServiceMesh) Canary Deployment with Ingress support&lt;/h2>
&lt;p>Linkerd provides Canary deployment using &lt;em>ServiceMesh Interface (SMI) &lt;a class="link" href="https://github.com/servicemeshinterface/smi-spec/blob/main/apis/traffic-split/v1alpha3/traffic-split.md" target="_blank" rel="noopener"
>TrafficSplit API&lt;/a>&lt;/em>.&lt;/p>
&lt;p>The specification states that:&lt;/p>
&lt;ul>
&lt;li>&lt;em>“It will be used by clients such as ingress controllers or service mesh sidecars to split the outgoing traffic to different destinations.”&lt;/em>&lt;/li>
&lt;li>&lt;em>“For any clients that are not forwarding their traffic through a proxy that implements this proposal, the standard Kubernetes service configuration would continue to operate.”&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>— What this means is, for Canary to work the Pods involved have to be &lt;em>meshed.&lt;/em>&lt;/p>
&lt;p>Linkerd’s traffic split functionality allows you to dynamically shift arbitrary portions of traffic destined for a Kubernetes service to different destination service. &lt;strong>But how&lt;/strong>?&lt;/p>
&lt;p>Flagger updates the weights in the &lt;em>TrafficSplit&lt;/em> resource and linkerd takes care of the rest. In a meshed pod, &lt;strong>linkerd-proxy&lt;/strong> controls the in and out the traffic of a Pod. It can mutate and re-route traffic. It is sort of the &lt;em>“router”&lt;/em> of the Pod*.*&lt;/p>
&lt;p>&lt;strong>Linkerd is the implementation detail here.&lt;/strong> It watches the &lt;em>TrafficSplit&lt;/em> resource and shapes traffic accordingly.&lt;/p>
&lt;p>&lt;strong>The main points&lt;/strong> to note using a Service Mesh for Canary:&lt;/p>
&lt;ul>
&lt;li>It works only for meshed Pods. Non-meshed Pods would forward / receive traffic regularly&lt;/li>
&lt;li>If you want ingress traffic to reach the Canary version, your ingress controller has to have meshed&lt;/li>
&lt;/ul>
&lt;h3 id="deploy-linkerd">Deploy linkerd&lt;/h3>
&lt;p>Let’s see an example (based on &lt;a class="link" href="https://docs.flagger.app/tutorials/linkerd-progressive-delivery" target="_blank" rel="noopener"
>this one&lt;/a> from the official docs). I will use &lt;a class="link" href="https://github.com/stefanprodan/podinfo" target="_blank" rel="noopener"
>podinfo&lt;/a> as our example app.&lt;/p>
&lt;p>Install linkerd and flagger in &lt;strong>linkerd namespace:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">linkerd install &lt;span class="p">|&lt;/span> kubectl apply -f -
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">linkerd viz install &lt;span class="p">|&lt;/span> kubectl apply -f -
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl apply -k github.com/fluxcd/flagger//kustomize/linkerd
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Install or patch NGINX controller:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Install NGINX&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">helm upgrade -i nginx-ingress ingress-nginx/ingress-nginx &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--namespace ingress-nginx &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--set controller.metrics.enabled&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--set controller.podAnnotations.&lt;span class="s2">&amp;#34;prometheus\.io/scrape&amp;#34;&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--set controller.podAnnotations.&lt;span class="s2">&amp;#34;prometheus\.io/port&amp;#34;&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">10254&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--set controller.podAnnotations.&lt;span class="s2">&amp;#34;linkerd\.io/inject&amp;#34;&lt;/span>&lt;span class="o">=&lt;/span>enabled
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Or upgrade an existing NGINX&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">helm upgrade nginx-ingress ingress-nginx/ingress-nginx &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--reuse-values &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--set controller.metrics.enabled&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--set controller.podAnnotations.&lt;span class="s2">&amp;#34;prometheus\.io/scrape&amp;#34;&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--set controller.podAnnotations.&lt;span class="s2">&amp;#34;prometheus\.io/port&amp;#34;&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">10254&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--set controller.podAnnotations.&lt;span class="s2">&amp;#34;linkerd\.io/inject&amp;#34;&lt;/span>&lt;span class="o">=&lt;/span>enabled
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Create a test namespace, enable Linkerd proxy injection and install load testing tool to generate traffic during &lt;em>canary&lt;/em> analysis:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl create ns &lt;span class="nb">test&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl annotate namespace &lt;span class="nb">test&lt;/span> linkerd.io/inject&lt;span class="o">=&lt;/span>enabled
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># install the load testing tool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl apply -k https://github.com/fluxcd/flagger//kustomize/tester?ref&lt;span class="o">=&lt;/span>main
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Before we continue, you need to validate both &lt;strong>ingress-nginx&lt;/strong> and the &lt;strong>flagger-loadtester&lt;/strong> pods are injected with the &lt;strong>linkerd-proxy&lt;/strong> container.&lt;/p>
&lt;p>Now to the cool parts. Deploy the app by applying the following yaml files:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt"> 10
&lt;/span>&lt;span class="lnt"> 11
&lt;/span>&lt;span class="lnt"> 12
&lt;/span>&lt;span class="lnt"> 13
&lt;/span>&lt;span class="lnt"> 14
&lt;/span>&lt;span class="lnt"> 15
&lt;/span>&lt;span class="lnt"> 16
&lt;/span>&lt;span class="lnt"> 17
&lt;/span>&lt;span class="lnt"> 18
&lt;/span>&lt;span class="lnt"> 19
&lt;/span>&lt;span class="lnt"> 20
&lt;/span>&lt;span class="lnt"> 21
&lt;/span>&lt;span class="lnt"> 22
&lt;/span>&lt;span class="lnt"> 23
&lt;/span>&lt;span class="lnt"> 24
&lt;/span>&lt;span class="lnt"> 25
&lt;/span>&lt;span class="lnt"> 26
&lt;/span>&lt;span class="lnt"> 27
&lt;/span>&lt;span class="lnt"> 28
&lt;/span>&lt;span class="lnt"> 29
&lt;/span>&lt;span class="lnt"> 30
&lt;/span>&lt;span class="lnt"> 31
&lt;/span>&lt;span class="lnt"> 32
&lt;/span>&lt;span class="lnt"> 33
&lt;/span>&lt;span class="lnt"> 34
&lt;/span>&lt;span class="lnt"> 35
&lt;/span>&lt;span class="lnt"> 36
&lt;/span>&lt;span class="lnt"> 37
&lt;/span>&lt;span class="lnt"> 38
&lt;/span>&lt;span class="lnt"> 39
&lt;/span>&lt;span class="lnt"> 40
&lt;/span>&lt;span class="lnt"> 41
&lt;/span>&lt;span class="lnt"> 42
&lt;/span>&lt;span class="lnt"> 43
&lt;/span>&lt;span class="lnt"> 44
&lt;/span>&lt;span class="lnt"> 45
&lt;/span>&lt;span class="lnt"> 46
&lt;/span>&lt;span class="lnt"> 47
&lt;/span>&lt;span class="lnt"> 48
&lt;/span>&lt;span class="lnt"> 49
&lt;/span>&lt;span class="lnt"> 50
&lt;/span>&lt;span class="lnt"> 51
&lt;/span>&lt;span class="lnt"> 52
&lt;/span>&lt;span class="lnt"> 53
&lt;/span>&lt;span class="lnt"> 54
&lt;/span>&lt;span class="lnt"> 55
&lt;/span>&lt;span class="lnt"> 56
&lt;/span>&lt;span class="lnt"> 57
&lt;/span>&lt;span class="lnt"> 58
&lt;/span>&lt;span class="lnt"> 59
&lt;/span>&lt;span class="lnt"> 60
&lt;/span>&lt;span class="lnt"> 61
&lt;/span>&lt;span class="lnt"> 62
&lt;/span>&lt;span class="lnt"> 63
&lt;/span>&lt;span class="lnt"> 64
&lt;/span>&lt;span class="lnt"> 65
&lt;/span>&lt;span class="lnt"> 66
&lt;/span>&lt;span class="lnt"> 67
&lt;/span>&lt;span class="lnt"> 68
&lt;/span>&lt;span class="lnt"> 69
&lt;/span>&lt;span class="lnt"> 70
&lt;/span>&lt;span class="lnt"> 71
&lt;/span>&lt;span class="lnt"> 72
&lt;/span>&lt;span class="lnt"> 73
&lt;/span>&lt;span class="lnt"> 74
&lt;/span>&lt;span class="lnt"> 75
&lt;/span>&lt;span class="lnt"> 76
&lt;/span>&lt;span class="lnt"> 77
&lt;/span>&lt;span class="lnt"> 78
&lt;/span>&lt;span class="lnt"> 79
&lt;/span>&lt;span class="lnt"> 80
&lt;/span>&lt;span class="lnt"> 81
&lt;/span>&lt;span class="lnt"> 82
&lt;/span>&lt;span class="lnt"> 83
&lt;/span>&lt;span class="lnt"> 84
&lt;/span>&lt;span class="lnt"> 85
&lt;/span>&lt;span class="lnt"> 86
&lt;/span>&lt;span class="lnt"> 87
&lt;/span>&lt;span class="lnt"> 88
&lt;/span>&lt;span class="lnt"> 89
&lt;/span>&lt;span class="lnt"> 90
&lt;/span>&lt;span class="lnt"> 91
&lt;/span>&lt;span class="lnt"> 92
&lt;/span>&lt;span class="lnt"> 93
&lt;/span>&lt;span class="lnt"> 94
&lt;/span>&lt;span class="lnt"> 95
&lt;/span>&lt;span class="lnt"> 96
&lt;/span>&lt;span class="lnt"> 97
&lt;/span>&lt;span class="lnt"> 98
&lt;/span>&lt;span class="lnt"> 99
&lt;/span>&lt;span class="lnt">100
&lt;/span>&lt;span class="lnt">101
&lt;/span>&lt;span class="lnt">102
&lt;/span>&lt;span class="lnt">103
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># deployment.yaml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">apps/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Deployment&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">minReadySeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">revisionHistoryLimit&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">progressDeadlineSeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">60&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">strategy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">rollingUpdate&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">maxUnavailable&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">RollingUpdate&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">template&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">annotations&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">prometheus.io/scrape&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;true&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">prometheus.io/port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;9797&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfod&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">stefanprodan/podinfo:3.1.0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">imagePullPolicy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">IfNotPresent&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containerPort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">9898&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">protocol&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">TCP&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http-metrics&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containerPort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">9797&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">protocol&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">TCP&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">grpc&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containerPort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">9999&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">protocol&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">TCP&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">port=9898&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">port-metrics=9797&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">grpc-port=9999&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">grpc-service-name=podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">level=info&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">random-delay=false&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">random-error=false&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">PODINFO_UI_COLOR&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">value&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;#34577c&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">livenessProbe&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">exec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">podcli&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">check&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">http&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">localhost:9898/healthz&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">initialDelaySeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timeoutSeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">readinessProbe&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">exec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">podcli&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">check&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">http&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">localhost:9898/readyz&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">initialDelaySeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timeoutSeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">limits&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">2000m&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">memory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">512Mi&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">requests&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">100m&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">memory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">64Mi&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># ingress.yaml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">networking.k8s.io/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Ingress&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">annotations&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nginx.ingress.kubernetes.io/service-upstream&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;true&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nginx.ingress.kubernetes.io/configuration-snippet&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> proxy_set_header l5d-dst-override $service_name.$namespace.svc.cluster.local:9898;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> proxy_hide_header l5d-remote-ip;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> proxy_hide_header l5d-server-id;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ingressClassName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;nginx&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">rules&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">host&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo.local&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">http&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">paths&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">backend&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">service&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">number&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">9898&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>Gotcha:&lt;/strong> By default, the NGINX ingress controller uses a list of all endpoints (Pod IP/port) in the NGINX upstream configuration. The &lt;code>nginx.ingress.kubernetes.io/service-upstream&lt;/code> annotation &lt;em>disables&lt;/em> that behavior and instead uses a single upstream in NGINX, the &lt;em>service&amp;rsquo;s&lt;/em> Cluster IP and port.&lt;/p>
&lt;p>The &lt;code>nginx.ingress.kubernetes.io/configuration-snippet&lt;/code> annotation rewrites the incoming header to the internal service name (required by Linkerd).&lt;/p>
&lt;p>Next we &lt;em>enable&lt;/em> Canary for our deployment:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># canary.yaml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">flagger.app/v1beta1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Canary&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># deployment reference&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">targetRef&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">apps/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Deployment&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">podinfo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># the maximum time in seconds for the canary deployment&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># to make progress before it is rollback (default 600s)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">progressDeadlineSeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">60&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">service&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># ClusterIP port number&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">9898&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># container port number or name (optional)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">targetPort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">9898&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">analysis&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># schedule interval (default 60s)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">20s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># max number of failed metric checks before rollback&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">threshold&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># max traffic percentage routed to canary&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># percentage (0-100)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">maxWeight&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">50&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># canary increment step&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># percentage (0-100)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">stepWeight&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Linkerd Prometheus checks&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metrics&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">request-success-rate&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># minimum req success rate (non 5xx responses)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># percentage (0-100)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">thresholdRange&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">min&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">99&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">1m&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">request-duration&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># maximum req duration P99&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># milliseconds&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">thresholdRange&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">max&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">500&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">30s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># testing (optional)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">webhooks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">acceptance-test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pre-rollout&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">url&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http://flagger-loadtester.test/&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timeout&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">30s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">bash&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cmd&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;curl -sd &amp;#39;test&amp;#39; http://podinfo-canary.test:9898/token | grep token&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">load-test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">rollout&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">url&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http://flagger-loadtester.test/&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cmd&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;hey -z 2m -q 10 -c 2 http://podinfo-canary.test:9898/&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>For details on the settings defined here, read &lt;a class="link" href="https://docs.flagger.app/usage/how-it-works#canary-analysis" target="_blank" rel="noopener"
>this&lt;/a>.&lt;/p>
&lt;p>In short, during a rollout of a new version, we do &lt;em>acceptance-test&lt;/em> and &lt;em>load-test.&lt;/em> Based on the &lt;em>metrics&lt;/em>, Flagger decides if it should keep rolling out the new version, halt or rollback.&lt;/p>
&lt;p>If you got up here, your setup should look like&lt;/p>
&lt;p>&lt;img src="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/kubeview3.png"
width="578"
height="816"
srcset="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/kubeview3_hu3b78959208c141a0c8440b76025ae069_84579_480x0_resize_box_3.png 480w, https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/kubeview3_hu3b78959208c141a0c8440b76025ae069_84579_1024x0_resize_box_3.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="70"
data-flex-basis="170px"
>&lt;/p>
&lt;p>Accessing our app shows:&lt;/p>
&lt;p>&lt;img src="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/podinfo2.png"
width="574"
height="756"
srcset="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/podinfo2_hu52cf9442b439b8f9bfa49bcaef77dd38_58464_480x0_resize_box_3.png 480w, https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/podinfo2_hu52cf9442b439b8f9bfa49bcaef77dd38_58464_1024x0_resize_box_3.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="182px"
>&lt;/p>
&lt;p>OK — We are all set. Let’s roll out a new version.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="l">kubectl -n test set image deployment/podinfo \&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="l">podinfod=stefanprodan/podinfo:3.1.1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This updates a deployment, which triggers Flagger, which updates our Canary resource:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">➜ kubectl -n &lt;span class="nb">test&lt;/span> describe canary/podinfo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Status:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Canary Weight: &lt;span class="m">5&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Conditions:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Last Transition Time: 2022-03-04T22:18:55Z
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Last Update Time: 2022-03-04T22:18:55Z
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Message: New revision detected, progressing canary analysis.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Reason: Progressing
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Status: Unknown
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Type: Promoted
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Failed Checks: &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Iterations: &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Last Applied Spec: 6ff7d4d4c
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Last Transition Time: 2022-03-04T22:23:15Z
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Phase: Progressing
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Tracked Configs:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Events:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Type Reason Age From Message
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ---- ------ ---- ---- -------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Normal Synced 4m26s flagger New revision detected! Scaling up podinfo.test
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Warning Synced 36s flagger canary deployment podinfo.test not ready: waiting &lt;span class="k">for&lt;/span> rollout to finish: &lt;span class="m">0&lt;/span> of &lt;span class="m">1&lt;/span> &lt;span class="o">(&lt;/span>readyThreshold 100%&lt;span class="o">)&lt;/span> updated replicas are available
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Normal Synced 26s flagger New revision detected! Restarting analysis &lt;span class="k">for&lt;/span> podinfo.test
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>We can see Flagger created a new Deployment, and started pointing traffic to it:&lt;/p>
&lt;p>&lt;img src="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/kubeview4.png"
width="559"
height="722"
srcset="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/kubeview4_hub9c7e8191345d577fe2aa37f75fe72e0_78694_480x0_resize_box_3.png 480w, https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/kubeview4_hub9c7e8191345d577fe2aa37f75fe72e0_78694_1024x0_resize_box_3.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="77"
data-flex-basis="185px"
>&lt;/p>
&lt;p>Our Canary deployment starts serving traffic gradually:&lt;/p>
&lt;p>&lt;img src="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/podinfo3.png"
width="570"
height="715"
srcset="https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/podinfo3_hu52db2ec310c1f760f1f3ef4c4b0e7dbd_56361_480x0_resize_box_3.png 480w, https://devopsian.net/p/a-deep-dive-to-canary-deployments-with-flagger-nginx-and-linkerd-on-kubernetes/podinfo3_hu52db2ec310c1f760f1f3ef4c4b0e7dbd_56361_1024x0_resize_box_3.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="79"
data-flex-basis="191px"
>&lt;/p>
&lt;p>If everything goes well, Flagger will &lt;strong>promote&lt;/strong> our new version to become &lt;strong>primary&lt;/strong>. The status looks like:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">➜ kubectl -n &lt;span class="nb">test&lt;/span> describe canary/podinfo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Status:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Canary Weight: &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Conditions:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Last Transition Time: 2022-03-04T24:20:35Z
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Last Update Time: 2022-03-04T24:20:35Z
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Message: Canary analysis completed successfully, promotion finished.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Reason: Succeeded
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Status: True
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Type: Promoted
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Failed Checks: &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Iterations: &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Last Applied Spec: 6ff7d4d4c
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Last Transition Time: 2022-03-04T24:20:35Z
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Phase: Succeeded
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>Flagger is a powerful tool. It allows safer software releases by gradually shifting the traffic and measuring metrics like HTTP/gRPC. Besides the built-in metrics analysis, you can extend it with custom webhooks for running acceptance and load tests.&lt;/p>
&lt;p>It integrates with multiple Ingress controllers and Service Meshes.&lt;/p>
&lt;p>In these modern times where successful teams look to increase software releases velocity, Flagger helps to govern the process and improve its reliability with fewer failures reaching production.&lt;/p>
&lt;p>While both NGINX and Linkerd can serve Flagger, these are the tradeoffs I found:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>NGINX&lt;/strong>
&lt;ul>
&lt;li>Makes the process simpler with &lt;em>fewer&lt;/em> components. While everybody uses Ingress, not all of us needs a Service Mesh&lt;/li>
&lt;li>Service-to-service communication, which bypasses Ingress, won’t be affected and never reach the Canary&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Linkerd&lt;/strong>
&lt;ul>
&lt;li>Pretty easy Service Mesh to setup with great Flagger integration&lt;/li>
&lt;li>Controls all traffic reaching to the service, both from Ingress and service-to-service communication&lt;/li>
&lt;li>For Ingress traffic, requires some special annotations&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>That’s it for today. Hope you had some insights and a better understanding of this problem.&lt;/p>
&lt;p>Shout out your thoughts on Twitter (&lt;a class="link" href="https://twitter.com/c0anidam" target="_blank" rel="noopener"
>@c0anidam&lt;/a>). &lt;strong>Stay humble, be kind.&lt;/strong>&lt;/p></description></item><item><title>Terraform: why data sources and filters are preferable over remote state</title><link>https://devopsian.net/p/terraform-why-data-sources-and-filters-are-preferable-over-remote-state/</link><pubDate>Tue, 01 Feb 2022 09:30:00 +0200</pubDate><guid>https://devopsian.net/p/terraform-why-data-sources-and-filters-are-preferable-over-remote-state/</guid><description>&lt;img src="https://devopsian.net/p/terraform-why-data-sources-and-filters-are-preferable-over-remote-state/cover.jpg" alt="Featured image of post Terraform: why data sources and filters are preferable over remote state" />&lt;p>Quite often you need to share data or output resources between your Terraform modules. Fundamental modules that build the infrastructure have no dependencies. As your infrastructure grows, the dependencies are inevitable. A frequently used module is the &lt;em>VPC.&lt;/em>&lt;/p>
&lt;p>Almost every resource (if not all of them) requires a VPC to be placed in. When you look up how to share resources between your modules, you’re led to articles and examples of &lt;a class="link" href="https://www.terraform.io/language/state/remote-state-data" target="_blank" rel="noopener"
>terraform_remote_state&lt;/a>. This solves the problem, but in my opinion, there is a better alternative. By better, I mean, more stable.&lt;/p>
&lt;p>&lt;em>Whenever you need to share the state between modules, your first choice should be terraform data sources. &lt;code>terraform_remote_state&lt;/code> should be the alternative when the first is not achievable.&lt;/em>&lt;/p>
&lt;h2 id="use-the-data-aws_-resource">Use the data “aws_*” resource&lt;/h2>
&lt;blockquote>
&lt;p>&lt;em>Data sources&lt;/em> allow Terraform to use the information defined outside of Terraform, defined by another separate Terraform configuration, or modified by functions.&lt;/p>
&lt;/blockquote>
&lt;p>Using data sources with Terraform is a good design choice. It uses AWS API to fetch resources based on names, filters (tags), etc. instead of hard-coding them in the module or fetching them from a remote state.&lt;/p>
&lt;p>Data sources are more &lt;em>accurate&lt;/em>. They are always up to date. The code will always check the &lt;em>active&lt;/em> resource and won’t depend on the module which created it.&lt;/p>
&lt;p>Let’s look at a couple of use cases.&lt;/p>
&lt;h3 id="s3-bucket">S3 bucket&lt;/h3>
&lt;p>If your module requires an S3 bucket name or ID as input, it is as simple as that to provide it using the relevant data source:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">data&lt;/span> &lt;span class="s2">&amp;#34;aws_s3_bucket&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;this&amp;#34;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">bucket&lt;/span> = &lt;span class="s2">&amp;#34;my-shiny-bucket&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">module&lt;/span> &lt;span class="s2">&amp;#34;app&amp;#34;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">source&lt;/span> = &lt;span class="s2">&amp;#34;../modules/app&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">..&lt;/span> &lt;span class="nx">removed&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="nx">brevity&lt;/span> &lt;span class="p">..&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">s3_bucket&lt;/span> = &lt;span class="nb">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">aws_s3_bucket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="aws-account-id">AWS account ID&lt;/h3>
&lt;p>How many times have you needed to provide your account ID to configure a policy? instead of copy-pasting this value between your module, simply fetch it using a data source:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">data&lt;/span> &lt;span class="s2">&amp;#34;aws_caller_identity&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;this&amp;#34;&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">module&lt;/span> &lt;span class="s2">&amp;#34;app&amp;#34;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">source&lt;/span> = &lt;span class="s2">&amp;#34;../modules/app&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">..&lt;/span> &lt;span class="nx">removed&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="nx">brevity&lt;/span> &lt;span class="p">..&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">aws_account&lt;/span> = &lt;span class="nb">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">aws_caller_identity&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">account_id&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Time to look for the more advanced features.&lt;/p>
&lt;h2 id="filters">Filters&lt;/h2>
&lt;p>Data sources support the &lt;code>filter&lt;/code> block, to fetch specific resources. A filter allows you to filter the results returned from a data source call. The use-case I’m using for an example is updating routing tables.&lt;/p>
&lt;p>You have your VPC, which contains multiple subnets. You have a task to add a route to these routing tables in the staging environment.&lt;/p>
&lt;p>How would you get the list of the routing tables? What if you would like to update just the ones related to private subnets? Does your solution still work?&lt;/p>
&lt;p>Let’s look at some code.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># main.tf
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># -------
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">data&lt;/span> &lt;span class="s2">&amp;#34;aws_route_tables&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;rts&amp;#34;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">vpc_id&lt;/span> = &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">vpc_id&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">dynamic&lt;/span> &lt;span class="s2">&amp;#34;filter&amp;#34;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">for_each&lt;/span> = &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rts_filters&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">content&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">name&lt;/span> = &lt;span class="nx">filter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">values&lt;/span> = &lt;span class="nx">filter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">values&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_route&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;r&amp;#34;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">count&lt;/span> =&lt;span class="nb"> length&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">aws_route_tables&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rts&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ids&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">route_table_id&lt;/span> = &lt;span class="nb">tolist&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">aws_route_tables&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rts&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ids&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="nb">count&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">index&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">destination_cidr_block&lt;/span> = &lt;span class="s2">&amp;#34;10.0.1.0/22&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">vpc_peering_connection_id&lt;/span> = &lt;span class="s2">&amp;#34;pcx-45ff3dc1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;span class="c1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># terraform.tfvars
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ----------------
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="na">rts_filters&lt;/span> = &lt;span class="p">[{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">name&lt;/span> = &lt;span class="s2">&amp;#34;tag:Environment&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">values&lt;/span> = &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;staging&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">name&lt;/span> = &lt;span class="s2">&amp;#34;tag:Name&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">values&lt;/span> = &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;*private*&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;ul>
&lt;li>The example assumes your resources are &lt;strong>tagged&lt;/strong> with &lt;em>Environment = staging,&lt;/em> and contains the string &lt;em>private&lt;/em> in their name (e.g, us-east-private-1a, us-east-private-1b, ..)&lt;/li>
&lt;li>A &lt;a class="link" href="https://www.terraform.io/language/expressions/dynamic-blocks" target="_blank" rel="noopener"
>dynamic&lt;/a> block acts much like a &lt;a class="link" href="https://www.terraform.io/language/expressions/for" target="_blank" rel="noopener"
>for expression&lt;/a> but produces nested blocks instead of a complex typed value. It iterates over a given complex value and generates a nested block for each element of that complex value.
&lt;ul>
&lt;li>Using this block, you can support multiple filters. This is translated to multiple &lt;code>filter&lt;/code> blocks&lt;/li>
&lt;li>I’m using the &lt;code>tag:..&lt;/code> key, but there are other filters you can use depending on the resource type&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Using &lt;code>filter.value&lt;/code> we have access to the value of the current element&lt;/li>
&lt;li>In the example, I’m adding a route to &lt;code>10.0.1.0/22&lt;/code> through VPC peering. For additional config options, check &lt;a class="link" href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route" target="_blank" rel="noopener"
>aws_route docs&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>This syntax feels a bit strange at first until you get used to it, and is very powerful.&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>In the world of programming or IaC, there is more than one way to solve a problem. In this post I wrote my preference after adopting the use of terraform data sources, rather than using &lt;code>terraform_remote_state&lt;/code>. The latter has its use. I find it more complex and fragile to changes.&lt;/p>
&lt;p>The takeaway from this is, the statement at the top of the article:&lt;/p>
&lt;blockquote>
&lt;p>&lt;em>Whenever you need to share the state between modules, your first choice should be terraform data sources. &lt;code>terraform_remote_state&lt;/code> should be the alternative when the first is not achievable.&lt;/em>&lt;/p>
&lt;/blockquote>
&lt;p>Any thoughts or comments are welcome.&lt;/p></description></item><item><title>Practical unit-testing web client in Go - part 2</title><link>https://devopsian.net/p/practical-unit-testing-web-client-in-go-part-2/</link><pubDate>Sat, 02 Oct 2021 23:01:00 +0300</pubDate><guid>https://devopsian.net/p/practical-unit-testing-web-client-in-go-part-2/</guid><description>&lt;img src="https://devopsian.net/p/practical-unit-testing-web-client-in-go-part-2/cover.jpg" alt="Featured image of post Practical unit-testing web client in Go - part 2" />&lt;h2 id="testing-webserver-with-tls">Testing webserver with TLS&lt;/h2>
&lt;p>In the previous &lt;a class="link" href="https://devopsian.net/p/practical-unit-testing-web-client-in-go/" >post&lt;/a>, I overviewed how to practically test web client in go.&lt;/p>
&lt;p>The next step, I want to test a web server with self-signed certificates. To cover this use-case, we need to:&lt;/p>
&lt;ol>
&lt;li>Create self-signed certificates, with SAN property of 127.0.0.1.&lt;/li>
&lt;li>Start the testing webserver using this certificate.&lt;/li>
&lt;li>Configure my client to trust the RootCA.&lt;/li>
&lt;/ol>
&lt;p>Most of the people, when they reach this stage of testing, usually bend corners. You can simply disable the TLS verification with &lt;code>&amp;amp;tls.Config{InsecureSkipVerify: true}&lt;/code> and be done with it.&lt;/p>
&lt;p>I can&amp;rsquo;t. Can&amp;rsquo;t treat security precautions with no care; this is a package that works with a &lt;em>Secret Manager&lt;/em>. In my case, testing the client works with self-signed certificate is significant.&lt;/p>
&lt;h2 id="create-self-signed-certificates">Create self-signed certificates&lt;/h2>
&lt;p>Luckily, there&amp;rsquo;s a great github repository that covers the creation of certificates: &lt;a class="link" href="https://github.com/jcbsmpsn/golang-https-example" target="_blank" rel="noopener"
>https://github.com/jcbsmpsn/golang-https-example&lt;/a>&lt;/p>
&lt;p>The repo contains solutions to common issues raised when trying to achieve this task. If you encounter any errors during this procedure, go check it out. To create certificates:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Create CA key + certificate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">openssl req &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -newkey rsa:2048 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -nodes &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -days &lt;span class="m">3650&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -x509 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -keyout ca.key &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -out ca.crt &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -subj &lt;span class="s2">&amp;#34;/CN=*&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Create server certificate request and key&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">openssl req &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -newkey rsa:2048 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -nodes &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -keyout server.key &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -out server.csr &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -subj &lt;span class="s2">&amp;#34;/C=GB/ST=London/L=London/O=libvault consultants/OU=IT Department/CN=*&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Sign the server certificate request with the CA key&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># adding SAN IP&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">openssl x509 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -req &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -days &lt;span class="m">365&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -sha256 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -in server.csr &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -CA ca.crt &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -CAkey ca.key &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -CAcreateserial &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -out server.crt &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -extfile &amp;lt;&lt;span class="o">(&lt;/span>&lt;span class="nb">echo&lt;/span> &lt;span class="nv">subjectAltName&lt;/span> &lt;span class="o">=&lt;/span> IP:127.0.0.1&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Running the above code results in 2 sets of certificates: ca and server certificates. Now let&amp;rsquo;s use them.&lt;/p>
&lt;h2 id="start-webserver-with-tls">Start webserver with TLS&lt;/h2>
&lt;p>There&amp;rsquo;s no single API to start the test server with TLS configuration, so we need to do it in few steps. First I&amp;rsquo;ll create a &lt;em>unstarted&lt;/em> test server, load the x509 keypairs and configure the server to use them. Only then I start the web server. In code, it looks like this&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">const&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">caCertPath&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;testdata/certs/ca.crt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">serverCertPath&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;testdata/certs/server.crt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">serverKeyPath&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;testdata/certs/server.key&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">TestClientLogin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">t&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">testing&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">T&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">mux&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewServeMux&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ts&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">httptest&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewUnstartedServer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">mux&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cert&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">tls&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadX509KeyPair&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">serverCertPath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">serverKeyPath&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// handle error
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// setp the server&amp;#39;s TLS configurations
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">ts&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">TLS&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">tls&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Config&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Certificates&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="nx">tls&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Certificate&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">cert&lt;/span>&lt;span class="p">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// start the server with TLS support
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">ts&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">StartTLS&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="configure-the-httpclient-to-trust-my-rootca">Configure the http.Client to trust my RootCA&lt;/h3>
&lt;p>Now we have a webserver that uses our own self-signed certificate. Cool.
Remember the CA key and certificate we created earlier? Now we need to configure our client to &lt;em>trust&lt;/em> certificates &lt;em>signed by&lt;/em> our CA.&lt;/p>
&lt;p>My client wraps the &lt;code>http.Client&lt;/code> structure. To simplify things, here I&amp;rsquo;ll work straight with the &lt;code>http.Client&lt;/code> struct.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;crypto/tls&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;crypto/x509&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">caCertPath&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;testdata/certs/ca.crt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">certPool&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">x509&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewCertPool&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">caCert&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">_&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">ioutil&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ReadFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">caCertPath&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">ok&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">certPool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">AppendCertsFromPEM&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">caCert&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="nx">ok&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// error handling
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">tlsCfg&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">tls&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Config&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">RootCAs&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">certPool&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">client&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Client&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Transport&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Transport&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">TLSClientConfig&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">tlsCfg&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now, requests made from my client to my secured test webserver are &lt;em>validated&lt;/em>.&lt;/p></description></item><item><title>Practical unit-testing web client in Go</title><link>https://devopsian.net/p/practical-unit-testing-web-client-in-go/</link><pubDate>Fri, 28 May 2021 10:45:00 +0300</pubDate><guid>https://devopsian.net/p/practical-unit-testing-web-client-in-go/</guid><description>&lt;img src="https://devopsian.net/p/practical-unit-testing-web-client-in-go/cover.jpg" alt="Featured image of post Practical unit-testing web client in Go" />&lt;p>I&amp;rsquo;ve started a Go project, a lightweight Hashicorp Vault&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> client with no dependencies, and a simple API (for the user). Part of the reason I use no 3rd party modules is, I want to better understand Go internals, structure, and improve my skills. In this post, I&amp;rsquo;ll conduct a &lt;em>practical&lt;/em> example of how I ended up testing it.&lt;/p>
&lt;p>The project&amp;rsquo;s name is &lt;strong>&lt;a class="link" href="https://github.com/canidam/libvault" target="_blank" rel="noopener"
>libvault&lt;/a>&lt;/strong>, and it&amp;rsquo;s my &lt;em>first open-source&lt;/em> project.&lt;/p>
&lt;p>Vault is a secret manager service with web API and CLI. Applications can communicate with it through HTTP. I find the CLI easy to use, however, the official Vault library is a bit more complicated; it felt like a swiss-army knife when all I needed was just a simple kitchen knife. I decided to implement a light version, that covers basic functionality while maintaining a simple API.&lt;/p>
&lt;blockquote>
&lt;p>I&amp;rsquo;m not going to cover the basics in this post. The intention of it is to be &lt;em>practical&lt;/em> and provide &lt;em>real-life, working&lt;/em> examples. The opinions here are mine. It worked for me, still, it doesn&amp;rsquo;t mean it would work for you.&lt;/p>
&lt;/blockquote>
&lt;p>&lt;em>I removed ALL the error handling from the code for brevity. Please make sure you handle your errors.&lt;/em>&lt;/p>
&lt;p>The Go standard library provides a really good testing package. You can manage without external frameworks or 3rd party packages. In my scenario, I need to test a web client that I wrote. The technique I found useful is &lt;em>mock testing.&lt;/em>&lt;/p>
&lt;blockquote>
&lt;p>Mock testing is an approach to unit testing that lets you make assertions about how the code under test is interacting with other system modules. In mock testing, the dependencies are replaced with objects that simulate the behaviour of the real ones. &amp;hellip; Such a service can be replaced with a mock object. ~ Wikipedia&lt;/p>
&lt;/blockquote>
&lt;p>I had to choose which component to mock:&lt;/p>
&lt;ol>
&lt;li>&lt;em>Client&lt;/em>&lt;/li>
&lt;li>&lt;em>Webserver&lt;/em>&lt;/li>
&lt;/ol>
&lt;p>At a high level, mocking the client means creating a new struct that implements the &lt;em>interface&lt;/em> you are testing (mocking the interface). Then, provide your mocking client to the code under test. A good library with examples is &lt;a class="link" href="https://github.com/stretchr/testify#mock-package" target="_blank" rel="noopener"
>testify&lt;/a>.&lt;/p>
&lt;p>I didn&amp;rsquo;t find it useful for my use case, as I need to mock the server-side. I prefer not to modify any code on the client if I can. This would result in more &lt;strong>reliable tests&lt;/strong> for my package. So, I&amp;rsquo;ve chosen to go with the second option. Read on for how.&lt;/p>
&lt;h2 id="the-httptest-package">The httptest package&lt;/h2>
&lt;p>Go is very friendly to web services; it has a &lt;a class="link" href="https://golang.org/pkg/net/http/httptest/" target="_blank" rel="noopener"
>utility package (httptest)&lt;/a> for testing an http server. You can simply start a webserver in your &lt;em>testing code&lt;/em>.&lt;/p>
&lt;p>First I had to fetch &lt;strong>an exact response&lt;/strong> from a &lt;strong>real webserver&lt;/strong> (Vault server), then I could easily mock it using this package. For example, when querying the &lt;code>/v1/auth/approle/login&lt;/code> endpoint, the response looks like this:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;request_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;de7c8097-1a38-50a6-b971-fe1836840e45&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;lease_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;renewable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now that I have the content, I find it easier to save it to a file (rather than put it inside the code). Go &lt;code>testing&lt;/code> package has another cool feature:&lt;/p>
&lt;blockquote>
&lt;p>The go tool will ignore a directory named &amp;ldquo;testdata&amp;rdquo;, making it available
to hold ancillary data needed by the tests.&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>&lt;/p>
&lt;/blockquote>
&lt;p>I created a directory named &lt;code>testdata&lt;/code> inside my project and placed the JSON content in a file - &lt;em>approleExample.json&lt;/em>.&lt;/p>
&lt;h2 id="start-the-mocking-server">Start the mocking server&lt;/h2>
&lt;p>I&amp;rsquo;ll start with code, followed by an explanation:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;io&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;net/http&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;net/http/httptest&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">TestClientLogin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">t&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">testing&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">T&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">mux&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewServeMux&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ts&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">httptest&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewServer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">mux&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nx">ts&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">mux&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">HandleFunc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/v1/auth/approle/login&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ResponseWriter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// request validation logic
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// read json response
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">jsonPayload&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">_&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">ioutil&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ReadFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;testdata/approleExample.json&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">w&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Header&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Content-Type&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;application/json&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">w&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WriteHeader&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">200&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">w&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">jsonPayload&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// initalize new client pointing to the testing server
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">client&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">NewClient&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ts&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">URL&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// error handling
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// test logic
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>If you&amp;rsquo;re familiar with the Go http package, this code is pretty self-explanatory. This is another advantage of using the standard library - &lt;em>you have one less thing to learn&lt;/em> when you want to contribute.&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;code>mux&lt;/code> is of &lt;code>ServeMux&lt;/code> type. Which is &lt;em>an HTTP request multiplexer&lt;/em>. It matches the URL of each incoming request (&lt;code>/v1/auth/approle/login&lt;/code>) against a list of registered patterns and calls the handler for the pattern that most closely matches the URL (our mux.HandleFunc function body).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>ts&lt;/code> is of &lt;code>Server&lt;/code> type. A Server is an HTTP server listening on a system-chosen port on the local loopback interface, for use in end-to-end HTTP tests. Note that I use &lt;code>httptest.NewServer&lt;/code> to initialize it.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>mux.HandleFunc(..)&lt;/code> defines a &lt;em>path&lt;/em> and an &lt;em>handler (a function)&lt;/em> to call. The content inside describes our server&amp;rsquo;s response.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>client, err := NewClient(ts.URL)&lt;/code> creates a new Client (my Vault client), providing it the test server URL to work with.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>The elegance of this pattern is, &lt;em>every&lt;/em> test case has its test webserver with all the relevant configurations. The mocked content, the test logic, etc. are all &lt;em>implemented inside the test itself.&lt;/em>
This really makes life easier when &lt;em>debugging, reviewing a test case logic&lt;/em> or coverage.&lt;/p>
&lt;p>We can improve it further, make the code more clear and concise. This code includes some boilerplate: creating the mux, the server, and reading the json content. We just need to refactor these elements out (to a &lt;code>setup()&lt;/code> function, and &lt;code>readJson(path string)&lt;/code>, for example). Then call this &lt;code>setup()&lt;/code> function for every test case. I&amp;rsquo;ll leave that to the reader to decide.&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>There are numerous articles about the importance of software testing. &lt;em>Go makes it easier&lt;/em>. You &lt;strong>should&lt;/strong> always write tests for your packages; it has so many advantages to just skip it. However, many people do that and I can assume the reason, which is it doesn&amp;rsquo;t provide any additional functionality to your code. &lt;em>Don&amp;rsquo;t be one of these people.&lt;/em>&lt;/p>
&lt;p>Go standard library provides great tools, and you should use them. Personally, I prefer it over other dependencies.&lt;/p>
&lt;p>In this post, I gave a practical example of how you can unit-test a web client by &lt;em>mocking&lt;/em> it.&lt;/p>
&lt;p>The takeaways:&lt;/p>
&lt;ol>
&lt;li>&lt;em>Always&lt;/em> write tests. They are too valuable to give up and very easy to do with Go.&lt;/li>
&lt;li>&lt;em>Mock&lt;/em> the part your code connects with, don&amp;rsquo;t mock &lt;em>your&lt;/em> code. If you write a server, mock the client, and vice-versa. It makes your tests much more reliable.
&lt;ol>
&lt;li>If you mock a server, get a &lt;em>real&lt;/em> server response and save it to a file&lt;/li>
&lt;li>Do that for every API you would like to mock&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>Use &lt;code>testdata&lt;/code> directory to hold your ancillary data needed by your tests&lt;/li>
&lt;/ol>
&lt;p>In the next &lt;a class="link" href="https://devopsian.net/posts/test-web-https-go/" target="_blank" rel="noopener"
>post&lt;/a>, I will cover how to test with a TLS (HTTPS) web server and self-signed certificates.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>&lt;a class="link" href="https://www.vaultproject.io/" target="_blank" rel="noopener"
>https://www.vaultproject.io/&lt;/a>&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>&lt;a class="link" href="https://golang.org/pkg/cmd/go/internal/test/" target="_blank" rel="noopener"
>https://golang.org/pkg/cmd/go/internal/test/&lt;/a>&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>How to use SED in a Jenkins Pipeline</title><link>https://devopsian.net/p/how-to-use-sed-in-a-jenkins-pipeline/</link><pubDate>Sat, 08 May 2021 20:37:30 +0200</pubDate><guid>https://devopsian.net/p/how-to-use-sed-in-a-jenkins-pipeline/</guid><description>&lt;img src="https://devopsian.net/p/how-to-use-sed-in-a-jenkins-pipeline/jenkins_logo.svg" alt="Featured image of post How to use SED in a Jenkins Pipeline" />&lt;p>Have you ever encountered the need to modify a single line in a file? While there are various utilities for this task, I consider &lt;code>sed&lt;/code> to be superior to other alternatives.&lt;/p>
&lt;p>&lt;code>sed&lt;/code> is a &lt;em>stream editor&lt;/em>, with which you can modify files. It&amp;rsquo;s a great utility in your toolbox.
If you&amp;rsquo;re unfamiliar with it, here&amp;rsquo;s a &lt;a class="link" href="https://www.digitalocean.com/community/tutorials/the-basics-of-using-the-sed-stream-editor-to-manipulate-text-in-linux" target="_blank" rel="noopener"
>link&lt;/a> for a quick intro.&lt;/p>
&lt;p>I had to use it in one of my Jenkins Pipelines. Since escaping commands in a pipeline is a &lt;a class="link" href="https://gist.github.com/Faheetah/e11bd0315c34ed32e681616e41279ef4" target="_blank" rel="noopener"
>nightmare&lt;/a>, I had to debug it for a while until I got it working.
If you have a similar task, this might save you some precious time.&lt;/p>
&lt;p>The requirement was to update a line in a large file (couple hundreds of lines), with an arbitrary string provided as an input.&lt;/p>
&lt;p>An elegant way of doing it with &lt;code>sed&lt;/code> is to capture the &lt;em>known prefix&lt;/em>, up to a delimiter and then replace the whole thing comes next (until the end of line).
For example, we have the following file:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yml" data-lang="yml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">myapp&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dev&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1.0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="l">..&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now we want to update the &lt;code>version&lt;/code> part from Jenkins. &lt;em>How can we do it?&lt;/em> When working with Jenkins, I recommend do it &lt;strong>verbosly&lt;/strong>. Print the stuff before and after you change,
it would be much easier to debug later on. This is how I decided implementing it:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-groovy" data-lang="groovy">&lt;span class="line">&lt;span class="cl">&lt;span class="n">sh&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> # Print current version
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> cat app.yml | grep version
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> # Update version using sed
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> sed -i &amp;#39;s|^version: .*|^version: &amp;#34;${VERSION}&amp;#34;|&amp;#39; app.yml
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> # Print updated version
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> cat app.yml | grep version
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Using &lt;code>sh&lt;/code> module, with &lt;code>&amp;quot;&amp;quot;&amp;quot; ... &amp;quot;&amp;quot;&amp;quot;&lt;/code> I can escape the escaping nightmare of groovy. Otherwise I would have to put multiple &lt;code>\\\\&lt;/code> in the command, up to the point it&amp;rsquo;s barely readable.
I also include &lt;code>#!/bin/bash&lt;/code>, to make sure I&amp;rsquo;m using bash shell.&lt;/p>
&lt;p>Let&amp;rsquo;s break down the &lt;code>sed&lt;/code> command.&lt;/p>
&lt;ul>
&lt;li>&lt;code>-i&lt;/code> means do the replacement &lt;em>in-place&lt;/em>; meaning &lt;code>sed&lt;/code> will update the file. You can provide a suffix after the parameter, and &lt;code>sed&lt;/code> will create a backup version of the file before doing the update.&lt;/li>
&lt;li>&lt;code>'s|..#1..|..#2..|'&lt;/code> - &lt;code>s&lt;/code> is for &lt;strong>substitute&lt;/strong>, &lt;strong>#1&lt;/strong> is the string, or pattern to look for and &lt;strong>#2&lt;/strong> is the replacement. The &lt;code>${VERSION}&lt;/code> variable is defined by Jenkins.&lt;/li>
&lt;li>for &lt;strong>#1&lt;/strong>, I&amp;rsquo;m looking for the &lt;em>prefix&lt;/em> &amp;ldquo;version:&amp;rdquo; and then using &lt;em>regex&lt;/em> to capture whatever comes next.&lt;/li>
&lt;li>for &lt;strong>#2&lt;/strong>, I&amp;rsquo;m putting the &lt;em>full string I expect&lt;/em> to be included in the final file.&lt;/li>
&lt;/ul>
&lt;p>There are other ways to achieve the same goal, but I find it a &lt;em>good practice&lt;/em> to print stuff before and after an automatic job changes something in a file.&lt;/p>
&lt;p>Feel free to integrate and adapt this approach to your projects. It&amp;rsquo;s a clean and efficient way to handle file updates within a Jenkins pipeline.&lt;/p></description></item><item><title>How to share persistent storage volumes in Swarm</title><link>https://devopsian.net/p/how-to-share-persistent-storage-volumes-in-swarm/</link><pubDate>Mon, 22 Feb 2021 23:47:30 +0200</pubDate><guid>https://devopsian.net/p/how-to-share-persistent-storage-volumes-in-swarm/</guid><description>&lt;img src="https://devopsian.net/p/how-to-share-persistent-storage-volumes-in-swarm/cover.jpg" alt="Featured image of post How to share persistent storage volumes in Swarm" />&lt;p>Docker &lt;a class="link" href="https://docs.docker.com/engine/swarm/key-concepts/" target="_blank" rel="noopener"
>swarm&lt;/a> is an orchestration tool, similar to Kubernetes, but simpler to set up and manage.
A swarm consists of multiple Docker hosts which run in swarm mode and act as managers (to manage membership and delegation) and workers (which run swarm services). A given Docker host can be a manager, a worker, or perform both roles.&lt;/p>
&lt;p>The docker swarm feature is embedded in the Docker Engine (using &lt;a class="link" href="https://github.com/docker/swarmkit/" target="_blank" rel="noopener"
>swarmkit&lt;/a>). This means you don&amp;rsquo;t need to install extra packages to use it. You just docker. If you decided to put it in place, one of the open problems to address is the persistent storage.&lt;/p>
&lt;p>In the old days, many processes could share a local disk on the host. It was a common pattern to have a process that writes to files, and another that consumes them. The two processes were living happily on the same host, having no problems working together. But it&amp;rsquo;s another story in the containerized world. When containers are managed by an orchestrator, you can&amp;rsquo;t really tell where your container would be scheduled to run. It won&amp;rsquo;t have a permanent host, neither you want to lock it to a specific host, as you lose many of the orchestrator benefits (resiliency, fail-over, etc.).&lt;/p>
&lt;h2 id="the-problem-data-persistency-on-the-swarm-cluster">The Problem: Data Persistency on the Swarm cluster&lt;/h2>
&lt;p>Suppose you have two services that need to share a disk, or a service that requires data persistency such as a Redis. What are your options?&lt;/p>
&lt;p>One easy option is to use &lt;em>&lt;a class="link" href="https://docs.docker.com/storage/volumes/" target="_blank" rel="noopener"
>docker volumes&lt;/a>&lt;/em>. It&amp;rsquo;s a good option if you run on a single node because when you create a new docker volume, it resides on the host it was created on.&lt;/p>
&lt;p>But what happens when it runs on a cluster? good question. Let&amp;rsquo;s walk through an example. I&amp;rsquo;m going to one from the use the Docker Documentation (&lt;a class="link" href="https://docs.docker.com/compose/gettingstarted/" target="_blank" rel="noopener"
>Get Started with Docker Compose&lt;/a>)&lt;/p>
&lt;p>Here are the files I&amp;rsquo;m using for this example:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">devopsian: ~/demo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">➜ tree
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">|&lt;/span>-app.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">|&lt;/span>-Dockerfile
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">|&lt;/span>-docker-compose.yml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># app.py&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">redis&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">flask&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Flask&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Flask&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__name__&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">redis&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Redis&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">host&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;redis&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">port&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">6379&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_hit_count&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">retries&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">5&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="kc">True&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">cache&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">incr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;hits&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="n">redis&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exceptions&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ConnectionError&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">exc&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">retries&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="n">exc&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">retries&lt;/span> &lt;span class="o">-=&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.5&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@app.route&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">hello&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_hit_count&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s1">&amp;#39;Hello World! I have been seen &lt;/span>&lt;span class="si">{}&lt;/span>&lt;span class="s1"> times.&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">count&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-Dockerfile" data-lang="Dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Dockerfile&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="s"> python:3.7-alpine&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">WORKDIR&lt;/span>&lt;span class="s"> /code&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENV&lt;/span> &lt;span class="nv">FLASK_APP&lt;/span>&lt;span class="o">=&lt;/span>app.py&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENV&lt;/span> &lt;span class="nv">FLASK_RUN_HOST&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">0&lt;/span>.0.0.0&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> apk add --no-cache gcc musl-dev linux-headers&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> pip install -r flask redis&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">EXPOSE&lt;/span>&lt;span class="s"> 5000&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> . .&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">CMD&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;flask&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;run&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-Dockerfile" data-lang="Dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># docker-compose.yml&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>version: &lt;span class="s2">&amp;#34;3.7&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>services:&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> app:&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> build: .&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> ports:&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> - &lt;span class="s2">&amp;#34;5000:5000&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> redis:&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> image: &lt;span class="s2">&amp;#34;redis:alpine&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> volumes:&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> - &lt;span class="s2">&amp;#34;redisdata:/data&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>volumes:&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> redisdata:&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> driver: &lt;span class="s2">&amp;#34;local&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Suppose you have a small cluster of 3 nodes. (Their roles in the cluster don&amp;rsquo;t matter for example). You run your docker-compose file with your app and a Redis instance, and define a volume for it. The first time you run your docker-compose, docker creates the volume and mounts it to your service on startup.&lt;/p>
&lt;p>&lt;img src="https://devopsian.net/p/how-to-share-persistent-storage-volumes-in-swarm/swarm-efs-a.png"
width="623"
height="349"
srcset="https://devopsian.net/p/how-to-share-persistent-storage-volumes-in-swarm/swarm-efs-a_hu0df1d0a692c22048502bdf208ca40118_51106_480x0_resize_box_3.png 480w, https://devopsian.net/p/how-to-share-persistent-storage-volumes-in-swarm/swarm-efs-a_hu0df1d0a692c22048502bdf208ca40118_51106_1024x0_resize_box_3.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="178"
data-flex-basis="428px"
>&lt;/p>
&lt;p>Now you want to deploy a new version of your service. The cluster decides to schedule the Redis instance on a different node than before. What will happen to your volume?&lt;/p>
&lt;p>&lt;img src="https://devopsian.net/p/how-to-share-persistent-storage-volumes-in-swarm/swarm-efs-b.png"
width="622"
height="348"
srcset="https://devopsian.net/p/how-to-share-persistent-storage-volumes-in-swarm/swarm-efs-b_hu3b823ba7547fed21eaf89384128f5c2d_52796_480x0_resize_box_3.png 480w, https://devopsian.net/p/how-to-share-persistent-storage-volumes-in-swarm/swarm-efs-b_hu3b823ba7547fed21eaf89384128f5c2d_52796_1024x0_resize_box_3.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="178"
data-flex-basis="428px"
>&lt;/p>
&lt;p>Since the volume doesn&amp;rsquo;t exist on the node the service runs at, docker would create a new volume with the same name on the new node. The previous volume still exists with the data but, it resides on the old node. The Redis service has no access to it. Redis now actually has a new, empty volume for use. &lt;strong>You end up in an inconsistent state&lt;/strong>. This solution doesn&amp;rsquo;t work for us.&lt;/p>
&lt;h2 id="aws-elastic-file-system-efs">AWS Elastic File System (EFS)&lt;/h2>
&lt;blockquote>
&lt;p>Amazon Elastic File System (Amazon EFS) provides a simple, scalable, fully managed elastic NFS file system for use .. It is built to &lt;em>scale on-demand&lt;/em> to petabytes without disrupting applications, &lt;em>growing and shrinking automatically&lt;/em> as you add and remove files, eliminating the need to provision and manage capacity to accommodate growth.&lt;/p>
&lt;/blockquote>
&lt;p>EFS provides a NFS volume you can mount at &lt;strong>runtime&lt;/strong>. The swarm cluster schedules to run the service on one of its nodes and mounts the NFS volume to the container.&lt;/p>
&lt;p>With these configurations, if the cluster decides to move our Redis service to a different node (due to a failure, deployment, etc.), the mount (and hence the data) will move to the new node too. I won&amp;rsquo;t go through how to create an EFS volume, you can find the steps &lt;strong>&lt;a class="link" href="https://docs.aws.amazon.com/efs/latest/ug/getting-started.html" target="_blank" rel="noopener"
>here on aws tutorial&lt;/a>&lt;/strong>.&lt;/p>
&lt;p>To achieve that, we need to install the &lt;em>nfs-common&lt;/em> package on the swarm nodes. On Ubuntu, you can install it with: &lt;code>sudo apt-get install nfs-common&lt;/code>&lt;/p>
&lt;p>Next, we will update the &lt;em>volumes&lt;/em> definition of our docker-compose, with the new driver type and the address. It looks as follows:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-Dockerfile" data-lang="Dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># docker-compose-with-efs.yml&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>version: &lt;span class="s2">&amp;#34;3.7&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>services:&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> app:&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> build: .&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> ports:&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> - &lt;span class="s2">&amp;#34;5000:5000&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> redis:&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> image: &lt;span class="s2">&amp;#34;redis:alpine&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> volumes:&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> - &lt;span class="s2">&amp;#34;redis-efs:/data&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>volumes:&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> redis-efs:&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> driver: local&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> driver_opts:&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> type: nfs&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> o: &lt;span class="nv">addr&lt;/span>&lt;span class="o">=&lt;/span>fs-1224ea45.efs.us-east-1.amazonaws.com,nfsvers&lt;span class="o">=&lt;/span>4.1,rsize&lt;span class="o">=&lt;/span>1048576,wsize&lt;span class="o">=&lt;/span>1048576,hard,timeo&lt;span class="o">=&lt;/span>600,retrans&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> device: &lt;span class="s2">&amp;#34;fs-1224ea45.efs.us-east-1.amazonaws.com:/&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>When we run our app now, the Redis data volume is &lt;em>persistent&lt;/em>. It will move with it to whatever node Redis runs on.&lt;/p>
&lt;p>&lt;img src="https://devopsian.net/p/how-to-share-persistent-storage-volumes-in-swarm/swarm-efs-c.png"
width="631"
height="515"
srcset="https://devopsian.net/p/how-to-share-persistent-storage-volumes-in-swarm/swarm-efs-c_hu36f1503b147abd00be6db3e25351f7eb_55500_480x0_resize_box_3.png 480w, https://devopsian.net/p/how-to-share-persistent-storage-volumes-in-swarm/swarm-efs-c_hu36f1503b147abd00be6db3e25351f7eb_55500_1024x0_resize_box_3.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="122"
data-flex-basis="294px"
>&lt;/p>
&lt;p>&lt;em>This solves our original problem.&lt;/em> We now have a way to persist data across our cluster.&lt;/p>
&lt;h2 id="costs">Costs&lt;/h2>
&lt;p>This blog post uses AWS as the cloud infrastructure. When comparing EFS, the closest service AWS I could find is EBS volumes. I assume you&amp;rsquo;re familiar with it.&lt;/p>
&lt;p>You should use these pricing calculators from AWS to tailor the price for your use case.&lt;/p>
&lt;p>These are not apples to apples, but let&amp;rsquo;s do a quick price comparison between the two. We will compare a 100GB volume in the EU region.&lt;/p>
&lt;h3 id="ebshttpsawsamazoncomebspricing">&lt;strong>&lt;a class="link" href="https://aws.amazon.com/ebs/pricing/" target="_blank" rel="noopener"
>EBS&lt;/a>&lt;/strong>&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Type&lt;/th>
&lt;th style="text-align:center">Charged for&lt;/th>
&lt;th style="text-align:right">Price&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Storage&lt;/td>
&lt;td style="text-align:center">0.088$/GB * 730 * 100&lt;/td>
&lt;td style="text-align:right">$64.24&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>IOPS&lt;/td>
&lt;td style="text-align:center">3K - included&lt;/td>
&lt;td style="text-align:right">$0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Throughput&lt;/td>
&lt;td style="text-align:center">125MB/s - included&lt;/td>
&lt;td style="text-align:right">$0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>You can increase the throughput or IOPS, with additional costs.&lt;/p>
&lt;h3 id="efshttpsawsamazoncomefspricing">&lt;strong>&lt;a class="link" href="https://aws.amazon.com/efs/pricing/" target="_blank" rel="noopener"
>EFS&lt;/a>&lt;/strong>&lt;/h3>
&lt;p>With EFS, the charges are different (shocked ah?). You pay for &lt;em>&amp;ldquo;Standard Storage Class&amp;rdquo;&lt;/em> which is designed for active file system workloads.&lt;/p>
&lt;p>You pay a different price for &lt;em>&amp;ldquo;Infrequent Access Storage Class&amp;rdquo;&lt;/em> (IA) which is cost-optimized for files accessed less frequently. For this comparison, let&amp;rsquo;s estimate 50% of the data is frequently accessed.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Type&lt;/th>
&lt;th style="text-align:center">Charged for&lt;/th>
&lt;th style="text-align:right">Price&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Standard Storage&lt;/td>
&lt;td style="text-align:center">0.33$/GB monthly * 50&lt;/td>
&lt;td style="text-align:right">$16.5&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>IA Storage&lt;/td>
&lt;td style="text-align:center">0.025$/GB monthly * 50&lt;/td>
&lt;td style="text-align:right">$1.25&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Throughput&lt;/td>
&lt;td style="text-align:center">2.5MB/s included, 6.60$ per additional MB/s&lt;/td>
&lt;td style="text-align:right">$0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The throughput part is kinda &lt;strong>tricky&lt;/strong>. You get 50KB/s per Stand Storage GB (50GB * 50KB = 2.5MB/s) for &lt;em>write operations&lt;/em> and 150KB/s (50GB * 150KB = 7.5MB/s) for &lt;em>read operations&lt;/em>.&lt;/p>
&lt;p>As you see, there is a big performance impact when comparing EBS with EFS. If you need high throughput, EFS might get very expensive.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>The problem I wrote about in this blog is a known problem when running microservices on a managed cluster using an orchestration tool. As with other problems, it has more than one solution where each has its trade-offs.&lt;/p>
&lt;p>I showed that EFS is a convenient, easy solution you can use to solve the shared storage problem. Yet, it is not the right solution for everything. The performance is limited, and it can get very expensive if your app requires high throughput. In those cases, you would want to use something else.&lt;/p>
&lt;p>If you can &lt;em>compromise&lt;/em> on the &lt;em>throughput&lt;/em>, EFS becomes an attractive option to manage your cluster shared storage.&lt;/p>
&lt;p>When you define your NFS volumes inside your docker-compose file, it is part of the service definition. It takes care of the infrastructure it uses. I find this pattern a good one when dealing with microservices.
If you need to move this service to another cluster, anytime in the future, the NFS mount is one thing less you need to remember to do.&lt;/p></description></item><item><title>Docker healthcheck experiments with Go web app</title><link>https://devopsian.net/p/docker-healthcheck-experiments-with-go-web-app/</link><pubDate>Fri, 12 Feb 2021 12:52:00 +0200</pubDate><guid>https://devopsian.net/p/docker-healthcheck-experiments-with-go-web-app/</guid><description>&lt;img src="https://devopsian.net/p/docker-healthcheck-experiments-with-go-web-app/cover.jpg" alt="Featured image of post Docker healthcheck experiments with Go web app" />&lt;p>One good way to monitor your container status is to use Docker&amp;rsquo;s HEALTHCHECK feature.&lt;/p>
&lt;p>As part of testing the followed actions upon an unhealthy container, I had to experiment how this works. I wrote a small Go app that replies to &lt;code>/health&lt;/code> requests, and the status it responds is configurable.&lt;/p>
&lt;p>The webserver listens on &lt;strong>$PORT&lt;/strong> (defaults to 8080). To change its status, simply make an api call to one of the supported actions. You can either connect the container to execute it,
or if you have mapped a port on the host, you can use it.&lt;/p>
&lt;p>Supported actions are:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>/sabotage&lt;/strong> will make it respond with 500.&lt;/li>
&lt;li>&lt;strong>/timeout&lt;/strong> will make it respond after 20s.&lt;/li>
&lt;li>&lt;strong>/recover&lt;/strong> will return it back to healthy state, with 200 response code.&lt;/li>
&lt;/ul>
&lt;p>You can find the source code &lt;a class="link" href="https://github.com/canidam/docker-go-healthcheck/tree/master/" target="_blank" rel="noopener"
>here&lt;/a>.&lt;/p>
&lt;p>From the Docker &lt;a class="link" href="https://docs.docker.com/engine/reference/builder/#healthcheck" target="_blank" rel="noopener"
>docs&lt;/a> -&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">The HEALTHCHECK instruction has two forms:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> HEALTHCHECK &lt;span class="o">[&lt;/span>OPTIONS&lt;span class="o">]&lt;/span> CMD &lt;span class="nb">command&lt;/span> &lt;span class="o">(&lt;/span>check container health by running a &lt;span class="nb">command&lt;/span> inside the container&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> HEALTHCHECK NONE &lt;span class="o">(&lt;/span>disable any healthcheck inherited from the base image&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">The HEALTHCHECK instruction tells Docker how to &lt;span class="nb">test&lt;/span> a container to check that it is still working.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">This can detect cases such as a web server that is stuck in an infinite loop and unable
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">to handle new connections, even though the server process is still running.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">When a container has a healthcheck specified, it has a health status in addition to its normal status.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">This status is initially starting. Whenever a health check passes, it becomes healthy &lt;span class="o">(&lt;/span>whatever state
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">it was previously in&lt;span class="o">)&lt;/span>. After a certain number of consecutive failures, it becomes unhealthy.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The options that can appear before CMD are:&lt;/p>
&lt;pre>&lt;code>--interval=DURATION (default: 30s)
--timeout=DURATION (default: 30s)
--start-period=DURATION (default: 0s)
--retries=N (default: 3)
&lt;/code>&lt;/pre>
&lt;p>You can configure the HEALTHCHECK settings in the &lt;strong>Dockerfile&lt;/strong> or the &lt;strong>docker-compose.yml&lt;/strong>. In my example,
I use it in the Dockerfile.&lt;/p>
&lt;h2 id="demo">Demo&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>Clone the repo and build the image: &lt;code>docker build . -t go-healthchecker&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Bring up the container, wait until it gets healthy.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://github.com/canidam/docker-go-healthcheck/blob/master/media/up.gif?raw=true"
loading="lazy"
alt="Run the container"
>&lt;/p>
&lt;ol start="3">
&lt;li>To change the healthcheck response, connect to the container and update its status&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://github.com/canidam/docker-go-healthcheck/blob/master/media/sabotage.gif?raw=true"
loading="lazy"
alt="Sabotage"
>&lt;/p></description></item><item><title>How can Stackoverflow make you a better developer</title><link>https://devopsian.net/p/how-can-stackoverflow-make-you-a-better-developer/</link><pubDate>Fri, 05 Feb 2021 15:30:00 +0200</pubDate><guid>https://devopsian.net/p/how-can-stackoverflow-make-you-a-better-developer/</guid><description>&lt;img src="https://devopsian.net/p/how-can-stackoverflow-make-you-a-better-developer/cover.jpg" alt="Featured image of post How can Stackoverflow make you a better developer" />&lt;blockquote>
&lt;p>&amp;ldquo;Time is the most valuable resource because you cannot get more of it.&amp;rdquo;&lt;/p>
&lt;/blockquote>
&lt;p>There isn&amp;rsquo;t a single developer who doesn&amp;rsquo;t use Stackoverflow. Most of us use it daily. How many times a day have you googled an error, and reached Stackoverflow? It is such a valuable resource that improves productivity every single day. Consider the amount of debugging time it saved you. Yet, the majority of the developers I know don&amp;rsquo;t even own an account, nevertheless trying to answer other people&amp;rsquo;s questions.&lt;/p>
&lt;p>How easy and convenient it is to find a solution to the problem you faced. In this post, I&amp;rsquo;ll share with you the benefits I found of being a contributor on Stackoverflow.&lt;/p>
&lt;p>Most of the experienced developers will probably stop reading right now. Individuals look to improve their coding and problem-solving skills, I hope you will own an account (and contribute) after reading this post.&lt;/p>
&lt;p>If you&amp;rsquo;re not familiar with where it started, the site was introduced by Jeff Atwood on his blog &lt;a class="link" href="https://blog.codinghorror.com/introducing-stackoverflow-com" target="_blank" rel="noopener"
>codinghorror&lt;/a>:&lt;/p>
&lt;blockquote>
&lt;p>&amp;ldquo;Stackoverflow is sort of like the anti-experts-exchange (minus the nausea-inducing sleaze and quasi-legal search engine gaming) meets Wikipedia meets programming Reddit. It is by programmers, for programmers, with the ultimate intent of collectively increasing the sum total of good programming knowledge in the world. No matter what programming language you use, or what operating system you call home. Better programming is our goal.&amp;rdquo;&lt;/p>
&lt;/blockquote>
&lt;h2 id="learn-something-new">Learn something new&lt;/h2>
&lt;p>When I started learning Python about five years ago, I built my curriculum that was made of few books and online courses. I put the time and effort (about four months) to practice. As most of us, when I had faced challenges I couldn&amp;rsquo;t solve by myself, I used Google and found an answer on .. you guessed it right, Stackoverflow. After a couple of iterations of this scenario, I thought to myself,&lt;/p>
&lt;p>&lt;em>&amp;ldquo;If I find answers to my questions on Stackoverflow, let&amp;rsquo;s take a peek at the most upvoted questions tagged with Python and see what it reveals&amp;rdquo;.&lt;/em>&lt;/p>
&lt;p>It was like a &lt;strong>goldmine&lt;/strong> for me. Clear, refined questions with detailed answers and examples, for free. No subscription, no money-back guarantee, plain simple. I started checking all of them, reading thoroughly all the answers to nail the subject in question. These are practical questions with high-quality answers from experienced professionals.&lt;/p>
&lt;p>Next time you plan to learn something new, give it a try. Go to Stackoverflow and look for the most upvoted questions and answers. If you haven&amp;rsquo;t ever done it before, you&amp;rsquo;re gonna be amazed.&lt;/p>
&lt;p>I then challenged myself. I pulled my sleeves and decided I will try to &lt;em>answer questions.&lt;/em>&lt;/p>
&lt;h2 id="why-would-i-spend-my-precious-time-solving-other-peoples-problems">Why would I spend my precious time solving other people&amp;rsquo;s problems?&lt;/h2>
&lt;p>That&amp;rsquo;s a damn good question. When I took the challenge, I was &lt;strong>selfish&lt;/strong>. I wanted to gain confidence in my knowledge. I liked to solve problems using the things I have learned. I looked for new questions that I had a solution for and tried to help using the gained knowledge. I did this to improve myself as a &lt;strong>programmer&lt;/strong>.&lt;/p>
&lt;p>I feel the time I spent, was paid back double. It made me a better programmer, problem-solver, and contributed to my professional career.&lt;/p>
&lt;blockquote>
&lt;p>&amp;ldquo;Knowledge is like money: to be of value it must circulate, and in circulating it can increase in quality and, hopefully, in value&amp;rdquo; &amp;ndash; &lt;strong>Louis L&amp;rsquo;Amour&lt;/strong>&lt;/p>
&lt;/blockquote>
&lt;h2 id="why-you-should-contribute">Why YOU should contribute&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;em>&lt;strong>Improve your coding and problem-solving skills&lt;/strong>&lt;/em>: I can&amp;rsquo;t stress enough the value of spending time on this site, reading through popular questions, and try to solve them yourself. Answering other people&amp;rsquo;s questions strengthens your knowledge and confidence in the topic&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;em>&lt;strong>Improve your debugging capabilities&lt;/strong>&lt;/em>: It&amp;rsquo;s one of the most valuable skills of a software engineer. By helping other people, you practice debugging their code, which is harder than debugging your own&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;em>&lt;strong>If you don&amp;rsquo;t find the question you&amp;rsquo;re looking for - ask&lt;/strong>&lt;/em>: Let the community help, and help other people in the future who encounters the same problem as you did&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;em>Learn something new, solve a new problem, dig deeper into a topic you&amp;rsquo;re familiar with, and expand your knowledge&lt;/em>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;em>Use your Stackoverflow profile to promote yourself. By accurately answering questions, you demonstrate your experience in a topic&lt;/em>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="where-to-start">Where to start?&lt;/h2>
&lt;p>This blog post aimed to get you started. First and foremost, if you don&amp;rsquo;t have an account, &lt;strong>sign up&lt;/strong>.&lt;/p>
&lt;p>I &lt;strong>recommend&lt;/strong> checking these two threads, &lt;a class="link" href="https://meta.stackoverflow.com/questions/318295/how-do-i-contribute-to-stack-overflow" target="_blank" rel="noopener"
>“How do I contribute to Stackoverflow”&lt;/a> and &lt;a class="link" href="https://meta.stackoverflow.com/questions/252149/how-does-a-new-user-get-started-on-stack-overflow" target="_blank" rel="noopener"
>“How does a new user get started”&lt;/a>. Spend the time reading to understand how this platform works and how to use it.&lt;/p>
&lt;p>Either you&amp;rsquo;re learning something new, or want to improve any of your coding skills, try to participate and answer questions:&lt;/p>
&lt;ul>
&lt;li>Search for the tags of interest to see only these kinds of questions (e.g, python, go, linux)&lt;/li>
&lt;li>Spend ~30-60min a day for 14 days. It doesn&amp;rsquo;t need to be contiguous, you can check it while you&amp;rsquo;re waiting for the compilation or deployment to finish&lt;/li>
&lt;li>Make it a &lt;strong>habit&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>That&amp;rsquo;s all it takes.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>I found that actively participating on Stackoverflow of great value, for my development skills and career. Every beginner and experienced developers alike will benefit from it if used correctly. I tried to share my own experience, what works for me, and I encourage you to share your knowledge. And have fun.&lt;/p>
&lt;p>&lt;em>Stay humble, be kind.&lt;/em>&lt;/p></description></item><item><title>nginx config template with environment vars</title><link>https://devopsian.net/p/nginx-config-template-with-environment-vars/</link><pubDate>Fri, 29 Jan 2021 11:53:00 +0200</pubDate><guid>https://devopsian.net/p/nginx-config-template-with-environment-vars/</guid><description>&lt;p>Until nginx v1.19, you had to either bake in the image a ready configuration file or mount it at runtime.&lt;/p>
&lt;p>Often, you would need to template the nginx config file. This was made possible since v1.19 in a convenient way using environment variables.&lt;/p>
&lt;p>First, you need a template to copy. Create &lt;em>default.conf.template&lt;/em> file:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">server &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> listen 80&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> server_name &lt;span class="si">${&lt;/span>&lt;span class="nv">NGINX_PORT&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location / &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> proxy_set_header Host &lt;span class="nv">$host&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> proxy_set_header X-Real-IP &lt;span class="nv">$remote_addr&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> root /usr/share/nginx/html&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> index index.html index.htm&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>In the Dockerfile, we need to place the *.template file in a specific location, &lt;em>/etc/nginx/templates&lt;/em>.&lt;/p>
&lt;p>On startup, the nginx entrypoint script scans this directory for files with *&lt;em>.template&lt;/em> suffix by default, and it runs &lt;code>envsubst&lt;/code>.
The envsubst parse the template using the shell interpolation and replaces shell variables with values from environment variables.
It outputs to a file in &lt;code>/etc/nginx/conf.d/&lt;/code>.&lt;/p>
&lt;p>If you&amp;rsquo;re using &lt;code>$var&lt;/code>, and there&amp;rsquo;s no such env-var, it will stay as is in the output file.
In the above file, &lt;code>$host&lt;/code> and &lt;code>$remote_addr&lt;/code> are such examples. We want them to stay as parameters in the output file, as they are parameters used by nginx.&lt;/p>
&lt;p>Let&amp;rsquo;s look at a complete example.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># docker-compose.yml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">version: &lt;span class="s1">&amp;#39;3.7&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">services:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> nginx:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> build:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> context: nginx/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ports:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="s2">&amp;#34;80:80&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> environment:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - NGINX_HOST
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Dockerfile&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">FROM nginx:1.19.6-alpine
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RUN mkdir /etc/nginx/templates
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">COPY default.conf.template /etc/nginx/templates
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">COPY index.html /usr/share/nginx/html/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Running the docker-compose with &lt;code>NGINX_HOST=1.1.1.1 docker-compose up -d&lt;/code> and the generated &lt;code>default.conf&lt;/code> would look:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">server &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> listen 80&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> server_name &lt;span class="s2">&amp;#34;1.1.1.1&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ..
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>If you&amp;rsquo;re interested, these are the internal scripts nginx uses in its image at startup:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#!/bin/sh
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>&lt;span class="c1"># docker-entrypoint.sh&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">set&lt;/span> -e
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="o">[&lt;/span> -z &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">NGINX_ENTRYPOINT_QUIET_LOGS&lt;/span>&lt;span class="k">:-&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="o">]&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">then&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">exec&lt;/span> 3&amp;gt;&lt;span class="p">&amp;amp;&lt;/span>&lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">exec&lt;/span> 3&amp;gt;/dev/null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="o">[&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$1&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;nginx&amp;#34;&lt;/span> -o &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$1&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;nginx-debug&amp;#34;&lt;/span> &lt;span class="o">]&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">then&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> /usr/bin/find &lt;span class="s2">&amp;#34;/docker-entrypoint.d/&amp;#34;&lt;/span> -mindepth &lt;span class="m">1&lt;/span> -maxdepth &lt;span class="m">1&lt;/span> -type f -print -quit 2&amp;gt;/dev/null &lt;span class="p">|&lt;/span> &lt;span class="nb">read&lt;/span> v&lt;span class="p">;&lt;/span> &lt;span class="k">then&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">echo&lt;/span> &amp;gt;&lt;span class="p">&amp;amp;&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$0&lt;/span>&lt;span class="s2">: /docker-entrypoint.d/ is not empty, will attempt to perform configuration&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">echo&lt;/span> &amp;gt;&lt;span class="p">&amp;amp;&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$0&lt;/span>&lt;span class="s2">: Looking for shell scripts in /docker-entrypoint.d/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> find &lt;span class="s2">&amp;#34;/docker-entrypoint.d/&amp;#34;&lt;/span> -follow -type f -print &lt;span class="p">|&lt;/span> sort -n &lt;span class="p">|&lt;/span> &lt;span class="k">while&lt;/span> &lt;span class="nb">read&lt;/span> -r f&lt;span class="p">;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> in
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> *.sh&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="o">[&lt;/span> -x &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="o">]&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">then&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">echo&lt;/span> &amp;gt;&lt;span class="p">&amp;amp;&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$0&lt;/span>&lt;span class="s2">: Launching &lt;/span>&lt;span class="nv">$f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># warn on shell scripts without exec bit&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">echo&lt;/span> &amp;gt;&lt;span class="p">&amp;amp;&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$0&lt;/span>&lt;span class="s2">: Ignoring &lt;/span>&lt;span class="nv">$f&lt;/span>&lt;span class="s2">, not executable&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">;;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> *&lt;span class="o">)&lt;/span> &lt;span class="nb">echo&lt;/span> &amp;gt;&lt;span class="p">&amp;amp;&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$0&lt;/span>&lt;span class="s2">: Ignoring &lt;/span>&lt;span class="nv">$f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">;;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">esac&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">echo&lt;/span> &amp;gt;&lt;span class="p">&amp;amp;&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$0&lt;/span>&lt;span class="s2">: Configuration complete; ready for start up&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">echo&lt;/span> &amp;gt;&lt;span class="p">&amp;amp;&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$0&lt;/span>&lt;span class="s2">: No files found in /docker-entrypoint.d/, skipping configuration&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">exec&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$@&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#!/bin/sh
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>&lt;span class="c1"># docker-entrypoint.d/20-envsubst-on-templates.sh&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">set&lt;/span> -e
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">ME&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>basename &lt;span class="nv">$0&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">auto_envsubst&lt;span class="o">()&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">local&lt;/span> &lt;span class="nv">template_dir&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">NGINX_ENVSUBST_TEMPLATE_DIR&lt;/span>&lt;span class="k">:-&lt;/span>&lt;span class="p">/etc/nginx/templates&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">local&lt;/span> &lt;span class="nv">suffix&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">NGINX_ENVSUBST_TEMPLATE_SUFFIX&lt;/span>&lt;span class="k">:-&lt;/span>&lt;span class="p">.template&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">local&lt;/span> &lt;span class="nv">output_dir&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">NGINX_ENVSUBST_OUTPUT_DIR&lt;/span>&lt;span class="k">:-&lt;/span>&lt;span class="p">/etc/nginx/conf.d&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">local&lt;/span> template defined_envs relative_path output_path subdir
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">defined_envs&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>&lt;span class="nb">printf&lt;/span> &lt;span class="s1">&amp;#39;${%s} &amp;#39;&lt;/span> &lt;span class="k">$(&lt;/span>env &lt;span class="p">|&lt;/span> cut -d&lt;span class="o">=&lt;/span> -f1&lt;span class="k">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">[&lt;/span> -d &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$template_dir&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="o">]&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="o">[&lt;/span> ! -w &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$output_dir&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="o">]&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">then&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">echo&lt;/span> &amp;gt;&lt;span class="p">&amp;amp;&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$ME&lt;/span>&lt;span class="s2">: ERROR: &lt;/span>&lt;span class="nv">$template_dir&lt;/span>&lt;span class="s2"> exists, but &lt;/span>&lt;span class="nv">$output_dir&lt;/span>&lt;span class="s2"> is not writable&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> find &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$template_dir&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> -follow -type f -name &lt;span class="s2">&amp;#34;*&lt;/span>&lt;span class="nv">$suffix&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> -print &lt;span class="p">|&lt;/span> &lt;span class="k">while&lt;/span> &lt;span class="nb">read&lt;/span> -r template&lt;span class="p">;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">relative_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">template&lt;/span>&lt;span class="p">#&lt;/span>&lt;span class="nv">$template_dir&lt;/span>&lt;span class="p">/&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">output_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$output_dir&lt;/span>&lt;span class="s2">/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">relative_path&lt;/span>&lt;span class="p">%&lt;/span>&lt;span class="nv">$suffix&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">subdir&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>dirname &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$relative_path&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># create a subdirectory where the template file exists&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mkdir -p &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$output_dir&lt;/span>&lt;span class="s2">/&lt;/span>&lt;span class="nv">$subdir&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">echo&lt;/span> &amp;gt;&lt;span class="p">&amp;amp;&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$ME&lt;/span>&lt;span class="s2">: Running envsubst on &lt;/span>&lt;span class="nv">$template&lt;/span>&lt;span class="s2"> to &lt;/span>&lt;span class="nv">$output_path&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> envsubst &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$defined_envs&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &amp;lt; &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$template&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &amp;gt; &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$output_path&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">auto_envsubst
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">exit&lt;/span> &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="resources">Resources&lt;/h2>
&lt;p>&lt;a class="link" href="https://github.com/docker-library/docs/tree/master/nginx#using-environment-variables-in-nginx-configuration-new-in-119" target="_blank" rel="noopener"
>https://github.com/docker-library/docs/tree/master/nginx#using-environment-variables-in-nginx-configuration-new-in-119&lt;/a>&lt;/p></description></item><item><title>If you don't use a secret management tool, you're doing it wrong</title><link>https://devopsian.net/p/if-you-dont-use-a-secret-management-tool-youre-doing-it-wrong/</link><pubDate>Wed, 27 Jan 2021 21:58:00 +0200</pubDate><guid>https://devopsian.net/p/if-you-dont-use-a-secret-management-tool-youre-doing-it-wrong/</guid><description>&lt;img src="https://devopsian.net/p/if-you-dont-use-a-secret-management-tool-youre-doing-it-wrong/cover.jpg" alt="Featured image of post If you don't use a secret management tool, you're doing it wrong" />&lt;p>Secrets management refers to the tools and methods for managing digital authentication credentials (secrets), including passwords, keys, APIs, and tokens for use in applications, services, privileged accounts, and other sensitive parts of the IT ecosystem.&lt;/p>
&lt;p>While secrets management is applicable across an entire enterprise, the terms secrets and secrets management are referred to more commonly in IT about DevOps environments, tools, and processes.&lt;/p>
&lt;blockquote>
&lt;p>“Three may keep a secret, if two of them are dead.”
― Benjamin Franklin, Poor Richard&amp;rsquo;s Almanack&lt;/p>
&lt;/blockquote>
&lt;p>Managing and sharing secrets is a complicated task. There are various environments, with many services, where each needs to authenticate itself.&lt;/p>
&lt;p>If you&amp;rsquo;re working in the Operations team, you clearly faced a secret committed to a git repository at one point. Even if the repository is private, this is a big no-no. If someone accidentally changes the repository to be public, or push a file with secrets to a public repository, there&amp;rsquo;s no turning back. The secret will be out there in the wild.&lt;/p>
&lt;p>Before I proceed, I want to ask you few questions:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Where does your organization keep its sensitive data? Is it in an encrypted file? A database? A shared tool such as 1Password?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Who can access it?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>How easy is it to add a new secret, or update an existing one?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Do you manage your secrets or they manage you?&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="hashicorp-vault">Hashicorp Vault&lt;/h2>
&lt;p>Hashicorp Vault is an open-source robust secret management. It serves as a secret repository with access control lists, auditing, and TTL access to the secrets. It also supports a variety of authentication mechanisms and storage backends.&lt;/p>
&lt;p>Vault keeps your secrets encrypted on disk and on transit. It has a simple API to communicate with and great documentation. The website presents two main use-cases:&lt;/p>
&lt;p>&lt;strong>Secrets Management&lt;/strong>: centrally store, access, and distribute dynamic secrets such as tokens, passwords, certificates, encryption keys.&lt;/p>
&lt;p>&lt;strong>Data Protection&lt;/strong>: keep application data secure with centralized key management and simple APIs for data encryption.&lt;/p>
&lt;p>It gives you a central place to safely store secrets. Define who can access what, with an audit on the operations. Use short-lived tokens to reduce the impact in case of secrets gets exposed. Has great APIs you can leverage to automate processes.&lt;/p>
&lt;p>Vault takes their business field seriously. Its architecture is a little complex, so let&amp;rsquo;s quickly overview the components and explain them in simple terms.&lt;/p>
&lt;h3 id="secrets-paths">&lt;em>&lt;strong>Secrets Paths&lt;/strong>&lt;/em>&lt;/h3>
&lt;p>Inside Vault, secrets kept on a file-system like path. Every secret is structured as a JSON object, with key-value pairs. These files are versioned and keep their history (refer to KV secrets engine)&lt;/p>
&lt;h3 id="policies">&lt;em>&lt;strong>Policies&lt;/strong>&lt;/em>&lt;/h3>
&lt;p>Vault uses policies to define fine-grained access-control. A policy is made of a list of paths (with regex support). A policy is then attached to the token and determines what secrets a token can access and what actions it can perform (create, read, update, delete).&lt;/p>
&lt;h3 id="authentication-and-authorization">&lt;em>&lt;strong>Authentication and authorization&lt;/strong>&lt;/em>&lt;/h3>
&lt;p>We use the term authentication to refer to the first phase of the login process. For example, the user provides the correct username and password to authenticate. If the credentials match, the user is authenticated.&lt;/p>
&lt;p>The authorization part comes next. It defines what an authenticated user has access to.&lt;/p>
&lt;p>To perform operations on the Vault, a user needs to authenticate first.
Vault refers to this as &lt;em>authentication backends&lt;/em>.
It is a list of &lt;a class="link" href="https://www.vaultproject.io/docs/auth" target="_blank" rel="noopener"
>authentication methods&lt;/a> a user can use to authenticate.&lt;/p>
&lt;p>The simplest option is a token, where you perform the login operation using a pre-defined token to gain access. This is a bad idea. There are better options.&lt;/p>
&lt;p>Vault has &lt;strong>SSO (OIDC)&lt;/strong> integration. People in the organization can access using their AzureAD or Google accounts. Once you set it up, everyone that has an account can access Vault! You don&amp;rsquo;t need to manage credentials for every person individually.&lt;/p>
&lt;p>Once the user logs in, the policies applied to his account or groups in the organization are applied to his token. This defines what he can do inside Vault.&lt;/p>
&lt;h3 id="the-flow">&lt;em>&lt;strong>The Flow&lt;/strong>&lt;/em>&lt;/h3>
&lt;p>Except for token-based authentication, all other methods work the same way. You first provide your credentials (username/password, SSO, etc.) to perform a login action. In exchange, Vault returns a &lt;em>temporary token&lt;/em> for you to use in subsequent API calls. The temporary token is defined by its attributes - lifetime, is it renewable, etc.&lt;/p>
&lt;p>When using the UI or the CLI, these things happen behind the scene. Yet, if you&amp;rsquo;re using the HTTP API, you&amp;rsquo;ll need to perform login action and grab the token returned by Vault.&lt;/p>
&lt;p>This is a new era. Developers can now read and add secrets by themselves and share these with Operations seamlessly. This increases the velocity of both teams and improving the security as a side-effect. For example, when an employee leaves the company, his access is revoked easily. Or the usage of short lived tokens instead of static passwords.&lt;/p>
&lt;h3 id="cicd-pipelines">&lt;em>&lt;strong>CI/CD pipelines&lt;/strong>&lt;/em>&lt;/h3>
&lt;p>The deployment platform should also access Vault. During the &lt;em>build&lt;/em> and &lt;em>deployment&lt;/em> processes, the platform can get the secrets using the &lt;a class="link" href="https://www.vaultproject.io/docs/auth/approle" target="_blank" rel="noopener"
>approle&lt;/a> authentication method. The approle method gives you the option to configure applications access type. This type should have a short time-to-live so it&amp;rsquo;s useless after the deployment process.&lt;/p>
&lt;p>We can take it a step further. Now that we have a centralized secrets store with an API, we can build a Jenkins job that generates secrets and push them to the Vault. This formalizes the procedure and prevents humble human mistakes, such as weak passwords.&lt;/p>
&lt;h3 id="increase-teams-productivity">&lt;em>&lt;strong>Increase teams productivity&lt;/strong>&lt;/em>&lt;/h3>
&lt;p>As the developers&amp;rsquo; team grows, without an appropriate tool time is going to be wasted here. Nobody likes to wait to test something just because they don&amp;rsquo;t have the password yet. How much time is spent here? This can be solved by.. using the right tool for the job.&lt;/p>
&lt;p>Having an interface to read and add secrets would remove the toil of secrets maintenance.&lt;/p>
&lt;h2 id="summary">&lt;em>&lt;strong>Summary&lt;/strong>&lt;/em>&lt;/h2>
&lt;p>Using a secrets management tool has numerous benefits, but I presume most of them are unseen. On the surface, it eases the process of adding or updating a secret and managing a unified access to them, by both Dev and Ops. Replacing a secret becomes an easy task for Operations.&lt;/p>
&lt;p>It provides all this while keeping high-security standards and auditing. It presents an API to automate the daunting toil of managing them yourself using a less sophisticated mechanism such as files or a database.&lt;/p>
&lt;p>It takes your secrets to the next level, and I haven&amp;rsquo;t touched the more advanced capabilities this tool has (dynamic secrets and AWS authentication method, for example). If you are intrigued, go check the documentation.&lt;/p>
&lt;p>&lt;em>The new capabilities introduced to automate processes shouldn&amp;rsquo;t be considered lightly.&lt;/em>&lt;/p>
&lt;p>How do you manage your secrets?&lt;/p></description></item><item><title>Go's method receiver: Pointer vs Value</title><link>https://devopsian.net/p/gos-method-receiver-pointer-vs-value/</link><pubDate>Sun, 17 Feb 2019 15:10:37 +0200</pubDate><guid>https://devopsian.net/p/gos-method-receiver-pointer-vs-value/</guid><description>&lt;img src="https://devopsian.net/p/gos-method-receiver-pointer-vs-value/cover.png" alt="Featured image of post Go's method receiver: Pointer vs Value" />&lt;p>&lt;em>The topic is large, and there is plenty of information online. This blog tries to keep it short and concise, and useful for experienced programmers that are new to Go.&lt;/em>&lt;/p>
&lt;p>Coming to Go from Python introduced me to a new concept I didn&amp;rsquo;t have to put thought into.
Python is a &lt;em>pass-by-object-reference&lt;/em> language. You have no direct control over that.
What this means is, when you pass an object (everything in Python is an object) to a function,
you pass a reference to the object itself.&lt;/p>
&lt;p>We can use the &lt;code>id()&lt;/code> function to illustrate that. It returns the identity of an object. This identity has to be unique and constant for this object during its lifetime.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">action_with_string&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">...&lt;/span> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">id&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">v&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;sample string&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="nb">id&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="mi">4360321520&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">action_with_string&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="mi">4360321520&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The object inside a function is as same as the one of the caller.
Whatever you pass, the function can mutate (as long as it is mutable). It is discussed here on &lt;a class="link" href="https://stackoverflow.com/questions/13299427/python-functions-call-by-reference" target="_blank" rel="noopener"
>StackOverflow: Python functions call by reference&lt;/a> if you are interested in reading further.&lt;/p>
&lt;p>In Go, when you define a method on a struct, you have to choose the receiver type - &lt;em>value&lt;/em> or a &lt;em>pointer&lt;/em> receiver.&lt;/p>
&lt;h2 id="what-does-this-means-anyway">What does this means, anyway?&lt;/h2>
&lt;p>In simple terms, the &lt;strong>value receiver&lt;/strong> makes a copy of the type and pass it to the function. The function stack now holds an equal object but at a different location on memory. If the method mutates the struct, it wouldn&amp;rsquo;t be reflected outside the function scope.&lt;/p>
&lt;p>The &lt;strong>pointer receiver&lt;/strong> passes the address of an object to the method. The function stack has a reference to the original object.&lt;/p>
&lt;p>A simple example shows the difference. (&lt;a class="link" href="https://play.golang.org/p/5TvO6YCIH5O" target="_blank" rel="noopener"
>Go Playground link&lt;/a>)&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">Bike&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Model&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Size&lt;/span> &lt;span class="kt">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">ValueReceiverExample&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">b&lt;/span> &lt;span class="nx">Bike&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;VALUE :: The address of the received bike is: %p\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Address of the object is different than the ones in main.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// Changes to the object are scoped to the method, and not reflected to the caller.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Model&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;BMW&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;INSIDE ValueReceiverExample :: model: &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Model&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">PointerReciverExample&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">b&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Bike&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;POINTER :: The address of the received bike is: %p\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Address of the object is as same as the ones in main.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Model&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;BMW&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;INSIDE PointerReciverExample :: model: &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Model&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">HondaBikeValue&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">Bike&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#34;Honda CBR&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">650&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">SuzukiBikePointer&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">Bike&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#34;Suzuki V-Storm&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">650&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Value object address in main: %p\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">HondaBikeValue&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Pointer object address in main: %p\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">SuzukiBikePointer&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">ValueReceiverExample&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">HondaBikeValue&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">PointerReciverExample&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">SuzukiBikePointer&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Value&amp;#39;s model in main: &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">HondaBikeValue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Model&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Pointer&amp;#39;s model in main: &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">SuzukiBikePointer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Model&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">OUTPUT&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Value&lt;/span> &lt;span class="nx">object&lt;/span> &lt;span class="nx">address&lt;/span> &lt;span class="nx">in&lt;/span> &lt;span class="nx">main&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">0xc00000c0a0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Pointer&lt;/span> &lt;span class="nx">object&lt;/span> &lt;span class="nx">address&lt;/span> &lt;span class="nx">in&lt;/span> &lt;span class="nx">main&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">0xc00000c0c0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">VALUE&lt;/span> &lt;span class="p">::&lt;/span> &lt;span class="nx">The&lt;/span> &lt;span class="nx">address&lt;/span> &lt;span class="nx">of&lt;/span> &lt;span class="nx">the&lt;/span> &lt;span class="nx">received&lt;/span> &lt;span class="nx">bike&lt;/span> &lt;span class="nx">is&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">0xc00000c0e0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">INSIDE&lt;/span> &lt;span class="nx">ValueReceiverExample&lt;/span> &lt;span class="p">::&lt;/span> &lt;span class="nx">model&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">BMW&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">POINTER&lt;/span> &lt;span class="p">::&lt;/span> &lt;span class="nx">The&lt;/span> &lt;span class="nx">address&lt;/span> &lt;span class="nx">of&lt;/span> &lt;span class="nx">the&lt;/span> &lt;span class="nx">received&lt;/span> &lt;span class="nx">bike&lt;/span> &lt;span class="nx">is&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">0xc00000c0c0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">INSIDE&lt;/span> &lt;span class="nx">PointerReciverExample&lt;/span> &lt;span class="p">::&lt;/span> &lt;span class="nx">model&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">BMW&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Value&lt;/span>&lt;span class="err">&amp;#39;&lt;/span>&lt;span class="nx">s&lt;/span> &lt;span class="nx">model&lt;/span> &lt;span class="nx">in&lt;/span> &lt;span class="nx">main&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">Honda&lt;/span> &lt;span class="nx">CBR&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Pointer&lt;/span>&lt;span class="err">&amp;#39;&lt;/span>&lt;span class="nx">s&lt;/span> &lt;span class="nx">model&lt;/span> &lt;span class="nx">in&lt;/span> &lt;span class="nx">main&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">BMW&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>So when should you use what?&lt;/p>
&lt;p>Regardless of what you choose, it is best practice to keep a uniformity of the struct methods. If the struct uses both types, it is hard to track which method use which receiver type, especially if you weren&amp;rsquo;t the one who wrote the code. Consistency is an important aspect of programming.&lt;/p>
&lt;h3 id="pointers">Pointers&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>If you want to share a value with its methods&lt;/p>
&lt;p>If the method modifies the state of the struct, you must use a pointer. Value receiver changes are local to the method scope.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>If the struct is very large (optimization)&lt;/p>
&lt;p>Large structs with many fields may be costly to copy every time they need to be passed around.
If you are in such a case, you should consider breaking it down into smaller pieces. If that&amp;rsquo;s not possible, use a pointer.
Optimization usually adds complexity. Be aware it comes with a trade-off.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>One major disadvantage of pointers is they are not safe for concurrency.
If you use pointers, you need to use synchronous mechanisms such as channels or use the atomic / sync builtin packages.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="values">Values&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>If you don&amp;rsquo;t want to share a value, use the value receiver.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Value receivers are concurrency safe. One of Go&amp;rsquo;s advantages is concurrency, so this is a huge plus.
You never know when you would need it, and if you write library code you can be sure someone at some point will use it concurrently.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>That was an interesting subject for me. It is a new concept I didn&amp;rsquo;t deal with before coding in Go.
After researching the subject thru blogs, documentation, videos, and Stackoverflow, I have come up with a small set of rules to remember:&lt;/p>
&lt;ol>
&lt;li>Consistency counts. Use the same receiver type for all your methods. It isn&amp;rsquo;t always feasible, but have this rule in mind when you need to mix them.&lt;/li>
&lt;li>Method defines the behavior of a type; if the method updates or modifies the state, use the pointer receiver.&lt;/li>
&lt;li>If a method doesn&amp;rsquo;t mutate state, use-value receiver.&lt;/li>
&lt;li>Functions operate on values; functions should not depend on the state of a type.&lt;/li>
&lt;/ol>
&lt;p>Here&amp;rsquo;s a list of great resources if you want to explore deeper:&lt;/p>
&lt;ul>
&lt;li>&lt;a class="link" href="https://golang.org/doc/faq#different_method_sets" target="_blank" rel="noopener"
>T and T* have different method sets&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://www.youtube.com/watch?v=29LLRKIL_TI" target="_blank" rel="noopener"
>Steve Francia “7 common mistakes in Go” talk on YouTube&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://github.com/golang/go/wiki/CodeReviewComments#receiver-type" target="_blank" rel="noopener"
>Comments from Go Code Review team&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://medium.com/gophersland/gopher-vs-object-oriented-golang-4fa62b88c701" target="_blank" rel="noopener"
>Gopher vs Object Oriented&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>How to run your own docker registry with password, SSL and S3 backend</title><link>https://devopsian.net/p/how-to-run-your-own-docker-registry-with-password-ssl-and-s3-backend/</link><pubDate>Sun, 13 Jan 2019 22:43:29 +0200</pubDate><guid>https://devopsian.net/p/how-to-run-your-own-docker-registry-with-password-ssl-and-s3-backend/</guid><description>&lt;img src="https://devopsian.net/p/how-to-run-your-own-docker-registry-with-password-ssl-and-s3-backend/cover.jpg" alt="Featured image of post How to run your own docker registry with password, SSL and S3 backend" />&lt;p>I remember the day I started using Docker. The simplicity was overwhelming. I went over the official docs which are very good, and looked up some 101 tutorials to get started. I had successfully made my first image. Now that I have one,
I want to deploy it over my servers. The first option was &lt;a class="link" href="http://www.dockerhub.com" target="_blank" rel="noopener"
>DockerHub&lt;/a>, but a free account obligates you to share the images. If you need to keep them private, this service costs money.&lt;/p>
&lt;p>Or, you can just run your own &lt;em>&lt;a class="link" href="https://docs.docker.com/registry/deploying/" target="_blank" rel="noopener"
>Docker Registry&lt;/a>&lt;/em> and have the flexability to:&lt;/p>
&lt;ul>
&lt;li>Password protect it&lt;/li>
&lt;li>Store the images on locally or in the cloud (S3/Azure/GoogleCloud)&lt;/li>
&lt;li>Use your self-signed SSL certificates&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&amp;ldquo;The Registry is a stateless, highly scalable server side application that stores and lets you distribute Docker images.&amp;rdquo;&lt;/p>
&lt;/blockquote>
&lt;p>The official Docker documentation covers the subject in-depth. You should read it to get a grasp of what is it and how to run it. In this post, I address the issues I had encountered when I deployed our registry server. I&amp;rsquo;ll explain how to implement the three items mentioned above. For the rest of this post, I assume you read the &lt;a class="link" href="https://docs.docker.com/registry/" target="_blank" rel="noopener"
>Registry Overview&lt;/a> and &lt;a class="link" href="https://docs.docker.com/registry/introduction/" target="_blank" rel="noopener"
>Understanding Docker Registry&lt;/a> at the official site. (&lt;em>~3min&lt;/em>).&lt;/p>
&lt;h2 id="get-the-registry-running">Get the registry running&lt;/h2>
&lt;p>The registry app itself runs in a container. It has a front-end component (which is optional) that allows you to access the registry data from a browser. Each one of the components runs inside its own container, which forms the app stack. &lt;em>An app stack is a group of apps (stack) that is linked together to perform certain tasks.&lt;/em> We can use &lt;em>&lt;a class="link" href="https://docs.docker.com/compose/" target="_blank" rel="noopener"
>docker-compose&lt;/a>&lt;/em> to run the app stack, instead of running each container separatley.&lt;/p>
&lt;p>Here&amp;rsquo;s the docker-compose.yml file we start with:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">version: &lt;span class="s1">&amp;#39;2&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">registry:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> image: registry:v2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> restart: always
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ports:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="s2">&amp;#34;5000&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> environment:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> REGISTRY_STORAGE_DELETE_ENABLED: &lt;span class="s1">&amp;#39;true&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> REGISTRY_HTTP_ADDR: 0.0.0.0:5000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">registry-frontend:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> image: konradkleine/docker-registry-frontend:v2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> restart: always
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> environment:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ENV_DOCKER_REGISTRY_HOST: &lt;span class="s1">&amp;#39;registry&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ENV_DOCKER_REGISTRY_PORT: &lt;span class="m">5000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> links:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - registry
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ports:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="s2">&amp;#34;8080:80&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> expose:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="m">80&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Let&amp;rsquo;s briefly break this down, as its just plain configuration (without password, S3 or SSL).&lt;/p>
&lt;p>The &lt;em>registry&lt;/em> and &lt;em>registry-frontend&lt;/em> are the container names in my stack. You can name them any way you like. The environment variable &lt;em>REGISTRY_STORAGE_DELETE_ENABLED&lt;/em> allows you delete images from the registry, and &lt;em>REGISTRY_HTTP_ADDR&lt;/em> binds the listening address.&lt;/p>
&lt;p>The &lt;em>registry-frontend&lt;/em> container uses &lt;em>ENV_DOCKER_REGISTRY_HOST&lt;/em> and &lt;em>ENV_DOCKER_REGISTRY_PORT&lt;/em> as the address to which it connects. Because we use docker-compose, it is available for us by &lt;em>name&lt;/em>. The &lt;a class="link" href="https://docs.docker.com/compose/compose-file/#links" target="_blank" rel="noopener"
>links&lt;/a> configuration links the container the another service, in our case to the &lt;em>registry&lt;/em>.&lt;/p>
&lt;p>Don&amp;rsquo;t you wonder &lt;em>what&amp;rsquo;s the difference between &lt;a class="link" href="https://docs.docker.com/compose/compose-file/#/ports" target="_blank" rel="noopener"
>ports&lt;/a> and &lt;a class="link" href="https://docs.docker.com/compose/compose-file/#/expose" target="_blank" rel="noopener"
>expose&lt;/a> settings?&lt;/em> the difference is &lt;em>ports&lt;/em> make the ports defined accessible to the host as well as other services in the compose file, whereas &lt;em>expose&lt;/em> actually expose the ports only to other services &lt;strong>without&lt;/strong> publishing them to the host machine. Only the internal port can be specified in such case.&lt;/p>
&lt;p>&lt;em>Ports&lt;/em> also &lt;strong>links&lt;/strong> between an external port (defined on the host) to a container assigned port. In the case of the &lt;code>registry&lt;/code> container, port 5000 is accessible from everywhere. On the other hand, &lt;em>registry-frontend&lt;/em> exposes port 80 &lt;em>only&lt;/em> to other services defined in the file, while accessing it from outside is bounded to port 8080.&lt;/p>
&lt;p>To run the services, we use &lt;code>docker-compose up&lt;/code> inside the directory where the &lt;em>docker-compose.yml&lt;/em> file resides.
You can validate the services are running with &lt;code>docker ps&lt;/code>. If the containers are up, you can now browse http://localhost:8080 to see your registry data.&lt;/p>
&lt;h2 id="passwords-time">Passwords time&lt;/h2>
&lt;p>As part of having my private registry, I would like to add some measure of security. I want to password protect it.
Once it set, the client needs to provide a password when they login the registry for the first time.
This is very simple, and there is a great article covering that. Check &lt;a class="link" href="https://medium.com/@cnadeau_/private-docker-registry-part-2-lets-add-basic-authentication-6a22e5cd459b" target="_blank" rel="noopener"
>&amp;ldquo;Private Docker Registry Part 2: let’s add basic authentication&amp;rdquo; on Medium&lt;/a> and configure if you need it too.&lt;/p>
&lt;p>&lt;img src="https://devopsian.net/p/how-to-run-your-own-docker-registry-with-password-ssl-and-s3-backend/liam-tucker-491202-unsplash.jpg"
width="2413"
height="1811"
srcset="https://devopsian.net/p/how-to-run-your-own-docker-registry-with-password-ssl-and-s3-backend/liam-tucker-491202-unsplash_huf1c5470e3e1f1f2c2d5f824e3b54f1f2_222887_480x0_resize_q75_box.jpg 480w, https://devopsian.net/p/how-to-run-your-own-docker-registry-with-password-ssl-and-s3-backend/liam-tucker-491202-unsplash_huf1c5470e3e1f1f2c2d5f824e3b54f1f2_222887_1024x0_resize_q75_box.jpg 1024w"
loading="lazy"
alt="Security. Photo by Liam Tucker on Unsplash"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;h2 id="private-ssl-certificates">Private SSL certificates&lt;/h2>
&lt;p>Security is always an issue. It&amp;rsquo;s something we tend to forget when we are in the middle of POC. We want to make things work first, and fast. It requires additional steps, and we tend to leave it as the last task.&lt;/p>
&lt;p>My advice for you is - &lt;em>whenever you can use SSL, use it.&lt;/em> It&amp;rsquo;s worth the extra time you put in it, and has no drawbacks.
The docker client uses HTTPS by default, so as I see it we have 3 options here:&lt;/p>
&lt;ul>
&lt;li>Make the client use HTTP instead of HTTPS&lt;/li>
&lt;li>Configure the client to trust our registry with &lt;em>insecure-registries&lt;/em> parameter&lt;/li>
&lt;li>Add our certificate to the &lt;em>trusted ones&lt;/em>, either the docker engine or the OS&lt;/li>
&lt;/ul>
&lt;p>I find the &lt;strong>3rd option&lt;/strong> as the best solution for me. I&amp;rsquo;ll use my own certificates for my server.&lt;/p>
&lt;p>I assume you have generated the certificate (cert.crt, cert.pem and ca-certificate) for your server.
Put the public and private keys in &lt;em>/var/lib/docker/certs&lt;/em>. For the registry to be using them, we need to add the following to our configuration, under the &lt;em>environment&lt;/em> of the &lt;em>registry&lt;/em>:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl"> REGISTRY_HTTP_TLS_CERTIFICATE: /certs/registry_example.crt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> REGISTRY_HTTP_TLS_KEY: /certs/registry_example_key.pem
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> volumes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - /var/lib/docker/certs:/certs
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Ok our server side now uses our certs. Great! but we are not done yet.
In order for clients to be able to connect the registry, we need their workstations to &lt;em>trust&lt;/em> our CA. Otherwise, &lt;strong>it won&amp;rsquo;t connect.&lt;/strong> If your client machine already trust your CA certificate, you&amp;rsquo;re done. Else, you need to &lt;em>copy your CA certificate over the machine.&lt;/em> You can copy it to &lt;em>/etc/docker/certs.d&lt;/em> and docker will trust it automatically, or for the OS to trust it you need to (On Ubuntu system, it may vary on other OS):&lt;/p>
&lt;ol>
&lt;li>Copy it to /usr/local/share/ca-certificates/&lt;/li>
&lt;li>sudo update-ca-certificates&lt;/li>
&lt;/ol>
&lt;p>&lt;em>Make sure to restart the docker service on the client after you do that.&lt;/em>&lt;/p>
&lt;p>Now that our registry has basic authentication and SSL encryption support, let&amp;rsquo;s continue our journey to the final configuration step. I&amp;rsquo;ll show how you connect to the registry after I explain the use of &lt;em>S3 as a backened.&lt;/em>&lt;/p>
&lt;h2 id="s3-storage-backend">S3 storage backend&lt;/h2>
&lt;p>The registry serves our images. We can pull and push images into or from it. So, these images must reside on a disk somewhere. By default, they reside on the docker host running the registry. You can explicity mount it with a different volume, but you can do something much cooler than that. I used &lt;em>AWS S3 storage&lt;/em> services as my backend. What this means is, every image I upload to the registry is saved on a dedicated bucket on S3.&lt;/p>
&lt;p>This has the advantage of:&lt;/p>
&lt;ol>
&lt;li>I don&amp;rsquo;t need to care about backups.&lt;/li>
&lt;li>My storage is unlimited.&lt;/li>
&lt;li>Disaster recovery, in case the machine my registry runs at burns or something, I can deploy a new service and connect it to the S3 bucket. All data is preserved.&lt;/li>
&lt;/ol>
&lt;p>Re-read item 3 again. This is priceless. I can start another registry container anywhere in the world, once I configure the &lt;em>S3 storage&lt;/em> in the &lt;em>docker-compose.yml&lt;/em> file I gain the access to all my images.&lt;/p>
&lt;p>To configure storage, we need this snippet in the &lt;em>environment&lt;/em> part of the &lt;em>registry&lt;/em> component:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl"> REGISTRY_STORAGE: s3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> REGISTRY_STORAGE_S3_ACCESSKEY: &amp;lt;api access key&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> REGISTRY_STORAGE_S3_SECRETKEY: &amp;lt;api secret&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> REGISTRY_STORAGE_S3_BUCKET: &amp;lt;bucket name&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> REGISTRY_STORAGE_S3_REGION: &amp;lt;region&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> REGISTRY_HEALTH_STORAGEDRIVER_ENABLED: &lt;span class="nb">false&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>It&amp;rsquo;s pretty straight forward, but the last one. &lt;em>REGISTRY_HEALTH_STORAGEDRIVER_ENABLED&lt;/em> is important.
Before I added that I ran into problems. I don&amp;rsquo;t recall the exact error, but after googling for some time, I find out that if you run the registry with an empty bucket (mine was) this health check fails, and the service fails to start. So I had to disable it, and things worked.
&lt;em>After you upload something, you may enable this check. I decided I don&amp;rsquo;t really care about that, so I left it disabled.&lt;/em>&lt;/p>
&lt;h2 id="client-connection">Client connection&lt;/h2>
&lt;p>In order to use my new registry, I need to connect the client so it can pull or push images from/to it. Remember our registry works with SSL, and the docker client too by default, so I need to &lt;em>trust my CA, as explained [above](# Deploy private SSL certificates).&lt;/em>&lt;/p>
&lt;p>To connect, execute &lt;code>docker login -u &amp;lt;user&amp;gt; &amp;lt;url:port&amp;gt;&lt;/code> (without the https prefix).&lt;/p>
&lt;p>Now, on my Ubuntu machine I encountered another error when doing this:
&lt;em>&amp;ldquo;Error saving credentials: error storing credentials - err: exit status 1, out: Cannot autolaunch D-Bus without X11 $DISPLAY&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>To solve this, I needed to delete a package from the OS: &lt;code>apt remove golang-docker-credential-helpers&lt;/code>
made it work. After you login to the registry, by default the credentials are kept in a file inside a hidden directory &lt;em>~/.docker/config.json&lt;/em>. Obviously this isn&amp;rsquo;t best practice, you can find better alternatives &lt;a class="link" href="https://docs.docker.com/engine/reference/commandline/login/#credentials-store" target="_blank" rel="noopener"
>here.&lt;/a>&lt;/p>
&lt;h2 id="push-and-pull">Push and Pull&lt;/h2>
&lt;p>To pull or push images we simply refer to our registry it&amp;rsquo;s address, &lt;code>docker pull our-registry.com:&amp;lt;port&amp;gt;/image&lt;/code>.
You can also use the IP if you don&amp;rsquo;t own a domain.&lt;/p>
&lt;h2 id="api-to-check-registry-contents">API to check registry contents&lt;/h2>
&lt;p>I will use a fake domain &lt;code>registry.example.com:5000&lt;/code> for my registry server.
Once I had the containers running, I could browse to &lt;a class="link" href="https://registry.example.com" target="_blank" rel="noopener"
>https://registry.example.com&lt;/a> and see the content of the registry, the repositories, images and tags. If you decided not to use the front-end or you need to use it within an app, you can access the registry using it&amp;rsquo;s API.&lt;/p>
&lt;p>I&amp;rsquo;ll show some useful examples,&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># list of the repositories&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chen@gns:~$ curl -ksS -u admin https://registry.example.com/v2/_catalog
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Enter host password &lt;span class="k">for&lt;/span> user &lt;span class="s1">&amp;#39;admin&amp;#39;&lt;/span>:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;repositories&amp;#34;&lt;/span>:&lt;span class="o">[&lt;/span>&lt;span class="s2">&amp;#34;sample_image&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;nginx&amp;#34;&lt;/span>&lt;span class="o">]}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># list of tags for an image&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chen@gns:~$ curl -ksS -u admin https://registry.example.com/v2/sample_image/tags/list
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Enter host password &lt;span class="k">for&lt;/span> user &lt;span class="s1">&amp;#39;admin&amp;#39;&lt;/span>:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;sample_image&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;tags&amp;#34;&lt;/span>:&lt;span class="o">[&lt;/span>&lt;span class="s2">&amp;#34;0.1&amp;#34;&lt;/span>, &lt;span class="s2">&amp;#34;0.2&amp;#34;&lt;/span>, &lt;span class="s2">&amp;#34;latest&amp;#34;&lt;/span>&lt;span class="o">]}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>I went through the process I did to deploy my registry. I know I didn&amp;rsquo;t cover it thoroughly, but that wasn&amp;rsquo;t the purpose of this post. If you need a private registry, it is very easy to set up one. There are many tutorials how to do that.
I shared with you the issues I had faced when deploying a registry, and the configuration I had applied.&lt;/p>
&lt;p>Applying password and SSL as security item is important, &lt;em>don&amp;rsquo;t forget that&lt;/em>. If you can use a cloud provider to store your images, it can save you a lot of headache. But it&amp;rsquo;s not the end of the world if you can&amp;rsquo;t, you just need take care for the items I listed.&lt;/p>
&lt;p>Here&amp;rsquo;s how the completed &lt;code>docker-compose.yml&lt;/code> looks like:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">version: &lt;span class="s1">&amp;#39;2&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">registry:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> image: registry:v2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> restart: always
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ports:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="s2">&amp;#34;5000&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> environment:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> REGISTRY_STORAGE_DELETE_ENABLED: &lt;span class="s1">&amp;#39;true&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> REGISTRY_HTTP_ADDR: 0.0.0.0:5000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> REGISTRY_HTTP_TLS_CERTIFICATE: /certs/registry_example.crt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> REGISTRY_HTTP_TLS_KEY: /certs/registry_example_key.pem
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> REGISTRY_STORAGE: s3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> REGISTRY_STORAGE_S3_ACCESSKEY: &amp;lt;api access key&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> REGISTRY_STORAGE_S3_SECRETKEY: &amp;lt;api secret&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> REGISTRY_STORAGE_S3_BUCKET: &amp;lt;bucket name&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> REGISTRY_STORAGE_S3_REGION: &amp;lt;region&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> REGISTRY_HEALTH_STORAGEDRIVER_ENABLED: &lt;span class="nb">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> volumes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - /var/lib/docker/certs:/certs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">registry-frontend:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> image: konradkleine/docker-registry-frontend:v2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> restart: always
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> environment:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ENV_DOCKER_REGISTRY_HOST: &lt;span class="s1">&amp;#39;registry&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ENV_DOCKER_REGISTRY_PORT: &lt;span class="m">5000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> links:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - registry
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ports:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="s2">&amp;#34;8080:80&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> expose:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="m">80&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>I hope you found this one useful.&lt;/p></description></item><item><title>About</title><link>https://devopsian.net/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://devopsian.net/about/</guid><description>&lt;h2 id="who-am-i">Who am I?&lt;/h2>
&lt;p>I’m Chen, a self-taught engineer with 13+ years of experience in system administration.
This blog is my digital notebook, where I share real solutions to problems in projects, thoughts about cloud-native, software architecture, devops principles, reliability and software engineering.&lt;/p>
&lt;p>I pen down and share my challenges because someday, you might encounter them too, and finding these solutions could be just the guidance you need.&lt;/p>
&lt;h2 id="my-projects">My projects&lt;/h2>
&lt;p>Below are some projects I had worked on.&lt;/p></description></item><item><title>Search</title><link>https://devopsian.net/search/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://devopsian.net/search/</guid><description/></item></channel></rss>