<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>DevOps on WebDevStation</title>
    <link>https://webdevstation.com/categories/devops/</link>
    <description>4 articles in the DevOps category — tutorials, code examples and notes from building real systems, newest first.</description>
    <generator>Hugo</generator>
    <language>en</language>
    <managingEditor>Alex</managingEditor>
    <webMaster>Alex</webMaster>
    <copyright>© 2026 WebDevStation</copyright>
    <lastBuildDate>Tue, 11 Aug 2026 18:40:00 +0200</lastBuildDate>
    <atom:link href="https://webdevstation.com/categories/devops/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Graceful Shutdown in Go Web Services: Stop Dropping Requests on Deploy</title>
      <link>https://webdevstation.com/posts/graceful-shutdown-in-go-web-services/</link>
      <pubDate>Tue, 11 Aug 2026 18:40:00 +0200</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/graceful-shutdown-in-go-web-services/</guid>
      <description>How to shut down a Go HTTP server without dropping in-flight requests: signal.NotifyContext, Server.Shutdown, draining background workers, and the timeouts that make…</description>
      <content:encoded><![CDATA[<p>The first time I deployed a Go service behind a rolling update, our error dashboard lit up on every single release. Nothing was broken — the new version was fine, the old version was fine. The problem was the half-second in between, where the old process died mid-request and a few dozen users got a connection reset. Fixing it took about twenty lines of code, and I have copied those twenty lines into every service since.</p>
<h2 id="what-actually-happens-on-shutdown">What Actually Happens on Shutdown</h2>
<p>When your orchestrator wants a container gone, it sends <code>SIGTERM</code> and starts a countdown. If the process is still alive when the countdown ends, it gets <code>SIGKILL</code>, which cannot be caught.</p>
<p>A Go program with no signal handling takes the default action for <code>SIGTERM</code>: immediate termination. Every open connection is severed. Any request that was 90% done is simply gone — the client sees a reset, your retry budget takes the hit, and if that request was a payment you now have a support ticket.</p>
<p>Graceful shutdown means using the window between <code>SIGTERM</code> and <code>SIGKILL</code> to:</p>
<ol>
<li>Stop accepting new connections.</li>
<li>Let in-flight requests finish.</li>
<li>Drain background workers.</li>
<li>Close databases, caches and queues.</li>
<li>Exit before the countdown runs out.</li>
</ol>
<h2 id="the-twenty-lines">The Twenty Lines</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;context&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;errors&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;log/slog&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;os&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;os/signal&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;syscall&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">srv</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Server</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Addr</span>:              <span style="color:#e6db74">&#34;:8080&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Handler</span>:           <span style="color:#a6e22e">newRouter</span>(),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ReadHeaderTimeout</span>: <span style="color:#ae81ff">5</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ReadTimeout</span>:       <span style="color:#ae81ff">15</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">WriteTimeout</span>:      <span style="color:#ae81ff">30</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">IdleTimeout</span>:       <span style="color:#ae81ff">60</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>,
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// ctx is cancelled the first time we receive SIGINT or SIGTERM.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">stop</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">signal</span>.<span style="color:#a6e22e">NotifyContext</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Interrupt</span>, <span style="color:#a6e22e">syscall</span>.<span style="color:#a6e22e">SIGTERM</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">stop</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;listening&#34;</span>, <span style="color:#e6db74">&#34;addr&#34;</span>, <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">Addr</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// ListenAndServe always returns a non-nil error; ErrServerClosed</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// is the expected one after Shutdown.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">ListenAndServe</span>(); !<span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ErrServerClosed</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;listen failed&#34;</span>, <span style="color:#e6db74">&#34;err&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Exit</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">stop</span>() <span style="color:#75715e">// restore default handling: a second Ctrl-C now kills us</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;shutdown signal received, draining&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">shutdownCtx</span>, <span style="color:#a6e22e">cancel</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithTimeout</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#ae81ff">20</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">cancel</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">Shutdown</span>(<span style="color:#a6e22e">shutdownCtx</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;graceful shutdown failed, forcing close&#34;</span>, <span style="color:#e6db74">&#34;err&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">Close</span>()
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;shutdown complete&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>That is the whole pattern. A few details are load-bearing:</p>
<p><strong><code>signal.NotifyContext</code> instead of a channel.</strong> Since Go 1.16 this gives you a <code>context.Context</code> that cancels on the listed signals, which composes with everything else that already takes a context. If contexts and cancellation are new to you, <a href="/posts/understanding-golang-context/">understanding Golang context</a> is the background reading.</p>
<p><strong>Calling <code>stop()</code> after the first signal.</strong> It restores the default signal behaviour, so an impatient operator pressing Ctrl-C a second time gets an immediate exit instead of being ignored.</p>
<p><strong>A fresh context for <code>Shutdown</code>.</strong> Deriving it from <code>ctx</code> would be a bug: <code>ctx</code> is already cancelled, so <code>Shutdown</code> would return instantly and drain nothing.</p>
<p><strong>Checking for <code>ErrServerClosed</code>.</strong> <code>ListenAndServe</code> returns it on a clean shutdown. Treating that as a failure produces a scary log line on every normal deploy.</p>
<h2 id="what-shutdown-does-and-does-not-do">What Shutdown Does and Does Not Do</h2>
<p><code>Server.Shutdown</code> closes all open listeners, closes idle connections, and then waits for active ones to become idle. It returns when everything is drained or when its context expires — whichever comes first.</p>
<p>What it does <strong>not</strong> cover:</p>
<ul>
<li><strong>Hijacked connections</strong>, including WebSockets. <code>Shutdown</code> does not wait for them, and it does not close them. You have to track and close them yourself.</li>
<li><strong>Background goroutines</strong> you started outside the request path. Nothing knows about them.</li>
<li><strong>Long-polling or streaming responses.</strong> These are &ldquo;active&rdquo; for as long as they stream, so they will hold the drain open until your timeout fires.</li>
</ul>
<p>For WebSockets, the usual approach is to register a callback that broadcasts a close frame:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">RegisterOnShutdown</span>(<span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">hub</span>.<span style="color:#a6e22e">CloseAll</span>(<span style="color:#a6e22e">websocket</span>.<span style="color:#a6e22e">CloseServiceRestart</span>, <span style="color:#e6db74">&#34;server restarting&#34;</span>)
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p><code>RegisterOnShutdown</code> callbacks run in their own goroutines as soon as <code>Shutdown</code> starts, so they get the whole drain window to do their work.</p>
<h2 id="draining-background-workers-too">Draining Background Workers Too</h2>
<p>Most real services do more than serve HTTP. If you have consumers, cron loops, or a queue like the one in <a href="/posts/simple-queue-implementation-in-golang/">my simple queue implementation</a>, they need to finish too. <code>errgroup</code> keeps this readable:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;golang.org/x/sync/errgroup&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">run</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">srv</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Server</span>{<span style="color:#a6e22e">Addr</span>: <span style="color:#e6db74">&#34;:8080&#34;</span>, <span style="color:#a6e22e">Handler</span>: <span style="color:#a6e22e">newRouter</span>()}
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">queue</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">NewQueue</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">g</span>, <span style="color:#a6e22e">gCtx</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">errgroup</span>.<span style="color:#a6e22e">WithContext</span>(<span style="color:#a6e22e">ctx</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 1. Serve HTTP.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">Go</span>(<span style="color:#66d9ef">func</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">ListenAndServe</span>(); !<span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ErrServerClosed</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;http server: %w&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 2. Consume the queue until the group context is cancelled.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">Go</span>(<span style="color:#66d9ef">func</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">queue</span>.<span style="color:#a6e22e">Consume</span>(<span style="color:#a6e22e">gCtx</span>)
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 3. When anything cancels gCtx — a signal, or a failure in another</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">//    goroutine — drain the HTTP server.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">Go</span>(<span style="color:#66d9ef">func</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">gCtx</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">shutdownCtx</span>, <span style="color:#a6e22e">cancel</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithTimeout</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#ae81ff">20</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">cancel</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">Shutdown</span>(<span style="color:#a6e22e">shutdownCtx</span>)
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">Wait</span>()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">stop</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">signal</span>.<span style="color:#a6e22e">NotifyContext</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Interrupt</span>, <span style="color:#a6e22e">syscall</span>.<span style="color:#a6e22e">SIGTERM</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">stop</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">run</span>(<span style="color:#a6e22e">ctx</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;service stopped&#34;</span>, <span style="color:#e6db74">&#34;err&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Exit</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;service stopped cleanly&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The nice property here is that failure propagates in both directions. A signal drains the HTTP server; a fatal error in the queue consumer also drains the HTTP server, because <code>errgroup.WithContext</code> cancels <code>gCtx</code> as soon as any goroutine returns an error.</p>
<p>The order of shutdown matters, and it is the reverse of startup: stop accepting work, finish what you have, then close the things that work depends on. Closing your database pool before draining HTTP guarantees a burst of errors from requests that were nearly done.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// After g.Wait() returns, nothing is still using these.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">Close</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">redis</span>.<span style="color:#a6e22e">Close</span>()
</span></span></code></pre></div><h2 id="the-load-balancer-problem">The Load Balancer Problem</h2>
<p>Here is the part that surprises people: even a perfectly graceful process can drop requests.</p>
<p>Between the moment your pod receives <code>SIGTERM</code> and the moment the load balancer stops sending it traffic, there is a gap. Endpoint updates propagate asynchronously — through the API server, to kube-proxy or an ingress controller, and finally to the actual routing table. During that gap the balancer is still sending new connections to a server that has already closed its listener. Those connections are refused.</p>
<p>The fix is to keep serving for a few seconds <em>after</em> the signal arrives:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;signal received, waiting for load balancer to deregister&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Keep serving while the endpoint removal propagates.</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#ae81ff">5</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">shutdownCtx</span>, <span style="color:#a6e22e">cancel</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithTimeout</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#ae81ff">20</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">cancel</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">Shutdown</span>(<span style="color:#a6e22e">shutdownCtx</span>)
</span></span></code></pre></div><p>It feels wrong to <code>sleep</code> on purpose, but it is the standard remedy, and Kubernetes has a hook for exactly this so you do not need it in your code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">lifecycle</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">preStop</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">exec</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">command</span>: [<span style="color:#e6db74">&#34;/bin/sh&#34;</span>, <span style="color:#e6db74">&#34;-c&#34;</span>, <span style="color:#e6db74">&#34;sleep 5&#34;</span>]
</span></span><span style="display:flex;"><span><span style="color:#f92672">terminationGracePeriodSeconds</span>: <span style="color:#ae81ff">45</span>
</span></span></code></pre></div><p><code>preStop</code> runs <em>before</em> <code>SIGTERM</code> is sent, while the pod is already being removed from the endpoints list. By the time your process sees the signal, traffic has stopped arriving.</p>
<p>Whichever way you do it, keep the arithmetic straight:</p>
<pre tabindex="0"><code>preStop sleep (5s) + drain timeout (20s) + close time (2s) &lt; terminationGracePeriodSeconds (45s)
</code></pre><p>If the total exceeds the grace period, you get <code>SIGKILL</code> mid-drain and you are back where you started. Give yourself real headroom — the default grace period is 30 seconds, which is not much once a slow request is in flight.</p>
<p>A readiness probe that starts failing on <code>SIGTERM</code> achieves the same thing more precisely:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">ready</span> <span style="color:#a6e22e">atomic</span>.<span style="color:#a6e22e">Bool</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">init</span>() { <span style="color:#a6e22e">ready</span>.<span style="color:#a6e22e">Store</span>(<span style="color:#66d9ef">true</span>) }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// /readyz</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">readyz</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">ready</span>.<span style="color:#a6e22e">Load</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;shutting down&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusServiceUnavailable</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusOK</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// On signal, before draining:</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ready</span>.<span style="color:#a6e22e">Store</span>(<span style="color:#66d9ef">false</span>)
</span></span></code></pre></div><p>Keep <code>/healthz</code> (liveness) returning 200 the whole time — if liveness fails during shutdown, the kubelet may kill the container instead of letting it drain.</p>
<h2 id="docker-gotchas">Docker Gotchas</h2>
<p>Two container-level mistakes will silently defeat everything above.</p>
<p><strong>Shell-form <code>CMD</code> makes your process PID 2.</strong> Written as <code>CMD ./server</code>, Docker runs <code>/bin/sh -c ./server</code>. The shell is PID 1, receives <code>SIGTERM</code>, and does not forward it. Your server never hears a thing.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-dockerfile" data-lang="dockerfile"><span style="display:flex;"><span><span style="color:#75715e"># Wrong — signals stop at the shell</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">CMD</span> ./server<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># Right — exec form, your binary is PID 1</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">CMD</span> [<span style="color:#e6db74">&#34;./server&#34;</span>]<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p><strong><code>docker stop</code> waits 10 seconds by default.</strong> If your drain window is 20 seconds, you will be killed halfway through. Raise it: <code>docker stop -t 45</code>, or <code>stop_grace_period: 45s</code> in Compose.</p>
<h2 id="verifying-it-works">Verifying It Works</h2>
<p>Do not take it on faith — this is easy to test. Add a slow endpoint, start a request, and signal the process mid-flight:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">mux</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;/slow&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">select</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">After</span>(<span style="color:#ae81ff">10</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>):
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Write</span>([]byte(<span style="color:#e6db74">&#34;finished\n&#34;</span>))
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Context</span>().<span style="color:#a6e22e">Done</span>():
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// The client gave up; Shutdown does not cancel request contexts.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./server &amp;
</span></span><span style="display:flex;"><span>curl -s localhost:8080/slow &amp;     <span style="color:#75715e"># starts a 10s request</span>
</span></span><span style="display:flex;"><span>sleep <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>kill -TERM %1                     <span style="color:#75715e"># signal while it is in flight</span>
</span></span></code></pre></div><p>A correct implementation prints <code>finished</code> after ten seconds and then exits. A broken one prints nothing and the curl reports a reset connection.</p>
<p>For the same check under real traffic, point a load test at the service and restart it mid-run — the technique from <a href="/posts/an-easy-way-to-loadtest-your-web-apps/">an easy way to load test your web apps</a> works well here. With graceful shutdown in place your error count during a restart should be exactly zero; without it, you will see the exact number of requests that were in flight.</p>
<p>Note what <code>Shutdown</code> does <em>not</em> do: it does not cancel <code>r.Context()</code> for in-flight requests. That is deliberate — the request should be allowed to complete. It also means a handler with no timeout of its own can hold the drain open until your shutdown context expires, which is why the <code>WriteTimeout</code> in the first example matters.</p>
<h2 id="checklist">Checklist</h2>
<ul>
<li><code>signal.NotifyContext</code> for <code>SIGINT</code> and <code>SIGTERM</code>.</li>
<li><code>srv.Shutdown</code> with its own fresh, bounded context.</li>
<li><code>errors.Is(err, http.ErrServerClosed)</code> treated as success.</li>
<li>Background workers cancelled through the same context, drained before dependencies close.</li>
<li>Dependencies closed last, in reverse order of startup.</li>
<li>Readiness probe flipped to failing before the drain begins.</li>
<li><code>preStop</code> hook or a deliberate sleep to cover load balancer propagation.</li>
<li>Grace period comfortably larger than the sum of your timeouts.</li>
<li>Exec-form <code>CMD</code> in the Dockerfile.</li>
<li>A test that proves an in-flight request survives a <code>SIGTERM</code>.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Graceful shutdown is one of those features nobody notices when it works — which is precisely the point. Twenty lines in <code>main</code>, a couple of timeouts that add up correctly, and a container that actually forwards signals will turn every deploy from a small burst of errors into a non-event. It is the cheapest reliability win available to a Go service, and worth adding before the next release rather than after the next incident.</p>]]></content:encoded>
      <category>Go Programming</category>
      <category>Backend Development</category>
      <category>DevOps</category>
    </item>
    <item>
      <title>One of The Easiest Ways to Host your Go Web App</title>
      <link>https://webdevstation.com/posts/one-of-thee-easiest-ways-to-host-go-web-apps/</link>
      <pubDate>Tue, 05 Sep 2023 09:53:15 +0000</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/one-of-thee-easiest-ways-to-host-go-web-apps/</guid>
      <description>Discover how to host Go web applications for as little as $5 per month using DigitalOcean and Docker. Learn this cost-effective, scalable deployment method with…</description>
      <content:encoded><![CDATA[<p>Hello! In this post, I will explain the cost-effective method I use to host my Go web applications
with varying levels of complexity, all starting from as low as $5 per month. This method also allows to easy
deploy and scale your golang application.</p>
<p>As an example, this is how I host <a href="https://whattoreadafter.xyz" title="What to read after: AI book recommendations">whattoreadafter.xyz</a>, a service that recommends books based on the book you just finished reading, by using AI.</p>
<p>Starting off, let&rsquo;s enumerate the tools we&rsquo;ll be using alongside Golang:</p>
<ul>
<li><a href="https://m.do.co/c/2a29ebc23e4a">DigitalOcean</a> - a cloud computing platform that provides virtual machines and other resources.</li>
<li><a href="https://www.docker.com/">Docker</a> - a set of platform as a service products that use OS-level virtualization to deliver software in packages called containers.</li>
</ul>
<p>In order to follow along, you will need to have a DigitalOcean account. If you don&rsquo;t have one, you can
sign up <a href="https://m.do.co/c/2a29ebc23e4a">here</a> and get $200 in credit over 60 days.</p>
<p>Let&rsquo;s get started!
For simplicity sake, our application will be a simple web server that returns current time in UTC format.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">currentTimeHandler</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">currentTime</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>().<span style="color:#a6e22e">UTC</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Fprintf</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;Current Time (UTC): %s&#34;</span>, <span style="color:#a6e22e">currentTime</span>.<span style="color:#a6e22e">Format</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">RFC3339</span>))
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;/&#34;</span>, <span style="color:#a6e22e">currentTimeHandler</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Server is running on port 8080&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ListenAndServe</span>(<span style="color:#e6db74">&#34;:8080&#34;</span>, <span style="color:#66d9ef">nil</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Now let&rsquo;s host it!</p>
<p>In order for you to easier follow along, I will list the steps we will take to host our application:</p>
<h2 id="1-prepare-a-dockerfile-for-our-application-and-place-it-in-the-root-of-our-project-alongside-the-maingo-file">1. Prepare a Dockerfile for our application and place it in the root of our project alongside the <code>main.go</code> file.</h2>
<p>Example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-dockerfile" data-lang="dockerfile"><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span> <span style="color:#e6db74">golang:alpine</span> <span style="color:#66d9ef">AS</span> <span style="color:#e6db74">builder</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span> apk add --no-cache --update <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>        git <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>        ca-certificates<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">ADD</span> . /app<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">WORKDIR</span> <span style="color:#e6db74">/app</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">COPY</span> go.mod ./<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span>  go mod download<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span> CGO_ENABLED<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span> GOOS<span style="color:#f92672">=</span>linux GOARCH<span style="color:#f92672">=</span>amd64 go build -a -o /main .<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span> <span style="color:#e6db74">alpine</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">COPY</span> --from<span style="color:#f92672">=</span>builder /main ./<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span> chmod +x ./main<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">ENTRYPOINT</span> [<span style="color:#e6db74">&#34;./main&#34;</span>]<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p> </p>
<h2 id="2-push-your-code-to-a-github-repository-you-can-make-it-private-if-you-want">2. Push your code to a GitHub repository. You can make it private if you want.</h2>
<p> </p>
<h2 id="3-go-to-digitalocean-and-create-a-new-app-you-can-do-so-by-clicking-on-the-apps-tab-in-the-left-sidebar-and-then-clicking-create-app">3. Go to <a href="https://cloud.digitalocean.com/apps">DigitalOcean</a> and create a new App. You can do so by clicking on the &ldquo;Apps&rdquo; tab in the left sidebar and then clicking &ldquo;Create App&rdquo;.</h2>
<p> </p>
<h2 id="4-select-github-as-your-source-and-click-continue">4. Select &ldquo;GitHub&rdquo; as your &ldquo;Source&rdquo; and click &ldquo;Continue&rdquo;.</h2>
<p><img src="/images/2023/do1.png" alt="Hosting Golang on digitalocean" title="Hosting Golang apps on digitalocean"></p>
<h2 id="5-select-the-repository-you-want-to-deploy-and-click-next">5. Select the repository you want to deploy and click &ldquo;Next&rdquo;.</h2>
<p><img src="/images/2023/do2.png" alt="Hosting Golang on digitalocean" title="Hosting Golang apps on digitalocean"></p>
<h2 id="6-select-dockerfile-as-your-build-type-and-click-next">6. Select &ldquo;Dockerfile&rdquo; as your &ldquo;Build type&rdquo; and click &ldquo;Next&rdquo;.</h2>
<p><img src="/images/2023/do3.png" alt="Hosting Golang on digitalocean" title="Hosting Golang apps on digitalocean"></p>
<h2 id="7-configure-environment-variables-if-you-need-to-and-click-next">7. Configure environment variables if you need to and click &ldquo;Next&rdquo;.</h2>
<p> </p>
<h2 id="8-select-region-for-your-app-and-click-next">8. Select region for your app and click &ldquo;Next&rdquo;.</h2>
<p><img src="/images/2023/do4.png" alt="Hosting Golang on digitalocean" title="Hosting Golang apps on digitalocean"></p>
<h2 id="9-in-the-billing-section-select-the-plan-you-want-to-use-you-can-start-with-the-5-per-month-plan-and-scale-up-later-if-you-need-to">9. In the billing section, select the plan you want to use. You can start with the $5 per month plan and scale up later if you need to.</h2>
<p><img src="/images/2023/do5.png" alt="Hosting Golang on digitalocean" title="Hosting Golang apps on digitalocean"></p>
<h2 id="10-click-create-resources-and-wait-for-your-app-to-be-deployed">10. Click &ldquo;Create Resources&rdquo; and wait for your app to be deployed.</h2>
<p> </p>
<h2 id="11-once-your-app-is-deployed-you-can-access-it-by-clicking-on-the-live-app-link">11. Once your app is deployed, you can access it by clicking on the &ldquo;Live App&rdquo; link.</h2>
<p><img src="/images/2023/do6.png" alt="Hosting Golang on digitalocean" title="Hosting Golang apps on digitalocean"></p>
<p>That&rsquo;s it! You have successfully deployed your Golang application!</p>
<p>You can also add your own domain name to your app by clicking on the &ldquo;Settings&rdquo; tab and then clicking on &ldquo;Domains&rdquo;.</p>
<p>Now, every time you push a new commit to your repository, DigitalOcean will automatically build and deploy your application.
This is a great way to host your Golang applications, especially if you are just starting out and don&rsquo;t want to spend a lot of money on hosting.</p>
<p>Thank you for reading! If you have any questions, feel free to reach out to me on <a href="https://twitter.com/oleks_i">Twitter</a>.</p>
<p>Before you point real traffic at it, two things are worth having in place: <a href="/posts/graceful-shutdown-in-go-web-services/">graceful shutdown</a>, so redeploys stop dropping requests, and <a href="/posts/an-easy-way-to-loadtest-your-web-apps/">a load test</a>, so you know what the box can actually take.</p>]]></content:encoded>
      <category>Go Programming</category>
      <category>DevOps</category>
      <category>Web Development</category>
    </item>
    <item>
      <title>An Easy Way to Load Test Your Web Apps</title>
      <link>https://webdevstation.com/posts/an-easy-way-to-loadtest-your-web-apps/</link>
      <pubDate>Fri, 12 Feb 2021 18:56:46 +0000</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/an-easy-way-to-loadtest-your-web-apps/</guid>
      <description>Learn how to implement effective load testing for your web applications using the k6 tool and automate performance testing in your GitLab CI/CD pipeline.</description>
      <content:encoded><![CDATA[<p>This time, I want to share my positive experience of load testing of one of our web services, by using <a href="https://k6.io/">K6</a> tool.
Moreover, we will see how easily we can integrate this into the GitLab CI pipeline.</p>
<p>When you develop web applications, it&rsquo;s crucial to have a testing strategy. Nobody argues about the importance of unit,
functional, and integration testing. Nevertheless, very often developers forget to test how their application works under high load.
Even, when we have a &ldquo;green light&rdquo; from all our testing stages, including manual testing, better to not release it to production, until you load test it.
Otherwise, nobody can guarantee, that application will work properly when 50 users will use it simultaneously.</p>
<p>In this article I&rsquo;m going to load test our web application from the <a href="https://webdevstation.com/posts/how-to-show-flash-messages-in-go-echo/">previous article</a>.</p>
<p>For that, we are going to use a K6 tool, written in Go, and uses JavaScript for scripting.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>k6 is a developer-centric, free and open-source load testing tool built for making performance testing a productive and enjoyable experience.
</span></span></code></pre></div><p>Let&rsquo;s install this tool:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>brew install k6
</span></span></code></pre></div><p>If you have different from the macOS operating system, please read about others ways to install <a href="https://k6.io/docs/getting-started/installation">here</a>.</p>
<p>Next, we create a <code>loadtests</code> folder in the root of our project and inside we add a <code>test.js</code> file, where we are going to write our load tests scenarios:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">sleep</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;k6&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">http</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;k6/http&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">check</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;k6&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">Rate</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;k6/metrics&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">errorRate</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Rate</span>(<span style="color:#e6db74">&#39;errors&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">options</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Here we define our scenarios.
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">scenarios</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">sign_in_page_test</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">executor</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;constant-vus&#39;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">duration</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;1m&#39;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">vus</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">100</span>, <span style="color:#75715e">// amount of the virtual users
</span></span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">tags</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">test_type</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;signInPage&#39;</span> },
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">exec</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;signInPage&#39;</span>,
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// List of thresholds.
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">thresholds</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">http_req_duration</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#39;avg&lt;500&#39;</span>], <span style="color:#75715e">// avg response times must be below 0.5s
</span></span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">errors</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#39;rate&lt;0.1&#39;</span>], <span style="color:#75715e">// &lt;10% errors
</span></span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">signInPage</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">res</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">get</span>(<span style="color:#a6e22e">getDomain</span>() <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;/user/signin&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Here we check the response status.
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">check</span>(<span style="color:#a6e22e">res</span>, {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;status is 200&#39;</span><span style="color:#f92672">:</span> (<span style="color:#a6e22e">r</span>) =&gt; <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">status</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">200</span>,
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// If it&#39;s different from 200, add info to the errorRate.
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">errorRate</span>.<span style="color:#a6e22e">add</span>(<span style="color:#f92672">!</span><span style="color:#a6e22e">result</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">sleep</span>(<span style="color:#ae81ff">3</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Gets the domain from the environment variables.
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getDomain</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">__ENV</span>.<span style="color:#a6e22e">DOMAIN</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Above, we have declared a scenario to load test <code>/user/signin</code> page with 100 virtual users who continuously accessing our page in parallel.</p>
<p>To run this load test, we need to execute this command in the terminal:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>k6 run --env DOMAIN<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;http://localhost:8777&#34;</span> ./loadtests/test.js
</span></span></code></pre></div><p>After some time we will see this results output:
<img src="/images/0221/k6.png" alt="k6 terminal output listing checks, request duration percentiles and the passing thresholds for the load test" title="k6 load test results in the terminal"></p>
<p>As you could notice, all our defined thresholds were satisfied. So far so good!</p>
<p>Now, let&rsquo;s see how we can integrate load testing to the Gitlab CI pipeline. Fortunately, it&rsquo;s easy to do :)</p>
<p>Inside <code>.gitlab-ci.yml</code> we need to add this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">stages</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">loadtest</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">loadtesting</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">stage</span>: <span style="color:#ae81ff">loadtest</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">image</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">loadimpact/k6:latest</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">entrypoint</span>: [ <span style="color:#e6db74">&#39;&#39;</span> ]
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">variables</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">DOMAIN</span>: <span style="color:#e6db74">&#39;[your-testing-domain-here]&#39;</span>  
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">script</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">echo &#34;executing K6 load tests in k6 container...&#34;</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">k6 run --env DOMAIN=${DOMAIN} ./loadtests/test.js</span>
</span></span></code></pre></div><p>That was it! I&rsquo;ve described just an idea how you can easily integrate the load testing in your development routine.
In the real-world situations you might create more complex load testing scenarios, which will help you find weak points of your application and prevent unexpected downtimes.</p>
<p>I wish you happy coding and no pagerduty calls during the night!😉</p>
<p>Once you can measure, you have something to optimise against. Two places I usually look first: <a href="/posts/early-hints-in-go-or-the-way-of-how-to-improve-perforamnce-of-web-page/">103 Early Hints in Go</a> for front-end latency, and <a href="/posts/ristretto-the-most-performant-concurrent-cache-library-for-go/">Ristretto caching</a> for the expensive calls behind it. It is also the right tool to prove your <a href="/posts/graceful-shutdown-in-go-web-services/">graceful shutdown</a> really does keep errors at zero during a deploy.</p>]]></content:encoded>
      <category>DevOps</category>
      <category>Testing</category>
    </item>
    <item>
      <title>How to make Nginx cache cookie aware</title>
      <link>https://webdevstation.com/posts/how-to-make-nginx-cookie-aware/</link>
      <pubDate>Tue, 15 Dec 2020 20:12:51 +0000</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/how-to-make-nginx-cookie-aware/</guid>
      <description>Learn how to configure Nginx to cache responses based on specific cookie values, enabling proper A/B testing and personalized content delivery while maintaining…</description>
      <content:encoded><![CDATA[<p>In this post, I&rsquo;m going to describe how we can configure nginx to be able to cache responses based on the specific cookie value.</p>
<p>Let&rsquo;s imagine a situation when you want to do an A/B test on your website, where 50% of users should see new headline text on the page. Other 50% of visitors will continue see the old page. In this case, all your server-side manipulation about splitting users to different versions will be ignored by nginx cache (of course, if you have it).</p>
<p>It happens because nginx, by default, has this configuration:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#66d9ef">proxy_cache_key</span> $scheme$proxy_host$request_uri;
</span></span></code></pre></div><p>So, it will use the same cache key for all users, who requests a page with the same url.</p>
<p>Luckily, nginx allows us easily to customize proxy_cache_key! What we need to do, it&rsquo;s just to add a specific cookie to this key:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#66d9ef">proxy_cache_key</span> $scheme$proxy_host$request_uri$cookie_MY_COOKIE_NAME;
</span></span></code></pre></div><p>And that&rsquo;s it! After this, if userA has MY_COOKIE_NAME=A and userB has MY_COOKIE_NAME=B they will receive different versions of page by the same url.</p>
<p>If you need more complex behaviour, where you won&rsquo;t use hardcoded cookie name in your nginx config, you can do something like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#e6db74">(</span>$http_cookie ~<span style="color:#e6db74">*</span> <span style="color:#e6db74">&#34;ab_(.*?)=([\w-]+)&#34;</span> <span style="color:#e6db74">)</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">set</span> $abcookie $1$2;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">proxy_cache_key</span> $scheme$proxy_host$request_uri$abcookie; 
</span></span></code></pre></div><p>As you can see, you can use $http_cookie and generate proxy_cache_key based on specific cookie patterns.
In this concrete example we check if http cookies contains cookie with regex <code>pattern ab_(.*?)=([\w-]+)</code> and if this cookie exists, we generate new variable for proxy_cache_key`.</p>
<p>Caching at the proxy is only one layer. For the one inside your Go process, see <a href="/posts/ristretto-the-most-performant-concurrent-cache-library-for-go/">Ristretto — the most performant concurrent cache library for Go</a>, and for shaving latency off the browser side, <a href="/posts/early-hints-in-go-or-the-way-of-how-to-improve-perforamnce-of-web-page/">103 Early Hints in Go</a>.</p>]]></content:encoded>
      <category>DevOps</category>
      <category>Web Development</category>
    </item>
  </channel>
</rss>
