<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Beth Anderson</title>
    <description>“First, solve the problem. Then, write the code.”
</description>
    <link>http://bet.andr.io/</link>
    <atom:link href="http://bet.andr.io/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Mon, 09 Jun 2025 12:18:35 +0000</pubDate>
    <lastBuildDate>Mon, 09 Jun 2025 12:18:35 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Managing Multiple Git(Hub) Profiles</title>
        <description>&lt;p&gt;&lt;em&gt;A little Git(Hub) hack that I use to manage config for a couple of GitHub accounts (work plus personal if I’m doing a 10% or hackday thing)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Instead of changing one &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.gitconfig&lt;/code&gt; file, I have a simple one (plus also my generic aliases in there so they’re available for all profiles), with:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[includeIf &quot;gitdir:~/home/&quot;]
path = ~/home/.gitconfig

[includeIf &quot;gitdir:~/work/&quot;]
path = ~/work/.gitconfig
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/work/.gitconfig&lt;/code&gt; file I have:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-[user]&quot;&gt;	email = work.email@example.com
	name = Beth Anderson

[github]
	user = &quot;githubusername&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;…and a different one in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/home/.gitconfig&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then if I’m in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/work/...&lt;/code&gt; directory it automatically uses my work user, and home for home…&lt;/p&gt;
</description>
        <pubDate>Mon, 09 Jun 2025 00:00:00 +0000</pubDate>
        <link>http://bet.andr.io/github/git/shell/terminal/2025/06/09/multiple-github-profiles.html</link>
        <guid isPermaLink="true">http://bet.andr.io/github/git/shell/terminal/2025/06/09/multiple-github-profiles.html</guid>
        
        
        <category>github</category>
        
        <category>git</category>
        
        <category>shell</category>
        
        <category>terminal</category>
        
      </item>
    
      <item>
        <title>Z Shell Performance</title>
        <description>&lt;p&gt;&lt;em&gt;My zsh is taking so long to launch now, like up to 40 seconds, and I wanted to find out why.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So I did some Googling and found out that adding zmodload zsh/zprof to the top of my .zshrc file meant that I could run zprof (after reloading the shell).
This yielded:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;num&lt;/th&gt;
      &lt;th&gt;calls&lt;/th&gt;
      &lt;th&gt;time&lt;/th&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;self&lt;/th&gt;
      &lt;th&gt;name&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1)&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;3067.88&lt;/td&gt;
      &lt;td&gt;3067.88&lt;/td&gt;
      &lt;td&gt;74.90%&lt;/td&gt;
      &lt;td&gt;1344.09&lt;/td&gt;
      &lt;td&gt;1344.09&lt;/td&gt;
      &lt;td&gt;32.82%&lt;/td&gt;
      &lt;td&gt;nvm_auto&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2)&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;909.13&lt;/td&gt;
      &lt;td&gt;454.57&lt;/td&gt;
      &lt;td&gt;22.20%&lt;/td&gt;
      &lt;td&gt;909.13&lt;/td&gt;
      &lt;td&gt;454.57&lt;/td&gt;
      &lt;td&gt;22.20%&lt;/td&gt;
      &lt;td&gt;jenv&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3)&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;1723.79&lt;/td&gt;
      &lt;td&gt;861.90&lt;/td&gt;
      &lt;td&gt;42.09%&lt;/td&gt;
      &lt;td&gt;852.65&lt;/td&gt;
      &lt;td&gt;426.32&lt;/td&gt;
      &lt;td&gt;20.82%&lt;/td&gt;
      &lt;td&gt;nvm&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4)&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;779.97&lt;/td&gt;
      &lt;td&gt;779.97&lt;/td&gt;
      &lt;td&gt;19.04%&lt;/td&gt;
      &lt;td&gt;710.71&lt;/td&gt;
      &lt;td&gt;710.71&lt;/td&gt;
      &lt;td&gt;17.35%&lt;/td&gt;
      &lt;td&gt;nvm_ensure_version_installed&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;5)&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;90.30&lt;/td&gt;
      &lt;td&gt;90.30&lt;/td&gt;
      &lt;td&gt;2.20%&lt;/td&gt;
      &lt;td&gt;72.26&lt;/td&gt;
      &lt;td&gt;72.26&lt;/td&gt;
      &lt;td&gt;1.76%&lt;/td&gt;
      &lt;td&gt;nvm_die_on_prefix&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;6)&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;69.26&lt;/td&gt;
      &lt;td&gt;69.26&lt;/td&gt;
      &lt;td&gt;1.69%&lt;/td&gt;
      &lt;td&gt;69.26&lt;/td&gt;
      &lt;td&gt;69.26&lt;/td&gt;
      &lt;td&gt;1.69%&lt;/td&gt;
      &lt;td&gt;nvm_is_version_installed&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;7)&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;47.94&lt;/td&gt;
      &lt;td&gt;47.94&lt;/td&gt;
      &lt;td&gt;1.17%&lt;/td&gt;
      &lt;td&gt;47.94&lt;/td&gt;
      &lt;td&gt;47.94&lt;/td&gt;
      &lt;td&gt;1.17%&lt;/td&gt;
      &lt;td&gt;handle_completion_insecurities&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;8)&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;18.56&lt;/td&gt;
      &lt;td&gt;18.56&lt;/td&gt;
      &lt;td&gt;0.45%&lt;/td&gt;
      &lt;td&gt;18.56&lt;/td&gt;
      &lt;td&gt;18.56&lt;/td&gt;
      &lt;td&gt;0.45%&lt;/td&gt;
      &lt;td&gt;compinit&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;9)&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;17.09&lt;/td&gt;
      &lt;td&gt;8.54&lt;/td&gt;
      &lt;td&gt;0.42%&lt;/td&gt;
      &lt;td&gt;17.09&lt;/td&gt;
      &lt;td&gt;8.54&lt;/td&gt;
      &lt;td&gt;0.42%&lt;/td&gt;
      &lt;td&gt;nvm_grep&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10)&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;12.73&lt;/td&gt;
      &lt;td&gt;6.36&lt;/td&gt;
      &lt;td&gt;0.31%&lt;/td&gt;
      &lt;td&gt;12.73&lt;/td&gt;
      &lt;td&gt;6.36&lt;/td&gt;
      &lt;td&gt;0.31%&lt;/td&gt;
      &lt;td&gt;grep-flag-available&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;11)&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;11.29&lt;/td&gt;
      &lt;td&gt;5.65&lt;/td&gt;
      &lt;td&gt;0.28%&lt;/td&gt;
      &lt;td&gt;11.29&lt;/td&gt;
      &lt;td&gt;5.65&lt;/td&gt;
      &lt;td&gt;0.28%&lt;/td&gt;
      &lt;td&gt;down-line-or-beginning-search&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;12)&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;4.79&lt;/td&gt;
      &lt;td&gt;4.79&lt;/td&gt;
      &lt;td&gt;0.12%&lt;/td&gt;
      &lt;td&gt;4.77&lt;/td&gt;
      &lt;td&gt;4.77&lt;/td&gt;
      &lt;td&gt;0.12%&lt;/td&gt;
      &lt;td&gt;powerlevel9k_vcs_init&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;13)&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;4.67&lt;/td&gt;
      &lt;td&gt;4.67&lt;/td&gt;
      &lt;td&gt;0.11%&lt;/td&gt;
      &lt;td&gt;4.67&lt;/td&gt;
      &lt;td&gt;4.67&lt;/td&gt;
      &lt;td&gt;0.11%&lt;/td&gt;
      &lt;td&gt;termColors&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;14)&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;2.91&lt;/td&gt;
      &lt;td&gt;1.45&lt;/td&gt;
      &lt;td&gt;0.07%&lt;/td&gt;
      &lt;td&gt;2.91&lt;/td&gt;
      &lt;td&gt;1.45&lt;/td&gt;
      &lt;td&gt;0.07%&lt;/td&gt;
      &lt;td&gt;env_default&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;15)&lt;/td&gt;
      &lt;td&gt;15&lt;/td&gt;
      &lt;td&gt;2.05&lt;/td&gt;
      &lt;td&gt;0.14&lt;/td&gt;
      &lt;td&gt;0.05%&lt;/td&gt;
      &lt;td&gt;2.05&lt;/td&gt;
      &lt;td&gt;0.14&lt;/td&gt;
      &lt;td&gt;0.05%&lt;/td&gt;
      &lt;td&gt;up-line-or-beginning-search&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;…which showed that nvm is taking a long time to load. I don’t use it a lot but I do want it sometimes so instead I’m using https://github.com/lukechilds/zsh-nvm#lazy-loading which lazy-loads nvm with some additions in the .zshrc file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export NVM_LAZY_LOAD=true
export NVM_COMPLETION=true
plugins=(zsh-nvm [plus your others])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s gone from around 40 seconds to around 4 seconds!&lt;/p&gt;

&lt;p&gt;However &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbenv&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jenv&lt;/code&gt; are also slow! I couldn’t find a reasonably easy way to improve that but I am using evalcache which will cache the init commands, saving some time.
Installed with:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/mroth/evalcache ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/evalcache
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;…and in .zshrc:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;plugins=(evalcache [plus your others])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Then instead of  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eval &quot;$(jenv init -)&quot;&lt;/code&gt; in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.zshrc&lt;/code&gt; use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_evalcache jenv init -&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Sadly that’s one less excuse though…&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/excuse.png&quot; alt=&quot;XKCD Slacking Off Excuse&quot; /&gt;&lt;/p&gt;
</description>
        <pubDate>Fri, 06 Jan 2023 00:00:00 +0000</pubDate>
        <link>http://bet.andr.io/zsh/shell/performance/terminal/2023/01/06/zsh-performance.html</link>
        <guid isPermaLink="true">http://bet.andr.io/zsh/shell/performance/terminal/2023/01/06/zsh-performance.html</guid>
        
        
        <category>zsh</category>
        
        <category>shell</category>
        
        <category>performance</category>
        
        <category>terminal</category>
        
      </item>
    
      <item>
        <title>What We Learned</title>
        <description>&lt;p&gt;&lt;em&gt;What We Learned&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Be a facilitator and accelerator not a gatekeeper. Find ways to include people.&lt;/li&gt;
  &lt;li&gt;Companies are often hard to communicate across so things get reinvented.&lt;/li&gt;
  &lt;li&gt;Introducing a new system &lt;em&gt;adds&lt;/em&gt; complexity. Only removing an old system reduces complexity.&lt;/li&gt;
  &lt;li&gt;Don’t try to solve every problem. Do &lt;em&gt;one&lt;/em&gt; thing well. (Don’t ignore the big problems though).&lt;/li&gt;
  &lt;li&gt;Don’t just build tooling; model processes.&lt;/li&gt;
  &lt;li&gt;Tools are temporary, ideas are forever…(well a bit longer than tools anyway).&lt;/li&gt;
  &lt;li&gt;Add value without adding complexity.&lt;/li&gt;
  &lt;li&gt;Teams depend on your stuff. You should too!&lt;/li&gt;
  &lt;li&gt;Don’t build everything straight away.  Get something out there and embrace feedback.&lt;/li&gt;
  &lt;li&gt;Work with people. Go and speak to them and include their ideas.&lt;/li&gt;
  &lt;li&gt;The real problem you’re solving is not technical, it’s people.&lt;/li&gt;
  &lt;li&gt;Ensure senior management understand the problem and support you.&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Mon, 15 Aug 2022 00:00:00 +0000</pubDate>
        <link>http://bet.andr.io/delivery/learned</link>
        <guid isPermaLink="true">http://bet.andr.io/delivery/learned</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Maze Generator in Go</title>
        <description>&lt;p&gt;&lt;em&gt;Named after the Overlook hotel in The Shining, Goverlook generates a maze&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Implemented with a stack, this approach is one of the simplest ways to generate a maze using a computer. Consider the space for a maze being a large grid of cells (like a large chess board), each cell starting with four walls. Starting from a random cell, the computer then selects a random neighbouring cell that has not yet been visited. The computer removes the wall between the two cells and marks the new cell as visited, and adds it to the stack to facilitate backtracking. The computer continues this process, with a cell that has no unvisited neighbours being considered a dead-end. When at a dead-end it backtracks through the path until it reaches a cell with an unvisited neighbour, continuing the path generation by visiting this new, unvisited cell (creating a new junction). This process continues until every cell has been visited, causing the computer to backtrack all the way back to the beginning cell. We can be sure every cell is visited.&lt;/p&gt;

&lt;p&gt;As given above this algorithm involves deep recursion which may cause stack overflow issues on some computer architectures. The algorithm can be rearranged into a loop by storing backtracking information in the maze itself. This also provides a quick way to display a solution, by starting at any given point and backtracking to the beginning.&lt;/p&gt;

&lt;p&gt;Horizontal Passage Bias
Mazes generated with a depth-first search have a low branching factor and contain many long corridors, because the algorithm explores as far as possible along each branch before backtracking.&lt;/p&gt;

&lt;p&gt;Using a depth-first maze-generation algorithm with a following recursive routine:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Given a current cell as a parameter&lt;/li&gt;
  &lt;li&gt;Mark the current cell as visited&lt;/li&gt;
  &lt;li&gt;While the current cell has any unvisited neighbour cells
    &lt;ul&gt;
      &lt;li&gt;Choose one of the unvisited neighbours&lt;/li&gt;
      &lt;li&gt;Remove the wall between the current cell and the chosen cell&lt;/li&gt;
      &lt;li&gt;Invoke the routine recursively for a chosen cell&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/maze.png&quot; alt=&quot;Maze&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;json-format&quot;&gt;JSON Format&lt;/h2&gt;
&lt;p&gt;Mazes are represented as a collection of collections of “cells”. Each cell has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;northRoute&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;westRoute&lt;/code&gt; boolean.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
	&quot;cells&quot;: [
		[{
			&quot;northRoute&quot;: false,
			&quot;westRoute&quot;: false
		}, {
			&quot;northRoute&quot;: false,
			&quot;westRoute&quot;: false
		}],
		[{
			&quot;northRoute&quot;: false,
			&quot;westRoute&quot;: true
		}, {
			&quot;northRoute&quot;: true,
			&quot;westRoute&quot;: true
		}]
	]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;code&quot;&gt;Code&lt;/h2&gt;

&lt;p&gt;Maze Generation: &lt;a href=&quot;https://github.com/betandr/goverlook&quot;&gt;github.com/betandr/goverlook&lt;/a&gt;
Boilerplate (you complete the algorithm): &lt;a href=&quot;https://github.com/betandr/gomazegen-boilerplate&quot;&gt;github.com/betandr/gomazegen-boilerplate&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Tue, 14 Jun 2022 00:00:00 +0000</pubDate>
        <link>http://bet.andr.io/code/go/maze/</link>
        <guid isPermaLink="true">http://bet.andr.io/code/go/maze/</guid>
        
        
        <category>project</category>
        
        <category>code</category>
        
        <category>maze</category>
        
        <category>go</category>
        
        <category>golang</category>
        
        <category>graph</category>
        
        <category>depthfirst</category>
        
      </item>
    
      <item>
        <title>Mille French Vocabulary - Privacy Policy</title>
        <description>&lt;p&gt;Hi, this is to confirm that none of your private data is stored or captured at all. Mille runs on your device and stores your progress locally.&lt;/p&gt;

&lt;p&gt;No data is transmitted or captured from your device. It’s just an app that helps you learn French. I wrote it for myself and thought it might be useful.&lt;/p&gt;

&lt;p&gt;Beth&lt;/p&gt;
</description>
        <pubDate>Mon, 31 Jan 2022 00:00:00 +0000</pubDate>
        <link>http://bet.andr.io/mille/privacy</link>
        <guid isPermaLink="true">http://bet.andr.io/mille/privacy</guid>
        
        
        <category>test</category>
        
      </item>
    
      <item>
        <title>Building Go with Containers</title>
        <description>&lt;p&gt;&lt;em&gt;Building Go with containers.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;build-for-a-particular-target&quot;&gt;Build for a particular target&lt;/h2&gt;

&lt;p&gt;Such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make PLATFORM=darwin/amd64&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;FROM --platform=${BUILDPLATFORM} golang:1.16.4-alpine3.13 AS base
WORKDIR /src
ENV CGO_ENABLED=0
COPY go.* .
RUN go mod download
COPY . .

FROM base as build
ARG TARGETOS
ARG TARGETARCH
RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /out/server .

FROM base as unit-test
RUN go test -v .

FROM scratch AS bin-unix
COPY --from=build /out/server /

FROM bin-unix AS bin-linux
FROM bin-unix AS bin-darwin

FROM scratch AS bin-windows
COPY --from=build /out/server /server.exe

FROM bin-${TARGETOS} AS bin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Repo: &lt;a href=&quot;https://github.com/betandr/dockergo&quot;&gt;github.com/betandr/congo&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;using-multi-stage-builds-to-build-a-container-without-go-development-dependencies&quot;&gt;Using Multi-stage builds to build a container without Go development dependencies&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# syntax=docker/dockerfile:1
FROM golang:1.16 AS builder
WORKDIR /server
COPY . .
COPY ./server .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app ./server

FROM alpine:latest
WORKDIR /root/
COPY --from=builder /server/app .
CMD [&quot;./app&quot;]  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Repo: &lt;a href=&quot;https://github.com/betandr/dockergo&quot;&gt;github.com/betandr/congo&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 03 Jun 2021 00:00:00 +0000</pubDate>
        <link>http://bet.andr.io/code/go/containers/</link>
        <guid isPermaLink="true">http://bet.andr.io/code/go/containers/</guid>
        
        
        <category>code</category>
        
        <category>go</category>
        
        <category>golang</category>
        
        <category>docker</category>
        
        <category>containers</category>
        
      </item>
    
      <item>
        <title>Managing OS X Credentials for Multiple GitHub Repos</title>
        <description>&lt;p&gt;&lt;em&gt;By default git stores credentials globally but it can be configured to cache credentials on a per-repo basis.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On OS X a tool called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;osxkeychain&lt;/code&gt; is used to store your GitHub credentials. This tool stores your credentials in the OS X Keychain which you can see by running the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Keychain Access&lt;/code&gt; app in your OS X Applications. To ensure this helper is working, run:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git config --global credential.helper osxkeychain
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To authenticate myself with GitHub I prefer to use &lt;em&gt;Personal Access Tokens&lt;/em&gt; as they’re a more granular and easily-repudiated credential, so you can visit &lt;a href=&quot;https://github.com/settings/tokens&quot;&gt;github.com/settings/tokens&lt;/a&gt; and generate a token. Usually I have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;repo&lt;/code&gt; scope only as I just need to manage repos.&lt;/p&gt;

&lt;p&gt;Keep a note of this token now as you won’t be able to see it again once you leave the GitHub page. It should be kept somewhere safe, such as a password manager like &lt;a href=&quot;https://github.com/gopasspw/gopass&quot;&gt;gopass&lt;/a&gt;. I have a little script that gets my access token and copies it into the clipboard buffer (although that might not be the most secure approach) ready for me to paste when my credentials are requested.&lt;/p&gt;

&lt;p&gt;At this stage you can use your username and this access token when git asks for your credentials, however these credentials will be stored for &lt;em&gt;all&lt;/em&gt; repos from GitHub. To use these credentials on a &lt;em&gt;per-repo&lt;/em&gt; basis, run:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git config --global credential.usehttppath true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This will mean that git can cache different credentials for github.com/person/example and github.com/person/another rather than just one set of credentials for github.com. Note that you should use &lt;em&gt;Clone with HTTPS&lt;/em&gt;, such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git clone https://github.com/person/example.git&lt;/code&gt; &lt;em&gt;not&lt;/em&gt; &lt;em&gt;Clone with SSH&lt;/em&gt; such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git clone git@github.com:person/example.git&lt;/code&gt; because using SSH just use an SSH key (if you have one set up) rather than the access token.&lt;/p&gt;

&lt;p&gt;When git needs credentials, it will prompt you as:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Cloning into &apos;example&apos;...
Username for &apos;https://github.com/person/example.git&apos;: YOUR_USERNAME
Password for &apos;https://betandr@github.com/person/example.git&apos;: YOUR_ACCESS_TOKEN
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At this stage you can create another access token in a different GitHub account and then use this different when working with another repo on the same system.&lt;/p&gt;

&lt;p&gt;You will be able to see these different credentials in your OS X Keychain by running &lt;em&gt;Keychain Access&lt;/em&gt; and searching for github. You should see a list of credentials for GitHub which, when clicked on, look like:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/git_cred_01.png&quot; alt=&quot;GitHub credentials for betandr account&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/git_cred_02.png&quot; alt=&quot;GitHub credentials for babbc account&quot; /&gt;&lt;/p&gt;

&lt;p&gt;These credentials can then be deleted from your Keychain if you no longer require them.&lt;/p&gt;
</description>
        <pubDate>Thu, 11 Jul 2019 00:00:00 +0000</pubDate>
        <link>http://bet.andr.io/git/osx/github/repos/2019/07/11/managing-osx-credentials-for-multiple-github-repos.html</link>
        <guid isPermaLink="true">http://bet.andr.io/git/osx/github/repos/2019/07/11/managing-osx-credentials-for-multiple-github-repos.html</guid>
        
        
        <category>git</category>
        
        <category>osx</category>
        
        <category>github</category>
        
        <category>repos</category>
        
      </item>
    
      <item>
        <title>Kubernetes Ingress with TLS on GKE</title>
        <description>&lt;p&gt;&lt;em&gt;Using an Ingress Controller we’re going to secure traffic to our Kubernetes cluster with TLS and a Let’s Encrypt certificate.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To follow these instructions you will need:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;A Kubernetes cluster you can manage; recommended 3 nodes.&lt;/li&gt;
  &lt;li&gt;A domain name that you can create DNS A records for&lt;/li&gt;
  &lt;li&gt;Although this tutorial was tested with GKE 1.13.6-gke.13 the same approach can be taken on other types of Kubernetes clusters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most Kubernetes deployment examples show you how to deploy a container and then expose the container to public traffic via the service, although sometimes you might like to have more control over ingress to your cluster services.&lt;/p&gt;

&lt;p&gt;Kubernetes Ingress was added in Kubernetes v1.1 and exposes HTTP and HTTPS routes from outside the cluster to services running within the cluster. Traffic routing is controlled by rules defined on the ingress resource which we’ll see later.&lt;/p&gt;

&lt;p&gt;To run kubectl you’ll need to have access to your cluster. For GKE the command gcloud container clusters get-credentials will do this. To ensure you’re actually using the correct cluster, you can run kubectl config current-context&lt;/p&gt;

&lt;p&gt;If you have a few clusters you can list all of the contexts using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl config get-contexts&lt;/code&gt; and then set a different context using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl config set-context $NAME&lt;/code&gt; where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$NAME&lt;/code&gt; is one of the entries in the NAME column when running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;...get-contexts&lt;/code&gt;. It’s always good to double check which cluster you’re going to make changes to.&lt;/p&gt;

&lt;h2 id=&quot;setup&quot;&gt;Setup&lt;/h2&gt;
&lt;p&gt;We’re going to use Helm to install tooling such as Cert Manager. The Helm documentation covers installation. We need to set up a Service Account and Role Bindings for Kubernetes’ Role-Based Access Control.&lt;/p&gt;

&lt;p&gt;Create a file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tiller-rbac-config.yaml&lt;/code&gt; containing:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tiller
  namespace: kube-system
--- 
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata: 
  name: tiller
roleRef: 
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects: 
  - 
    kind: ServiceAccount
    name: tiller
    namespace: kube-system
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Create these resources by running:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl create -f tiller-rbac-config.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Then initialise the tiller service account by running:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;helm init --service-account tiller
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;service&quot;&gt;Service&lt;/h2&gt;
&lt;p&gt;Next up we need to schedule some work to the cluster so for this we’re going to use a Hello, World app.&lt;/p&gt;

&lt;p&gt;Create a file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deployment.yaml&lt;/code&gt; containing:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;--- 
apiVersion: apps/v1
kind: Deployment
metadata: 
  labels: 
    app: hello
  name: hello
  namespace: default
spec: 
  replicas: 1
  selector: 
    matchLabels: 
      app: hello
  template: 
    metadata: 
      labels: 
        app: hello
    spec: 
      containers: 
        - 
          image: &quot;betandr/gollo-world:latest&quot;
          livenessProbe: 
            httpGet: 
              path: /
              port: 8888
          name: hello
          ports: 
            - 
              containerPort: 8888
          readinessProbe: 
            httpGet: 
              path: /
              port: 8888
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Then schedule this by running:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl create -f deployment.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can check on the deployment’s progress using:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl get deployment hello
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This should show your deployment is available, such as:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hello     1         1         1            1           37s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Create a service for this workload by creating a file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;service.yaml&lt;/code&gt; containing:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;--- 
apiVersion: v1
kind: Service
metadata: 
  name: hello
  namespace: default
spec: 
  ports: 
    - 
      name: http
      port: 80
      protocol: TCP
      targetPort: 8888
  selector: 
    app: hello
  type: NodePort
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Note that while the service does run on port 80, this service does not have public ingress into the cluster. The hello service is only accessible from within the server. Create this service using:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl create -f service.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can check on the service’s progress using:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl get service hello
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Your service should be assigned an internal IP address, but not a public IP address as we don’t wish this service to have public ingress. Instead we’re going to route all of our traffic to this service via an ingress controller.&lt;/p&gt;

&lt;h2 id=&quot;ingress&quot;&gt;Ingress&lt;/h2&gt;
&lt;p&gt;We should now have a service running on port 80 serving traffic within the cluster to our container running in a pod on port 8888. Now we can create our Ingress Controller.&lt;/p&gt;

&lt;p&gt;Because we wish to use HTTPS for our service we need to use a certificate from Let’s Encrypt. Cert Manager will obtain this certificate for us but initially we will need to create the Ingress Controller without the references to the certificates. This is because you need to create the ingress first to obtain the IP address which is used when creating the DNS A-record (we will see this later); all before we can get the certificate.&lt;/p&gt;

&lt;p&gt;Create a file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ingress.yaml&lt;/code&gt; containing:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;--- 
apiVersion: extensions/v1beta1
kind: Ingress
metadata: 
  name: demo-ingress
spec: 
  rules: 
    - 
      host: YOUR_DOMAIN_NAME_HERE
      http: 
        paths: 
          - 
            backend: 
              serviceName: hello
              servicePort: 80
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;YOUR_DOMAIN_NAME_HERE&lt;/code&gt; with your own domain name, in a format such as example.com This manifest sets up a rule which matches requests from your host (as an HTTP host header field) to a particular service—in this instance the hello service running on port 80.&lt;/p&gt;

&lt;p&gt;At this stage we are currently not using TLS. We want to initially create this ingress without TLS until we have cert-manager running and a demo-tls secret available. Otherwise we’ll have no ingress at this stage. It’s a little bit horse and cart but we’ll add TLS later.&lt;/p&gt;

&lt;p&gt;To create the ingress controller run:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl create -f ingress.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Obtain the external IP address of the ingress:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl get ing demo-ingress
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;It may take some time to assign an external IP—as GCP is provisioning you a load-balancer—but when one is assigned, visit your domain management console and create an A record with the new IP. This will mean that a DNS lookup of your domain name will yield the IP address of your ingress controller so requests to your domain name will be forwarded to your cluster’s Ingress Controller.&lt;/p&gt;

&lt;p&gt;This all takes some time; to get an external IP, to propagate the DNS A record, and then for everything to start responding. Visiting your domain will likely return a Google 404 for a while as it takes some time to provision the load-balancers and is normal.&lt;/p&gt;

&lt;p&gt;While this is happening you can watch cluster events using:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl get events --sort-by=.metadata.creationTimestamp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;…and to check the progress of your DNS record you can use dig.&lt;/p&gt;

&lt;p&gt;When, eventually, everything is working, you should see a response of hello, world! when requesting http://YOUR_DOMAIN_NAME/. Note we use http at the moment. Next we’ll set up https&lt;/p&gt;

&lt;h2 id=&quot;install-and-configure-certificate-manager&quot;&gt;Install and Configure Certificate Manager&lt;/h2&gt;
&lt;p&gt;We will use Jetstack’s cert-manager to manage certificates, providing us with TLS.&lt;/p&gt;

&lt;p&gt;Install cert-manager using the command:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;helm install stable/cert-manager \
  --name cert-manager \
  --namespace kube-system \
  --version v0.5.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Next we’re going to create a ClusterIssuer which will provide the ability to obtain certificates from a central authority; Letsencrypt in our case. A cluster-wide issuer like this may not be the correct option for a multi-tenanted cluster but for our use it’s fine. Something to keep in mind at least. &lt;a href=&quot;https://dictionary.cambridge.org/dictionary/english/ymmv&quot;&gt;YMMV&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Create a file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;issuer.yaml&lt;/code&gt; with the contents:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;--- 
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata: 
  name: letsencrypt-issuer
spec: 
  acme: 
    email: ADD_YOUR_EMAIL@ADDRESS.com
    http01: {}
    privateKeySecretRef: 
      name: letsencrypt
    server: &quot;https://acme-v02.api.letsencrypt.org/directory&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Create the issuer by running:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl apply -f issuer.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;To watch the status of the issuer, run:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl describe ClusterIssuer letsencrypt-issuer
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;…which will show you the issuer’s progress in obtaining a certificate.&lt;/p&gt;

&lt;p&gt;Next create the cluster’s Certificate which will hold the certificate that Let’s Encrypt has issued us.&lt;/p&gt;

&lt;p&gt;Create a file called certificate.yaml containing:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;--- 
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata: 
  name: demo-certificate
  namespace: default
spec: 
  acme: 
    config: 
      - 
        domains: 
          - YOUR_DOMAIN_NAME_HERE
        http01: 
          ingress: demo-ingress
  commonName: YOUR_DOMAIN_NAME_HERE
  dnsNames: 
    - YOUR_DOMAIN_NAME_HERE
  issuerRef: 
    kind: ClusterIssuer
    name: letsencrypt-issuer
  secretName: demo-tls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Create the certificate resource with:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl create -f certificate.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;To check the progress of your certificate, run:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl describe certificate demo-certificate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You may see some messages such as http-01 self check failed for domain or ValidateError but these are likely to be transitory and things should begin to settle down when you enable TLS through the Ingress Controller.&lt;/p&gt;

&lt;p&gt;Previously we created the Ingress resource with some lines commented out. Now we should have our certificate issued so can enable TLS via the ingress controller.&lt;/p&gt;

&lt;p&gt;Edit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ingress.yaml&lt;/code&gt; to contain the following:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;--- 
apiVersion: extensions/v1beta1
kind: Ingress
metadata: 
  annotations: 
    certmanager.k8s.io/acme-http01-edit-in-place: &quot;true&quot;
    certmanager.k8s.io/cluster-issuer: letsencrypt-issuer
  name: demo-ingress
spec: 
  rules: 
    - 
      host: faworth.co.uk
      http: 
        paths: 
          - 
            backend: 
              serviceName: hello
              servicePort: 80
  tls: 
    - 
      secretName: demo-tls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Then update the ingress controller using (note apply rather than create ):&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl apply -f ingress.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Now running:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl get ing demo-ingress
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;…you should see 80, 443 in the PORTS column as we now have TLS over port 433 enabled.&lt;/p&gt;

&lt;p&gt;After some time your domain should start responding using TLS: https://YOUR_DOMAIN_NAME_HERE/ (note https rather than http now). However this could take quite a while. At first you may see nothing, then 404s from Google, then eventually your site should be responding.&lt;/p&gt;

&lt;p&gt;Now we have our service (hello, world) accessible via TLS with a Let’s Encrypt certificate!&lt;/p&gt;
</description>
        <pubDate>Wed, 26 Jun 2019 00:00:00 +0000</pubDate>
        <link>http://bet.andr.io/gcp/tls/gke/kubernetes/k8s/ssl/certificate/2019/06/26/kubernetes-ingress-with-tlson-gke.html</link>
        <guid isPermaLink="true">http://bet.andr.io/gcp/tls/gke/kubernetes/k8s/ssl/certificate/2019/06/26/kubernetes-ingress-with-tlson-gke.html</guid>
        
        
        <category>gcp</category>
        
        <category>tls</category>
        
        <category>gke</category>
        
        <category>kubernetes</category>
        
        <category>k8s</category>
        
        <category>ssl</category>
        
        <category>certificate</category>
        
      </item>
    
      <item>
        <title>Metal Band Name AI Twitter Bot</title>
        <description>&lt;p&gt;&lt;em&gt;Metal Band Bot is a Twitter bot which uses a Recurrent Neural Network to think up a brand new metal band name at two minutes to midnight every night.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Metal Band Bot (&lt;a href=&quot;https://twitter.com/MetalBandBot&quot;&gt;twitter.com/MetalBandBot&lt;/a&gt;) uses the &lt;a href=&quot;https://github.com/minimaxir/textgenrnn&quot;&gt;textgenrnn&lt;/a&gt; library to train an RNN model from a 
&lt;a href=&quot;https://github.com/betandr/mbrnn/blob/master/data/bands.txt&quot;&gt;data file of band names&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;textgen.train_from_file(&apos;data/bands.txt&apos;, num_epochs=1)
textgen.save(&apos;metal_bands.hdf5&apos;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This model is then used to generate a new, unique name.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;textgen = textgenrnn(&apos;weights/bands.hdf5&apos;)
band_name = textgen.generate(1, return_as_list=True, temperature=0.8)[0]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/mbb.png&quot; alt=&quot;Maze&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;code&quot;&gt;Code&lt;/h2&gt;

&lt;p&gt;Metal Band Bot: &lt;a href=&quot;https://github.com/betandr/mbrnn&quot;&gt;github.com/betandr/mbrnn&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Wed, 08 Aug 2018 00:00:00 +0000</pubDate>
        <link>http://bet.andr.io/projects/2018/08/08/metal-band-bot.html</link>
        <guid isPermaLink="true">http://bet.andr.io/projects/2018/08/08/metal-band-bot.html</guid>
        
        
        <category>projects</category>
        
      </item>
    
      <item>
        <title>Delivering with GCP at the BBC</title>
        <description>&lt;p&gt;&lt;em&gt;There probably is a software engineer’s equivalent of the Rifleman’s Creed and it’s probably something like…&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;This is my infrastructure stack.

There are many like it, but this is mine.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So, this is ours… but first, some context.&lt;/p&gt;

&lt;p&gt;Having been at the BBC for 9 years I’ve seen a few major changes in infrastructure. When I started, our process was largely to edit HTML and Javascript locally and FTP the changes up to the live server. The live servers were the source control and to make a change a Web Developer would copy the live site and update it, then send it back again. I’m not sure if I’m actually allowed to fully recount one of the stories of that time but suffice to say the Wayback Machine is a good method to recover lost HTML…&lt;/p&gt;

&lt;p&gt;At this time we also built Rails apps and hosted these on our own on-prem infrastructure and had a great Rails team. Incidentally, it was a Rails App that powered my first BBC project; Visual Radio.&lt;/p&gt;

&lt;p&gt;The next big shift-change was to a platform called Forge. This supported Java services, implemented usually in Spring, and PHP web applications, implemented with frameworks such as Zend. On the journey to a mature software organisation this was a time that many found restrictive but it supported the organisation in defining process and control over how we deliver features to our audience.&lt;/p&gt;

&lt;p&gt;Shortly after we had finished migrating all of our apps to Forge, we started building new apps in the cloud with Amazon Web Services. To support this new platform we developed a system called Cosmos which acts as a delivery shim between the engineering and delivery teams at the BBC and the cloud provider. This provides a mechanism by which we can safely and, with relatively low friction, release to our cloud services provider and the audience. Cosmos also provides secure VMs, rollbacks, and a host of other benefits.&lt;/p&gt;

&lt;p&gt;Let’s skip to the present and, 10months ago, I joined a fledgling team at the BBC called Datalab, a team tasked with bringing together what we know about our content into one place and using machine learning to enrich it. Not only did we have our own roadmap of products to deliver but—as we were a greenfield project without any legacy—we would also move into Google Cloud Platform (GCP). We arrived at GCP with no infrastructure and no legacy so set about defining how we’d use this new platform.&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/1951_election.jpg&quot; alt=&quot;‘Data visualisation’ during the 1951 general election.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;‘Data visualisation’ during the 1951 general election.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Previously, to build an API, we’d typically employ an architectural pattern of some application code (such as Scala), implemented with a framework (such as Akka), running in an application server (such as Spray or Akka HTTP), packaged as a binary (such as an RPM package), deployed onto a snapshot machine image (such as CentOS 7), deployed as a VM (such as an EC2 instance), running behind a load-balancer (such as an Elastic Load Balancer) [see fig 1.]. Usually we’d have a set number of these instances running and scale out under increased load. We’d follow the maxim of scale out fast, scale in slowly to benefit from the overhead and cost of spinning up new instances. It takes in the order of large values of seconds to small values of minutes to scale out.&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/deliver_gcp_fig1.png&quot; alt=&quot;Fig. 1 — API stack&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Fig. 1 — API stack&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To deploy a new feature we’d check the feature into our Git repository, then the build pipeline would be triggered. This would build a new VM for us, with our application binary baked in. We could then deploy to our pre-production environments using a rolling deployment which replaced the VMs with a new instance one by one. After running our smoke tests we’d promote this to production, again using a rolling deployment. If the smoke tests failed we’d rollback by pushing an older, known to be working, version of our application.&lt;/p&gt;

&lt;p&gt;Moving into GCP allowed us the opportunity to re-think our stack and the first thing on our list was to benefit from containers. Let’s deftly skip over why containers are a good idea but they were a great fit for our project; a system built using microservices.&lt;/p&gt;

&lt;p&gt;We also made the decision to move to Google Kubernetes Engine (GKE) as our container orchestrator. This means our containers need only be declared as workloads and GKE will make this request a reality. We can benefit from autoscaling in our Kubernetes (K8s) cluster and benefit from another optimisation; swapping HTTP internal traffic to RPC. Rather than our components being deployed in a VM and communicating via HTTP our pattern now is to have external HTTP(S) ingress and internal RPC communications. We implemented this with gRPC.&lt;/p&gt;

&lt;p&gt;The choice of language in the implementation of any component is important for an organisation and we decided to use Python as our house language. This is mostly because our team comprises of a number of different roles such as data scientist, data engineer, and software engineer but we all have Python in common—and we think it’s important that everybody can work on all parts of the system. Our containers run Flask/Green Unicorn for HTTP and gRPC Protocol Buffers for RPC. Compared to a full VM, we can scale out in seconds—especially due to each container having a lightweight parent based on Alpine Linux and the actual container layer is very small.&lt;/p&gt;

&lt;p&gt;We now have containers and a cluster to put them on [see Fig. 2], but we needed to think about how we delivered features to production. One of our goals is the potential to release a feature on the first morning you join the team. This means there should be no complex processes, no restricted access, no arcane knowledge, and no hierarchy.&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/deliver_gcp_fig2.png&quot; alt=&quot;Fig. 2 — Container Application Structure&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Fig. 2 — Container Application Structure&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Firstly we merge features we wish to release into our Git repository. This triggers our Continuous Integration (CI) pipeline (in fact any commit to any branch does). To run our CI we use Drone and define a pipeline for each component. With Drone we can run our automated testing and validate the containers we’re building. Our repos are then in a constant state of healthy or broken (which inhibits a release).&lt;/p&gt;

&lt;p&gt;To release that feature we tag it, which triggers Google Container Builder to build our image and push it to our Container Registry.&lt;/p&gt;

&lt;p&gt;At this point our Continuous Delivery (CD) tooling takes over, triggered by the presence of a new version of a container. To run our CD we use Spinnaker which runs our delivery pipelines. These pipelines define how a microservice should be deployed into the cluster. Typically we deploy automatically into the cluster to our stage environment (more on that later) and then send a message to our team’s Slack channel to let us know a deployment is ready to be promoted. Our smoke tests are then run and the feature promoted to production.&lt;/p&gt;

&lt;p&gt;These deployments run as a green/blue deployment (or red/black in Spinnaker/Netflix parlance). So instead of performing a rolling update of replacing node by node, we spin up a mirror deployment on our cluster and switch the service from the old deployment to the new using traffic splitting. This means that the previous deployment is still running and we can slowly bring a new feature into service and run canary tests while this is happening. If we experience any issues we can simply send the traffic back to the old deployment without any issues. We leave the previous deployment running, but disabled, until we’re confident the new deployment is operating correctly. Google also recently announced the availability of Kayenta on the platform which provides automated canary analysis tooling.&lt;/p&gt;

&lt;p&gt;We generally run two environments; stage and production. However, because we run everything on a K8s cluster these environments can be as similar to each other as we need them to be; we don’t have a pre-production environment which is markedly different to production. These environments are deployed as versions of a Kubernetes service. So each container, say foo-service, is deployed as a Kubernetes service foo-service-stage and foo-service-production. This approach allows us to deploy interim environments too, for experiments etc. So foo-service-experiment-bar environments are possible too.&lt;/p&gt;

&lt;p&gt;Our microservices communicate via RPC but to provide HTTP ingress we use a Kubernetes Ingress Controller which accepts HTTP, terminates the TLS connection, and forwards to our service within the cluster. This is implemented with a Google Cloud Load Balancer and provides secured ingress to our cluster.&lt;/p&gt;

&lt;p&gt;We’re not replacing our VM strategy but adding more deployment options to our enterprise. While this post started with a story of how far we’ve come in the last few years, I’m sure that if we check back in a few months things will have continued to change and move forward at an ever-increasing rate at a velocity that’s well supported by our infrastructure and tooling.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href=&quot;https://medium.com/bbc-design-engineering/how-we-deliver-with-gcp-at-the-bbc-1c9812acf3a1&quot;&gt;How we deliver with GCP at the BBC&lt;/a&gt; on the &lt;a href=&quot;https://medium.com/bbc-design-engineering/&quot;&gt;BBC Design + Engineering Blog at Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 23 Jul 2018 00:00:00 +0000</pubDate>
        <link>http://bet.andr.io/gcp/bbc/sre/2018/07/23/how-we-deliver-at-the-bbc-with-gcp.html</link>
        <guid isPermaLink="true">http://bet.andr.io/gcp/bbc/sre/2018/07/23/how-we-deliver-at-the-bbc-with-gcp.html</guid>
        
        
        <category>gcp</category>
        
        <category>bbc</category>
        
        <category>sre</category>
        
      </item>
    
  </channel>
</rss>
