<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Astobbe]]></title><description><![CDATA[Aspiring software craftsman, writer, personal development nerd. Engineering @ EdgelessSystems]]></description><link>https://blog.adrianstobbe.com</link><generator>RSS for Node</generator><lastBuildDate>Mon, 14 Sep 2026 20:30:31 GMT</lastBuildDate><atom:link href="https://blog.adrianstobbe.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[The potential of pod migrations in Kubernetes]]></title><description><![CDATA[Kubernetes is not only used for stateless applications, but also in High Performance Computing (HPC), Machine Learning, and other areas to run long-running, stateful containers. When a failure occurs, Kubernetes has no way to rescue it. Their state i...]]></description><link>https://blog.adrianstobbe.com/the-potential-of-pod-migrations-in-kubernetes</link><guid isPermaLink="true">https://blog.adrianstobbe.com/the-potential-of-pod-migrations-in-kubernetes</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[containers]]></category><dc:creator><![CDATA[Adrian Stobbe]]></dc:creator><pubDate>Wed, 30 Nov 2022 20:25:03 GMT</pubDate><content:encoded><![CDATA[<p>Kubernetes is not only used for stateless applications, but also in High Performance Computing (HPC), Machine Learning, and other areas to run long-running, stateful containers. When a failure occurs, Kubernetes has no way to rescue it. Their state is often not persisted on disk, so stateful sets can't help either. Likewise, there is no way to optimize cluster resource utilization through rescheduling these workloads (see the <a target="_blank" href="https://github.com/kubernetes-sigs/descheduler">descheduler</a> project). This is especially interesting for workloads with unknown resource requirements, but resource over provisioning is a wide-spread phenomenon in the industry. Experts estimate the medium utilization between 6% to 12% <a target="_blank" href="https://www.csl.cornell.edu/~delimitrou/papers/2014.asplos.quasar.pdf">1</a>. Fortunately, initial investigations suggest that container migrations are feasible with reasonable downtime and potentially bring immense increases in cluster resource efficiency.</p>
<h3 id="heading-how-to-migrate-a-pod">How to migrate a pod?</h3>
<p>Before the advent of containers, VM migrations were a well established solution to move application state across machines. Luckily, the <a target="_blank" href="https://criu.org/Main_Page">CRIU</a> project has made it possible to checkpoint and restore containers.
In the past months, I set up a Kubernetes cluster with pod migration support based on prior work, exploring the migration performance through high-speed file servers on Azure and investigated the feasibility and potential for a commercial HPC service run on Kubernetes. You can find a tutorial for the cluster setup <a target="_blank" href="https://astobbe.me/posts/pod-migration/">here</a> and a short pod migration demo <a target="_blank" href="https://www.youtube.com/watch?v=IPY852th_T0&amp;ab_channel=AdrianStobbe">here</a>.</p>
<h3 id="heading-case-study-results-and-the-potential-of-migration">Case study results and the potential of migration</h3>
<p>The research findings show that the migration time is mainly influenced by memory load and just takes a few seconds for containers with a few gigabytes of memory usage and still less than 2 minutes for 50 GB. 
A <a target="_blank" href="https://github.com/elchead/k8s-migration-controller">migration controller</a> was used to observe node memory consumption and preemptively migrate workloads upon imminent resource shortage. Different intervention heuristics were investigated, and the best performing one used a memory threshold to trigger migration. By selecting the pods with the steepest memory slope for migration, the heuristic could prevent forceful pod evictions due to resource over-commitment in most cases. The analysis was performed with production cluster data on a <a target="_blank" href="https://github.com/elchead/k8s-cluster-simulator">Kubernetes scheduler simulator</a>.
For the investigated HPC service, the simulations showed that cluster utilization could be improved by a factor of up to 4 times compared to status quo production scenarios.
You can see the migration controller in action here:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/t-TGMeJhXWw"></iframe>

<h3 id="heading-the-status-quo-and-conclusion">The status quo and conclusion</h3>
<p>Research for stateful pod migration on Kubernetes is rare, but prior proof of concepts have shown the practical feasibility. However, they did not evaluate its applicability or benefits for real-world problems.
But there is demand for such a feature<a target="_blank" href="https://ieeexplore.ieee.org/abstract/document/9646259">3</a>,<a target="_blank" href="https://github.com/kubernetes/enhancements/pull/1990#issuecomment-1178741679">4</a> and just recently, a checkpointing solution (without restore) was officially integrated in Kubernetes v1.24. Container migration is a great option to migrate stateful pods without application knowledge. It's a workload agnostic approach, whose downtime is fairly predictable through the memory load of the container. While some applications might have features to persist state to disk, this does not apply to all, and when available the effect of migration (e.g. downtime, checkpointable state) is often not predictable. The hurdle of the technical setup is still the biggest one to overcome, but there exists an easier-to-use project called KubeVirt that allows running a VM inside a pod, which can be <a target="_blank" href="https://kubevirt.io//2020/Live-migration.html">live migrated</a> through a Kubernetes API extension object. </p>
<p>If you have a use-case for pod migration or want to know more about my findings, please reach out!
I would like to make stateful pod migration more accessible!</p>
]]></content:encoded></item><item><title><![CDATA[Calico config for kubeadm cluster on Azure]]></title><description><![CDATA[Contents
Recently, I tried to configure calico networking on a self-managed Kubernetes cluster on Azure. It did not work out of the box and many instructions on the internet did not work for me. In the following, I want to share my setup. To set up t...]]></description><link>https://blog.adrianstobbe.com/calico-config-for-kubeadm-cluster-on-azure-1</link><guid isPermaLink="true">https://blog.adrianstobbe.com/calico-config-for-kubeadm-cluster-on-azure-1</guid><dc:creator><![CDATA[Adrian Stobbe]]></dc:creator><pubDate>Thu, 21 Apr 2022 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Contents</p>
<p>Recently, I tried to configure calico networking on a self-managed Kubernetes cluster on Azure. It did not work out of the box and many instructions on the internet did not work for me. In the following, I want to share my setup. To set up the network and VMs, I followed <a target="_blank" href="https://blog.nillsf.com/index.php/2021/10/29/setting-up-kubernetes-on-azure-using-kubeadm/">this tutorial</a>. After installing the <a target="_blank" href="https://projectcalico.docs.tigera.io/getting-started/kubernetes/self-managed-onprem/onpremises">default configuration of calico</a>, inter-node communication between pods did not work.</p>
<p>My working approach uses User-Defined-Routes (UDR) on Azure to route traffic from the different pod-subnets of each node. Be sure to only have one IP address assigned to each network interface! I installed the Azure CNI plugin before, which assigns pod IPs from the secondary IPs of the vnet that are assigned to the network interface. This caused problems in the IP detection in Calico, but it can be easily fixed by deleting the secondary IPs.</p>
<p>When bootstrapping Kubernetes, you should also set the pod subnet (CIDR) to avoid address overlap with the virtual network. The default for kubeadm is <code>192.168.0.0/16</code> which indeed overlapped with my Azure network.</p>
<p>This is my kubeadm config:</p>
<pre><code> <span class="hljs-number">1</span>
 <span class="hljs-number">2</span>
 <span class="hljs-number">3</span>
 <span class="hljs-number">4</span>
 <span class="hljs-number">5</span>
 <span class="hljs-number">6</span>
 <span class="hljs-number">7</span>
 <span class="hljs-number">8</span>
 <span class="hljs-number">9</span>
<span class="hljs-number">10</span>
<span class="hljs-number">11</span>
<span class="hljs-number">12</span>
<span class="hljs-number">13</span>


<span class="hljs-string">cat</span> <span class="hljs-string">conf.yaml</span>
<span class="hljs-attr">apiServer:</span>
  <span class="hljs-attr">timeoutForControlPlane:</span> <span class="hljs-string">4m0s</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">kubeadm.k8s.io/v1beta2</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">ClusterConfiguration</span>
<span class="hljs-attr">kubernetesVersion:</span> <span class="hljs-string">v1.19.16</span> <span class="hljs-comment">#-beta.0.1017_d0acb1e3ae68d3-dirty</span>
<span class="hljs-attr">networking:</span>
  <span class="hljs-attr">podSubnet:</span> <span class="hljs-string">"172.16.0.0/16"</span>
<span class="hljs-meta">---</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">kubeadm.k8s.io/v1beta2</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">InitConfiguration</span>
<span class="hljs-attr">nodeRegistration:</span>
  <span class="hljs-attr">criSocket:</span> <span class="hljs-string">"/run/containerd/containerd.sock"</span>
</code></pre><p>To bootstrap the cluster, execute: <code>./kubeadm init --upload-certs --cri-socket "/run/containerd/containerd.sock" --config conf.yaml</code></p>
<p>Then, get the calico setup from <a target="_blank" href="https://projectcalico.docs.tigera.io/getting-started/kubernetes/self-managed-onprem/onpremises">here</a>.</p>
<p>In the yaml set the pod CIDR:</p>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>


<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">CALICO_IPV4POOL_CIDR</span>
  <span class="hljs-attr">value:</span> <span class="hljs-string">"172.16.0.0/16"</span> 
</code></pre><p>To configure the inter-node pod communication, I followed these steps <a target="_blank" href="https://stackoverflow.com/a/67242381/10531075">here</a>.</p>
<p>Before applying I recommend to set up VXLAN instead of ipip mode (see below). By default, calico uses ipip tunneling for pod communication. A tunnel is a virtual network interface to connect subnets. This did not work in my Azure network. Instead, I configured VXLAN.</p>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>
<span class="hljs-number">3</span>
<span class="hljs-number">4</span>
<span class="hljs-number">5</span>
<span class="hljs-number">6</span>
<span class="hljs-number">7</span>
<span class="hljs-number">8</span>
<span class="hljs-number">9</span>


<span class="hljs-string">cat</span> <span class="hljs-string">vxlan.yaml</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">crd.projectcalico.org/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">IPPool</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">ippool-vxlan-1</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">cidr:</span> <span class="hljs-number">172.16</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/16</span>
  <span class="hljs-attr">vxlanMode:</span> <span class="hljs-string">Always</span>
  <span class="hljs-attr">natOutgoing:</span> <span class="hljs-literal">true</span>
</code></pre><p>Traffic to the pod network (outside the node) should then not be routed to <code>tunl0</code> anymore:</p>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>
<span class="hljs-number">3</span>


<span class="hljs-string">ip</span> <span class="hljs-string">a</span>
<span class="hljs-attr">4:</span> <span class="hljs-string">tunl0@NONE:</span> <span class="hljs-string">&lt;NOARP,UP,LOWER_UP&gt;</span> <span class="hljs-string">mtu</span> <span class="hljs-number">1480 </span><span class="hljs-string">qdisc</span> <span class="hljs-string">noqueue</span> <span class="hljs-string">state</span> <span class="hljs-string">UNKNOWN</span> <span class="hljs-string">group</span> <span class="hljs-string">default</span> <span class="hljs-string">qlen</span> <span class="hljs-number">1000</span>
    <span class="hljs-string">link/ipip</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span> <span class="hljs-string">brd</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>
</code></pre><p>Install <code>calioctl</code> and verify that there is only one ippool: <code>calicoctl get ippools</code></p>
<p>If not, delete the other pool: <code>calicoctl delete pool default-ipv4-ippool</code></p>
<p>You need to reboot, to force a reassignment of the pod IPs to the new network.</p>
<p>Then doublecheck that the the CIDR did not change:</p>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>


kubectl get ipamblocks.crd.projectcalico.org \
<span class="hljs-operator">-</span>o jsonpath<span class="hljs-operator">=</span><span class="hljs-string">"{range .items[*]}{'podNetwork: '}{.spec.cidr}{'\t NodeIP: '}{.spec.affinity}{'\n'}"</span>
</code></pre><p>Otherwise, you need to update the Azure routing table!</p>
<p>If you want to dig deeper into the different networking options in calico for Azure, I recommend this video:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/JyLtg_SJ1lo"></iframe>]]></content:encoded></item><item><title><![CDATA[[Tutorial]: Bootsrapping your custom Kubernetes with kubeadm]]></title><description><![CDATA[Contents

Requirements
Build images
Tag images and push to registry
Kubeadm bootstrap

In this tutorial, I want to show you how to bootstrap a Kubernetes cluster with kubeadm using your customized Kubernetes fork. This might be useful if you want to ...]]></description><link>https://blog.adrianstobbe.com/tutorial-bootsrapping-your-custom-kubernetes-with-kubeadm-1</link><guid isPermaLink="true">https://blog.adrianstobbe.com/tutorial-bootsrapping-your-custom-kubernetes-with-kubeadm-1</guid><dc:creator><![CDATA[Adrian Stobbe]]></dc:creator><pubDate>Thu, 07 Apr 2022 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Contents</p>
<ul>
<li><a target="_blank" href="https://astobbe.me/#requirements">Requirements</a></li>
<li><a target="_blank" href="https://astobbe.me/#build-images">Build images</a></li>
<li><a target="_blank" href="https://astobbe.me/#tag-images-and-push-to-registry">Tag images and push to registry</a></li>
<li><a target="_blank" href="https://astobbe.me/#kubeadm-bootstrap">Kubeadm bootstrap</a></li>
</ul>
<p>In this tutorial, I want to show you how to bootstrap a Kubernetes cluster with <code>kubeadm</code> using your customized Kubernetes fork. This might be useful if you want to use new features that are not yet merged in the upstream. For development, it’s of course much easier to set up a local cluster (<code>./hack/local-up-cluster.sh</code>), but to test functionality across different nodes, you might need a distributed cluster.</p>
<p>One option is to install it <a target="_blank" href="https://github.com/kelseyhightower/kubernetes-the-hard-way">The hard way</a>, but I think it’s more convenient to use <code>kubeadm</code>. The challenge is that you need container images for the kube control-plane components.</p>
<p>I found it difficult to find good documentation on this process, so I hope to help you along with this.</p>
<h2 id="heading-requirements">Requirements</h2>
<p>By default Kubeadm pulls the Kubernetes version matching the version of kubeadm from the official registry. But it also allows to specify a custom registry. To install our custom Kubernetes, we will need to build and pull the required container images, tag them with the expected version of kubeadm, and the push them to our registry.</p>
<h2 id="heading-build-images">Build images</h2>
<p>I built the images on a Ubuntu machine, since the build is resource intensive. Kubernetes is big, so be sure to have enough space on the VM! The build happens inside a docker container, so make sure to have docker installed. VMs usually only have a small disk image, so I created a symbolic link to save the docker files on another attached drive (here mounted on <code>/mnt</code>):</p>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>
<span class="hljs-number">3</span>
<span class="hljs-number">4</span>
<span class="hljs-number">5</span>
<span class="hljs-number">6</span>


sudo <span class="hljs-operator">-</span>s
systemctl stop docker
rm <span class="hljs-operator">-</span>rf <span class="hljs-operator">/</span><span class="hljs-keyword">var</span><span class="hljs-operator">/</span>lib<span class="hljs-operator">/</span>docker
mkdir <span class="hljs-operator">-</span>p <span class="hljs-operator">/</span>mnt<span class="hljs-operator">/</span>docker
ln <span class="hljs-operator">-</span>s <span class="hljs-operator">/</span>mnt<span class="hljs-operator">/</span>docker <span class="hljs-operator">/</span><span class="hljs-keyword">var</span><span class="hljs-operator">/</span>lib<span class="hljs-operator">/</span>docker
systemctl restart docker
</code></pre><p>Then, inside the kubernetes root path, specify your docker registry and run: <code>KUBE_SERVER_PLATTFORMS="linux/amd64" KUBE_DOCKER_REGISTRY="sadrian99" KUBE_RELEASE_RUN_TESTS=n ./build/release-images.sh</code> I recommend to set a flag for the image version such that the version coincides with what kubeadm expects (version of kubeadm). In my case, this was <code>v1.19.16</code>.</p>
<h2 id="heading-tag-images-and-push-to-registry">Tag images and push to registry</h2>
<p>The generated image names have a trailing <code>amd64</code> which needs to be removed for <code>kubeadm</code>: <code>IMGN</code>: new name for kubadm <code>IMG</code>: old image name <code>VER</code>: old version tag <code>VERN</code>: new version tag <code>REPO</code>: your container registry url In my case, <code>REPO=sadrian99</code> and <code>VERN=v.19.16</code>. Then we tag the image with the new name:</p>
<pre><code>1
2


<span class="hljs-built_in">export</span> IMG=kube-apiserver-amd64; <span class="hljs-built_in">export</span> IMGN=kube-apiserver
docker tag <span class="hljs-variable">$REPO</span>/<span class="hljs-variable">$IMG</span>:<span class="hljs-variable">$VER</span> sadrian99/<span class="hljs-variable">$IMGN</span>:<span class="hljs-variable">$VERN</span> &amp;&amp; docker push <span class="hljs-variable">$REPO</span>/<span class="hljs-variable">$IMGN</span>:<span class="hljs-variable">$VERN</span>```
</code></pre><p>You need to do this for these images:</p>
<ul>
<li>kube-apiserver:v1.19.16</li>
<li>kube-controller-manager:v1.19.16</li>
<li>kube-scheduler:v1.19.16</li>
<li>kube-proxy:v1.19.16</li>
</ul>
<p>Additionally you need to pull these images and copy them to your registry:</p>
<ul>
<li>pause:3.2</li>
<li>etcd:3.4.7-0</li>
<li><p>coredns:1.6.7</p>
<p>1
2</p>
</li>
</ul>
<pre><code>export D<span class="hljs-operator">=</span>coredns:<span class="hljs-number">1.6</span><span class="hljs-number">.7</span>
docker pull k8s.gcr.io/$D <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> docker tag k8s.gcr.io/$D $REPO<span class="hljs-operator">/</span>$D <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> docker push $REPO<span class="hljs-operator">/</span>$D
</code></pre><h2 id="heading-kubeadm-bootstrap">Kubeadm bootstrap</h2>
<p>You can test if the images are all available: <code>kubeadm config images pull --image-repository sadrian99</code></p>
<p>Then you can add <code>--image-repository sadrian99</code> to the <code>init</code> command to bootstrap your cluster.</p>
<p>Congrats, you should now have a cluster with your own Kubernetes version! I used this to <a target="_blank" href="https://astobbe.me/posts/pod-migration">migrate pods</a> across nodes. Let me know what your use case is!</p>
]]></content:encoded></item><item><title><![CDATA[[Tutorial]: Kubernetes stateful pod migration]]></title><description><![CDATA[Contents

Why
Status quo
Goal
Demo
1. Cluster setup
Network setup
VM provisioning


2. Kernel downgrade
Worker


3. Install Prequisites
Master node
Containerd + Kubelet
Kubeadm
Kubectl


Worker node
CRIU
Golang
Containerd
Install dependencies
Systemd...]]></description><link>https://blog.adrianstobbe.com/tutorial-kubernetes-stateful-pod-migration</link><guid isPermaLink="true">https://blog.adrianstobbe.com/tutorial-kubernetes-stateful-pod-migration</guid><dc:creator><![CDATA[Adrian Stobbe]]></dc:creator><pubDate>Wed, 06 Apr 2022 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Contents</p>
<ul>
<li><a target="_blank" href="https://astobbe.me/#why">Why</a></li>
<li><a target="_blank" href="https://astobbe.me/#status-quo">Status quo</a></li>
<li><a target="_blank" href="https://astobbe.me/#goal">Goal</a></li>
<li><a target="_blank" href="https://astobbe.me/#demo">Demo</a></li>
<li><a target="_blank" href="https://astobbe.me/#1-cluster-setup">1. Cluster setup</a><ul>
<li><a target="_blank" href="https://astobbe.me/#network-setup">Network setup</a></li>
<li><a target="_blank" href="https://astobbe.me/#vm-provisioning">VM provisioning</a></li>
</ul>
</li>
<li><a target="_blank" href="https://astobbe.me/#2-kernel-downgrade">2. Kernel downgrade</a><ul>
<li><a target="_blank" href="https://astobbe.me/#worker">Worker</a></li>
</ul>
</li>
<li><a target="_blank" href="https://astobbe.me/#3-install-prequisites">3. Install Prequisites</a><ul>
<li><a target="_blank" href="https://astobbe.me/#master-node">Master node</a><ul>
<li><a target="_blank" href="https://astobbe.me/#containerd--kubelet">Containerd + Kubelet</a></li>
<li><a target="_blank" href="https://astobbe.me/#kubeadm">Kubeadm</a></li>
<li><a target="_blank" href="https://astobbe.me/#kubectl">Kubectl</a></li>
</ul>
</li>
<li><a target="_blank" href="https://astobbe.me/#worker-node">Worker node</a><ul>
<li><a target="_blank" href="https://astobbe.me/#criu">CRIU</a></li>
<li><a target="_blank" href="https://astobbe.me/#golang">Golang</a></li>
<li><a target="_blank" href="https://astobbe.me/#containerd">Containerd</a><ul>
<li><a target="_blank" href="https://astobbe.me/#install-dependencies">Install dependencies</a></li>
<li><a target="_blank" href="https://astobbe.me/#systemd-service">Systemd service</a></li>
<li><a target="_blank" href="https://astobbe.me/#build">Build</a></li>
</ul>
</li>
<li><a target="_blank" href="https://astobbe.me/#update-systemd-svc-to-custom-binary">Update systemd svc to custom binary</a></li>
<li><a target="_blank" href="https://astobbe.me/#configure-cni-plugins">Configure CNI plugins</a></li>
<li><a target="_blank" href="https://astobbe.me/#kubelet">Kubelet</a></li>
<li><a target="_blank" href="https://astobbe.me/#kubeadm-1">Kubeadm</a></li>
</ul>
</li>
</ul>
</li>
<li><a target="_blank" href="https://astobbe.me/#4-kubernetes-bootstrapping">4. Kubernetes bootstrapping</a><ul>
<li><a target="_blank" href="https://astobbe.me/#networking">Networking</a></li>
<li><a target="_blank" href="https://astobbe.me/#kubeadm-init">Kubeadm init</a></li>
<li><a target="_blank" href="https://astobbe.me/#verify-cluster-state">Verify cluster state</a></li>
<li><a target="_blank" href="https://astobbe.me/#debugging">(Debugging)</a></li>
<li><a target="_blank" href="https://astobbe.me/#join-worker">Join worker</a></li>
<li><a target="_blank" href="https://astobbe.me/#copy-kubeconfig">Copy kubeconfig</a></li>
</ul>
</li>
<li><a target="_blank" href="https://astobbe.me/#5-demo-test-migration">5. Demo (test migration)</a><ul>
<li><a target="_blank" href="https://astobbe.me/#deploy-stateful-pod">Deploy stateful pod</a></li>
<li><a target="_blank" href="https://astobbe.me/#clone-pod">Clone pod</a></li>
</ul>
</li>
<li><a target="_blank" href="https://astobbe.me/#6-set-up-file-server">6. Set up file server</a><ul>
<li><a target="_blank" href="https://astobbe.me/#important-warning">Important warning</a></li>
<li><a target="_blank" href="https://astobbe.me/#steps">Steps</a></li>
</ul>
</li>
<li><a target="_blank" href="https://astobbe.me/#development">Development</a></li>
<li><a target="_blank" href="https://astobbe.me/#end">End</a></li>
</ul>
<p>This is an extensive tutorial on how to set up a Kubernetes cluster that supports pod migration.</p>
<h2 id="heading-why">Why</h2>
<p>Statelessness is the basic foundation for microservices run inside Kubernetes. Outside it’s main application domain, the platform also appeals to the High Performance Computing (HPC) community for that infrastructure management can be delegated to cloud providers and it’s on-demand scaling. The challenge is that HPC jobs are usually long running and stateful. Jobs such as simulations or optimization problems usually keep their state in memory and state checkpointing on disk is not always available. This is undesirable because failures are expected to occur. Matters becomes even worse for jobs with unpredictable resource requirements. Unexpected spikes in memory can lead to out-of-memory node situations, which results in pods being killed. The catastrophic consequence is the complete loss of job progress from many hours or even days of compute time. To avoid this, a migration of stateful pods to another node would be desirable.</p>
<h2 id="heading-status-quo">Status quo</h2>
<p>Currently, Kubernetes does not support pod migration. However, a PoC of a pod migration n prior work by <a target="_blank" href="https://www.researchgate.net/publication/349662156_Migrating_Pods_in_Kubernetes">Jakob Schrettenbrunner</a> showed the feasibility. A <a target="_blank" href="https://github.com/kubernetes/enhancements/pull/1990">proposal</a> to support very basic checkpointing (forensic checkpoiting without restore) functionality has recently been accepted by the Kubernetes community as well and is expected to be available in future releases.</p>
<h2 id="heading-goal">Goal</h2>
<p>Building on the prior PoC of Jakob Schrettenbrunner, I want to show you step by step how to set up a Kubernetes cluster with pod migration functionality. Bootstrapping a Kubernetes cluster from scratch is not a trivial task, but <code>kubeadm</code> will help us. Jakob also provided some <a target="_blank" href="https://docs.google.com/document/d/1E5p_FOHDGAp5YEQ23dCi9I8wPnMzd4aOazxI4uO_AMo/edit#">documentation</a> on his setup and while very helpful it is far from complete and does not mention all potential gotchas. You might suspect already that this won’t be a quick and easy process, but I hope to make it a lot easier for you through this extensive tutorial.</p>
<h2 id="heading-demo">Demo</h2>
<p>To see what to expect, here is a quick demo of the steps to migrate a pod:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/IPY852th_T0"></iframe>

<h2 id="heading-1-cluster-setup">1. Cluster setup</h2>
<p>The cluster consists of 1 master node and 2 worker nodes. The VMs are provisioned in Microsoft Azure. For migrating the pod across a worker node, <a target="_blank" href="https://docs.microsoft.com/en-us/azure/storage/files/files-smb-protocol?tabs=azure-portal">Azure’s SMB file share server</a>) is used. You might also use an NFS server (and it might even make things easier as mentioned later..), but this was not possible for company policy reasons in my case.</p>
<p>Kubernetes is bootstrapped using <code>kubeadm</code>. It’s tested with version <code>v1.19.0-beta.0.1015+b521fb5114995f-dirty</code> ( binaries are available <a target="_blank" href="https://github.com/elchead/kubernetes/releases/tag/v8.1.0">here</a>, but I recommend building from source).</p>
<h3 id="heading-network-setup">Network setup</h3>
<p>To set up the cluster network, I followed <a target="_blank" href="https://blog.nillsf.com/index.php/2021/10/29/setting-up-kubernetes-on-azure-using-kubeadm/">this tutorial</a>. You can use the web shell on Azure for this:</p>
<pre><code> <span class="hljs-number">1</span>
 <span class="hljs-number">2</span>
 <span class="hljs-number">3</span>
 <span class="hljs-number">4</span>
 <span class="hljs-number">5</span>
 <span class="hljs-number">6</span>
 <span class="hljs-number">7</span>
 <span class="hljs-number">8</span>
 <span class="hljs-number">9</span>
<span class="hljs-number">10</span>
<span class="hljs-number">11</span>
<span class="hljs-number">12</span>
<span class="hljs-number">13</span>
<span class="hljs-number">14</span>
<span class="hljs-number">15</span>
<span class="hljs-number">16</span>
<span class="hljs-number">17</span>
<span class="hljs-number">18</span>
<span class="hljs-number">19</span>
<span class="hljs-number">20</span>
<span class="hljs-number">21</span>
<span class="hljs-number">22</span>
<span class="hljs-number">23</span>
<span class="hljs-number">24</span>
<span class="hljs-number">25</span>
<span class="hljs-number">26</span>
<span class="hljs-number">27</span>
<span class="hljs-number">28</span>
<span class="hljs-number">29</span>
<span class="hljs-number">30</span>
<span class="hljs-number">31</span>
<span class="hljs-number">32</span>
<span class="hljs-number">33</span>
<span class="hljs-number">34</span>
<span class="hljs-number">35</span>
<span class="hljs-number">36</span>
<span class="hljs-number">37</span>
<span class="hljs-number">38</span>
<span class="hljs-number">39</span>
<span class="hljs-number">40</span>
<span class="hljs-number">41</span>
<span class="hljs-number">42</span>
<span class="hljs-number">43</span>
<span class="hljs-number">44</span>
<span class="hljs-number">45</span>
<span class="hljs-number">46</span>
<span class="hljs-number">47</span>
<span class="hljs-number">48</span>
<span class="hljs-number">49</span>
<span class="hljs-number">50</span>
<span class="hljs-number">51</span>
<span class="hljs-number">52</span>
<span class="hljs-number">53</span>
<span class="hljs-number">54</span>
<span class="hljs-number">55</span>
<span class="hljs-number">56</span>
<span class="hljs-number">57</span>
<span class="hljs-number">58</span>
<span class="hljs-number">59</span>
<span class="hljs-number">60</span>
<span class="hljs-number">61</span>
<span class="hljs-number">62</span>
<span class="hljs-number">63</span>
<span class="hljs-number">64</span>
<span class="hljs-number">65</span>
<span class="hljs-number">66</span>
<span class="hljs-number">67</span>
<span class="hljs-number">68</span>
<span class="hljs-number">69</span>
<span class="hljs-number">70</span>
<span class="hljs-number">71</span>
<span class="hljs-number">72</span>
<span class="hljs-number">73</span>
<span class="hljs-number">74</span>
<span class="hljs-number">75</span>
<span class="hljs-number">76</span>
<span class="hljs-number">77</span>
<span class="hljs-number">78</span>
<span class="hljs-number">79</span>
<span class="hljs-number">80</span>
<span class="hljs-number">81</span>
<span class="hljs-number">82</span>
<span class="hljs-number">83</span>
<span class="hljs-number">84</span>
<span class="hljs-number">85</span>


<span class="hljs-string">az</span> <span class="hljs-string">group</span> <span class="hljs-string">create</span> <span class="hljs-string">-n</span> <span class="hljs-string">kubeadm</span> <span class="hljs-string">-l</span> <span class="hljs-string">westus2</span>

<span class="hljs-string">az</span> <span class="hljs-string">network</span> <span class="hljs-string">vnet</span> <span class="hljs-string">create</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--resource-group</span> <span class="hljs-string">kubeadm</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--name</span> <span class="hljs-string">kubeadm</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--address-prefix</span> <span class="hljs-number">192.168</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/16</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--subnet-name</span> <span class="hljs-string">kube</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--subnet-prefix</span> <span class="hljs-number">192.168</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/16</span>

<span class="hljs-string">az</span> <span class="hljs-string">network</span> <span class="hljs-string">nsg</span> <span class="hljs-string">create</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--resource-group</span> <span class="hljs-string">kubeadm</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--name</span> <span class="hljs-string">kubeadm</span>

<span class="hljs-string">az</span> <span class="hljs-string">network</span> <span class="hljs-string">nsg</span> <span class="hljs-string">rule</span> <span class="hljs-string">create</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--resource-group</span> <span class="hljs-string">kubeadm</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--nsg-name</span> <span class="hljs-string">kubeadm</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--name</span> <span class="hljs-string">kubeadmssh</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--protocol</span> <span class="hljs-string">tcp</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--priority</span> <span class="hljs-number">1000</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--destination-port-range</span> <span class="hljs-number">22</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--access</span> <span class="hljs-string">allow</span>

<span class="hljs-string">az</span> <span class="hljs-string">network</span> <span class="hljs-string">nsg</span> <span class="hljs-string">rule</span> <span class="hljs-string">create</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--resource-group</span> <span class="hljs-string">kubeadm</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--nsg-name</span> <span class="hljs-string">kubeadm</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--name</span> <span class="hljs-string">kubeadmWeb</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--protocol</span> <span class="hljs-string">tcp</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--priority</span> <span class="hljs-number">1001</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--destination-port-range</span> <span class="hljs-number">6443</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--access</span> <span class="hljs-string">allow</span>

<span class="hljs-string">az</span> <span class="hljs-string">network</span> <span class="hljs-string">vnet</span> <span class="hljs-string">subnet</span> <span class="hljs-string">update</span> <span class="hljs-string">\</span>
    <span class="hljs-string">-g</span> <span class="hljs-string">kubeadm</span> <span class="hljs-string">\</span>
    <span class="hljs-string">-n</span> <span class="hljs-string">kube</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--vnet-name</span> <span class="hljs-string">kubeadm</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--network-security-group</span> <span class="hljs-string">kubeadm</span>
<span class="hljs-comment"># load balancer:</span>
<span class="hljs-string">az</span> <span class="hljs-string">network</span> <span class="hljs-string">public-ip</span> <span class="hljs-string">create</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--resource-group</span> <span class="hljs-string">kubeadm</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--name</span> <span class="hljs-string">controlplaneip</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--sku</span> <span class="hljs-string">Standard</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--dns-name</span> <span class="hljs-string">nilfrankubeadm</span>

 <span class="hljs-string">az</span> <span class="hljs-string">network</span> <span class="hljs-string">lb</span> <span class="hljs-string">create</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--resource-group</span> <span class="hljs-string">kubeadm</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--name</span> <span class="hljs-string">kubemaster</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--sku</span> <span class="hljs-string">Standard</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--public-ip-address</span> <span class="hljs-string">controlplaneip</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--frontend-ip-name</span> <span class="hljs-string">controlplaneip</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--backend-pool-name</span> <span class="hljs-string">masternodes</span>

<span class="hljs-string">az</span> <span class="hljs-string">network</span> <span class="hljs-string">lb</span> <span class="hljs-string">probe</span> <span class="hljs-string">create</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--resource-group</span> <span class="hljs-string">kubeadm</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--lb-name</span> <span class="hljs-string">kubemaster</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--name</span> <span class="hljs-string">kubemasterweb</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--protocol</span> <span class="hljs-string">tcp</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--port</span> <span class="hljs-number">6443</span>

<span class="hljs-string">az</span> <span class="hljs-string">network</span> <span class="hljs-string">lb</span> <span class="hljs-string">rule</span> <span class="hljs-string">create</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--resource-group</span> <span class="hljs-string">kubeadm</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--lb-name</span> <span class="hljs-string">kubemaster</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--name</span> <span class="hljs-string">kubemaster</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--protocol</span> <span class="hljs-string">tcp</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--frontend-port</span> <span class="hljs-number">6443</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--backend-port</span> <span class="hljs-number">6443</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--frontend-ip-name</span> <span class="hljs-string">controlplaneip</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--backend-pool-name</span> <span class="hljs-string">masternodes</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--probe-name</span> <span class="hljs-string">kubemasterweb</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--disable-outbound-snat</span> <span class="hljs-literal">true</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--idle-timeout</span> <span class="hljs-number">15</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--enable-tcp-reset</span> <span class="hljs-literal">true</span>

<span class="hljs-string">az</span> <span class="hljs-string">network</span> <span class="hljs-string">nic</span> <span class="hljs-string">ip-config</span> <span class="hljs-string">address-pool</span> <span class="hljs-string">add</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--address-pool</span> <span class="hljs-string">masternodes</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--ip-config-name</span> <span class="hljs-string">ipconfigkube-master-1</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--nic-name</span> <span class="hljs-string">kube-master-1VMNic</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--resource-group</span> <span class="hljs-string">kubeadm</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--lb-name</span> <span class="hljs-string">kubemaster</span>

<span class="hljs-string">az</span> <span class="hljs-string">network</span> <span class="hljs-string">nic</span> <span class="hljs-string">ip-config</span> <span class="hljs-string">address-pool</span> <span class="hljs-string">add</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--address-pool</span> <span class="hljs-string">masternodes</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--ip-config-name</span> <span class="hljs-string">ipconfigkube-master-2</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--nic-name</span> <span class="hljs-string">kube-master-2VMNic</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--resource-group</span> <span class="hljs-string">kubeadm</span> <span class="hljs-string">\</span>
    <span class="hljs-string">--lb-name</span> <span class="hljs-string">kubemaster</span>
</code></pre><h3 id="heading-vm-provisioning">VM provisioning</h3>
<p><strong>Master</strong></p>
<p>The specs of my master node VM are as follows on Ubuntu 21.04 (20.04 LTS could also be used):</p>
<pre><code> <span class="hljs-number">1</span>
 <span class="hljs-number">2</span>
 <span class="hljs-number">3</span>
 <span class="hljs-number">4</span>
 <span class="hljs-number">5</span>
 <span class="hljs-number">6</span>
 <span class="hljs-number">7</span>
 <span class="hljs-number">8</span>
 <span class="hljs-number">9</span>
<span class="hljs-number">10</span>
<span class="hljs-number">11</span>
<span class="hljs-number">12</span>
<span class="hljs-number">13</span>


{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"kube-master-3"</span>,

  <span class="hljs-attr">"location"</span>: <span class="hljs-string">"westeurope"</span>,

  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"ubuntu-21-04-lts"</span>,

  <span class="hljs-attr">"publisher"</span>: <span class="hljs-string">"tidalmediainc"</span>,

  <span class="hljs-attr">"product"</span>: <span class="hljs-string">"ubuntu-21-04-lts"</span>,

  <span class="hljs-attr">"vmSize"</span>: <span class="hljs-string">"Standard_D2ds_v4"</span>
}
</code></pre><p><strong>Worker</strong></p>
<p>Both worker VMs share the same specs:</p>
<pre><code> <span class="hljs-number">1</span>
 <span class="hljs-number">2</span>
 <span class="hljs-number">3</span>
 <span class="hljs-number">4</span>
 <span class="hljs-number">5</span>
 <span class="hljs-number">6</span>
 <span class="hljs-number">7</span>
 <span class="hljs-number">8</span>
 <span class="hljs-number">9</span>
<span class="hljs-number">10</span>
<span class="hljs-number">11</span>
<span class="hljs-number">12</span>
<span class="hljs-number">13</span>
<span class="hljs-number">14</span>
<span class="hljs-number">15</span>
<span class="hljs-number">16</span>
<span class="hljs-number">17</span>
<span class="hljs-number">18</span>
<span class="hljs-number">19</span>
<span class="hljs-number">20</span>


{

<span class="hljs-attr">"name":</span> <span class="hljs-string">"zone2/zone3"</span>,

<span class="hljs-attr">"location":</span> <span class="hljs-string">"westeurope"</span>,

<span class="hljs-attr">"vmSize":</span> <span class="hljs-string">"Standard_E16-4ds_v4"</span>,


<span class="hljs-attr">"imageReference":</span> {

<span class="hljs-attr">"publisher":</span> <span class="hljs-string">"canonical"</span>,

<span class="hljs-attr">"offer":</span> <span class="hljs-string">"0001-com-ubuntu-server-focal"</span>,

<span class="hljs-attr">"sku":</span> <span class="hljs-string">"20_04-lts-gen2"</span>,

<span class="hljs-attr">"version":</span> <span class="hljs-string">"latest"</span>

}
</code></pre><p>After connecting to the VM, go into root mode: <code>sudo -s</code> All following steps assume this!</p>
<h2 id="heading-2-kernel-downgrade">2. Kernel downgrade</h2>
<h3 id="heading-worker">Worker</h3>
<p>As mentioned <a target="_blank" href="https://github.com/checkpoint-restore/criu/issues/860">here</a>, recent Ubuntu kernels broke compatibility with CRIU. Hence we downgrade the kernel: <code>apt install -y linux-image-unsigned-5.4.0-1068-azure</code></p>
<p>Follow <a target="_blank" href="https://meetrix.io/blog/aws/changing-default-ubuntu-kernel.html">these steps</a> to change the boot kernel.</p>
<h2 id="heading-3-install-prequisites">3. Install Prequisites</h2>
<h3 id="heading-master-node">Master node</h3>
<h4 id="heading-containerd-kubelet">Containerd + Kubelet</h4>
<p>On the master node, you can install the official releases:</p>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>
<span class="hljs-number">3</span>
<span class="hljs-number">4</span>
<span class="hljs-number">5</span>
<span class="hljs-number">6</span>


curl <span class="hljs-operator">-</span>s https:<span class="hljs-comment">//packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -</span>
echo <span class="hljs-string">"deb https://apt.kubernetes.io/ kubernetes-xenial main"</span> <span class="hljs-operator">|</span> sudo tee <span class="hljs-operator">/</span>etc<span class="hljs-operator">/</span>apt<span class="hljs-operator">/</span>sources.list.d/kubernetes.list

apt update

apt install <span class="hljs-operator">-</span>y containerd kubelet
</code></pre><p>But for safety, you should replace the <code>kubelet</code> with the binary path defined in the systemd service (<code>/usr/bin/kubelet</code>). You can get the customized kubelet like this:</p>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>
<span class="hljs-number">3</span>


wget https:<span class="hljs-comment">//github.com/elchead/kubernetes/releases/download/v8.1.0/kubelet</span>
chmod <span class="hljs-operator">+</span>x ./kubelet
cp ./kubelet <span class="hljs-operator">/</span>usr<span class="hljs-operator">/</span>bin
</code></pre><h4 id="heading-kubeadm">Kubeadm</h4>
<p>To get the <code>kubeadm</code> version compatible with our modified kubernetes. Inside your home directory, do:</p>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>


wget https:<span class="hljs-comment">//github.com/elchead/kubernetes/releases/download/v8.1.0/kubeadm</span>
chmod <span class="hljs-operator">+</span>x ./kubeadm
</code></pre><h4 id="heading-kubectl">Kubectl</h4>
<p>Install as described <a target="_blank" href="https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/">here</a></p>
<h3 id="heading-worker-node">Worker node</h3>
<p>I recommend to perform the following steps in parallel on both worker VMs. You might use <a target="_blank" href="https://github.com/kelseyhightower/kubernetes-the-hard-way/blob/master/docs/01-prerequisites.md#running-commands-in-parallel-with-tmux">tmux</a> or iTerm on Mac ( <code>Cmd+Shift+i</code>).</p>
<h4 id="heading-criu">CRIU</h4>
<p>As described <a target="_blank" href="https://software.opensuse.org/download/package?package=criu&amp;project=devel%3Atools%3Acriu">here</a>:</p>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>
<span class="hljs-number">3</span>
<span class="hljs-number">4</span>


echo <span class="hljs-string">'deb http://download.opensuse.org/repositories/devel:/tools:/criu/xUbuntu_20.04/ /'</span> <span class="hljs-operator">|</span> sudo tee <span class="hljs-operator">/</span>etc<span class="hljs-operator">/</span>apt<span class="hljs-operator">/</span>sources.list.d/devel:tools:criu.list
curl <span class="hljs-operator">-</span>fsSL https:<span class="hljs-comment">//download.opensuse.org/repositories/devel:tools:criu/xUbuntu_20.04/Release.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/devel_tools_criu.gpg &gt; /dev/null</span>
sudo apt update
sudo apt install <span class="hljs-operator">-</span>y criu
</code></pre><p>Verify the version to be 3.16.1: <code>criu --version</code></p>
<h4 id="heading-golang">Golang</h4>
<p>Follow <a target="_blank" href="https://go.dev/doc/install">here</a>. I installed v1.17.8:</p>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>
<span class="hljs-number">3</span>


wget https:<span class="hljs-comment">//go.dev/dl/go1.17.8.linux-amd64.tar.gz</span>
tar <span class="hljs-operator">-</span>C <span class="hljs-operator">/</span>usr<span class="hljs-operator">/</span>local <span class="hljs-operator">-</span>xzf go1<span class="hljs-number">.17</span><span class="hljs-number">.8</span>.linux-amd64.tar.gz
export PATH<span class="hljs-operator">=</span>$PATH:<span class="hljs-operator">/</span>usr<span class="hljs-operator">/</span>local<span class="hljs-operator">/</span>go<span class="hljs-operator">/</span>bin
</code></pre><h4 id="heading-containerd">Containerd</h4>
<h5 id="heading-install-dependencies">Install dependencies</h5>
<pre><code><span class="hljs-number">1</span>


apt install btrfs<span class="hljs-operator">-</span>progs libbtrfs<span class="hljs-operator">-</span>dev runc
</code></pre><h5 id="heading-systemd-service">Systemd service</h5>
<p>I took the dirty path and used <code>apt</code> to install an official release :</p>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>


<span class="hljs-string">apt</span> <span class="hljs-string">update</span>
<span class="hljs-string">apt</span> <span class="hljs-string">install</span> <span class="hljs-string">containerd</span>
</code></pre><p>Then, I later replaced the binary defined in the systemd config with my fork binary. The file looks like this:</p>
<pre><code> <span class="hljs-number">1</span>
 <span class="hljs-number">2</span>
 <span class="hljs-number">3</span>
 <span class="hljs-number">4</span>
 <span class="hljs-number">5</span>
 <span class="hljs-number">6</span>
 <span class="hljs-number">7</span>
 <span class="hljs-number">8</span>
 <span class="hljs-number">9</span>
<span class="hljs-number">10</span>
<span class="hljs-number">11</span>
<span class="hljs-number">12</span>
<span class="hljs-number">13</span>
<span class="hljs-number">14</span>
<span class="hljs-number">15</span>
<span class="hljs-number">16</span>
<span class="hljs-number">17</span>
<span class="hljs-number">18</span>
<span class="hljs-number">19</span>
<span class="hljs-number">20</span>
<span class="hljs-number">21</span>
<span class="hljs-number">22</span>
<span class="hljs-number">23</span>
<span class="hljs-number">24</span>
<span class="hljs-number">25</span>
<span class="hljs-number">26</span>
<span class="hljs-number">27</span>
<span class="hljs-number">28</span>
<span class="hljs-number">29</span>
<span class="hljs-number">30</span>
<span class="hljs-number">31</span>
<span class="hljs-number">32</span>
<span class="hljs-number">33</span>
<span class="hljs-number">34</span>
<span class="hljs-number">35</span>
<span class="hljs-number">36</span>
<span class="hljs-number">37</span>
<span class="hljs-number">38</span>
<span class="hljs-number">39</span>
<span class="hljs-number">40</span>
<span class="hljs-number">41</span>
<span class="hljs-number">42</span>


<span class="hljs-string">cat</span> <span class="hljs-string">/lib/systemd/system/containerd.service</span>

<span class="hljs-comment"># Copyright The containerd Authors.</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># Licensed under the Apache License, Version 2.0 (the "License");</span>
<span class="hljs-comment"># you may not use this file except in compliance with the License.</span>
<span class="hljs-comment"># You may obtain a copy of the License at</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment">#     http://www.apache.org/licenses/LICENSE-2.0</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># Unless required by applicable law or agreed to in writing, software</span>
<span class="hljs-comment"># distributed under the License is distributed on an "AS IS" BASIS,</span>
<span class="hljs-comment"># WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.</span>
<span class="hljs-comment"># See the License for the specific language governing permissions and</span>
<span class="hljs-comment"># limitations under the License.</span>

[<span class="hljs-string">Unit</span>]
<span class="hljs-string">Description=containerd</span> <span class="hljs-string">container</span> <span class="hljs-string">runtime</span>
<span class="hljs-string">Documentation=https://containerd.io</span>
<span class="hljs-string">After=network.target</span> <span class="hljs-string">local-fs.target</span>

[<span class="hljs-string">Service</span>]
<span class="hljs-string">ExecStartPre=-/sbin/modprobe</span> <span class="hljs-string">overlay</span>
<span class="hljs-string">ExecStart=/usr/bin/containerd</span>

<span class="hljs-string">Type=notify</span>
<span class="hljs-string">Delegate=yes</span>
<span class="hljs-string">KillMode=process</span>
<span class="hljs-string">Restart=always</span>
<span class="hljs-string">RestartSec=5</span>
<span class="hljs-comment"># Having non-zero Limit*s causes performance problems due to accounting overhead</span>
<span class="hljs-comment"># in the kernel. We recommend using cgroups to do container-local accounting.</span>
<span class="hljs-string">LimitNPROC=infinity</span>
<span class="hljs-string">LimitCORE=infinity</span>
<span class="hljs-string">LimitNOFILE=infinity</span>
<span class="hljs-comment"># Comment TasksMax if your systemd version does not supports it.</span>
<span class="hljs-comment"># Only systemd 226 and above support this version.</span>
<span class="hljs-string">TasksMax=infinity</span>
<span class="hljs-string">OOMScoreAdjust=-999</span>

[<span class="hljs-string">Install</span>]
<span class="hljs-string">WantedBy=multi-user.target</span>
</code></pre><h5 id="heading-build">Build</h5>
<p>I recommend to build from source, but you may also use the binaries inside <code>bin</code>.</p>
<p>Clone my fork and checkout the <code>checkpoint</code> branch. If you want to use the version that only uploads a zip to the file server (please read under [6. Set up file server](#6. Set up file server), use <code>checkpoint-zip</code></p>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>
<span class="hljs-number">3</span>
<span class="hljs-number">4</span>


mkdir <span class="hljs-operator">-</span>p <span class="hljs-operator">/</span>root<span class="hljs-operator">/</span>go<span class="hljs-operator">/</span>src<span class="hljs-operator">/</span>github.com/containerd <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> cd <span class="hljs-operator">/</span>root<span class="hljs-operator">/</span>go<span class="hljs-operator">/</span>src<span class="hljs-operator">/</span>github.com/containerd
git clone https:<span class="hljs-comment">//github.com/elchead/containerd.git</span>
cd containerd
git checkout checkpoint
</code></pre><p>Build from source: <code>make &amp;&amp; make install</code></p>
<h4 id="heading-update-systemd-svc-to-custom-binary">Update systemd svc to custom binary</h4>
<p>Stop the service: `systemctl stop containerd</p>
<p>Inside root of the repository, do:</p>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>


cp ./bin<span class="hljs-operator">/</span>containerd <span class="hljs-operator">/</span>usr<span class="hljs-operator">/</span>bin<span class="hljs-operator">/</span>containerd
systemctl start containerd
</code></pre><h4 id="heading-configure-cni-plugins">Configure CNI plugins</h4>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>


export GOPATH<span class="hljs-operator">=</span><span class="hljs-operator">/</span>root<span class="hljs-operator">/</span>go
./script<span class="hljs-operator">/</span>setup<span class="hljs-operator">/</span>install<span class="hljs-operator">-</span>cni
</code></pre><p>In the output the CNI version is set to 1.0.0 which is wrong. So we change it to a supported version such as 0.3.0 : <code>vim /etc/cni/net.d/10-containerd-net.conflist</code></p>
<p>To be safe, restart the containerd service after after configuring the CNI plugins: `systemctl restart containerd</p>
<h4 id="heading-kubelet">Kubelet</h4>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>
<span class="hljs-number">3</span>
<span class="hljs-number">4</span>
<span class="hljs-number">5</span>
<span class="hljs-number">6</span>
<span class="hljs-number">7</span>
<span class="hljs-number">8</span>
<span class="hljs-number">9</span>


curl <span class="hljs-operator">-</span>s https:<span class="hljs-comment">//packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -</span>

echo <span class="hljs-string">"deb https://apt.kubernetes.io/ kubernetes-xenial main"</span> <span class="hljs-operator">|</span> sudo tee <span class="hljs-operator">/</span>etc<span class="hljs-operator">/</span>apt<span class="hljs-operator">/</span>sources.list.d/kubernetes.list

sudo apt update

sudo apt <span class="hljs-operator">-</span>y install kubelet

Then replace the binary:


<span class="hljs-number">1</span>
<span class="hljs-number">2</span>
<span class="hljs-number">3</span>


wget https:<span class="hljs-comment">//github.com/elchead/kubernetes/releases/download/v8.1.0/kubelet</span>
chmod <span class="hljs-operator">+</span>x ./kubelet
cp ./kubelet <span class="hljs-operator">/</span>usr<span class="hljs-operator">/</span>bin
</code></pre><p>Modify the systemd service:</p>
<pre><code> <span class="hljs-number">1</span>
 <span class="hljs-number">2</span>
 <span class="hljs-number">3</span>
 <span class="hljs-number">4</span>
 <span class="hljs-number">5</span>
 <span class="hljs-number">6</span>
 <span class="hljs-number">7</span>
 <span class="hljs-number">8</span>
 <span class="hljs-number">9</span>
<span class="hljs-number">10</span>
<span class="hljs-number">11</span>
<span class="hljs-number">12</span>
<span class="hljs-number">13</span>


<span class="hljs-string">vim</span> <span class="hljs-string">/etc/systemd/system/kubelet.service.d/10-kubeadm.conf</span>
<span class="hljs-comment"># <span class="hljs-doctag">Note:</span> This dropin only works with kubeadm and kubelet v1.11+</span>
[<span class="hljs-string">Service</span>]
<span class="hljs-string">Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf</span> <span class="hljs-string">--kubeconfig=/etc/kubernetes/kubelet.conf"</span>
<span class="hljs-string">Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"</span>
<span class="hljs-comment"># This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically</span>
<span class="hljs-string">EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env</span>
<span class="hljs-comment"># This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use</span>
<span class="hljs-comment"># the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file.</span>
<span class="hljs-string">EnvironmentFile=-/etc/default/kubelet</span>
<span class="hljs-string">ExecStart=</span>
<span class="hljs-string">ExecStart=/usr/bin/kubelet</span> <span class="hljs-string">$KUBELET_KUBECONFIG_ARGS</span> <span class="hljs-string">$KUBELET_CONFIG_ARGS</span> <span class="hljs-string">$KUBELET_KUBEADM_ARGS</span> <span class="hljs-string">$KUBELET_EXTRA_ARGS</span> <span class="hljs-string">--container-runtime-endpoint=/run/containerd/containerd.sock</span> <span class="hljs-string">--v=9</span> <span class="hljs-string">--read-only-port=0</span> <span class="hljs-string">--anonymous-auth=true</span> <span class="hljs-string">--authorization-mode=AlwaysAllow</span> <span class="hljs-string">--container-runtime=remote</span>
<span class="hljs-string">~</span>
</code></pre><p>The kubelet config should look like this:</p>
<pre><code> <span class="hljs-number">1</span>
 <span class="hljs-number">2</span>
 <span class="hljs-number">3</span>
 <span class="hljs-number">4</span>
 <span class="hljs-number">5</span>
 <span class="hljs-number">6</span>
 <span class="hljs-number">7</span>
 <span class="hljs-number">8</span>
 <span class="hljs-number">9</span>
<span class="hljs-number">10</span>
<span class="hljs-number">11</span>
<span class="hljs-number">12</span>
<span class="hljs-number">13</span>
<span class="hljs-number">14</span>
<span class="hljs-number">15</span>
<span class="hljs-number">16</span>
<span class="hljs-number">17</span>
<span class="hljs-number">18</span>
<span class="hljs-number">19</span>
<span class="hljs-number">20</span>
<span class="hljs-number">21</span>
<span class="hljs-number">22</span>
<span class="hljs-number">23</span>
<span class="hljs-number">24</span>
<span class="hljs-number">25</span>
<span class="hljs-number">26</span>
<span class="hljs-number">27</span>
<span class="hljs-number">28</span>
<span class="hljs-number">29</span>
<span class="hljs-number">30</span>
<span class="hljs-number">31</span>
<span class="hljs-number">32</span>
<span class="hljs-number">33</span>
<span class="hljs-number">34</span>
<span class="hljs-number">35</span>


<span class="hljs-string">cat</span> <span class="hljs-string">/var/lib/kubelet/config.yaml</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">kubelet.config.k8s.io/v1beta1</span>
<span class="hljs-attr">authentication:</span>
  <span class="hljs-attr">anonymous:</span>
    <span class="hljs-attr">enabled:</span> <span class="hljs-literal">false</span>
  <span class="hljs-attr">webhook:</span>
    <span class="hljs-attr">cacheTTL:</span> <span class="hljs-string">0s</span>
    <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">x509:</span>
    <span class="hljs-attr">clientCAFile:</span> <span class="hljs-string">/etc/kubernetes/pki/ca.crt</span>
<span class="hljs-attr">authorization:</span>
  <span class="hljs-attr">mode:</span> <span class="hljs-string">Webhook</span>
  <span class="hljs-attr">webhook:</span>
    <span class="hljs-attr">cacheAuthorizedTTL:</span> <span class="hljs-string">0s</span>
    <span class="hljs-attr">cacheUnauthorizedTTL:</span> <span class="hljs-string">0s</span>
<span class="hljs-attr">clusterDNS:</span>
<span class="hljs-bullet">-</span> <span class="hljs-number">10.96</span><span class="hljs-number">.0</span><span class="hljs-number">.10</span>
<span class="hljs-attr">clusterDomain:</span> <span class="hljs-string">cluster.local</span>
<span class="hljs-attr">cpuManagerReconcilePeriod:</span> <span class="hljs-string">0s</span>
<span class="hljs-attr">evictionPressureTransitionPeriod:</span> <span class="hljs-string">0s</span>
<span class="hljs-attr">fileCheckFrequency:</span> <span class="hljs-string">0s</span>
<span class="hljs-attr">healthzBindAddress:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>
<span class="hljs-attr">healthzPort:</span> <span class="hljs-number">10248</span>
<span class="hljs-attr">httpCheckFrequency:</span> <span class="hljs-string">0s</span>
<span class="hljs-attr">imageMinimumGCAge:</span> <span class="hljs-string">0s</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">KubeletConfiguration</span>
<span class="hljs-attr">nodeStatusReportFrequency:</span> <span class="hljs-string">0s</span>
<span class="hljs-attr">nodeStatusUpdateFrequency:</span> <span class="hljs-string">0s</span>
<span class="hljs-attr">resolvConf:</span> <span class="hljs-string">/run/systemd/resolve/resolv.conf</span>
<span class="hljs-attr">rotateCertificates:</span> <span class="hljs-literal">true</span>
<span class="hljs-attr">runtimeRequestTimeout:</span> <span class="hljs-string">50m0s</span>
<span class="hljs-attr">staticPodPath:</span> <span class="hljs-string">/etc/kubernetes/manifests</span>
<span class="hljs-attr">streamingConnectionIdleTimeout:</span> <span class="hljs-string">0s</span>
<span class="hljs-attr">syncFrequency:</span> <span class="hljs-string">0s</span>
<span class="hljs-attr">volumeStatsAggPeriod:</span> <span class="hljs-string">0s</span>
</code></pre><p>It’s important to set the <code>runtimeRequestTimeout</code> to a higher value (default is 2 minutes), if you intend to migrate big containers (~50GB+)!</p>
<h4 id="heading-kubeadm">Kubeadm</h4>
<p>Install as above for the master node:</p>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>


wget https:<span class="hljs-comment">//github.com/elchead/kubernetes/releases/download/v8.1.0/kubeadm</span>
chmod <span class="hljs-operator">+</span>x ./kubeadm
</code></pre><h2 id="heading-4-kubernetes-bootstrapping">4. Kubernetes bootstrapping</h2>
<h3 id="heading-networking">Networking</h3>
<p>First, we set up the networking and containerd. I followed <a target="_blank" href="https://blog.nillsf.com/index.php/2021/10/29/setting-up-kubernetes-on-azure-using-kubeadm/">this tutorial</a>, but it might not be necessary:</p>
<pre><code> <span class="hljs-number">1</span>
 <span class="hljs-number">2</span>
 <span class="hljs-number">3</span>
 <span class="hljs-number">4</span>
 <span class="hljs-number">5</span>
 <span class="hljs-number">6</span>
 <span class="hljs-number">7</span>
 <span class="hljs-number">8</span>
 <span class="hljs-number">9</span>
<span class="hljs-number">10</span>
<span class="hljs-number">11</span>
<span class="hljs-number">12</span>
<span class="hljs-number">13</span>
<span class="hljs-number">14</span>
<span class="hljs-number">15</span>
<span class="hljs-number">16</span>
<span class="hljs-number">17</span>
<span class="hljs-number">18</span>
<span class="hljs-number">19</span>
<span class="hljs-number">20</span>
<span class="hljs-number">21</span>
<span class="hljs-number">22</span>
<span class="hljs-number">23</span>
<span class="hljs-number">24</span>
<span class="hljs-number">25</span>
<span class="hljs-number">26</span>
<span class="hljs-number">27</span>
<span class="hljs-number">28</span>
<span class="hljs-number">29</span>
<span class="hljs-number">30</span>
<span class="hljs-number">31</span>
<span class="hljs-number">32</span>


<span class="hljs-string">cat</span> <span class="hljs-string">&lt;&lt;EOF</span> <span class="hljs-string">|</span> <span class="hljs-string">sudo</span> <span class="hljs-string">tee</span> <span class="hljs-string">/etc/modules-load.d/containerd.conf</span>
<span class="hljs-string">overlay</span>
<span class="hljs-string">br_netfilter</span>
<span class="hljs-string">EOF</span>

<span class="hljs-string">sudo</span> <span class="hljs-string">modprobe</span> <span class="hljs-string">overlay</span>
<span class="hljs-string">sudo</span> <span class="hljs-string">modprobe</span> <span class="hljs-string">br_netfilter</span>

<span class="hljs-comment"># Setup required sysctl params, these persist across reboots.</span>
<span class="hljs-string">cat</span> <span class="hljs-string">&lt;&lt;EOF</span> <span class="hljs-string">|</span> <span class="hljs-string">sudo</span> <span class="hljs-string">tee</span> <span class="hljs-string">/etc/sysctl.d/99-kubernetes-cri.conf</span>
<span class="hljs-string">net.bridge.bridge-nf-call-iptables</span>  <span class="hljs-string">=</span> <span class="hljs-number">1</span>
<span class="hljs-string">net.ipv4.ip_forward</span>                 <span class="hljs-string">=</span> <span class="hljs-number">1</span>
<span class="hljs-string">net.bridge.bridge-nf-call-ip6tables</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>
<span class="hljs-string">EOF</span>

<span class="hljs-comment"># Apply sysctl params without reboot</span>
<span class="hljs-string">sudo</span> <span class="hljs-string">sysctl</span> <span class="hljs-string">--system</span>

<span class="hljs-string">sudo</span> <span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">/etc/containerd</span>
<span class="hljs-string">containerd</span> <span class="hljs-string">config</span> <span class="hljs-string">default</span> <span class="hljs-string">|</span> <span class="hljs-string">sudo</span> <span class="hljs-string">tee</span> <span class="hljs-string">/etc/containerd/config.toml</span>

<span class="hljs-string">sudo</span> <span class="hljs-string">systemctl</span> <span class="hljs-string">restart</span> <span class="hljs-string">containerd</span>

<span class="hljs-string">cat</span> <span class="hljs-string">&lt;&lt;EOF</span> <span class="hljs-string">|</span> <span class="hljs-string">sudo</span> <span class="hljs-string">tee</span> <span class="hljs-string">/etc/modules-load.d/k8s.conf</span>
<span class="hljs-string">br_netfilter</span>
<span class="hljs-string">EOF</span>

<span class="hljs-string">cat</span> <span class="hljs-string">&lt;&lt;EOF</span> <span class="hljs-string">|</span> <span class="hljs-string">sudo</span> <span class="hljs-string">tee</span> <span class="hljs-string">/etc/sysctl.d/k8s.conf</span>
<span class="hljs-string">net.bridge.bridge-nf-call-ip6tables</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>
<span class="hljs-string">net.bridge.bridge-nf-call-iptables</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span>
<span class="hljs-string">EOF</span>
<span class="hljs-string">sudo</span> <span class="hljs-string">sysctl</span> <span class="hljs-string">--system</span>
</code></pre><h3 id="heading-kubeadm-init">Kubeadm init</h3>
<p>On the master node, create a config file in your home directory:</p>
<pre><code> <span class="hljs-number">1</span>
 <span class="hljs-number">2</span>
 <span class="hljs-number">3</span>
 <span class="hljs-number">4</span>
 <span class="hljs-number">5</span>
 <span class="hljs-number">6</span>
 <span class="hljs-number">7</span>
 <span class="hljs-number">8</span>
 <span class="hljs-number">9</span>
<span class="hljs-number">10</span>
<span class="hljs-number">11</span>
<span class="hljs-number">12</span>


<span class="hljs-string">vim</span> <span class="hljs-string">conf.yaml</span>
<span class="hljs-attr">apiServer:</span>
  <span class="hljs-attr">timeoutForControlPlane:</span> <span class="hljs-string">4m0s</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">kubeadm.k8s.io/v1beta2</span>
<span class="hljs-attr">imageRepository:</span> <span class="hljs-string">sadrian99</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">ClusterConfiguration</span>
<span class="hljs-attr">kubernetesVersion:</span> <span class="hljs-string">v1.19.16</span>
<span class="hljs-meta">---</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">kubeadm.k8s.io/v1beta2</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">InitConfiguration</span>
<span class="hljs-attr">nodeRegistration:</span>
  <span class="hljs-attr">criSocket:</span> <span class="hljs-string">"/run/containerd/containerd.sock"</span>
</code></pre><p>Through this, the registry with the customized Kubernetes components will be used.</p>
<p>Then, start the cluster with: <code>./kubeadm init --upload-certs --cri-socket "/run/containerd/containerd.sock" --config conf.yaml</code></p>
<p>Note: Ideally, the cluster is exposed with a DNS endpoint, but this did not work for me!</p>
<p>As the output indicates, perform:</p>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>
<span class="hljs-number">3</span>


mkdir <span class="hljs-operator">-</span>p $HOME<span class="hljs-operator">/</span>.kube
sudo cp <span class="hljs-operator">-</span>i <span class="hljs-operator">/</span>etc<span class="hljs-operator">/</span>kubernetes<span class="hljs-operator">/</span>admin.conf $HOME<span class="hljs-operator">/</span>.kube/config
sudo chown $(id <span class="hljs-operator">-</span>u):$(id <span class="hljs-operator">-</span>g) $HOME<span class="hljs-operator">/</span>.kube/config
</code></pre><p>Then, copy the output to join the cluster on a worker node. It looks like this:</p>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>


kubeadm join <span class="hljs-number">192.168</span><span class="hljs-number">.0</span><span class="hljs-number">.7</span>:<span class="hljs-number">6443</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span>token 90djfo.2386gkxicg6y2ywo \
    <span class="hljs-operator">-</span><span class="hljs-operator">-</span>discovery<span class="hljs-operator">-</span>token<span class="hljs-operator">-</span>ca<span class="hljs-operator">-</span>cert<span class="hljs-operator">-</span>hash <span class="hljs-built_in">sha256</span>:0834d5d47c16799e2b1b4df3431923570549fe903f9339f875dd1f2f9d2dd2ef
</code></pre><p>Finally, set up CNI:</p>
<pre><code><span class="hljs-number">1</span>


kubectl apply <span class="hljs-operator">-</span>f <span class="hljs-string">"https://cloud.weave.works/k8s/net?k8s-version=1.19"</span>
</code></pre><h3 id="heading-verify-cluster-state">Verify cluster state</h3>
<p><code>kubectl get node -w</code> Should show a ready node after a few seconds.</p>
<p><code>kubectl get po -A</code> Should show all pods running (including coredns!).</p>
<h3 id="heading-debugging">(Debugging)</h3>
<p>If the node does not get ready, check the logs of the kubelet:</p>
<pre><code><span class="hljs-number">1</span>


journalctl <span class="hljs-operator">-</span>u kubelet [<span class="hljs-operator">-</span>f]
</code></pre><p>Or if you don’t find any hints from there, look here:</p>
<pre><code><span class="hljs-number">1</span>


journalctl <span class="hljs-operator">-</span>u  containerd [<span class="hljs-operator">-</span>f]
</code></pre><p>It happened to me, that I got the error <code>cni plugin not initialized</code>. If this is the case, be sure to repeat step the CNI plugin installation from above again.</p>
<p>If a pod is not running, use <code>kubectl describe</code> to debug.</p>
<p>Otherwise, a cluster reset might also help: <code>./kubeadm reset --cri-socket "/run/containerd/containerd.sock"</code></p>
<h3 id="heading-join-worker">Join worker</h3>
<p>Install crictl as prequisite:</p>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>
<span class="hljs-number">3</span>
<span class="hljs-number">4</span>


VERSION<span class="hljs-operator">=</span><span class="hljs-string">"v1.23.0"</span>
wget https:<span class="hljs-comment">//github.com/kubernetes-sigs/cri-tools/releases/download/$VERSION/crictl-$VERSION-linux-amd64.tar.gz</span>
sudo tar zxvf crictl<span class="hljs-operator">-</span>$VERSION<span class="hljs-operator">-</span>linux<span class="hljs-operator">-</span>amd64.tar.gz <span class="hljs-operator">-</span>C <span class="hljs-operator">/</span>usr<span class="hljs-operator">/</span>local<span class="hljs-operator">/</span>bin
rm <span class="hljs-operator">-</span>f crictl<span class="hljs-operator">-</span>$VERSION<span class="hljs-operator">-</span>linux<span class="hljs-operator">-</span>amd64.tar.gz
</code></pre><p>On the worker node, run the previously copied join command. You might need to add <code>--cri-socket "/run/containerd/containerd.sock"</code>:</p>
<pre><code><span class="hljs-number">1</span>


./kubeadm join <span class="hljs-number">192.168</span><span class="hljs-number">.0</span><span class="hljs-number">.7</span>:<span class="hljs-number">6443</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span>token 90djfo.2386gkxicg6y2ywo     <span class="hljs-operator">-</span><span class="hljs-operator">-</span>discovery<span class="hljs-operator">-</span>token<span class="hljs-operator">-</span>ca<span class="hljs-operator">-</span>cert<span class="hljs-operator">-</span>hash <span class="hljs-built_in">sha256</span>:0834d5d47c16799e2b1b4df3431923570549fe903f9339f875dd1f2f9d2dd2ef <span class="hljs-operator">-</span><span class="hljs-operator">-</span>cri<span class="hljs-operator">-</span>socket <span class="hljs-string">"/run/containerd/containerd.sock"</span>
</code></pre><p>To verify, go back to the master node and check that the new node appears ready.</p>
<h3 id="heading-copy-kubeconfig">Copy kubeconfig</h3>
<p>Now, copy the kubeconfig from the master node (<code>$HOME/.kube/config</code>) to the worker node inside <code>config</code> and export it: <code>export KUBECONFIG=$HOME/config</code></p>
<p>The migration PoC needs the kubeconfig inside a special directory:</p>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>


mkdir <span class="hljs-operator">-</span>p <span class="hljs-operator">/</span><span class="hljs-keyword">var</span><span class="hljs-operator">/</span>lib<span class="hljs-operator">/</span>kubelet
cp $HOME<span class="hljs-operator">/</span>config <span class="hljs-operator">/</span><span class="hljs-keyword">var</span><span class="hljs-operator">/</span>lib<span class="hljs-operator">/</span>kubelet<span class="hljs-operator">/</span>kubeconfig
</code></pre><h2 id="heading-5-demo-test-migration">5. Demo (test migration)</h2>
<h3 id="heading-deploy-stateful-pod">Deploy stateful pod</h3>
<p>Now it’s time to test the migration, with a simple memory allocating pod (here 50 MB): <code>kubectl run counter1 --restart=Never --image "ghcr.io/schrej/podmigration-testapp:latest" -- -m 50</code>. It’s important to set <code>restartPolicy:Never</code> to prevent the original container from restarting during migration (relevant for large migrations)!</p>
<p>Through <code>kubectl get po -owide</code>, you can get pod IP and increment a stateful counter. Be sure to do this on the worker node: <code>curl $POD_IP:8080</code> Repeat the counter increment a few times, to validate the successful migration later.</p>
<h3 id="heading-clone-pod">Clone pod</h3>
<p>The pod spec is identical, except that it has an additional field <code>spec.clonePod</code> :</p>
<pre><code> <span class="hljs-number">1</span>
 <span class="hljs-number">2</span>
 <span class="hljs-number">3</span>
 <span class="hljs-number">4</span>
 <span class="hljs-number">5</span>
 <span class="hljs-number">6</span>
 <span class="hljs-number">7</span>
 <span class="hljs-number">8</span>
 <span class="hljs-number">9</span>
<span class="hljs-number">10</span>
<span class="hljs-number">11</span>
<span class="hljs-number">12</span>
<span class="hljs-number">13</span>
<span class="hljs-number">14</span>
<span class="hljs-number">15</span>
<span class="hljs-number">16</span>
<span class="hljs-number">17</span>
<span class="hljs-number">18</span>
<span class="hljs-number">19</span>


<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Pod</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">creationTimestamp:</span> <span class="hljs-literal">null</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">run:</span> <span class="hljs-string">counter1</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">mcounter1</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">clonePod:</span> <span class="hljs-string">counter1</span>
  <span class="hljs-attr">containers:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">args:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">-m</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">"50"</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">ghcr.io/schrej/podmigration-testapp:latest</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">counter1</span>
      <span class="hljs-attr">resources:</span> {}
  <span class="hljs-attr">dnsPolicy:</span> <span class="hljs-string">ClusterFirst</span>
  <span class="hljs-attr">restartPolicy:</span> <span class="hljs-string">Never</span>
<span class="hljs-attr">status:</span> {}
</code></pre><p>The migration should be very fast. Currently, the old pod gets broken during the migration. But the cloned pod should be running. Requesting it’s endpoint with <code>curl</code> should return a number bigger than 1. Voila - you have successfully cloned a stateful pod in Kubernetes!</p>
<h2 id="heading-6-set-up-file-server">6. Set up file server</h2>
<h3 id="heading-important-warning">Important warning</h3>
<p>I had consistency problems for bigger file uploads with SMB. The container restore command is issued 1 second after the disk checkpoint has been saved completely. However, at this time not all files of the checkpoint directory were uploaded successfully.</p>
<p>I circumvented this problem by storing the checkpoint on local disk and only storing a zipped archive on the server. The temporary local-disk location is <code>/var/lib/kubelet/check</code>. Since, the OS disk is usually only 30GB, you will need to create a symbolic link to a bigger disk. In my case, a temporary disk with 500GB was mounted in <code>/mnt</code>. To solve this, do:</p>
<pre><code><span class="hljs-number">1</span>
<span class="hljs-number">2</span>


rm <span class="hljs-operator">-</span>r <span class="hljs-operator">/</span><span class="hljs-keyword">var</span><span class="hljs-operator">/</span>lib<span class="hljs-operator">/</span>kubelet<span class="hljs-operator">/</span>check<span class="hljs-operator">/</span>
ln <span class="hljs-operator">-</span>s <span class="hljs-operator">/</span>mnt <span class="hljs-operator">/</span><span class="hljs-keyword">var</span><span class="hljs-operator">/</span>lib<span class="hljs-operator">/</span>kubelet<span class="hljs-operator">/</span>check
</code></pre><p>Interestingly, the compression immensly reduced the checkpoint size for the simple example app. For 50GB of allocated memory, the compressed zip was only around 20MB! This modification was done inside containerd in the branch <code>checkpoint-zip</code>.</p>
<h3 id="heading-steps">Steps</h3>
<p>The procedure is specific to Azure and is well documented <a target="_blank" href="https://docs.microsoft.com/en-us/azure/storage/files/storage-how-to-use-files-linux?tabs=smb311">here</a>. The server should be mounted inside <code>/var/libe/kubelet/migration</code>. I used the static mount and my /etc/fstab entry looks like this:</p>
<pre><code><span class="hljs-number">1</span>


/<span class="hljs-regexp">/SERVER_URL/checkpoints</span> /var/lib/kubelet/migration cifs nofail,credentials=<span class="hljs-regexp">/etc/smbcredentials</span><span class="hljs-regexp">/STORAGECLASSNAME.cred,serverino,cache=none</span>
</code></pre><h2 id="heading-development">Development</h2>
<p>If you want to develop further or quickly test changes, it is much easier to work with a local cluster. Inside the kubernetes repo root, run:</p>
<pre><code><span class="hljs-number">1</span>


CONTAINER_RUNTIME<span class="hljs-operator">=</span>remote CONTAINER_RUNTIME_ENDPOINT<span class="hljs-operator">=</span><span class="hljs-string">"unix:///run/containerd/containerd.sock"</span> CGROUP_DRIVER<span class="hljs-operator">=</span><span class="hljs-string">"systemd"</span> KUBELET_AUTHORIZATION_WEBHOOK<span class="hljs-operator">=</span><span class="hljs-string">"false"</span> KUBELET_FLAGS<span class="hljs-operator">=</span><span class="hljs-string">"--read-only-port=0 --anonymous-auth=true --authorization-mode=AlwaysAllow"</span> ./hack<span class="hljs-operator">/</span>local<span class="hljs-operator">-</span>up<span class="hljs-operator">-</span>cluster.sh
</code></pre><p>To read the logs for the kubelet, you can use: <code>tail -f /tmp/kubelet.log</code>.</p>
<h2 id="heading-end">End</h2>
<p>I admit that this was a long tutorial and it’s likely not everything went smooth while following along. If you are stuck at some step, you can contact me and I can try to help :)</p>
]]></content:encoded></item><item><title><![CDATA[[Tutorial]: K8s Monitoring with InfluxDB's Telegraf]]></title><description><![CDATA[Contents


1. Download helm chart repo and add to helm:
2. Deploy Influxdb2
3. Add Telegraf Configuration in Influxdb
4. Deploy Telegraf
5. View metrics data
6. Connect local client with DB



InfluxDB is an Open-Source timeseries database which can ...]]></description><link>https://blog.adrianstobbe.com/tutorial-k8s-monitoring-with-influxdbs-telegraf</link><guid isPermaLink="true">https://blog.adrianstobbe.com/tutorial-k8s-monitoring-with-influxdbs-telegraf</guid><dc:creator><![CDATA[Adrian Stobbe]]></dc:creator><pubDate>Fri, 04 Mar 2022 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p><img src="undefined" alt /></p>
<p>Contents</p>
<ul>
<li><ul>
<li><a target="_blank" href="https://astobbe.me/#1-download-helm-chart-repohttpsgithubcominfluxdatahelm-charts-and-add-to-helm">1. Download</a> <a target="_blank" href="https://github.com/influxdata/helm-charts">helm chart repo</a> and add to helm:</li>
<li><a target="_blank" href="https://astobbe.me/#2-deploy-influxdb2">2. Deploy Influxdb2</a></li>
<li><a target="_blank" href="https://astobbe.me/#3-add-telegraf-configuration-in-influxdb">3. Add Telegraf Configuration in Influxdb</a></li>
<li><a target="_blank" href="https://astobbe.me/#4-deploy-telegraf">4. Deploy Telegraf</a></li>
<li><a target="_blank" href="https://astobbe.me/#5-view-metrics-data">5. View metrics data</a></li>
<li><a target="_blank" href="https://astobbe.me/#6-connect-local-client-with-db">6. Connect local client with DB</a></li>
</ul>
</li>
</ul>
<p>InfluxDB is an Open-Source timeseries database which can be used for monitoring Kubernetes clusters. In this tutorial, I want to show you step by step how to get a dashboard with metrics on Kuberntes resource usage of podes and nodes. It’s not difficult, but the official documentation is outdated and confusing, so I hope to make it easier for you. We will use InfluxDB2 which includes a nice dashboard and supports the Flux QL query language.</p>
<h3 id="heading-1-download-helm-chart-repohttpsgithubcominfluxdatahelm-charts-and-add-to-helm">1. Download <a target="_blank" href="https://github.com/influxdata/helm-charts">helm chart repo</a> and add to helm:</h3>
<pre><code>helm repo <span class="hljs-keyword">add</span> influxdata https:<span class="hljs-comment">//helm.influxdata.com/</span>
</code></pre><h3 id="heading-2-deploy-influxdb2">2. Deploy Influxdb2</h3>
<p>No settings need to be adjusted:</p>
<p><code>helm upgrade --install influx2 influxdata/influxdb2</code></p>
<p>But you should note down the password for the admin user:</p>
<p><code>echo $(kubectl get secret influx2-influxdb2-auth -o "jsonpath={.data['admin-password']}" --namespace tick | base64 --decode)</code></p>
<p>To open the Influx UI, do:</p>
<p><code>kubectl port-forward svc/influx2-influxdb2 8081:80</code></p>
<p>Now, you can open <code>localhost:8081</code> and log in with user: admin and the before copied password.</p>
<h3 id="heading-3-add-telegraf-configuration-in-influxdb">3. Add Telegraf Configuration in Influxdb</h3>
<p>In the UI sidebar, click <code>Data</code> and add “Telegraf Configuration”. Choose Kubernetes and enter a name: (e.g <code>telegraf</code>). Then, copy the API token.</p>
<h3 id="heading-4-deploy-telegraf">4. Deploy Telegraf</h3>
<p>We choose the Daemonset <code>telegraf-ds</code>, because it is expected for the <code>Kuberntes input</code> plugin, which among many others observes the node and pod resources. Now open the <code>values.yaml</code> and add the following information under <code>config.outputs</code>:</p>
<pre><code>    <span class="hljs-bullet">-</span> <span class="hljs-attr">influxdb_v2:</span>
        <span class="hljs-attr">urls:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">"http://influx2-influxdb2.tick.svc"</span>
        <span class="hljs-attr">token:</span> <span class="hljs-string">INSERT_TOKEN</span>
        <span class="hljs-attr">organization:</span> <span class="hljs-string">"influxdata"</span>
        <span class="hljs-attr">bucket:</span> <span class="hljs-string">"default"</span>
</code></pre><p>The input plugins are configured by default.</p>
<p>Then deploy the chart:</p>
<pre><code>helm upgrade <span class="hljs-operator">-</span><span class="hljs-operator">-</span>install ds<span class="hljs-operator">-</span>telegraf influxdata<span class="hljs-operator">/</span>telegraf<span class="hljs-operator">-</span>ds <span class="hljs-operator">-</span>f values.yaml
</code></pre><h3 id="heading-5-view-metrics-data">5. View metrics data</h3>
<p>If all went well, you should now receive metrics data and see them in the UI under <code>Explore</code>:</p>
<p><img src="undefined" alt="dashboard-memory-influx.png" /></p>
<h3 id="heading-6-connect-local-client-with-db">6. Connect local client with DB</h3>
<p>If you want to query the DB through a locally installed CLI client or their Go-client, you can use the port-forwarding from earlier and use this adress to connect: <code>kubectl port-forward svc/influx2-influxdb2 8081:80</code>.</p>
<p>This step was not so intuitive to me, because in InfluxDb 1 the service exposed the 8086 port for this. But now the UI and the database connection are both reachable through this port.</p>
<p>To test the connection with the CLI you can use:</p>
<p><code>influx ping --host http://localhost:8081</code></p>
<p>This is all there is to it. If you have any questions or find that my tutorial got outdated, please contact me :)</p>
]]></content:encoded></item><item><title><![CDATA[Testable code has few mocks]]></title><description><![CDATA[Testing can sometimes seem hard and tedious. We might be faced with complex setup logic and many mocks. But this is a smell of poor code design. When properly done, mocks are rarely needed. TDD helps to avoid tight coupling and it naturally tends tow...]]></description><link>https://blog.adrianstobbe.com/testable-code-has-few-mocks</link><guid isPermaLink="true">https://blog.adrianstobbe.com/testable-code-has-few-mocks</guid><category><![CDATA[Testing]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Adrian Stobbe]]></dc:creator><pubDate>Wed, 24 Nov 2021 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Testing can sometimes seem hard and tedious. We might be faced with complex setup logic and many mocks. But this is a smell of poor code design. When properly done, mocks are rarely needed. TDD helps to avoid tight coupling and it naturally tends towards functional code. In this post, I cover how the functional style leads to less error prone code and fewer code to be tested. Moreover, we will explore when it is proper to use mocks.</p>
<h2 id="heading-integration-vs-operation">Integration vs Operation</h2>
<p>When solving a problem the process is always to break it up into smaller pieces. The problem solution is then just the composition of the smaller units. So we define functions for each subproblem and one integration function to solve the big problem. There should be a clear distinction between operation and integration (also see <a target="_blank" href="https://clean-code-developer.com/grades/grade-1-red/#Integration_Operation_Segregation_Principle_IOSP">Integration Operation Segregation Principle</a>). Each small unit should be independent, i.e. unaware of the other parts in the composition.</p>
<blockquote>
<p>Mocking is required when our decomposition strategy has failed, Eric Elliott</p>
</blockquote>
<h2 id="heading-function-composition">Function composition</h2>
<p>What was new to me is that composing functions do not need to be unit tested when they are truly independent. Because in such case we can use a generic composition utility.</p>
<p>Let's look at an example[^1]. The imperative and obvious solution to integrate is this:</p>
<p>[^1]: adopted from <a target="_blank" href="https://medium.com/javascript-scene/mocking-is-a-code-smell-944a70c90a6a">Source</a></p>
<pre><code class="lang-js"><span class="hljs-comment">// Imperative composition</span>
<span class="hljs-keyword">const</span> composition = <span class="hljs-function">(<span class="hljs-params">x</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> afterG = g(x);
  <span class="hljs-keyword">const</span> afterF = f(afterG);
  <span class="hljs-keyword">return</span> afterF;
};
</code></pre>
<p>In languages without first-class functions, there might be no way around this. But in most popular languages, such as JavaScript, you can do better. Function composition is declarative and avoids bugs such as passing or returning the wrong variable.</p>
<p>For the declarative composition, you can either define your own pipe (which could be error-prone) or use a library[^1]:</p>
<pre><code class="lang-js"><span class="hljs-comment">// import pipe from 'lodash/fp/flow';</span>
<span class="hljs-keyword">const</span> pipe = <span class="hljs-function">(<span class="hljs-params">...fns</span>) =&gt;</span> <span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> fns.reduce(<span class="hljs-function">(<span class="hljs-params">y, f</span>) =&gt;</span> f(y), x);
<span class="hljs-comment">// Functions to compose</span>
<span class="hljs-keyword">const</span> g = <span class="hljs-function"><span class="hljs-params">n</span> =&gt;</span> n + <span class="hljs-number">1</span>;
<span class="hljs-keyword">const</span> f = <span class="hljs-function"><span class="hljs-params">n</span> =&gt;</span> n * <span class="hljs-number">2</span>;
<span class="hljs-comment">// Declarative composition</span>
<span class="hljs-keyword">const</span> doStuffBetter = pipe(g, f);
doStuffBetter(<span class="hljs-number">20</span>), <span class="hljs-comment">// 42</span>
</code></pre>
<p>Note that <code>reduce</code> applies the accumulator on each value from left to right! There is also a reverse variant called <code>reduceRight</code>. This reduction would have given 41 as result.</p>
<p>Commonly, we have asynchronous calls in our code, but we can also compose promises! I think this is also where it really pays off - when you compose calls with side effects. Unit testing the integration function becomes tedious, because we need to stub all participants[^1]:</p>
<pre><code class="lang-js"><span class="hljs-comment">// imperative</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">uploadFiles</span>(<span class="hljs-params">{ user, folder, files }</span>) </span>{
  <span class="hljs-keyword">const</span> dbUser = <span class="hljs-keyword">await</span> readUser({ user });
  <span class="hljs-keyword">const</span> folderInfo = <span class="hljs-keyword">await</span> getFolderInfo({ folder });
  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">await</span> haveWriteAccess({ dbUser, folderInfo })) {
    <span class="hljs-keyword">return</span> uploadToFolder({ dbUser, folderInfo, files });
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"No write access to that folder"</span>);
  }
}

<span class="hljs-comment">// declarative</span>
<span class="hljs-keyword">const</span> asyncPipe =
  <span class="hljs-function">(<span class="hljs-params">...fns</span>) =&gt;</span>
  <span class="hljs-function">(<span class="hljs-params">x</span>) =&gt;</span>
    fns.reduce(<span class="hljs-keyword">async</span> (y, f) =&gt; f(<span class="hljs-keyword">await</span> y), x);
<span class="hljs-keyword">const</span> uploadFiles = asyncPipe(
  readUser,
  getFolderInfo,
  haveWriteAccess,
  uploadToFolder
);

uploadFiles({ user, folder, files }).then(log);
</code></pre>
<p>As you see, the declarative <code>uploadFiles</code> function is just a function call - no logic to be tested! The correctness of the step order is not assured, but in most cases this is covered in integration tests. If it's complex logic, you might still write a unit test to test the correct step order of the composition.</p>
<p>I agree that the declarative implementation (<code>asyncPipe</code>) is more difficult to understand at first, but it is less error prone and more concise. <code>asyncPipe</code> is given an array of functions that it reduces. <code>y</code> is the previous result and we apply <code>f</code> on it's result. The second return value <code>x</code> is the initial value. This syntax confused me a bit, but the initial value obviously needs to be provided at some place. This is functional programming!</p>
<h2 id="heading-the-merits-of-functional-programming">The merits of Functional Programming</h2>
<p>The paradigm leads to code that is easier to test, because it is a stateless input / output machine. Moreover, it leaves less room for bugs, because you only declare what you want to perform instead of how to do it (imperative). However, it's not possible to only rely on functional programming, because applications are stateful and have side effects (network requests, file operations, logging...). A good strategy is to keep the business logic functional and move side effects to the outer boundary.</p>
<p>Let's consider the example of an online shop that decides to give some premium benefits to its loyal customers. Premium customers might benefit from special discounts and free shipping so we want to update their status in the database. On the other hand, we might want to reach out to the inactive customers. The status of our customer is clearly business logic and it should not be mixed with database logic. One approach would be to pass a database interface and mock it in the test.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateCustomer</span>(<span class="hljs-params">today,entry,db</span>) </span>{
    <span class="hljs-keyword">if</span>(isLoyalCustomer(today,entry.date)){
        newEntry = {<span class="hljs-attr">premium</span>: <span class="hljs-literal">true</span>, ...entry}
        db.UpdateEntry(newEntry)
    }
    <span class="hljs-keyword">if</span>(isInactiveCustomer(today,entry.date){
        newEntry = {<span class="hljs-attr">inactive</span>: <span class="hljs-literal">true</span>, ...entry}
        <span class="hljs-keyword">return</span> db.UpdateEntry(newEntry)
    })
}
</code></pre>
<p>But there is a better approach that is declarative and easier to test:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateCustomer</span>(<span class="hljs-params">today,entry</span>) </span>{
    <span class="hljs-keyword">if</span>(isLoyalCustomer(today,entry.date)){
        newEntry = {<span class="hljs-attr">premium</span>: <span class="hljs-literal">true</span>, ...entry}
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> FileUpdate(newEntry)
    }
    <span class="hljs-keyword">if</span>(isInactiveCustomer(today,entry.date){
        newEntry = {<span class="hljs-attr">inactive</span>: <span class="hljs-literal">true</span>, ...entry}
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> FileUpdate(newEntry)
    })
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> NoUpdate(entry)

}
</code></pre>
<p>It's clearer in the intent that the output is a return value instead of an input value with side effects. Also, there is no need to mock! Of course there is still a missing piece for this variant - the mutable shell that applies the side effects. In our case, there would be a <code>Persister</code>, which is a database wrapper that reads the update instructions. It's correctness would be covered in the integration test.</p>
<h2 id="heading-when-to-use-mocks">When to use mocks</h2>
<p>Be aware that mocks are sometimes the only way to test logic. But think what exactly needs to be tested. For testing the request handler of our express app, it's not necessary to create a mockserver. We only want to test the handler logic. Express logic to create the server with port allocation etc. does not need to be tested by us!</p>
<p>I find the distinction between handlers and servers especially clear in Golang:</p>
<pre><code class="lang-go">handler := newHelloHandler()

request := newRequest(<span class="hljs-string">"Floyd"</span>)
response := httptest.NewRecorder()

handler.ServeHTTP(response, request)
assertStatus(t, response.Code, http.StatusOK)
assertResponseBody(t, response.Body.String(), <span class="hljs-string">"Hello Floyd"</span>)
</code></pre>
<p>The handler object includes all logic how to process requests, so we can use it to test the correct behavior. Note how only the response needs to be mocked (spied to be precise).</p>
<p>In Express.js, we do the same. We test the handler function and spy the response object.</p>
<pre><code class="lang-plaintext">const helloHandler  = (req, res) =&gt; res.send('Hello World!');

const expected = 'Hello World!';
const resSpy = {
    send: (actual) =&gt; assert.Equal(actual,expected)
}
hello({}, resSpy);
</code></pre>
<p>The request handler is at the outer layer of our application, i.e. it connects with external dependencies to cause side effects. In our case the external dependency is the response object which makes a network call. In that case it is justified and necessary to mock. The general guideline is to only mock external dependencies.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>By pushing the side effects to the outer layer of the application, we can mostly avoid mocks. Functional code is easy to test and allows to use function composition. This reduces the surface for bugs and saves us to write unit tests for integration functions. Mocks have its place, but if it's not an external dependency it might be code smell.</p>
<p>If you want to learn more about testing, I can highly recommend <a target="_blank" href="https://www.manning.com/books/unit-testing">Unit Testing Principles, Practices, and Patterns</a>.</p>
]]></content:encoded></item></channel></rss>