<?xml version="1.0"?>
<rss version="2.0">
    <channel>
        <title>thebsbdox</title>
        <link>http://thebsdbox.co.uk</link>
        <description>Cheap conjorur of tricks</description>
        <language>en</language>
        <pubDate>Wed, 14 Jan 2026 15:05:39 +0000</pubDate>
        <lastBuildDate>Wed, 14 Jan 2026 15:05:39 +0000</lastBuildDate>
        <category>eBPF</category>
        <category>Networking</category>
        <category>Layer 7</category>
        <category>networking</category>
        <category>Service Mesh</category>
        <category>Kubernetes</category>
        <category>Egress</category>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2026/01/14/Adding-a-gateway-to-kube-vip/</guid>
            <title>Adding a gateway to kube-vip</title>
            <link>http://thebsdbox.co.uk/2026/01/14/Adding-a-gateway-to-kube-vip/</link>
            <category>eBPF</category>
            <category>Networking</category>
            <category>Layer 7</category>
            <category>Egress</category>
            <pubDate>Wed, 14 Jan 2026 15:05:39 +0000</pubDate>
            <description><![CDATA[ &lt;p&gt;So …&lt;/p&gt;
&lt;div class=&#34;twitter-wrapper&#34;&gt;&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;a href=&#34;https://twitter.com/thebsdbox/status/1860044871702290553&#34;&gt;&lt;/a&gt;&lt;/blockquote&gt;&lt;/div&gt;&lt;script async defer src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;

&lt;p&gt;That was over a year ago, and well I didn’t write a service mesh (in the traditional sense) although I did write some interesting bits and finally did some &lt;code&gt;MutatingWebHook&lt;/code&gt; stuff. With my initial idea being to use sidecars to implement all of the magic proxy stuff. Although I largely ended up getting stuck with some issues around Kubernetes v1.33 and &lt;code&gt;EphemeralContainers&lt;/code&gt; which has largely been fixed at this point. So given the Christmas break I decided to have another crack at it!&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tl;dr&lt;/code&gt; where’s the code -&amp;gt; &lt;a href=&#34;https://github.com/kube-vip/kube-gateway&#34;&gt;https://github.com/kube-vip/kube-gateway&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;kube-gateway-v0-0001&#34;&gt;&lt;a href=&#34;#kube-gateway-v0-0001&#34; class=&#34;headerlink&#34; title=&#34;kube-gateway (v0.0001)&#34;&gt;&lt;/a&gt;kube-gateway (v0.0001)&lt;/h2&gt;&lt;p&gt;The architecture for the kube-gateway (today) consists of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The gateway &lt;strong&gt;watcher&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;The proxy&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;The-gateway-watcher&#34;&gt;&lt;a href=&#34;#The-gateway-watcher&#34; class=&#34;headerlink&#34; title=&#34;The gateway watcher&#34;&gt;&lt;/a&gt;The gateway watcher&lt;/h3&gt;&lt;p&gt;This currently is just a pod that has the permissions to watch for pod &lt;code&gt;updates&lt;/code&gt; for things such as an IP address has been assigned or that an annotation has been applied. When the correct annotations are applied to a pod then the watcher will modify the &lt;code&gt;pod.Spec&lt;/code&gt;, which I hear you cry 😿 is READ-ONLY! This is true (partially), but the read-only sections typicaly mean I can’t add a volume/configmap/secret and that can render it impossible to add things like secrets or sidecars to a pod (at run-time) impossible. &lt;strong&gt;However&lt;/strong&gt;, it turns out that the &lt;code&gt;pod.Spec.EphemeralContainer&lt;/code&gt; is read-write so this does provide us with an opportunity to add an additional container although we still can’t add any volumes as they are immutable.&lt;/p&gt;
&lt;p&gt;So to get around this the watcher has this snippet of code:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;// Create certificates and then a Kubernetes secret&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;i.c.createCertificate(pod.Name, pod.Status.PodIP)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// create a secret in kubernetes&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;err := i.c.loadSecret(pod.Name, i.clientset)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	slog.Error(err)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ec.EnvFrom = append(ec.EnvFrom, v1.EnvFromSource&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	SecretRef: &amp;amp;v1.SecretEnvSource&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		LocalObjectReference: v1.LocalObjectReference&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			Name: secret, // map to the secret we created earlier&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		&amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		Optional: nil,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;The watcher itself has it’s own Certificate Authority certs and will use them to create new certificates for the pod (based upon it’s IP address), which are then loaded into a Kubernetes secret. Finally we can reference the secrets inside the &lt;code&gt;EphermeralContainer&lt;/code&gt; as environment variables as this is allowed in the Kubernetes spec!&lt;/p&gt;
&lt;p&gt;Then the annotations we apply are typically passed to the &lt;code&gt;EphermeralContainer&lt;/code&gt; as additional environment variables that the kube-gateway uses!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/podSpec.jpg&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;Annotating-your-pod-for-mTLS-🔐&#34;&gt;&lt;a href=&#34;#Annotating-your-pod-for-mTLS-🔐&#34; class=&#34;headerlink&#34; title=&#34;Annotating your pod for mTLS 🔐&#34;&gt;&lt;/a&gt;Annotating your pod for mTLS 🔐&lt;/h3&gt;&lt;p&gt;To enable mTLS between two pods we will need to apply a gateway to each pod (to handle the encryption and decryption on either side).&lt;br&gt;&lt;strong&gt;BONUS:&lt;/strong&gt; If you want to offload the TLS encryption the kube-gateway supports kTLS (&lt;a href=&#34;https://docs.kernel.org/networking/tls-offload.html&#34;&gt;in-kernel TLS&lt;/a&gt;)!&lt;/p&gt;
&lt;p&gt;To enable this then each pod will need the following annotation &lt;strong&gt;before&lt;/strong&gt; enabling encryption:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;kubectl annotate pod &amp;lt;pod name&amp;gt; kube-gateway.io/ktls=&amp;quot;true&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This is done with the following:&lt;/p&gt;
&lt;p&gt;This will apply the gateway to pod-01: &lt;/p&gt;
&lt;p&gt;&lt;code&gt;kubectl annotate pod pod-01 kube-gateway.io/encrypt=&amp;quot;true&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This will then apply the other gateway to pod-02: &lt;/p&gt;
&lt;p&gt;&lt;code&gt;kubectl annotate pod pod-02 kube-gateway.io/encrypt=&amp;quot;true&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&#34;Annotating-your-pod-for-AI-🤖&#34;&gt;&lt;a href=&#34;#Annotating-your-pod-for-AI-🤖&#34; class=&#34;headerlink&#34; title=&#34;Annotating your pod for AI 🤖&#34;&gt;&lt;/a&gt;Annotating your pod for AI 🤖&lt;/h3&gt;&lt;h4 id=&#34;Annotate-your-LLM&#34;&gt;&lt;a href=&#34;#Annotate-your-LLM&#34; class=&#34;headerlink&#34; title=&#34;Annotate your LLM&#34;&gt;&lt;/a&gt;Annotate your LLM&lt;/h4&gt;&lt;p&gt;This is highly experimental at the moment, but the feature set will expand as it is worked on 😀&lt;/p&gt;
&lt;p&gt;As of today (15th January 2026) the implementation is expecting your LLM to be running within the cluster somewhere, this is a self imposed limitation of the kube-gateway (and will be changed soon). &lt;/p&gt;
&lt;p&gt;A kube-gateway endpoint is required (&lt;strong&gt;today&lt;/strong&gt;) in order to facilitate end-to-end connectivity, we will need to annotate our LLM with a gateway endpoint annotation:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;kubectl annotate pod -n ollama ollama-bbbc54cbc-8mqt5 kube-gateway.io/endpoint=&amp;quot;true&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Now anything that is interacting with it can be modified with our gateway annotations!&lt;/p&gt;
&lt;h4 id=&#34;Annotate-your-AI-workloads&#34;&gt;&lt;a href=&#34;#Annotate-your-AI-workloads&#34; class=&#34;headerlink&#34; title=&#34;Annotate your AI workloads&#34;&gt;&lt;/a&gt;Annotate your AI workloads&lt;/h4&gt;&lt;p&gt;Imagine we have some workloads created a long time ago (3 weeks in the world of AI 🙄), and these workloads are using an old/outdated/expensive model and although they workloads perform OK we want to improve them. With the kube-gateway we can modify this on the fly.&lt;/p&gt;
&lt;p&gt;Depending on the workload type or language/code or SDK used the connection to the LLM, then a &lt;code&gt;HTTP keep-alive&lt;/code&gt; may be used and if this is the case then the existing TCP session will be used for all API calls (and we wont be able to pass it to the gateway). So the netflush annotion will instruct the kube-gateway pod on startup to use some eBPF 🐝 magic to knock the TCP session to be recreated without disturbing the connection. (this annotation is optional and will need investigating, the python ollama SDK doesn’t require this for example).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;kubectl annotate pod aiworkload kube-gateway.io/netflush=&amp;quot;true&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The next annation for our aiworkload is to change the model:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;kubectl annotate pod aiworkload kube-gateway.io/ai-model=&amp;quot;gemma2:2b&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Now whenever there is a request our kube-gateway will modify the traffic transparently to use the model specified!&lt;/p&gt;
&lt;p&gt;Finally &lt;strong&gt;enable&lt;/strong&gt; the kube-gateway on the workload:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;kubectl annotate pod aiworkload kube-gateway.io/ai=&amp;quot;true&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;h2 id=&#34;eBPF-mTLS-and-AI-oh-my&#34;&gt;&lt;a href=&#34;#eBPF-mTLS-and-AI-oh-my&#34; class=&#34;headerlink&#34; title=&#34;eBPF, mTLS and AI oh my..&#34;&gt;&lt;/a&gt;eBPF, mTLS and AI oh my..&lt;/h2&gt;&lt;p&gt;So under the covers what is actually occuring in order to facilitate this? &lt;/p&gt;
&lt;p&gt;Well simply put there are three parts to it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The watcher takes input and adds an ephemeral container to a specified workload&lt;/li&gt;
&lt;li&gt;Some eBPF code is injected to ensure that traffic from the correct process has it’s original destination captured &lt;strong&gt;and&lt;/strong&gt; is now sent to a &lt;strong&gt;new&lt;/strong&gt; destination, which is our gateway!&lt;/li&gt;
&lt;li&gt;Our gateway in the ephermal container recieves the connection, and will lookup the original destination. We can make changes to the TCP stream/data (such as in the AI use-case) or we can encrypt it and send it to the pod that now also has a gateway as a ephemeral container attached to decrypt the traffic and send it to the original application.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;What’s-next&#34;&gt;&lt;a href=&#34;#What’s-next&#34; class=&#34;headerlink&#34; title=&#34;What’s next?&#34;&gt;&lt;/a&gt;What’s next?&lt;/h3&gt;&lt;p&gt;At this point we can easily sit in the middle of any traffic and transparently mutate it, which allows us a huge opportunity to add in all sorts of guardrails and monitoring opportunities.&lt;/p&gt;
&lt;h4 id=&#34;mTLS&#34;&gt;&lt;a href=&#34;#mTLS&#34; class=&#34;headerlink&#34; title=&#34;mTLS&#34;&gt;&lt;/a&gt;mTLS&lt;/h4&gt;&lt;p&gt;The main goal in the future would be to redirect to a per-node gateway, which should be doable and drastically reduce the need for a gateway per pod architecture.&lt;/p&gt;
&lt;h4 id=&#34;AI-workloads&#34;&gt;&lt;a href=&#34;#AI-workloads&#34; class=&#34;headerlink&#34; title=&#34;AI workloads&#34;&gt;&lt;/a&gt;AI workloads&lt;/h4&gt;&lt;p&gt;Remove the need for a gateway endpoint, which means that only the “client” side would need to have a gateway attached and would mean the “client” can connect to anything transparently. Additionally extending the functionality of what we may want to mutate in the AI payload, such as limitating tokens or having rules about prompts etc.. I guess we shall see!&lt;/p&gt;
&lt;p&gt;Either head to &lt;a href=&#34;https://kube-gateway.io/&#34;&gt;kube-gateway.io&lt;/a&gt; or the Github repository &lt;a href=&#34;https://github.com/kube-vip/kube-gateway&#34;&gt;https://github.com/kube-vip/kube-gateway&lt;/a&gt;&lt;/p&gt;
 ]]></description>
        </item>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2025/07/17/egress-v2-in-kube-vip/</guid>
            <title>Egress v2 in kube-vip</title>
            <link>http://thebsdbox.co.uk/2025/07/17/egress-v2-in-kube-vip/</link>
            <category>Networking</category>
            <category>Kubernetes</category>
            <category>Egress</category>
            <pubDate>Thu, 17 Jul 2025 10:19:56 +0100</pubDate>
            <description><![CDATA[ &lt;p&gt;I’m not sure if it was my time working for &lt;a href=&#34;https://docker.io/&#34;&gt;Docker&lt;/a&gt; or just the general confusion around what various companies and startups are doing around container images, &lt;strong&gt;but&lt;/strong&gt; I’ve always opted to go &lt;code&gt;FROM scratch&lt;/code&gt; where possible. Several years ago we (me) wanted to implement &lt;code&gt;egress&lt;/code&gt; control within &lt;a href=&#34;https://kube-vip.io/&#34;&gt;Kube-vip&lt;/a&gt;, although up to that point I didn’t really want to be involved in the datapath I decided to go for it. My main concern about the datapath is that people get very upset when their packets arrive backwards or get lost 😱. However after a bit of research and seeing what the rest of the world was doing, I decided to opt for a slightly different (if not weird approach) which was to tie the service to a pod and map the external address 1:1 with a pod. There is a ton of detail about that in this blog post 👉 &lt;a href=&#34;https://thebsdbox.co.uk/2024/11/03/Egress/&#34;&gt;https://thebsdbox.co.uk/2024/11/03/Egress/&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;iptables-amp-nftables&#34;&gt;&lt;a href=&#34;#iptables-amp-nftables&#34; class=&#34;headerlink&#34; title=&#34;iptables &amp;amp; nftables&#34;&gt;&lt;/a&gt;iptables &amp;amp; nftables&lt;/h2&gt;&lt;p&gt;In order to implement the egress source NAT I decided to largely duplicate the behaviour of &lt;code&gt;kube-proxy&lt;/code&gt;, which is to SNAT the address of the pod to the address of the load balancer service and job done 🎉. However in order to do that we needed to start adding &lt;code&gt;iptables&lt;/code&gt; binaries into the kube-vip images. This now suddenly means adding in other dependancies, and as we all know more dependancies means a larger vector for attacking. Under most circumstances a lot of people wouldn’t mind and/or care but unfortunately kube-vip serves as the control plane HA VIP and is in a critical position, so exploiting it would be a big issue. So now we introduce two kube-vip images, one from &lt;code&gt;scratch&lt;/code&gt; and another with all the iptables and dependancies.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tech-debt = tech-debt + 1;&lt;/code&gt;&lt;/p&gt;
&lt;h2 id=&#34;The-state-of-iptables-amp-nftables&#34;&gt;&lt;a href=&#34;#The-state-of-iptables-amp-nftables&#34; class=&#34;headerlink&#34; title=&#34;The state of iptables &amp;amp; nftables&#34;&gt;&lt;/a&gt;The state of iptables &amp;amp; nftables&lt;/h2&gt;&lt;p&gt;&lt;img src=&#34;https://i.makeagif.com/media/9-30-2016/y1ivlU.gif&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;What-you-want-some-details-fine&#34;&gt;&lt;a href=&#34;#What-you-want-some-details-fine&#34; class=&#34;headerlink&#34; title=&#34;What, you want some details.. fine&#34;&gt;&lt;/a&gt;What, you want some details.. fine&lt;/h3&gt;&lt;p&gt;So we’ve been using iptables for years and it largely works, it has its issues with scale but so does everything at one point or another. One of the main issues has been programming rules or lower level abstractions, given we’re ultimately programming the kernel can we get rid of all the binaries. Well the &lt;a href=&#34;https://www.netfilter.org/documentation/FAQ/netfilter-faq-4.html#ss4.5&#34;&gt;answer&lt;/a&gt; to that is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The answer unfortunately is: No.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Additionally:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We don’t guarantee a stable interface&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;… awesome, and it has been this way for years and years.&lt;/p&gt;
&lt;h3 id=&#34;When-is-iptables-not-iptables&#34;&gt;&lt;a href=&#34;#When-is-iptables-not-iptables&#34; class=&#34;headerlink&#34; title=&#34;When is iptables not iptables&#34;&gt;&lt;/a&gt;When is iptables &lt;strong&gt;not&lt;/strong&gt; iptables&lt;/h3&gt;&lt;p&gt;When it is &lt;strong&gt;nftables&lt;/strong&gt;, of course 🤷🏼‍♂️. It turns out that for the last few years all of our legacy iptables code hasn’t been churning out iptables rules, you’ve been living a lie! Feel free to read &lt;a href=&#34;https://developers.redhat.com/blog/2020/08/18/iptables-the-two-variants-and-their-relationship-with-nftables#the_kernel_api&#34;&gt;this madness&lt;/a&gt; at your earliest convenience. But the &lt;code&gt;tl;dr&lt;/code&gt; is that in most places the iptables binary is actually a new program that will take your iptables syntax and pump out nftables rules. &lt;/p&gt;
&lt;h2 id=&#34;So-why-is-no-one-writing-nftables-rules&#34;&gt;&lt;a href=&#34;#So-why-is-no-one-writing-nftables-rules&#34; class=&#34;headerlink&#34; title=&#34;So why is no-one writing nftables rules?&#34;&gt;&lt;/a&gt;So why is no-one writing nftables rules?&lt;/h2&gt;&lt;p&gt;Well because you didn’t have to, if the iptables rules are being on the-fly being written as nftables rules then just leave it to that. But additionally the syntax is dramatically different to iptables rules, which means that it comes with it’s own learning curve. This and a few buggy releases of the userland tooling have unfortunately slowed it’s adoption. &lt;strong&gt;However&lt;/strong&gt;, recently there has been quite a few things happening in the nftables space! Namely Kubernetes 1.31 introduced the capability to use &lt;code&gt;kube-proxy&lt;/code&gt; in an nftables mode, which is detailed &lt;a href=&#34;https://kubernetes.io/blog/2025/02/28/nftables-kube-proxy/&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;The-finally-growing-ecosystem-for-nftables&#34;&gt;&lt;a href=&#34;#The-finally-growing-ecosystem-for-nftables&#34; class=&#34;headerlink&#34; title=&#34;The (finally) growing ecosystem for nftables&#34;&gt;&lt;/a&gt;The (finally) growing ecosystem for nftables&lt;/h2&gt;&lt;p&gt;The &lt;a href=&#34;kubernetes.io&#34;&gt;Kubernetes&lt;/a&gt; project has finally started to embrace nftables and has opted to write a wrapper around nftables, which is called &lt;a href=&#34;https://github.com/kubernetes-sigs/knftables&#34;&gt;knftables&lt;/a&gt;. This is a wrapper around the nftables binaries, which unfortunately still requires bringing along both the nftables binaires, the libraries and any other dependancies. But what is there if you’re a slightly mad purist who demands &lt;code&gt;scratch&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;A-pure-Go-package-for-manipulating-nftables-🎉&#34;&gt;&lt;a href=&#34;#A-pure-Go-package-for-manipulating-nftables-🎉&#34; class=&#34;headerlink&#34; title=&#34;A pure Go package for manipulating nftables 🎉&#34;&gt;&lt;/a&gt;A pure Go package for manipulating nftables 🎉&lt;/h3&gt;&lt;p&gt;I’ve been watching &lt;a href=&#34;https://github.com/google/nftables&#34;&gt;https://github.com/google/nftables&lt;/a&gt; for quite some time, actually since &lt;a href=&#34;https://github.com/google/nftables/issues/248#issuecomment-1830366097&#34;&gt;November 2023&lt;/a&gt;. The thing that was missing from this was the capability to implement SNAT rules in this pure Go implementation. Luckily this feature was implemented not too long after, but then life gets in the way and you’re too busy climbing a mountain 🧗🏻 or doing something else 🚵🏻.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;However&lt;/strong&gt; I finally managed to find some time several weeks ago to finally investigate what the code would look like in order to finally rip out all the external dependancies and program the kernel directly!&lt;/p&gt;
&lt;h2 id=&#34;Egress-v2-finally-🥹&#34;&gt;&lt;a href=&#34;#Egress-v2-finally-🥹&#34; class=&#34;headerlink&#34; title=&#34;Egress v2 finally 🥹!&#34;&gt;&lt;/a&gt;Egress v2 finally 🥹!&lt;/h2&gt;&lt;p&gt;We’re calling this Egress v2 as it’s a complete re-write of how egress is handled within kube-vip, although to make things as simple as possible we’re just adding an additional annotation to the service that will be used to control egress :-)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;kube-vip.io/egress: true&lt;/code&gt; - will enable the service to be used as the egress for the pod it is selecting&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kube-vip.io/egress-internal: true&lt;/code&gt; - will use the internal/Egress v2 mechanism for configuring the source NATing&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This move also simplifies the rules that need writing within the kernel, before we would have to write multiple rules to ensure that we didn’t egress when a pod would try and talk to another pod or a service (this would break everything). Additionally added the capability add additional subnets that shouldn’t have the egress address changed or to &lt;strong&gt;only&lt;/strong&gt; egress when sending traffic to a specific destination port. All of this functionality created additional rules that where hard to order or could be accidentally left behind when cleaning rules (on service deletion).&lt;/p&gt;
&lt;h3 id=&#34;Egress-v2-tables-and-chains&#34;&gt;&lt;a href=&#34;#Egress-v2-tables-and-chains&#34; class=&#34;headerlink&#34; title=&#34;Egress v2 tables and chains&#34;&gt;&lt;/a&gt;Egress v2 tables and chains&lt;/h3&gt;&lt;p&gt;To make it super easy to ensure that kube-vip orders and cleans its egress configuration correctly it will do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a table for IPv4 &lt;code&gt;table ip kube_vip_v4&lt;/code&gt; and IPv6 &lt;code&gt;table ip6 kube_vip_v6&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Each service creates it’s own chain &lt;code&gt;chain kube_vip_snat_v4_abc&lt;/code&gt; the chain contains the IP version (&lt;code&gt;v4/v6&lt;/code&gt;) and more importantly the UUID of the service &lt;code&gt;abc&lt;/code&gt; in reality would be much longer&lt;/li&gt;
&lt;li&gt;In each chain is the rule for a particular service &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Example rule:&lt;/em&gt; &lt;code&gt;ip saddr &amp;#123; 10.0.0.1 &amp;#125; ip daddr != 10.0.0.0-10.0.255.255 ip daddr != 10.96.0.0-10.96.255.255 snat to 11.0.0.2&lt;/code&gt;&lt;br&gt;The above rule is typical of the rules created for egress, where we ensure that we &lt;strong&gt;don’t&lt;/strong&gt; egress for the podCIDR or the serviceCIDR. We can see that the POD IP &lt;code&gt;10.0.0.1&lt;/code&gt; is source NAT’d to &lt;code&gt;11.0.0.2&lt;/code&gt; when not going to those networks. &lt;/p&gt;
&lt;p&gt;When a service is deleted we retrieve it’s UUID along with the IP address version and simply delete the chain, and everything is completely tidy. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Additional details&lt;/strong&gt; If kube-vip is forcefully restarted then it won’t garbage collect old rules. However we can ensure kube-vip is restarted with the environment variable &lt;code&gt;egress_clean&lt;/code&gt; which will clean all rules from the kube-vip tables ensuring a clean slate as kube-vip re-participates in being part of hosting load balancing services.&lt;/p&gt;
&lt;h2 id=&#34;The-TL-DR&#34;&gt;&lt;a href=&#34;#The-TL-DR&#34; class=&#34;headerlink&#34; title=&#34;The TL;DR&#34;&gt;&lt;/a&gt;The &lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;We’re going to be removing all external dependancies from kube-vip, so &lt;strong&gt;no more programs&lt;/strong&gt; or other tooling to manage the networking (Great for image size and security)! Kube-vip can now speak directly to the kernel and configure your egress rules, in a much better way than before! 🤩&lt;/p&gt;
&lt;h2 id=&#34;Coming-soon&#34;&gt;&lt;a href=&#34;#Coming-soon&#34; class=&#34;headerlink&#34; title=&#34;Coming soon!&#34;&gt;&lt;/a&gt;Coming soon!&lt;/h2&gt;&lt;p&gt;All of the code has been written and is ready to merge at this time, and we’re hoping to get kube-vip v1.0 in to your hands soon! 🎉&lt;/p&gt;
 ]]></description>
        </item>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2024/12/02/Continuing-building-your-service-mesh/</guid>
            <title>Continuing building your service mesh</title>
            <link>http://thebsdbox.co.uk/2024/12/02/Continuing-building-your-service-mesh/</link>
            <category>eBPF</category>
            <category>networking</category>
            <category>Service Mesh</category>
            <category>Kubernetes</category>
            <pubDate>Mon, 02 Dec 2024 11:54:19 +0000</pubDate>
            <description><![CDATA[ &lt;p&gt;In a previous post I detailed (in my head at least) the “shopping list” of bits needed to implement a simple service mesh, you can read that here &lt;a href=&#34;https://thebsdbox.co.uk/2024/11/30/Building-your-own-service-mesh/&#34;&gt;https://thebsdbox.co.uk/2024/11/30/Building-your-own-service-mesh/&lt;/a&gt;. Whilst that post covered some of the theoretical bits and the eBPF 🐝 magic, my aim is to wrap up all of the other pieces needed here.&lt;/p&gt;
&lt;h2 id=&#34;The-proxy&#34;&gt;&lt;a href=&#34;#The-proxy&#34; class=&#34;headerlink&#34; title=&#34;The proxy&#34;&gt;&lt;/a&gt;The proxy&lt;/h2&gt;&lt;p&gt;In this “build your own” service mesh, the proxy will live next to the application that we care about. &lt;/p&gt;
&lt;p&gt;OMG a sidecar 😱&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/sidecars.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;How that sidecar gets there can be an interesting discussion, so lets look at the choices.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You maintain some yaml, basically your deployments will need to have the sidecar added to to them. This has to be &lt;strong&gt;before&lt;/strong&gt; you deploy as you can’t add a sidecar to an existing pod.&lt;/li&gt;
&lt;li&gt;But what about ephemeral containers! (I hear you ask), well they’re pretty good and yes you can add them to an existing pod. &lt;strong&gt;BUT&lt;/strong&gt;, if you need to mount files (like 🔐 certificates), then &lt;code&gt;Volumes&lt;/code&gt; need adding to the &lt;code&gt;pod.spec&lt;/code&gt; and you can’t do that. AH HA! I hear you think, use secrets and environment variables! that way it doesn’t modify the main body of the &lt;code&gt;pod.spec&lt;/code&gt;, just the &lt;code&gt;pod.spec.ephemeralcontainer[x]&lt;/code&gt;. Great idea, but it doesn’t work and there has been an &lt;a href=&#34;https://github.com/kubernetes/kubernetes/issues/114984&#34;&gt;issue open&lt;/a&gt; about it for nearly (checks notes) 2 years. &lt;/li&gt;
&lt;li&gt;Ye olde sidecar injector! The defacto method of modifying a &lt;code&gt;pod.spec&lt;/code&gt; &lt;strong&gt;before&lt;/strong&gt; it’s actually committed to the Kubernetes API. (I won’t go into detail as people have written about this for some time)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So regardless of how the proxy gets there, it needs to be there. There will be a proxy in every application that we care about. When one pod wants to talk to another pod it will actually be the proxies that are doing the talking!&lt;/p&gt;
&lt;h3 id=&#34;What-is-in-a-proxy&#34;&gt;&lt;a href=&#34;#What-is-in-a-proxy&#34; class=&#34;headerlink&#34; title=&#34;What is in a proxy&#34;&gt;&lt;/a&gt;What is in a proxy&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;Our eBPF code&lt;/li&gt;
&lt;li&gt;Code that will create connections and TCP listeners for sending and receiving traffic&lt;/li&gt;
&lt;li&gt;The required certificates in order for traffic to be encrypted&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;Proxy-startup&#34;&gt;&lt;a href=&#34;#Proxy-startup&#34; class=&#34;headerlink&#34; title=&#34;Proxy startup&#34;&gt;&lt;/a&gt;Proxy startup&lt;/h3&gt;&lt;p&gt;On startup out proxy will determine it’s own &lt;code&gt;pid&lt;/code&gt; and along with the pod CIDR range add that to an eBPF map, it will then attach our eBPF programs to the kernel. Once that has occurred it will start the proxy listener, this is where our proxy will receive all traffic forwarded by our eBPF program. It will then read the certificates (from the filesystem or environment variables) and once they’re loaded it will start another listener for incoming TLS connections! That’s it.&lt;/p&gt;
&lt;h3 id=&#34;Proxy-running-lifecycle&#34;&gt;&lt;a href=&#34;#Proxy-running-lifecycle&#34; class=&#34;headerlink&#34; title=&#34;Proxy running lifecycle&#34;&gt;&lt;/a&gt;Proxy running lifecycle&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;The proxy is listening on it’s internal proxy port&lt;/li&gt;
&lt;li&gt;A new connection is received on this port hijacked by eBPF 🐝, where we do a &lt;code&gt;getsockopt&lt;/code&gt; syscall with the option &lt;code&gt;SO_ORIGINAL_DST&lt;/code&gt;. This returns to us the original destination &lt;em&gt;address&lt;/em&gt; and &lt;em&gt;port&lt;/em&gt; that this connection was heading to before we hijacked it with eBPF.&lt;/li&gt;
&lt;li&gt;We create a &lt;strong&gt;new&lt;/strong&gt; outbound connection to the original destination address, however we substitute the port with that of the proxies TLS listening port. This initiates a new TLS connection between both proxies!&lt;/li&gt;
&lt;li&gt;The source proxy will send to the destination proxy the &lt;em&gt;port&lt;/em&gt; that we were originally accessing. &lt;/li&gt;
&lt;li&gt;The destination proxy will connect to that port and begin receiving traffic from the source.&lt;/li&gt;
&lt;li&gt;At this point we have full end-to-end connectivity from one application to another, &lt;strong&gt;without&lt;/strong&gt; the applications realising that we’re in the middle!&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;Creating-Certificates-📝&#34;&gt;&lt;a href=&#34;#Creating-Certificates-📝&#34; class=&#34;headerlink&#34; title=&#34;Creating Certificates 📝&#34;&gt;&lt;/a&gt;Creating Certificates 📝&lt;/h2&gt;&lt;p&gt;In order for the TLS to work the certificates will need to be created with the correct details, namely the IP addresses of the pods to ensure that the identification works correctly. This raising a chicken and egg scenario as ideally we require these details asap, however we only can be allocated this IP address &lt;strong&gt;once&lt;/strong&gt; the pod has been created by the Kubernetes API. As we can’t modify the &lt;code&gt;Volume&lt;/code&gt; section of a pod once it has been created we can refer to secrets as environment variables before they have been created. &lt;/p&gt;
&lt;p&gt;We then write some code to using Kubernetes &lt;code&gt;informers&lt;/code&gt;, there is excellent detail &lt;a href=&#34;https://macias.info/entry/202109081800_k8s_informers.md&#34;&gt;here&lt;/a&gt;. These informers will “inform” us when a pod has been both &lt;em&gt;created&lt;/em&gt; and &lt;em&gt;updated&lt;/em&gt; , we care more about the update as this is the operation where the &lt;code&gt;pod.status.podIP&lt;/code&gt; will be populated with the address we care about. Once we have this we can create the required certificates and upload them as a secret to be used by the proxy container.&lt;/p&gt;
&lt;h2 id=&#34;The-final-piece-is-the-injector-💉&#34;&gt;&lt;a href=&#34;#The-final-piece-is-the-injector-💉&#34; class=&#34;headerlink&#34; title=&#34;The final piece is the injector 💉&#34;&gt;&lt;/a&gt;The final piece is the injector 💉&lt;/h2&gt;&lt;p&gt;This is relatively straight forward, this piece of code will on startup register through the  &lt;code&gt;AdmissionController&lt;/code&gt; that certain resources (pods in our case) when &lt;em&gt;created&lt;/em&gt; be sent to our code. Which will patch the &lt;code&gt;pod.spec&lt;/code&gt; to now include our container as an &lt;code&gt;initContainer&lt;/code&gt;, and be sent back to the Kubernetes API server to be scheduled. &lt;/p&gt;
&lt;h2 id=&#34;In-Summary&#34;&gt;&lt;a href=&#34;#In-Summary&#34; class=&#34;headerlink&#34; title=&#34;In Summary&#34;&gt;&lt;/a&gt;In Summary&lt;/h2&gt;&lt;p&gt;We “kind of” have the makings of a service mesh at this point (in my mind at least), we transparently move traffic from the application through our proxy, where we can apply what we wish. In this Proof of concept we newly mint certificates and then establish end to end mTLS, where traffic is encrypted between source and destination. Although that doesn’t mean we have to end there 😀&lt;/p&gt;
&lt;h2 id=&#34;What-next&#34;&gt;&lt;a href=&#34;#What-next&#34; class=&#34;headerlink&#34; title=&#34;What next..&#34;&gt;&lt;/a&gt;What next..&lt;/h2&gt;&lt;p&gt;All the source code for this experiment is available at &lt;a href=&#34;https://github.com/thebsdbox/smesh&#34;&gt;https://github.com/thebsdbox/smesh&lt;/a&gt; so feel free to go and have a look around. It isn’t the tidiest, but it does work 😂&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/smesh.png&#34;&gt;&lt;/p&gt;
 ]]></description>
        </item>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2024/11/30/Building-your-own-service-mesh/</guid>
            <title>Building your own service mesh</title>
            <link>http://thebsdbox.co.uk/2024/11/30/Building-your-own-service-mesh/</link>
            <category>eBPF</category>
            <category>networking</category>
            <category>Service Mesh</category>
            <category>Kubernetes</category>
            <pubDate>Sat, 30 Nov 2024 16:55:22 +0000</pubDate>
            <description><![CDATA[ &lt;p&gt;I saw a few mentions about “service mesh” and mTLS amongst other things during the KubeCon US week and given some of the messing around i’d been doing with eBPF recently I asked myself “how hard could it be to write one from scratch”?&lt;/p&gt;
&lt;h2 id=&#34;The-service-mesh-shopping-list&#34;&gt;&lt;a href=&#34;#The-service-mesh-shopping-list&#34; class=&#34;headerlink&#34; title=&#34;The service mesh shopping list&#34;&gt;&lt;/a&gt;The service mesh shopping list&lt;/h2&gt;&lt;p&gt;There are a bunch of components that we will need to implement in order for us to implement the “service mesh” type behaviour. Most service meshes implement a heck of a lot more, we’re exploring the basics needed to implement it.&lt;/p&gt;
&lt;h3 id=&#34;Traffic-redirector-🚦&#34;&gt;&lt;a href=&#34;#Traffic-redirector-🚦&#34; class=&#34;headerlink&#34; title=&#34;Traffic redirector 🚦&#34;&gt;&lt;/a&gt;Traffic redirector 🚦&lt;/h3&gt;&lt;p&gt;We need a way of taking traffic from an application and sending it elsewhere, typically to our proxy where we will potentially modify the traffic. The traffic needs to be redirected in a way where the application does’t need to know about it occurring, however we need to ensure that the traffic will reach its destination and traffic is returned in a way that makes sense to the application. In most circumstances this is handled by &lt;code&gt;iptables&lt;/code&gt; rules that will change the source and destination of the packets as they navigate the kernel. As a pod initiates a connection to another pod within the cluster we will need to redirect it to our program, which we will call the proxy. &lt;/p&gt;
&lt;h3 id=&#34;The-Proxy&#34;&gt;&lt;a href=&#34;#The-Proxy&#34; class=&#34;headerlink&#34; title=&#34;The Proxy&#34;&gt;&lt;/a&gt;The Proxy&lt;/h3&gt;&lt;p&gt;Our proxy will need to be listening somewhere that is accessible on the network and as outbound connections are created their destination will be modified to that of the proxy (we also need to keep a copy of that destination somewhere). At this point we will start receiving data from the source and it is here were we opportunity to potentially change the original traffic or parse the traffic and then make decisions based upon what we learn.&lt;/p&gt;
&lt;h3 id=&#34;The-Injector-💉&#34;&gt;&lt;a href=&#34;#The-Injector-💉&#34; class=&#34;headerlink&#34; title=&#34;The Injector 💉&#34;&gt;&lt;/a&gt;The Injector 💉&lt;/h3&gt;&lt;p&gt;The injector is code that will modify the behaviour of Kubernetes so that when new workloads are scheduled an additional container could be added, or something could run before the workload starts that will write iptables/nftables rules into the kernel.&lt;/p&gt;
&lt;h3 id=&#34;Certificates-📝&#34;&gt;&lt;a href=&#34;#Certificates-📝&#34; class=&#34;headerlink&#34; title=&#34;Certificates 📝&#34;&gt;&lt;/a&gt;Certificates 📝&lt;/h3&gt;&lt;p&gt;If we are wanting to use mTLS between pods then we will need to create certificates, these certs will need things like the pod IPs or pod hostnames etc. in order for the certificates to work. Given that we wont know these details until the pod starts we will need to capture this information by watching Kubernetes and creating the certificates when we see a pod being created. &lt;/p&gt;
&lt;h2 id=&#34;Lets-get-started-🐝&#34;&gt;&lt;a href=&#34;#Lets-get-started-🐝&#34; class=&#34;headerlink&#34; title=&#34;Lets get started 🐝&#34;&gt;&lt;/a&gt;Lets get started 🐝&lt;/h2&gt;&lt;p&gt;If I can’t control the traffic then I can’t do anything, so first things first, I’m going to use eBPF in order to manipulate the traffic and make sure that it is sent to where I need it to go. Why eBPF? well because!&lt;/p&gt;
&lt;p&gt;So lets walk this through…&lt;/p&gt;
&lt;p&gt;There are a bunch of methods for manipulating traffic XDP, TC, sockets etc.. so what’s the choice?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;XDP&lt;/strong&gt;? Nope, no egress and if we’re wanting to capture traffic being initiated out to somewhere else, then that’s egress.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TC&lt;/strong&gt;? It has egress, BUT it’s already gone through the kernel, iptables, sockets etc.. changing the traffic to send back into the kernel is a bit of a pain.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sockets&lt;/strong&gt;, seems like the best option for what we’re aiming for.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;The-eBPF-🐝-magic-🪄&#34;&gt;&lt;a href=&#34;#The-eBPF-🐝-magic-🪄&#34; class=&#34;headerlink&#34; title=&#34;The eBPF 🐝 magic 🪄&#34;&gt;&lt;/a&gt;The eBPF 🐝 magic 🪄&lt;/h3&gt;&lt;p&gt;Our eBPF code is going to manipulate the L3 &amp;amp; L4 behaviour of packets as they traverse the kernel and in some-cases user-land (i.e. the proxy). &lt;/p&gt;
&lt;p&gt;The life of our packet is the following!&lt;/p&gt;
&lt;p&gt;For this walkthrough:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pod-01&lt;/code&gt; is 10.0.0.10&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pod-02&lt;/code&gt; is 10.10.0.20&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Our eBPF program is started and is passed the CIDR range of pods in our Kubernetes cluster and the &lt;code&gt;pid&lt;/code&gt; of the proxy, this is done through an eBPF map. &lt;/li&gt;
&lt;li&gt;The application within the pod (pod-01) is wanting to create an outbound connection &lt;code&gt;connect()&lt;/code&gt;, in this case to pod-02. This would typically be a high internal port &lt;code&gt;32305&lt;/code&gt; (for example) attempting to connect outbound. &lt;/li&gt;
&lt;li&gt;The eBPF program will change the destination from &lt;code&gt;10.10.0.20&lt;/code&gt; to the proxy that is listening on localhost, so &lt;code&gt;10.10.0.20:&amp;lt;port&amp;gt;&lt;/code&gt; would become &lt;code&gt;127.0.0.1:18000&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We also stuff the original destination address and port into a map, which uses the socket “cookie” as it’s key.&lt;/li&gt;
&lt;li&gt;The proxy on &lt;code&gt;127.0.0.1:18000&lt;/code&gt; will receive all the TCP magic from the application that started the connection and once the socket has been established we hook in with eBPF. &lt;/li&gt;
&lt;li&gt;Here we will add to another map the source port &lt;code&gt;32305&lt;/code&gt; and the unique socket “cookie”. &lt;/li&gt;
&lt;li&gt;The proxy has an established connection from the application, however it needs to know the original destination, we do this through calling a syscall &lt;code&gt;getsockopt&lt;/code&gt; with a specific option &lt;code&gt;SO_ORIGINAL_DST&lt;/code&gt;. This is captured by eBPF, which it does a look up on the src port &lt;code&gt;32305&lt;/code&gt; to find the cookie, it then uses the &lt;code&gt;cookie&lt;/code&gt; to look up in another map to return the original destination &lt;code&gt;10.10.0.20:&amp;lt;port&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The proxy can now establish a connection outbound to the destination pod or another proxy &lt;strong&gt;(this will be covered later)&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;As traffic is &lt;code&gt;read()&lt;/code&gt; from the proxy it is then forwarded to the internal connection and the application in &lt;code&gt;pod-01&lt;/code&gt; processes it as if there was no proxy in the middle.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;Why-do-we-pass-the-pid-of-the-proxy-into-the-eBPF-program-I-hear-you-ask&#34;&gt;&lt;a href=&#34;#Why-do-we-pass-the-pid-of-the-proxy-into-the-eBPF-program-I-hear-you-ask&#34; class=&#34;headerlink&#34; title=&#34;Why do we pass the pid of the proxy into the eBPF program? (I hear you ask)&#34;&gt;&lt;/a&gt;Why do we pass the &lt;code&gt;pid&lt;/code&gt; of the proxy into the eBPF program? (I hear you ask)&lt;/h4&gt;&lt;p&gt;Well, we would end up in a loop if the proxy has it’s out bound connections looped back to itself. So if we see a connection from the proxy then we &lt;strong&gt;don’t&lt;/strong&gt; redirect it.&lt;/p&gt;
&lt;h4 id=&#34;Abridged-logs&#34;&gt;&lt;a href=&#34;#Abridged-logs&#34; class=&#34;headerlink&#34; title=&#34;Abridged logs&#34;&gt;&lt;/a&gt;Abridged logs&lt;/h4&gt;&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;$ kubectl logs  pod-01 -c smesh-proxy&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;[2024/12/02T10:17:58.618] [application] [INFO] [main.go:66,main] Starting the SMESH 🐝&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;[2024/12/02T10:17:58.618] [application] [INFO] [main.go:94,main] detected Kernel 6.8.x&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;[2024/12/02T10:17:58.682] [application] [INFO] [connection.go:23,startInternalListener] internal proxy [pid: 7] 127.0.0.1:18000&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;[2024/12/02T10:17:58.682] [application] [INFO] [connection.go:33,startExternalListener] external proxy [pid: 7] 0.0.0.0:18001&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;[2024/12/02T10:17:58.682] [application] [INFO] [connection.go:62,startExternalTLSListener] external TLS proxy [pid: 7] 0.0.0.0:18443&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;lt; Proxy is up and running&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;lt; We receieve a forwarded connection from eBPF 🐝 &amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;[2024/12/02T10:18:14.080] [application] [INFO] [connection.go:75,start] internal proxy connection from 127.0.0.1:33804 -&amp;gt; 127.0.0.1:18000&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;lt; We&amp;#x27;ve looked up the connection through eBPF to find the original destination &amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;lt; The proxy connects to pod-02 (on it&amp;#x27;s local proxy port, where it takes care of forwarding to the application in the same pod) and we can now start sending traffic from pod-01 through the proxy &amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;[2024/12/02T10:18:14.087] [application] [INFO] [connection.go:156,internalProxy] Connected to remote endpoint 10.10.0.20:18443, original dest 10.10.0.20:9000&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;lt; The application in pod-02 has established a new connection in the opposite direction &amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;[2024/12/02T10:18:16.081] [application] [INFO] [connection.go:95,startTLS] external TLS proxy connection from 10.10.0.20:47292 -&amp;gt; 10.0.0.10:18443&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;


&lt;h2 id=&#34;Summary&#34;&gt;&lt;a href=&#34;#Summary&#34; class=&#34;headerlink&#34; title=&#34;Summary&#34;&gt;&lt;/a&gt;Summary&lt;/h2&gt;&lt;p&gt;This post steps through the bits needed in order to form a service mesh and how we use eBPF in order to redirect traffic to another process listening within the same pod. We know that this is achievable, but we now need to understand how to architect these pieces and get traffic across to the other pod! (which i’ll cover in the next post)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UPDATE:&lt;/strong&gt; That post is now available &lt;a href=&#34;https://thebsdbox.co.uk/2024/12/02/Continuing-building-your-service-mesh/&#34;&gt;here&lt;/a&gt;&lt;/p&gt;
 ]]></description>
        </item>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2024/11/03/Egress/</guid>
            <title>Egress</title>
            <link>http://thebsdbox.co.uk/2024/11/03/Egress/</link>
            <category>Networking</category>
            <category>Kubernetes</category>
            <category>Egress</category>
            <pubDate>Sun, 03 Nov 2024 16:14:21 +0000</pubDate>
            <description><![CDATA[ &lt;p&gt;This is the third time I’ve tried to write this, a random panic on my Hackintosh wiped out half a blog post and the other attempt read like garbage 🫠. So here we go! I recently added Egress IPv6 to &lt;a href=&#34;https://kube-vip.io/&#34;&gt;kube-vip&lt;/a&gt;, but it isn’t something that I really advertised as functionality in kube-vip, so I though it best to actually write about it. The concept of Egress within Kubernetes is a bit of an interesting one because it also exposes a bit of confusion when it comes to Kubernetes networking. Namely what actually comes out of the box with a Kubernetes cluster.&lt;/p&gt;
&lt;h2 id=&#34;Kubernetes-Networking&#34;&gt;&lt;a href=&#34;#Kubernetes-Networking&#34; class=&#34;headerlink&#34; title=&#34;Kubernetes Networking&#34;&gt;&lt;/a&gt;Kubernetes Networking&lt;/h2&gt;&lt;p&gt;When we think about a simple Kubernetes cluster (and deploying it), then we need to consider all of the additional components that are actually required in order for the cluster to &lt;strong&gt;actually&lt;/strong&gt; work. These are commonly thought of as the &lt;code&gt;CxI&lt;/code&gt; plugins, where the &lt;code&gt;x&lt;/code&gt; is usually &lt;code&gt;R&lt;/code&gt; for the runtime, &lt;code&gt;N&lt;/code&gt; for the networking plugin and finally &lt;code&gt;S&lt;/code&gt; for storage. Without a &lt;code&gt;CRI&lt;/code&gt;, there is simply no functionality to stop and start containers effectively making your cluster pointless and additionally without a &lt;code&gt;CNI&lt;/code&gt; your containers wont have any networking capabilities! (well it turns out that’s not strictly true).  &lt;/p&gt;
&lt;p&gt;The CNI in it’s most basic form is largely doing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pod networking with IPAM&lt;/li&gt;
&lt;li&gt;Multi node networking, allowing pods to speak to one another as if they were on a big flat network (often an overlay)&lt;/li&gt;
&lt;li&gt;Network Policy&lt;/li&gt;
&lt;li&gt;Ingress typically though &lt;code&gt;hostPort&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Above is a gross over simplification of what CNIs accomplish and most now do far more than just basic networking, however there is a huge amount of networking functionality that isn’t explained. Who or what creates a Kubernetes &lt;code&gt;service&lt;/code&gt; and manages the forwarding to a pod that is selected, and additionally when a pod wishes to send traffic externally to the cluster who or what is happening.&lt;/p&gt;
&lt;h3 id=&#34;Kube-Proxy&#34;&gt;&lt;a href=&#34;#Kube-Proxy&#34; class=&#34;headerlink&#34; title=&#34;Kube-Proxy&#34;&gt;&lt;/a&gt;Kube-Proxy&lt;/h3&gt;&lt;p&gt;So if you do a &lt;code&gt;kubectl get pods -A&lt;/code&gt; on a cluster you may encounter &lt;code&gt;kube-proxy&lt;/code&gt; pods, which typically run on all nodes as a &lt;code&gt;daemonset&lt;/code&gt; and are largely there to manipulate the masses of iptables rules required in order to make the internals of a Kubernetes cluster work. Whenever a service is created &lt;code&gt;kube-proxy&lt;/code&gt; will create rules that will watch for ingress traffic to that service address, additionally it also keeps track of the &lt;code&gt;endpoints&lt;/code&gt; that match that particular service. These rules, regardless of the technology (currently iptables but soon nftables in upstream) will modify traffic that is destined for a service IP and change that destination to one of the endpoint IP addresses. There is a great overview (in epic detail) available at &lt;a href=&#34;https://learnk8s.io/kubernetes-services-and-load-balancing#:~:text=Kube%2Dproxy%20is%20a%20pod,its%20interception%20and%20redirect%20rules.&#34;&gt;learnk8s&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Where things get interesting is egress traffic, if you had a pod that had busybox+curl and you were using that the &lt;code&gt;curl https://google.com&lt;/code&gt; it will typically just work, but the thing about the pod network is that it isn’t meant to be directly accessible! So how does return traffic get to the pod?&lt;/p&gt;
&lt;p&gt;So the answer is pretty simple, and it is pretty much the same logic that applies to most household internet connections. This logic is the idea of NAT (Network Address Translation), but simply put like the pod in a Kubernetes cluster your home laptop isn’t directly accessible on the internet however the ADSL/Fibre router is. When an internal device (or pod) wants to connect to the outside world the traffic will traverse the either a physical router or routing logic in the kernel and the destination address is changed to the address of the router. So in the diagram below &lt;code&gt;google.com&lt;/code&gt; will receive a connection from the external address which it can communicate with, when it receives traffic from this connection it will then change the address back to the internal address and traffic is then passed back internally. &lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;┌────────────────────────────────────┐                                                 &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;│┌────────────────┐                  │                                                 &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;││Pod-01 10.0.0.3 ┼─────────┐        │            ┌───────────────────────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;│└────────────────┘         │        │            │ google.com                │&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;│                           │        │            └───────────────────────────┘&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;└───────────────────────────▼────────┘                          ▲              &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                 Node-01 192.168.0.22───────────────────────────┘              &lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt; So this is fine, effectively each node in the cluster will take care of handling the traffic originating from pods by making it look like it’s originating from the node where the pod resides and in most cases this works. However if your cluster has 1000 nodes and you have a pod that wants to access something external protected by a firewall, well that firewall will now need 1000 rules allowing access because who knows where that pod will be scheduled to run in the future. Or perhaps one of those modern app transformation programs actually works (I’ve been part of a myriad of failed attempts to create these programs) and you’ve finally migrated an application that has some static IP requirements, well again there is no way to have anything static in this scenario.&lt;/p&gt;
&lt;h2 id=&#34;Egress-Magic-🪄&#34;&gt;&lt;a href=&#34;#Egress-Magic-🪄&#34; class=&#34;headerlink&#34; title=&#34;Egress Magic 🪄&#34;&gt;&lt;/a&gt;Egress Magic 🪄&lt;/h2&gt;&lt;p&gt; The most common solution to implement “controlled” Egress within a Kubernetes cluster is the concept of an Egress gateway and in most cases this is another small pod (or pods) that sit within the cluster that traffic is forwarded to. This gateway will then do the same manipulation however it will typically have a series of IP addresses that it can use dependant on who the originating traffic is coming from. So in theory we could tie our legacy application to its original IP address and the gateway will then take care of manipulating the traffic to make it appear as though this original IP address is still in use.&lt;/p&gt;
 &lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;Node-01 192.168.0.22                                      &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;┌──────────────────────────────────────────┐                                    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;│┌────────────────┐                        │                                    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;││Pod-01 10.0.0.3 │                        │                                    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;│└───────┬────────┘                        │                                    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;│        │                                 │                                    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;└────────┼─────────────────────────────────┘                                    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;         │                                                                      &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;         │                                         ┌───────────────────────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;         │                      ┌─────────────────►│ Google.com                │&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;┌────────┼──────────────────────┼──────────┐       └───────────────────────────┘&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;│        │              ┌───────┼────────┐ │                                    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;│        └─────────────►│Egress Gateway  │ │                                    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;│                       └────────────────┘ │                                    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;│                                          │                                    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;└──────────────────────────────────────────┘                                    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                      Node-02 192.168.0.23                                      &lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&#34;Egress-with-kube-vip-🐙&#34;&gt;&lt;a href=&#34;#Egress-with-kube-vip-🐙&#34; class=&#34;headerlink&#34; title=&#34;Egress with kube-vip 🐙&#34;&gt;&lt;/a&gt;Egress with kube-vip 🐙&lt;/h2&gt;&lt;p&gt;A few years ago I popped into the #sig-networking channel (excellent place to discuss ideas btw) to discuss egress within Kubernetes as I wanted to implement it within kube-vip. There are a few projects that have implemented through different mechanisms in the past, but largely if you wanted to implement it with a stable architecture the obvious solution was using a gateway (as discussed above). After fiddling around with the rules that kube-proxy implements I decided that perhaps there was an alternative solution!&lt;/p&gt;
&lt;p&gt;The kube-vip project is everyones (some peoples) goto project when wanting to deploy a load balancer solution within a Kubernetes cluster, and it is pretty &lt;strong&gt;devastatingly&lt;/strong&gt; simple. Apply a load balancer address to a node, and then when traffic is sent to it the traffic then makes its way to a pod via kube-proxy (easy)!&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;┌─────────┐                                                                 &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;│Client🧑‍💻 ┼────────────────────► 192.168.0.100───────►Node-01 192.168.0.22  &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;└─────────┘                     ┌──────▲──────────────────┬────────────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                                │      │                  │                │&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                                │┌─────┼──────┐         ┌─▼──────────────┐ │&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                                ││kube-vip🐙  │         │Pod-01 10.0.0.3 │ │&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                                │└────────────┘         └────────────────┘ │&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                                └──────────────────────────────────────────┘&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h3 id=&#34;The-same-but-backwards&#34;&gt;&lt;a href=&#34;#The-same-but-backwards&#34; class=&#34;headerlink&#34; title=&#34;The same, but backwards&#34;&gt;&lt;/a&gt;The same, but backwards&lt;/h3&gt;&lt;p&gt;  For Egress could we potentially combine some of the behaviour of both &lt;code&gt;kube-proxy&lt;/code&gt; and kube-vip to produce a stable Egress solution? It turns out yes, we can and again it is &lt;strong&gt;devastatingly&lt;/strong&gt; simple. To accomplish this we effectively &lt;em&gt;overload&lt;/em&gt; the behaviour of a Kubernetes service by using it to define not only the ingress (as a loadbalancer) but also the egress with additional iptables rules. This creates a 1:1 relationship between a service and a pod and uses the service IP as the pods ingress and egress address. &lt;/p&gt;
  &lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;┌────────────┐                                                                    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;│Server      │◄──────────────────────┐                                            &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;└────────────┘                       │192.168.0.100           Node-01 192.168.0.22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                                      ┌──▲────▲──────────────────────────────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                                      │  │    │               ┌──────────┐       │&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                                      │  │    └───────────────┤Kernel 🧠 │◄─┐    │&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                                      │  │                    │          │  │    │&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                                      │  │        ┌──────────►│(iptables)│  │    │&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                                      │  │        │           └──────────┘  │    │&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                                      │┌─┼────────┼─┐         ┌─────────────┼──┐ │&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                                      ││kube-vip 🐙 │         │Pod-01 10.0.0.3 │ │&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                                      │└────────────┘         └────────────────┘ │&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                                      └──────────────────────────────────────────┘&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;  If we look the above diagram we’ve created a service that selects &lt;code&gt;Pod-01&lt;/code&gt; and the following will happen:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The service load balancer is attached to the node where &lt;code&gt;Pod-01&lt;/code&gt; currently resides&lt;/li&gt;
&lt;li&gt;The Kernel is updated so that traffic leaving from &lt;code&gt;Pod-01&lt;/code&gt; should be updated so that it looks like it is coming from the load balancer, so effectively &lt;code&gt;10.0.0.3&lt;/code&gt; is re-written to become &lt;code&gt;192.168.0.100&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;As the process sends traffic or initiates connections to the &lt;code&gt;Server&lt;/code&gt; they are now all coming from the externally facing load balancer address.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;  In the event &lt;code&gt;Pod-01&lt;/code&gt; is killed or crashes then kube-vip is notified through a Kubernetes &lt;a href=&#34;https://kubernetes.io/docs/reference/using-api/api-concepts/#efficient-detection-of-changes&#34;&gt;watcher&lt;/a&gt;, where the event of a pod &lt;strong&gt;delete&lt;/strong&gt; will result in the egress rules being cleaned up. When the Pod reappears on another node, then the egress rules will be re-applied and the traffic will continue to be appearing to come from the same IP address, so we get a level of High Availability for free. &lt;/p&gt;
&lt;h2 id=&#34;Where-is-the-eBPF-🐝&#34;&gt;&lt;a href=&#34;#Where-is-the-eBPF-🐝&#34; class=&#34;headerlink&#34; title=&#34;Where is the eBPF 🐝&#34;&gt;&lt;/a&gt;Where is the eBPF 🐝&lt;/h2&gt;&lt;p&gt;  I’ve wanted to re-implement this functionality in eBPF for quite a long time, however it simply isn’t as easy as I hoped.&lt;/p&gt;
&lt;h3 id=&#34;“But-isn’t-there-an-egress-hook-for-TC-”&#34;&gt;&lt;a href=&#34;#“But-isn’t-there-an-egress-hook-for-TC-”&#34; class=&#34;headerlink&#34; title=&#34;“But isn’t there an egress hook for TC?”&#34;&gt;&lt;/a&gt;&lt;em&gt;“But isn’t there an egress hook for TC?”&lt;/em&gt;&lt;/h3&gt;&lt;p&gt;Indeed there is, &lt;strong&gt;however&lt;/strong&gt; this hook is the last step in the chain before the packet goes back onto the nic and leaves the machine. What this effectively means is that all of the netfilter (&lt;em&gt;iptables&lt;/em&gt; &amp;amp; &lt;code&gt;kube-proxy&lt;/code&gt;) magic will have already modified the packet before our eBPF program can see it. &lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.researchgate.net/profile/Sebastiano-Miano/publication/330620362/figure/fig1/AS:773669240438785@1561468529285/Location-of-netfilter-and-eBPF-hooks.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;So for now the plan is to migrate kube-vip to use googles nftables GO library and we’ll see what happens next. &lt;/p&gt;
&lt;h2 id=&#34;Thanks&#34;&gt;&lt;a href=&#34;#Thanks&#34; class=&#34;headerlink&#34; title=&#34;Thanks&#34;&gt;&lt;/a&gt;Thanks&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Lars Ekman&lt;/li&gt;
&lt;li&gt;Antonio Ojea&lt;/li&gt;
&lt;li&gt;The other folks that listened to my daft ideas in #SIG-NETWORK&lt;/li&gt;
&lt;/ul&gt;
 ]]></description>
        </item>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2024/05/11/Highly-Available-workloads-across-Mulitple-Kubernetes-Clusters/</guid>
            <title>Highly Available workloads across Mulitple Kubernetes Clusters</title>
            <link>http://thebsdbox.co.uk/2024/05/11/Highly-Available-workloads-across-Mulitple-Kubernetes-Clusters/</link>
            <category>Kubernetes</category>
            <pubDate>Sat, 11 May 2024 09:19:58 +0100</pubDate>
            <description><![CDATA[ &lt;p&gt;Our Journey begins in 2003 were I somehow blagged a Unix role, largely due to experience with &lt;a href=&#34;https://www.freebsd.org/&#34;&gt;FreeBSD&lt;/a&gt; and sparse access to a few Unix systems over the years. This first role consisted of four days and twelve hours per shift, where we were expected to watch for alerts and fix a myriad of different systems ranging from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Tru64_UNIX&#34;&gt;Tru64&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/HP-UX&#34;&gt;HP-UX&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/IBM_AIX&#34;&gt;AIX&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/SunOS&#34;&gt;SunOS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Oracle_Solaris&#34;&gt;Solaris&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every alert in &lt;a href=&#34;https://en.wikipedia.org/wiki/HP_OpenView&#34;&gt;HP Openview&lt;/a&gt; (which you can see in the screenshot below) would lead down a rabbit hole of navigating various multi-homed jump boxes in order to track down the host that had generated the alert.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.netiq.com/documentation/operations-center-57/adapter_integration/graphics/ai_ovo_ovomsgbrowser_a.png&#34; alt=&#34;OpenView&#34;&gt;&lt;/p&gt;
&lt;p&gt;Once we finally logged onto the machine we were often presented with a obscure version of Unix, a completely random shell or a user-land set of tooling that was incomprehensible.. it was ace. &lt;/p&gt;
&lt;p&gt;Typically in this environment the architecture was 1:1, as in one server would host one big application. Although there were some applications that had strict demands and in some cases penalties in the event of downtime, it was these applications that would often make use of various clustering technologies of the time in order to provide high availability. Given the infrastructure was a complete hodgepodge of varying systems it would stand to reason that the clustering software would follow suit, this meant that we were presented with systems such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Veritas_Cluster_Server&#34;&gt;Veritas Cluster Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Solaris_Cluster&#34;&gt;Sun Cluster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/HP_Serviceguard&#34;&gt;HP Serviceguard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/TruCluster&#34;&gt;TruCluster&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;High-Availability&#34;&gt;&lt;a href=&#34;#High-Availability&#34; class=&#34;headerlink&#34; title=&#34;High Availability&#34;&gt;&lt;/a&gt;High Availability&lt;/h2&gt;&lt;p&gt;As mentioned above in this place of work some applications simply weren’t allowed to fail as there would be a penalty (typically per minute of downtime charges) so for these applications a highly available solution is required. These solutions are to keep an application as available to end users as possible, so in the event an application crashes then it’s the clustering softwares job to restart it. Although you could create your own with a bash script:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;#!/bin/bash&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;while true&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;do&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  echo &amp;quot;Starting program&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  /usr/bin/exciting_program&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  sleep 1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;done&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;So restarting the program to ensure availability is one thing, but what about things such as OS upgrades or hardware failures? In those use-cases then the application will cease to run as the system itself will be unavailable. This is where multiple systems are clustered together in order to provide a highly available solution to any downtime, when a system becomes unavailable then it’s the clustering softwares role to make a decision about how and where to restart the application. A lot of my time was fighting with &lt;strong&gt;Sun Cluster&lt;/strong&gt; and how it would implement high availability 🫠&lt;/p&gt;
&lt;h3 id=&#34;Implementing-High-Availability-back-in-the-day&#34;&gt;&lt;a href=&#34;#Implementing-High-Availability-back-in-the-day&#34; class=&#34;headerlink&#34; title=&#34;Implementing High Availability (back in the day)&#34;&gt;&lt;/a&gt;Implementing High Availability (back in the day)&lt;/h3&gt;&lt;p&gt;In these systems there were a number of pre-requisites in order for high availability to work&lt;/p&gt;
&lt;h4 id=&#34;Shared-storage&#34;&gt;&lt;a href=&#34;#Shared-storage&#34; class=&#34;headerlink&#34; title=&#34;Shared storage&#34;&gt;&lt;/a&gt;Shared storage&lt;/h4&gt;&lt;p&gt;If the application had persistent data, and they pretty much all were based upon Oracle databases back then then this underlying storage needed to be shared. This is so that in the event of a failure the storage can be mounted on the node that is selected to take over the workload. &lt;/p&gt;
&lt;h4 id=&#34;Re-architecting-the-application&#34;&gt;&lt;a href=&#34;#Re-architecting-the-application&#34; class=&#34;headerlink&#34; title=&#34;Re-architecting the application&#34;&gt;&lt;/a&gt;Re-architecting the application&lt;/h4&gt;&lt;p&gt;This doesn’t technically mean re-writing the application, it means writing various startup and shutdown scripts with logic in them in order to ensure that the clustering software can successfully complete them without them ending in a failed state. &lt;/p&gt;
&lt;h4 id=&#34;Networking&#34;&gt;&lt;a href=&#34;#Networking&#34; class=&#34;headerlink&#34; title=&#34;Networking&#34;&gt;&lt;/a&gt;Networking&lt;/h4&gt;&lt;p&gt;If the application was moving around during a failover then external programs or end users still needed to access it with the same address/hostname etc.. so in the event of a failover a virtual IP (VIP) and hostname will typically be added to the node where the application is failing over to. &lt;/p&gt;
&lt;h4 id=&#34;Quorum&#34;&gt;&lt;a href=&#34;#Quorum&#34; class=&#34;headerlink&#34; title=&#34;Quorum&#34;&gt;&lt;/a&gt;Quorum&lt;/h4&gt;&lt;p&gt;In order for a node to become the chosen one in the cluster a quorum device should be present in order to ensure that a decision can be made about who will takeover the running of applications, &lt;strong&gt;and&lt;/strong&gt; in the case of a network connection failure between nodes that a “split brain” scenario can’t occur.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Split Brain&lt;/em&gt; will occur when cluster nodes can no longer communicate, leading them to believe that they’re the only nodes in the cluster and thus voting for themselves in order to become the leader. This would lead to multiple nodes all accessing the same data or advertising conflicting IP addresses, and generally causing chaos. &lt;/p&gt;
&lt;h2 id=&#34;Did-HA-work&#34;&gt;&lt;a href=&#34;#Did-HA-work&#34; class=&#34;headerlink&#34; title=&#34;Did HA work?&#34;&gt;&lt;/a&gt;Did HA work?&lt;/h2&gt;&lt;p&gt;Sometimes&lt;/p&gt;
&lt;p&gt;One evening I was asked to lead a large planned change to upgrade the hardware on a mighty &lt;a href=&#34;https://cray-cyber.org/old/systems/e10k.php&#34;&gt;Sun e10K&lt;/a&gt; that was being used to host arguably one of the most important applications within the entire company. The plan was relatively straight forward: &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Log into node1, check it was primary and running the application.&lt;/li&gt;
&lt;li&gt;Log into node2, ensure its health (hardware/disk space etc..)&lt;/li&gt;
&lt;li&gt;Initiate a failover on node1&lt;/li&gt;
&lt;li&gt;Watch and ensure services came up on node2 and validate the application was healthy and externally accessible&lt;/li&gt;
&lt;li&gt;Upgrade hardware on node1&lt;/li&gt;
&lt;li&gt;Check node1 is healthy again (after upgrade)&lt;/li&gt;
&lt;li&gt;Initiate a failover on node2, back to node1&lt;/li&gt;
&lt;li&gt;Realise something is wrong&lt;/li&gt;
&lt;li&gt;Panic&lt;/li&gt;
&lt;li&gt;Really panic&lt;/li&gt;
&lt;li&gt;Consider fleeing to a far flung country&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;So-what-went-wrong&#34;&gt;&lt;a href=&#34;#So-what-went-wrong&#34; class=&#34;headerlink&#34; title=&#34;So what went wrong?&#34;&gt;&lt;/a&gt;So what went wrong?&lt;/h3&gt;&lt;p&gt;The application failover from node1-&amp;gt;node2 went fine, we saw the application shutdown followed by the shared storage being unmounted and finally the network information removed from node1. Then on node2 we witnessed the storage being mounted, the networking details being applied followed by the application being restarted. We even had the applications/database people log in and watch all the logs to ensure that everything came up correctly. &lt;/p&gt;
&lt;p&gt;When we failed back things went very wrong, the first application/storage/network all moved back however the second application stopped and everything just hung. Eventually the process excited with an error about the storage being half remounted. The app/database people jumped onto the second node to see what was happening with the first application whilst we tried to work out what was happening. Eventually we tried to bring everything back to node2 where everything was last running successfully and again the application stopped and the process timed out about the storage 🤯&lt;/p&gt;
&lt;p&gt;At this point we had a broken application spread across two machines trying to head in opposite directions but stuck in a failed state, at this point various incident teams were being woken up and various people started prodding and poking things to fix it.. this went on for a few hours before we worked out what was going on. (&lt;strong&gt;Spoiler&lt;/strong&gt; it was step 4)&lt;/p&gt;
&lt;p&gt;So this change was happening in the middle of the night, meaning that ideally no-one should really be using it or noticing it not working for the “momentary” downtime. One of the applications team was had opened a terminal and had changed directory to where the application logs where (on the shared storage) in order to watch and make sure the application came up correctly. &lt;strong&gt;However&lt;/strong&gt;, this person then went to watch TV or get a nap (who knows) leaving their session logged on and living within the directory on the shared storage. When it came to failing the application back the system refused to unmount the shared storage as something was still accessing the filesystem 🤬 .. even better when we tried to bring the other half of the application back it failed because someone was looking at the database logs when it attempted to unmount the shared storage for that 🫠&lt;/p&gt;
&lt;p&gt;I think it was this somewhat stressful experience that weirdly made me want to learn more about Sun cluster and other HA solutions, and here we are today :-D&lt;/p&gt;
&lt;h2 id=&#34;High-Availability-in-Kubernetes&#34;&gt;&lt;a href=&#34;#High-Availability-in-Kubernetes&#34; class=&#34;headerlink&#34; title=&#34;High Availability in Kubernetes&#34;&gt;&lt;/a&gt;High Availability in Kubernetes&lt;/h2&gt;&lt;p&gt;Much like that script I embedded above, HA is endlessly performed by Kubernetes typically referred to as the “reconciliation loop”. The reconciliation loops role is largely to compare &lt;strong&gt;expected&lt;/strong&gt; state and &lt;strong&gt;actual&lt;/strong&gt; state and reconcile the difference, so expected state if 3 pods and there is only 1 then schedule 2 more etc. Additionally within the Kubernetes cluster (actually it comes from &lt;code&gt;etcd&lt;/code&gt; but 🤷🏼‍♂️) is the concept of leader election, which allows things running within the cluster to use this mechanism to elect a leader amongst all participants. This mechanism allows you to have multiple copies of an application running and with a bit of simple logic ensure that only the active/leader instance is the one that is actually doing the processing or accepting connections etc.&lt;/p&gt;
&lt;h3 id=&#34;High-Availability-across-Kubernetes&#34;&gt;&lt;a href=&#34;#High-Availability-across-Kubernetes&#34; class=&#34;headerlink&#34; title=&#34;High Availability across Kubernetes&#34;&gt;&lt;/a&gt;High Availability across Kubernetes&lt;/h3&gt; ]]></description>
        </item>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2024/01/15/Perfecting-Protocol-Parsing-Probably-with-eBPF/</guid>
            <title>Perfecting Protocol Parsing (Probably) with eBPF</title>
            <link>http://thebsdbox.co.uk/2024/01/15/Perfecting-Protocol-Parsing-Probably-with-eBPF/</link>
            <category>eBPF</category>
            <category>networking</category>
            <pubDate>Mon, 15 Jan 2024 20:18:40 +0000</pubDate>
            <description><![CDATA[ &lt;p&gt;I recently had a little bit of time to kill and decided to see if I could actually do some parsing of other protocols with eBPF. The previous post that I created was about &lt;a href=&#34;https://thebsdbox.co.uk/2023/12/08/Application-traffic-with-eBPF/&#34;&gt;http&lt;/a&gt; and whilst it’s an important application protocol to be able to read and potentially manipulate, it feels like there was only so much that could be done. Webpages are highly dynamic and can contain large amounts of data, which are qualities that aren’t always the best to try and parse with eBPF. &lt;/p&gt;
&lt;p&gt;So my next attempt was to see how difficult it would be in order to parse something a bit spicier 🌶️! So I recently wrote a basic parsed for BGP messages, which originally was designed to just parse the first bit of data to understand the different message types and give some insight into what was occurring when BGP peers are sending info back and forth, it evolved over the weekend a little bit and now understands peering information and before I decided to write this can now manipulate the data between peers (without the BGP software being aware).&lt;/p&gt;
&lt;div class=&#34;twitter-wrapper&#34;&gt;&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;a href=&#34;https://twitter.com/thebsdbox/status/1746119139591168182&#34;&gt;&lt;/a&gt;&lt;/blockquote&gt;&lt;/div&gt;&lt;script async defer src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;

&lt;p&gt;Code is available &lt;a href=&#34;https://github.com/thebsdbox/learning-ebpf/blob/main/ebpf/bgp/bgp.c&#34;&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So to begin we will need to do what we always do when we have some network data (the socket buffer &lt;code&gt;skb&lt;/code&gt;) in eBPF, which is to check it’s HTTP-&amp;gt;TCP/UDP and strip off the headers once we are looking at the correct traffic. This is covered in the previous two eBPF posts, and is in all of the example code so I wont &lt;del&gt;duplicate&lt;/del&gt; triplicate the code here. With all of these headers removed (I say removed, we just move the pointer (bit like the needle on a record player) past them so we’re now left with the data portion remaining). With our raw data remaining we now need to convert this into a format that matches the protocol itself, so lets start there!&lt;/p&gt;
&lt;h2 id=&#34;Protocols&#34;&gt;&lt;a href=&#34;#Protocols&#34; class=&#34;headerlink&#34; title=&#34;Protocols&#34;&gt;&lt;/a&gt;Protocols&lt;/h2&gt;&lt;p&gt;A lot of these protocols are pretty old, and are detailed in documents called Request for Comments or an &lt;a href=&#34;https://en.wikipedia.org/wiki/Request_for_Comments&#34;&gt;rfc&lt;/a&gt;. These documents put together by experts in the field largely define the architecture of a protocol and a good example, which I used in order to parse HTTP is &lt;a href=&#34;https://www.ietf.org/rfc/rfc2617.txt&#34;&gt;this one&lt;/a&gt; and you can see that this was originally authored in 1999.&lt;/p&gt;
&lt;p&gt;So lets get to the crux of it, if you’ve been working with JSON/YAML/XML etc. or anything else that is obviously structured then &lt;strong&gt;abandon hope all yee who enter&lt;/strong&gt; 😂 Almost every protocol has it’s own unique way of how it structures data, some are cleaner than others. To begin with BGP seemed pretty straight forward…&lt;/p&gt;
&lt;p&gt;To begin with we’ll need to use the rfc document for the &lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc4271#page-12&#34;&gt;BGP standards&lt;/a&gt;, quickly reading through this we can understand that every BGP message starts with the same “fixed size” header:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;0                   1                   2                   3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;|                           Marker                              |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;|          Length               |      Type     |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;em&gt;(the diagrams in rfcs are a tad confusing, however the descriptions are a bit clearer)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Simply put the marker should be 16 octets! (aka 16 bytes), the length should be 2 octets (2 bytes or 16 bits) and the type is 1 octet (1 byte or 8 bits) and with this information we can create a structure to put the raw data in that will allow us to shape it into the BGP Message header.&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;struct bgp_message &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	__u8 marker[16];&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	__u16 length;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	__u8 type;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&#34;The-joys-of-padding&#34;&gt;&lt;a href=&#34;#The-joys-of-padding&#34; class=&#34;headerlink&#34; title=&#34;The joys of padding&#34;&gt;&lt;/a&gt;The joys of padding&lt;/h3&gt;&lt;p&gt;If we look at our struct above we can see marker is 16 bytes, length is 2 and type is 1 giving us a grand total of (drum roll 🥁) … 19 bytes. So why oh why, when we do a &lt;code&gt;sizeof(bgp_message)&lt;/code&gt; do we end up with 20 bytes 🤯 This was specifically an issue with the BGP keep alive messages that consist of just a BGP message (Type set to 4), where I would attempt to read the BGP message header (expecting it to be 19 bytes) and the compiler was trying to read 20, which was obviously 1 byte too many causing the load_bytes function to fail. &lt;/p&gt;
&lt;p&gt;So after some annoying failed attempts to copy 20 bytes our of 19, I realised that my &lt;code&gt;bpg_message&lt;/code&gt; struct is probably being padded, this is process of adding some additional data making it more efficient for the CPU to load and store the data. More detail is available &lt;a href=&#34;https://www.geeksforgeeks.org/structure-member-alignment-padding-and-data-packing/&#34;&gt;here&lt;/a&gt;, in most cases it’s not a problem, however we need everything to align perfectly so set packing per byte we can add the following &lt;code&gt;#pragma pack(1)&lt;/code&gt; (which effectively disables packing). Now our struct is the correct size and we have will be able to retrieve data from the &lt;code&gt;skb&lt;/code&gt; without causing any errors. &lt;/p&gt;
&lt;h2 id=&#34;Getting-data-from-the-skb&#34;&gt;&lt;a href=&#34;#Getting-data-from-the-skb&#34; class=&#34;headerlink&#34; title=&#34;Getting data from the skb&#34;&gt;&lt;/a&gt;Getting data from the &lt;code&gt;skb&lt;/code&gt;&lt;/h2&gt;&lt;p&gt;So we should have a variable that points to the location in the &lt;code&gt;skb&lt;/code&gt; where the data lives, this after the frame/IP/TCP headers have ended in my code it’s usually &lt;code&gt;poffset&lt;/code&gt;. We will create a variable called &lt;code&gt;bgpm&lt;/code&gt; that will now populate with the bytes from the &lt;code&gt;skb&lt;/code&gt; using the &lt;code&gt;bpf_skb_load_bytes&lt;/code&gt; function.&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;struct bgp_message bgpm;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;int ret = bpf_skb_load_bytes(skb, poffset, &amp;amp;bgpm, sizeof(bgpm));&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if (ret != 0) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	bpf_printk(&amp;quot;error %d&amp;quot;,ret); // if we can&amp;#x27;t load the data print the error message&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return 0;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;   &lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;em&gt;(We can see that the &lt;code&gt;sizeof(bgpm)&lt;/code&gt;, with the padding enabled was causing this to fail as there were only 19 bytes left in the &lt;code&gt;skb&lt;/code&gt; and we were trying to load 20 🙄)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once we have the header, we need to move our &lt;code&gt;poffset&lt;/code&gt; so that we point to whatever exists after the header &lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;poffset += sizeof(bgpm); // remove header&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&#34;Understanding-application-data&#34;&gt;&lt;a href=&#34;#Understanding-application-data&#34; class=&#34;headerlink&#34; title=&#34;Understanding application data&#34;&gt;&lt;/a&gt;Understanding application data&lt;/h2&gt;&lt;p&gt;We have successfully parsed the header, so we can now use this information to start to understand what the additional data remaining is and with BGP the message type and the length of the remaining data are key. The &lt;code&gt;bgp.type&lt;/code&gt; will be one of the following values:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1 - OPEN&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2 - UPDATE&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3 - NOTIFICATION&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4 - KEEPALIVE&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Where the &lt;code&gt;bgp.length&lt;/code&gt; will represent how much data exists (including the header), so to determine how much “remaining” data is left we would &lt;code&gt;remaining = bgpm.len - sizeof(bgpm)&lt;/code&gt; given a &lt;code&gt;KEEPALIVE&lt;/code&gt; message is just the header, this should return &lt;code&gt;0&lt;/code&gt;. However other message types often come with additional data!&lt;/p&gt;
&lt;p&gt;So lets parse the header, and we’ll look at the UPDATE message in further detail!&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;#define BGP_OPEN		    1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;#define BGP_UPDATE		    2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;#define BGP_NOTIFICATION	3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;#define BGP_KEEPALIVE		4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;...&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;switch (bgpm.type)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	case BGP_OPEN:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	case BGP_UPDATE:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	// Parse the UPDATE data :-)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	case BGP_NOTIFICATION:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	case BGP_KEEPALIVE:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	default:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;em&gt;(As every message comes through the kernel we parse the header and then process the remainder of the data)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The UPDATE message is (personally) pretty bonkers:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;+-----------------------------------------------------+&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;|   Withdrawn Routes Length (2 octets)                |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+-----------------------------------------------------+&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;|   Withdrawn Routes (variable)                       |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+-----------------------------------------------------+&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;|   Total Path Attribute Length (2 octets)            |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+-----------------------------------------------------+&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;|   Path Attributes (variable)                        |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+-----------------------------------------------------+&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;|   Network Layer Reachability Information (variable) |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+-----------------------------------------------------+&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;As mentioned, if you’ve been writing/parsing JSON or higher level data structures then arrays etc. are pretty simplistic. With these older structures we will need to do various bits of logic to determine how many pieces of information are marked as &lt;code&gt;variable&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Withdrawn Routes&lt;/code&gt; is straight forward enough, the Total Path Attributes is mind boggling… &lt;/p&gt;
&lt;p&gt;Without screaming into the void too much, we’re given {x} amount of bytes as the &lt;code&gt;Path Attributes&lt;/code&gt; and we would need to do the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Read the first &lt;code&gt;3&lt;/code&gt; bytes to get the &lt;code&gt;flags&lt;/code&gt;/&lt;code&gt;type&lt;/code&gt;/&lt;code&gt;len&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;Then dependant on the &lt;code&gt;type&lt;/code&gt; read another random sized number of bytes, as each path attribute contains a different amount of data&lt;/li&gt;
&lt;li&gt;We can &lt;code&gt;load&lt;/code&gt; that &lt;code&gt;len&lt;/code&gt; into the another specific path attribute struct and read that particular data&lt;/li&gt;
&lt;li&gt;Move the data pointer forward the size of the &lt;code&gt;Path Attribute&lt;/code&gt; “header” and the length of the remaining data &lt;code&gt;len&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Once we’ve done all that move the &lt;code&gt;poffset&lt;/code&gt; the size of the &lt;code&gt;Total Path Attributes Length&lt;/code&gt; so we can read the NLRI data&lt;/li&gt;
&lt;li&gt;Sip a large glass of whisky&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;Modify-the-BGP-data&#34;&gt;&lt;a href=&#34;#Modify-the-BGP-data&#34; class=&#34;headerlink&#34; title=&#34;Modify the BGP data&#34;&gt;&lt;/a&gt;Modify the BGP data&lt;/h2&gt;&lt;p&gt;So whilst everything detailed above is great for gaining insight into what is happening from a BGP perspective, perhaps we may want to impose some changes to the BGP data as it’s flowing through! For this example we will change the AS number of a new route as it’s being pushed out to a ToR switch. In order to do this we will need to look for the &lt;code&gt;Path Attribute&lt;/code&gt; with the &lt;code&gt;type&lt;/code&gt; of &lt;code&gt;2&lt;/code&gt; known as the &lt;code&gt;AS_PATH&lt;/code&gt; detailed &lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc4271#page-18&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;struct bgp_path_as &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	__u8 type;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	__u8 lenth;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	__u32 as; &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;em&gt;(Here is the format defined as a C struct)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;At this point we’ve gone through through each of the &lt;code&gt;Path Attributes&lt;/code&gt; found type &lt;code&gt;2&lt;/code&gt;/&lt;code&gt;AS_PATH&lt;/code&gt; and pulled it from the &lt;code&gt;skb&lt;/code&gt;, and we want to change it to a different AS number.&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;bgp_as.as = bpf_htonl(65002);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ret = bpf_skb_store_bytes(skb, pathOffset, &amp;amp;bgp_as, sizeof(bgp_as), BPF_F_RECOMPUTE_CSUM);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if (ret != 0) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    bpf_printk(&amp;quot;error %d&amp;quot;,ret);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    return 0;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;*(NOTE: &lt;code&gt;pathOffset&lt;/code&gt; points to after the path header of the &lt;code&gt;AS_PATH&lt;/code&gt; entry exists)&lt;/p&gt;
&lt;p&gt;Here we can use the &lt;code&gt;bpf_skb_store_bytes&lt;/code&gt; to write an updated &lt;code&gt;bpg_as&lt;/code&gt; that has our changed AS number, this helper also has the flag &lt;code&gt;BPF_F_RECOMPUTE_CSUM&lt;/code&gt; that takes care of fixing any checksum changes due to the changed underlying data. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: You should notice that where we’re assigning the new AS &lt;code&gt;65002&lt;/code&gt; we’re wrapping it with the function &lt;code&gt;bpf_htonl&lt;/code&gt;, which is effectively changing a &lt;strong&gt;h&lt;/strong&gt;ost &lt;strong&gt;to&lt;/strong&gt; &lt;strong&gt;n&lt;/strong&gt;etwork &lt;strong&gt;l&lt;/strong&gt;ong. Simply put numbers that are used for networking use a different “endian” (the number are stored backwards), you can read more about that &lt;a href=&#34;https://en.wikipedia.org/wiki/Endianness&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The user land BGP program that is peering to the ToR is blissfully unaware that the route it is advertising is using a different AS number 😂 at this point.&lt;/p&gt;
&lt;h2 id=&#34;Outro&#34;&gt;&lt;a href=&#34;#Outro&#34; class=&#34;headerlink&#34; title=&#34;Outro&#34;&gt;&lt;/a&gt;Outro&lt;/h2&gt;&lt;p&gt;The RFC docs are a great way to begin to understand what this seemingly opaque block of data that proceeds the various headers when processing network data with eBPF. The lack of unbounded loops and some other freely expressible way of manipulating data mean that extra thought has to be given when looking and parsing application data. But with a thoughtful approach I don’t see why most protocols can’t be processed by eBPF, today we need to bind programs to TC (Traffic Control) but once XDP has egress support we can offload so much application processing that the network layer will become incredibly powerful. I’m exciting to parse more protocols :-) (DNS next).&lt;/p&gt;
 ]]></description>
        </item>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2023/12/08/Application-traffic-with-eBPF/</guid>
            <title>Application traffic with eBPF</title>
            <link>http://thebsdbox.co.uk/2023/12/08/Application-traffic-with-eBPF/</link>
            <category>eBPF</category>
            <category>Networking</category>
            <category>Layer 7</category>
            <pubDate>Fri, 08 Dec 2023 18:01:41 +0000</pubDate>
            <description><![CDATA[ &lt;p&gt;In a &lt;a href=&#34;https://thebsdbox.co.uk/2023/11/18/eBPF-adventures-in-networking/&#34;&gt;previous post&lt;/a&gt; I talked a little bit about building up the knowledge with eBPF to start to understand a little bit more about what is going in and out of a network adapter. Basically taking your ethernet frame and stripping off the headers (Ethernet + IP Header + TCP/UDP Header) you are finally left with what remains within the packet from an application or data sense. &lt;/p&gt;
&lt;p&gt;All of the code lives within the “learning eBPF” repository, specifically the eBPF code is &lt;a href=&#34;https://github.com/thebsdbox/learning-ebpf/blob/main/ebpf/http/http.c&#34;&gt;here&lt;/a&gt;. The plan for this post is to step through the bits that I think are useful or could be important…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; This code did do some Ingress/Egress packet modification so uses some eBPF helpers that requires 6.1+ of the Linux Kernel to work.&lt;/p&gt;
&lt;h2 id=&#34;The-maps&#34;&gt;&lt;a href=&#34;#The-maps&#34; class=&#34;headerlink&#34; title=&#34;The maps!&#34;&gt;&lt;/a&gt;The maps!&lt;/h2&gt;&lt;p&gt;Presumably you’ve come across these before? If not never fear!! Simply put an eBPF map is the mechanism for communicating between user-land and the in-kernel eBPF program. What is exceptionally cool (in my mind at least) is that these maps use keys and values.. so I don’t have to loop around data comparing and looking for what matches whatver it is i’m looking for, I pass a key and if something matches I get the corresponding data :D &lt;/p&gt;
&lt;p&gt;Below is the map that I will use, which is called &lt;code&gt;url_map&lt;/code&gt; the key is 20 characters long (a bounded “string” some might say), and the value that is assigned to that key is a struct that i’ve defined above.&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;// Defines a different URL associated with a key&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;struct url_path &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  __u8 path_len;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  __u8 path[max_path_len]; // This should be a char but code generation between here and Go..&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// Defines my URL map&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;struct &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  __uint(type, BPF_MAP_TYPE_HASH);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  __uint(max_entries, 1024);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  __type(key, char[max_path_len]);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  __type(value, struct url_path);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;url_map SEC(&amp;quot;.maps&amp;quot;);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&#34;The-eBPF-programs&#34;&gt;&lt;a href=&#34;#The-eBPF-programs&#34; class=&#34;headerlink&#34; title=&#34;The eBPF programs!&#34;&gt;&lt;/a&gt;The eBPF programs!&lt;/h2&gt;&lt;p&gt;There are two eBPF programs defined in the code &lt;code&gt;tc_egress&lt;/code&gt; and &lt;code&gt;tc_ingress&lt;/code&gt;, bonus points if you can guess how they are attached! For this post, we will only concern ourselves with the &lt;code&gt;tc_ingress&lt;/code&gt; program.&lt;/p&gt;
&lt;p&gt;So as we would see in the myriad of examples that already exist we need to go through the header identification dance. &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Do the sanity checks, and cast the &lt;code&gt;data&lt;/code&gt; to the type of &lt;code&gt;ethhdr&lt;/code&gt; (&lt;a href=&#34;https://en.wikipedia.org/wiki/Ethernet_frame&#34;&gt;Ethernet header&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Find the protocol within the ethernet frame by reading the &lt;code&gt;h_proto&lt;/code&gt; within the ethernet header (also called &lt;code&gt;Ethertype&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Cast the data &lt;em&gt;after&lt;/em&gt; the ethernet header as a &lt;code&gt;iphdr&lt;/code&gt; (&lt;a href=&#34;https://en.wikipedia.org/wiki/Internet_Protocol_version_4#Header&#34;&gt;IP header&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Find the protocol within the IP Header, we also will need to determine the size of the IP header (turns out they can be different sizes!  &lt;code&gt;¯\_(ツ)_/¯&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;To determine the size of the header we times it’s value by four, why I hear you ask! Well this value is multiplied by 32bits do determine the size of the header, so if the value was 6 then the header would be 192 bits (or 24 bytes). So to simply determine the IP header in bytes we can multiply this value by 4!&lt;/li&gt;
&lt;li&gt;Cast the data *after the IP Header as a &lt;code&gt;tcphdr&lt;/code&gt; (&lt;a href=&#34;https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_segment_structure&#34;&gt;TCP Header&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Like step (5) we will need to determine the size of the TCP Header (it again can be dynamic) and it’s the same step here, we simply need to multiply the value &lt;code&gt;doff&lt;/code&gt; by four to determine the header size in bytes.&lt;/li&gt;
&lt;li&gt;With all of this calculated we can now infer that the data lives at the end of the Ethernet Header size, the IP Header size and the TCP Header size. &lt;/li&gt;
&lt;li&gt;Finally we can determine how big the application data is by taking the &lt;code&gt;tot_len&lt;/code&gt; (total length) from the IP Header and subtracting the IP and TCP Header sizes.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;Application-Data&#34;&gt;&lt;a href=&#34;#Application-Data&#34; class=&#34;headerlink&#34; title=&#34;Application Data !!&#34;&gt;&lt;/a&gt;Application Data !!&lt;/h3&gt;&lt;p&gt;In order to read this data we will need a few things that were mentioned above! &lt;/p&gt;
&lt;p&gt;First, we will need the data offset (where the data starts) and that is found after the Ethernet header + the IP Header size (once calculated) and the TCP Header (again, once calculated). We will also need a buffer in order to store the data we will be reading from the socket buffer.&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;// A data buffer to store our application data&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;char pdata[60];&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// Calculate the offset to where our data actually lives&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;poffset = ETH_HLEN + ip_hlen + tcp_hlen;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// Load data from the socket buffer, poffset starts at the end of the TCP Header&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;int ret = bpf_skb_load_bytes(skb, poffset, pdata, 60);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if (ret != 0) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   return 0;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;We use the &lt;code&gt;bpf_skb_load_bytes&lt;/code&gt; to read the a set amount of data (&lt;code&gt;60&lt;/code&gt; bytes) into our buffer (&lt;code&gt;pdata&lt;/code&gt;) from the socket buffer (&lt;code&gt;skb&lt;/code&gt;) starting from the offset where we know the data is (&lt;code&gt;poffset&lt;/code&gt;)!&lt;/p&gt;
&lt;p&gt;At this point we have 60 bytes of data, should be enough for us to write some code to understand it.&lt;/p&gt;
&lt;h3 id=&#34;HTTP-Data&#34;&gt;&lt;a href=&#34;#HTTP-Data&#34; class=&#34;headerlink&#34; title=&#34;HTTP Data :-)&#34;&gt;&lt;/a&gt;HTTP Data :-)&lt;/h3&gt;&lt;p&gt;Lets look at what happens when we try a HTTP request!&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt; ~ curl code/test -vvv&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;*   Trying 192.168.0.22:80...&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;* Connected to code (192.168.0.22) port 80 (#0)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;gt; GET /test HTTP/1.1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;gt; Host: code&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;gt; User-Agent: curl/7.87.0&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;gt; Accept: */*&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;...&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;I’m using &lt;code&gt;curl&lt;/code&gt; to request the URL &lt;code&gt;/test&lt;/code&gt; from the host &lt;code&gt;code&lt;/code&gt; (code is my development VM, that runs code-server). We can see the data that is sent to the server (each line begins with &lt;code&gt;&amp;gt;&lt;/code&gt; to determine the direction of communication). The first line of data in a HTTP request is typically a &lt;em&gt;verb&lt;/em&gt; followed by the resource we would like to interact with and this request ends with the HTTP specification and a carriage return as defined in the &lt;a href=&#34;https://en.wikipedia.org/wiki/HTTP#HTTP/1.1_request_messages&#34;&gt;HTTP standards&lt;/a&gt;. So we can see the line that we care about is &lt;code&gt;GET /test&lt;/code&gt; (we/I don’t really care about the HTTP specification at this point :D). &lt;/p&gt;
&lt;h4 id=&#34;Find-the-HTTP-method&#34;&gt;&lt;a href=&#34;#Find-the-HTTP-method&#34; class=&#34;headerlink&#34; title=&#34;Find the HTTP method&#34;&gt;&lt;/a&gt;Find the HTTP method&lt;/h4&gt;&lt;p&gt;The first step is to read the first three characters of &lt;code&gt;pdata&lt;/code&gt; and make find if &lt;code&gt;pdata[0] == G&lt;/code&gt;, &lt;code&gt;pdata[1] == E&lt;/code&gt; and &lt;code&gt;pdata[2] == T&lt;/code&gt; this will effectively allow us to find if this is both a HTTP request in the first place and specifically if it is a HTTP request!&lt;/p&gt;
&lt;p&gt;Once we’ve validated those first 3 bytes we will want to read the more data starting from the 4 byte (three bytes for the request and one for the space between)!&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;char path[max_path_len];&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;memset(&amp;amp;path, 0, sizeof(path));&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;int path_len = 0;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// Find the request URI (starts at offset 4), ends with a space&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;for (int i = 4; i &amp;lt; sizeof(pdata) ; i++)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    if (pdata[i] != &amp;#x27; &amp;#x27;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        path[i-4] = pdata[i];&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#125; else &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        path[i-4] = &amp;#x27;\0&amp;#x27;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        path_len = i-4;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        break;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;The above function will read through the rest of the HTTP data (from the 4th byte) until it encounters a space, leaving us with the URL we are trying to &lt;code&gt;GET&lt;/code&gt;! We can validate this with a debug print statement:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;bpf_printk(&amp;quot;&amp;lt;- incoming path [%s], length [%d]&amp;quot;, path, path_len);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Which will look like the following in your logs:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&amp;lt;idle&amp;gt;-0       [001] dNs3. 2252901.017812: bpf_trace_printk: &amp;lt;- incoming path [/test], length [5]&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&#34;Acting-on-the-HTTP-application-request&#34;&gt;&lt;a href=&#34;#Acting-on-the-HTTP-application-request&#34; class=&#34;headerlink&#34; title=&#34;Acting on the HTTP application request&#34;&gt;&lt;/a&gt;Acting on the HTTP application request&lt;/h3&gt;&lt;p&gt;The above explanations detail what and how we’re reading the data, but if we want to “dynamically” look up the HTTP requests we will need to make use of eBPF maps. &lt;/p&gt;
&lt;p&gt;In our GO userland code we do the following:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;path := flag.String(&amp;quot;path&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;The URL Path to watch for&amp;quot;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;flag.Parse()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// ... &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// Create a uint8 array&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;var urlPath [20]uint8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// copy our bytes into the uint8 array (we can cast)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;copy(urlPath[:], *path)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// place our urlPath as the key&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;err = objs.UrlMap.Put(urlPath,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  bpfUrlPath&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Path:    urlPath,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    PathLen: uint8(len(urlPath)),&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &amp;#125;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  panic(err)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;As we can see in the code above our Go program when started will read from the flag &lt;code&gt;-path&lt;/code&gt; and that will be used as a &lt;strong&gt;key&lt;/strong&gt; in our eBPF map, the value can be ignored for now.&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;struct url_path *found_path = bpf_map_lookup_elem(&amp;amp;url_map, path);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if (found_path &amp;gt; 0) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    bpf_printk(&amp;quot;Looks like we&amp;#x27;ve found your path [%s]&amp;quot;, path);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    // perhaps do more, block traffic or redirect?&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;In our eBPF program we will do a map lookup on the HTTP request, if that request as a char array exists as a &lt;strong&gt;key&lt;/strong&gt; then we can operate on it!&lt;/p&gt;
&lt;p&gt;Starting our Go program now &lt;code&gt;sudo ./http -interface ens160 -path /test&lt;/code&gt; will yield the following:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;INFO[0000] Starting 🐝 the eBPF HTTP watcher, on interface [ens160] for path [/test] &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;INFO[0000] Loaded TC QDisc                              &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;INFO[0000] Press Ctrl-C to exit and remove the program  &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          &amp;lt;idle&amp;gt;-0       [001] d.s3. 2252901.015575: bpf_trace_printk: &amp;lt;- 0.0.0.0:56345 -&amp;gt; 0.0.0.0:80&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          &amp;lt;idle&amp;gt;-0       [001] D.s3. 2252901.015642: bpf_trace_printk: -&amp;gt; 192.168.0.22:80 -&amp;gt; 192.168.0.180:56345&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          &amp;lt;idle&amp;gt;-0       [001] d.s3. 2252901.017552: bpf_trace_printk: &amp;lt;- 0.0.0.0:56345 -&amp;gt; 0.0.0.0:80&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          &amp;lt;idle&amp;gt;-0       [001] d.s3. 2252901.017793: bpf_trace_printk: &amp;lt;- 0.0.0.0:56345 -&amp;gt; 0.0.0.0:80&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          &amp;lt;idle&amp;gt;-0       [001] dNs3. 2252901.017812: bpf_trace_printk: &amp;lt;- incoming path [/test], length [5]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          &amp;lt;idle&amp;gt;-0       [001] dNs3. 2252901.017814: bpf_trace_printk: Looks like we&amp;#x27;ve found your path [/test]&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&#34;Conclusion&#34;&gt;&lt;a href=&#34;#Conclusion&#34; class=&#34;headerlink&#34; title=&#34;Conclusion&#34;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;&lt;p&gt;Parsing HTTP isn’t too bad as it is a relatively simple protocol, it uses easy verbs and simple methods for structure with spaces and carriage returns to differentiate. This methodology would potentially work OK with other protocols like DNS, POP3 or SMTP. When things are encrypted we would need someway of decrypting before we can parse the data (that’s beyond me…). However, I hope that this sparks some ideas into playing more with eBPF and attempting to parse and operate on applications with eBPF!&lt;/p&gt;
 ]]></description>
        </item>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2023/11/18/eBPF-adventures-in-networking/</guid>
            <title>eBPF adventures in networking</title>
            <link>http://thebsdbox.co.uk/2023/11/18/eBPF-adventures-in-networking/</link>
            <category>eBPF</category>
            <category>networking</category>
            <pubDate>Sat, 18 Nov 2023 17:50:16 +0000</pubDate>
            <description><![CDATA[ &lt;p&gt;I’ve been wanting to write some hopefully useful posts around eBPF for sometime, although usually by the time I’ve come up with something I though may be useful someone has already beaten me to the punch. Given that I’ve been focussing in networking one way or another for a while, this has largely been the area that I’ve focussed on, although I did manage to put something together for the recent eBPF summit 2023 that I thought was quite fun. As mentioned there are a lot of people that are starting to write eBPF content, so I’ll potentially refer to their posts instead of duplicating content. &lt;/p&gt;
&lt;h2 id=&#34;XDP-vs-TC-or-even-sysprobes&#34;&gt;&lt;a href=&#34;#XDP-vs-TC-or-even-sysprobes&#34; class=&#34;headerlink&#34; title=&#34;XDP vs TC, or even sysprobes&#34;&gt;&lt;/a&gt;&lt;code&gt;XDP&lt;/code&gt; vs &lt;code&gt;TC&lt;/code&gt;, or even &lt;code&gt;sysprobes&lt;/code&gt;&lt;/h2&gt;&lt;p&gt;I’ll start with a few acronyms or even technologies in the Linux Kernel that you may or may not have come across. But basically from my perspective at least these are your main options for modifying a running system to interact with networking data.&lt;/p&gt;
&lt;h3 id=&#34;XDP&#34;&gt;&lt;a href=&#34;#XDP&#34; class=&#34;headerlink&#34; title=&#34;XDP&#34;&gt;&lt;/a&gt;XDP&lt;/h3&gt;&lt;p&gt;There already exists a lot of information about the eXpress Data Plane, so I’ll not delve into too much detail. The &lt;code&gt;tl;dr&lt;/code&gt; is that an XDP eBPF program that hooks into XDP will have access to the an incoming network frame before it is processed by the kernel itself. In some cases the eBPF program will be loaded into the NIC driver itself, which will effectively offload the program to the NIC itself. &lt;/p&gt;
&lt;h4 id=&#34;PROs&#34;&gt;&lt;a href=&#34;#PROs&#34; class=&#34;headerlink&#34; title=&#34;PROs&#34;&gt;&lt;/a&gt;PROs&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;The best performance&lt;/li&gt;
&lt;li&gt;Excellent for use cases such as firewalls, DDos protection or load balancing&lt;/li&gt;
&lt;li&gt;Sees incoming traffic before anything else can make any modifications&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;CONs&#34;&gt;&lt;a href=&#34;#CONs&#34; class=&#34;headerlink&#34; title=&#34;CONs&#34;&gt;&lt;/a&gt;CONs&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Ingress only, any traffic that you see with an XDP program is only incoming and there is currently no way of seeing traffic that is outbound&lt;/li&gt;
&lt;li&gt;Uses the &lt;code&gt;XDP&lt;/code&gt; data structure, which is a little different the &lt;code&gt;SKB&lt;/code&gt; that is the default for most socket programming.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;TC-or-Traffic-Control&#34;&gt;&lt;a href=&#34;#TC-or-Traffic-Control&#34; class=&#34;headerlink&#34; title=&#34;TC (or Traffic Control)&#34;&gt;&lt;/a&gt;TC (or Traffic Control)&lt;/h3&gt;&lt;p&gt;The Traffic Control is an integral part of the kernel networking structure, largely comprising of the capability of adding things such as qdiscs and filters to an interface. The qdisc largely focuses on providing a TBD and a filter can then be attached to this qdisc, often a filter will actually be an eBPF program under the covers. &lt;/p&gt;
&lt;p&gt;A common workflow is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a qdisc or replace an existing one that concerns itself with either ingress or egress. The qdisc is attached to an interface.&lt;/li&gt;
&lt;li&gt;Load your eBPF program&lt;/li&gt;
&lt;li&gt;Create a filter, that attaches itself to either ingress or egress now exposed through the qdisc on an interface. That filters has the eBPF program attached too it, meaning all traffic either incoming or outgoing will now run through a program (if connected)&lt;/li&gt;
&lt;li&gt;Profit 💰&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;PROs-1&#34;&gt;&lt;a href=&#34;#PROs-1&#34; class=&#34;headerlink&#34; title=&#34;PROs&#34;&gt;&lt;/a&gt;PROs&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Provides hooks for ingress and egress&lt;/li&gt;
&lt;li&gt;Uses the traditional &lt;code&gt;SKB&lt;/code&gt; data structure&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;CONs-1&#34;&gt;&lt;a href=&#34;#CONs-1&#34; class=&#34;headerlink&#34; title=&#34;CONs&#34;&gt;&lt;/a&gt;CONs&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;It’s slightly more complicated to attach a TC program to either their ingress or egress queues. The user will need to make use of qdiscs in order to do this, some eBPF SDKs don’t support TC program usage natively. &lt;/li&gt;
&lt;li&gt;The traffic a TC eBPF program sees may have already been modified by an earlier XDP program or even the kernel itself. &lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;Syscalls&#34;&gt;&lt;a href=&#34;#Syscalls&#34; class=&#34;headerlink&#34; title=&#34;Syscalls&#34;&gt;&lt;/a&gt;Syscalls&lt;/h3&gt;&lt;p&gt;This might seem a little weird compared to the other two, which are specifically designed in order to handle networking. Whereas an alternative is to attach some eBPF code to a syscall within the kernel, specifically calls such as &lt;code&gt;tcp4_connect()&lt;/code&gt; / &lt;code&gt;tcp6_connect()&lt;/code&gt;. This is a little bit further down the stack as at this point an incoming packet has already been through a lot of the kernel logic and the eBPF introspection point is as the traffic is about to interact with an application itself. &lt;/p&gt;
&lt;h2 id=&#34;Programming-a-network&#34;&gt;&lt;a href=&#34;#Programming-a-network&#34; class=&#34;headerlink&#34; title=&#34;Programming a network!&#34;&gt;&lt;/a&gt;Programming a network!&lt;/h2&gt;&lt;p&gt;So at this point we (hopefully) realise that we’ve a number of different entry points that will allow us to inject our code on the “conveyor belt” that a packet will traverse starting from the NIC all the way to the application (and back, in the case of egress). &lt;/p&gt;
&lt;h3 id=&#34;Recap&#34;&gt;&lt;a href=&#34;#Recap&#34; class=&#34;headerlink&#34; title=&#34;Recap&#34;&gt;&lt;/a&gt;Recap&lt;/h3&gt;&lt;p&gt;At the beginning of our so called “conveyor belt” we can attach our XDP program and get the raw untouched network data. In the middle of the “conveyor belt” our TC program will become part of the path through the kernel and receive potentially modified network data. At the end of the conveyor belt we can attach code to functions that the application will call in order to get the network data just before it is ingested by the running application. &lt;/p&gt;
&lt;h3 id=&#34;Data-representation&#34;&gt;&lt;a href=&#34;#Data-representation&#34; class=&#34;headerlink&#34; title=&#34;Data representation&#34;&gt;&lt;/a&gt;Data representation&lt;/h3&gt;&lt;p&gt;Depending where you attach your program determines two main things, one the relative level of potential modification of traffic and how the traffic is represented. &lt;/p&gt;
&lt;h4 id=&#34;The-XDP-struct&#34;&gt;&lt;a href=&#34;#The-XDP-struct&#34; class=&#34;headerlink&#34; title=&#34;The XDP struct&#34;&gt;&lt;/a&gt;The XDP struct&lt;/h4&gt;&lt;p&gt;I’d write about it but DataDog already have done, you can read that &lt;a href=&#34;https://www.datadoghq.com/blog/xdp-intro/#the-flow-of-an-xdp-program&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&#34;The-SKB-Socket-buffer&#34;&gt;&lt;a href=&#34;#The-SKB-Socket-buffer&#34; class=&#34;headerlink&#34; title=&#34;The SKB (Socket buffer)&#34;&gt;&lt;/a&gt;The SKB (Socket buffer)&lt;/h4&gt;&lt;p&gt;The SKB is a data type that has existed within the kernel long before eBPF was added to the kernel, and it already comes with a number of helper functions that make interacting with an SKB object a little easier. For more deep dive into SKB you can read this -&amp;gt; &lt;a href=&#34;http://vger.kernel.org/~davem/skb_data.html&#34;&gt;http://vger.kernel.org/~davem/skb_data.html&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&#34;Parsing-the-data&#34;&gt;&lt;a href=&#34;#Parsing-the-data&#34; class=&#34;headerlink&#34; title=&#34;Parsing the data&#34;&gt;&lt;/a&gt;Parsing the data&lt;/h4&gt;&lt;p&gt;Regardless of which struct you interact with, they share some commonality and that is largely that there are two variables that are identical across both data types. &lt;/p&gt;
&lt;p&gt;These are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;*data&lt;/code&gt;, which is a pointer to the data received by the eBPF program&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data_len&lt;/code&gt;, which is an integer that specified how much data there is (to help make sure you never access &lt;code&gt;*data&lt;/code&gt; more than &lt;code&gt;data_len&lt;/code&gt; (obvious really 🤓))&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So that all seems simple enough, but wait… what is actually in &lt;code&gt;*data&lt;/code&gt;?? (Well that is for you to discover)&lt;/p&gt;
&lt;p&gt;Well we do that through continually “casting” the &lt;code&gt;*data&lt;/code&gt; and moving along it to strip off the various headers in order to understand and find the underlying data!&lt;/p&gt;
&lt;h4 id=&#34;casting&#34;&gt;&lt;a href=&#34;#casting&#34; class=&#34;headerlink&#34; title=&#34;casting?&#34;&gt;&lt;/a&gt;casting?&lt;/h4&gt;&lt;p&gt;You can skip this if you like, but this is a quick (and terrible) example of how we typically take some raw data and turn it into something that makes sense. At the moment &lt;code&gt;*data&lt;/code&gt; will just be a stream of random data that won’t make any sense and we will need to effectively add “formatting” too it so that we can understand what it looks like.&lt;/p&gt;
&lt;p&gt;Consider the following random line of data &lt;code&gt;Bobby0004500100.503 Harvard Drive90210&lt;/code&gt; some of it makes sense to the raw eye but some of it is unclear. &lt;/p&gt;
&lt;p&gt;Imagine the data structure called “person”:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;Name: string&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Age: number&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Balance: float&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Street: string&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ZipCode: number&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;If we were to “cast” our random data to the “person” structure above it would suddenly become:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;Name: Bobby&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Age: 45&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Balance: 100.50&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Street: 3 Harvard Drive&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ZipCode: 90210&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Now all of a sudden I’m able to both understand and access the underlying variables in the structure as they now make sense, I.e. person-&amp;gt;Name and find out that this particular object of type person has the name variable “Bobby”!&lt;/p&gt;
&lt;p&gt;This is exactly what we will do to our &lt;code&gt;*data&lt;/code&gt; !&lt;/p&gt;
&lt;h3 id=&#34;What’s-in-the-data&#34;&gt;&lt;a href=&#34;#What’s-in-the-data&#34; class=&#34;headerlink&#34; title=&#34;What’s in the data?&#34;&gt;&lt;/a&gt;What’s in the data?&lt;/h3&gt;&lt;p&gt;So the first step is to determine if the data starts with an Ethernet frame! Pretty much all of the data that travels around starts with an Ethernet frame, which is pretty simplistic but it’s role is to have a source and destination hardware address (regardless of virtualisation/containerisation/cabled network or WiFi). So our first step is to cast our &lt;code&gt;*data&lt;/code&gt; to the type &lt;code&gt;ETHHDR&lt;/code&gt;, if this is successful we will now be able to understand the variables that make up the Ethernet header data type. These would include the source and destination MAC addresses, but also more importantly the what the contents of the remaining data is. Again, in most circumstances the contents of the &lt;code&gt;*data&lt;/code&gt; after the Ethernet header is typically an IP header, but we will validate be checking the Ethernet frames TBD variable. &lt;/p&gt;
&lt;p&gt;Once we validate that the next set of data is the IP Header we will need to cast the data after the Ethernet Header to the type IPHDR. Once we do this we will have access to the IP specific data such as source IP (&lt;code&gt;saddr&lt;/code&gt;) or destination address (&lt;code&gt;daddr&lt;/code&gt;), again importantly the IP header contains a variable that details what the data is after the end of the IP Header. This is usually a TCP header or UDP header, but there are other alternatives such as sctp etc.. &lt;/p&gt;
&lt;p&gt;Once we’ve looked inside the IPHeader and determined that the data type is TCP (could be UDP or something else), we will cast the data after both the Ethernet header and the IP header to the type TCP header! (Almost there). With access to the contends to the TCP header we have the TCP specific data, such as source port or destination port, the checksum to ensure validity of the data amongst other useful variables. &lt;/p&gt;
&lt;p&gt;We now have almost everything, however the TCP header can be variable length so we will need to determine this by looking at the tcl_len variable, which we need to times by 4. We now have everything we need to get to the final data!&lt;/p&gt;
&lt;p&gt;So, the *data points to the beginning of the data! We have determined that there is an Ethernet header followed by a IP header and finally a TCP header, which means *data + Ethernet header + IP header + TCP header = Actual application data !&lt;/p&gt;
&lt;h3 id=&#34;What-can-we-do-with-this-information&#34;&gt;&lt;a href=&#34;#What-can-we-do-with-this-information&#34; class=&#34;headerlink&#34; title=&#34;What can we do with this information ?&#34;&gt;&lt;/a&gt;What can we do with this information ?&lt;/h3&gt;&lt;p&gt;As we parse through the various headers, we effectively unlock more and more information at different layers of the OSI model!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[layer 2]&lt;/strong&gt; The Ethernet Header provides us with the source and destination hardware addresses, we could use this information to potentially stop frames being processed from source MAC addresses that we know to be dangerous.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[layer 3]&lt;/strong&gt; The IP Header contains the source and destination IP addresses, again we can act like a firewall by having an eBPF program drop all traffic for a specific IP address. Alternatively we could have logic that will potentially redirect traffic based upon the IP addresses of we could even implement load balancing logic at this layer that will redirect to an underlying set of other IP addresses&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[layer 4]&lt;/strong&gt; The TCP or UDP Headers define the destination port numbers, which we can use to determine what the application protocol is (I.e. port 80 typically means that the remaining *data is likely to be HTTP data). More often than not we would perform actions such as load balancing at this layer, based upon the destination (I.e. balance across multiple other load balancer addresses) &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[layer 7]&lt;/strong&gt; As mentioned the data at the end of the collection of various headers is the actual application data, which we can also parse (as long as we know the format). So for instance if an external web browser were to try and access &lt;code&gt;/index.html&lt;/code&gt; on my machine with an eBPF program attached, I’d parse all the way to TCP to determine that it was port 80 and then the application data should be in the HTTP format. I could validate this by looking at the first three characters of application data (after all the headers), with some pseudo code like below:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;ApplicationData = EthernetHDR + IPHDR + TCPHDR // Add all headers lengths together to find the data&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;If ( data[ApplicationData] = &amp;quot;G&amp;quot; &amp;amp;&amp;amp; data[ApplicationData+1] = &amp;quot;E&amp;quot; &amp;amp;&amp;amp; data[ApplicationData+2] = &amp;quot;T&amp;quot; ) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	// It&amp;#x27;s a HTTP GET request &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	// do something exciting&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&#34;Wrap-up&#34;&gt;&lt;a href=&#34;#Wrap-up&#34; class=&#34;headerlink&#34; title=&#34;Wrap up&#34;&gt;&lt;/a&gt;Wrap up&lt;/h2&gt;&lt;p&gt;Now we “kind of” understand the logic we should probably look at implementing some code to do all this .. that’s for another day though.&lt;/p&gt;
 ]]></description>
        </item>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2023/10/18/Finding-and-fiddling-with-Slacks-APIs/</guid>
            <title>Finding and fiddling with Slacks APIs</title>
            <link>http://thebsdbox.co.uk/2023/10/18/Finding-and-fiddling-with-Slacks-APIs/</link>
            <pubDate>Wed, 18 Oct 2023 11:41:53 +0100</pubDate>
            <description><![CDATA[ &lt;p&gt;It’s starting to feel a little bit as though the noose is starting to tighten a lot in the IT industry at the moment, whether it be Open Source projects spuriously changing their licenses and pulling the rug from their users to companies that expanded too far and too quickly during the pandemic suddenly contracting. This tightening of the belt has also impacted a bunch of tooling that people have come to depend on as part of their day to day life, or their workflow. &lt;/p&gt;
&lt;h2 id=&#34;Slack-isn’t-slack-when-it-comes-to-the-community-usage&#34;&gt;&lt;a href=&#34;#Slack-isn’t-slack-when-it-comes-to-the-community-usage&#34; class=&#34;headerlink&#34; title=&#34;Slack isn’t slack when it comes to the community usage&#34;&gt;&lt;/a&gt;Slack isn’t slack when it comes to the community usage&lt;/h2&gt;&lt;p&gt;So! Whats this rambling collection of words about?&lt;/p&gt;
&lt;p&gt;Like all of the wonderful platforms as a service out there &lt;del&gt;IRC for money&lt;/del&gt; Slack has the various tiers, each unlocking more and more functionality whilst ramping up the various costs associated. A lot of communities (especially in the Open Source world) have been built on the free and open communication that takes place on a Slack instance/workspace devoted to a project or community. But the problem i’m trying to address in this post is the hoops people need to jump though in order to join these communities hosted on Slack.&lt;/p&gt;
&lt;p&gt;When the community first starts out, it makes perfect sense that could be a need for simple restraints and/or approval process for joining a community. This is usually handled through an invitation process, if you want to join the community then you need to somehow signal that intent allowing the owner(s) of the community to then invite you to join. However once you start to hit any sort of scale then this simply becomes an unmanageable task, ultimately impacting the growth and health of a hopefully growing community and it is at this point where you would potentially want to open the floodgates as the community (hopefully) exponentially grows. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; On the side of security, we’re moving to a reactive instead of proactive approach for maintaining community membership (again coming with it’s own challenges). &lt;/p&gt;
&lt;h2 id=&#34;So-what-are-your-options&#34;&gt;&lt;a href=&#34;#So-what-are-your-options&#34; class=&#34;headerlink&#34; title=&#34;So what are your options?&#34;&gt;&lt;/a&gt;So what are your options?&lt;/h2&gt;&lt;h3 id=&#34;The-legitimate-option&#34;&gt;&lt;a href=&#34;#The-legitimate-option&#34; class=&#34;headerlink&#34; title=&#34;The legitimate option&#34;&gt;&lt;/a&gt;The legitimate option&lt;/h3&gt;&lt;p&gt;You’re main options without trying to automate anything are to simply select the &lt;code&gt;+ Add coworkers&lt;/code&gt; button and create an invite link, this can be set to never expire and is meant to work for 400 people (YMMV). It doesn’t really look like there are many options available to automate this procedure, you can open Slack in a web browser with “developer mode” enabled to capture the API endpoint but i’ve been unable to determine what token can be used.&lt;/p&gt;
&lt;p&gt;The endpoint for this API call is &lt;code&gt;https://&amp;lt;workspace&amp;gt;.slack.com/api/users.admin.createSharedInvite&lt;/code&gt;, which should be posted with a FORM with fields such as &lt;code&gt;token&lt;/code&gt;, &lt;code&gt;set_active&lt;/code&gt; however no token I could produce would seem to get this to work (even taken the token used in the browser). &lt;/p&gt;
&lt;p&gt;Example code:&lt;/p&gt;
&lt;figure class=&#34;highlight go&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;34&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;token := os.Getenv(&lt;span class=&#34;string&#34;&gt;&amp;quot;SLACK_TOKEN&amp;quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;instance := os.Getenv(&lt;span class=&#34;string&#34;&gt;&amp;quot;SLACK_INSTANCE&amp;quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;slack_url := fmt.Sprintf(&lt;span class=&#34;string&#34;&gt;&amp;quot;https://%s/api/users.admin.createSharedInvite&amp;quot;&lt;/span&gt;, instance)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;values := url.Values&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&lt;span class=&#34;string&#34;&gt;&amp;quot;expiration&amp;quot;&lt;/span&gt;:  &amp;#123;&lt;span class=&#34;string&#34;&gt;&amp;quot;99999&amp;quot;&lt;/span&gt;&amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&lt;span class=&#34;string&#34;&gt;&amp;quot;token&amp;quot;&lt;/span&gt;:       &amp;#123;token&amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&lt;span class=&#34;string&#34;&gt;&amp;quot;set_active&amp;quot;&lt;/span&gt;:  &amp;#123;&lt;span class=&#34;string&#34;&gt;&amp;quot;true&amp;quot;&lt;/span&gt;&amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&lt;span class=&#34;string&#34;&gt;&amp;quot;max_signups&amp;quot;&lt;/span&gt;: &amp;#123;&lt;span class=&#34;string&#34;&gt;&amp;quot;200&amp;quot;&lt;/span&gt;&amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;client := &amp;amp;http.Client&amp;#123;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;req, err := http.NewRequest(&lt;span class=&#34;string&#34;&gt;&amp;quot;POST&amp;quot;&lt;/span&gt;, slack_url, strings.NewReader(values.Encode()))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; err != &lt;span class=&#34;literal&#34;&gt;nil&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	fmt.Println(err)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;req.Header.Add(&lt;span class=&#34;string&#34;&gt;&amp;quot;Content-Type&amp;quot;&lt;/span&gt;, &lt;span class=&#34;string&#34;&gt;&amp;quot;application/x-www-form-urlencoded&amp;quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;resp, err := client.Do(req)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; err != &lt;span class=&#34;literal&#34;&gt;nil&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	fmt.Println(err)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; err != &lt;span class=&#34;literal&#34;&gt;nil&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	log.Fatal(err)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;defer&lt;/span&gt; resp.Body.Close()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;body, err := io.ReadAll(resp.Body)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; err != &lt;span class=&#34;literal&#34;&gt;nil&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	log.Fatal(err)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;fmt.Println(&lt;span class=&#34;type&#34;&gt;string&lt;/span&gt;(body[:]))&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&#34;The-“other”-option&#34;&gt;&lt;a href=&#34;#The-“other”-option&#34; class=&#34;headerlink&#34; title=&#34;The “other” option&#34;&gt;&lt;/a&gt;The “other” option&lt;/h3&gt;&lt;p&gt;With trying to replicate the Slack client behaviour not working, we’re left to trying less legitimate routes to make it easier for people to join the Slack workspace. So there are a number of Open Source projects that are quite old and unmaintained that make use of an old API (undocumented) endpoint &lt;code&gt;/api/users.admin.invite&lt;/code&gt; (not to be confused with &lt;code&gt;/api/admin.users.invite&lt;/code&gt;, &lt;a href=&#34;https://api.slack.com/methods/admin.users.invite&#34;&gt;api docs&lt;/a&gt;). This endpoint works with a legacy token that you’re no longer able to create (as of 2020).. it turns out invoking the correct sequence will allow you to create a token that can still use this endpoint however!&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create an App &lt;a href=&#34;https://api.slack.com/start/quickstart&#34;&gt;https://api.slack.com/apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Create the App from scratch, give it a name (any will do)&lt;/li&gt;
&lt;li&gt;Scroll down and make a copy the &lt;code&gt;Client ID&lt;/code&gt; and the &lt;code&gt;Client Secret&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Scroll further up and find the &lt;strong&gt;Permissions&lt;/strong&gt; box&lt;/li&gt;
&lt;li&gt;In the Redirect URLs box we will need to enter a bogus URL (make sure it doesn’t actually resolve) such as &lt;code&gt;https://0.0.0.0/&lt;/code&gt;, ensure you click &lt;code&gt;Save URLs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Under the &lt;strong&gt;User Token Scopes&lt;/strong&gt;, select the &lt;code&gt;Add an OAuth Scope&lt;/code&gt; and add &lt;code&gt;admin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Finally scroll back up to &lt;strong&gt;OAuth Tokens for Your Workspace&lt;/strong&gt; and select &lt;code&gt;Install to Workspace&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Select the &lt;code&gt;Allow&lt;/code&gt; button to create our first token!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At this point we will have a User OAuth Token, however this token even with it’s permissions wont work with the &lt;code&gt;users.admin.invite&lt;/code&gt; endpoint 🙄&lt;/p&gt;
&lt;p&gt;To generate our actual token we need to do some additional oauth steps!&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Build our specific url! &lt;code&gt;https://&amp;lt;workspace&amp;gt;.slack.com/oauth?client_id=&amp;lt;Client ID&amp;gt;&amp;amp;scope=admin%2Cclient&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Replace the &lt;code&gt;&amp;lt;workspace&amp;gt;&lt;/code&gt; with the name of your particular workspace, and change &lt;code&gt;&amp;lt;Client ID&amp;gt;&lt;/code&gt; to the ID from your application.&lt;/li&gt;
&lt;li&gt;Paste this URL into a new tab on the logged in browser, you’ll be redirected to a webpage that won’t work (remember the 0.0.0.0). However you should see the new URL in the address bar! (Including a &lt;code&gt;code=&lt;/code&gt; part of the URL). Grab this code for the next part!&lt;/li&gt;
&lt;li&gt;Build our new URL! &lt;code&gt;https://&amp;lt;workspace&amp;gt;.slack.com/api/oauth.access?client_id=&amp;lt;Client ID&amp;gt;&amp;amp;client_secret=&amp;lt;Client Secret&amp;gt;&amp;amp;code=&amp;lt;code&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Either open this in a web browser again, or in curl (remember to put the URL in quotes &lt;code&gt;&amp;quot;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The output from this will be some json &lt;code&gt;&amp;#123;&amp;quot;ok&amp;quot;:true,&amp;quot;access_token&amp;quot;:&amp;quot;xoxp-...&lt;/code&gt;, the &lt;code&gt;access_token&lt;/code&gt; is what we’re after !!&lt;/li&gt;
&lt;li&gt;Verify that you can invite people with your new token!&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;curl -X POST &amp;#x27;https://&amp;lt;workspace&amp;gt;.slack.com/api/users.admin.invite&amp;#x27; \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;--data &amp;#x27;email=dan@thebsdbox.co.uk&amp;amp;token=&amp;lt;token&amp;gt;&amp;amp;set_active=true&amp;#x27; \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;--compressed&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;A few seconds later…&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&amp;#123;&amp;quot;ok&amp;quot;:true&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Just to test, you can run the invite command again and if it’s all working as expected you should receive an error!&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&amp;#123;&amp;quot;ok&amp;quot;:false,&amp;quot;error&amp;quot;:&amp;quot;already_in_team_invited_user&amp;quot;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&#34;In-conclusion&#34;&gt;&lt;a href=&#34;#In-conclusion&#34; class=&#34;headerlink&#34; title=&#34;In conclusion&#34;&gt;&lt;/a&gt;In conclusion&lt;/h2&gt;&lt;p&gt;That seems like a lot of effort, but in the end it’s now possible again to build a simple web form allowing people to register themselves to join your Slack workspace!&lt;/p&gt;
 ]]></description>
        </item>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2022/12/31/The-Hackintoshes-over-the-years/</guid>
            <title>The Hackintoshes over the years</title>
            <link>http://thebsdbox.co.uk/2022/12/31/The-Hackintoshes-over-the-years/</link>
            <pubDate>Sat, 31 Dec 2022 10:46:07 +0000</pubDate>
            <description><![CDATA[ &lt;p&gt;Here we are again.. do I bother.. I guess I did&lt;/p&gt;
&lt;h2 id=&#34;The-Why&#34;&gt;&lt;a href=&#34;#The-Why&#34; class=&#34;headerlink&#34; title=&#34;The Why?&#34;&gt;&lt;/a&gt;The Why?&lt;/h2&gt;&lt;p&gt;As much as I love a nice shiny laptop, i’ve been using a steadily evolving desktop machine for well over a decade. In fact I don’t think i’ve ever been without a workstation since I was first introduced to a computer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Amstrad_CPC_464&#34;&gt;Amstrad CPC 464&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/PC1512&#34;&gt;Amstrad 1512&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Intel_DX4&#34;&gt;Intel DX4 (100Mhz)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/MMX_(instruction_set)&#34;&gt;P200 (With MMX), had a snappy advert back in the day&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Pentium_III&#34;&gt;P3 came in a cool cartridge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;P4 something or other .. &lt;/li&gt;
&lt;li&gt;Intel core quad something&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Haswell_(microarchitecture)&#34;&gt;i7 Haswell (todays machine)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In hindsight I realise that my current set up is actually pretty aged, but either I haven’t noticed or don’t care enough to upgrade. I also feel like i’ve been through a lot of Operating systems through my time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/MS-DOS#MS-DOS_2.x&#34;&gt;MS-DOS 2/3/6.22&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/GEM_(desktop_environment)&#34;&gt;Gem.. not sure this counts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Windows_3.1x&#34;&gt;Windows 3.11&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Windows_95&#34;&gt;Windows 95 .. on 1,000,000 floppy disks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Windows 98/ME, I don’t want to talk about it&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Windows_2000&#34;&gt;Windows 2000, the pinacle of Windows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Windows_XP&#34;&gt;Windows XP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/BeOS&#34;&gt;BeOS, some random gave me a CD in a bookshop at UNI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Slackware&#34;&gt;Slackware, such a miserable experience I still don’t run a Linux desktop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At some point at University I started a part time job as a Macintosh repair/support person, which I used to hold the belief was my worst work experience. &lt;strong&gt;However&lt;/strong&gt;, this exposed me to the world of Apple hardware and the symbiotic operating system. Well.. System7, MacOS 8 and MacOS 9 were pretty grim but some of the hardware 🤩. I remember one day someone bringing in a 20th Anniversary Mac &lt;img src=&#34;https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Apple_Museum_%28Prague%29_20th_Anniversary_Mac_%281997%29.jpg/2560px-Apple_Museum_%28Prague%29_20th_Anniversary_Mac_%281997%29.jpg&#34; alt=&#34;The 20th Anniversary Mac&#34;&gt; The product was a complete failure, but look at it !&lt;/p&gt;
&lt;p&gt;The G5 tower, such a clean yet imposing piece of metal.. pop the side off and it looked like the inside of a Dyson vacuum cleaner. I also got to work with the Apple rack mount hardware too, arguably the last time there would be anything aesthetically pleasing in a data centre. &lt;/p&gt;
&lt;p&gt;Anyway… I was lucky enough to be given a Powerbook G4, it had been repaired and after 6 months the person had never collected it or replied to calls/emails &lt;code&gt;¯\_(ツ)_/¯&lt;/code&gt; and thus began my switch over to OSX (then MacOS X and now MacOS, clearly naming is hard).&lt;/p&gt;
&lt;h2 id=&#34;Hackintosh&#34;&gt;&lt;a href=&#34;#Hackintosh&#34; class=&#34;headerlink&#34; title=&#34;Hackintosh&#34;&gt;&lt;/a&gt;Hackintosh&lt;/h2&gt;&lt;p&gt;With a shiny Powerbook in hand I proceeded to move all my daily content (music/photos) and social platform(s).. largely just last.fm, myspace and facebook at that point. My poor old workstation started to be turned on less and less as I started to find it slow and clunky when compared. In the mid 2000s Apple did the unthinkable (or maybe the thinkable, IBM were taking forever for a laptop G5 chip) and pivoted the Apple hardware away from PowerPC to Intel hardware, &lt;strong&gt;HOWEVER&lt;/strong&gt; the OS whilst x86 is still tied to Apple specific hardware.&lt;/p&gt;
&lt;p&gt;I honestly have no recollection where I found it (probably a dodgy torrent site), but I came across a OS X 10.4 DVD Image that was hacked to install on non Apple hardware and that was it! &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Some looking around on archive.org has revealed that it was &lt;code&gt;JaS OS Hackintosh&lt;/code&gt; OS images.. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For the next couple of OS X releases all of the underlying work was largely taken care of and things “just worked”.. here I was with the Apple experience but without the additional cost!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/hackintosh/hackintosh-1.jpeg&#34;&gt;&lt;br&gt;Apple Airport on the shelf there, a fantastic Access point back in the day&lt;br&gt;&lt;img src=&#34;/Images/hackintosh/hackintosh-2.jpeg&#34;&gt;&lt;br&gt;&lt;img src=&#34;/Images/hackintosh/hackintosh-3.jpeg&#34;&gt;&lt;br&gt;The Hackintosh’s endured a lot of my terrible skillz at WoW&lt;br&gt;&lt;img src=&#34;/Images/hackintosh/hackintosh-4.jpeg&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;Additional-complexity&#34;&gt;&lt;a href=&#34;#Additional-complexity&#34; class=&#34;headerlink&#34; title=&#34;Additional complexity&#34;&gt;&lt;/a&gt;Additional complexity&lt;/h2&gt;&lt;p&gt;At some point the overhead of producing these packaged OS installers clearly wasn’t worth it (presumably Apple were making things additionally difficult) and suddenly a lot of the overhead was past down to the users. At this point Hackintosh users suddenly had to care about things like EFI/Bootloaders/Kexts and a raft of additional tooling 🤯.&lt;/p&gt;
&lt;p&gt;To get a working system the following steps usually were required:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get a USB stick&lt;/li&gt;
&lt;li&gt;Create an EFI partition&lt;/li&gt;
&lt;li&gt;Install a bootloader (clover was the one, back in the day) and kexts for your system&lt;/li&gt;
&lt;li&gt;Get a MacOS X installer and write it to the USB stick&lt;/li&gt;
&lt;li&gt;Boot from the USB stick and find that (Display/USB/Network/Disk etc.. didn’t work) go back to step 3&lt;/li&gt;
&lt;li&gt;Install MacOS X to internal disk&lt;/li&gt;
&lt;li&gt;Boot from USB Stick into Mac OSX on internal disk&lt;/li&gt;
&lt;li&gt;Transfer working /EFI to internal disk and install any missing drivers/kernel modules&lt;/li&gt;
&lt;li&gt;Hope Apple didn’t release anything new for a while…&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Additionally in the middle of all this Nvidia had a falling out with Apple, meaning that Apple switched to AMD and immediately stopped any new drivers for Nvidia cards :-(&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/hackintosh/hackintosh-5.jpeg&#34;&gt;&lt;br&gt;&lt;img src=&#34;/Images/hackintosh/hackintosh-6.jpeg&#34;&gt;&lt;/p&gt;
&lt;p&gt;This was pretty much par for the course until MacOS Big Sur was released at which point a new boot loader was required (for reasons I’m not bothered to understand at this point)… Whilst this new bootloader allowed for installing these new MacOS releases, it incurs a significant overhead.. largely the managing of a LOT of XML. If by some chance you manage to incant a working /EFI configuration then lucky you, but it’s a slow and steady process involving many many many reboots and kernel panics. Luckily there is a pretty good community (it’s &lt;strong&gt;always&lt;/strong&gt; about a strong community) in the hackintosh space both on reddit and in other places such as TonyMacx86. The community is pretty helpful at inspecting peoples XML and pointing out irregularities and mistakes and getting people on their way. &lt;/p&gt;
&lt;p&gt;With a working /EFI I managed to move to OpenCore and finally stand up BigSur and then later on Monteray!&lt;/p&gt;
&lt;h2 id=&#34;Today&#34;&gt;&lt;a href=&#34;#Today&#34; class=&#34;headerlink&#34; title=&#34;Today&#34;&gt;&lt;/a&gt;Today&lt;/h2&gt;&lt;p&gt;So here we are.. to today (or the day I wrote this).. &lt;/p&gt;
&lt;p&gt;Anyway, for “reasons” the /EFI partition is FAT (old Microsoft standard) which isn’t the most reliable during power loss… Inevitably I had a powercut a few days ago and my /EFI partition was wiped, which lost the entire contents of my finely tuned and artisanal XML config. At this point I couldn’t decide if I had the energy to go through this process again. But given the xmas break and prospect of having to speak with the in-laws I decided this might be an opportunity..&lt;/p&gt;
&lt;p&gt;The process has improved since I followed the steps for BigSur, in fact the guide for &lt;a href=&#34;https://dortania.github.io/OpenCore-Install-Guide/&#34;&gt;OpenCore&lt;/a&gt; is fantastic and the additional drivers for USB/Audio just work™️ etc.. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get the OpenCore bootloader&lt;/li&gt;
&lt;li&gt;Add in the ACPI tables for your architecture&lt;/li&gt;
&lt;li&gt;Add in drivers that are needed&lt;/li&gt;
&lt;li&gt;Fight with XML&lt;/li&gt;
&lt;li&gt;Fight with XML more&lt;/li&gt;
&lt;li&gt;Fight with XML even more&lt;/li&gt;
&lt;li&gt;Profit?  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;/Images/hackintosh/hackintosh-7.jpeg&#34;&gt;&lt;br&gt;Nope&lt;br&gt;&lt;img src=&#34;/Images/hackintosh/hackintosh-8.jpeg&#34;&gt;&lt;br&gt;Yep&lt;/p&gt;
&lt;p&gt;My trusty workstation is now back up and running on &lt;a href=&#34;https://www.tomsguide.com/reviews/macos-ventura-review&#34;&gt;Ventura&lt;/a&gt; (although it doesn’t really seem like an OS for workstation use … )&lt;/p&gt;
&lt;h2 id=&#34;The-Future&#34;&gt;&lt;a href=&#34;#The-Future&#34; class=&#34;headerlink&#34; title=&#34;The Future&#34;&gt;&lt;/a&gt;The Future&lt;/h2&gt;&lt;p&gt;The move to Intel allowed hackintosh’s to become a thing, however the recent move to ARM from Apple will probably be the final nail in the coffin in the community. It’s been a good run of running MacOS on my hardware, but it does leave me in a quandary of where to go next when I can no longer run a Hackintosh.&lt;/p&gt;
 ]]></description>
        </item>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2021/07/28/KatNav-A-Kubernetes-Sat-Nav/</guid>
            <title>KatNav - A Kubernetes Sat Nav</title>
            <link>http://thebsdbox.co.uk/2021/07/28/KatNav-A-Kubernetes-Sat-Nav/</link>
            <pubDate>Wed, 28 Jul 2021 19:32:51 +0100</pubDate>
            <description><![CDATA[ &lt;h2 id=&#34;Create-the-project-with-kubebuilder&#34;&gt;&lt;a href=&#34;#Create-the-project-with-kubebuilder&#34; class=&#34;headerlink&#34; title=&#34;Create the project with kubebuilder&#34;&gt;&lt;/a&gt;Create the project with &lt;code&gt;kubebuilder&lt;/code&gt;&lt;/h2&gt;&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;kubebuilder init --domain fnnrn.me --owner &amp;quot;Dan Finneran&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;kubebuilder create api --group katnav --kind Directions --version v1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&#34;Edit-the-Resources&#34;&gt;&lt;a href=&#34;#Edit-the-Resources&#34; class=&#34;headerlink&#34; title=&#34;Edit the Resources&#34;&gt;&lt;/a&gt;Edit the Resources&lt;/h2&gt;&lt;p&gt;The path is &lt;code&gt;api/v1/directions_types.go&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&#34;Edit-the-Directions-Specification-DirectionsSpec&#34;&gt;&lt;a href=&#34;#Edit-the-Directions-Specification-DirectionsSpec&#34; class=&#34;headerlink&#34; title=&#34;Edit the Directions Specification DirectionsSpec&#34;&gt;&lt;/a&gt;Edit the Directions Specification &lt;code&gt;DirectionsSpec&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;The example is the usual &lt;code&gt;Foo&lt;/code&gt; etc..&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;// Foo is an example field of Directions. Edit directions_types.go to remove/update&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Foo string `json:&amp;quot;foo,omitempty&amp;quot;`&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;We will change this to what we need for the directions to actually work:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;// Source is where the beginning of our journey is&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Source string `json:&amp;quot;source&amp;quot;`&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// Destination is the end of our journey&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Destination string `json:&amp;quot;destination&amp;quot;`&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&#34;Edit-the-Directions-Status-DirectionsStatus&#34;&gt;&lt;a href=&#34;#Edit-the-Directions-Status-DirectionsStatus&#34; class=&#34;headerlink&#34; title=&#34;Edit the Directions Status DirectionsStatus&#34;&gt;&lt;/a&gt;Edit the Directions Status &lt;code&gt;DirectionsStatus&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;The Status reflects what has happened to our resource, and the expected status will be the directions.&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;// Directions is a list of directions to our destination&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Directions string `json:&amp;quot;directions&amp;quot;`&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// Routesummary gives a simple overview of the route&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;RouteSummary string `json:&amp;quot;routeSummary&amp;quot;`&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// StartLocation is the start from the directions API&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;StartLocation string `json:&amp;quot;startLocation&amp;quot;`&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// EndLocation is the start from the directions API&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;EndLocation string `json:&amp;quot;endLocation&amp;quot;`&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// Distance is the total distance of the journey&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Distance string `json:&amp;quot;distance&amp;quot;`&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// Duration is the amount of time the journey will take&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Duration string `json:&amp;quot;duration&amp;quot;`&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// Error captures an error message if the route isn&amp;#x27;t possible&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Error string `json:&amp;quot;error,omitempty&amp;quot;`&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&#34;Configure-Google-Account&#34;&gt;&lt;a href=&#34;#Configure-Google-Account&#34; class=&#34;headerlink&#34; title=&#34;Configure Google Account&#34;&gt;&lt;/a&gt;Configure Google Account&lt;/h2&gt;&lt;p&gt;To get an API key:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Visit Google APIs Console and log in with a Google Account.&lt;/li&gt;
&lt;li&gt;Select one of your existing projects, or create a new project.&lt;/li&gt;
&lt;li&gt;Enable the API(s) you want to use. The Go Client for Google Maps Services accesses the following APIs:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Directions API&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Create a new Server key from the &lt;strong&gt;credentials&lt;/strong&gt; menu.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Billing&lt;/strong&gt; may need to be enabled so do be careful with usage!&lt;/p&gt;
&lt;h3 id=&#34;Apply-API-key-to-secret&#34;&gt;&lt;a href=&#34;#Apply-API-key-to-secret&#34; class=&#34;headerlink&#34; title=&#34;Apply API key to secret&#34;&gt;&lt;/a&gt;Apply API key to secret&lt;/h3&gt;&lt;p&gt;This will create our secret (called katnav) within the “default” namespace:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;kubectl create secret generic katnav --from-literal=directionsKey=&amp;lt;API TOKEN&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&#34;Add-our-code-to-the-controller&#34;&gt;&lt;a href=&#34;#Add-our-code-to-the-controller&#34; class=&#34;headerlink&#34; title=&#34;Add our code to the controller&#34;&gt;&lt;/a&gt;Add our code to the controller&lt;/h2&gt;&lt;p&gt;The code that we’re going to add to the controller will do a few things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Connect to the Google Maps API&lt;/li&gt;
&lt;li&gt;Watch for &lt;code&gt;directions&lt;/code&gt; resources being created within the Kubernetes API&lt;/li&gt;
&lt;li&gt;Update these resources (the &lt;code&gt;status&lt;/code&gt;) with the directions and additional journey information&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;Add-the-Google-Maps-client&#34;&gt;&lt;a href=&#34;#Add-the-Google-Maps-client&#34; class=&#34;headerlink&#34; title=&#34;Add the Google Maps client&#34;&gt;&lt;/a&gt;Add the Google Maps client&lt;/h3&gt;&lt;p&gt;Add &lt;code&gt;    &amp;quot;googlemaps.github.io/maps&amp;quot;&lt;/code&gt; to the imports and a pointer to the client in the &lt;code&gt;DirectionsReconciler struct&lt;/code&gt;:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;// DirectionsReconciler reconciles a Directions object&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;type DirectionsReconciler struct &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	client.Client&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	Scheme  *runtime.Scheme&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	mClient *maps.Client&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&#34;Initialise-the-Google-Maps-client&#34;&gt;&lt;a href=&#34;#Initialise-the-Google-Maps-client&#34; class=&#34;headerlink&#34; title=&#34;Initialise the Google Maps client&#34;&gt;&lt;/a&gt;Initialise the Google Maps client&lt;/h3&gt;&lt;p&gt;At the beginning of the &lt;code&gt;SetupWithManager()&lt;/code&gt; function we will add in code that will do the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create our own Kubernetes client&lt;/li&gt;
&lt;li&gt;Retrieve our secret in the &lt;code&gt;default&lt;/code&gt; namespace&lt;/li&gt;
&lt;li&gt;Create our maps client and update out pointer to the client so we can use it in the &lt;code&gt;reconcile()&lt;/code&gt; function later:&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;33&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// So we have a Kubernetes cluster in r.Client, however we can&amp;#x27;t use it until the caches&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// start otherwise it will just return an error. So in order to get things ready we will&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// use our own client in order to get the key and set up the Google Maps client in advance&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;config, err := restClient.InClusterConfig()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	kubeConfig :=&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		cmdClient.NewDefaultClientConfigLoadingRules().GetDefaultFilename()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	config, err = cmdClient.BuildConfigFromFlags(&amp;quot;&amp;quot;, kubeConfig)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		return err&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// create the clientset&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;clientset, err := kubernetes.NewForConfig(config)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	panic(err.Error())&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;secret, err := clientset.CoreV1().Secrets(&amp;quot;default&amp;quot;).Get(context.TODO(), &amp;quot;katnav&amp;quot;, v1.GetOptions&amp;#123;&amp;#125;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return err&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;token := string(secret.Data[&amp;quot;directionsKey&amp;quot;])&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if token == &amp;quot;&amp;quot; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return fmt.Errorf(&amp;quot;no Token found within API Key&amp;quot;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;r.mClient, err = maps.NewClient(maps.WithAPIKey(token))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return err&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&#34;Reconcile&#34;&gt;&lt;a href=&#34;#Reconcile&#34; class=&#34;headerlink&#34; title=&#34;Reconcile !&#34;&gt;&lt;/a&gt;Reconcile !&lt;/h2&gt;&lt;p&gt;Below is our full &lt;code&gt;Reconcile()&lt;/code&gt; function that does the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Retrieves our resource that needs reconciling&lt;/li&gt;
&lt;li&gt;Gets the &lt;code&gt;source/destination&lt;/code&gt; from the &lt;code&gt;spec&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Creates a Google Maps Directions request to the API&lt;/li&gt;
&lt;li&gt;Updates the &lt;code&gt;status&lt;/code&gt; of the resource with the direction information (that we format, such as removing the HTML tags etc..)&lt;/li&gt;
&lt;li&gt;Apply the updated directions Object to the Kubernetes API!&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;41&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;42&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;43&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;44&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;45&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;46&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;47&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;48&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;49&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;50&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;51&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;52&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;53&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;54&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;55&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;56&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;57&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;58&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;func (r *DirectionsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	// Set the log to an acutal value so we can create log messages&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	log := log.FromContext(ctx)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	log.Info(&amp;quot;Reconciling direction resources&amp;quot;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	var directions katnavv1.Directions&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	if err := r.Get(ctx, req.NamespacedName, &amp;amp;directions); err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		if errors.IsNotFound(err) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			// object not found, could have been deleted after&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			// reconcile request, hence don&amp;#x27;t requeue&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			return ctrl.Result&amp;#123;&amp;#125;, nil&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		log.Error(err, &amp;quot;unable to fetch Directions object&amp;quot;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		// we&amp;#x27;ll ignore not-found errors, since they can&amp;#x27;t be fixed by an immediate&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		// requeue (we&amp;#x27;ll need to wait for a new notification), and we can get them&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		// on deleted requests.&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		return ctrl.Result&amp;#123;&amp;#125;, client.IgnoreNotFound(err)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	// your logic here&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	log.Info(&amp;quot;Determining journey&amp;quot;, &amp;quot;Source&amp;quot;, directions.Spec.Source, &amp;quot;Destination&amp;quot;, directions.Spec.Destination)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	request := &amp;amp;maps.DirectionsRequest&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		Origin:      directions.Spec.Source,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		Destination: directions.Spec.Destination,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		Mode:        maps.TravelModeDriving,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	route, _, err := r.mClient.Directions(context.Background(), request)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		log.Error(err, &amp;quot;unable to fetch Directions&amp;quot;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	var directionsString string&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	if len(route) == 0 &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		return ctrl.Result&amp;#123;&amp;#125;, nil&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	log.Info(&amp;quot;New Route&amp;quot;, &amp;quot;Summary&amp;quot;, route[0].Summary)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	for x := range route[0].Legs &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		for y := range route[0].Legs[x].Steps &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			stripped := strip.StripTags(route[0].Legs[x].Steps[y].HTMLInstructions)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			directionsString += stripped + &amp;quot;\n&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		directions.Status.Distance = route[0].Legs[x].Distance.HumanReadable&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		directions.Status.Duration = fmt.Sprintf(&amp;quot;Total Minutes: %f&amp;quot;, route[0].Legs[x].Duration.Minutes())&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		directions.Status.StartLocation = route[0].Legs[x].StartAddress&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		directions.Status.EndLocation = route[0].Legs[x].EndAddress&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	directions.Status.RouteSummary = route[0].Summary&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	directions.Status.Directions = directionsString&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	err = r.Client.Status().Update(context.TODO(), &amp;amp;directions, &amp;amp;client.UpdateOptions&amp;#123;&amp;#125;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		log.Error(err, &amp;quot;unable to update journey&amp;quot;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return ctrl.Result&amp;#123;&amp;#125;, nil&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&#34;Using&#34;&gt;&lt;a href=&#34;#Using&#34; class=&#34;headerlink&#34; title=&#34;Using !&#34;&gt;&lt;/a&gt;Using !&lt;/h2&gt;&lt;h3 id=&#34;Sheffield-yaml&#34;&gt;&lt;a href=&#34;#Sheffield-yaml&#34; class=&#34;headerlink&#34; title=&#34;Sheffield.yaml&#34;&gt;&lt;/a&gt;&lt;code&gt;Sheffield.yaml&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;This is our manifest for directions from York -&amp;gt; Sheffield!&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;apiVersion: katnav.fnnrn.me/v1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;kind: Directions&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;metadata:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  name: directions-to-sheffield&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;spec:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  source: York, uk&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  destination: Sheffield, uk&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;We can apply this to Kubernetes with:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;kubectl apply -f ./sheffield.yaml&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;We should see the following logs from the Controller:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;2021-07-28T15:56:47.901+0100    INFO    controller-runtime.manager.controller.directions        New Route       &amp;#123;&amp;quot;reconciler group&amp;quot;: &amp;quot;katnav.fnnrn.me&amp;quot;, &amp;quot;reconciler kind&amp;quot;: &amp;quot;Directions&amp;quot;, &amp;quot;name&amp;quot;: &amp;quot;directions-to-sheffield&amp;quot;, &amp;quot;namespace&amp;quot;: &amp;quot;default&amp;quot;, &amp;quot;Summary&amp;quot;: &amp;quot;M1&amp;quot;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2021-07-28T15:56:47.906+0100    INFO    controller-runtime.manager.controller.directions        Reconciling direction resources &amp;#123;&amp;quot;reconciler group&amp;quot;: &amp;quot;katnav.fnnrn.me&amp;quot;, &amp;quot;reconciler kind&amp;quot;: &amp;quot;Directions&amp;quot;, &amp;quot;name&amp;quot;: &amp;quot;directions-to-sheffield&amp;quot;, &amp;quot;namespace&amp;quot;: &amp;quot;default&amp;quot;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2021-07-28T15:56:47.906+0100    INFO    controller-runtime.manager.controller.directions        Determining journey     &amp;#123;&amp;quot;reconciler group&amp;quot;: &amp;quot;katnav.fnnrn.me&amp;quot;, &amp;quot;reconciler kind&amp;quot;: &amp;quot;Directions&amp;quot;, &amp;quot;name&amp;quot;: &amp;quot;directions-to-sheffield&amp;quot;, &amp;quot;namespace&amp;quot;: &amp;quot;default&amp;quot;, &amp;quot;Source&amp;quot;: &amp;quot;York, uk&amp;quot;, &amp;quot;Destination&amp;quot;: &amp;quot;Sheffield, uk&amp;quot;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&#34;Looking-at-our-Directions-👀&#34;&gt;&lt;a href=&#34;#Looking-at-our-Directions-👀&#34; class=&#34;headerlink&#34; title=&#34;Looking at our Directions 👀&#34;&gt;&lt;/a&gt;Looking at our Directions 👀&lt;/h3&gt;&lt;p&gt;If we examine the newly created directions object we should see that it’s status has been updated accordingly !&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;41&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;42&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;43&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;44&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;45&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;46&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;47&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;48&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;49&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;50&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;51&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;$ kubectl get directions directions-to-sheffield -o yaml&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;apiVersion: katnav.fnnrn.me/v1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;kind: Directions&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;metadata:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  annotations:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    kubectl.kubernetes.io/last-applied-configuration: |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &amp;#123;&amp;quot;apiVersion&amp;quot;:&amp;quot;katnav.fnnrn.me/v1&amp;quot;,&amp;quot;kind&amp;quot;:&amp;quot;Directions&amp;quot;,&amp;quot;metadata&amp;quot;:&amp;#123;&amp;quot;annotations&amp;quot;:&amp;#123;&amp;#125;,&amp;quot;name&amp;quot;:&amp;quot;directions-to-sheffield&amp;quot;,&amp;quot;namespace&amp;quot;:&amp;quot;default&amp;quot;&amp;#125;,&amp;quot;spec&amp;quot;:&amp;#123;&amp;quot;destination&amp;quot;:&amp;quot;Sheffield, uk&amp;quot;,&amp;quot;source&amp;quot;:&amp;quot;York, uk&amp;quot;&amp;#125;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  creationTimestamp: &amp;quot;2021-07-28T14:34:56Z&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  generation: 2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  name: directions-to-sheffield&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  namespace: default&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  resourceVersion: &amp;quot;6986253&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  uid: c4077b0b-8f2f-4fc3-82c0-2d690a24d98a&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;spec:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  destination: Sheffield, uk&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  source: York, uk&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;status:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  directions: |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Head southwest on Lendal Bridge/Station Rd/A1036 toward Rougier St/B1227Continue to follow A1036&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Keep left to continue toward Station Rd/A1036&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Continue onto Station Rd/A1036Continue to follow A1036&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Turn right onto Blossom St/A1036Continue to follow A1036&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    At the roundabout, take the 2nd exit onto Tadcaster Rd Dringhouses/A1036&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Take the ramp to Leeds&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Merge onto A64&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Merge onto A1(M) via the ramp to Leeds/M1/Manchester/M62&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Keep right at the fork to continue on M1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    At junction 34, take the A6109 exit to Sheffield(E)/Rotherham(C)/Meadowhall&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    At Meadowhall Roundabout, take the 4th exit onto Meadowhall Rd/A6109&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Keep right to stay on Meadowhall Rd/A6109Continue to follow A6109&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    At the roundabout, take the 1st exit onto Brightside Ln/A6109Continue to follow A6109&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Slight right onto Savile St/A6109&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Turn right onto Derek Dooley Way/A61Continue to follow A61&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Slight left onto Corporation St/A61&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Slight left onto Corporation St/B6539&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    At the roundabout, take the 2nd exit onto W Bar Green/B6539Continue to follow B6539&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    At the roundabout, take the 3rd exit onto Broad Ln/B6539Continue to follow B6539&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    At the roundabout, take the 1st exit onto Upper Hanover St&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Continue onto Hanover Way&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    At the roundabout, take the 1st exit onto Moore St&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Continue onto Charter Row&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Continue onto Furnival Gate&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Furnival Gate turns left and becomes Pinstone St&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Turn right onto Burgess St&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Burgess St turns right and becomes Barker&amp;#x27;s Pool&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Barker&amp;#x27;s Pool turns left and becomes Leopold St&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  distance: 93.8 km&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  duration: &amp;#x27;Total Minutes: 77.816667&amp;#x27;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  endLocation: Sheffield, UK&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  routeSummary: M1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  startLocation: York, UK&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt; ]]></description>
        </item>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2021/04/02/The%20%22perfect%22%20virtual%20Tinkerbell%20environment%20on%20Equinix%20Metal/</guid>
            <title>The &#34;perfect&#34; virtual Tinkerbell environment on Equinix Metal</title>
            <link>http://thebsdbox.co.uk/2021/04/02/The%20%22perfect%22%20virtual%20Tinkerbell%20environment%20on%20Equinix%20Metal/</link>
            <pubDate>Fri, 02 Apr 2021 17:45:02 +0100</pubDate>
            <description><![CDATA[ &lt;h2 id=&#34;pre-requisites&#34;&gt;&lt;a href=&#34;#pre-requisites&#34; class=&#34;headerlink&#34; title=&#34;pre-requisites&#34;&gt;&lt;/a&gt;pre-requisites&lt;/h2&gt;&lt;p&gt;This is a rough shopping list of skills/accounts that will be a benefit for this guide.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Equinix Metal portal account&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GO&lt;/code&gt; experience (basic)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;iptables&lt;/code&gt; usage (basic)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;qemu&lt;/code&gt; usage (basic)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;Our-Tinkerbell-server-considerations&#34;&gt;&lt;a href=&#34;#Our-Tinkerbell-server-considerations&#34; class=&#34;headerlink&#34; title=&#34;Our Tinkerbell server considerations&#34;&gt;&lt;/a&gt;Our Tinkerbell server considerations&lt;/h2&gt;&lt;p&gt;Some “finger in the air” mathematics are generally required when selecting an appropriately sized physical host on Equinix metal. But if we take a quick look at the expected requirements:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;CONTAINER ID        NAME                   CPU %               MEM USAGE / LIMIT    MEM %               NET I/O             BLOCK I/O           PIDS&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;582ce8ba4cdf        deploy_tink-cli_1      0.00%               832KiB / 7.79GiB     0.01%               0B / 0B             13.1MB / 0B         1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4aeb11684865        deploy_tink-server_1   0.00%               7.113MiB / 7.79GiB   0.09%               75.7MB / 76.6MB     4.94MB / 0B         11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24c7c5961a2b        deploy_boots_1         0.00%               8.652MiB / 7.79GiB   0.11%               0B / 0B             5.66MB / 0B         7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;37d2e840bd54        deploy_registry_1      0.02%               8.344MiB / 7.79GiB   0.10%               0B / 0B             1.32MB / 0B         11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;a70a54baff08        deploy_hegel_1         0.00%               2.984MiB / 7.79GiB   0.04%               0B / 0B             246kB / 0B          5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;33d953c9b057        deploy_db_1            13.66%              6.363MiB / 7.79GiB   0.08%               24.7MB / 22.2MB     1.5MB / 1.34GB      8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2fa8a6064036        deploy_nginx_1         0.00%               2.867MiB / 7.79GiB   0.04%               341kB / 0B          0B / 0B             3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;We can see that the components for the Tinkerbell stack are particularly light, with this in mind we can be very confident that we can have all of our userland components (tinkerbell/docker/bash etc..) within 1GB of ram and leave all remaining memory for the virtual machines. &lt;/p&gt;
&lt;p&gt;That brings us onto the next part, which is how big should the virtual machines be?&lt;/p&gt;
&lt;h3 id=&#34;In-memory-OS-OSIE&#34;&gt;&lt;a href=&#34;#In-memory-OS-OSIE&#34; class=&#34;headerlink&#34; title=&#34;In memory OS (OSIE)&#34;&gt;&lt;/a&gt;In memory OS (OSIE)&lt;/h3&gt;&lt;p&gt;Every machine that is booted by Tinkerbell will be passed the in-memory Operating System called &lt;code&gt;OSIE&lt;/code&gt; which is an alpine based Linux OS that ultimately will run the workflows. As this is in-memory we will need to account for a few things (before we even install our Operating System through a workflow. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OSIE kernel&lt;/li&gt;
&lt;li&gt;OSIE RAM Disk (Includes Alpine userland and the docker engine)&lt;/li&gt;
&lt;li&gt;Action images (at rest)&lt;/li&gt;
&lt;li&gt;Action containers (running)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;strong&gt;OSIE Ram Disk&lt;/strong&gt; whilst it looks like a normal filesystem is actually held in the memory of the host itself so immediately will withhold that memory from other usage.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Action image&lt;/strong&gt; will be pulled locally from a repository and again written to disk, &lt;strong&gt;however&lt;/strong&gt; the disk that these images are written to is a ram disk, so these images will again withhold available memory.&lt;/p&gt;
&lt;p&gt;Finally, these images when ran (&lt;strong&gt;Action containers&lt;/strong&gt;) will have binaries in them that will require available memory in order to run.&lt;/p&gt;
&lt;p&gt;The majority of this memory usage from the as seen from above is for the in-memory filesystem in order to host the userland tools and the images listed in the workflow. From testing we’ve normally seen that &lt;strong&gt;&amp;gt;2GB&lt;/strong&gt; is required, however if your workflow consists of large action images then this will need adjusting accordingly.&lt;/p&gt;
&lt;p&gt;With all this in consideration, it is quite possible to run Tinkerbell on Equinix Metals smallest offering the &lt;code&gt;t1.small.x86&lt;/code&gt;, however if you’re looking at deploying multiple machines with tinkerbell then ideally a machine with 32GB of ram will comfortably allow a comfortable amount of headroom.&lt;/p&gt;
&lt;h3 id=&#34;Recomended-instances-OS&#34;&gt;&lt;a href=&#34;#Recomended-instances-OS&#34; class=&#34;headerlink&#34; title=&#34;Recomended instances/OS&#34;&gt;&lt;/a&gt;Recomended instances/OS&lt;/h3&gt;&lt;p&gt;Check the inventory of your desired facility, but the recommended instances are below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;c1.small.x86&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;c3.small.x86&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x1.small.x86&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For speed of deployment and modernity of the Operating System, either ubuntu 18.04 or ubuntu 20.04 are recommended.&lt;/p&gt;
&lt;h2 id=&#34;Deploying-Tinkerbell-on-Equinix-Metal&#34;&gt;&lt;a href=&#34;#Deploying-Tinkerbell-on-Equinix-Metal&#34; class=&#34;headerlink&#34; title=&#34;Deploying Tinkerbell on Equinix Metal&#34;&gt;&lt;/a&gt;Deploying Tinkerbell on Equinix Metal&lt;/h2&gt;&lt;p&gt;In this example I’ll be deploying a &lt;code&gt;c3.small.x86&lt;/code&gt; in the Amsterdamn faclity &lt;code&gt;ams6&lt;/code&gt; with &lt;code&gt;ubuntu 20.04&lt;/code&gt;. Once our machine is up and running, we’ll need to install our required packages for running  tinkerbell and our virtual machines.&lt;/p&gt;
&lt;h3 id=&#34;Update-the-packages&#34;&gt;&lt;a href=&#34;#Update-the-packages&#34; class=&#34;headerlink&#34; title=&#34;Update the packages&#34;&gt;&lt;/a&gt;Update the packages&lt;/h3&gt;&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;apt-get update -y&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&#34;Install-git-to-clone-the-sandbox&#34;&gt;&lt;a href=&#34;#Install-git-to-clone-the-sandbox&#34; class=&#34;headerlink&#34; title=&#34;Install git (to clone the sandbox)&#34;&gt;&lt;/a&gt;Install git (to clone the sandbox)&lt;/h3&gt;&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;apt-get install -y git&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&#34;Install-required-dependancies&#34;&gt;&lt;a href=&#34;#Install-required-dependancies&#34; class=&#34;headerlink&#34; title=&#34;Install required dependancies&#34;&gt;&lt;/a&gt;Install required dependancies&lt;/h3&gt;&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;apt-get install -y 	apt-transport-https \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	ca-certificates \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	curl \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	gnupg-agent \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	software-properties-common \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	qemu-kvm \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	libguestfs-tools \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	libosinfo-bin&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&#34;Grab-shack-qemu-wrapper&#34;&gt;&lt;a href=&#34;#Grab-shack-qemu-wrapper&#34; class=&#34;headerlink&#34; title=&#34;Grab shack (qemu wrapper)&#34;&gt;&lt;/a&gt;Grab shack (qemu wrapper)&lt;/h3&gt;&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;wget https://github.com/plunder-app/shack/releases/download/v0.0.0/shack-0.0.0.tar.gz; \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;tar -xvzf shack-0.0.0.tar.gz; \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;mv shack /usr/local/bin&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&#34;Create-our-internal-tinkerbell-network-not-needed&#34;&gt;&lt;a href=&#34;#Create-our-internal-tinkerbell-network-not-needed&#34; class=&#34;headerlink&#34; title=&#34;Create our internal tinkerbell network (not needed)&#34;&gt;&lt;/a&gt;Create our internal tinkerbell network (not needed)&lt;/h3&gt;&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;sudo ip link add tinkerbell type bridge&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&#34;Create-shack-configuration&#34;&gt;&lt;a href=&#34;#Create-shack-configuration&#34; class=&#34;headerlink&#34; title=&#34;Create shack configuration&#34;&gt;&lt;/a&gt;Create shack configuration&lt;/h3&gt;&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;shack example &amp;gt; shack.yaml&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&#34;Edit-and-apply-configuration&#34;&gt;&lt;a href=&#34;#Edit-and-apply-configuration&#34; class=&#34;headerlink&#34; title=&#34;Edit and apply configuration&#34;&gt;&lt;/a&gt;Edit and apply configuration&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;Change the bridgeName: from &lt;code&gt;plunder&lt;/code&gt; to &lt;code&gt;tinkerbell&lt;/code&gt;, then run &lt;code&gt;shack network create&lt;/code&gt;. This will create a new interface on our tinkerbell bridge&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Run &lt;code&gt;shack network create&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&#34;Test-virtual-machine-creation&#34;&gt;&lt;a href=&#34;#Test-virtual-machine-creation&#34; class=&#34;headerlink&#34; title=&#34;Test virtual machine creation&#34;&gt;&lt;/a&gt;Test virtual machine creation&lt;/h3&gt;&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;shack vm start --id f0cb3c -v&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;lt;...&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;shack VM configuration&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Network Device:	plndrVM-f0cb3c&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;VM MAC Address:	c0:ff:ee:f0:cb:3c&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;VM UUID:	f0cb3c&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;VNC Port:	6671&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;We can also examine that this has worked, by examining &lt;code&gt;ip addr&lt;/code&gt;:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;11: plunder: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc noqueue state UP group default &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    link/ether 2a:27:61:44:d2:07 brd ff:ff:ff:ff:ff:ff&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    inet 192.168.1.1/24 brd 192.168.1.255 scope global plunder&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;       valid_lft forever preferred_lft forever&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    inet6 fe80::bcc7:caff:fe63:8016/64 scope link &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;       valid_lft forever preferred_lft forever&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12: plndrVM-f0cb3c: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc fq_codel master plunder state UP group default qlen 1000&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    link/ether 2a:27:61:44:d2:07 brd ff:ff:ff:ff:ff:ff&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    inet6 fe80::2827:61ff:fe44:d207/64 scope link &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;       valid_lft forever preferred_lft forever&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Connect to the VNC port with a client (the random port generated in this example is &lt;code&gt;6671&lt;/code&gt;).. it will be exposed on the public address of our equinix metal host.&lt;/p&gt;
&lt;p&gt;Kill the VM:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;shack vm stop --id f0cb3c -d&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;


&lt;h3 id=&#34;Install-sandbox-dependencies&#34;&gt;&lt;a href=&#34;#Install-sandbox-dependencies&#34; class=&#34;headerlink&#34; title=&#34;Install sandbox dependencies&#34;&gt;&lt;/a&gt;Install sandbox dependencies&lt;/h3&gt;&lt;h4 id=&#34;Docker&#34;&gt;&lt;a href=&#34;#Docker&#34; class=&#34;headerlink&#34; title=&#34;Docker&#34;&gt;&lt;/a&gt;Docker&lt;/h4&gt;&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;curl -fsSL https://download.docker.com/linux/ubuntu/gpg |sudo apt-key add - \ sudo add-apt-repository &amp;quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable&amp;quot; \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;sudo apt-get update \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;sudo apt-get install -y docker-ce docker-ce-cli containerd.io &lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h4 id=&#34;Docker-compose&#34;&gt;&lt;a href=&#34;#Docker-compose&#34; class=&#34;headerlink&#34; title=&#34;Docker compose&#34;&gt;&lt;/a&gt;Docker compose&lt;/h4&gt;&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;sudo curl -L \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&amp;quot;https://github.com/docker/compose/releases/download/1.26.0/docker-compose-$(uname -s)-$(uname -m)&amp;quot; \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	-o /usr/local/bin/docker-compose&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;sudo chmod +x /usr/local/bin/docker-compose&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&#34;Clone-the-sandbox&#34;&gt;&lt;a href=&#34;#Clone-the-sandbox&#34; class=&#34;headerlink&#34; title=&#34;Clone the sandbox&#34;&gt;&lt;/a&gt;Clone the sandbox&lt;/h3&gt;&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;git clone https://github.com/tinkerbell/sandbox.git&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;cd sandbox&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&#34;Configure-the-sandbox&#34;&gt;&lt;a href=&#34;#Configure-the-sandbox&#34; class=&#34;headerlink&#34; title=&#34;Configure the sandbox&#34;&gt;&lt;/a&gt;Configure the sandbox&lt;/h3&gt;&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;./generate-envrc.sh plunder &amp;gt; .env&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;./setup.sh&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&#34;Start-Tinkerbell&#34;&gt;&lt;a href=&#34;#Start-Tinkerbell&#34; class=&#34;headerlink&#34; title=&#34;Start Tinkerbell&#34;&gt;&lt;/a&gt;Start Tinkerbell&lt;/h3&gt;&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;# Add Nginx address to Tinkerbell&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;sudo ip addr add 192.168.1.2/24 dev plunder&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;cd deploy&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;source ../.env; docker-compose up -d&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;At this point we now have a server with available resource, we can create virtual machines and tinkerbell is listening on the correct internal network!&lt;/p&gt;
&lt;h2 id=&#34;Create-a-workflow-debian-example&#34;&gt;&lt;a href=&#34;#Create-a-workflow-debian-example&#34; class=&#34;headerlink&#34; title=&#34;Create a workflow (debian example)&#34;&gt;&lt;/a&gt;Create a workflow (debian example)&lt;/h2&gt;&lt;h3 id=&#34;Clone-the-debian-repository&#34;&gt;&lt;a href=&#34;#Clone-the-debian-repository&#34; class=&#34;headerlink&#34; title=&#34;Clone the debian repository&#34;&gt;&lt;/a&gt;Clone the debian repository&lt;/h3&gt;&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;cd $HOME&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;git clone https://github.com/fransvanberckel/debian-workflow&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;cd debian-workflow/debian&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&#34;Build-the-debian-content&#34;&gt;&lt;a href=&#34;#Build-the-debian-content&#34; class=&#34;headerlink&#34; title=&#34;Build the debian content&#34;&gt;&lt;/a&gt;Build the debian content&lt;/h3&gt;&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;./verify_json_tweaks.sh &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;# The JSON syntax is valid&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;./build_and_push_images.sh&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&#34;Edit-configuration&#34;&gt;&lt;a href=&#34;#Edit-configuration&#34; class=&#34;headerlink&#34; title=&#34;Edit configuration&#34;&gt;&lt;/a&gt;Edit configuration&lt;/h3&gt;&lt;p&gt;Modify the &lt;code&gt;create_tink_workflow.sh&lt;/code&gt; so that the mac address is &lt;code&gt;c0:ff:ee:f0:cb:3c&lt;/code&gt;, this is the mac address we will be using as part of our demonstration. &lt;/p&gt;
&lt;p&gt;For using VNC, modify the &lt;code&gt;facility.facility_code&lt;/code&gt; from &lt;code&gt;&amp;quot;onprem&amp;quot;&lt;/code&gt; to &lt;code&gt;&amp;quot;onprem console=ttys0 vha=normal&amp;quot;&lt;/code&gt;. This will ensure all output is printed to the VNC window that we connect to.&lt;/p&gt;
&lt;h3 id=&#34;Create-the-workflow&#34;&gt;&lt;a href=&#34;#Create-the-workflow&#34; class=&#34;headerlink&#34; title=&#34;Create the workflow&#34;&gt;&lt;/a&gt;Create the workflow&lt;/h3&gt;&lt;p&gt;Here we will be asked for some password credentials for our new machine:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;./create_tink_workflow.sh&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&#34;Start-our-virtual-host-to-install-on&#34;&gt;&lt;a href=&#34;#Start-our-virtual-host-to-install-on&#34; class=&#34;headerlink&#34; title=&#34;Start our virtual host to install on!&#34;&gt;&lt;/a&gt;Start our virtual host to install on!&lt;/h2&gt;&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;shack vm start --id f0cb3c -v&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;lt;...&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;shack VM configuration&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Network Device:	plndrVM-f0cb3c&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;VM MAC Address:	c0:ff:ee:f0:cb:3c&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;VM UUID:	f0cb3c&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;VNC Port:	6671&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;We can now watch the install on the VNC port &lt;code&gt;6671&lt;/code&gt;&lt;/p&gt;
&lt;h2 id=&#34;Troubleshooting&#34;&gt;&lt;a href=&#34;#Troubleshooting&#34; class=&#34;headerlink&#34; title=&#34;Troubleshooting&#34;&gt;&lt;/a&gt;Troubleshooting&lt;/h2&gt;&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;http://192.168.1.1/undionly.pxe could not be found&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;If a machine boots and has this error it means that it’s workflow has been completed, in order to boot this server &lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;could not configure /dev/net/tun (plndrVM-f0cb3c): Device or resource busy &lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;This means that an old qemu session left an old adapter, we can remove it with the command below:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ip link delete plndrVM-f0cb3c&lt;/code&gt;&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;Is another process using the image [f0cb3c.qcow2]? &lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;We’ve left an old disk image laying around, we can remove this with &lt;code&gt;rm&lt;/code&gt;&lt;/p&gt;
 ]]></description>
        </item>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2021/01/01/Putting-a-VIP-in-your-Kubernetes-Clusters/</guid>
            <title>Putting a VIP in your Kubernetes Clusters</title>
            <link>http://thebsdbox.co.uk/2021/01/01/Putting-a-VIP-in-your-Kubernetes-Clusters/</link>
            <pubDate>Fri, 01 Jan 2021 10:46:26 +0000</pubDate>
            <description><![CDATA[ &lt;p&gt;In this post i’ve a bunch of things I want to cover all about &lt;code&gt;Type:LoadBalancer&lt;/code&gt; (or in most cases a VIP (Virtual IP address). In most Kubernetes environments a user will fire in some &lt;code&gt;yaml&lt;/code&gt; defining a LoadBalancer service or do a &lt;code&gt;kubectl expose&lt;/code&gt; and “magic” will occur. As far as the end-user is concerned their new service will have a brand new IP address attached to it and when an end-user hits that IP address their traffic will hit a pod that is attached to that service. But what’s actually occuring in most cases? Who can provide this address? How does it all hang together?&lt;/p&gt;
&lt;p&gt;That’s what we’re going to cover in this post.&lt;/p&gt;
&lt;p&gt;The venerable &lt;a href=&#34;https://twitter.com/thockin&#34;&gt;Tim Hockin&lt;/a&gt; covers this in a bunch of slidedecks here -&amp;gt; &lt;a href=&#34;https://speakerdeck.com/thockin/bringing-traffic-into-your-kubernetes-cluster&#34;&gt;https://speakerdeck.com/thockin/bringing-traffic-into-your-kubernetes-cluster&lt;/a&gt;, so feel free to start here and then i’ll cover some bits in more detail or from a different angle :-)&lt;/p&gt;
&lt;h2 id=&#34;Kubernetes-Services&#34;&gt;&lt;a href=&#34;#Kubernetes-Services&#34; class=&#34;headerlink&#34; title=&#34;Kubernetes Services&#34;&gt;&lt;/a&gt;Kubernetes Services&lt;/h2&gt;&lt;p&gt;A lot of this is already mentioned above, but put simply a service is a method of providing access to a pod or number of pods either externally or internally. A common example is exposing a web server front end, where we may have a deployment of 10 nginx pods.. we need to allow end users to access these 10 pods. Within Kubernetes we can define a service that is attached to this deployment, and thanks to the logic within Kubernetes we don’t need to concern ourselves too much with these pods.. we can scale up/scale down.. kill pods etc.. as long as we’re coming through the service it will always have an upto date list of the pods underneath it. &lt;/p&gt;
&lt;h3 id=&#34;Types-of-service&#34;&gt;&lt;a href=&#34;#Types-of-service&#34; class=&#34;headerlink&#34; title=&#34;Types of service&#34;&gt;&lt;/a&gt;Types of service&lt;/h3&gt;&lt;h4 id=&#34;ClusterIP&#34;&gt;&lt;a href=&#34;#ClusterIP&#34; class=&#34;headerlink&#34; title=&#34;ClusterIP&#34;&gt;&lt;/a&gt;ClusterIP&lt;/h4&gt;&lt;p&gt;This is an internal only service.. typically used for internal communication between two services such as some middlewhere level connectivity.&lt;/p&gt;
&lt;h4 id=&#34;NodePort&#34;&gt;&lt;a href=&#34;#NodePort&#34; class=&#34;headerlink&#34; title=&#34;NodePort&#34;&gt;&lt;/a&gt;NodePort&lt;/h4&gt;&lt;p&gt;A &lt;code&gt;NodePort&lt;/code&gt; is a port created on every node in the cluster. An external user can connect to a node address on this port to access the service. If we were to use a &lt;code&gt;NodePort&lt;/code&gt; with nginx then we’d be given a high port (usually 30000+) that will route traffic to the nginx ports e.g. &lt;code&gt;worker0xx:36123 --&amp;gt; [nginx-pod0x:80]&lt;/code&gt; &lt;/p&gt;
&lt;h4 id=&#34;LoadBalancer&#34;&gt;&lt;a href=&#34;#LoadBalancer&#34; class=&#34;headerlink&#34; title=&#34;LoadBalancer&#34;&gt;&lt;/a&gt;LoadBalancer&lt;/h4&gt;&lt;p&gt;The &lt;code&gt;LoadBalancer&lt;/code&gt; service is used to allow external access into a service, it usually requires something external (Cloud Controller Manager) to inform the Kubernetes API what external address traffic should be accepted on. &lt;/p&gt;
&lt;h4 id=&#34;ExternalName&#34;&gt;&lt;a href=&#34;#ExternalName&#34; class=&#34;headerlink&#34; title=&#34;ExternalName&#34;&gt;&lt;/a&gt;ExternalName&lt;/h4&gt;&lt;p&gt;This allows a service to be exposed on an external name to point to something else. The main use-case is being able to define a service name and having it point to an existing external service…&lt;/p&gt;
&lt;h2 id=&#34;The-Kubernetes-Load-Balancer-service&#34;&gt;&lt;a href=&#34;#The-Kubernetes-Load-Balancer-service&#34; class=&#34;headerlink&#34; title=&#34;The Kubernetes Load Balancer service&#34;&gt;&lt;/a&gt;The Kubernetes Load Balancer service&lt;/h2&gt;&lt;p&gt;So how does all this hang together… &lt;/p&gt;
&lt;p&gt;-&amp;gt; also i’ll be walking through how you can implement this yourself NOT discussing how the big cloud providers do it :-)&lt;/p&gt;
&lt;p&gt;If you take a “fresh” Kubernetes cluster and create a simple &lt;code&gt;nginx&lt;/code&gt; deployment and try to expose it as a LoadBalancer you’ll find that it doesn’t work (or sits in pending).&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;kubectl expose deployment hello-world --type=LoadBalancer --name=my-service`&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;[some time later]&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;NAME         TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)    AGE&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;my-service   LoadBalancer   10.3.245.137   &amp;lt;pending&amp;gt;        8080/TCP   54s&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Why??? Well the &lt;code&gt;Type:LoadBalancer&lt;/code&gt; isn’t the responsibility of kubernetes, it effectively doesn’t come out of the box… as we can see the internal IP (&lt;code&gt;CLUSTER-IP&lt;/code&gt;) has been created but no external address exists. This &lt;code&gt;EXTERNAL-IP&lt;/code&gt; is typically a piece of information that is infrastructure specific, in the cloud we need addresses to come from their IP address management systems and on-premises who knows how addresses are managed &lt;code&gt;¯\_(ツ)_/¯&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&#34;Cloud-Controller-Manager&#34;&gt;&lt;a href=&#34;#Cloud-Controller-Manager&#34; class=&#34;headerlink&#34; title=&#34;Cloud Controller Manager&#34;&gt;&lt;/a&gt;Cloud Controller Manager&lt;/h3&gt;&lt;p&gt;The name can be slightly confusing as anyone can write a CCM and their roles aren’t necessarily cloud specific, i presume it’s more that their main use-case is to extend a kubernetes cluster to be aware of a cloud providers functionality. &lt;/p&gt;
&lt;p&gt;I’ve covered Cloud Controllers before, but to save the dear reader a few mouse-clicks i’ll cover it briefly again… The role of a CCM is usually to plug into the Kubernetes API and watch and act upon certain resources/objects. For objects of type &lt;code&gt;node&lt;/code&gt; then the CCM can speak with the infrastructure to verify that the nodes are deployed correctly and enable them to handle workloads. The objects of type &lt;code&gt;LoadBalancer&lt;/code&gt; would require the CCM to speak with an API to request an IP address that is available to be an &lt;code&gt;EXTERNAL-IP&lt;/code&gt;, alternatively a CCM may be configured with an IP address range it can use in order to configure the &lt;code&gt;EXTERNAL-IP&lt;/code&gt;. &lt;/p&gt;
&lt;h4 id=&#34;LoadBalancer-watcher&#34;&gt;&lt;a href=&#34;#LoadBalancer-watcher&#34; class=&#34;headerlink&#34; title=&#34;LoadBalancer watcher&#34;&gt;&lt;/a&gt;LoadBalancer watcher&lt;/h4&gt;&lt;p&gt;Within the CCM it will have code that will be watching the Kubernetes API, &lt;strong&gt;specifically&lt;/strong&gt; for watching Kubernetes services… When one has the &lt;code&gt;spec.type = LoadBalancer&lt;/code&gt; then the CCM will act!&lt;/p&gt;
&lt;p&gt;Before the CCM has chance to act, this is what the service will look like:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;kubectl get svc my-service -o yaml&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;(some stuff omitted…)&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;- apiVersion: v1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  kind: Service&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  metadata:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    creationTimestamp: &amp;quot;2021-01-02T17:46:16Z&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    name: my-service&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    namespace: default&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    resourceVersion: &amp;quot;5281940&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    selfLink: /api/v1/namespaces/default/services/hello-world&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    uid: 7c14dbbd-f9fd-4279-8524-7418ff62d08f&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  spec:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    clusterIP: 10.107.2.227&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    externalTrafficPolicy: Cluster&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    ports:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    - nodePort: 30497&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      port: 80&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      protocol: TCP&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      targetPort: 80&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    selector:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      app: hello-world&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    sessionAffinity: None&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    type: LoadBalancer&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  status:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    loadBalancer: &amp;#123;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt; We can see that the &lt;code&gt;status&lt;/code&gt; is blank and the &lt;code&gt;spec.loadBalancerIP&lt;/code&gt; doesn’t exist.. well, whilst we’ve been reading this the CCM has acted.. if we’re in the cloud it’s done some API calls or if we’re running our own it may have looked at it’s pool of addresses and found a free address. The CCM will take this address and &lt;em&gt;modify&lt;/em&gt; the object by updating the &lt;code&gt;spec&lt;/code&gt; of the service.&lt;/p&gt;
&lt;p&gt; &lt;code&gt;kubectl get svc my-service -o yaml&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;(more stuff omitted…)&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;- apiVersion: v1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt; kind: Service&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;[....]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt; spec:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   clusterIP: 10.107.2.227&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   externalTrafficPolicy: Cluster&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   loadBalancerIP: 147.75.100.235&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   ports:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   - nodePort: 30497&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;     port: 80&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;     protocol: TCP&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;     targetPort: 80&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   selector:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;     app: hello-world&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   sessionAffinity: None&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   type: LoadBalancer&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt; status:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   loadBalancer: &amp;#123;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;… and that is it. &lt;/p&gt;
&lt;p&gt;That is &lt;strong&gt;all&lt;/strong&gt; the CCM needs to do although but your service will still be &lt;code&gt;&amp;lt;pending&amp;gt;&lt;/code&gt; as the status is still blank :-(&lt;/p&gt;
&lt;p&gt;Two other things need to happen at this point!&lt;/p&gt;
&lt;h3 id=&#34;Kubernetes-Services-proxy&#34;&gt;&lt;a href=&#34;#Kubernetes-Services-proxy&#34; class=&#34;headerlink&#34; title=&#34;Kubernetes Services proxy&#34;&gt;&lt;/a&gt;Kubernetes Services proxy&lt;/h3&gt;&lt;p&gt;What is this &lt;strong&gt;“proxy”&lt;/strong&gt; I speak of! … well, if you’ve noticed the &lt;code&gt;kube-proxy&lt;/code&gt; pod on your cluster and wondered why it’s there then &lt;em&gt;read on&lt;/em&gt;!&lt;/p&gt;
&lt;p&gt;There is plenty more detail =&amp;gt; &lt;a href=&#34;https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies&#34;&gt;here&lt;/a&gt;, but i’ll break it down mainly for &lt;code&gt;LoadBalancers&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When we expose a service within Kubernetes, then the API server will instruct the &lt;code&gt;kube-proxy&lt;/code&gt; to inject a bunch of rules into &lt;code&gt;iptables/ipvs&lt;/code&gt; that will effectively capture traffic and ensure that this traffic goes to the pods defined under the service. By default any service we create will have its &lt;code&gt;clusterIP&lt;/code&gt; and a &lt;code&gt;port&lt;/code&gt; written into these rules so that regardless which node &lt;strong&gt;inside&lt;/strong&gt; the cluster we try to access this &lt;code&gt;clusterIP:port&lt;/code&gt; then the services proxy will handle this traffic and distribute it accordingly. The &lt;code&gt;clusterIP&lt;/code&gt; are virtual IP addresses that are managed by the Kubernetes API, however with &lt;code&gt;LoadBalancers&lt;/code&gt; the traffic is external and we don’t know what the IP address will be &lt;code&gt;¯\_(ツ)_/¯&lt;/code&gt;!&lt;/p&gt;
&lt;p&gt;Well, as shown above the CCM will eventually modify the &lt;code&gt;spec.loadBalancerIP&lt;/code&gt; with an address from the environment.. once this spec is updated then the API will instruct &lt;code&gt;kube-proxy&lt;/code&gt; to ensure that any traffic for this &lt;code&gt;externalIP:port&lt;/code&gt; is also captured and proxied to the pods underneath the service.&lt;/p&gt;
&lt;p&gt;We can see that these rules now exist by looking at the output for &lt;code&gt;iptables-save&lt;/code&gt;, all traffic for the address of the LoadBalancer is now forwarded on…&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;root@k8s:~# iptables-save | grep 147&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;-A KUBE-SERVICES -d 147.75.100.235/32 -p tcp -m comment --comment &amp;quot;default/hello-world loadbalancer IP&amp;quot; -m tcp --dport 80 -j KUBE-FW-2BRRD7ARGUSQ7MRP&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;The final piece of the puzzle is getting traffic to the machines themselves … &lt;/p&gt;
&lt;h3 id=&#34;External-traffic&#34;&gt;&lt;a href=&#34;#External-traffic&#34; class=&#34;headerlink&#34; title=&#34;External traffic !&#34;&gt;&lt;/a&gt;External traffic !&lt;/h3&gt;&lt;p&gt;So between a CCM and the Kubernetes service proxy, we have been given an external IP address that we have for our service &lt;strong&gt;and&lt;/strong&gt; the Kubernetes service proxy will ensure that any traffic in the cluster for that external IP address is distributed to the various pods. We now need to get traffic to the nodes themselves…&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Hypothetically&lt;/em&gt;, if we had a second network adapter in one of the nodes then we could configure this network adapter with the &lt;code&gt;externalIP&lt;/code&gt; and as long as we can route traffic to that IP address then the kubernetes service proxy will capture and distribute that traffic. Unfortunately that is a very manual operation, so what options/technologies could we adopt in order to manage this?&lt;/p&gt;
&lt;p&gt;We would usually need a final running piece of software that “watches” a service, and once the service is updated with a &lt;code&gt;spec.loadBalancerIP&lt;/code&gt; from the CCM we know that it’s good to advertise to the outside world! Also once we’re exposing this to the outside world we can modify the &lt;code&gt;status&lt;/code&gt; of the service with the address that we’re exposing on so that clients and end-users know that they can now use this address!&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;status:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  loadBalancer:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    ingress:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    - ip: 147.75.100.235&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;There are two main technologies that we can use to tell an existing environment about our new loadBalancer address, and when someone accesses this address where to then send the traffic!&lt;/p&gt;
&lt;h4 id=&#34;ARP&#34;&gt;&lt;a href=&#34;#ARP&#34; class=&#34;headerlink&#34; title=&#34;ARP&#34;&gt;&lt;/a&gt;ARP&lt;/h4&gt;&lt;p&gt;ARP (Address resolution Protocol) is a Layer 2 protocol, who has the main task of working out which hardware (MAC address) an IP address belongs to. When an IP address first appears on the network it will typically broadcast to the network that it exists, it will also broadcast the MAC address of the adapter that it’s bound to. This informs the network of this IP &amp;lt;==&amp;gt; MAC binding, meaning that on a simple network when packets need to go to an IP address the switching infrastructure knows which machine to send the traffic. &lt;/p&gt;
&lt;p&gt;We can see this mapping by using the &lt;code&gt;arp&lt;/code&gt; command:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;$ arp -a&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;_gateway (192.168.0.1) at b4:fb:e4:cc:d3:80 [ether] on ens160&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;? (10.0.73.65) at e6:ca:eb:b8:a0:f3 [ether] on cali43893380087&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;? (192.168.0.170) at f4:fe:fb:54:89:16 [ether] on ens160&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;? (10.0.73.67) at 52:5c:1b:5f:e1:50 [ether] on cali0e915999b8d&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;? (192.168.0.44) at 00:50:56:a5:13:11 [ether] on ens160&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;? (192.168.0.45) at 00:50:56:a5:c1:86 [ether] on ens160&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;? (192.168.0.40) at 00:50:56:a5:5f:1d [ether] on ens160&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Load Balancing NOTE:&lt;/strong&gt; In order for this to work with out Kubernetes cluster, we would need to select a single node that would become in charge of hosting this &lt;code&gt;externalIP&lt;/code&gt; and using ARP to inform the network that traffic for this address should be sent to that machine. This is because of two reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If another machine broadcasts an ARP update then existing connections will be disrupted&lt;/li&gt;
&lt;li&gt;Multiple machines can’t have the same IP address exposed on the same network&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The flow of operation is:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A leader is selected&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spec.loadBalancerIP&lt;/code&gt; is updated to &lt;code&gt;147.75.100.235&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;147.75.100.235&lt;/code&gt; is added as an additional address to interface &lt;code&gt;ens160&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;ARP broadcasts that traffic to &lt;code&gt;147.75.100.235&lt;/code&gt; is available at &lt;code&gt;00:50:56:a5:4f:05&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Update the service &lt;code&gt;status&lt;/code&gt; that the service is being advertised&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At this point the externalIP address is known to the larger network, and any traffic will be sent to the node that is elected leader. Once the traffic is captured by the rules in the kernel, the traffic is then sent to the pods that are part of the service.&lt;/p&gt;
&lt;h4 id=&#34;BGP&#34;&gt;&lt;a href=&#34;#BGP&#34; class=&#34;headerlink&#34; title=&#34;BGP&#34;&gt;&lt;/a&gt;BGP&lt;/h4&gt;&lt;p&gt;BGP (Border Gateway Protocol) is a Layer 3 protocol, the main task is to update routers with the path to new routes (that new route can be a single address or a range of addresses). For our use-cases in a Kubernetes cluster we would use BGP to announce to the routers in the infrastructure that traffic for a our &lt;code&gt;spec.loadBalancerIP&lt;/code&gt; should be sent to one or more machines. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Load Balancing NOTE:&lt;/strong&gt; One additional benefit over &lt;code&gt;ARP&lt;/code&gt; is that multiple nodes can advertise the same address, this actually provides both HA and load-balancing across all nodes advertising in the cluster. In order to do this, we can bind the &lt;code&gt;externalIP&lt;/code&gt; to the localhost adapter (so it’s not present on the actual network) and leave it to the kernel to allow traffic from the routers into the kernel and allow it to be proxied by &lt;code&gt;kube-proxy&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The flow of operation is:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;spec.loadBalancerIP&lt;/code&gt; is updated to &lt;code&gt;147.75.100.235&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;147.75.100.235&lt;/code&gt; is added as an additional address to interface &lt;code&gt;lo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A BGP peer advertisement updates the other peers (routers) that the &lt;code&gt;loadBalancerIP&lt;/code&gt; is available by routing to the machines address on the network (all machines can/will do this)&lt;/li&gt;
&lt;li&gt;Update the service &lt;code&gt;status&lt;/code&gt; that the service is being advertised&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Any client/end-user that tries to access &lt;code&gt;147.75.100.235&lt;/code&gt; will have their traffic go through the router, where a route will exist to send that traffic to one of the nodes in the cluster, where it will be passed to the kubernetes service proxy. &lt;/p&gt;
&lt;h2 id=&#34;Overview&#34;&gt;&lt;a href=&#34;#Overview&#34; class=&#34;headerlink&#34; title=&#34;Overview&#34;&gt;&lt;/a&gt;Overview&lt;/h2&gt;&lt;p&gt;At this point we can see there are a number of key pieces amd technologies that can all be harnesed to put together a load-balancing solution for Kubernetes. However the CCM is arguably the most important, as the CCM has the role of being “aware” of the topology or architecture of the infrastructure. It may need to prep nodes with configuration details (BGP configuration settings etc.) and speak with other systems to request valid addresses that can be used for the loadBalancer addresses. &lt;/p&gt;
 ]]></description>
        </item>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2020/11/22/Kube-Vip-and-BGP-loadbalancers-with-Unifi/</guid>
            <title>Kube-Vip and BGP loadbalancers with Unifi</title>
            <link>http://thebsdbox.co.uk/2020/11/22/Kube-Vip-and-BGP-loadbalancers-with-Unifi/</link>
            <pubDate>Sun, 22 Nov 2020 16:06:56 +0000</pubDate>
            <description><![CDATA[ &lt;p&gt;Since starting at &lt;del&gt;Packet&lt;/del&gt; Equinix Metal i’ve had to spend a bit of time getting my head around a technology called BGP. This is widely used at Equinix Metal, the main use case is to allow an Elastic IP address to route traffic externally (i.e. from the internet) into one or more physical machines in Equinix’s facilities. An &lt;strong&gt;external&lt;/strong&gt; address will usually be routed into one of the Equinix datacenters, we can configure physical infrastructure to use this external IP and communicate to the networking equipment in this facility (using BGP) that traffic should be routed to these physical servers. &lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/bgp.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;I’ve since been working to add BGP functionality to a Kubernetes load-balancing solution that i’ve been working on in my spare time &lt;a href=&#34;https://kube-vip.io/&#34;&gt;kube-vip.io&lt;/a&gt;. This typically works in exactly the same method as described as above, where an EIP is advertised to the outside world and the BGP functionality will route external traffic to worker nodes and, where the service traffic is then handled by the service “mesh” (usually &lt;code&gt;iptables&lt;/code&gt; rules capturing service traffic) within the cluster.&lt;/p&gt;
&lt;p&gt;I’m hoping to take this functionality further and decided it would be nice to try and emulate the Equinix Metal environment as much as possible, i.e. having BGP at home. There are two methods that I could go down in order to have this sort of functionality within my house:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a local linux router, with it’s own network and use &lt;code&gt;bgpd&lt;/code&gt; so I can advertise routes&lt;/li&gt;
&lt;li&gt;Utilise my Unifi router, which after some light googling it turns out supports &lt;code&gt;bgp&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Given that:&lt;br&gt;a. It’s Sunday&lt;br&gt;b. I’m Lazy&lt;br&gt;c. The instructions for setting up a local linux router looked like a &lt;code&gt;pita&lt;/code&gt; I opted to just use my USG.&lt;br&gt;d. The USG will enable me to advertise addresses on my existing network and the USG will route traffic the advertising hosts without me doing anything (exacerbating the laziness here)&lt;/p&gt;
&lt;h2 id=&#34;Configuring-the-Unifi-Security-Gateway&#34;&gt;&lt;a href=&#34;#Configuring-the-Unifi-Security-Gateway&#34; class=&#34;headerlink&#34; title=&#34;Configuring the Unifi Security Gateway&#34;&gt;&lt;/a&gt;Configuring the Unifi Security Gateway&lt;/h2&gt;&lt;p&gt;These instructions are for the Unifi Security Gateway, however I suspect that the security gateway that is part of the “dream machine” should support the same level of functionality. &lt;/p&gt;
&lt;p&gt;Before we begin we’ll need to ensure that we understand the network topology of the network so that we can configure for &lt;code&gt;bgp&lt;/code&gt; to function as expected.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Device&lt;/th&gt;
&lt;th&gt;Address&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;gateway&lt;/td&gt;
&lt;td&gt;192.168.0.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;k8sworker01&lt;/td&gt;
&lt;td&gt;192.168.0.70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;k8sworker02&lt;/td&gt;
&lt;td&gt;192.168.0.71&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;k8sworker03&lt;/td&gt;
&lt;td&gt;192.168.0.72&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;In the above table we can see a subset of hosts on my network, the first host that is important is the gateway address on my network (which is the address of the USG). All hosts on my network have this set as the default gateway, which means that when a machine needs to access an IP address that &lt;strong&gt;isn’t&lt;/strong&gt; &lt;code&gt;192.168.0.1-254&lt;/code&gt; then it will send the traffic to the gateway for it to be routed to that specific address. &lt;/p&gt;
&lt;p&gt;Your gateway can be found by doing something like the following:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;$  ip route | grep default&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;default via 192.168.0.1 dev ens160 proto static&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;The other addresses that are worth noting are the three worker hosts in my Kubernetes cluster that will advertise their &lt;code&gt;bgp&lt;/code&gt; routes to the gateway. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;:&lt;br&gt;All of the next steps require &lt;code&gt;ssh&lt;/code&gt; access into the appliance in order to enable and configure the USG so that &lt;code&gt;bgp&lt;/code&gt; is enabled on the network. &lt;/p&gt;
&lt;p&gt;If you don’t know your ssh password for your gateway then &lt;em&gt;help&lt;/em&gt; is at hand.. because for some bizarre reason you can get the ssh username/password straight from the web UI.. Navigate to &lt;code&gt;Network Settings&lt;/code&gt; -&amp;gt; &lt;code&gt;Device Authentication&lt;/code&gt; and here you’ll find the ssh username and password.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE 2&lt;/strong&gt;:&lt;br&gt;Whilst it is possible to enable &lt;code&gt;bgp&lt;/code&gt; functionality through the cli, the actual web client isn’t aware of the &lt;code&gt;bgp&lt;/code&gt; configuration. This will result in the &lt;code&gt;bgp&lt;/code&gt; configuration being wiped when you do things like modify firewall rules or port forwarding in the web UI.&lt;/p&gt;
&lt;h3 id=&#34;Enabling-BGP&#34;&gt;&lt;a href=&#34;#Enabling-BGP&#34; class=&#34;headerlink&#34; title=&#34;Enabling BGP&#34;&gt;&lt;/a&gt;Enabling BGP&lt;/h3&gt;&lt;p&gt;To begin with we’ll need to &lt;code&gt;ssh&lt;/code&gt; to the Unifi USG with the credentials that can be found from the web portal. &lt;/p&gt;
&lt;p&gt;Below is the configuration that we’ll add, i’ll break it down below:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;configure&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;set protocols bgp 64501 parameters router-id 192.168.0.1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;set protocols bgp 64501 neighbor 192.168.0.70 remote-as 64500&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;set protocols bgp 64501 neighbor 192.168.0.71 remote-as 64500&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;set protocols bgp 64501 neighbor 192.168.0.72 remote-as 64500&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;commit&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;save&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;exit&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;&lt;code&gt;configure&lt;/code&gt; - will enable the configuration mode for the USG.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;set protocols bgp 64501 parameters router-id 192.168.0.1&lt;/code&gt; - will enable &lt;code&gt;bgp&lt;/code&gt; on the USG with the router-id as it’s IP address &lt;code&gt;192.168.0.1&lt;/code&gt; the AS number &lt;code&gt;64501&lt;/code&gt; is used to determine our particular &lt;code&gt;bgp&lt;/code&gt; network&lt;/p&gt;
&lt;p&gt;&lt;code&gt;set protocols bgp 64501 neighbor x.x.x.x remote-as 64500&lt;/code&gt; - will allow our &lt;code&gt;bgp&lt;/code&gt; instance to take advertised routes from &lt;code&gt;x.x.x.x&lt;/code&gt; with the AS identifier &lt;code&gt;64500&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;commit | save&lt;/code&gt; - will both enable and then save the configuration&lt;/p&gt;
&lt;p&gt;This is it.. the USG is now configured to take routes from our Kubernetes workers!!!&lt;/p&gt;
&lt;h2 id=&#34;Configuring-Kube-Vip&#34;&gt;&lt;a href=&#34;#Configuring-Kube-Vip&#34; class=&#34;headerlink&#34; title=&#34;Configuring Kube-Vip&#34;&gt;&lt;/a&gt;Configuring Kube-Vip&lt;/h2&gt;&lt;p&gt;I’ve recently started on a release of &lt;code&gt;kube-vip&lt;/code&gt; that will provide additional functionality in order to automate some of the &lt;code&gt;bgp&lt;/code&gt; configuration, namely the capability to determine the host IP address for the &lt;code&gt;bgp&lt;/code&gt; &lt;code&gt;server-id&lt;/code&gt; for advertising from a pod. &lt;/p&gt;
&lt;p&gt;We can follow the guide for setting up &lt;a href=&#34;https://kube-vip.io/kubernetes/bgp/&#34;&gt;kube-vip with bgp&lt;/a&gt; with some additional modifications show below!&lt;/p&gt;
&lt;p&gt;Ensure that the version of the &lt;code&gt;kube-vip&lt;/code&gt; image is &lt;code&gt;0.2.2&lt;/code&gt; or &lt;strong&gt;higher&lt;/strong&gt;&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;env:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  - name: vip_interface&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	value: &amp;quot;lo&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  - name: vip_configmap&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	value: &amp;quot;plndr&amp;quot; &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  - name: bgp_enable&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	value: &amp;quot;true&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  - name: vip_loglevel&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	value: &amp;quot;5&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  - name: bgp_routerinterface&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	value: &amp;quot;ens160&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  - name: bgp_as&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	value: &amp;quot;64500&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  - name: bgp_peeraddress&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	value: &amp;quot;192.168.0.1&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  - name: bgp_peeras&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	value: &amp;quot;64501&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;The &lt;code&gt;bgp_routerinterface&lt;/code&gt; will autodetect the host IP address of the interface specified and this will become our &lt;code&gt;server-id&lt;/code&gt; for each pods &lt;code&gt;bgp&lt;/code&gt; peering configuration. The &lt;code&gt;bgp_peer&amp;lt;as|address&amp;gt;&lt;/code&gt; is the remote configuration for our USG as specified above.&lt;/p&gt;
&lt;h2 id=&#34;Testing-it-out&#34;&gt;&lt;a href=&#34;#Testing-it-out&#34; class=&#34;headerlink&#34; title=&#34;Testing it out !&#34;&gt;&lt;/a&gt;Testing it out !&lt;/h2&gt;&lt;p&gt;Create a easy deployment, we can use the &lt;code&gt;nginx&lt;/code&gt; demo for this:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;kubectl apply -f https://k8s.io/examples/application/deployment.yaml&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Given that the USG is our default gateway we can actually advertise any address we like as a &lt;code&gt;type:LoadBalancer&lt;/code&gt; and when we try and access if from inside the network the USG will route us back to the the Kubernetes servers!&lt;/p&gt;
&lt;p&gt;For example we can create a load-balancer with the address &lt;code&gt;10.0.0.1&lt;/code&gt;&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;kubectl expose deployment nginx-deployment --port=80 --type=LoadBalancer --name=nginx-bgp --load-balancer-ip=10.0.0.1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Given the home network is in the 192.168.0.0/24 range anything that is outside of it will need to go through our USG router, where we’ve advertised (through BGP) that traffic needs to come to any of the Kubernetes workers.&lt;/p&gt;
&lt;p&gt;We can see this below on the USG with &lt;code&gt;show ip bgp&lt;/code&gt;&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;admin@Gateway:~$ show ip bgp&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;BGP table version is 0, local router ID is 192.168.0.1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Status codes: s suppressed, d damped, h history, * valid, &amp;gt; best, i - internal,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			  r RIB-failure, S Stale, R Removed&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Origin codes: i - IGP, e - EGP, ? - incomplete&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   Network          Next Hop            Metric LocPrf Weight Path&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;* i10.0.0.1/32      192.168.0.45                  100      0 i&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;*&amp;gt;i                 192.168.0.44                  100      0 i&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;* i                 192.168.0.46                  100      0 i&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
 ]]></description>
        </item>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2020/06/04/Fixing-Plunders-OS-Image-problem/</guid>
            <title>Fixing Plunders (OS) Image problem</title>
            <link>http://thebsdbox.co.uk/2020/06/04/Fixing-Plunders-OS-Image-problem/</link>
            <pubDate>Thu, 04 Jun 2020 08:57:43 +0100</pubDate>
            <description><![CDATA[ &lt;p&gt;This is a post about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Operating Systems&lt;/li&gt;
&lt;li&gt;The Linux Boot process&lt;/li&gt;
&lt;li&gt;Installation methods&lt;/li&gt;
&lt;li&gt;Doing weird &lt;code&gt;hacky&lt;/code&gt; things 😃&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this post I’m going to cover the options that are typically available to an end user when looking to install an Operating System (mainly Linux, but I may touch on others 🤷) , we will touch on their pros/cons and look at what the alternatives are. We will then discuss concepts of immutable Operating System deployments and how we may go about doing so, including the existing eco-system. Finally we’ll look at what is required (using Go) to  implement an Image based Operating System creation and deployment, and how they’ve been implemented into &lt;a href=&#34;https://plndr.io/&#34;&gt;plunder&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;Getting-an-Operating-System-deployed&#34;&gt;&lt;a href=&#34;#Getting-an-Operating-System-deployed&#34; class=&#34;headerlink&#34; title=&#34;Getting an Operating System deployed&#34;&gt;&lt;/a&gt;Getting an Operating System deployed&lt;/h2&gt;&lt;p&gt;Most Operating Systems are deployed in relatively the same manner:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A machine boots and reads from installation media (presented locally or over the network)&lt;/li&gt;
&lt;li&gt;The target disks are prepared, typically partitions may be created or HA technologies such as disk mirroring and then finally these partitions are “formatted” so that they contain a file system. &lt;/li&gt;
&lt;li&gt;Either a minimal set of packages or a custom selection of packages will be installed to the new file system. Most Operating Systems or distributions have their own concept of “packages” but ultimately under the covers the package contains binaries and required libraries for an application along with some logic that dictates where the files should be written too along with some versioning information that the package manager can use.&lt;/li&gt;
&lt;li&gt;There may be some final customisation such as setting users, network configuration etc..&lt;/li&gt;
&lt;li&gt;A Boot loader is written to the target disk so that when the machine is next powered on it can boot the newly provisioned Operating System.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The order of the steps may differ but pretty much all of the major Operating Systems (Linux, Windows, MacOS) follow the same pattern to deploy on target hardware. &lt;/p&gt;
&lt;h3 id=&#34;Options-for-automating-a-deployment&#34;&gt;&lt;a href=&#34;#Options-for-automating-a-deployment&#34; class=&#34;headerlink&#34; title=&#34;Options for automating a deployment&#34;&gt;&lt;/a&gt;Options for automating a deployment&lt;/h3&gt;&lt;p&gt;There are usually two trains of thought when thinking about deploying an Operating System, which are &lt;strong&gt;scripted&lt;/strong&gt; which will go through the steps listed above but no user interaction is required or &lt;strong&gt;image&lt;/strong&gt; based which takes a copy of a deployment and uses that as a “rubber stamp” for other installs.&lt;/p&gt;
&lt;h4 id=&#34;Scripted&#34;&gt;&lt;a href=&#34;#Scripted&#34; class=&#34;headerlink&#34; title=&#34;Scripted&#34;&gt;&lt;/a&gt;Scripted&lt;/h4&gt;&lt;p&gt;Operating Systems were originally designed to be ran on hardware of a predetermined configuration, which meant that there was no need for customising of the installation. However as time passed a few things happened that suddenly required Operating Systems to become more flexible:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Usage of computers sky rocketed&lt;/li&gt;
&lt;li&gt;The number of hardware vendors producing compute parts increased&lt;/li&gt;
&lt;li&gt;A lot of traditional types of work became digitised. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of these factors suddenly required an Operating System to support more and more types of hardware and it’s required configuration(s), furthermore end-users required the capability to tailor an Operating System to behave as needed. To provide this functionality Operating System vendors built rudimentary user interfaces that would ask questions or provide the capability for a user installing the OS to set various configuration options. This worked for a period of time but as more and more computer systems were deployed this became an administrative nightmare, as it was impossible to automate multiple installations as they required interaction to proceed and finally the need for humans to interact brings about the possibility of human error (&lt;strong&gt;Pesky humans&lt;/strong&gt;) during the deployment. &lt;/p&gt;
&lt;p&gt;In order for large scale IT system installations to take place then operations needed a method for &lt;a href=&#34;https://en.wikipedia.org/wiki/Installation_(computer_programs)#Unattended_installation&#34;&gt;unattended installations&lt;/a&gt;, where installations can happen without any involvement. The technique for this to work was to modify the Operating System installation code so that it could take a file that can answer all of the questions that would have previously required user input in order to progress the installation. These technologies are all named in a way that reflects that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;preeseed&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;answer file(s)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kickstart&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;jumpstart&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once a member of the operations team has “designed” the set of responses for their chosen Operating System then this single configuration can be re-used as many times as required. This removes the human element from accidentally entering the wrong data or clicking the wrong button during an Operating System installation and ensures that the installation is standardised and “documented”. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;However&lt;/strong&gt;, one thing to consider is that although a scripted installation is a repeatable procedure that requires no human interaction it is not always 100% reliable.  This installation still runs through a &lt;strong&gt;lot&lt;/strong&gt; of steps, such as every package has to be installed along with the prerequisite package management along with configuring devices and other things that happen during that initial installation phase. Whilst this may work perfectly on the initial machine undiscovered errors can appear when moving this installation method to different hardware. There have been numerous issues caused by packages relying on &lt;code&gt;sleep&lt;/code&gt; during an installation step, the problem here usually is due to this package being developed on a laptop and then moved to much larger hardware. Suddenly this &lt;code&gt;sleep&lt;/code&gt;  is no longer in step with the behaviour of the hardware as the task completes much quicker than it had on slower hardware. This has typically led to numerous installation failures and can be thought of as a race-condition.&lt;/p&gt;
&lt;h4 id=&#34;Image&#34;&gt;&lt;a href=&#34;#Image&#34; class=&#34;headerlink&#34; title=&#34;Image&#34;&gt;&lt;/a&gt;Image&lt;/h4&gt;&lt;p&gt;Creating an &lt;strong&gt;image&lt;/strong&gt; of an existing Operating System has existed for a long time, we can see it referenced in this &lt;a href=&#34;https://web.archive.org/web/20140701185435/http://www.demorton.com/Tech/$OSTL.pdf&#34;&gt;1960s IBM manual&lt;/a&gt; for their mainframes. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Shoutout for Norton Ghost !!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I have covered image based deployments, but my (google) research show ghost (1998) pre-dates Preseed/&lt;a href=&#34;https://en.wikipedia.org/wiki/Debian-Installer&#34;&gt;DebianInstaller&lt;/a&gt; (2004) and &lt;a href=&#34;d&#34;&gt;Kickstart&lt;/a&gt;(RHEL in 2000) and even Sun Microsystems with jumpstart (earliest mention I can find is Solaris 2.8 which is 1999). &lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/ghost.jpg&#34;&gt;&lt;/p&gt;
&lt;h5 id=&#34;Anatomy-of-a-disk&#34;&gt;&lt;a href=&#34;#Anatomy-of-a-disk&#34; class=&#34;headerlink&#34; title=&#34;Anatomy of a disk&#34;&gt;&lt;/a&gt;Anatomy of a disk&lt;/h5&gt;&lt;p&gt;We can usually think of a disk being like a long strip of paper starting at position 0 and ending with the length of the strip of paper (or its capacity). The positions are vitally important as they’re used by a computer when it starts in order to find things on disk. &lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Boot sector&lt;/strong&gt; is the first place a machine will attempt to boot from once it has completed its hardware initialisation and hardware checks the code in this location will be used in order to instruct the computer where to look for the rest of the boot sequence and code. In the majority of examples a computer will boot the first phase from this boot sector and then be told where to look for the subsequent phases and more than likely the remaining code will live within a partition. &lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;partition&lt;/strong&gt; defines some ring-fenced capacity on an underlying device that can be then presented to the underlying hardware as usable storage. Partitions will then be “formatted” so that they have a structure that understands concepts such as folders/directories and files, along with additional functionality such as permissions. &lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/disk_layout.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;Now that we know the makeup of a disk we can see that there are lots of different things that we may need to be aware of, such as type of boot loader, size or number of partitions, type of file systems and then the files and packages that need installing within those partitions. &lt;/p&gt;
&lt;p&gt;We can safely move away from all of this by taking a full copy of the disk! Starting from position 0 we can read every byte until we’ve reached the end of the disk (EOF) and we have a full copy of &lt;em&gt;everything&lt;/em&gt; from boot loaders to partitions and the underlying files. &lt;/p&gt;
&lt;p&gt;The steps for creating and using a machine image are usually:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install an Operating System once (correctly)&lt;/li&gt;
&lt;li&gt;Create an image of this deployed Operating System&lt;/li&gt;
&lt;li&gt;Deploy the “golden image” to all other hosts &lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;Concepts-for-managing-OS-images&#34;&gt;&lt;a href=&#34;#Concepts-for-managing-OS-images&#34; class=&#34;headerlink&#34; title=&#34;Concepts for managing OS images&#34;&gt;&lt;/a&gt;Concepts for managing OS images&lt;/h2&gt;&lt;p&gt;The concept for reading/writing OS images consists of a number of basic actions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reading or Writing to a storage device e.g. &lt;code&gt;/dev/sda&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A source image (either copied from a deployed system or generated elsewhere)&lt;/li&gt;
&lt;li&gt;A destination for the date from the cloned system, when reading from a deployed system we need to store a copy of the data somewhere&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In relation to the final point, there have been numerous options to capture the contents of an existing system over time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mainframes could clone the system to tape&lt;/li&gt;
&lt;li&gt;Earlier “modern” systems would/could clone to a series of Floppy disks and later on CD-ROM media&lt;/li&gt;
&lt;li&gt;Stream the contents of a storage device over the network to a remote listening server.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;Reading-cloning-data-from-a-storage-device&#34;&gt;&lt;a href=&#34;#Reading-cloning-data-from-a-storage-device&#34; class=&#34;headerlink&#34; title=&#34;Reading (cloning) data from a storage device&#34;&gt;&lt;/a&gt;Reading (cloning) data from a storage device&lt;/h3&gt;&lt;p&gt;When capturing from an existing system, especially when this OS image will be used to deploy else where, there are a few things that need to be considered about the state of the source system. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Firstly&lt;/strong&gt; it is ideal that it is as “clean” as possible, which typically means that the OS is deployed and any required POST-configuration and nothing else. If a system has been up for time or has been used then the filesystem could already be dirty through excess logs or files generated that will clutter the source image and end up being deployed through to all destination servers. We may want to consider removing or zeroing the contents of swap partitions or files as well to ensure nothing is taken from the source and passed to destination machines that doesn’t need to be copied.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Secondly&lt;/strong&gt; that the disk isn’t actually being used when we want to clone or image its contents, whilst technically possible if the Operating System is busy reading and writing whilst we attempt to copy the underlying storage we will end up with files that could be half-written to or ultimately corrupt. We can boot from alternative media (USB/CD-ROM/Alternate Disk/Network) in order to leave our source disk un-used, which will allow us to copy it’s contents in a completely stable state. &lt;/p&gt;
&lt;p&gt;Once we’ve started up our cloning tooling we only need to read all date from the underlying storage device to a secondary data store. We need to be aware that even if we’ve only installed a small amount of data as part of the Operating System our cloned copy will be the exact size of the underling storage device. e.g. a 100GB disk that is installed with a basic Ubuntu 18.04 package set (900MB) will still result in a 100GB OS Image.&lt;/p&gt;
&lt;h3 id=&#34;Writing-an-OS-Image-to-a-destination-device&#34;&gt;&lt;a href=&#34;#Writing-an-OS-Image-to-a-destination-device&#34; class=&#34;headerlink&#34; title=&#34;Writing an OS Image to a destination device&#34;&gt;&lt;/a&gt;Writing an OS Image to a destination device&lt;/h3&gt;&lt;p&gt;As with reading the contents of an underlying storage device we can’t (or I certainly wouldn’t recommend) trying to use the same storage device at the same time as writing too it. If you had an existing system running an Operating System such as Win10 and we used some tooling to re-write our ubuntu disk image to the same storage then well … &lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/bad_feeling.gif&#34;&gt;&lt;/p&gt;
&lt;p&gt;To circumvent this issue we would need to start our destination machine from another medium such as another storage device leaving our destination device alone until we’re ready to write our destination disk image to this storage. &lt;/p&gt;
&lt;p&gt;Finally we also need to consider the sizes of the disk image and the destination storage:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If our destination storage is smaller than the disk image then our writing of the image will fail. However, the system may start afterwards as the main contents of the Operating System has fit on the storage device. This can lead to an unstable state as the filesystem and partitions have logical boundaries that exist well beyond the capacity of the destination disk.&lt;/li&gt;
&lt;li&gt;On the other hand if we write a 10GB OS image to a 100GB physical storage device then the remaining 90GB is left unused without some additional steps, typically involving first growing the partition to occupy the remaining 90GB of space and then growing the filesystem so it is aware of this additional capacity. &lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;Writing-your-own-OS-imaging-software&#34;&gt;&lt;a href=&#34;#Writing-your-own-OS-imaging-software&#34; class=&#34;headerlink&#34; title=&#34;Writing your own OS imaging software&#34;&gt;&lt;/a&gt;Writing your own OS imaging software&lt;/h2&gt;&lt;p&gt;This section will detail the design around software I wrote to accomplish all of the things I’ve discussed above. All of the code samples are written in Go and the source code for the project can be found on the &lt;a href=&#34;https://github.com/plunder-app/BOOTy&#34;&gt;BOOTy repository&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;It will be in three parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, creating a boot environment that allows us to safely read/write from underlying storage&lt;/li&gt;
&lt;li&gt;Second, Reading and Writing from underlying storage&lt;/li&gt;
&lt;li&gt;Finally, post steps.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;A-solution-for-a-clean-environment&#34;&gt;&lt;a href=&#34;#A-solution-for-a-clean-environment&#34; class=&#34;headerlink&#34; title=&#34;A solution for a clean environment&#34;&gt;&lt;/a&gt;A solution for a clean environment&lt;/h3&gt;&lt;p&gt;To ensure we can make a clean read or write to a storage device we need to ensure that it’s not being used at the time, so we will need to boot from a different environment that has our tooling in it. &lt;/p&gt;
&lt;h4 id=&#34;Simplest-option&#34;&gt;&lt;a href=&#34;#Simplest-option&#34; class=&#34;headerlink&#34; title=&#34;Simplest option&#34;&gt;&lt;/a&gt;Simplest option&lt;/h4&gt;&lt;p&gt;We can simply use a &lt;em&gt;live-CD&lt;/em&gt; image that allow us to boot a fully fledged Operating System, from here we can use some tooling like &lt;a href=&#34;https://en.wikipedia.org/wiki/Dd_(Unix)&#34;&gt;dd&lt;/a&gt; to take a copy of contents of the storage. The problems with this solution are that we can’t really automate this procedure and we will need a writeable location for the contents of the  source disk. &lt;/p&gt;
&lt;h4 id=&#34;Writing-a-custom-Operating-System&#34;&gt;&lt;a href=&#34;#Writing-a-custom-Operating-System&#34; class=&#34;headerlink&#34; title=&#34;Writing a custom Operating System&#34;&gt;&lt;/a&gt;Writing a custom Operating System&lt;/h4&gt;&lt;h5 id=&#34;Phase-one-LinuxKit&#34;&gt;&lt;a href=&#34;#Phase-one-LinuxKit&#34; class=&#34;headerlink&#34; title=&#34;Phase one: LinuxKit&#34;&gt;&lt;/a&gt;Phase one: LinuxKit&lt;/h5&gt;&lt;p&gt;My initial plan was to do this with &lt;a href=&#34;https://github.com/linuxkit/linuxkit&#34;&gt;LinuxKit&lt;/a&gt;, where the code to handle the reading and writing of the underlying storage would live within a container and I would then use LinuxKit to bake this into a custom Linux distribution that I could then start on remote hosts.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start remote host and point to our LinuxKit OS&lt;/li&gt;
&lt;li&gt;Start Linux Kernel&lt;/li&gt;
&lt;li&gt;Kernel boots, finds all hardware (including our storage device(s)&lt;/li&gt;
&lt;li&gt;LinuxKit starts &lt;code&gt;/init&lt;/code&gt; which in turn will start our application container&lt;/li&gt;
&lt;li&gt;The container will then interact with the underlying storage device (e.g. write an image to the disk)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This solution was quick and easy to write, however a few issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ensuring console output of what is transpiring &lt;/li&gt;
&lt;li&gt;Stable rebooting or restarting of the host&lt;/li&gt;
&lt;li&gt;Dropping to a console if a write fails&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So whilst it did work, I decided I could probably do something a little smaller&lt;/p&gt;
&lt;h5 id=&#34;Phase-two-initramfs&#34;&gt;&lt;a href=&#34;#Phase-two-initramfs&#34; class=&#34;headerlink&#34; title=&#34;Phase two: initramfs&#34;&gt;&lt;/a&gt;Phase two: &lt;code&gt;initramfs&lt;/code&gt;&lt;/h5&gt;&lt;p&gt;This design uses two pieces of technology, mainly a Linux Kernel and an &lt;code&gt;initramfs&lt;/code&gt; which is where the second stage of Linux Booting occurs, if we use the same chronology as above this design would look like the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start remote host and point to our OS&lt;/li&gt;
&lt;li&gt;Start Linux Kernel&lt;/li&gt;
&lt;li&gt;Kernel boots, finds all hardware (including our storage device(s)&lt;/li&gt;
&lt;li&gt;Kernel starts &lt;code&gt;/init&lt;/code&gt;  within our &lt;code&gt;initramfs&lt;/code&gt; , this &lt;code&gt;init&lt;/code&gt; is our code to manage underlying storage. &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At this point our &lt;code&gt;init&lt;/code&gt; is the only process started by the kernel and all work is performed within our &lt;code&gt;init&lt;/code&gt; binary. Within our &lt;code&gt;initramfs&lt;/code&gt; we can add in any additional binaries, such as a shell (&lt;a href=&#34;https://busybox.net/&#34;&gt;busybox&lt;/a&gt;) or &lt;a href=&#34;https://en.wikipedia.org/wiki/Logical_Volume_Manager_(Linux)&#34;&gt;Logical Volume Manager&lt;/a&gt; tooling to manipulate disks (more on this later).&lt;/p&gt;
&lt;p&gt;In order for this tooling to work our custom &lt;code&gt;init&lt;/code&gt; will need to “setup” the environment so that various things exist within the Linux filesystem, including special device nodes such as &lt;code&gt;/dev/tty&lt;/code&gt; or &lt;code&gt;/dev/random&lt;/code&gt; which are required for things like writing console output or generating UUIDs for filesystems etc..&lt;/p&gt;
&lt;p&gt;Below is the example code that we will need to generate our environment:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;package main&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;import &amp;quot;syscall&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func main() &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	// Mount /dev&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  err := syscall.Mount(&amp;quot;devtmpfs&amp;quot;, &amp;quot;/dev&amp;quot;, &amp;quot;devtmpfs&amp;quot;, syscall.MS_MGC_VAL, 0)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	// Create /dev/random&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	err := syscall.Mknod(&amp;quot;/dev/random&amp;quot;, syscall.S_IFCHR, makedev(1, 3))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;We will need to mount various additional file systems in order for additional functionality to work as expected, mainly systems such as &lt;code&gt;/proc&lt;/code&gt;(proc) and &lt;code&gt;/sys&lt;/code&gt;  (sysfs).  &lt;/p&gt;
&lt;p&gt;Once our environment is up and running, we will need to use &lt;a href=&#34;https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol&#34;&gt;dhcp&lt;/a&gt; in order to get an address on the network. There are numerous libraries available that provide DHCP client functionality and we can leverage one to run in a go routine to ensure we have an address and renew it if we get close to our lease expiring before we finish our work. &lt;/p&gt;
&lt;p&gt;At this point we would do our disk image work, this will be covered &lt;strong&gt;next&lt;/strong&gt; !&lt;/p&gt;
&lt;p&gt;With an &lt;code&gt;init&lt;/code&gt; we need to handle failure carefully, if our init process just ends either successfully &lt;code&gt;0&lt;/code&gt; or with failure &lt;code&gt;-1&lt;/code&gt; then our kernel will simply panic. This means that we need to ensure we handle all errors carefully and in the event something fails ensure it is captured on screen (with a delay long enough for the error to be read) and then issue a command to either start a &lt;code&gt;shell&lt;/code&gt; or &lt;code&gt;reboot&lt;/code&gt; the host. &lt;/p&gt;
&lt;p&gt;On error and we want to drop to a &lt;code&gt;shell&lt;/code&gt; then the following code will ensure that the interrupt is passed from our &lt;code&gt;init&lt;/code&gt; process to its children.&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;// TTY hack to support ctrl+c&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;cmd := exec.Command(&amp;quot;/usr/bin/setsid&amp;quot;, &amp;quot;cttyhack&amp;quot;, &amp;quot;/bin/sh&amp;quot;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;err := cmd.Start()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	log.Errorf(&amp;quot;Shell error [%v]&amp;quot;, err)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt; &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;err = cmd.Wait()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	log.Errorf(&amp;quot;Shell error [%v]&amp;quot;, err)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Finally instead of simply ending our &lt;code&gt;init&lt;/code&gt; process we will need to issue a &lt;em&gt;reboot&lt;/em&gt; or &lt;em&gt;shutdown&lt;/em&gt; as simply ending &lt;code&gt;init&lt;/code&gt; will result in a kernel panic and a hung system. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Before doing this ensure that disk operations are complete and that anything &lt;code&gt;mounted&lt;/code&gt; or &lt;code&gt;open&lt;/code&gt; has been closed and unmounted, as this &lt;code&gt;syscall&lt;/code&gt; will immediately reboot. &lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;err := syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	log.Errorf(&amp;quot;reboot off failed: %v&amp;quot;, err)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	Shell()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// We can opt to panic by doing the following&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;os.Exit(1)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;We now have all of the required bits in place:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Our custom &lt;code&gt;/init&lt;/code&gt; starts up&lt;/li&gt;
&lt;li&gt;The relevant paths are created or mounted&lt;/li&gt;
&lt;li&gt;Any devices nodes are created allowing us to interact with the system as a whole&lt;/li&gt;
&lt;li&gt;We use a DHCP Client to retrieve an address on the network&lt;/li&gt;
&lt;li&gt;In the event of an &lt;strong&gt;error&lt;/strong&gt; we can start a shell process allowing us to examine further as to what the error may be&lt;/li&gt;
&lt;li&gt;Finally we will issue a reboot &lt;code&gt;syscall&lt;/code&gt; when we’ve finished&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;Managing-images-and-storage-devices&#34;&gt;&lt;a href=&#34;#Managing-images-and-storage-devices&#34; class=&#34;headerlink&#34; title=&#34;Managing images, and storage devices&#34;&gt;&lt;/a&gt;Managing images, and storage devices&lt;/h3&gt;&lt;p&gt;With the above &lt;code&gt;/init&lt;/code&gt; in place we can now boot a system that has all the underlying devices ready, is present on the network and in event of errors can either restart or drop to a point where we can look into debugging. &lt;/p&gt;
&lt;p&gt;We are in a position where we can put together some code that will allow reading the storage and sending it across the network to a remote server OR pull that same data and write it to the underlying storage.&lt;/p&gt;
&lt;h4 id=&#34;Identifying-the-server-and-behaviour&#34;&gt;&lt;a href=&#34;#Identifying-the-server-and-behaviour&#34; class=&#34;headerlink&#34; title=&#34;Identifying the server and behaviour&#34;&gt;&lt;/a&gt;Identifying the server and behaviour&lt;/h4&gt;&lt;p&gt;In order to know what action a server should take when it is booted from our custom &lt;code&gt;init&lt;/code&gt; we need to use a unique identifier, luckily we have one that is built into every machine its MAC/Hardware address. &lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;// Get the mac address of the interface ifname (in our case &amp;quot;eth0&amp;quot;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;mac, err := net.InterfaceByName(ifname)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return &amp;quot;&amp;quot;, err&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// Convert into something that RFC 3986 compliant&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;return strings.Replace(mac, &amp;quot;:&amp;quot;, &amp;quot;-&amp;quot;, -1)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;The above piece of code will find the MAC address of an interface passed as &lt;code&gt;ifname&lt;/code&gt; and it will then convert that into a string that is a compliant URI string &lt;code&gt;00:11:22:33:44:55&lt;/code&gt; -&amp;gt; &lt;code&gt;00-11-22-33-44-55&lt;/code&gt; . We can now use this to build a url that we can request to find out what action we should be performing, so once &lt;code&gt;init&lt;/code&gt; is ready it will build this configuration and perform a &lt;code&gt;GET&lt;/code&gt; on &lt;code&gt;http://&amp;lt;serverURL&amp;gt;/&amp;lt;MAC address&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You may be wondering where we specify the &lt;code&gt;&amp;lt;serverURL&amp;gt;&lt;/code&gt; address, well we can hard code this into our &lt;code&gt;init&lt;/code&gt; alternatively we can pass this on boot as a flag to the kernel &lt;code&gt;MYURL=http://192.168.0.1&lt;/code&gt;, this will appear as a environment variable within our newly started Operating System.&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;serverURL := os.Getenv(&amp;quot;MYURL&amp;quot;)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h4 id=&#34;Reading-and-Writing&#34;&gt;&lt;a href=&#34;#Reading-and-Writing&#34; class=&#34;headerlink&#34; title=&#34;Reading and Writing&#34;&gt;&lt;/a&gt;Reading and Writing&lt;/h4&gt;&lt;p&gt;We can now call back to a server that will can inform the &lt;code&gt;init&lt;/code&gt; process if it should be reading from the storage device and creating an OS image or pulling an image and writing it to a device. &lt;/p&gt;
&lt;h5 id=&#34;Writing-an-image-to-disk&#34;&gt;&lt;a href=&#34;#Writing-an-image-to-disk&#34; class=&#34;headerlink&#34; title=&#34;Writing an image to disk&#34;&gt;&lt;/a&gt;Writing an image to disk&lt;/h5&gt;&lt;p&gt;This is arguably the easier task as we can use a number of pre-existing features within Go to make this very straight forward. Once we’ve been told from the server that we’re writing an image, we should also be given a URL that points to an OS image location. We can pass this &lt;code&gt;imageURL&lt;/code&gt; to our writing function and use an &lt;code&gt;io.Copy()&lt;/code&gt; to write/stream this image directly to the underlying storage.&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;// New request to GET the actual image&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;req, err := http.NewRequest(http.MethodGet, imageURL, nil)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return err&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;resp, err := http.DefaultClient.Do(req)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return err&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;defer resp.Body.Close()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// Open the underlying storage&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;diskIn, err := os.OpenFile(&amp;quot;/dev/sda&amp;quot;, os.O_CREATE|os.O_WRONLY, 0644)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return err&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt; // Read from resp.Body and Write to the underlying Storage &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;count, err := io.Copy(diskIn, resp.Body)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return fmt.Errorf(&amp;quot;Error writing %d bytes to disk [%s] -&amp;gt; %v&amp;quot;, count, destinationDevice, err)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h5 id=&#34;Reading-from-Disk-to-an-Image&#34;&gt;&lt;a href=&#34;#Reading-from-Disk-to-an-Image&#34; class=&#34;headerlink&#34; title=&#34;Reading from Disk to an Image&#34;&gt;&lt;/a&gt;Reading from Disk to an Image&lt;/h5&gt;&lt;p&gt;The main issue we hit with reading from a disk to a remote location is around the large amounts of data, the underlying storage could easily be many many GBs of data that would need transmitting to a remote server. In order to send such large amounts of data over the HTTP protocol we can use a &lt;a href=&#34;https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html&#34;&gt;multipart&lt;/a&gt; writer that will break up the file and rebuild it on the server side.  To do this we create a multipart writer and then as we read chunks of data from &lt;code&gt;/dev/sda&lt;/code&gt; we send them as multiple parts over the network. &lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;41&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;42&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;43&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;44&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;45&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;46&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;47&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;48&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;body, writer := io.Pipe()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt; // New request to POST the image (contents of the disk)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;req, err := http.NewRequest(http.MethodPost, uri, body)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return nil, err&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt; // Create our multipart Writer &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;mwriter := multipart.NewWriter(writer)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;req.Header.Add(&amp;quot;Content-Type&amp;quot;, mwriter.FormDataContentType())&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;errchan := make(chan error)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// Go routine for the copy operation&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;go func() &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	defer close(errchan)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	defer writer.Close()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	defer mwriter.Close()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	// imageKey is the key that the client will look for and&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	// key is the what the file should be called, we can set this to teh MAC address of the host&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	w, err := mwriter.CreateFormFile(&amp;quot;imageKey&amp;quot;, key)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		errchan &amp;lt;- err&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		return&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;     // Open the underlying storage&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	diskIn, err := os.Open(&amp;quot;/dev/sda&amp;quot;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		errchan &amp;lt;- err&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		return&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;     // Copy from the disk into the mulitpart writer (which inturn sends data over the network)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	if written, err := io.Copy(w, diskIn); err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		errchan &amp;lt;- fmt.Errorf(&amp;quot;error copying %s (%d bytes written): %v&amp;quot;, path, written, err)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		return&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;resp, err := client.Do(req)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;merr := &amp;lt;-errchan&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;if err != nil || merr != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return resp, fmt.Errorf(&amp;quot;http error: %v, multipart error: %v&amp;quot;, err, merr)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h4 id=&#34;Shrinking-Images&#34;&gt;&lt;a href=&#34;#Shrinking-Images&#34; class=&#34;headerlink&#34; title=&#34;Shrinking Images&#34;&gt;&lt;/a&gt;Shrinking Images&lt;/h4&gt;&lt;p&gt;Using disk images can be incredibly wasteful when thinking about network traffic, a 1GB Operating System on a 100GB disk requires sending 100GB of data (even when most of the disk is probably zeros). To save a lot of space we can expand our read/write functions so that we pass our data through a compressor (or expander in the other direction), we can do this very easily by modifying the above code examples.&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;33&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;// Reading raw data from Disk, compressing it and sending it over the network&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		zipWriter := gzip.NewWriter(w)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;				errchan &amp;lt;- fmt.Errorf(&amp;quot;[ERROR] New gzip reader: %s&amp;quot;, err)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;				return&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			// run an io.Copy on the disk into the zipWriter&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			if written, err := io.Copy(zipWriter, diskIn); err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;				errchan &amp;lt;- fmt.Errorf(&amp;quot;error copying %s (%d bytes written): %v&amp;quot;, path, written, err)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;				return&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			// Ensure we close our zipWriter (otherwise we will get &amp;quot;unexpected EOF&amp;quot;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			err = zipWriter.Close()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// Expanding compressed data and writing it to Disk&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		// Create a gzip reader that takes the compressed data over the network&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		zipOUT, err := gzip.NewReader(resp.Body)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			fmt.Println(&amp;quot;[ERROR] New gzip reader:&amp;quot;, err)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		defer zipOUT.Close()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      // Read uncompressed data from gzip Reader and Write to the underlying Storage &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		count, err := io.Copy(fileOut, zipOUT)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		if err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			return fmt.Errorf(&amp;quot;Error writing %d bytes to disk [%s] -&amp;gt; %v&amp;quot;, count, destinationDevice, err)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;We can see that we simply “man-in-the-middle” a compression solution that slots straight into the existing workflow that was shown earlier. The results of adding in compression are clear to see when taking an Ubuntu 18.04 standard install on any sized disk 4GB / 100GB the compressed OS Image is always around 900MB. &lt;/p&gt;
&lt;h3 id=&#34;Tidying-up&#34;&gt;&lt;a href=&#34;#Tidying-up&#34; class=&#34;headerlink&#34; title=&#34;Tidying up&#34;&gt;&lt;/a&gt;Tidying up&lt;/h3&gt;&lt;p&gt;We could simply stop here and we have all of the components in place to both create and write Operating System images to various hardware over the network, however in a lot of cases we may want to perform some &lt;em&gt;POST&lt;/em&gt; configuration once we’ve deployed an image. &lt;/p&gt;
&lt;h4 id=&#34;Grow-the-storage&#34;&gt;&lt;a href=&#34;#Grow-the-storage&#34; class=&#34;headerlink&#34; title=&#34;Grow the storage&#34;&gt;&lt;/a&gt;Grow the storage&lt;/h4&gt;&lt;p&gt;To perform this we (in most circumstances) require some external tooling, if our base Operating System image used technologies like LVM etc.. then we’ll need additional tooling to interact with them. So within our &lt;code&gt;initramfs&lt;/code&gt; we may want to create a static build of &lt;code&gt;LVM2&lt;/code&gt; so that we can use this tooling without requiring a large amount of additional libraries within our RAM disk. One other technique we can use is to make use of tooling that may exist within the Operating System Image that we’ve just written to disk, below is a sample workflow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write Operating System image to &lt;code&gt;/dev/sda&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Exec out to &lt;code&gt;/usr/sbin/partprobe&lt;/code&gt; within out ram-disk to scan &lt;code&gt;/dev/sda&lt;/code&gt; and find our newly written disk contents&lt;/li&gt;
&lt;li&gt;Exec out to &lt;code&gt;/sbin/lvm&lt;/code&gt; to enable any logical volumes&lt;br&gt;&lt;code&gt;TODO: look at BLKRRPART to replace partprobe&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At this point our &lt;code&gt;/init&lt;/code&gt; will have an update &lt;code&gt;/dev&lt;/code&gt; that will have any partitions (&lt;code&gt;/dev/sda1&lt;/code&gt;) or logical volumes (&lt;code&gt;/dev/ubuntu-vg/root&lt;/code&gt;) present at which point we can act on these partitions to grow them and their filesystems.&lt;/p&gt;
&lt;h5 id=&#34;Some-cheeky-chroot-manuveurs&#34;&gt;&lt;a href=&#34;#Some-cheeky-chroot-manuveurs&#34; class=&#34;headerlink&#34; title=&#34;Some cheeky chroot manuveurs&#34;&gt;&lt;/a&gt;Some cheeky &lt;code&gt;chroot&lt;/code&gt; manuveurs&lt;/h5&gt;&lt;p&gt;As mentioned we can use a workflow that means we can use the tooling that exists within our newly deployed Operating System instead of filling our &lt;code&gt;ramdisk&lt;/code&gt; with additional tooling and the associated dependencies.&lt;/p&gt;
&lt;p&gt;The following workflow will grow a 900MB image written to a 20GB disk that uses LVM:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Mount the root volume inside the ram-disk &lt;code&gt;/dev/ubuntu-vg/root&lt;/code&gt; -&amp;gt; &lt;code&gt;/mnt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Use chroot to pretend we’re inside the newly deployed Operating System grow the underlying partition &lt;code&gt;chroot /mnt /usr/bin/growpart /dev/sda 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Again chroot and update LVM to see the newly grown disk &lt;code&gt;chroot /mnt /sbin/pvresize /dev/sda1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Grow the root logical volume &lt;code&gt;chroot /mnt /sbin/lvresize -l +100%FREE /dev/ubuntu-vg/root&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Finally grow the filesystem within the logical volume &lt;code&gt;chroot /mnt /sbin/resize2fs /dev/ubuntu-vg/root&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Finally,&lt;/strong&gt; unmount the logical volume ensuring that any writes are flushed! &lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;Configure-networking-and-other-things&#34;&gt;&lt;a href=&#34;#Configure-networking-and-other-things&#34; class=&#34;headerlink&#34; title=&#34;Configure networking and other things&#34;&gt;&lt;/a&gt;Configure networking and other things&lt;/h4&gt;&lt;p&gt;The above workflow for growing the storage mounts our newly provisioned disk image, once we’ve finished growing the disk/partitions and filesystem we have the opportunity to do additional post-deployment steps. For networking this could include writing a static networking configuration given too us from the provisioning server to the underlying filesystem before we unmount it and boot from it.&lt;/p&gt;
&lt;h2 id=&#34;Overview&#34;&gt;&lt;a href=&#34;#Overview&#34; class=&#34;headerlink&#34; title=&#34;Overview&#34;&gt;&lt;/a&gt;Overview&lt;/h2&gt;&lt;p&gt;In this post we have detailed some of the deployment technologies that exist in order to provision Operating Systems and we can appreciate that there are a number of options available to us about which approach can work best. We’ve also stepped through various code snippets that detail some of the functionality that has recently been added into &lt;a href=&#34;https://plndr.io/&#34;&gt;plndr&lt;/a&gt; to add the capability to create and use OS images to quickly and efficiently deploy Operating Systems to bare-metal (and virtualised) servers. &lt;/p&gt;
&lt;p&gt;All of the source code samples came from &lt;a href=&#34;https://github.com/plunder-app/BOOTy&#34;&gt;BOOTy&lt;/a&gt; which is a project to build an &lt;code&gt;initramfs&lt;/code&gt; that can perform a call back to a &lt;code&gt;plndr&lt;/code&gt; server to find out it’s course of action. &lt;/p&gt;
&lt;p&gt;Any questions, mistakes or corrections either add in the comments or hit me up on twitter -&amp;gt; &lt;a href=&#34;https://twitter.com/thebsdbox&#34;&gt;thebsdbox&lt;/a&gt;&lt;/p&gt;
 ]]></description>
        </item>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2020/03/30/ready-set-client-go/</guid>
            <title>Ready, set, client-go</title>
            <link>http://thebsdbox.co.uk/2020/03/30/ready-set-client-go/</link>
            <pubDate>Mon, 30 Mar 2020 15:43:24 +0100</pubDate>
            <description><![CDATA[ &lt;p&gt;This post will detail a number of (I think at least) awesome use-cases for &lt;a href=&#34;https://github.com/kubernetes/client-go&#34;&gt;client-go&lt;/a&gt;. Some of these are use-cases that are similar or may almost be identical to the &lt;a href=&#34;https://github.com/kubernetes/client-go/tree/master/examples&#34;&gt;examples&lt;/a&gt; that already exist, but with some additional text that details what some of these terms actually mean and when it makes sense to use them.&lt;/p&gt;
&lt;h2 id=&#34;Why-Client-Go&#34;&gt;&lt;a href=&#34;#Why-Client-Go&#34; class=&#34;headerlink&#34; title=&#34;Why Client-Go?&#34;&gt;&lt;/a&gt;Why Client-Go?&lt;/h2&gt;&lt;p&gt;Kubernetes exposes everything through an API (all managed by the active API server) from the control-plane, the API is &lt;a href=&#34;https://en.wikipedia.org/wiki/Representational_state_transfer&#34;&gt;rest&lt;/a&gt; based and is the sole way of controlling the a cluster. This means that things like CI/CD pipelines, various dashboards and even &lt;code&gt;kubectl&lt;/code&gt; will all use the API through an endpoint (network address) with the credentials (key) in order communicate with a cluster. &lt;/p&gt;
&lt;p&gt;As this is just a standard REST over http(s) then there are a myriad of methods that can be used in order to communicate with the Kubernetes API. We can demonstrate this in a quick example:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Check health of Kubernetes API&lt;/strong&gt;&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;$ curl -k https://control-plane01:6443/healthz&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ok&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;The above example is an endpoint that requires no authentication, however if we try to use another endpoint without the correct authentication we’ll receive something like the following:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;$ curl -k https://control-plane01:6443/api&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  “kind”: “Status”,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  “apiVersion”: “v1”,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  “metadata”: &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  “status”: “Failure”,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  “message”: “forbidden: User \”system:anonymous\” cannot get path \”/api\””,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  “reason”: “Forbidden”,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  “details”: &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  “code”: 403&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Under most circumstances the authentication a user would need to speak with a cluster will live within a &lt;code&gt;$home/.kube/config&lt;/code&gt; file. Tooling like &lt;code&gt;kubectl&lt;/code&gt; will automatically look for this file in order to communicate with the API endpoint.&lt;/p&gt;
&lt;p&gt;I wouldn’t recommend interacting with the raw endpoints as shown above, they’re mainly shown as an example of what is possible. To make life far simpler for developers to interact with the Kubernetes API then there are a number of wrappers/SDKs that provide:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Control and management of both the SDK &amp;lt;-&amp;gt; API versions&lt;/li&gt;
&lt;li&gt;Language specific objects and methods to provide sane interfaces to Kubernetes objects&lt;/li&gt;
&lt;li&gt;Helper functions for logging in and managing cluster access&lt;/li&gt;
&lt;li&gt;(A plethora of additional features to make your life easier)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As mentioned this post will cover &lt;code&gt;client-go&lt;/code&gt;, but there are numerous SDKs in various languages that are covered in varying levels of detail &lt;a href=&#34;https://kubernetes.io/docs/tasks/administer-cluster/access-cluster-api/&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;Accessing-a-cluster-either-In-Cluster-or-Outside-cluster&#34;&gt;&lt;a href=&#34;#Accessing-a-cluster-either-In-Cluster-or-Outside-cluster&#34; class=&#34;headerlink&#34; title=&#34;Accessing a cluster, either In-Cluster or Outside cluster&#34;&gt;&lt;/a&gt;Accessing a cluster, either In-Cluster or Outside cluster&lt;/h2&gt;&lt;p&gt;This can be confusing for the first time an end-user will attempt to authenticate with a cluster using &lt;code&gt;client-go&lt;/code&gt;. &lt;/p&gt;
 ]]></description>
        </item>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2020/03/18/Creating-a-Kubernetes-cloud-doesn-t-required-boiling-the-ocean/</guid>
            <title>Creating a Kubernetes cloud provider, doesn&#39;t required boiling the ocean</title>
            <link>http://thebsdbox.co.uk/2020/03/18/Creating-a-Kubernetes-cloud-doesn-t-required-boiling-the-ocean/</link>
            <pubDate>Wed, 18 Mar 2020 11:49:34 +0000</pubDate>
            <description><![CDATA[ &lt;p&gt;Whilst working on a number of &lt;a href=&#34;https://kubernetes.io/&#34;&gt;kubernetes&lt;/a&gt; control-plane deployments and the fighting with &lt;code&gt;kubeadm&lt;/code&gt; and components like &lt;code&gt;haproxy&lt;/code&gt;, &lt;code&gt;nginx&lt;/code&gt; and &lt;code&gt;keepalived&lt;/code&gt;, I decided to try and create my own load-balancer. In the end this proved to be not too complicated to replicate both a Virtual IP and load-balancing over the backends (master nodes) with the standard Go packages. That project now is pretty stable and can easily be used to create a HA control-plane, all of that can be found on &lt;a href=&#34;https://kube-vip.io/&#34;&gt;https://kube-vip.io&lt;/a&gt;. The next thing I wanted to try (and I’ve been considering learning about this for a while) was creating a load-balancer “within” Kubernetes, as in provide the capability and functionality to &lt;code&gt;kubectl expose &amp;lt;...&amp;gt; --type=LoadBalancer&lt;/code&gt;. It turns out that in order to provide this functionality within Kubernetes you need to write a &lt;a href=&#34;https://kubernetes.io/docs/concepts/architecture/cloud-controller/&#34;&gt;Cloud Provider&lt;/a&gt; that the Kubernetes Cloud Controller Manager can interface with. &lt;/p&gt;
&lt;p&gt;This post will “chronicle” the process for doing that… :-D&lt;/p&gt;
&lt;h2 id=&#34;Kubernetes-Cloud-Providers&#34;&gt;&lt;a href=&#34;#Kubernetes-Cloud-Providers&#34; class=&#34;headerlink&#34; title=&#34;Kubernetes Cloud Providers&#34;&gt;&lt;/a&gt;Kubernetes Cloud Providers&lt;/h2&gt;&lt;p&gt;We will start with the obvious question.&lt;/p&gt;
&lt;h3 id=&#34;What-are-Cloud-providers&#34;&gt;&lt;a href=&#34;#What-are-Cloud-providers&#34; class=&#34;headerlink&#34; title=&#34;What are Cloud providers?&#34;&gt;&lt;/a&gt;What are Cloud providers?&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Out of the box&lt;/strong&gt; Kubernetes can’t really do a great deal, it really needs a lot of components to sit on top of or to interface with in order for it to provide the capability to run workloads. For example even in a basic Kubernetes cluster there is a requirement to have a container runtime (&lt;code&gt;CRI, Container Runtime Interface&lt;/code&gt;) in order to execute the container, then we would need a networking plugin (&lt;code&gt;CNI, Container Network Interface&lt;/code&gt;) in order to provide networking within the cluster.&lt;/p&gt;
&lt;p&gt;On the flip side, a typical cloud company (AWS, GCloud, Azure etc…) offers a &lt;strong&gt;plethora&lt;/strong&gt; of cool features and functionality that it would be awesome to consume through the Kubernetes cluster:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Load Balancers&lt;/li&gt;
&lt;li&gt;Cloud instances (VMs, in some placess bare-metal)&lt;/li&gt;
&lt;li&gt;Areas/zones&lt;/li&gt;
&lt;li&gt;Deep API integrations into the infrastructure&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So how do we marry up these two platforms to share that functionality …&lt;/p&gt;
&lt;p&gt; .. &lt;strong&gt;Kubernetes Cloud Providers&lt;/strong&gt; ..&lt;/p&gt;
&lt;h3 id=&#34;Using-Cloud-providers&#34;&gt;&lt;a href=&#34;#Using-Cloud-providers&#34; class=&#34;headerlink&#34; title=&#34;Using Cloud providers&#34;&gt;&lt;/a&gt;Using Cloud providers&lt;/h3&gt;&lt;p&gt;In most circumstances, you won’t even know that you’re using a cloud provider (which I suppose is kind of the point) and only when you try to create an object that the cloud provide can create/manage/delete will it actually be invoked.&lt;/p&gt;
&lt;p&gt;The most common use-case (and the one this post is focussing on) is the creation of a &lt;strong&gt;load balancer&lt;/strong&gt; within Kubernetes and it’s “counterpart” being provided by the cloud vendor. In the case of the cloud vendor Amazon Web Services (AWS) then creating a services &lt;code&gt;type: LoadBalancer&lt;/code&gt; will create an Elastic Load Balancer (&lt;a href=&#34;https://aws.amazon.com/elasticloadbalancing/&#34;&gt;ELB&lt;/a&gt;) that will then load balance traffic over the selected pods. All of this functionality from the Cloud Provider Interface abstracts away the underlying technology and regardless of where a cluster is running a &lt;code&gt;LoadBalancer&lt;/code&gt; just becomes a &lt;code&gt;LoadBalancer&lt;/code&gt;. &lt;/p&gt;
&lt;h2 id=&#34;Creating-a-Cloud-Provider&#34;&gt;&lt;a href=&#34;#Creating-a-Cloud-Provider&#34; class=&#34;headerlink&#34; title=&#34;Creating a Cloud Provider!&#34;&gt;&lt;/a&gt;Creating a Cloud Provider!&lt;/h2&gt;&lt;p&gt;So now onto the actual steps to creating your own cloud provider, this is all going to be written in &lt;a href=&#34;https://golang.org/&#34;&gt;Go&lt;/a&gt; and I’ll do my best to be as descriptive as possible.&lt;/p&gt;
&lt;h3 id=&#34;Wait-what-is-the-cloud-controller-manager&#34;&gt;&lt;a href=&#34;#Wait-what-is-the-cloud-controller-manager&#34; class=&#34;headerlink&#34; title=&#34;Wait, what is the cloud-controller-manager?&#34;&gt;&lt;/a&gt;Wait, what is the &lt;code&gt;cloud-controller-manager&lt;/code&gt;?&lt;/h3&gt;&lt;p&gt;In Kubernetes &lt;code&gt;v1.6&lt;/code&gt; the original design was that all the cloud providers would have their vendor specific code all live in the same place. This ultimately lead to a point where all Kubernetes clusters came with a large &lt;code&gt;cloud-controller-manager&lt;/code&gt; that at startup would be told which vendor code path to run down. &lt;/p&gt;
&lt;p&gt;These were originally called &lt;code&gt;In Tree&lt;/code&gt; cloud providers and there has been a push over the last few years to move to &lt;code&gt;Out of Tree&lt;/code&gt; providers. When deploying a Kubernetes cluster the only change is that instead of starting the &lt;code&gt;cloud-controller-manager&lt;/code&gt; with a specific vendor path (e.g. &lt;em&gt;vsphere&lt;/em&gt; or &lt;em&gt;aws&lt;/em&gt;), the operator would deploy the vendor specific &lt;code&gt;cloud-provider&lt;/code&gt; such as &lt;a href=&#34;https://github.com/kubernetes/cloud-provider-aws&#34;&gt;cloud-provider-aws&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A Note about the “why” of &lt;code&gt;In Tree / Out of Tree&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There has been a shift of stripping code and “vendor specific” functionality from the main Kubernetes source repositories and into their own repositories. The main reasons for this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Removes a &lt;em&gt;tight-coupling&lt;/em&gt; between external/vendor code and Kubernetes proper&lt;/li&gt;
&lt;li&gt;Allowed these projects to move at a different release rate to the main project&lt;/li&gt;
&lt;li&gt;Slims the Kubernetes code base and allows these things to become optional&lt;/li&gt;
&lt;li&gt;Reduces the vulnerability footprint of vendor code within the Kubernetes project&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;interfaces&lt;/code&gt; ensure ongoing compatibility for these &lt;code&gt;Out Of Tree&lt;/code&gt; projects&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So for someone to create their own cloud provider they will need to follow a standard that was set by the original &lt;code&gt;cloud-controller-manager&lt;/code&gt;, this standard is exposed through &lt;a href=&#34;https://golang.org/ref/spec#Method_sets&#34;&gt;method sets&lt;/a&gt; and &lt;a href=&#34;https://golang.org/ref/spec#Interface_types&#34;&gt;interfaces&lt;/a&gt; which can be read about more &lt;a href=&#34;https://www.ardanlabs.com/blog/2014/05/methods-interfaces-and-embedded-types.html&#34;&gt;here&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;&lt;code&gt;tl;dr&lt;/code&gt; simply put, the &lt;code&gt;cloud-controller-manager&lt;/code&gt; sets a standard that means if I want to expose a Load Balancer service it needs to also expose a number of methods (with matching signature). We can further see in the &lt;code&gt;LoadBalancer&lt;/code&gt; interface &lt;a href=&#34;https://github.com/kubernetes/cloud-provider/blob/master/cloud.go#L126&#34;&gt;here&lt;/a&gt; all of the functions that &lt;em&gt;my&lt;/em&gt; Loadbalancer &lt;strong&gt;must&lt;/strong&gt; expose in order to work.&lt;/p&gt;
&lt;h3 id=&#34;The-interface&#34;&gt;&lt;a href=&#34;#The-interface&#34; class=&#34;headerlink&#34; title=&#34;The interface&#34;&gt;&lt;/a&gt;The interface&lt;/h3&gt;&lt;p&gt;The interface for a cloud-provider can be viewed &lt;a href=&#34;https://github.com/kubernetes/cloud-provider/blob/master/cloud.go#L43&#34;&gt;here&lt;/a&gt;, we can see that this interface provides a number of functions that will return the interface for a specific type of functionality. &lt;/p&gt;
&lt;p&gt;The more common interfaces I’ve summarised below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Instances&lt;/code&gt; controller&lt;/strong&gt; - responsible for updating kubernetes nodes using cloud APIs and deleting kubernetes nodes that were deleted on your cloud.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;LoadBalancers&lt;/code&gt; controller&lt;/strong&gt; - responsible for &lt;strong&gt;load balancers&lt;/strong&gt; on your cloud against services of &lt;code&gt;type: LoadBalancer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Routes&lt;/code&gt; controller&lt;/strong&gt; - responsible for setting up network routes on your cloud&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;Example-provider-code-cloud-provider-thebsdbox&#34;&gt;&lt;a href=&#34;#Example-provider-code-cloud-provider-thebsdbox&#34; class=&#34;headerlink&#34; title=&#34;Example provider (code) cloud-provider-thebsdbox&#34;&gt;&lt;/a&gt;Example provider (code) &lt;code&gt;cloud-provider-thebsdbox&lt;/code&gt;&lt;/h2&gt;&lt;p&gt;This section will cover in Go code all of the basics for building a cloud-provider that will handle all of the services requests (that are &lt;code&gt;type: LoadBalancer&lt;/code&gt;). When implementing your own, ensure you use correct paths and package names!&lt;/p&gt;
&lt;p&gt;Our cloud-provider will use the following files:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pkg/thebsdbox/cloud.go&lt;/code&gt;&lt;br&gt;&lt;code&gt;pkg/thebsdbox/loadbalancer.go&lt;/code&gt;&lt;br&gt;&lt;code&gt;pkg/thebsdbox/disabled.go&lt;/code&gt;&lt;br&gt;&lt;code&gt;main.go&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The next sections will have the source code and then an overview at the end of each file.&lt;/p&gt;
&lt;h3 id=&#34;cloud-go&#34;&gt;&lt;a href=&#34;#cloud-go&#34; class=&#34;headerlink&#34; title=&#34;cloud.go&#34;&gt;&lt;/a&gt;&lt;code&gt;cloud.go&lt;/code&gt;&lt;/h3&gt;&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;41&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;42&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;43&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;44&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;45&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;46&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;47&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;48&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;package thebsdbox&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;import (&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	“k8s.io/client-go/informers”&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	“k8s.io/client-go/tools/clientcmd”&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	cloudprovider “k8s.io/cloud-provider”&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;const ProviderName = “thebsbdox” &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// ThebsdboxCloudProvider - contains all of the interfaces for our cloud provider&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;type ThebsdboxCloudProvider struct &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	lb cloudprovider.LoadBalancer&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;var _ cloudprovider.Interface = &amp;amp;thebsdboxCloudProvider&amp;#123;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func init() &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	cloudprovider.RegisterCloudProvider(ProviderName, newThebsdboxCloudProvider)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func newThebsdboxCloudProvider(io.Reader) (cloudprovider.Interface, error) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return &amp;amp;ThebsdboxCloudProvider&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		lb: newLoadBalancer(),&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&amp;#125;, nil&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// Initialize - starts the clound-provider controller&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func (t *ThebsdboxCloudProvider) Initialize(clientBuilder cloudprovider.ControllerClientBuilder, stop &amp;lt;-chan struct&amp;#123;&amp;#125;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	clientset := clientBuilder.ClientOrDie(“do-shared-informers”)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	sharedInformer := informers.NewSharedInformerFactory(clientset, 0)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	sharedInformer.Start(nil)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	sharedInformer.WaitForCacheSync(nil)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// ProviderName returns the cloud provider ID.&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func (t *ThebsdboxCloudProvider) ProviderName() string &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return ProviderName&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// ENABLED Services&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// LoadBalancer returns a loadbalancer interface. Also returns true if the interface is supported, false otherwise.&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func (t *ThebsdboxCloudProvider) LoadBalancer() (cloudprovider.LoadBalancer, bool) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return t.lb, true&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h4 id=&#34;Our-Cloud-Provider-ThebsdboxCloudProvider-struct&#34;&gt;&lt;a href=&#34;#Our-Cloud-Provider-ThebsdboxCloudProvider-struct&#34; class=&#34;headerlink&#34; title=&#34;Our Cloud-Provider ThebsdboxCloudProvider struct{}&#34;&gt;&lt;/a&gt;Our Cloud-Provider &lt;code&gt;ThebsdboxCloudProvider&lt;/code&gt; struct{}&lt;/h4&gt;&lt;p&gt;This &lt;code&gt;struct&amp;#123;&amp;#125;&lt;/code&gt; contains our vendor specific implementations of functionality, such as load-balancers, instances etc..&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;type ThebsdboxCloudProvider struct &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	lb cloudprovider.LoadBalancer&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;We’re only defining a loadbalancer &lt;code&gt;lb&lt;/code&gt; variable as part of our cloud-provider instances as this is the only functionality our provider will expose.&lt;/p&gt;
&lt;h4 id=&#34;init&#34;&gt;&lt;a href=&#34;#init&#34; class=&#34;headerlink&#34; title=&#34;init()&#34;&gt;&lt;/a&gt;&lt;code&gt;init()&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;This function will mean that before our cloud-provider actually starts (before the &lt;code&gt;main()&lt;/code&gt; function is called) we will register our vendor specific cloud-provider. It will also ensure that our newly registered cloud-provider will be instantiated with the &lt;code&gt;newThebsdboxCloudProvider&lt;/code&gt; function.&lt;/p&gt;
&lt;h4 id=&#34;Instantiating-our-cloud-provider-newThebsdboxCloudProvider&#34;&gt;&lt;a href=&#34;#Instantiating-our-cloud-provider-newThebsdboxCloudProvider&#34; class=&#34;headerlink&#34; title=&#34;Instantiating our cloud-provider newThebsdboxCloudProvider()&#34;&gt;&lt;/a&gt;Instantiating our cloud-provider &lt;code&gt;newThebsdboxCloudProvider()&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;When our cloud-provider actually has started (the &lt;code&gt;main()&lt;/code&gt; function has been called) the cloud-provider controller will look at all registered providers, and it will find ours that we registered in the &lt;code&gt;init()&lt;/code&gt; function. It will then call our instantiation function &lt;code&gt;newLoadBalancer()&lt;/code&gt;, which will do any pre-tasks for setting up our load balancer, it will then assign it to &lt;code&gt;lb&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&#34;Enabling-our-Load-Balancer-func-t-ThebsdboxCloudProvider-LoadBalancer-cloudprovider-LoadBalancer-bool&#34;&gt;&lt;a href=&#34;#Enabling-our-Load-Balancer-func-t-ThebsdboxCloudProvider-LoadBalancer-cloudprovider-LoadBalancer-bool&#34; class=&#34;headerlink&#34; title=&#34;Enabling our Load-Balancer func (t *ThebsdboxCloudProvider) LoadBalancer() (cloudprovider.LoadBalancer, bool)&#34;&gt;&lt;/a&gt;Enabling our Load-Balancer &lt;code&gt;func (t *ThebsdboxCloudProvider) LoadBalancer() (cloudprovider.LoadBalancer, bool)&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;This function is pretty much the &lt;strong&gt;crux&lt;/strong&gt; of enabling the load balancer functionality and as part of the &lt;code&gt;cloud-controller-manager&lt;/code&gt; spec defines what functionality our cloud-provider will expose. These functions will return one of two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Our instantiated functionality (in this case our loadbalancer object, returned as &lt;code&gt;lb&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;If this is enabled or not (&lt;code&gt;true&lt;/code&gt;/&lt;code&gt;false&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Everything that we’re not exposing from our cloud-provider will return &lt;code&gt;false&lt;/code&gt; and can be seen in the &lt;code&gt;disabled.go&lt;/code&gt; source.&lt;/p&gt;
&lt;h3 id=&#34;disabled-go&#34;&gt;&lt;a href=&#34;#disabled-go&#34; class=&#34;headerlink&#34; title=&#34;disabled.go&#34;&gt;&lt;/a&gt;&lt;code&gt;disabled.go&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;All of these functions disable these bits of functionality within our cloud-provider&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;package thebsdbox&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;import cloudprovider “k8s.io/cloud-provider”&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// Instances returns an instances interface. Also returns true if the interface is supported, false otherwise.&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func (t *ThebsdboxCloudProvider) Instances() (cloudprovider.Instances, bool) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return nil, false&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// Zones returns a zones interface. Also returns true if the interface is supported, false otherwise.&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func (t *ThebsdboxCloudProvider) Zones() (cloudprovider.Zones, bool) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return nil, false&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// Clusters returns a clusters interface.  Also returns true if the interface is supported, false otherwise.&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func (t *ThebsdboxCloudProvider) Clusters() (cloudprovider.Clusters, bool) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return nil, false&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// Routes returns a routes interface along with whether the interface is supported.&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func (t *ThebsdboxCloudProvider) Routes() (cloudprovider.Routes, bool) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return nil, false&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// HasClusterID provides an opportunity for cloud-provider-specific code to process DNS settings for pods.&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func (t *ThebsdboxCloudProvider) HasClusterID() bool &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return false&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&#34;loadbalancer-go&#34;&gt;&lt;a href=&#34;#loadbalancer-go&#34; class=&#34;headerlink&#34; title=&#34;loadbalancer.go&#34;&gt;&lt;/a&gt;&lt;code&gt;loadbalancer.go&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Our LoadBalancer source code again has to match the interface as expressed &lt;a href=&#34;https://github.com/kubernetes/cloud-provider/blob/master/cloud.go#L126&#34;&gt;here&lt;/a&gt;, we can see those functions defined below and exposed as methods as part of our &lt;code&gt;thebsdboxLBManager&lt;/code&gt; struct.&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;41&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;42&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;43&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;44&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;45&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;46&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;47&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;48&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;49&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;50&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;51&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;52&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;53&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;54&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;55&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;56&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;57&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;58&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;59&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;60&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;61&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;62&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;63&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;64&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;65&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;66&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;67&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;68&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;69&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;70&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;71&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;72&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;73&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;74&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;75&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;76&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;77&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;78&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;79&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;80&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;81&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;package thebsdbox&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;import (&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	“context”&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	“fmt”&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	v1 “k8s.io/api/core/v1”&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	“k8s.io/client-go/kubernetes”&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	cloudprovider “k8s.io/cloud-provider”&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	“k8s.io/klog”&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;//thebsdboxLBManager - &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;type thebsdboxLBManager struct &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	kubeClient     *kubernetes.Clientset&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	nameSpace      string&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func newLoadBalancer() cloudprovider.LoadBalancer &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	// Needs code to get a kubeclient =&amp;gt; client&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	// Needs code to get a namespace to operate in =&amp;gt; namespace&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return &amp;amp;thebsdboxLBManager&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		kubeClient: client,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		namespace: ns,&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func (tlb *thebsdboxLBManager) EnsureLoadBalancer(ctx context.Context, clusterName string, service *v1.Service, nodes []*v1.Node) (lbs *v1.LoadBalancerStatus, err error) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return tlb.syncLoadBalancer(service)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func (tlb *thebsdboxLBManager) UpdateLoadBalancer(ctx context.Context, clusterName string, service *v1.Service, nodes []*v1.Node) (err error) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	_, err = tlb.syncLoadBalancer(service)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return err&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func (tlb *thebsdboxLBManager) EnsureLoadBalancerDeleted(ctx context.Context, clusterName string, service *v1.Service) error &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return tlb.deleteLoadBalancer(service)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func (tlb *thebsdboxLBManager) GetLoadBalancer(ctx context.Context, clusterName string, service *v1.Service) (status *v1.LoadBalancerStatus, exists bool, err error) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	// RETRIEVE EXISTING LOAD BALANCER STATUS	&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return &amp;amp;v1.LoadBalancerStatus&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		Ingress: []v1.LoadBalancerIngress&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;				IP: vip,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			&amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		&amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&amp;#125;, nil&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// GetLoadBalancerName returns the name of the load balancer. Implementations must treat the&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// *v1.Service parameter as read-only and not modify it.&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func (tlb *thebsdboxLBManager) GetLoadBalancerName(_ context.Context, clusterName string, service *v1.Service) string &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return getDefaultLoadBalancerName(service)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func getDefaultLoadBalancerName(service *v1.Service) string &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return cloudprovider.DefaultLoadBalancerName(service)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func (tlb *thebsdboxLBManager) deleteLoadBalancer(service *v1.Service) error &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	klog.Infof(“deleting service ‘%s’ (%s)”, service.Name, service.UID)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	// DELETE LOAD BALANCER LOGIC&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return err&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func (tlb *thebsdboxLBManager) syncLoadBalancer(service *v1.Service) (*v1.LoadBalancerStatus, error) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	// CREATE / UPDATE LOAD BALANCER LOGIC (and return updated load balancer IP)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	return &amp;amp;v1.LoadBalancerStatus&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		Ingress: []v1.LoadBalancerIngress&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;				IP: vip,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;			&amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		&amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&amp;#125;, nil&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h4 id=&#34;Instantiating-the-LoadBalancer-newLoadBalancer&#34;&gt;&lt;a href=&#34;#Instantiating-the-LoadBalancer-newLoadBalancer&#34; class=&#34;headerlink&#34; title=&#34;Instantiating the LoadBalancer newLoadBalancer()&#34;&gt;&lt;/a&gt;Instantiating the LoadBalancer &lt;code&gt;newLoadBalancer()&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;This function is called when the cloud-provider itself is initialised and can be seen in &lt;code&gt;cloud.go&lt;/code&gt; as part of the &lt;code&gt;newThebsdboxCloudProvider()&lt;/code&gt; method. The newly created load balancer object once created is then added to the cloud-providers main object for use when needed. &lt;/p&gt;
&lt;h4 id=&#34;Interface-methods&#34;&gt;&lt;a href=&#34;#Interface-methods&#34; class=&#34;headerlink&#34; title=&#34;Interface methods&#34;&gt;&lt;/a&gt;Interface methods&lt;/h4&gt;&lt;h5 id=&#34;EnsureLoadBalancer&#34;&gt;&lt;a href=&#34;#EnsureLoadBalancer&#34; class=&#34;headerlink&#34; title=&#34;EnsureLoadBalancer&#34;&gt;&lt;/a&gt;&lt;code&gt;EnsureLoadBalancer&lt;/code&gt;&lt;/h5&gt;&lt;p&gt;Creates a LoadBalancer if one didn’t exist to begin with and then return it’s status (with the load balancer address)&lt;/p&gt;
&lt;h5 id=&#34;UpdateLoadBalancer&#34;&gt;&lt;a href=&#34;#UpdateLoadBalancer&#34; class=&#34;headerlink&#34; title=&#34;UpdateLoadBalancer&#34;&gt;&lt;/a&gt;&lt;code&gt;UpdateLoadBalancer&lt;/code&gt;&lt;/h5&gt;&lt;p&gt;Updates an existing LoadBalancer, or will create one if it didn’t exist and then return it’s status (with the load balancer address)&lt;/p&gt;
&lt;h5 id=&#34;EnsureLoadBalancerDeleted&#34;&gt;&lt;a href=&#34;#EnsureLoadBalancerDeleted&#34; class=&#34;headerlink&#34; title=&#34;EnsureLoadBalancerDeleted&#34;&gt;&lt;/a&gt;&lt;code&gt;EnsureLoadBalancerDeleted&lt;/code&gt;&lt;/h5&gt;&lt;p&gt;Calls &lt;code&gt;GetLoadBalancer&lt;/code&gt; first to ensure that the load balancer exists, and if so it will delete the vendor specific load balancer. If this completes successfully then the service of &lt;code&gt;type: LoadBalancer&lt;/code&gt; is removed as an object within Kubernetes.&lt;/p&gt;
&lt;h5 id=&#34;GetLoadBalancer&#34;&gt;&lt;a href=&#34;#GetLoadBalancer&#34; class=&#34;headerlink&#34; title=&#34;GetLoadBalancer&#34;&gt;&lt;/a&gt;&lt;code&gt;GetLoadBalancer&lt;/code&gt;&lt;/h5&gt;&lt;p&gt;This will speak natively to the vendor specific load balancer to make sure that it has been provisioned correctly.&lt;/p&gt;
&lt;h5 id=&#34;GetLoadBalancerName&#34;&gt;&lt;a href=&#34;#GetLoadBalancerName&#34; class=&#34;headerlink&#34; title=&#34;GetLoadBalancerName&#34;&gt;&lt;/a&gt;&lt;code&gt;GetLoadBalancerName&lt;/code&gt;&lt;/h5&gt;&lt;p&gt;Returns the name of the load balancer instance.&lt;/p&gt;
&lt;h3 id=&#34;main-go&#34;&gt;&lt;a href=&#34;#main-go&#34; class=&#34;headerlink&#34; title=&#34;main.go&#34;&gt;&lt;/a&gt;&lt;code&gt;main.go&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;This is a the standard &lt;code&gt;main.go&lt;/code&gt; as given by the actual &lt;code&gt;cloud-controller-manager&lt;/code&gt; example. The one change is the addition of our &lt;code&gt;// OUR CLOUD PROVIDER&lt;/code&gt; which adds all of our vendor specific &lt;code&gt;cloud-provider&lt;/code&gt; methods.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;init()&lt;/code&gt; in &lt;code&gt;cloud.go&lt;/code&gt; is called and registers our cloud-provider and the call back to our &lt;code&gt;newCloudProvider()&lt;/code&gt; method.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;command.Execute()&lt;/code&gt; in &lt;code&gt;main.go&lt;/code&gt; starts the &lt;code&gt;cloud-controller-manager&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;cloud-controller-manager&lt;/code&gt; method will look at all of the registered cloud-providers and find our registered provider.&lt;/li&gt;
&lt;li&gt;Our provider will have its &lt;code&gt;newCloudProvider()&lt;/code&gt; method called which sets up everything that is needed for it to be able to complete it’s tasks.&lt;/li&gt;
&lt;li&gt;Our cloud provider is now running, when a user tries to create a resource that we’ve registered for (Load Balancers) our vendor code will be called to provide this functionality.&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;32&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;package main&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;import (&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	“math/rand”&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	“os”&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	“time”&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	“k8s.io/component-base/logs”&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	“k8s.io/kubernetes/cmd/cloud-controller-manager/app”&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	_ “k8s.io/component-base/metrics/prometheus/version” // for version metric registration&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	// NOTE: Importing all in-tree cloud-providers is not required when&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	// implementing an out-of-tree cloud-provider.&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	_ “k8s.io/component-base/metrics/prometheus/clientgo” // load all the prometheus client-go plugins&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	_ “k8s.io/kubernetes/pkg/cloudprovider/providers”&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	// OUR CLOUD PROVIDER&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	_ “github.com/user/pkg/thebsdbox/”&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;func main() &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	rand.Seed(time.Now().UnixNano())&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	command := app.NewCloudControllerManagerCommand()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	logs.InitLogs()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	defer logs.FlushLogs()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	if err := command.Execute(); err != nil &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;		os.Exit(1)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&#34;Summary&#34;&gt;&lt;a href=&#34;#Summary&#34; class=&#34;headerlink&#34; title=&#34;Summary&#34;&gt;&lt;/a&gt;Summary&lt;/h2&gt;&lt;p&gt;Hopefully this is of some use as to how a Kubernetes cloud-provider is architected, to understand a few more examples I’ve included some other providers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/plunder-app/plndr-cloud-provider&#34;&gt;plndr-cloud-provider&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/munnerz/keepalived-cloud-provider&#34;&gt;keepalived-cloud-provider&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Good luck and any feedback feel free to message &lt;a href=&#34;https://twitter.com/thebsdbox&#34;&gt;me&lt;/a&gt;&lt;/p&gt;
 ]]></description>
        </item>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2020/01/18/A-Decade-in-review-2009-2019/</guid>
            <title>A Decade in review 2009-2019</title>
            <link>http://thebsdbox.co.uk/2020/01/18/A-Decade-in-review-2009-2019/</link>
            <pubDate>Sat, 18 Jan 2020 16:42:40 +0000</pubDate>
            <description><![CDATA[ &lt;p&gt;This post is relatively delayed due to &lt;del&gt;laziness&lt;/del&gt; “business reasons”, also the last post about bare-metal Kubernetes deployments took too long. &lt;/p&gt;
&lt;p&gt;A lot of people on the twitter sphere published a decade and/or a year in review.. so I’ve raided my iCloud photo library and will attempt to put something together.&lt;/p&gt;
&lt;h2 id=&#34;Pre-2009&#34;&gt;&lt;a href=&#34;#Pre-2009&#34; class=&#34;headerlink&#34; title=&#34;Pre-2009&#34;&gt;&lt;/a&gt;Pre-2009&lt;/h2&gt;&lt;p&gt;Added this in because it’s relevant to where I found myself ending up in 2009.. &lt;/p&gt;
&lt;p&gt;So prior to 2009 I had been through a couple of support roles, being a DB admin and running a support team of Linux/UNIX, Windows, DBs and backup engineers inside a (B)ritish (T)elecommunications company. Following this I became a consolidation architect with &lt;a href=&#34;https://en.wikipedia.org/wiki/Sun_Microsystems&#34;&gt;Sun Microsystems&lt;/a&gt; (&lt;code&gt;/me pours one out for SUN&lt;/code&gt;), with the project goal of shrinking five creaking old data centres into a shiny brand new one for the same (B)ritish (T)elecommunications company. The main goal was taking their big old servers (E10Ks, V440s etc..) and migrating their applications into &lt;a href=&#34;https://en.wikipedia.org/wiki/Solaris_Containers&#34;&gt;Solaris Zones&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/E10k.jpg&#34; alt=&#34;E10K with human for comparison&#34;&gt;&lt;br&gt;&lt;em&gt;E10K with human for comparison&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You might be thinking why does this matter, well somehow I’ve ended up doing the same thing over ten years later &lt;code&gt;¯\_(ツ)_/¯&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Back to the timeline,&lt;/em&gt; Towards the end of 2008 things took a pretty grim turn and the &lt;a href=&#34;https://en.wikipedia.org/wiki/Financial_crisis_of_2007%E2%80%9308&#34;&gt;financial crash&lt;/a&gt; caused a 50 person team (In September) to be down to 4 people by November at which point it became a 3 person team (me being the 4th person 😔).&lt;/p&gt;
&lt;h2 id=&#34;2009&#34;&gt;&lt;a href=&#34;#2009&#34; class=&#34;headerlink&#34; title=&#34;2009&#34;&gt;&lt;/a&gt;2009&lt;/h2&gt;&lt;p&gt;In mid-January I had been out of work for three months, the financial crash combined with the Christmas period meant that the job market was pretty much none-existent. I think we had enough money for a month or two of rent for where we were living and then that would be it 😐…&lt;/p&gt;
&lt;p&gt;Then &lt;strong&gt;finally&lt;/strong&gt; a random recruitment email appeared (which I was surprised to find that I still have it in my gmail😀)&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;Hello Daniel,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Would you be interested in a permanent role with the &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;European Space Agency based in Frankfurt?&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Regards&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;A number of interviews later I accepted the role, packed bags, booked flight to Frankfurt … and then got the bus to Darmstadt&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/decade_darmstadt.png&#34;&gt;&lt;br&gt;&lt;em&gt;So &lt;em&gt;not&lt;/em&gt; actually Frankfurt after all&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;The-European-Space-Agency-ESOC-Space-Operations-Centre-2009-2012&#34;&gt;&lt;a href=&#34;#The-European-Space-Agency-ESOC-Space-Operations-Centre-2009-2012&#34; class=&#34;headerlink&#34; title=&#34;The European Space Agency (ESOC - Space Operations Centre) 2009-2012&#34;&gt;&lt;/a&gt;The European Space Agency (ESOC - Space Operations Centre) 2009-2012&lt;/h2&gt;&lt;p&gt;I spent four crazy years living in Germany where I made a ton of friends from all over the world (Australia, Indonesia, Bulgaria, Italy, America, Spain, Greece and of course Germany). From a social perspective Germany has a fantastic outdoor lifestyle:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Endless wine and beer festivals&lt;/li&gt;
&lt;li&gt;Dedicated cycling lanes and walking routes throughout the countryside&lt;/li&gt;
&lt;li&gt;Excellent weather for lazy evenings&lt;/li&gt;
&lt;li&gt;Cafes and restaurants have a focus chilling outdoors&lt;/li&gt;
&lt;li&gt;Towards the end of the year it’s the autumn festivals and then the Christmas markets&lt;/li&gt;
&lt;li&gt;Did I mention the beer festivals?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The work itself was pretty &lt;strong&gt;varied&lt;/strong&gt;, sometimes a focus on writing reams of paperwork (100+ page guide to installing SuSE 🙄) to architecting and building platforms and systems for actual space missions. Also there was the opportunity to support some of the actual satellite missions which was always both exciting and pretty terrifying be part of. &lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/atv.jpg&#34;&gt;&lt;br&gt;&lt;em&gt;Everyone terrifying witness the ATV (automated telemetry vehicle) dock un-assisted with ISS&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/esoc_command.jpg&#34;&gt;&lt;br&gt;&lt;em&gt;Before we modernised the command centre&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/esoc_command_after.jpg&#34;&gt;&lt;br&gt;&lt;em&gt;All brand new, (still powered by some of my hacky kernel modules)&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;Back-to-UK-SKY-2012-2014&#34;&gt;&lt;a href=&#34;#Back-to-UK-SKY-2012-2014&#34; class=&#34;headerlink&#34; title=&#34;Back to UK, SKY 2012-2014&#34;&gt;&lt;/a&gt;Back to UK, SKY 2012-2014&lt;/h2&gt;&lt;p&gt;With Kim unable really to find the role that made sense for her, we made the decision to head back to the UK as the market had started to improve. I took a role with Sky that was involved some of the heaviest infrastructure work I’ve ever been part of, but a fantastic experience none-the-less. Deploying a Disaster recovery site almost single handedly was a highlight! (Ah site B) Everything from Servers(HPE/Cisco with vSphere), to networking (Cisco Nexus switches) and storage (EMC/IBM XiV/Brocade) I got to play with and learn about. I also had the opportunity to decommission the original site, which was also fun and also a little bit on the gross side… When your cabling is “organically” grown it ultimately becomes an unfathomable under the floor mess for someone else (me) to sort out.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/cabling.jpg&#34;&gt;&lt;br&gt;&lt;em&gt;This took days to sort out&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;Suits-travel-and-presenting-HPE-2014-2017&#34;&gt;&lt;a href=&#34;#Suits-travel-and-presenting-HPE-2014-2017&#34; class=&#34;headerlink&#34; title=&#34;Suits, travel and presenting, HPE 2014 - 2017&#34;&gt;&lt;/a&gt;Suits, travel and presenting, HPE 2014 - 2017&lt;/h2&gt;&lt;p&gt;By now Kims career had taken off, I’d spent a fantastic few years getting pretty low-level with various infrastructure vendors when one reached out to me. Approximately 48 interviews later on I’d bought a suit (mandatory) and was part of a team in HPE focussed on the merits of “converged infrastructure”!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/cringe.jpg&#34;&gt;&lt;br&gt;&lt;em&gt;I certainly did &lt;em&gt;not&lt;/em&gt; choose the title on this 🙄&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Over the three to four years at HPE I created a lot of powerpoint, learnt a great deal about PCI-E switching &lt;code&gt;¯\_(ツ)_/¯&lt;/code&gt;, memristors and a variety of other technologies. I was more than a little insecure about being the only person on the team without several &lt;a href=&#34;https://en.wikipedia.org/wiki/CCIE_Certification&#34;&gt;CCIE&lt;/a&gt; certifications amongst other various industry accolades, however the entire team was 110% focussed on knowledge sharing and ensuring we all learnt from one another as much as possible (something I’m passionate about). It was here that I was asked to present in-front of an audience for the first time, which I still have nightmares about too this day… standing on stage shaking and stumbling over my words whilst trying to remember all my content about “sustainable data centre energy use” 😬. Although somehow after getting through that presentation I ended up being drafted to present more and more, to the point where I was regularly travelling around doing roadshows about various new HPE products. At some point I was asked to work on a new HPE partnership with a start up called Docker 🐳…&lt;/p&gt;
&lt;p&gt;As part of some of the work I’d been doing at HPE around automating bare-metal, I’d stumbled across a project called Docker &lt;a href=&#34;https://github.com/docker/deploykit&#34;&gt;InfraKit&lt;/a&gt; and had written a plugin so that it could be extended to automate HPEs converged infrastructure. This lead to a chance email from an employee at Docker that asked if I’d be interested in participating in another project they were developing. I immediately said “yes” as it sounded super exciting to be part of! I received an email invite to a private GitHub repository to discover a new project for building Linux distributions .. to my horror the entire project was written in GO (I’d never written a single line of GO code at this point) 😱&lt;/p&gt;
&lt;p&gt;Too embarrassed to say anything I decided to try and quickly get up to speed, I quietly worked away on simple fixes to command line flags and updating documentation. Finally I managed to write some code that “worked” and allowed the tool to build Linux images as &lt;code&gt;VMDK&lt;/code&gt; files and run them in VMware Fusion and vSphere clusters. &lt;/p&gt;
&lt;h2 id=&#34;A-random-start-to-a-startup-Docker-2017-2018&#34;&gt;&lt;a href=&#34;#A-random-start-to-a-startup-Docker-2017-2018&#34; class=&#34;headerlink&#34; title=&#34;A random start to a startup, Docker 2017 - 2018&#34;&gt;&lt;/a&gt;A random start to a startup, Docker 2017 - 2018&lt;/h2&gt;&lt;p&gt;Whilst still at HPE I went to DockerCon US 2017 in Austin, which turned out to be a very bonkers experience… &lt;/p&gt;
&lt;p&gt;I had the privilege of joining the &lt;a href=&#34;https://www.docker.com/community/captains&#34;&gt;Docker Captains&lt;/a&gt; program, which is a group of people that are &lt;strong&gt;ambassadors&lt;/strong&gt; around all things Docker.&lt;br&gt;&lt;img src=&#34;/Images/captains.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;I got to see the project I’d become part of finally be renamed (&lt;code&gt;Moby&lt;/code&gt; -&amp;gt; &lt;code&gt;LinuxKit&lt;/code&gt;) and be released to the wider community!&lt;br&gt;&lt;img src=&#34;/Images/dockercon17.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;I made some amazing friends at Docker including &lt;a href=&#34;https://twitter.com/BettyJunod&#34;&gt;Betty&lt;/a&gt; and &lt;a href=&#34;https://twitter.com/scottcoulton&#34;&gt;Scott&lt;/a&gt;, amongst others :-)&lt;br&gt;&lt;img src=&#34;/Images/betty.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;Also getting to hang around with &lt;a href=&#34;https://twitter.com/solomonstre&#34;&gt;Solomon&lt;/a&gt; was great fun :-)&lt;br&gt;&lt;img src=&#34;/Images/solomon.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;Whilst also in Austin I was asked to join the Docker team 🐳😀 &lt;/p&gt;
&lt;p&gt;It was a fantastic 18 months which involved:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Continuing working on InfraKit/LinuxKit&lt;/li&gt;
&lt;li&gt;Working on Docker Swarm (&lt;a href=&#34;https://github.com/docker/swarmkit&#34;&gt;swarmkit&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Presenting at DockerCon EU 17 and DockerCon US 18&lt;/li&gt;
&lt;li&gt;Helping customers around EMEA successfully deploy Containers and clusters, also how to migrate their applications correctly&lt;/li&gt;
&lt;li&gt;Building a modern app migration strategy for to Docker Enterprise Edition&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;36-hours-in-Seattle-vHeptio-2018-now&#34;&gt;&lt;a href=&#34;#36-hours-in-Seattle-vHeptio-2018-now&#34; class=&#34;headerlink&#34; title=&#34;36 hours in Seattle, vHeptio 2018 - now&#34;&gt;&lt;/a&gt;36 hours in Seattle, vHeptio 2018 - now&lt;/h2&gt;&lt;p&gt;Toward the end of the 18 months the changes in strategy had started to push me to consider other options, and after a &lt;em&gt;“chance”&lt;/em&gt; meeting with &lt;a href=&#34;https://twitter.com/scott_lowe&#34;&gt;Scott Lowe&lt;/a&gt; in a coffee shop in San Francisco I decided to go have a real conversation with Heptio (I’d also had a conversation with &lt;code&gt;$current_employer&lt;/code&gt; but that’s another story). I flew from the UK to spend a day and a half in Seattle to speak with Heptio, I pretty much spent the same amount of time in airports and planes as I did in Seattle itself but it was worth it. &lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/needle.jpg&#34;&gt;&lt;br&gt;&lt;em&gt;The Seattle Space Needle&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/seattle&#34;&gt;&lt;br&gt;&lt;em&gt;Seattle has some amazing buildings&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;After accepting I became the sole “field engineer” in EMEA and ended up fiddling with, breaking and fixing Kubernetes clusters for a living :-)&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/heptio_jacket.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;Heptio was acquired by VMware in early 2019, and we’ve largely been continuing the same work.. The only change is that I’m now no longer the only person in EMEA 😀&lt;/p&gt;
 ]]></description>
        </item>
        <item>
            <guid isPermalink="true">http://thebsdbox.co.uk/2020/01/02/Designing-Building-HA-bare-metal-Kubernetes-cluster/</guid>
            <title>Designing and Building HA Kubernetes on Bare-Metal</title>
            <link>http://thebsdbox.co.uk/2020/01/02/Designing-Building-HA-bare-metal-Kubernetes-cluster/</link>
            <pubDate>Thu, 02 Jan 2020 17:19:58 +0000</pubDate>
            <description><![CDATA[ &lt;p&gt;Undeterred by the memes and “thought leadership” I decided I’d finally complete this architectural overview of deploying Kubernetes on bare-metal. &lt;/p&gt;
&lt;div class=&#34;twitter-wrapper&#34;&gt;&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;a href=&#34;https://twitter.com/memenetes/status/1208315567431663616&#34;&gt;&lt;/a&gt;&lt;/blockquote&gt;&lt;/div&gt;&lt;script async defer src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;

&lt;p&gt;To begin with we need to fix the naming.. no-one has ever deployed Kubernetes on bare-metal. You deploy Kubernetes on an Operating System, that OS can be installed on bare-metal hardware or virtual hardware etc.. (moving on). &lt;/p&gt;
&lt;p&gt;This post ended up sprawling on much further than I ever really intended it to, so It’s broken into four main sections:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Why would I bother with bare-metal in the first place?&lt;/li&gt;
&lt;li&gt;What does the operating environment look like, i.e. what do I get to build my cluster with?&lt;/li&gt;
&lt;li&gt;Architectural decisions and designs when deploying a cluster on bare-metal&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actual&lt;/strong&gt; steps to deploy the cluster… (probably what most people would care about)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One other assumption that this post takes when considering bare-metal is that the we’re typically in an on-premise environment or in an environment that doesn’t present any existing services so everything will need to be created to build the HA cluster. For further clarification about what bare-metal means you can start &lt;a href=&#34;https://en.wikipedia.org/wiki/Bare-metal_server&#34;&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So lets begin!&lt;/p&gt;
&lt;h2 id=&#34;Why-Bare-Metal&#34;&gt;&lt;a href=&#34;#Why-Bare-Metal&#34; class=&#34;headerlink&#34; title=&#34;Why Bare-Metal?&#34;&gt;&lt;/a&gt;Why Bare-Metal?&lt;/h2&gt;&lt;p&gt;After having a colleague read through a draft of this post, his immediate thoughts were:&lt;/p&gt;
&lt;p&gt;“I’m left feeling a bit ‘why would I do this’ 🙂”&lt;/p&gt;
&lt;p&gt;It turned out that I’d not really given any actual positive reasons for running Kubernetes on Bare-Metal… So why would anyone go to the trouble?&lt;/p&gt;
&lt;h3 id=&#34;Control&#34;&gt;&lt;a href=&#34;#Control&#34; class=&#34;headerlink&#34; title=&#34;Control&#34;&gt;&lt;/a&gt;Control&lt;/h3&gt;&lt;p&gt;If we’re not building our Kubernetes clusters on bare-metal, then typically we’re building them in &lt;strong&gt;“the cloud”&lt;/strong&gt; !&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/cloud.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;If that is the case then we typically will be presented with two options to deploy our clusters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Managed as a service&lt;/strong&gt; (KaaS/CaaS or whatever marketing call it these days), but effectively the cloud provider architects and owns the cluster architecture and you get access to deployed solution.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DIY&lt;/strong&gt;, virtual hardware + virtual networking followed by a bit of rolling up the sleeves and “hey-presto”!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both of these options are fantastic and provide a solution if you need to move quick (KaaS) or need a degree of flexibility (DIY). Given that these two examples appear to cover most use-cases why should we care? &lt;/p&gt;
&lt;p&gt;Well mainly &lt;strong&gt;control&lt;/strong&gt; and &lt;strong&gt;customisation&lt;/strong&gt;.. I’m not sure if it’s just me but anything that is a prebuilt “completed” solution warrants a desire to immediately deconstruct to understand, and customise to suit needs that I didn’t know that I had. &lt;/p&gt;
&lt;p&gt;A lot of this can crop up in cloud environments, where architectural decisions around the entire cluster (KaaS) have already been decided and can’t be changed. This can also exist even in a DIY cluster in cloud environments, where virtual machines sizes or configurations are fixed limiting cluster flexibility. It can even extend to design decisions on networking functionality or technologies to limitations on scale.&lt;/p&gt;
&lt;h3 id=&#34;Design-Architecture-and-Edge&#34;&gt;&lt;a href=&#34;#Design-Architecture-and-Edge&#34; class=&#34;headerlink&#34; title=&#34;Design / Architecture and Edge&#34;&gt;&lt;/a&gt;Design / Architecture and Edge&lt;/h3&gt;&lt;p&gt;This is tied to the above point, but it is still relevant to be called out as it’s own reason. One of the most common reasons for a bare-metal requirement is that the design and architecture requirements. Often the applications that are being migrated or being re-designed to “cloud-native” principles may need access to existing systems that can’t be changed or updated. Alternatively there may be a requirement on things like FPGAs or types of GPUs that simply aren’t available within alternative environments. &lt;/p&gt;
&lt;p&gt;One other requirement that is starting to become more and more common are edge clusters, which are typically small on-site clusters that will handle local processes in things like stores, offices, warehouses etc.. and will send the results back to central processing once complete. In most use-cases the infrastructure can be 2-3 1U servers to a stack of Raspberry PIs, all driven by things like application needs and physical space and power.&lt;/p&gt;
&lt;h3 id=&#34;Workloads&#34;&gt;&lt;a href=&#34;#Workloads&#34; class=&#34;headerlink&#34; title=&#34;Workloads&#34;&gt;&lt;/a&gt;Workloads&lt;/h3&gt;&lt;p&gt;In a lot of scenarios running a “simple” workload on bare-metal versus within a cloud environment won’t look any different. However there will be a number of application workloads that will require bare-metal:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;100% predictable performance that dictate no noisy neighbours or overhead&lt;/li&gt;
&lt;li&gt;Custom or specific hardware: Crypto, GPUs or FPGAs&lt;/li&gt;
&lt;li&gt;Data locality and/or local laws&lt;/li&gt;
&lt;li&gt;Application network requirements or hard coded addresses&lt;/li&gt;
&lt;li&gt;Operational team skillset (having to learn a cloud provider technology stack)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;Security-and-locality&#34;&gt;&lt;a href=&#34;#Security-and-locality&#34; class=&#34;headerlink&#34; title=&#34;Security and locality&#34;&gt;&lt;/a&gt;Security and locality&lt;/h3&gt;&lt;p&gt;Some applications that process customer, financial or personal data may (by law) have requirements that dictate that all communication paths or compute locations have to be verifiable. Unfortunately the nebulous behaviour of the cloud means that in some cases it’s not possible at the time of an audit to reliably say where customer data is located. In these situations the most common strategy is to have both the data and processing work done in an owned or co-located datacenter that meets security and legal requirements.&lt;/p&gt;
&lt;p&gt;Also with bare-metal the onus to provide security is on the bare-metal or infrastructure operators, allowing them to be as bleeding edge when it comes to patches and updates. This is true not just for the applications, the Kubernetes cluster and the Operating System but also the hardware devices such as server firmware and networking devices. With a cloud environment the underlying infrastructure vendors or it’s vulnerabilities isn’t always commonly disclosed, meaning that you’re at the mercy of the cloud provider to handle this level of patching and fixes.&lt;/p&gt;
&lt;h3 id=&#34;Lock-in&#34;&gt;&lt;a href=&#34;#Lock-in&#34; class=&#34;headerlink&#34; title=&#34;Lock-in?&#34;&gt;&lt;/a&gt;Lock-in?&lt;/h3&gt;&lt;p&gt;Finally, lock-in … I don’t work in sales so I don’t necessarily buy this as a reason. A “well” architected solution can negate a lot of the lock-in, however when things are rushed or you opt to deeply couple with too many cloud services then you can end up in a situation where you’re dependant on this functionality and at which point you may be technically locked in, with a lot of work required to un-pick and re-build this functionality elsewhere.&lt;/p&gt;
&lt;p&gt;…&lt;/p&gt;
&lt;p&gt;So, with all this in mind what do we have to work with in a typical bare-metal environment ?&lt;/p&gt;
&lt;h2 id=&#34;Operating-Environment&#34;&gt;&lt;a href=&#34;#Operating-Environment&#34; class=&#34;headerlink&#34; title=&#34;Operating Environment&#34;&gt;&lt;/a&gt;Operating Environment&lt;/h2&gt;&lt;p&gt;This section will have a brief overview of what is typically available to us in a bare-metal environment, again I typically will consider bare-metal a non-hypervisor on-premises environment.&lt;/p&gt;
&lt;h3 id=&#34;Compute-hardware&#34;&gt;&lt;a href=&#34;#Compute-hardware&#34; class=&#34;headerlink&#34; title=&#34;Compute hardware&#34;&gt;&lt;/a&gt;Compute hardware&lt;/h3&gt;&lt;p&gt;With a bare-metal deployment we usually mean installing software onto hardware with no hypervisor present. In some circumstances this may or may not provide better performance or save both money or reduce complexity. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;However&lt;/strong&gt;, if we take into consideration the size of a standard enterprise class server (TBs of ram, 10s of cores/CPUs) we can start to see that we have a huge amount of computing power restrained to a single instance of both Operating System and Kubernetes use-case (worker/control plane). If we just consider the Kubernetes use-case for control plane nodes (low memory and CPU) then bare-metal servers can immediately lead to hugely under-utilised hardware.&lt;/p&gt;
&lt;p&gt;We also need to consider the actual deployment of the Operating system, which still relies on technologies that are ~20 years old. With virtualisation we can utilise templates and OS images to rapidly deploy a node on virtual hardware. With bare-metal we need to care about the physical hardware and either take a CD to the machine or aim to deploy using a remote method.&lt;/p&gt;
&lt;h3 id=&#34;Load-balancing&#34;&gt;&lt;a href=&#34;#Load-balancing&#34; class=&#34;headerlink&#34; title=&#34;Load-balancing&#34;&gt;&lt;/a&gt;Load-balancing&lt;/h3&gt;&lt;p&gt;A highly available Kubernetes cluster requires a load-balancer to provide availability to the control plane in the event a node fails and to balance the load into the control plane. In a cloud environment an end-user clicks the [load-balancer] button and * &lt;em&gt;magic&lt;/em&gt; * occurs, followed by a virtual IP that an end user will access to provide both HA and load-balancing to nodes underneath it. &lt;/p&gt;
&lt;p&gt;When thinking about a bare-metal &lt;del&gt;on-premises&lt;/del&gt; environment we have only a handful of architectural options.&lt;/p&gt;
&lt;h4 id=&#34;Hardware-appliance-Load-Balancer&#34;&gt;&lt;a href=&#34;#Hardware-appliance-Load-Balancer&#34; class=&#34;headerlink&#34; title=&#34;Hardware (appliance) Load Balancer&#34;&gt;&lt;/a&gt;Hardware (appliance) Load Balancer&lt;/h4&gt;&lt;p&gt;If budget allows then as part of the architecture we can use a pair of load balancer appliances. We will require a pair of them to provide redundancy incase the appliance fails or requires maintenance. These appliances will typically provide the capability to create an external virtual IP address that can load balance over a number of physical IP addresses. &lt;/p&gt;
&lt;p&gt;In some circumstances the load-balancers may provide an API or capability to integrate their functionality into a Kubernetes cluster making it much easier for applications deployed within the cluster to utilise these hardware load balancers for application redundancy. &lt;/p&gt;
&lt;h4 id=&#34;Software-Load-balancers&#34;&gt;&lt;a href=&#34;#Software-Load-balancers&#34; class=&#34;headerlink&#34; title=&#34;Software Load balancers&#34;&gt;&lt;/a&gt;Software Load balancers&lt;/h4&gt;&lt;p&gt;The alternative is to use a software based load balancer which are usually simple to deploy. However in order to provide both load-balancing and high-availability then we will have to implement additional software to sit along side the software load-balancers. These two pieces of functionality are:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Virtual IP address&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This functionality provides the capability of having an externally accessible IP address that can move between functioning nodes. This means that users attempting to access a service will use this VIP (virtual IP), which will always be exposed on a functioning node.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Network Service (application) Load-balancing&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Load-balancing provides two pieces of functionality, it provides high-availability by ensuring that traffic is directed to a working node. It also ensures that traffic can be shared between a pool of working nodes ensuring that load is balanced. This provides the capability of having a larger amount of available service capacity than a single host, that can be scaled up by increasing nodes in the pool. &lt;/p&gt;
&lt;p&gt;With both of these pieces of functionality in place we have a single virtual IP that will always direct us to a working load-balancer instance, which in turn will load-balance our access to the network service we want to access.&lt;/p&gt;
&lt;h3 id=&#34;Automation&#34;&gt;&lt;a href=&#34;#Automation&#34; class=&#34;headerlink&#34; title=&#34;Automation&#34;&gt;&lt;/a&gt;Automation&lt;/h3&gt;&lt;p&gt;The combination of {x}aaS and virtual machines both massively simplify or obscure a lot of the implementation work for deploying servers/services. &lt;/p&gt;
&lt;p&gt;Going back to bare-metal we suddenly  are back to a large amount of very old and very hard to automate pieces of work, which I’ve previously discussed here &lt;a href=&#34;https://thebsdbox.co.uk/2019/12/08/The-alchemy-of-turning-bare-metal-into-a-cloud/&#34;&gt;https://thebsdbox.co.uk/2019/12/08/The-alchemy-of-turning-bare-metal-into-a-cloud/&lt;/a&gt;. We will typically have to follow older installation methods to do the actual provisioning such as &lt;a href=&#34;https://en.wikipedia.org/wiki/Kickstart_(Linux&#34;&gt;kickstart&lt;/a&gt; or &lt;a href=&#34;https://en.wikipedia.org/wiki/Preseed&#34;&gt;preseed&lt;/a&gt; and &lt;strong&gt;only&lt;/strong&gt; after this can we look at automation such as &lt;a href=&#34;https://www.ansible.com/&#34;&gt;ansible&lt;/a&gt; to automate the deployment of Kubernetes. &lt;/p&gt;
&lt;h2 id=&#34;Architectural-choices&#34;&gt;&lt;a href=&#34;#Architectural-choices&#34; class=&#34;headerlink&#34; title=&#34;Architectural choices&#34;&gt;&lt;/a&gt;Architectural choices&lt;/h2&gt;&lt;p&gt;This section provides design decisions that need to be considered when deploying a highly-available Kubernetes cluster on bare-metal hardware and without cloud services. Using some of these design decisions can allow you to be both more efficient in the use of modern hardware and provide a lot of the same sorts of service that people come to expect from a cloud environment!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/cool_cloud.jpg&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;Nodes&#34;&gt;&lt;a href=&#34;#Nodes&#34; class=&#34;headerlink&#34; title=&#34;Nodes&#34;&gt;&lt;/a&gt;Nodes&lt;/h3&gt;&lt;p&gt;Node sizing typically falls into two categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Control-plane node(s)&lt;/strong&gt; scaled based upon how many workers will be managed, and how much additional API requests will be hitting the manager components&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Worker node(s)&lt;/strong&gt; scaled based upon the application requirements, often clusters will have more than one application running on them. So your capacity planning will need to ensure that all requirements are captured, along with sufficient multi-tenancy and node labelling where needed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’ve been back and forth through &lt;a href=&#34;https://kubernetes.io/&#34;&gt;https://kubernetes.io&lt;/a&gt;, looking for anything documents that explicitly say that a control-plane node should look like &lt;code&gt;&amp;#123;x&amp;#125;&lt;/code&gt; but I’ve drawn a blank. In the end the only sizing I can actually find is within the documentation for kubeadm, which states:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Supported OS (ubuntu/centos etc..)&lt;/li&gt;
&lt;li&gt;2 GB or more ram&lt;/li&gt;
&lt;li&gt;2 CPUs&lt;/li&gt;
&lt;li&gt;Connectivity&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Try finding a server with those specs without a time machine to the year 2000. In a number of engagements that I’ve been part of have consisted of server farms all built from a pre-chosen server specification. Recently in one engagement I came across 2u servers with 2x24 cores and 512GB of ram as their standard build, this meant that this relatively small Kubernetes HA control plane was underutilising:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;138&lt;/strong&gt; cores&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1530&lt;/strong&gt; GB of ram&lt;/li&gt;
&lt;li&gt;Power, cooling and support/maintenance …  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;/Images/physical_utilisation.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;What are the options for ring-fencing these control-plane nodes, to allow us to use the remaining capacity …&lt;/p&gt;
&lt;h4 id=&#34;Virtualise-control-plane-only&#34;&gt;&lt;a href=&#34;#Virtualise-control-plane-only&#34; class=&#34;headerlink&#34; title=&#34;Virtualise control-plane only&#34;&gt;&lt;/a&gt;Virtualise control-plane only&lt;/h4&gt;&lt;p&gt;This seems a bit like a cheat, but given the reasonably small requirements for the control-plane components it does make good sense to run the control-plane nodes as virtualised machines (with the adequate resources &lt;strong&gt;guaranteed&lt;/strong&gt;). Regardless of hypervisor or vmm (virtual machine manager) typically a small amount of overhead is required for the emulation of physical hardware along with minuscule performance overheads on I/O. However the benefit of freeing the remaining capacity to be used for other use-cases hugely outweighs any tiny performance or virtualisation inefficiencies. &lt;/p&gt;
&lt;h4 id=&#34;Kubernetes-resource-control&#34;&gt;&lt;a href=&#34;#Kubernetes-resource-control&#34; class=&#34;headerlink&#34; title=&#34;Kubernetes resource control&#34;&gt;&lt;/a&gt;Kubernetes resource control&lt;/h4&gt;&lt;p&gt;In a production environment the control-plane nodes should only be running the control-plane components. This means that anything application specific is &lt;strong&gt;only&lt;/strong&gt; ran on the worker nodes. The main reason that this is a recommendation or usually a best-practice is mainly down to a few key reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Security&lt;/strong&gt;: Applications strive to be secure, however we can help security by keeping the running workloads separate to the control-plane&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Management&lt;/strong&gt; and Operations: Keeping application workloads separate allows easier and clearer monitoring of both the application and the infrastructure (node behaviour)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stability&lt;/strong&gt;: Applications can be end-user facing and sometimes subject to Attacks or denials of service, keeping them separate will ensure that the control-plane is unaffected by these events.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are a number of options available to us that would allow some workloads to be safely ran next to the control-plane components. All of these would involve modifying the &lt;code&gt;kubelet&lt;/code&gt; configuration on the nodes that will be running the kubernetes management components, along with the manifests for the management pieces. &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;            **Would NOT recommend doing this in production**
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Kubelet Reserved CPU configuration&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(Optional, 1.17 required)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Modify &lt;code&gt;/var/lib/kubelet/config.yaml&lt;/code&gt; to ensure that a set of CPUs are reserved for things like the &lt;code&gt;kubelet&lt;/code&gt; and system daemons. &lt;/p&gt;
&lt;p&gt;In a 24 CPU system the below addition will pin the first four CPUs for system and kubernetes daemons.&lt;br&gt;&lt;code&gt;reservedSystemCPUs: 0,1,2,3&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;CPU Manager (1.10+ required))&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is discussed in more detail &lt;a href=&#34;https://kubernetes.io/blog/2018/07/24/feature-highlight-cpu-manager/&#34;&gt;here&lt;/a&gt; however, this setting when enabled allows the kubelet to have more “robust” control over how CPU utilisation is allocated to pods. To enable this modify &lt;code&gt;/var/lib/kubelet/config.yaml&lt;/code&gt; to ensure that the &lt;code&gt;cpuManagerPolicy: static&lt;/code&gt; exists or is modified from &lt;code&gt;none&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Control Plane components&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In order for these components to be secured by the CPU Manager we will need to modify their Spec so that they are given the &lt;code&gt;Guaranteed&lt;/code&gt; QoS class. We can find the manifests for the control-plane components under &lt;code&gt;/etc/kubernetes/manifests&lt;/code&gt; and with the above configuration enabled we can modify these manifests with configuration that will tie them to resources and ensure their stability.&lt;/p&gt;
&lt;p&gt;In order to give this QoS class we need to ensure that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Both the &lt;code&gt;requests&lt;/code&gt; and &lt;code&gt;limits&lt;/code&gt; match (or that &lt;code&gt;request&lt;/code&gt; is ommitted)&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;limits&lt;/code&gt; for &lt;code&gt;cpu&lt;/code&gt; are a whole number (not a fraction)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Modifying the &lt;code&gt;/etc/kubernetes/manifests/kube-apiserver.yaml&lt;/code&gt; to have the following resource section, will bind it to a &lt;code&gt;Guaranteed&lt;/code&gt; QoS class&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;resources:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  limits:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    memory: “500Mi”&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    cpu: “1”&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;We can see this by describing the pod:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;kubectl describe pods -n kube-system kube-apiserver-cp01&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;...&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Limits:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      cpu:     1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      memory:  500Mi&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Requests:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      cpu:        1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      memory:     500Mi&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;...&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;QoS Class:         Guaranteed&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;The remaining control-plane components will need modifying in the same manner in order to ensure that they all have &lt;code&gt;Guaranteed&lt;/code&gt; resource leaving the remaining capacity for other use-cases.&lt;/p&gt;
&lt;h4 id=&#34;Utilise-this-“freed”-capacity&#34;&gt;&lt;a href=&#34;#Utilise-this-“freed”-capacity&#34; class=&#34;headerlink&#34; title=&#34;Utilise this “freed” capacity&#34;&gt;&lt;/a&gt;Utilise this “freed” capacity&lt;/h4&gt;&lt;p&gt;With some level of protection around the control-plane components we can look into what could make sense to run on this same infrastructure. Both of the above examples should ring-fence resources around processing capacity &lt;strong&gt;CPU&lt;/strong&gt; and application &lt;strong&gt;memory&lt;/strong&gt;. However the control-plane can still be impacted by things like slow I/O, in the case that something else was thrashing the same underlying storage, we could end up in a position where the control-plane components fail or etcd nodes fail due to high latency. A simple solution for this would be to ensure that these two use-cases use different underlying storage, so that neither can impact each other. One other area is system bandwidth, if this additional capacity is used by applications with high bandwidth requirements then it could potentially effect the control-plane components. Again in this scenario consider additional network interfaces that ensure that traffic is completely segregated from the control-plane traffic. &lt;/p&gt;
&lt;p&gt;With the above in mind what other utilisation considerations could be taken into consideration?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use-case: Ideally keeping the use-cases aligned makes it easier from a security perspective as only the same teams would need to interact with the same node. &lt;/li&gt;
&lt;li&gt;Application load: &lt;strong&gt;No&lt;/strong&gt;/&lt;strong&gt;never&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example workloads that *could be considered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;Dashboard&lt;/li&gt;
&lt;li&gt;Ingress&lt;/li&gt;
&lt;li&gt;Load-Balancing&lt;/li&gt;
&lt;li&gt;Container registry (more for HA, if supported)&lt;/li&gt;
&lt;li&gt;Image scanning&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;All of the above is a fine balance between getting more utilisation from your bare-metal servers, and ensuring that additional workload is non-impacting to the control-plane whilst introducing minimal operating overhead or security issues. &lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;Networking-load-balancing&#34;&gt;&lt;a href=&#34;#Networking-load-balancing&#34; class=&#34;headerlink&#34; title=&#34;Networking (load-balancing)&#34;&gt;&lt;/a&gt;Networking (load-balancing)&lt;/h3&gt;&lt;p&gt;This section is limited to both the networking function of load-balancing and the control-plane for kubernetes. The load-balancing for applications and services that are running within a Kubernetes cluster can be hosted elsewhere and usually is more application focused. &lt;/p&gt;
&lt;p&gt;In the event that hardware appliances such as F5s (docs are &lt;a href=&#34;https://clouddocs.f5.com/containers/v2/kubernetes/&#34;&gt;here&lt;/a&gt;) are present then follow the vendor documentation for deploying that particular solution. However in the event we need to roll our own, then we will discuss the architecture decisions and options in this section. &lt;/p&gt;
&lt;p&gt;As mentioned above, we require two components for this to be a completely HA and resilient solution:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Virtual IP&lt;/strong&gt;, ensuring that an active load balance can always be addressed and accessed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Load-Balancer instance&lt;/strong&gt;, ensuring traffic is load balanced between Control-plane nodes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The two software solutions for this that we will be using are &lt;a href=&#34;https://www.keepalived.org/&#34;&gt;keepalived&lt;/a&gt; for the Virtual IP address and &lt;a href=&#34;http://www.haproxy.org/&#34;&gt;HAProxy&lt;/a&gt; for the load-balancing.&lt;/p&gt;
&lt;p&gt;Then finally there are two architectures that we will discuss that can be considered in order to provide a HA design:&lt;/p&gt;
&lt;h4 id=&#34;External-to-the-control-plane-Load-Balancer&#34;&gt;&lt;a href=&#34;#External-to-the-control-plane-Load-Balancer&#34; class=&#34;headerlink&#34; title=&#34;External (to the control-plane) Load-Balancer&#34;&gt;&lt;/a&gt;External (to the control-plane) Load-Balancer&lt;/h4&gt;&lt;p&gt;As with the discussion of stacked vs unstacked control plane nodes (&lt;code&gt;etcd&lt;/code&gt; on the same nodes), we also have the architectural decision of co-locating the load-balancing components on the same nodes. This first architecture will utilise two systems external to the Kubernetes nodes to create an external load-balancer pair, that under most circumstances would in a similar manner to a load-balancing appliance.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/external_bm_loadbalancer.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Load-Balancer is physically separate from Kubernetes Control pane nodes&lt;/li&gt;
&lt;li&gt;Only requires two instances/deployments of the required components&lt;/li&gt;
&lt;li&gt;Easy to scale as not tightly coupled to the control plane nodes&lt;/li&gt;
&lt;li&gt;Opportunity for Kubernetes services running in the cluster or other hosts to be load-balanced by the same external load-balancer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Requires it’s own hardware (or virtual hardware) &lt;/li&gt;
&lt;li&gt;Additional management and operational overhead&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;Stacked-on-the-control-plane-node-s-Load-Balancer&#34;&gt;&lt;a href=&#34;#Stacked-on-the-control-plane-node-s-Load-Balancer&#34; class=&#34;headerlink&#34; title=&#34;Stacked (on the control-plane node(s)) Load-Balancer&#34;&gt;&lt;/a&gt;Stacked (on the control-plane node(s)) Load-Balancer&lt;/h4&gt;&lt;p&gt;The opposite the above architecture would be locate the load-balancer components locally to the Kubernetes control plane components. Simplifying the architecture, but creating it’s own architectural challenges.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/stacked_bm_loadbalancer.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Simplifies deployment, control plane nodes are always deployed with all of the required components&lt;/li&gt;
&lt;li&gt;Scaling becomes simplified, going from 3 -&amp;gt; 5 control plane nodes involves the deployments of 2 more control plane nodes and adding into load balancer/VIP configuration&lt;/li&gt;
&lt;li&gt;Reduces the infrastructure requirement as load-balancing shares the same infrastructure as the control plane components&lt;/li&gt;
&lt;li&gt;Possibility of one-day having &lt;code&gt;kubeadm&lt;/code&gt; manage the load-balancing lifecycle in the same way that it now manages &lt;code&gt;etcd&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tight coupling, can result in hard to debug networking, performance or configuration issues.&lt;/li&gt;
&lt;li&gt;An issue with either of the load-balancing components could have a knock-on effect to the control pane nodes without proper precautions in place. &lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;Stacked-Port-conflicts&#34;&gt;&lt;a href=&#34;#Stacked-Port-conflicts&#34; class=&#34;headerlink&#34; title=&#34;Stacked Port conflicts&#34;&gt;&lt;/a&gt;Stacked Port conflicts&lt;/h4&gt;&lt;p&gt;Another thing that can appear confusing with co-locating the load-balancers on the same node(s) as the Kubernetes API control plane components are the port configurations. We can’t have two applications listening on the same port, which means if we try to configuration the load-balancer to expose its services on the standard Kubernetes API-server port (6443) then we can’t also have the api-server trying to use the same port. &lt;/p&gt;
&lt;p&gt;The architecture diagram below depicts the load-balancer binding to port 6443 and sending requests to one of the API-Servers listening on port 6444:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/Images/stacked_bm_loadbalancer_port.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;In order to allow the API-server to behave as expected we expose the load-balanced API-servers through the standard port (6443) and configure all of the API-servers to bind to the port (6444). This means that there are no port conflicts, but can cause confusion when trying to debug issues e.g. if the load-balancer isn’t behaving as expected or is down then to connect to a specific Kubernetes API-server we need to remember to connect to &lt;code&gt;controlplane0&amp;#123;X&amp;#125;:6444&lt;/code&gt;. Finally when using &lt;code&gt;kubeadm&lt;/code&gt; to perform the installation we need to do two additional things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use the VIP and load-balancer port 6443 &lt;code&gt; --control-plane-endpoint “$VIP_ADDRESS:6443”&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Ensure that the actual Kubernetes API-server binds to port 6444 &lt;code&gt;--apiserver-bind-port 6444&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;--apiserver-bind-port&lt;/code&gt; is required for initialising the first control plane and joining of additional control plane nodes.&lt;/p&gt;
&lt;h4 id=&#34;The-outlier-worker-architecture&#34;&gt;&lt;a href=&#34;#The-outlier-worker-architecture&#34; class=&#34;headerlink&#34; title=&#34;The outlier (worker) architecture&#34;&gt;&lt;/a&gt;The outlier (worker) architecture&lt;/h4&gt;&lt;p&gt;It could be possible to have the load-balancing components deployed first on identified worker nodes, which would free up any risk of the either workload causing performance issues or outages. However in this scenario we break the concept of workers being something we care about ( &lt;del&gt;cattle vs pets&lt;/del&gt; , whatever the analogy is for not having to worry too much about the infra). If workers are suddenly &lt;strong&gt;tied&lt;/strong&gt; to particular workloads that we need to deeply worry about, then we’re in a position where we’ve broken the model to easily replace, scale and destroy our worker pools.&lt;/p&gt;
&lt;p&gt;The other issue is that we would need the workers in place &lt;strong&gt;before&lt;/strong&gt; we can actually deploy our control plane, which introduces more chicken and egg architectural design choices.&lt;/p&gt;
&lt;p&gt;With all this in mind, lets look at how we actually would get this deployed …&lt;/p&gt;
&lt;h2 id=&#34;Getting-Kubernetes-deployed-on-Bare-Metal&#34;&gt;&lt;a href=&#34;#Getting-Kubernetes-deployed-on-Bare-Metal&#34; class=&#34;headerlink&#34; title=&#34;Getting Kubernetes deployed on Bare-Metal&#34;&gt;&lt;/a&gt;Getting Kubernetes deployed on Bare-Metal&lt;/h2&gt;&lt;p&gt;This section will detail the majority of steps that are required to deploy the load-balancing and the Control-plane components of a cluster in a typical bare-metal environment. Worker nodes aren’t covered, but as this deployment will be making use of &lt;code&gt;kubeadm&lt;/code&gt; then the workers will be managed through the &lt;code&gt;kubeadm join&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;For workloads that target specific nodes (e.g. nodes with lower core higher frequency for single thread or GPU(s)) then we should label the nodes and use a &lt;code&gt;nodeSelector&lt;/code&gt; in the &lt;code&gt;spec&lt;/code&gt; as detailed &lt;a href=&#34;https://kubernetes.io/docs/concepts/configuration/assign-pod-node/&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, these steps are for &lt;strong&gt;Ubuntu&lt;/strong&gt;.. If you’re deploying on some-other distro the steps or package names shouldn’t be too different in order to install and configure in the same manner.&lt;/p&gt;
&lt;h3 id=&#34;The-Infrastructure&#34;&gt;&lt;a href=&#34;#The-Infrastructure&#34; class=&#34;headerlink&#34; title=&#34;The Infrastructure&#34;&gt;&lt;/a&gt;The Infrastructure&lt;/h3&gt;&lt;p&gt;In this deployment, we will be deploying a &lt;strong&gt;stacked&lt;/strong&gt; architecture which means that the control plane nodes will have everything installed on each node:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kubernetes Control plane components&lt;/li&gt;
&lt;li&gt;etcd&lt;/li&gt;
&lt;li&gt;Load-Balancer components&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The nodes will consist of three Ubuntu 16.04 or 18.04 standard installations with a user that has Sudo installed.. How that OS gets there, for remote installations I can highly recommend &lt;a href=&#34;https://plndr.io/&#34;&gt;plunder&lt;/a&gt; (“Shameless plug”). With the bare-metal nodes up, Operating Systems installed and permanent network addresses given we need to consider one final step (due to the VIP), the network addressing for our cluster.&lt;/p&gt;
&lt;p&gt;In our deployment we’ll manage this address scheme quite simply, with the first address for the VIP and all subsequent addresses following in parallel.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Example IP address table&lt;/em&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Node&lt;/th&gt;
&lt;th&gt;Address&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;VIP&lt;/td&gt;
&lt;td&gt;10.0.0.100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;controlPlane01&lt;/td&gt;
&lt;td&gt;10.0.0.101&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;controlPlane02&lt;/td&gt;
&lt;td&gt;10.0.0.102&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;controlPlane03&lt;/td&gt;
&lt;td&gt;10.0.0.103&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&#34;The-Kubernetes-install-one-liner&#34;&gt;&lt;a href=&#34;#The-Kubernetes-install-one-liner&#34; class=&#34;headerlink&#34; title=&#34;The Kubernetes install one-liner&#34;&gt;&lt;/a&gt;The Kubernetes install one-liner&lt;/h3&gt;&lt;p&gt;With our infrastructure in place, we can now begin the installation of Kubernetes! The installation will be following the steps from &lt;a href=&#34;https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/high-availability/&#34;&gt;https://kubernetes.io&lt;/a&gt; using &lt;code&gt;kubeadm&lt;/code&gt;. However a much more condensed version is below, I like to refer to it as a one-liner as it is &lt;em&gt;technically&lt;/em&gt; a one-liner(ish)…&lt;/p&gt;
&lt;p&gt;Once the version is set then the next step will update Ubuntu, update repositories and install all of the components that we need to install a HA Kubernetes cluster.&lt;/p&gt;
&lt;h4 id=&#34;Set-the-version&#34;&gt;&lt;a href=&#34;#Set-the-version&#34; class=&#34;headerlink&#34; title=&#34;Set the version&#34;&gt;&lt;/a&gt;Set the version&lt;/h4&gt;&lt;p&gt;This environment variable is used to determine the version of Kubernetes that will be installed by the below one-liner. &lt;/p&gt;
&lt;p&gt;&lt;code&gt;export kVersion=&amp;quot;1.17.0-00&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;h4 id=&#34;Install-all-the-packages&#34;&gt;&lt;a href=&#34;#Install-all-the-packages&#34; class=&#34;headerlink&#34; title=&#34;Install all the packages&#34;&gt;&lt;/a&gt;Install all the packages&lt;/h4&gt;&lt;p&gt;This will install “everything” needed (on ubuntu) for a control-plane node with load-balancing and high-availability.&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;sudo -E sh -c &amp;#x27;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;apt-get update; \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;apt-get install curl apt-transport-https gnupg-agent ca-certificates software-properties-common -y; \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;echo &amp;quot;deb https://download.docker.com/linux/ubuntu xenial stable&amp;quot; &amp;gt;&amp;gt; /etc/apt/sources.list.d/docker.list; \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;echo &amp;quot;deb https://apt.kubernetes.io/ kubernetes-xenial main&amp;quot; &amp;gt;&amp;gt; /etc/apt/sources.list.d/kubernetes.list; \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - ; \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - ; \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;apt-get update; \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;apt-get install -y ethtool socat ebtables conntrack libnetfilter-conntrack3 docker-ce=18.06.1~ce~3-0~ubuntu kubelet=$kVersion kubeadm=$kVersion kubectl=$kVersion kubernetes-cni cri-tools haproxy keepalived; \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;systemctl enable kubelet.service&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#x27; &lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Once the above is complete the system is ready to be configured, however ensure that this is repeated on all of the control plane nodes. &lt;/p&gt;
&lt;h3 id=&#34;Load-balancing-the-load-balancing&#34;&gt;&lt;a href=&#34;#Load-balancing-the-load-balancing&#34; class=&#34;headerlink&#34; title=&#34;Load-balancing the load-balancing&#34;&gt;&lt;/a&gt;Load-balancing the load-balancing&lt;/h3&gt;&lt;h4 id=&#34;Virtual-IP-KeepaliveD-configuration&#34;&gt;&lt;a href=&#34;#Virtual-IP-KeepaliveD-configuration&#34; class=&#34;headerlink&#34; title=&#34;Virtual IP / KeepaliveD configuration&#34;&gt;&lt;/a&gt;Virtual IP / KeepaliveD configuration&lt;/h4&gt;&lt;p&gt;Below are the important sections for the &lt;code&gt;/etc/keepalived/keepalived.conf&lt;/code&gt; that will be required on each of the nodes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Global Definition&lt;/strong&gt; of the &lt;code&gt;keepalived&lt;/code&gt; configuration&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;global_defs &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   # Name of VIP Instance&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   router_id KubernetesVIP&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   # Enable SNMP Monitoring (Optional)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   # enable_traps&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Virtual Service Definition&lt;/strong&gt; (this is where we define the configuration of our VIP)&lt;/p&gt;
&lt;p&gt;As mentioned in the comments on the example configuration below, typically the first node &lt;code&gt;01&lt;/code&gt; we need to set the &lt;code&gt;state&lt;/code&gt; to &lt;code&gt;MASTER&lt;/code&gt;. The means that on startup that this node will be the node be allocated the VIP first. The &lt;code&gt;priority&lt;/code&gt; number is used during the keepalived cluster elections to determine who will become the next &lt;code&gt;MASTER&lt;/code&gt; and the highest priority wins.&lt;/p&gt;
&lt;p&gt;For further details on these configurations the keepalived documentation can be found &lt;a href=&#34;https://www.keepalived.org/manpage.html&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;39&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;vrrp_instance APIServerVIP &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    # Interface to bind to&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    interface ens192&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    # This should be set to MASTER on the first node and BACKUP on the other two&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    state MASTER&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    # This should be 50+ lower on the other two nodes to enable the lead election&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    priority 100&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    # Address of this particular node&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    mcast_src_ip $node_IP&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    # A unique ID if more than one service is being defined&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    virtual_router_id 61&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    advert_int 1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    nopreempt&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    # Authentication for keepalived to speak with one another&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    authentication &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          auth_type PASS&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          auth_pass $bloody_secure_password&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    # Other Nodes in Cluster&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    unicast_peer &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        $other_node_IP&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        $other_node_IP&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    # Kubernetes Virtual IP&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    virtual_ipaddress &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        10.0.0.100/24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    # Health check function (optional)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    #track_script &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    #    APIServerProbe&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    #&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Health check&lt;/strong&gt; (optional)&lt;/p&gt;
&lt;p&gt;This can be used to determine if the Kubernetes API server is up and running, if not fail the VIP to another node, but with the load-balancer performing the same task it’s not a requirement.&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;vrrp_script APIServerProbe &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    # Health check the Kubernetes API Server&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    script &amp;quot;curl -k https://$node_IP:6443&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    interval 3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    timeout 9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    fall 2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    rise 2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h4 id=&#34;Control-plane-load-balancing-HAProxy-configuration&#34;&gt;&lt;a href=&#34;#Control-plane-load-balancing-HAProxy-configuration&#34; class=&#34;headerlink&#34; title=&#34;Control-plane load-balancing / HAProxy configuration&#34;&gt;&lt;/a&gt;Control-plane load-balancing / HAProxy configuration&lt;/h4&gt;&lt;p&gt;Below are the additions to the &lt;code&gt;/etc/haproxy/haproxy.conf&lt;/code&gt; that will be there by default, ensure you back up the original before modifying and then append the configuration below. As mentioned &lt;a href=&#34;#Stacked-Port-conflicts&#34;&gt;here&lt;/a&gt; we need to remember that the frontend will expose itself on port &lt;code&gt;6443&lt;/code&gt;, and it will load-balance to the kubernetes API-servers listening on port &lt;code&gt;6444&lt;/code&gt;.&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;frontend http_stats&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   bind *:8080&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   mode http&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   stats uri /haproxy?stats&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;frontend kube_api_server&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    bind 0.0.0.0:6443&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    mode tcp&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    option tcplog&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    timeout client  10800s&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    default_backend controlPlanes&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;backend controlPlanes&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    mode tcp&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    option tcplog&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    balance leastconn&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    timeout server  10800s&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    server controlPlane01 10.0.0.101:6444 check&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    server controlPlane02 10.0.0.102:6444 check&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    server controlPlane03 10.0.0.103:6444 check&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&#34;Install-Kubernetes-Control-Plane&#34;&gt;&lt;a href=&#34;#Install-Kubernetes-Control-Plane&#34; class=&#34;headerlink&#34; title=&#34;Install Kubernetes Control-Plane&#34;&gt;&lt;/a&gt;Install Kubernetes Control-Plane&lt;/h3&gt;&lt;p&gt;With the VIP and load-balancer up and running, we can now install the kubernetes control plane components in a HA configuration using &lt;code&gt;kubeadm&lt;/code&gt;. &lt;/p&gt;
&lt;h4 id=&#34;First-node&#34;&gt;&lt;a href=&#34;#First-node&#34; class=&#34;headerlink&#34; title=&#34;First node&#34;&gt;&lt;/a&gt;First node&lt;/h4&gt;&lt;p&gt;On the first node we will initialise our Kubernetes cluster using the &lt;code&gt;kubeadm init&lt;/code&gt; command with these additional flags.&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;sudo kubeadm init \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;--control-plane-endpoint &amp;quot;10.0.0.100:6443&amp;quot; \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;--apiserver-bind-port 6444 \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;--upload-certs&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;If we wanted to use the flags mentioned above in the section &lt;a href=&#34;#Kubernetes-resource-control&#34;&gt;Kubernetes resource control&lt;/a&gt; then we can use &lt;code&gt;kubeadm&lt;/code&gt; to print out all of the configuration yaml and we can edit the sections that are identified using the &lt;code&gt;kind:&lt;/code&gt; key.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step one:&lt;/strong&gt; Print configuration and save configuration&lt;/p&gt;
&lt;p&gt;&lt;code&gt;kubeadm config  print init-defaults --component-configs KubeletConfiguration &amp;gt; config.yaml&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step two:&lt;/strong&gt; Edit &lt;code&gt;InitConfiguration&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;We need to remove the &lt;code&gt;advertiseAddress&lt;/code&gt; as it defaults to a ridiculous default (not sure why), and edit the &lt;code&gt;bindPort&lt;/code&gt; to &lt;code&gt;6444&lt;/code&gt; as this is what the API-Server needs to listen on in order to not conflict with the load-balancer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step three:&lt;/strong&gt; Edit &lt;code&gt;ClusterConfiguration&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;We need to add the line &lt;code&gt;controlPlaneEndpoint: &amp;quot;LOAD_BALANCER_DNS:LOAD_BALANCER_PORT&amp;quot;&lt;/code&gt; to this section so that it uses our VIP address and uses the port of the load balancer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step four:&lt;/strong&gt; Edit &lt;code&gt;KubeletConfiguration&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If we want to use the &lt;strong&gt;ReservedCPU&lt;/strong&gt; functionality then add the line: &lt;code&gt;reservedSystemCPUs: 0,1&lt;/code&gt; (edit for the number of CPUs to save). &lt;/p&gt;
&lt;p&gt;If we want to have &lt;strong&gt;Guaranteed QOS&lt;/strong&gt; for our Pods then we can add the line: &lt;code&gt;cpuManagerPolicy: static&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We can then apply our configuration with the command:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;sudo kubeadm init  --config ./cluster.yaml &lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;This will initialise our control plane on the first node and print out the subsequent join command for other control plane nodes:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;  kubeadm join 100.0.0.100:6443 \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;--token fg55b4.o5n57eyna50627dc \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;--discovery-token-ca-cert-hash sha256:&amp;lt;massive hash&amp;gt;  \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;--control-plane --certificate-key &amp;lt;massive key&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h4 id=&#34;Remaining-Nodes&#34;&gt;&lt;a href=&#34;#Remaining-Nodes&#34; class=&#34;headerlink&#34; title=&#34;Remaining Nodes&#34;&gt;&lt;/a&gt;Remaining Nodes&lt;/h4&gt;&lt;p&gt;When joining the remaining nodes to the cluster, ensure that you add the &lt;code&gt;--apiserver-bind-port 6444&lt;/code&gt; to ensure that the Api-server binds on a different port to the load balancer sitting above it.&lt;/p&gt;
&lt;p&gt;With the VIP and load-balancers up and running, along with our cluster initialised we can now add in our additional nodes and build out a HA/Load-balancer Kubernetes cluster.&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;$ kubectl get nodes&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;NAME             STATUS   ROLES    AGE     VERSION&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;controlplane01   Ready    master   2m33s   v1.17.0&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;controlplane02   Ready    master   108s    v1.17.0&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;controlplane03   Ready    master   56s     v1.17.0&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;To test the cluster, we can stop and start the VIP with &lt;code&gt;sudo systemctl stop keepalived&lt;/code&gt; and ensure that &lt;code&gt;kubectl get nodes&lt;/code&gt; continues to act as expected. Rebooting of nodes will also create the same experiences as having node failures. We should be able to see logs showing that &lt;code&gt;keepalived&lt;/code&gt; is moving our VIP to working nodes and ensuring that access always remains into the running cluster.&lt;/p&gt;
&lt;h3 id=&#34;Considerations-for-an-unstacked-load-balancer&#34;&gt;&lt;a href=&#34;#Considerations-for-an-unstacked-load-balancer&#34; class=&#34;headerlink&#34; title=&#34;Considerations for an unstacked load-balancer&#34;&gt;&lt;/a&gt;Considerations for an unstacked load-balancer&lt;/h3&gt;&lt;p&gt;The above guide details all of the steps required to build a HA Kubernetes cluster that has the load-balancing components co-located on the same nodes as the Kubernetes components. If we wanted to build an external or unstacked load-balancing pair of nodes then the process is very similar and covered in brief below.&lt;/p&gt;
&lt;p&gt;On both of the load-balancing nodes (&lt;code&gt;loadBalancer01/02&lt;/code&gt;) we will need to install the components for the load-balancing:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;apt-get install -y haproxy keepalived &lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;With these components installed on both nodes we can now configure them in the exact same way as before except. The two load-balancing nodes will need the VIP and HAProxy configured in an almost identical manner, however the only consideration is the ports of the Kubernetes API server. As the load-balancer and the Kubernetes API-Server will be on different nodes they won’t have to deal with a port conflict, this means that we can leave the API-Server listening on power 6443 on the &lt;code&gt;controlPlane01/02/03&lt;/code&gt; nodes.&lt;/p&gt;
&lt;h3 id=&#34;Using-Nginx-instead-of-HAProxy&#34;&gt;&lt;a href=&#34;#Using-Nginx-instead-of-HAProxy&#34; class=&#34;headerlink&#34; title=&#34;Using Nginx instead of HAProxy&#34;&gt;&lt;/a&gt;Using Nginx instead of HAProxy&lt;/h3&gt;&lt;p&gt;The above examples all use HAProxy as it’s been my default, however Nginx can also be used as a load-balancer that sits between the VIP and the Kubernetes API-server. Below is an &lt;strong&gt;identical&lt;/strong&gt; configuration to the HAProxy configuration above that can be appended to &lt;code&gt;/etc/nginx/nginx.conf&lt;/code&gt;:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;events &amp;#123; &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;stream &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    upstream apiServer_backend &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        least_conn;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        # controlPlane01&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        server 100.0.0.101:6444;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        # controlPlane02&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        server 100.0.0.102:6444;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        # controlPlane03&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        server 100.0.0.103:6444;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;       &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    server &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        listen        6443;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        proxy_pass    apiServer_backend;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        proxy_timeout 3s;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        proxy_connect_timeout 1s;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;       &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The above is a stacked example that has the load-balancer binding to port &lt;code&gt;6443&lt;/code&gt; and using the Kubernetes API-servers that are bound to port &lt;code&gt;6444&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;Feedback&#34;&gt;&lt;a href=&#34;#Feedback&#34; class=&#34;headerlink&#34; title=&#34;Feedback&#34;&gt;&lt;/a&gt;Feedback&lt;/h2&gt;&lt;p&gt;Either write something in the comments below or hit me up at @thebsdbox on twitter :-)&lt;/p&gt;
 ]]></description>
        </item>
    </channel>
</rss>
