<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.0">Jekyll</generator><link href="https://blog.zespre.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://blog.zespre.com/" rel="alternate" type="text/html" /><updated>2023-05-28T05:51:48+00:00</updated><id>https://blog.zespre.com/feed.xml</id><title type="html">Zespre’s Blog</title><subtitle>A real sysadmin doesn't have colleagues, only enemies.
</subtitle><author><name>Zespre Schmidt</name></author><entry><title type="html">Changing Container Runtime from Docker to containerd</title><link href="https://blog.zespre.com/changing-container-runtime-from-docker-to-containerd.html" rel="alternate" type="text/html" title="Changing Container Runtime from Docker to containerd" /><published>2023-02-27T00:00:00+00:00</published><updated>2023-02-27T00:00:00+00:00</updated><id>https://blog.zespre.com/changing-container-runtime-from-docker-to-containerd</id><content type="html" xml:base="https://blog.zespre.com/changing-container-runtime-from-docker-to-containerd.html">&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dockershim&lt;/code&gt; has been announced as deprecated since Kubernetes v1.20. And
Kubernetes v1.23 is about to step into its EOL after the end of this month. So
it’s time to move forward! Let’s upgrade the cluster to v1.24. This article is
solely for the personal record about changing container runtimes to upgrade
Kubernetes from v1.23 to v1.24.&lt;/p&gt;

&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;

&lt;p&gt;Firstly, make sure what container runtime you are using:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl get nodes &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; wide
NAME                               STATUS   ROLES                  AGE     VERSION    INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION       CONTAINER-RUNTIME
k8s-master.internal.zespre.com     Ready    control-plane,master   2y97d   v1.23.15   192.168.88.111   &amp;lt;none&amp;gt;        Ubuntu 18.04.5 LTS   4.15.0-194-generic   docker://20.10.7
k8s-worker-1.internal.zespre.com   Ready    &amp;lt;none&amp;gt;                 2y97d   v1.23.15   192.168.88.112   &amp;lt;none&amp;gt;        Ubuntu 18.04.5 LTS   4.15.0-194-generic   docker://20.10.7
k8s-worker-2.internal.zespre.com   Ready    &amp;lt;none&amp;gt;                 2y97d   v1.23.15   192.168.88.113   &amp;lt;none&amp;gt;        Ubuntu 18.04.5 LTS   4.15.0-194-generic   docker://20.10.7
k8s-worker-3.internal.zespre.com   Ready    &amp;lt;none&amp;gt;                 2y50d   v1.23.15   192.168.88.114   &amp;lt;none&amp;gt;        Ubuntu 18.04.5 LTS   4.15.0-194-generic   docker://20.10.7
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For my environment, it is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker://20.10.7&lt;/code&gt;. So it’s a must to change to other
CRI-compliant container runtimes.&lt;/p&gt;

&lt;p&gt;Drain the node that you want to work on:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubelet drain &amp;lt;node&amp;gt; &lt;span class=&quot;nt&quot;&gt;--ignore-daemonsets&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--delete-emptydir-data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Stop the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubelet&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker&lt;/code&gt; on the node:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl stop kubelet.service
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl disable docker.service &lt;span class=&quot;nt&quot;&gt;--now&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;

&lt;h3 id=&quot;containerd&quot;&gt;containerd&lt;/h3&gt;

&lt;p&gt;Install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;containerd&lt;/code&gt; binaries and their corresponding service file:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;wget https://github.com/containerd/containerd/releases/download/v1.6.18/containerd-1.6.18-linux-amd64.tar.gz
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo tar&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-zxvf&lt;/span&gt; containerd-1.6.18-linux-amd64.tar.gz &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; /usr/local
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /usr/local/lib/systemd/system/
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-sfL&lt;/span&gt; https://raw.githubusercontent.com/containerd/containerd/main/containerd.service &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; /usr/local/lib/systemd/system/containerd.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl &lt;span class=&quot;nb&quot;&gt;enable &lt;/span&gt;containerd.service &lt;span class=&quot;nt&quot;&gt;--now&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;runc&quot;&gt;runc&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;wget https://github.com/opencontainers/runc/releases/download/v1.1.4/runc.amd64
&lt;span class=&quot;nb&quot;&gt;sudo install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; 755 runc.amd64 /usr/local/sbin/runc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;cni&quot;&gt;CNI&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;wget https://github.com/containernetworking/plugins/releases/download/v1.2.0/cni-plugins-linux-amd64-v1.2.0.tgz
&lt;span class=&quot;nb&quot;&gt;sudo mv&lt;/span&gt; /opt/cni/bin /opt/cni/bin.bak
&lt;span class=&quot;nb&quot;&gt;sudo mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /opt/cni/bin
&lt;span class=&quot;nb&quot;&gt;sudo tar&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-zxvf&lt;/span&gt; cni-plugins-linux-amd64-v1.2.0.tgz &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; /opt/cni/bin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /etc/containerd
containerd config default | &lt;span class=&quot;nb&quot;&gt;sudo tee&lt;/span&gt; /etc/containerd/config.toml
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl restart containerd.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;configure-kubelet-to-use-containerd&quot;&gt;Configure kubelet to Use containerd&lt;/h2&gt;

&lt;p&gt;Basically, there are two places to modify if you provision the node with
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubeadm&lt;/code&gt; at the beginning:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/lib/kubelet/kubeadm-flags.env&lt;/code&gt; file&lt;/li&gt;
  &lt;li&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubeadm.alpha.kubernetes.io/cri-socket&lt;/code&gt; annotation of the node&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/lib/kubelet/kubeadm-flags.env&lt;/code&gt;, append two flags to make &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubelet&lt;/code&gt; use
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;containerd&lt;/code&gt; as the new container runtime:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--container-runtime=remote&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--container-runtime-endpoint=unix:///run/containerd/containerd.sock&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;KUBELET_KUBEADM_ARGS=&quot;--network-plugin=cni --pod-infra-container-image=k8s.gcr.io/pause:3.2 --container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For the annotation in the node’s resource, a similar change can be done via&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl annotate node &amp;lt;node&amp;gt; kubeadm.alpha.kubernetes.io/cri-socket&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;unix:///run/containerd/containerd.sock &lt;span class=&quot;nt&quot;&gt;--overwrite&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then we’re ready to start the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubelet&lt;/code&gt; again with the new container runtime!&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl start kubelet.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After a while, you should see the container runtime of the target node becomes
something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;containerd-1.6.18&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl get nodes &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; wide
NAME                               STATUS                     ROLES                  AGE     VERSION    INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION       CONTAINER-RUNTIME
k8s-master.internal.zespre.com     Ready                      control-plane,master   2y98d   v1.23.15   192.168.88.111   &amp;lt;none&amp;gt;        Ubuntu 18.04.5 LTS   4.15.0-194-generic   docker://20.10.7
k8s-worker-1.internal.zespre.com   Ready,SchedulingDisabled   &amp;lt;none&amp;gt;                 2y98d   v1.23.15   192.168.88.112   &amp;lt;none&amp;gt;        Ubuntu 18.04.5 LTS   4.15.0-194-generic   containerd://1.6.18
k8s-worker-2.internal.zespre.com   NotReady                   &amp;lt;none&amp;gt;                 2y98d   v1.23.15   192.168.88.113   &amp;lt;none&amp;gt;        Ubuntu 18.04.5 LTS   4.15.0-194-generic   docker://20.10.7
k8s-worker-3.internal.zespre.com   Ready                      &amp;lt;none&amp;gt;                 2y51d   v1.23.15   192.168.88.114   &amp;lt;none&amp;gt;        Ubuntu 18.04.5 LTS   4.15.0-194-generic   docker://20.10.7
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And you can uncordon the node:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl uncordon &amp;lt;node&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Iterate these steps through all the nodes in the cluster.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Drain the node&lt;/li&gt;
  &lt;li&gt;Install containerd, runc, CNI binaries on the node&lt;/li&gt;
  &lt;li&gt;Configure kubelet with the new container runtime&lt;/li&gt;
  &lt;li&gt;Uncordon the node&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;cleanup&quot;&gt;Cleanup&lt;/h2&gt;

&lt;p&gt;If things are going well, you can remove the old container runtime since it’s no
longer needed. In my case, it is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[docker.io](http://docker.io)&lt;/code&gt; package to
remove:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt purge docker.io
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
  bridge-utils cgroupfs-mount containerd pigz runc ubuntu-fan
Use &lt;span class=&quot;s1&quot;&gt;'sudo apt autoremove'&lt;/span&gt; to remove them.
The following packages will be REMOVED:
  docker.io&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
0 upgraded, 0 newly installed, 1 to remove and 70 not upgraded.
After this operation, 193 MB disk space will be freed.
Do you want to &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;? &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Y/n]
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Reading database ... 141670 files and directories currently installed.&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
Removing docker.io &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;20.10.7-0ubuntu5~18.04.3&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; ...
&lt;span class=&quot;s1&quot;&gt;'/usr/share/docker.io/contrib/nuke-graph-directory.sh'&lt;/span&gt; -&amp;gt; &lt;span class=&quot;s1&quot;&gt;'/var/lib/docker/nuke-graph-directory.sh'&lt;/span&gt;
Processing triggers &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;man-db &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;2.8.3-2ubuntu0.1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; ...
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Reading database ... 141464 files and directories currently installed.&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
Purging configuration files &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;docker.io &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;20.10.7-0ubuntu5~18.04.3&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; ...
debconf: unable to initialize frontend: Dialog
debconf: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Dialog frontend requires a screen at least 13 lines tall and 31 columns wide.&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
debconf: falling back to frontend: Readline

Nuking /var/lib/docker ...
  &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;this is wrong, press Ctrl+C NOW!&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

+ &lt;span class=&quot;nb&quot;&gt;sleep &lt;/span&gt;10

+ &lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-rf&lt;/span&gt; /var/lib/docker/builder /var/lib/docker/buildkit /var/lib/docker/containers /var/lib/docker/image /var/lib/docker/network /var/lib/docker/nuke-graph-directory.sh /var/lib/docker/overlay2 /var/lib/docker/plugins /var/lib/docker/runtimes /var/lib/docker/swarm /var/lib/docker/tmp /var/lib/docker/trust /var/lib/docker/volumes
dpkg: warning: &lt;span class=&quot;k&quot;&gt;while &lt;/span&gt;removing docker.io, directory &lt;span class=&quot;s1&quot;&gt;'/etc/docker'&lt;/span&gt; not empty so not removed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;post-configs&quot;&gt;Post-configs&lt;/h2&gt;

&lt;p&gt;To make sure things won’t break after node reboots, we need to persist some
configurations currently effective on the system. Please do the following steps
on every node if there’s no similar setup existed.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt; | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt; | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;troubleshooting&quot;&gt;Troubleshooting&lt;/h2&gt;

&lt;h3 id=&quot;pleg-is-not-healthy&quot;&gt;PLEG is not healthy&lt;/h3&gt;

&lt;p&gt;When doing the work on the first node, I noticed that the second worker node
became unready even though I didn’t drain the node. And the reason for the
unready state is shown below:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Ready                Unknown   Mon, 27 Feb 2023 13:55:37 +0800   Mon, 27 Feb 2023 13:54:29 +0800   NodeStatusUnknown   &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;container runtime is down, PLEG is not healthy: pleg was
ast seen active 7m27.961229215s ago&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; threshold is 3m0s[]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The load is extremely high on the worker node:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;uptime
 &lt;/span&gt;14:56:34 up 135 days,  2:36,  1 user,  load average: 190.44, 174.46, 169.43
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://kubernetes.io/docs/tasks/administer-cluster/migrating-from-dockershim/find-out-runtime-you-use/&quot;&gt;Find Out What Container Runtime is Used on a
Node&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://kubernetes.io/docs/tasks/administer-cluster/migrating-from-dockershim/change-runtime-containerd/&quot;&gt;Changing the Container Runtime on a Node from Docker Engine to
containerd&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/containerd/containerd/blob/main/docs/getting-started.md&quot;&gt;containerd/getting-started.md at main ·
containerd/containerd&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Zespre Schmidt</name></author><category term="note" /><summary type="html">The dockershim has been announced as deprecated since Kubernetes v1.20. And Kubernetes v1.23 is about to step into its EOL after the end of this month. So it’s time to move forward! Let’s upgrade the cluster to v1.24. This article is solely for the personal record about changing container runtimes to upgrade Kubernetes from v1.23 to v1.24.</summary></entry><entry><title type="html">Blogging the Hard Way</title><link href="https://blog.zespre.com/blogging-the-hard-way.html" rel="alternate" type="text/html" title="Blogging the Hard Way" /><published>2023-02-01T00:00:00+00:00</published><updated>2023-02-01T00:00:00+00:00</updated><id>https://blog.zespre.com/blogging-the-hard-way</id><content type="html" xml:base="https://blog.zespre.com/blogging-the-hard-way.html">&lt;p&gt;There’s an old saying:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;One must first polish his/her tools to do a good job.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I love to polish things but hesitate to do real jobs. That’s my problem, and I
have realized that for a long time. Anyway, in this article, I’d like to briefly
share how I set up the on-prem infrastructure for blog hosting featuring:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;static blog with version control&lt;/li&gt;
  &lt;li&gt;containerized environment managed by Kubernetes&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;push-based&lt;/strong&gt; GitOps using Gitea and Drone&lt;/li&gt;
  &lt;li&gt;HTTPS connection secured by cert-manager&lt;/li&gt;
  &lt;li&gt;publicly accessible endpoint powered by inlets tunnel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Though I mentioned “briefly,” it is a lengthy post. So please bear with me a
little more.&lt;/p&gt;

&lt;h2 id=&quot;the-why&quot;&gt;The Why&lt;/h2&gt;

&lt;p&gt;Some of you might wonder why all of these work just for one static blog. Why
bother? And I’ll tell you: there’s no strong reason for doing this; it’s just
because it is &lt;strong&gt;cool&lt;/strong&gt;. We all love cool things, right? Besides, I learned a lot
by going through these. Do you want to get a peek at how GitOps works? Curious
about what role Kubernetes plays in modern software industries? Get your hands
dirty, and you’ll come out with your conclusion.&lt;/p&gt;

&lt;p&gt;There’s another debate on cloud and on-prem. “Why do you set up all of these
on-prem but not on the cloud,” you might ask. It may be easier to put all of
these on the cloud since there’re various services and integrations a cloud
provides that you can leverage. But it can also be harder to understand and
debug when you host everything on the cloud. I’m not saying that cloud is bad;
it’s just how I learn things. I prefer to get familiar with new tech on the
&lt;strong&gt;ground&lt;/strong&gt;, then try to get the same things running on the &lt;strong&gt;cloud&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;

&lt;p&gt;Writing articles in Markdown format using my favorite editor on mobile devices.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blogging-the-hard-way/drafts.png&quot; alt=&quot;Using Drafts.app&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Pushing changes (articles) to the Git server, and voilà!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blogging-the-hard-way/my-blog.png&quot; alt=&quot;My Blog&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And that is just the tip of the iceberg. However, the work behind the scene is
like …&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blogging-the-hard-way/overview.png&quot; alt=&quot;The Workflow and Architecture of My Blogging Infrastructure&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Yes, in case you were wondering, I’m just trying to shoot myself in the foot.
However, it does work pretty well once you set it up.&lt;/p&gt;

&lt;p&gt;The objective of this setup is to encourage me to write more. It is hard for me
to write at least one article per month, though I took many notes as
ingredients. Turning a “note” or “memo” into an “article” takes &lt;a href=&quot;https://www.openfaas.com/blog/promote-and-share/&quot;&gt;a huge amount
of effort&lt;/a&gt;. So there must be
something to stimulate and help me get through this.&lt;/p&gt;

&lt;p&gt;Watching every part collaborate, i.e., automation, brings me great joy. I
decided to build a platform with pipelines to write articles with &lt;strong&gt;version
control&lt;/strong&gt;, some basic &lt;strong&gt;syntax checks&lt;/strong&gt;, and at the same time,
&lt;strong&gt;auto-publishing&lt;/strong&gt;. The whole infrastructure consists of the following
components:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Dev environment
    &lt;ul&gt;
      &lt;li&gt;The blog itself with Jekyll static site generator&lt;/li&gt;
      &lt;li&gt;inletsctl (tunneling tool)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;On-prem Kubernetes cluster
    &lt;ul&gt;
      &lt;li&gt;MetalLB (load balancer controller)&lt;/li&gt;
      &lt;li&gt;ingress-nginx (ingress controller)&lt;/li&gt;
      &lt;li&gt;inlets-operator (tunnel service)&lt;/li&gt;
      &lt;li&gt;cert-manager&lt;/li&gt;
      &lt;li&gt;Gitea (git service)&lt;/li&gt;
      &lt;li&gt;Drone (CI/CD service)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;VPS with publicly accessible IPs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I already have a 4-node Kubernetes cluster (one control plane node and three
worker nodes) in my home lab. Though it’s not a highly available setup, it’s
enough for me as a playground. For this article, it’s doable with a single-node
cluster provisioned by &lt;a href=&quot;https://minikube.sigs.k8s.io/docs/&quot;&gt;minikube&lt;/a&gt; or
&lt;a href=&quot;https://kind.sigs.k8s.io&quot;&gt;kind&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;static-blog&quot;&gt;Static Blog&lt;/h2&gt;

&lt;p&gt;Regarding blogging and version control as requirements, static site generator
like Jekyll, Pelican, Hugo, etc., is the right choice. Here I use Jekyll to
generate my blog. You can choose whatever you want. I won’t go through the
details about how to write Markdown syntax documents and turn them into HTML
files via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll&lt;/code&gt; commands. Please refer to &lt;a href=&quot;https://jekyllrb.com/docs/&quot;&gt;Jekyll’s official
documentation&lt;/a&gt; for installation and usage if needed.
With a static site generator, you can treat Markdown files as the blog’s source
code and somewhat “compile” them to become a visualized result in the form of
HTML (of course, there’s much more like CSS and JavaScript to make the blog not
so ugly). As we all know, source code can be version-controlled, and so do
markdown files. Now the entire blog can be version-controlled using Git locally.&lt;/p&gt;

&lt;p&gt;To bootstrap a new site (blog), just run the following command in your local
environment:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gem &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;bundler jekyll
&lt;span class=&quot;nb&quot;&gt;mkdir &lt;/span&gt;blog &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;blog/
jekyll new blog
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The site structure is as follows:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;tree &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
├── 404.html
├── Gemfile
├── Gemfile.lock
├── _config.yml
├── _posts
│   └── 2023-02-01-welcome-to-jekyll.markdown
├── about.markdown
└── index.markdown

2 directories, 7 files
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The actual blog posts will be under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_posts/&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;For linting, &lt;a href=&quot;https://github.com/markdownlint/markdownlint&quot;&gt;markdownlint&lt;/a&gt;
is a good choice:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gem &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;mdl
mdl _posts/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If the output contains warnings, there are violations of the rules in the
markdown files that need to be fixed.&lt;/p&gt;

&lt;p&gt;To run the blog and see the result, you can run the following command and head
to &lt;a href=&quot;http://localhost:4000&quot;&gt;http://localhost:4000&lt;/a&gt; in your browser.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;jekyll serve
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blogging-the-hard-way/jekyll-first-sight.png&quot; alt=&quot;Jekyll First Sight&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Okay, enough Jekyll hands-on. Let’s go to the next phase.&lt;/p&gt;

&lt;h2 id=&quot;manually-run-the-blog-like-an-octopus&quot;&gt;Manually Run the Blog Like an Octopus&lt;/h2&gt;

&lt;p&gt;We’re not satisfied with the status quo. We can’t just leave the command &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle
exec jekyll serve&lt;/code&gt; running in the terminal and tell everybody “Hey, come visit
my new shiny blog!” That’s intolerable, regardless of the reachability,
security, and maintenance perspective.&lt;/p&gt;

&lt;p&gt;The static blog is better to be hosted under a web server like Nginx and exposed
to the Internet for anyone to access. So we’re going to combine the blog with a
web server into a container and expose it using inlets.&lt;/p&gt;

&lt;h3 id=&quot;containerization&quot;&gt;Containerization&lt;/h3&gt;

&lt;p&gt;Static site generator takes Markdown files as input and outputs HTML files under
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_site&lt;/code&gt; by default. Typically, one has to set up a web server to serve those HTML
files so that users can access the blog via browser. There’re three things to
do:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Build: generating &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.html&lt;/code&gt; from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.md&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Ship: moving the output directory full of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.html&lt;/code&gt; files to the serving
location specified in the configuration of the web server&lt;/li&gt;
  &lt;li&gt;Run: kick off the web server which serves the static blog&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And that is exactly the motto of Docker (though it’s changed from “Ship” to
“Share” nowadays). As I’m learning Kubernetes and its ecosystem, I think it is a
great chance to build the pipeline on top of the container management platform.
So the overall workflow is built on the Kubernetes cluster in my home lab.&lt;/p&gt;

&lt;p&gt;Docker is quite an elegant and well-engineered tool. We can create the blog
container image by defining a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt; simply copied from what manual steps
we will take to build and host the blog:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;FROM ruby:3.1.3-buster AS build
COPY blog /app
WORKDIR /app
RUN bundle install \
    &amp;amp;&amp;amp; bundle exec jekyll build

FROM nginx:1.19.7-alpine AS final
COPY --from=build /app/_site /usr/share/nginx/html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, we can build the container image with the following command:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker build &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; starbops/blog:v0.1.0 &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With a container image, it’s super easy to start an instance of the blog:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 8080:80/tcp starbops/blog:v0.1.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Head over to &lt;a href=&quot;http://localhost:8080&quot;&gt;http://localhost:8080&lt;/a&gt; in the browser and there will be the same
page as we saw from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle exec jekyll serve&lt;/code&gt; but hosted in a container.
Hooray!&lt;/p&gt;

&lt;h3 id=&quot;inlets&quot;&gt;Inlets&lt;/h3&gt;

&lt;p&gt;The next step is trying to expose the blog to the Internet since we’re still
running the blog container in the development environment, e.g. our laptop. Most
of us will not have an Internet-accessible public IP address with our laptops,
right? Tunnels to the rescue!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://inlets.dev&quot;&gt;inlets&lt;/a&gt; is a cloud-native tunneling solution with versatile
user scenarios. There’s already an introduction in &lt;a href=&quot;/inlets-the-cloud-native-tunnel.html&quot;&gt;my other blog post&lt;/a&gt; which you might want to
take a look at. We can leverage the solution to expose the blog to the outside
world.&lt;/p&gt;

&lt;p&gt;On the other hand, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inletsctl&lt;/code&gt; helps provision an inlets exit server and
provides handy hints for you to construct a tunnel to the desired backend
service. It integrates several cloud service providers so users can choose
whichever they prefer.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-sLSf&lt;/span&gt; https://inletsctl.inlets.dev | &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;sh
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;inletsctl version
 _       _      _            _   _
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;_&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;_ __ | | ___| |_ ___  ___| |_| |
| | &lt;span class=&quot;s1&quot;&gt;'_ \| |/ _ \ __/ __|/ __| __| |
| | | | | |  __/ |_\__ \ (__| |_| |
|_|_| |_|_|\___|\__|___/\___|\__|_|

Version: 0.8.19
Git Commit: 2379c374e879b91c8c4024b24e4954e013524e8e
Build target: darwin/arm64
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We’ll need the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inlets-pro&lt;/code&gt; binary later, so download it via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inletsctl&lt;/code&gt; and
prepare a &lt;a href=&quot;https://gumroad.com/a/751932531/HGlxA&quot;&gt;valid license&lt;/a&gt; under
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$HOME/.inlets/LICENSE&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;inletsctl download
Password:
2023/02/01 16:34:12 https://github.com/inlets/inlets-pro/releases/tag/0.9.13
Starting download of inlets-pro 0.9.13, this could take a few moments.
Download completed, make sure that /usr/local/bin is on your path.
  inlets-pro version

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;inlets-pro version
 _       _      _            _
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;_&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;_ __ | | ___| |_ ___   __| | _____   __
| | &lt;span class=&quot;s1&quot;&gt;'_ \| |/ _ \ __/ __| / _` |/ _ \ \ / /
| | | | | |  __/ |_\__ \| (_| |  __/\ V /
|_|_| |_|_|\___|\__|___(_)__,_|\___| \_/

  inlets (tm) - Cloud Native Tunnels
Version: 0.9.13 - 90e5c951334923b4d6d97628be054eb6c39a6170
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here I choose &lt;a href=&quot;https://m.do.co/c/6e1b8678fb22&quot;&gt;DigitalOcean&lt;/a&gt; as the provider
(need a &lt;a href=&quot;https://docs.digitalocean.com/reference/api/create-personal-access-token/&quot;&gt;valid access
token&lt;/a&gt;
generated from their website).&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;inletsctl create &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--provider&lt;/span&gt; digitalocean &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--access-token-file&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;/.inlets/do-access-token &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--region&lt;/span&gt; sgp1 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--plan&lt;/span&gt; s-1vcpu-512mb-10gb &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
Using provider: digitalocean
Requesting host: hopeful-albattani1 &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;sgp1, from digitalocean
2023/02/02 09:48:37 Provisioning host with DigitalOcean
Host: 338720570, status:
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;1/500] Host: 338720570, status: new
...
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;15/500] Host: 338720570, status: active
inlets Pro TCP &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;0.9.9&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; server summary:
  IP: 159.223.56.95
  Auth-token: d7kGahG1KFELADyVd3oTMjaM4SyEgxVhI60XxTQBSNv0GqQk6xgJGKrpVvM8X9Vm

Command:

&lt;span class=&quot;c&quot;&gt;# Obtain a license at https://inlets.dev/pricing&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Store it at $HOME/.inlets/LICENSE or use --help for more options&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Give a single value or comma-separated&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PORTS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;8000&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Where to route traffic from the inlets server&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;UPSTREAM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;localhost&quot;&lt;/span&gt;

inlets-pro tcp client &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;wss://159.223.56.95:8123&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--token&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;d7kGahG1KFELADyVd3oTMjaM4SyEgxVhI60XxTQBSNv0GqQk6xgJGKrpVvM8X9Vm&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--upstream&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$UPSTREAM&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--ports&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$PORTS&lt;/span&gt;

To delete:
  inletsctl delete &lt;span class=&quot;nt&quot;&gt;--provider&lt;/span&gt; digitalocean &lt;span class=&quot;nt&quot;&gt;--id&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;338720570&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blogging-the-hard-way/do-exit-server-inletsctl.png&quot; alt=&quot;Exit Server Provisioned by inletsctl&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Follow the instruction shown in the output, and modify the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UPSTREAM&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PORTS&lt;/code&gt;
environment variables to point to the blog container.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PORTS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;8080&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;UPSTREAM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;localhost&quot;&lt;/span&gt;
inlets-pro tcp client &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;wss://159.223.56.95:8123&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--token&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;d7kGahG1KFELADyVd3oTMjaM4SyEgxVhI60XxTQBSNv0GqQk6xgJGKrpVvM8X9Vm&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--upstream&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$UPSTREAM&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--ports&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$PORTS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then head to &lt;a href=&quot;http://159.223.56.95:8080&quot;&gt;http://159.223.56.95:8080&lt;/a&gt; or whatever IP address you got from
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inletsctl&lt;/code&gt; above. Now that the blog has been exposed to the world in plain
HTTP.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blogging-the-hard-way/exposing-the-blog-to-the-world.png&quot; alt=&quot;Exposing the Blog to the World&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;container-management&quot;&gt;Container Management&lt;/h2&gt;

&lt;p&gt;But that’s not enough. A single container is like a little boat in the ocean.
Besides, there is much more than a single container to run to construct a
secured and robust blog. We want the boats to be under a fleet’s control. We
will put the container on a container management platform, under Kubernetes’
control. First, we need a container registry to distribute the image because the
container will no longer just running in our development environment.&lt;/p&gt;

&lt;h3 id=&quot;metallb&quot;&gt;MetalLB&lt;/h3&gt;

&lt;p&gt;With that said, we need some auxiliary services to be ready before setting up
the container registry. The first one is the load balancer controller. The
vanilla Kubernetes does not come with a working implementation of LoadBalancer
Services. Typically, this feature is fulfilled by public cloud service
providers. However, MetalLB provides a way to enable LoadBalancer Services for
on-prem Kubernetes installations. We need this because we’re going to access
LoadBalancer Services of the container registry (and later, the reverse proxies,
the Git server, and the Continuous Integration platform.)&lt;/p&gt;

&lt;p&gt;To install MetalLB on Kubernetes via Helm:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;helm upgrade &lt;span class=&quot;nt&quot;&gt;--install&lt;/span&gt; metallb metallb &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--create-namespace&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;metallb-system &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--repo&lt;/span&gt; https://metallb.github.io/metallb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To configure MetalLB, two types of custom resources need to be defined, one is
IPAddressPool, and the other is L2Advertisement. Both of them are
self-explanatory from their names.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; ipaddresspool.yaml &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: first-pool
  namespace: metallb-system
spec:
  addresses:
  - 192.168.48.240-192.168.48.250
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl apply &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; l2advertisement.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; l2advertisement.yaml &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: example
  namespace: metallb-system
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF

&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl apply &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; l2advertisement.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;ingress-nginx-internal&quot;&gt;Ingress Nginx (Internal)&lt;/h3&gt;

&lt;p&gt;Ingress is another example that the vanilla Kubernetes has-defined but
not-yet-implemented function. Ingress Nginx is an open-source Ingress controller
implementation powered by Nginx. You can treat it as a layer 7 load balancer if
that makes more sense to you.&lt;/p&gt;

&lt;p&gt;We’re going to have two instances of the Ingress Nginx controller, one is for
internal services, i.e., the container registry, the Git server, and the
Continuous Integration platform, and the other is for the external service,
which is the blog. The LoadBalancer Service of the internal one will retrieve an
IP address from MetalLB. However, the external one will get one from a public
cloud service provider. We’ll get into that later. Let’s focus on the internal
one first.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;helm upgrade &lt;span class=&quot;nt&quot;&gt;--install&lt;/span&gt; ingress-nginx ingress-nginx &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; values.yaml &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--create-namespace&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt; ingress-nginx &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--repo&lt;/span&gt; https://kubernetes.github.io/ingress-nginx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;values.yaml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;controller&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ingressClassResource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nginx&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;controllerValue&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;k8s.io/ingress-nginx&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ingressClass&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nginx&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;watchIngressWithoutClass&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;publishService&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;extraArgs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;default-ssl-certificate&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$(POD_NAMESPACE)/internal-tls&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;metallb.universe.tf/allow-shared-ip&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;gateway&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you might notice there’s a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;internal-tls&lt;/code&gt; reference in the above, it’s the
default TLS certificate that secures the HTTP connections. Typically, we need to
specify a Secret that contains a TLS private key and certificate for each
Ingress resource that we want to secure. And the above &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default-ssl-certificate&lt;/code&gt;
is to make sure that any Ingress resource with TLS enabled but without
specifying a Secret can fall into this safety net. For the actual TLS private
key and certificate, I managed to get one by using a Let’s Encrypt installation
outside of the Kubernetes cluster because it’s a wildcard certificate and it’s a
little complicated to set up (DNS challenges) so I will not cover it here. If
you’re interested, maybe take a look at &lt;a href=&quot;/lets-encrypt-dns-challenge.html&quot;&gt;this article&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;container-registry&quot;&gt;Container Registry&lt;/h3&gt;

&lt;p&gt;To push the image to remote, there must be a registry beforehand. You can
leverage existing solutions like Docker Hub. I have a private registry with
basic authentication and HTTPS enabled running in my Kubernetes cluster already.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--entrypoint&lt;/span&gt; htpasswd httpd:2 &lt;span class=&quot;nt&quot;&gt;-Bbn&lt;/span&gt; admin password &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; ./htpasswd
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;helm upgrade &lt;span class=&quot;nt&quot;&gt;--install&lt;/span&gt; private-registry docker-registry &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--set&lt;/span&gt; secrets.htpasswd&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; ./htpasswd&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; values.yaml &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--create-namespace&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt; registry &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--repo&lt;/span&gt; https://helm.twun.io
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The configuration of the private registry is in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;values.yaml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;persistence&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;ingress&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;nginx.ingress.kubernetes.io/proxy-body-size&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;1g&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;tls&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;registry.internal.example.com&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;registry.internal.example.com&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With a valid container registry up and running, we can push the image onto it:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker login &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; admin &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; password registry.internal.example.com
docker tag starbops/blog:v0.1.0 registry.internal.example.com/starbops/blog:v0.1.0
docker push registry.internal.example.com/starbops/blog:v0.1.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The Docker registry server provides a set of APIs for users to interact with. We
can check the existence of the blog image:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; admin:password https://registry.internal.example.com/v2/_catalog
&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;repositories&quot;&lt;/span&gt;:[&lt;span class=&quot;s2&quot;&gt;&quot;starbops/blog&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we’re able to run the blog not just locally but everywhere (as long as the
registry is network-reachable).&lt;/p&gt;

&lt;h3 id=&quot;running-on-kubernetes&quot;&gt;Running on Kubernetes&lt;/h3&gt;

&lt;p&gt;When the container registry is ready, we’re able to run the blog container in
the Kubernetes cluster. We need to design the blueprint of the infrastructure
first. This should look like the following diagram:&lt;/p&gt;

&lt;script src=&quot;/assets/js/mermaid.min.js&quot;&gt;&lt;/script&gt;
&lt;div class=&quot;mermaid&quot;&gt;
flowchart TD;
    subgraph Kubernetes;
    deploy(blog Deployment);
    rs(blog-664d54cf5c ReplicaSet);
    pod(blog-664d54cf5c-6gtdj Pod);
    secret(regcred Secret);
    svc(blog Service);
    ing(blog Ingress);
    deploy -.-&amp;gt; secret;
    deploy --&amp;gt; rs --&amp;gt; pod;
    ing -.-&amp;gt; svc -..-&amp;gt; pod;
    end;
&lt;/div&gt;

&lt;p&gt;It’s time to prepare the manifests for it. It first comes with some plain YAML
files for various Kubernetes resources. Basically, there are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Deployment: manages the blog Pod (the blog container will be inside the Pod)&lt;/li&gt;
  &lt;li&gt;Secret: contains the credentials accessing the private container registry&lt;/li&gt;
  &lt;li&gt;Service: provides a fixed L3/L4 endpoint for accessing the backend blog Pod&lt;/li&gt;
  &lt;li&gt;Ingress: configures HTTPS endpoint for the blog&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;REGISTRY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;registry.internal.example.com&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt; | tee deployment.yaml | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: blog
  namespace: blog
spec:
  replicas: 1
  selector:
    matchLabels:
      app: blog
  template:
    metadata:
      labels:
        app: blog
    spec:
      imagePullSecrets:
      - name: regcred
      containers:
      - image: &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$REGISTRY&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;/starbops/blog:0.1.0
        name: blog
        ports:
        - containerPort: 80
          name: http
          protocol: TCP
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl create secret generic regcred &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--from-file&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;.dockerconfigjson&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;/.docker/config.json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;kubernetes.io/dockerconfigjson &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; yaml &lt;span class=&quot;nt&quot;&gt;--dry-run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;client | &lt;span class=&quot;nb&quot;&gt;tee &lt;/span&gt;secret.yaml | kubectl apply &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; -
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt; | tee service.yaml | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: blog
  namespace: blog
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: http
  selector:
    app: blog
  type: ClusterIP
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The Ingress resource we’re going to create is designed to be associated with the
external-facing ingress-nginx, which is taken care of in the next section.
Notice that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ingressClassName&lt;/code&gt; is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nginx-cloud&lt;/code&gt; but not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nginx&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DOMAIN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;blog.example.com&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt; | tee ingress.yaml | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-staging-cloud
    kubernetes.io/ingress.class: nginx-cloud
  name: blog
  namespace: blog
spec:
  ingressClassName: nginx-cloud
  rules:
  - host: &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$DOMAIN&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
    http:
      paths:
      - backend:
          service:
            name: blog
            port:
              number: 80
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$DOMAIN&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
    secretName: blog-tls
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After creating the Ingress resource, the blog is still not widely accessible
from the Internet due to the absence of the inlets tunnel and the TLS
certificate.&lt;/p&gt;

&lt;h3 id=&quot;inlets-operator&quot;&gt;Inlets Operator&lt;/h3&gt;

&lt;p&gt;inlets-operator is yet another open-source project in the inlets ecosystem that
reflects the merit of the cloud-native spirit. It brings your local Kubernetes
to the public with ease. With inlets-operator installed, it will automatically
provision inlets exit servers on the designated cloud service provider and
establish tunnels for the LoadBalancer Services in the local Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;For advanced use cases like using MetalLB and inlets-operator at the same time,
&lt;a href=&quot;/inlets-the-cloud-native-tunnel.html#exposing-private-service&quot;&gt;here&lt;/a&gt; has some explanation of it.&lt;/p&gt;

&lt;p&gt;Here are the simple setup steps:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;helm repo add inlets https://inlets.github.io/inlets-operator/
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;helm repo update
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;git clone https://github.com/inlets/inlets-operator.git
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl apply &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; ./inlets-operator/artifacts/crds/
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;helm upgrade &lt;span class=&quot;nt&quot;&gt;--install&lt;/span&gt; inlets-operator inlets/inlets-operator &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--set&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;digitalocean &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--set&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;region&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;sgp1 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--set&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;plan&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;s-1vcpu-512mb-10gb &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--set&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;inletsProLicense&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;/.inlets/LICENSE&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--set&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;annotatedOnly&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--create-namespace&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt; inlets-system
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; inlets-system create secret generic inlets-access-key &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--from-literal&lt;/span&gt; inlets-access-key&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;/.inlets/do-access-token&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In our setup, it is the Ingress controller’s Service that uses inlets-operator.
So we’ll stop here and continue to the next component: ingress-nginx.&lt;/p&gt;

&lt;h3 id=&quot;ingress-nginx-external&quot;&gt;Ingress Nginx (External)&lt;/h3&gt;

&lt;p&gt;The Ingress Nginx controller is essential because we want to share our blog with
the outside world with a memorizable domain name.&lt;/p&gt;

&lt;p&gt;To install ingress-nginx in the Kubernetes cluster is fairly simple:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;helm upgrade &lt;span class=&quot;nt&quot;&gt;--install&lt;/span&gt; ingress-nginx-cloud ingress-nginx &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; values.yaml &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--create-namespace&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt; ingress-nginx-cloud &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--repo&lt;/span&gt; https://kubernetes.github.io/ingress-nginx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We need to distinguish this external-facing ingress-nginx from the
internal-facing ingress-nginx we’re going to install in the later chapter.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;controller&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ingressClassResource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nginx-cloud&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;controllerValue&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;k8s.io/ingress-nginx-cloud&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ingressClass&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nginx-cloud&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;publishService&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;metallb.universe.tf/address-pool&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;dummy&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;dev.inlets.manage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;true&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After the external-facing ingress-nginx is installed, we can observe that the
corresponding inlets exit server and tunnel are provisioned.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blogging-the-hard-way/do-exit-server-inlets-operator.png&quot; alt=&quot;Exit Server Provisioned by inlets-operator&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; ingress-nginx-cloud get svc,tunnels
NAME                                               TYPE           CLUSTER-IP      EXTERNAL-IP      PORT&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;S&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;                      AGE
service/ingress-nginx-cloud-controller             LoadBalancer   10.53.234.28    165.22.101.142   80:30388/TCP,443:32421/TCP   5h21m
service/ingress-nginx-cloud-controller-admission   ClusterIP      10.53.205.209   &amp;lt;none&amp;gt;           443/TCP                      5h21m

NAME                                                             SERVICE                          TUNNEL   HOSTSTATUS   HOSTIP           HOSTID
tunnel.inlets.inlets.dev/ingress-nginx-cloud-controller-tunnel   ingress-nginx-cloud-controller            active       165.22.101.142   338744799
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;cert-manager&quot;&gt;Cert Manager&lt;/h3&gt;

&lt;p&gt;We will need cert-manager to get a valid certificate for the blog and also help
us handle the certificate-related procedure like issuer management, certificate
signing request generation, challenge responding, etc. It’s quite easy to deploy
in a Kubernetes cluster:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;helm upgrade &lt;span class=&quot;nt&quot;&gt;--install&lt;/span&gt; cert-manager cert-manager &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--set&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;installCRDs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt; v1.11.0 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--create-namespace&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt; cert-manager &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--repo&lt;/span&gt; https://charts.jetstack.io
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Set up a ClusterIssuer for later use. Due to the strict rate limit of production
usage set by Let’s Encrypt, we’ll stick to the staging server for demonstration.
Once the configuration is settled, we can replace it with the production one.
Make sure the email address is valid.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;EMAIL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;admin@example.com&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; staging-issuer-cloud.yaml &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging-cloud
spec:
  acme:
    # The ACME server URL
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$EMAIL&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-staging-cloud
    # Enable the HTTP-01 challenge provider
    solvers:
    - http01:
        ingress:
          class: nginx-cloud
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF

&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl apply &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; staging-issuer-cloud.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;helm-charts&quot;&gt;Helm Charts&lt;/h3&gt;

&lt;p&gt;Remember that we created several manifest files for the blog to run on the
Kubernetes cluster?&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl apply &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; deployment.yaml &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; service.yaml &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; ingress.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Having these manifest files at hand is very helpful to further devise the Helm
chart template.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;s&quot;&gt;$ tree .&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;├── blog-0.1.0.tgz&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;├── charts&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;├── Chart.yaml&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;├── README.md&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;├── templates&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;│   ├── certificate.yaml&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;│   ├── deployment.yaml&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;│   ├── _helpers.tpl&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;│   ├── hpa.yaml&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;│   ├── ingress.yaml&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;│   ├── NOTES.txt&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;│   ├── serviceaccount.yaml&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;│   ├── service.yaml&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;│   └── tests&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;│       └── test-connection.yaml&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;└── values.yaml&lt;/span&gt;

&lt;span class=&quot;s&quot;&gt;3 directories, 13 files&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;helm package &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
curl &lt;span class=&quot;nt&quot;&gt;--data-binary&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;@blog-0.1.0.tgz&quot;&lt;/span&gt; https://charts.internal.example.com/api/charts
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;helm upgrade &lt;span class=&quot;nt&quot;&gt;--install&lt;/span&gt; blog blog &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; values.yaml &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--create-namespace&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt; blog &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--repo&lt;/span&gt; https://charts.internal.example.com/charts
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; blog logs deploy/blog &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;gitops&quot;&gt;GitOps&lt;/h2&gt;

&lt;p&gt;Now that the version-control and syntax-checking parts are done, it’s time for
the last part - auto-publishing. What I want is actually:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;When new commits are pushed
    &lt;ol&gt;
      &lt;li&gt;Check syntax&lt;/li&gt;
      &lt;li&gt;Build the source of the blog&lt;/li&gt;
      &lt;li&gt;Package the blog in HTML into a container image&lt;/li&gt;
      &lt;li&gt;Upload the container image to the registry&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;When PRs are merged
    &lt;ol&gt;
      &lt;li&gt;Do the steps mentioned above&lt;/li&gt;
      &lt;li&gt;Deploy the blog to the staging environment&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;When release tags are pushed
    &lt;ol&gt;
      &lt;li&gt;Do the steps mentioned above&lt;/li&gt;
      &lt;li&gt;Deploy the blog to the production environment&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Long story short, I’d like to &lt;strong&gt;apply the continuous
integration/delivery/deployment practices to the blogging flow same as we do with
software development.&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;git-service&quot;&gt;Git Service&lt;/h3&gt;

&lt;p&gt;We need a git service to store the git repo. Here I choose Gitea and self-host
it on my Kubernetes cluster.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;helm upgrade &lt;span class=&quot;nt&quot;&gt;--install&lt;/span&gt; gitea gitea &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; values.yaml &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--create-namespace&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt; gitea &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--repo&lt;/span&gt; https://dl.gitea.io/charts/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s better to have persistent storage configured so the data won’t get lost
after restart. I also have a bunch of settings in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;values.yaml&lt;/code&gt; which you might
not need, but you can take a peek for references:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;global&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;storageClass&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;longhorn&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;persistence&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;storageClass&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;longhorn&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ssh&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;LoadBalancer&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;metallb.universe.tf/allow-shared-ip&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;gateway&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;ingress&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;nginx.ingress.kubernetes.io/proxy-body-size&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;50m&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;git.internal.example.com&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;tls&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;git.internal.example.com&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;gitea&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;admin&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;redacted&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;redacted&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;admin@example.com&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;PROTOCOL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ROOT_URL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https://git.internal.example.com&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;builtIn&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;postgresql&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;mariadb&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ldap&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ldap&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;securityProtocol&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;starttls&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ldap.internal.example.com&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;389&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;userSearchBase&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ou=people,dc=internal,dc=example,dc=com&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;userFilter&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;(&amp;amp;(objectClass=posixAccount)(uid=%s))&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;emailAttribute&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mail&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;usernameAttribute&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;uid&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;publicSshKeyAttribute&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sshPublicKey&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see I set up the git service with L3 and L4 load balancers: MetalLB
and L7 load balancer: ingress-nginx. The former provides an endpoint for SSH
accessing like pushing changes to the git service; the latter is for HTTP(S)
accessing like web browsing and cloning public repos.&lt;/p&gt;

&lt;p&gt;It’s better to have TLS enabled, too. You can use cert-manager in the Kubernetes
cluster and fill up the corresponding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tls&lt;/code&gt; sections above to enable HTTPS for
the git service with the aid of cert-manager. However, I’m not going to expose
the git service on the Internet for security concerns therefore cert-manager is
not applicable in my case. I choose an alternative way to enable HTTPS for the
git service: getting a valid wildcard certificate from the other Let’s Encrypt
installation (with DNS challenges) and setting up the default SSL certificate
config for the ingress-nginx so that any TLS-enabled Ingress resources without a
respective Secret resource specified will fall into this safety net. Here are
the example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;values.yaml&lt;/code&gt; when installing ingress-nginx with Helm:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;controller&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ingressClassResource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nginx&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;controllerValue&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;k8s.io/ingress-nginx&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ingressClass&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nginx&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;watchIngressWithoutClass&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;publishService&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;extraArgs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;default-ssl-certificate&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$(POD_NAMESPACE)/internal-tls&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;metallb.universe.tf/allow-shared-ip&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;gateway&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Needless to say, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;internal-tls&lt;/code&gt; Secret must be provided in the same
namespace with ingress-nginx. I’ll just leave that part to you to save the
space.&lt;/p&gt;

&lt;p&gt;Also, the LDAP section above is to integrate with my existing identity service
in my home lab. You can omit it if you don’t want to use it.&lt;/p&gt;

&lt;p&gt;One point worth mentioning is that I realized &lt;a href=&quot;https://longhorn.io&quot;&gt;Longhorn&lt;/a&gt;
outperforms NFS in my scenario, in which all the Kubernetes nodes and the NFS
server are VMs running on the same bare-metal server. I used to go with
&lt;a href=&quot;https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner&quot;&gt;nfs-subdir-external-provisioner&lt;/a&gt;
in the beginning, since it’s much easier to set up. But soon after, I
encountered performance issues when executing CI jobs. So I turned to Longhorn,
and it did not fail me.&lt;/p&gt;

&lt;p&gt;Now I can write articles, commit changes, and push them to the Git server.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blogging-the-hard-way/starbops-blog-gitea.png&quot; alt=&quot;Blog Repo on Gitea&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;chart-repository&quot;&gt;Chart Repository&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://chartmuseum.com&quot;&gt;ChartMuseum&lt;/a&gt; is an open-source Helm Chart repository
server that supports many cloud storage backends. We will install ChartMuseum on
the Kubernetes cluster to host the blog’s chart from a local filesystem.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;helm upgrade &lt;span class=&quot;nt&quot;&gt;--install&lt;/span&gt; chartmuseum chartmuseum &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; values.yaml
    &lt;span class=&quot;nt&quot;&gt;--create-namespace&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt; chartmuseum &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--repo&lt;/span&gt; https://chartmuseum.github.io/charts
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;values.yaml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;STORAGE&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;local&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;DISABLE_API&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;persistence&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;accessMode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ReadWriteOnce&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;8Gi&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;storageClass&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nfs-client&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;ingress&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ingressClassName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nginx&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;charts.internal.example.com&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;tls&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;blog-chart/
helm package &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl &lt;span class=&quot;nt&quot;&gt;--data-binary&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;@blog-0.1.0.tgz&quot;&lt;/span&gt; https://charts.internal.example.com/api/charts
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;drone&quot;&gt;Drone&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/drone/charts/blob/master/charts/drone/docs/install.md&quot;&gt;Drone server
installation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/drone/charts/blob/master/charts/drone-runner-docker/docs/install.md&quot;&gt;Drone Docker runner
installation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;helm upgrade &lt;span class=&quot;nt&quot;&gt;--install&lt;/span&gt; drone drone &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; drone-values.yaml &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--create-namespace&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt; drone &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--repo&lt;/span&gt; https://charts.drone.io
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;helm upgrade &lt;span class=&quot;nt&quot;&gt;--install&lt;/span&gt; drone-runner-kube drone-runner-kube &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; drone-runner-kube-values.yaml &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--create-namespace&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt; drone &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--repo&lt;/span&gt; https://charts.drone.io
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drone-values.yaml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;persistentVolume&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;storageClass&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nfs-client&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;ingress&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;drone.internal.example.com&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;pathType&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Prefix&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;tls&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;drone.internal.example.com&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;DRONE_SERVER_HOST&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;drone.internal.example.com&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;DRONE_SERVER_PROTO&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;DRONE_RPC_SECRET&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;randomly-generated-secret-here&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;DRONE_GITEA_CLIENT_ID&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;redacted&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;DRONE_GITEA_CLIENT_SECRET&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;redacted&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;DRONE_GITEA_SERVER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https://git.internal.example.com&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;DRONE_GIT_ALWAYS_AUTH&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drone-runner-kube-values.yaml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;rbac&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;buildNamespaces&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;drone&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;DRONE_RPC_SECRET&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;randomly-generated-secret-here&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;DRONE_NAMESPACE_DEFAULT&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;drone&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blogging-the-hard-way/drone-blog.png&quot; alt=&quot;Drone Blog Activity Feed&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blogging-the-hard-way/drone-blog-settings.png&quot; alt=&quot;Drone Settings&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;final-integration&quot;&gt;Final Integration&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blogging-the-hard-way/pushing-commits.png&quot; alt=&quot;Pushing Commits&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blogging-the-hard-way/pr-merge.png&quot; alt=&quot;PR Merge&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blogging-the-hard-way/pushing-tags.png&quot; alt=&quot;Pushing Tags&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.drone.yaml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;pipeline&lt;/span&gt;
 &lt;span class=&quot;s&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;kubernetes&lt;/span&gt;
 &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;default&lt;/span&gt;
 &lt;span class=&quot;s&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;
 &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;lint&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ruby:2.7.1-buster&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
   &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;gem install mdl&lt;/span&gt;
   &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mdl blog/*.md blog/_posts/*.md&lt;/span&gt;
 &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;build on tag&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;plugins/docker&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;mtu&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1450&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;registry&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;registry.internal.example.com&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;registry.internal.example.com/starbops/blog&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${DRONE_TAG##v}-${DRONE_COMMIT_SHA:0:7}&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
       &lt;span class=&quot;na&quot;&gt;from_secret&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker_username&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
       &lt;span class=&quot;na&quot;&gt;from_secret&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker_password&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;build_args&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;JEKYLL_ENV=production&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;VERSION=${DRONE_TAG##v}-${DRONE_COMMIT_SHA:0:7}&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;tag&lt;/span&gt;
 &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;build on push&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;plugins/docker&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;mtu&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1450&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;registry&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;registry.internal.example.com&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;registry.internal.example.com/starbops/blog&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;latest&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${DRONE_COMMIT_SHA:0:7}&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
       &lt;span class=&quot;na&quot;&gt;from_secret&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker_username&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
       &lt;span class=&quot;na&quot;&gt;from_secret&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker_password&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;build_args&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;JEKYLL_ENV=development&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;VERSION=${DRONE_COMMIT_SHA:0:7}&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;push&lt;/span&gt;
 &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;deploy to staging&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bitsbeats/drone-helm3&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;kube_api_server&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
       &lt;span class=&quot;na&quot;&gt;from_secret&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;kube_api_server&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;kube_token&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
       &lt;span class=&quot;na&quot;&gt;from_secret&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;kube_token_staging&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;kube_skip_tls&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;lint&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;build_dependencies&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;zpcc/blog&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;blog&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;blog-staging&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;5m&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;helm_repos&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;zpcc=https://charts.internal.example.com&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;envsubst&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;image.tag=${DRONE_COMMIT_SHA:0:7}&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;imagePullSecrets[0].name=regcred&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;push&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;branch&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;master&lt;/span&gt;
 &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;deploy to prod&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bitsbeats/drone-helm3&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;kube_api_server&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
       &lt;span class=&quot;na&quot;&gt;from_secret&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;kube_api_server&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;kube_token&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
       &lt;span class=&quot;na&quot;&gt;from_secret&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;kube_token_prod&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;kube_skip_tls&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;lint&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;build_dependencies&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;zpcc/blog&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;blog&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;blog-prod&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;5m&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;helm_repos&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;zpcc=https://charts.internal.example.com&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;envsubst&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;image.tag=${DRONE_TAG##v}-${DRONE_COMMIT_SHA:0:7}&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;imagePullSecrets[0].name=regcred&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ingress.enabled=true&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ingress.annotations.kubernetes\\.io/ingress\\.class=nginx-cloud&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ingress.annotations.cert-manager\\.io/cluster-issuer=letsencrypt-prod-cloud&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ingress.hosts[0].host=blog.example.com&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ingress.hosts[0].paths[0].path=&quot;/&quot;&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ingress.tls[0].hosts[0]=blog.example.com&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ingress.tls[0].secretName=blog-tls&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;tag&lt;/span&gt;
 &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;notification&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;plugins/slack&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;webhook&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https://mattermost.internal.example.com/hooks/d1ymz9ie3fyrudk67qon6hc4dr&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;channel&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cicd&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;drone-bot&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;icon_url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https://pbs.twimg.com/profile_images/1291040329395613698/bpYKcF66_400x400.jpg&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;na&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;success&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;failure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blogging-the-hard-way/gitea-blog-webhook.png&quot; alt=&quot;Setting Up Webhook on Gitea to Send Events to Drone&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blogging-the-hard-way/continuous-integration-drone.png&quot; alt=&quot;Continuous Integration - Drone&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; drone get pods drone-1vm0inc02qppitcpil9s &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;jsonpath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'{range .spec.containers[*]}{.name}{&quot;\t&quot;}{.image}{&quot;\n&quot;}{end}'&lt;/span&gt;
drone-e34n2kzrxfm4uelofsh6      drone/git:latest
drone-zwpdoaar9z4cc3cvx7tm      docker.io/library/ruby:2.7.1-buster
drone-to3dil7z30ydj30ay27w      docker.io/plugins/docker:latest
drone-4j6jzl2lhogttpoekpox      drone/placeholder:1
drone-55wpfjiyycnwv2338xqw      drone/placeholder:1
drone-afmx6bk3s1nwkst0dn2k      docker.io/bitsbeats/drone-helm3:latest
drone-j3n4pf7gazaqqpgjro12      docker.io/plugins/slack:latest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;pros-and-cons&quot;&gt;Pros and Cons&lt;/h2&gt;

&lt;p&gt;The deployment pipeline is only triggered when the environment repository
changes. It cannot automatically notice any deviations from the environment and
its desired state. This means it needs some ways of monitoring in place so that
one can intervene if the environment doesn’t match what is described in the
environment repository.&lt;/p&gt;

&lt;h2 id=&quot;future-work&quot;&gt;Future Work&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Implement chart pipeline&lt;/li&gt;
  &lt;li&gt;Make blogging on mobile devices possible, like an iPad with a magic keyboard&lt;/li&gt;
  &lt;li&gt;Adopt a more comprehensive GitOps tool like ArgoCD&lt;/li&gt;
  &lt;li&gt;Collect monitoring metrics&lt;/li&gt;
  &lt;li&gt;Introduce cache layer (CDN etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;

&lt;p&gt;In this lengthy article, we bootstrap a static blog and put it onto Kubernetes,
following the GitOps approach.&lt;/p&gt;

&lt;p&gt;Setting all these up might be hard&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.moncefbelyamani.com/the-definitive-guide-to-installing-ruby-gems-on-a-mac/&quot;&gt;Install Ruby on Apple
Silicon&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://forums.getdrafts.com/t/setup-drafts-on-ios-with-working-copy/9197&quot;&gt;Setup: Drafts on iOS with Working
Copy&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://scottwillsey.com/blog/ios/draftsworkflow/&quot;&gt;Drafts, Shortcuts, and Scriptable – A Workflow Story, Part
1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Zespre Schmidt</name></author><category term="memo" /><summary type="html">There’s an old saying:</summary></entry><entry><title type="html">GnuPG Cheatsheet</title><link href="https://blog.zespre.com/gnupg-cheatsheet.html" rel="alternate" type="text/html" title="GnuPG Cheatsheet" /><published>2022-08-16T00:00:00+00:00</published><updated>2022-08-16T00:00:00+00:00</updated><id>https://blog.zespre.com/gnupg-cheatsheet</id><content type="html" xml:base="https://blog.zespre.com/gnupg-cheatsheet.html">&lt;p&gt;These are important but not so commonly used. Therefore, I noted down some
critical parts of it in case I need them in the future.&lt;/p&gt;

&lt;h2 id=&quot;create-keys&quot;&gt;Create Keys&lt;/h2&gt;

&lt;p&gt;The interactive way:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gpg &lt;span class=&quot;nt&quot;&gt;--expert&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--full-gen-key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;delete-keys&quot;&gt;Delete Keys&lt;/h2&gt;

&lt;h3 id=&quot;delete-subkeys&quot;&gt;Delete Subkeys&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;gpg &lt;span class=&quot;nt&quot;&gt;--edit-key&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$KEYID&lt;/span&gt;
gpg&amp;gt; list
gpg&amp;gt; key 1
gpg&amp;gt; delkey
gpg&amp;gt; save
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;delete-specific-secret-keys&quot;&gt;Delete Specific Secret Keys&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;gpg-connect-agent &lt;span class=&quot;s2&quot;&gt;&quot;HELP DELETE_KEY&quot;&lt;/span&gt; /bye
&lt;span class=&quot;c&quot;&gt;# DELETE_KEY [--force|--stub-only] &amp;lt;hexstring_with_keygrip&amp;gt;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Delete a secret key from the key store.  If --force is used&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# and a loopback pinentry is allowed, the agent will not ask&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# the user for confirmation.  If --stub-only is used the key will&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# only be deleted if it is a reference to a token.&lt;/span&gt;
OK
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To delete a specific secret key (secret master key or secret subkey), you have
to first obtain the target secret key’s keygrip. Then use the keygrip to delete
the correct secret key:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gpg &lt;span class=&quot;nt&quot;&gt;--list-secret-keys&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--with-keygrip&lt;/span&gt; starbops@hey.com
gpg-connect-agent &lt;span class=&quot;s2&quot;&gt;&quot;DELETE_KEY &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$KEYGRIP&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; /bye
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;sign-data&quot;&gt;Sign Data&lt;/h3&gt;

&lt;p&gt;Content with signature in binary format (generates &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$FILE.gpg&lt;/code&gt;):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gpg &lt;span class=&quot;nt&quot;&gt;--local-user&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$SIGNING_KEYID&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--sign&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$FILE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Content with signature in ASCII format (generates &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$FILE.asc&lt;/code&gt;):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gpg &lt;span class=&quot;nt&quot;&gt;--local-user&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$SIGNING_KEYID&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--sign&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--armor&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$FILE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Content followed by signature in ASCII format (generates &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$FILE.asc&lt;/code&gt;):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gpg &lt;span class=&quot;nt&quot;&gt;--local-user&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$SIGNING_KEYID&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--clear-sign&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$FILE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Only signature in binary format (generates &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$FILE.gpg&lt;/code&gt;):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gpg &lt;span class=&quot;nt&quot;&gt;--local-user&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$SIGNING_KEYID&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--detach-sign&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$FILE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Only signature in ASCII format (generates &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$FILE.asc&lt;/code&gt;):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gpg &lt;span class=&quot;nt&quot;&gt;--local-user&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$SIGNING_KEYID&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--detach-sign&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--armor&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$FILE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;interact-with-other-people&quot;&gt;Interact with Other People&lt;/h2&gt;

&lt;p&gt;When you go to some key-signing parties, you might exchange fingerprints with
people (maybe it’s on your business card)&lt;/p&gt;

&lt;h3 id=&quot;upload-public-keys&quot;&gt;Upload Public Keys&lt;/h3&gt;

&lt;p&gt;There’re several public GPG key servers. You have to upload your public key to
one of them.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;keyserver.ubuntu.com&lt;/li&gt;
  &lt;li&gt;keys.openpgp.org&lt;/li&gt;
  &lt;li&gt;pgp.mit.edu&lt;/li&gt;
  &lt;li&gt;pgp.uni-mainz.de&lt;/li&gt;
  &lt;li&gt;pgp.net.nz&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gpg &lt;span class=&quot;nt&quot;&gt;--keyserver&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$KEYSERVER&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--send-keys&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$KEYID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;download-other-peoples-public-key&quot;&gt;Download Other People’s Public Key&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gpg &lt;span class=&quot;nt&quot;&gt;--keyserver&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$KEYSERVER&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--recv-keys&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$KEYID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Or, if you don’t know what the ID is for the key, specify the UID (email
address):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gpg &lt;span class=&quot;nt&quot;&gt;--keyserver&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$KEYSERVER&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--search-keys&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$UID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After you download their public keys, you could check their signatures, sign
them, or use them to encrypt data, then transfer the protected data back to
their owner via email. Lots of things could be done!&lt;/p&gt;

&lt;p&gt;Oh, BTW, if you encounter any &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Network is unreachable&lt;/code&gt; issue during sending,
receiving, or even searching keys on any keyserver, try
&lt;a href=&quot;https://stackoverflow.com/questions/67251078/gpg-keyserver-send-failed-no-keyserver-available-when-sending-to-hkp-pool&quot;&gt;this&lt;/a&gt;.
Make sure you add this line in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.gnupg/dirmngr.conf&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;standard-resolver
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To make the config take effect, reload &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dirmngr&lt;/code&gt; process by:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gpgconf &lt;span class=&quot;nt&quot;&gt;--reload&lt;/span&gt; dirmngr
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now you should be all good. I encountered this on my M1 Mac mini environment.
Not sure what the root cause is. Maybe it’s a bug on the M1 version of gpg.&lt;/p&gt;

&lt;h3 id=&quot;sign-public-keys&quot;&gt;Sign Public Keys&lt;/h3&gt;

&lt;p&gt;Signing other people’s public keys is a very serious thing. You should check the
key’s fingerprint with its owner face to face. If not possible, schedule a video
meeting. This is to keep the authenticity of the key, making sure that the key
owner owns the key you’re going to sign.&lt;/p&gt;

&lt;p&gt;To sign a public key, you can do the following (if you have multiple keys in
your GPG keyring, you have to decide which key is the signing key with
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--default-key $SIGNING_KEYID&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--local-user $SIGNING_KEYID&lt;/code&gt;):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gpg &lt;span class=&quot;nt&quot;&gt;--local-user&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$SIGNING_KEYID&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--sign-key&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$TARGET_KEYID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Or, if you prefer the interactive way:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;gpg &lt;span class=&quot;nt&quot;&gt;--local-user&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$SIGNING_KEYID&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--edit-key&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$TARGET_KEYID&lt;/span&gt;
gpg&amp;gt; list
gpg&amp;gt; sign
gpg&amp;gt; save
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;check-key-signature&quot;&gt;Check Key Signature&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gpg &lt;span class=&quot;nt&quot;&gt;--check-sigs&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$KEYID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://security.stackexchange.com/questions/207138/how-do-i-delete-secret-subkeys-correctly&quot;&gt;Delete secret
subkeys&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://superuser.com/questions/1132263/how-to-delete-a-subkey-on-linux-in-gnupg&quot;&gt;Delete subkeys (pub +
secret)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://lists.gnupg.org/pipermail/gnupg-users/2004-May/022471.html&quot;&gt;Choose different secret keys to sign different public
keys&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://unix.stackexchange.com/questions/644304/key-signing-cant-see-new-signatures&quot;&gt;Signing
keys&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://gist.github.com/F21/b0e8c62c49dfab267ff1d0c6af39ab84&quot;&gt;Signing someone’s GPG key&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Zespre Schmidt</name></author><category term="note" /><summary type="html">These are important but not so commonly used. Therefore, I noted down some critical parts of it in case I need them in the future.</summary></entry><entry><title type="html">Syncing Files among 3 Nodes with Ansible</title><link href="https://blog.zespre.com/syncing-files-among-3-nodes-with-ansible.html" rel="alternate" type="text/html" title="Syncing Files among 3 Nodes with Ansible" /><published>2022-07-29T00:00:00+00:00</published><updated>2022-07-29T00:00:00+00:00</updated><id>https://blog.zespre.com/syncing-files-among-3-nodes-with-ansible</id><content type="html" xml:base="https://blog.zespre.com/syncing-files-among-3-nodes-with-ansible.html">&lt;p&gt;Imagine a scenario:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;There are three nodes and each of them runs a daemon. The daemon will
generate a token file in the name of its hostname under a specific location,
say &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/tmp/node-01&lt;/code&gt;. To form a fully functional cluster, the daemon on one
node needs to know the tokens of the other two nodes. The only way is to sync
those token files across these three nodes so that each node has all three
nodes’ token files, including the one generated by itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;the-environment&quot;&gt;The Environment&lt;/h2&gt;

&lt;p&gt;Let’s say the hostname of the three nodes are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-01&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-02&lt;/code&gt;, and
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-03&lt;/code&gt;. For demonstration, I use &lt;a href=&quot;https://harvesterhci.io/&quot;&gt;Harvester&lt;/a&gt; to
create VMs, you can use whatever you have in hand. Or you already have similar
setup, just skip this section. Note that there’s a VM called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tower&lt;/code&gt;, it is the
place where we run Ansible.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/syncing-files-among-3-nodes-with-ansible/4-vms-on-harvester-hci.png&quot; alt=&quot;4 VMs on Harvester
HCI&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The token files generated by the daemon will be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/tmp/node-01&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/tmp/node-02&lt;/code&gt;,
and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/tmp/node-03&lt;/code&gt; respectively.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/syncing-files-among-3-nodes-with-ansible/3-node-diagram.png&quot; alt=&quot;3 Nodes with Token
Files&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;

&lt;p&gt;First thing first, install Ansible on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tower&lt;/code&gt; machine:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt update
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; ansible
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ansible &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;
ansible 2.9.6
  config file &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; /etc/ansible/ansible.cfg
  configured module search path &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'/home/ubuntu/.ansible/plugins/modules'&lt;/span&gt;, &lt;span class=&quot;s1&quot;&gt;'/usr/share/ansible/plugins/modules'&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
  ansible python module location &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; /usr/lib/python3/dist-packages/ansible
  executable location &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; /usr/bin/ansible
  python version &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 3.8.10 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;default, Mar 15 2022, 12:22:08&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;GCC 9.4.0]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Create the inventory file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hosts.yml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;node-01&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ansible_host&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;10.52.0.124&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ansible_user&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;node-02&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ansible_host&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;10.52.0.125&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ansible_user&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;node-03&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ansible_host&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;10.52.0.126&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ansible_user&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;vars&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ansible_ssh_common_args&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-o&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;StrictHostKeyChecking=no'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now validate the inventory file we just created and also test the connectivity
between the target nodes and the Ansible host.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ansible &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; hosts.yml &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; ping all
node-01 | SUCCESS &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;ansible_facts&quot;&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;discovered_interpreter_python&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;/usr/bin/python3&quot;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,
    &lt;span class=&quot;s2&quot;&gt;&quot;changed&quot;&lt;/span&gt;: &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;,
    &lt;span class=&quot;s2&quot;&gt;&quot;ping&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;pong&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
node-03 | SUCCESS &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;ansible_facts&quot;&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;discovered_interpreter_python&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;/usr/bin/python3&quot;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,
    &lt;span class=&quot;s2&quot;&gt;&quot;changed&quot;&lt;/span&gt;: &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;,
    &lt;span class=&quot;s2&quot;&gt;&quot;ping&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;pong&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
node-02 | SUCCESS &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;ansible_facts&quot;&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;discovered_interpreter_python&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;/usr/bin/python3&quot;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,
    &lt;span class=&quot;s2&quot;&gt;&quot;changed&quot;&lt;/span&gt;: &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;,
    &lt;span class=&quot;s2&quot;&gt;&quot;ping&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;pong&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We’re good to go!&lt;/p&gt;

&lt;h2 id=&quot;setting-up-demo-scenario&quot;&gt;Setting Up Demo Scenario&lt;/h2&gt;

&lt;p&gt;Since there’s no such daemon, it’s just our imagination, we have to generate
the token files by ourselves. Let’s also do this with Ansible!&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Set up demo environment&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;all&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Genrate the token&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ansible.builtin.set_fact&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Print the token&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ansible.builtin.debug&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Create the token file&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ansible.builtin.copy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/tmp/&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then run the playbook:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ansible-playbook &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; hosts.yml setup.yml

PLAY &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Set up demo environment] &lt;span class=&quot;k&quot;&gt;*****************************************************************************************&lt;/span&gt;

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Gathering Facts] &lt;span class=&quot;k&quot;&gt;*************************************************************************************************&lt;/span&gt;
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03]
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02]
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01]

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Genrate the token] &lt;span class=&quot;k&quot;&gt;***********************************************************************************************&lt;/span&gt;
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01]
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02]
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03]

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Print the token] &lt;span class=&quot;k&quot;&gt;*************************************************************************************************&lt;/span&gt;
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;changed&quot;&lt;/span&gt;: &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;,
    &lt;span class=&quot;s2&quot;&gt;&quot;msg&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;Awp5JzuQeAyvsSDu6l2a&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;changed&quot;&lt;/span&gt;: &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;,
    &lt;span class=&quot;s2&quot;&gt;&quot;msg&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;,ktiL0H0:YSYBNmiCREy&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;changed&quot;&lt;/span&gt;: &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;,
    &lt;span class=&quot;s2&quot;&gt;&quot;msg&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;bLn7A87q4aqLLzSVlC02&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Create the token file] &lt;span class=&quot;k&quot;&gt;*******************************************************************************************&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02]
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01]
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03]

PLAY RECAP &lt;span class=&quot;k&quot;&gt;*************************************************************************************************************&lt;/span&gt;
node-01                    : &lt;span class=&quot;nv&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4    &lt;span class=&quot;nv&quot;&gt;changed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1    &lt;span class=&quot;nv&quot;&gt;unreachable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;failed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;skipped&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;rescued&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;ignored&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
node-02                    : &lt;span class=&quot;nv&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4    &lt;span class=&quot;nv&quot;&gt;changed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1    &lt;span class=&quot;nv&quot;&gt;unreachable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;failed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;skipped&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;rescued&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;ignored&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
node-03                    : &lt;span class=&quot;nv&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4    &lt;span class=&quot;nv&quot;&gt;changed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1    &lt;span class=&quot;nv&quot;&gt;unreachable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;failed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;skipped&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;rescued&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;ignored&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In case you want to verify that the tokens are actually deployed on the right
node, right path:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ssh &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;StrictHostKeyChecking&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;no 10.52.0.124 &lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; /tmp/node-01&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;echo
&lt;/span&gt;Awp5JzuQeAyvsSDu6l2a
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ssh &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;StrictHostKeyChecking&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;no 10.52.0.125 &lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; /tmp/node-02&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt;
,ktiL0H0:YSYBNmiCREy
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ssh &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;StrictHostKeyChecking&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;no 10.52.0.126 &lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; /tmp/node-03&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;echo
&lt;/span&gt;bLn7A87q4aqLLzSVlC02
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s time for the real work: &lt;strong&gt;file synchronization&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;syncing-files-with-ansible&quot;&gt;Syncing Files with Ansible&lt;/h2&gt;

&lt;p&gt;Then how do we achieve our goal using Ansible? Basically, we utilize the
&lt;a href=&quot;https://docs.ansible.com/ansible/latest/collections/ansible/posix/synchronize_module.html&quot;&gt;synchronize
module&lt;/a&gt;
of Ansible, and there’re two ways of doing that:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The centralized way&lt;/li&gt;
  &lt;li&gt;The ad-hoc way&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each way has its pros and cons, I’ll explain them in the following
respectively.&lt;/p&gt;

&lt;h3 id=&quot;the-centralized-way&quot;&gt;The Centralized Way&lt;/h3&gt;

&lt;p&gt;I believe it’s much easier to understand how this work with a diagram.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/syncing-files-among-3-nodes-with-ansible/centralized-sync-diagram.png&quot; alt=&quot;Centralized Sync&quot; /&gt;&lt;/p&gt;

&lt;p&gt;First, we pull the token files from all the nodes to the local directory on
Ansible host. Then push these fetched token files back to the nodes. In the
end, all the nodes have all the token files. And we’ll deliberately skip the
“push to itself” part.&lt;/p&gt;

&lt;p&gt;Create the playbook &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;centralized-sync.yml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Centralized sync&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;all&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Create buffer directory&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;local_action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ansible.builtin.file&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;buffer&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;directory&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;run_once&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yes&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Pull the token file to localhost&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;synchronize&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/tmp/&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;buffer/&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;pull&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Push back token files to each node&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;synchronize&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;buffer/&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/tmp/&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;push&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;item != inventory_hostname&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, we create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buffer&lt;/code&gt; directory on the Ansible host for
collecting the token files. After that we initiate connections and pull the
token file with synchronize module from each nodes to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buffer&lt;/code&gt; directory.
Finally, pushing all the token files to each node. Note the last line, what it
means is when the name of the token file and the name of the node are the same,
just skip and straight to the next iteration.&lt;/p&gt;

&lt;p&gt;To execute the playbook we’ve just created:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ansible-playbook &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; hosts.yml centralized-sync.yml

PLAY &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Centralized &lt;span class=&quot;nb&quot;&gt;sync&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*************************************************************************************************************&lt;/span&gt;

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Gathering Facts] &lt;span class=&quot;k&quot;&gt;*************************************************************************************************&lt;/span&gt;
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02]
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03]
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01]

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Create buffer directory] &lt;span class=&quot;k&quot;&gt;*****************************************************************************************&lt;/span&gt;
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01 -&amp;gt; localhost]

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Pull the token file to localhost] &lt;span class=&quot;k&quot;&gt;********************************************************************************&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02]
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03]
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01]

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Push back token files to each node] &lt;span class=&quot;k&quot;&gt;******************************************************************************&lt;/span&gt;
skipping: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-01&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-02&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-01&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
skipping: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-02&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-01&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-03&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-03&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-02&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
skipping: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-03&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

PLAY RECAP &lt;span class=&quot;k&quot;&gt;*************************************************************************************************************&lt;/span&gt;
node-01                    : &lt;span class=&quot;nv&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4    &lt;span class=&quot;nv&quot;&gt;changed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2    &lt;span class=&quot;nv&quot;&gt;unreachable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;failed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;skipped&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;rescued&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;ignored&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
node-02                    : &lt;span class=&quot;nv&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3    &lt;span class=&quot;nv&quot;&gt;changed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2    &lt;span class=&quot;nv&quot;&gt;unreachable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;failed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;skipped&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;rescued&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;ignored&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
node-03                    : &lt;span class=&quot;nv&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3    &lt;span class=&quot;nv&quot;&gt;changed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2    &lt;span class=&quot;nv&quot;&gt;unreachable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;failed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;skipped&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;rescued&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;ignored&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To verify that each node has all the token files, you can &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh&lt;/code&gt; to each node
and execute a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for&lt;/code&gt; loop to show the file content.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ssh &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;StrictHostKeyChecking&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;no 10.52.0.124 &lt;span class=&quot;s1&quot;&gt;'for i in {1..3}; do cat /tmp/node-0$i; echo; done'&lt;/span&gt;
Awp5JzuQeAyvsSDu6l2a
,ktiL0H0:YSYBNmiCREy
bLn7A87q4aqLLzSVlC02
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ssh &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;StrictHostKeyChecking&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;no 10.52.0.125 &lt;span class=&quot;s1&quot;&gt;'for i in {1..3}; do cat /tmp/node-0$i; echo; done'&lt;/span&gt;
Awp5JzuQeAyvsSDu6l2a
,ktiL0H0:YSYBNmiCREy
bLn7A87q4aqLLzSVlC02
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ssh &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;StrictHostKeyChecking&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;no 10.52.0.126 &lt;span class=&quot;s1&quot;&gt;'for i in {1..3}; do cat /tmp/node-0$i; echo; done'&lt;/span&gt;
Awp5JzuQeAyvsSDu6l2a
,ktiL0H0:YSYBNmiCREy
bLn7A87q4aqLLzSVlC02
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is actually what we want. Now let’s take a look of the other method.&lt;/p&gt;

&lt;h3 id=&quot;the-ad-hoc-way&quot;&gt;The Ad-hoc Way&lt;/h3&gt;

&lt;p&gt;Same, the diagram tells us all.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/syncing-files-among-3-nodes-with-ansible/adhoc-sync-diagram.png&quot; alt=&quot;Ad-hoc Sync&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This time, we utilize the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;delegate_to&lt;/code&gt; keyword heavily, though it just appears
once in the playbook shown below. The point is, we don’t want to install
Ansible and execute the playbook on every node. If so, what’s the difference
between copying files with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scp&lt;/code&gt; manually and using Ansible? What the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;delegate_to&lt;/code&gt; keyword does is, it delegates the task to the host specified and
references to the other hosts. In our scenario, as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-01&lt;/code&gt;, we want to fetch
the token files on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-02&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-03&lt;/code&gt;. Then we go to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-02&lt;/code&gt; to fetch
the token files on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-01&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-03&lt;/code&gt;. Finally, we fetch &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-01&lt;/code&gt; and
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-02&lt;/code&gt;’s token files from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-03&lt;/code&gt;’s perspective. It’s pretty suitable to
the usage of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;delegate_to&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Create the playbook &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;adhoc-sync.yml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Ad-hoc sync&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;all&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Create ssh public key buffer directory&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;local_action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ansible.builtin.file&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;buffer/keys&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;directory&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;run_once&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yes&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Generate ssh keypair on each node&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ansible.builtin.user&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;generate_ssh_key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yes&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Fetch all public keys from each node&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ansible.builtin.fetch&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/home//.ssh/id_rsa.pub&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;buffer/keys/-id_rsa.pub&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;flat&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yes&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Assemble authorized keys from buffer&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;local_action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ansible.builtin.assemble&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;buffer/keys&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;buffer/keys/authorized_keys&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;run_once&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yes&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Update authorized keys on each node&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ansible.builtin.blockinfile&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/home//.ssh/authorized_keys&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;backup&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yes&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yes&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0600&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;present&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Synchronize files from each other&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ansible.builtin.synchronize&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/tmp/&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/tmp/&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;pull&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;delegate_to&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;item != inventory_hostname&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This time the playbook is quite lengthy, but the real work, which is file
synchronization, is written in the last task. In order to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rsync&lt;/code&gt; token files
successfully, we have to make sure that each one of the node can &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh&lt;/code&gt; to the
other two nodes using SSH keys. So the first thing is to generate SSH key pair
on every node, then distribute the public keys. We achieve this by collecting
public keys from each node, assembling them into a temporary file in buffer
directory, and updating &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;authorized_keys&lt;/code&gt; on each node with the content of the
temporary file.&lt;/p&gt;

&lt;p&gt;To execute the playbook:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ansible-playbook &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; hosts.yml adhoc-sync.yml

PLAY &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Ad-hoc &lt;span class=&quot;nb&quot;&gt;sync&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*****************************************************************************************************&lt;/span&gt;

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Gathering Facts] &lt;span class=&quot;k&quot;&gt;*************************************************************************************************&lt;/span&gt;
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01]
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02]
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03]

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Create ssh public key buffer directory] &lt;span class=&quot;k&quot;&gt;**************************************************************************&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01 -&amp;gt; localhost]

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Generate ssh keypair on each node] &lt;span class=&quot;k&quot;&gt;*******************************************************************************&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01]
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03]
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02]

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Fetch all public keys from each node] &lt;span class=&quot;k&quot;&gt;****************************************************************************&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03]
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01]
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02]

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Assemble authorized keys from buffer] &lt;span class=&quot;k&quot;&gt;****************************************************************************&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01 -&amp;gt; localhost]

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Update authorized keys on each node] &lt;span class=&quot;k&quot;&gt;*****************************************************************************&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01]
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02]
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03]

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Synchronize files from each other] &lt;span class=&quot;k&quot;&gt;*******************************************************************************&lt;/span&gt;
skipping: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-01&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01 -&amp;gt; 10.52.0.125] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-02&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03 -&amp;gt; 10.52.0.124] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-01&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02 -&amp;gt; 10.52.0.124] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-01&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
skipping: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-02&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01 -&amp;gt; 10.52.0.126] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-03&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03 -&amp;gt; 10.52.0.125] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-02&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
skipping: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-03&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02 -&amp;gt; 10.52.0.126] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-03&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

PLAY RECAP &lt;span class=&quot;k&quot;&gt;*************************************************************************************************************&lt;/span&gt;
node-01                    : &lt;span class=&quot;nv&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;7    &lt;span class=&quot;nv&quot;&gt;changed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;6    &lt;span class=&quot;nv&quot;&gt;unreachable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;failed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;skipped&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;rescued&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;ignored&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
node-02                    : &lt;span class=&quot;nv&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;5    &lt;span class=&quot;nv&quot;&gt;changed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4    &lt;span class=&quot;nv&quot;&gt;unreachable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;failed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;skipped&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;rescued&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;ignored&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
node-03                    : &lt;span class=&quot;nv&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;5    &lt;span class=&quot;nv&quot;&gt;changed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4    &lt;span class=&quot;nv&quot;&gt;unreachable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;failed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;skipped&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;rescued&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;ignored&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now that all the nodes have all the token files. You can verify that with the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh&lt;/code&gt; commands in the last section, I’ll just skip the detail for simplicity.&lt;/p&gt;

&lt;h3 id=&quot;pros-and-cons&quot;&gt;Pros and Cons&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;The centralized way
    &lt;ul&gt;
      &lt;li&gt;Advantages
        &lt;ul&gt;
          &lt;li&gt;Simple logic (pull then push)&lt;/li&gt;
          &lt;li&gt;No prerequisites&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Drawbacks
        &lt;ul&gt;
          &lt;li&gt;Waste of traffic (outbound bandwidth might be expensive if this is in
public cloud environment)&lt;/li&gt;
          &lt;li&gt;Security issue (traffic will leave the cluster network)&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;The ad-hoc way
    &lt;ul&gt;
      &lt;li&gt;Advantages
        &lt;ul&gt;
          &lt;li&gt;Simple in architecture (direct sync among nodes)&lt;/li&gt;
          &lt;li&gt;Performant when it comes to large file synchronization, not just small
file like token&lt;/li&gt;
          &lt;li&gt;More secure in terms of data leak (traffic remains in the cluster
network)&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Drawbacks
        &lt;ul&gt;
          &lt;li&gt;Rather complicated logic (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;delegate_to&lt;/code&gt; keyword could be hard to
understand at the beginning)&lt;/li&gt;
          &lt;li&gt;Got things to do beforehand (SSH pubkey distribution)&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;tearing-down-demo-scenario&quot;&gt;Tearing Down Demo Scenario&lt;/h2&gt;

&lt;p&gt;Things have to be cleaned up after we’ve done the demonstration successfully.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Tear down demo environment&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;all&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Remove buffered token files&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;local_action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ansible.builtin.file&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;buffer/&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;absent&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;run_once&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yes&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Remove combined authorized keys on each node&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ansible.builtin.blockinfile&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/home//.ssh/authorized_keys&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;backup&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yes&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;absent&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Remove buffered public keys&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;local_action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ansible.builtin.file&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;buffer/keys/&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;absent&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;run_once&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yes&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Remove the token file&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ansible.builtin.file&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/tmp/&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;absent&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now clean up the intermediate files and synced token files if you want to redo
the demo. Otherwise, you can wipe out the VMs directly.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ansible-playbook &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; hosts.yml teardown.yml

PLAY &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Tear down demo environment] &lt;span class=&quot;k&quot;&gt;**************************************************************************************&lt;/span&gt;

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Gathering Facts] &lt;span class=&quot;k&quot;&gt;*************************************************************************************************&lt;/span&gt;
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02]
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01]
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03]

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Remove buffered token files] &lt;span class=&quot;k&quot;&gt;*************************************************************************************&lt;/span&gt;
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01 -&amp;gt; localhost] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-01&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01 -&amp;gt; localhost] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-02&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ok: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01 -&amp;gt; localhost] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-03&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Remove combined authorized keys on each node] &lt;span class=&quot;k&quot;&gt;********************************************************************&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02]
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03]
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01]

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Remove buffered public keys] &lt;span class=&quot;k&quot;&gt;*************************************************************************************&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01 -&amp;gt; localhost]

TASK &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Remove the token file] &lt;span class=&quot;k&quot;&gt;*******************************************************************************************&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-01&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-01&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-01&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-02&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-02&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-02&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-01] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-03&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-02] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-03&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
changed: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;node-03] &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-03&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

PLAY RECAP &lt;span class=&quot;k&quot;&gt;*************************************************************************************************************&lt;/span&gt;
node-01                    : &lt;span class=&quot;nv&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;5    &lt;span class=&quot;nv&quot;&gt;changed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3    &lt;span class=&quot;nv&quot;&gt;unreachable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;failed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;skipped&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;rescued&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;ignored&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
node-02                    : &lt;span class=&quot;nv&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3    &lt;span class=&quot;nv&quot;&gt;changed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2    &lt;span class=&quot;nv&quot;&gt;unreachable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;failed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;skipped&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;rescued&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;ignored&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
node-03                    : &lt;span class=&quot;nv&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3    &lt;span class=&quot;nv&quot;&gt;changed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2    &lt;span class=&quot;nv&quot;&gt;unreachable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;failed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;skipped&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;rescued&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0    &lt;span class=&quot;nv&quot;&gt;ignored&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/46732703/how-to-generate-single-reusable-random-password-with-ansible&quot;&gt;How to generate single reusable random password with
ansible&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.ansible.com/ansible/latest/user_guide/playbooks_delegation.html&quot;&gt;Controlling where tasks run: delegation and local actions - Ansible
Documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.dazhuanlan.com/a2840734928/topics/1011492&quot;&gt;ansible synchronize
同步文件夹&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/57291141/how-to-synchronize-a-file-between-two-remote-servers-in-ansible&quot;&gt;How to synchronize a file between two remote servers in
Ansible?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/62235777/the-best-way-to-authorize-ssh-key-of-each-node-to-all-nodes-in-the-cluster&quot;&gt;The best way to authorize ssh key of each node to all nodes in the
cluster&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Zespre Schmidt</name></author><category term="memo" /><summary type="html">Imagine a scenario:</summary></entry><entry><title type="html">Deploying metrics-server on Kubernetes Cluster Installed with kubeadm</title><link href="https://blog.zespre.com/deploying-metrics-server-on-kubernetes-cluster-installed-with-kubeadm.html" rel="alternate" type="text/html" title="Deploying metrics-server on Kubernetes Cluster Installed with kubeadm" /><published>2022-01-21T00:00:00+00:00</published><updated>2022-01-21T00:00:00+00:00</updated><id>https://blog.zespre.com/deploying-metrics-server-on-kubernetes-cluster-installed-with-kubeadm</id><content type="html" xml:base="https://blog.zespre.com/deploying-metrics-server-on-kubernetes-cluster-installed-with-kubeadm.html">&lt;p&gt;As you may have already known, I have a 4-node Kubernetes cluster, which was
installed using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubeadm&lt;/code&gt;. When I was trying to deploy metrics-server on my
cluster using the &lt;a href=&quot;https://artifacthub.io/packages/helm/metrics-server/metrics-server&quot;&gt;official Helm
chart&lt;/a&gt;, I
got the following situation:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; metrics-server get deploy
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
metrics-server   0/3     3            0           26h
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; metrics-server get po
NAME                              READY   STATUS    RESTARTS   AGE
metrics-server-59c9f76588-mnhst   0/1     Running   0          26h
metrics-server-59c9f76588-tf9fr   0/1     Running   0          26h
metrics-server-59c9f76588-zbwd4   0/1     Running   0          26h
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In my deployment, there’re 3 replicas. Clearly, none of them are in ready state.
Pick one Pod and see what happened to the metrics-server application by
inspecting the log output:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; metrics-server logs metrics-server-59c9f76588-mnhst
...
E0114 02:25:00.528571       1 scraper.go:139] &lt;span class=&quot;s2&quot;&gt;&quot;Failed to scrape node&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Get &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https://192.168.88.114:10250/stats/summary?only_cpu_and_memory=true&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: x509: certificate has
 expired or is not yet valid: current time 2022-01-14T02:25:00Z is after 2022-01-06T06:23:35Z&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;k8s-worker-3.internal.zespre.com&quot;&lt;/span&gt;
E0114 02:25:00.528999       1 scraper.go:139] &lt;span class=&quot;s2&quot;&gt;&quot;Failed to scrape node&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Get &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https://192.168.88.111:10250/stats/summary?only_cpu_and_memory=true&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: x509: certificate has
 expired or is not yet valid: current time 2022-01-14T02:25:00Z is after 2021-11-20T07:36:02Z&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;k8s-master.internal.zespre.com&quot;&lt;/span&gt;
E0114 02:25:00.537740       1 scraper.go:139] &lt;span class=&quot;s2&quot;&gt;&quot;Failed to scrape node&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Get &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https://192.168.88.113:10250/stats/summary?only_cpu_and_memory=true&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: x509: certificate has
 expired or is not yet valid: current time 2022-01-14T02:25:00Z is after 2021-11-20T07:36:02Z&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;k8s-worker-2.internal.zespre.com&quot;&lt;/span&gt;
E0114 02:25:00.543400       1 scraper.go:139] &lt;span class=&quot;s2&quot;&gt;&quot;Failed to scrape node&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Get &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https://192.168.88.112:10250/stats/summary?only_cpu_and_memory=true&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: x509: certificate has
 expired or is not yet valid: current time 2022-01-14T02:25:00Z is after 2021-11-20T07:36:02Z&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;k8s-worker-1.internal.zespre.com&quot;&lt;/span&gt;
I0114 02:25:01.372652       1 server.go:188] &lt;span class=&quot;s2&quot;&gt;&quot;Failed probe&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;probe&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;metric-storage-ready&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;not metrics to serve&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What the logs shown intrigued me, especially the “certificate expired” part. If
I remember correctly, the Kubernetes control plane uses a set of keys and
certificates for authentication over TLS for security reasons. The cluster was
installed with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubeadm&lt;/code&gt;. By default, the certificates required are
automatically generated and being valid for one year long. I went to the master
node and checked whether they’re expired or not.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;kubeadm certs check-expiration
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;check-expiration] Reading configuration from the cluster...
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;check-expiration] FYI: You can look at this config file with &lt;span class=&quot;s1&quot;&gt;'kubectl -n kube-system get cm kubeadm-config -o yaml'&lt;/span&gt;

CERTIFICATE                EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY   EXTERNALLY MANAGED
admin.conf                 Mar 23, 2022 02:44 UTC   67d                                     no
apiserver                  Mar 23, 2022 02:42 UTC   67d             ca                      no
apiserver-etcd-client      Mar 23, 2022 02:42 UTC   67d             etcd-ca                 no
apiserver-kubelet-client   Mar 23, 2022 02:42 UTC   67d             ca                      no
controller-manager.conf    Mar 23, 2022 02:43 UTC   67d                                     no
etcd-healthcheck-client    Mar 23, 2022 02:42 UTC   67d             etcd-ca                 no
etcd-peer                  Mar 23, 2022 02:42 UTC   67d             etcd-ca                 no
etcd-server                Mar 23, 2022 02:42 UTC   67d             etcd-ca                 no
front-proxy-client         Mar 23, 2022 02:42 UTC   67d             front-proxy-ca          no
scheduler.conf             Mar 23, 2022 02:43 UTC   67d                                     no

CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
ca                      Nov 18, 2030 09:01 UTC   8y              no
etcd-ca                 Nov 18, 2030 09:01 UTC   8y              no
front-proxy-ca          Nov 18, 2030 09:01 UTC   8y              no
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Turns out that they’re still valid as of now. If you look closer, you’ll notice
that there’s no single certificate related to kubelet, which means we’re in the
wrong place for the answer (actually there’s a certificate called
“apiserver-kubelet-client” shown in the list, but it’s a &lt;strong&gt;client auth&lt;/strong&gt;
certificate for apiserver to connect to kubelet, which is not in our
interest). So the next step is to check the configuration of kubelet daemons
running on all nodes including master node.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ps &lt;span class=&quot;nt&quot;&gt;-ef&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;kubelet
root      5688     1  5  2021 ?        2-03:25:39 /usr/bin/kubelet &lt;span class=&quot;nt&quot;&gt;--bootstrap-kubeconfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/etc/kubernetes/bootstrap-kubelet.conf &lt;span class=&quot;nt&quot;&gt;--kubeconfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/etc/kubernetes/kubelet.conf &lt;span class=&quot;nt&quot;&gt;--config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/var/lib/kubelet/config.yaml &lt;span class=&quot;nt&quot;&gt;--network-plugin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;cni &lt;span class=&quot;nt&quot;&gt;--pod-infra-container-image&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;k8s.gcr.io/pause:3.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;According to the official document, while installing the cluster with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubeadm&lt;/code&gt;,
the kubelet daemon uses a bootstrap token to do authentication and request a
client certificate from the API server. After the certificate request is
approved, the kubelet daemon retrieves the certificate and put it under
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/lib/kubelet/pki&lt;/code&gt; (configurable via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--cert-dir&lt;/code&gt; as an option of kubelet).&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo cat&lt;/span&gt; /etc/kubernetes/kubelet.conf
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: &amp;lt;redacted&amp;gt;
    server: https://192.168.88.111:6443
  name: default-cluster
contexts:
- context:
    cluster: default-cluster
    namespace: default
    user: default-auth
  name: default-context
current-context: default-context
kind: Config
preferences: &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;users&lt;/span&gt;:
- name: default-auth
  user:
    client-certificate: /var/lib/kubelet/pki/kubelet-client-current.pem
    client-key: /var/lib/kubelet/pki/kubelet-client-current.pem
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; /var/lib/kubelet/pki/
total 16
&lt;span class=&quot;nt&quot;&gt;-rw-------&lt;/span&gt; 1 root root 1143 Nov 20  2020 kubelet-client-2020-11-20-17-12-03.pem
&lt;span class=&quot;nt&quot;&gt;-rw-------&lt;/span&gt; 1 root root 1147 Aug 27 15:01 kubelet-client-2021-08-27-15-01-25.pem
lrwxrwxrwx 1 root root   59 Aug 27 15:01 kubelet-client-current.pem -&amp;gt; /var/lib/kubelet/pki/kubelet-client-2021-08-27-15-01-25.pem
&lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt; 1 root root 2417 Nov 20  2020 kubelet.crt
&lt;span class=&quot;nt&quot;&gt;-rw-------&lt;/span&gt; 1 root root 1675 Nov 20  2020 kubelet.key
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, the certificate and key are encoded in
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubelet-client-current.pem&lt;/code&gt; and are renewed periodically. However, this
certificate provided by TLS bootstrapping is signed for &lt;strong&gt;client auth&lt;/strong&gt; only,
and thus cannot be used as serving certificates, or &lt;strong&gt;server auth&lt;/strong&gt;. Try to
examine the certificate by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openssl&lt;/code&gt; tool:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;openssl x509 &lt;span class=&quot;nt&quot;&gt;-in&lt;/span&gt; /var/lib/kubelet/pki/kubelet-client-current.pem &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;    &lt;span class=&quot;nt&quot;&gt;-noout&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-subject&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-issuer&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-dates&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-ext&lt;/span&gt; extendedKeyUsage
&lt;span class=&quot;nv&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;O &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; system:nodes, CN &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; system:node:k8s-worker-1.internal.zespre.com
&lt;span class=&quot;nv&quot;&gt;issuer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;CN &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; kubernetes
&lt;span class=&quot;nv&quot;&gt;notBefore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Aug 27 06:56:24 2021 GMT
&lt;span class=&quot;nv&quot;&gt;notAfter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Aug 27 06:56:24 2022 GMT
X509v3 Extended Key Usage:
    TLS Web Client Authentication
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At the same time, we could inspect the serving certificate from the kubelet API
endpoint shown in the metrics-server logs for comparison:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; | &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; openssl s_client &lt;span class=&quot;nt&quot;&gt;-connect&lt;/span&gt; k8s-worker-1.internal.zespre.com:10250 2&amp;gt;/dev/null | &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; openssl x509 &lt;span class=&quot;nt&quot;&gt;-noout&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-subject&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-issuer&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-dates&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-ext&lt;/span&gt; extendedKeyUsage
&lt;span class=&quot;nv&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;CN &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; k8s-worker-1.internal.zespre.com@1605861362
&lt;span class=&quot;nv&quot;&gt;issuer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;CN &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; k8s-worker-1.internal.zespre.com-ca@1605861362
&lt;span class=&quot;nv&quot;&gt;notBefore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Nov 20 07:36:02 2020 GMT
&lt;span class=&quot;nv&quot;&gt;notAfter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Nov 20 07:36:02 2021 GMT
X509v3 Extended Key Usage:
    TLS Web Server Authentication
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The serving certificate is not the same as the one under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/lib/kubelet/pki&lt;/code&gt;
as their dates of issuance and expiration are different. So, again, we’re on the
wrong place. But I have a strong feeling that we’re close to the answer. I then
noticed there’s a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubelet.crt&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubelet.key&lt;/code&gt; under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/lib/kubelet/pki&lt;/code&gt;
directory. I checked the certificate using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openssl&lt;/code&gt; again out of curiosity:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;openssl x509 &lt;span class=&quot;nt&quot;&gt;-in&lt;/span&gt; /var/lib/kubelet/pki/kubelet.crt &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;    &lt;span class=&quot;nt&quot;&gt;-noout&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-subject&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-issuer&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-dates&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-ext&lt;/span&gt; extendedKeyUsage
&lt;span class=&quot;nv&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;CN &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; k8s-worker-1.internal.zespre.com@1605861362
&lt;span class=&quot;nv&quot;&gt;issuer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;CN &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; k8s-worker-1.internal.zespre.com-ca@1605861362
&lt;span class=&quot;nv&quot;&gt;notBefore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Nov 20 07:36:02 2020 GMT
&lt;span class=&quot;nv&quot;&gt;notAfter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Nov 20 07:36:02 2021 GMT
X509v3 Extended Key Usage:
    TLS Web Server Authentication
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is the exact certificate that kubelet API is serving! It’s worth mentioning
that this is a self-signed certificate. It is generated during the installation
of kubelet. If I had deployed metrics-server earlier within the certification’s
expiration date, I’ll be encounter with another issue related to validation of
self-signed certificate which is often solved by running metrics-server with
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--kubelet-insecure-tls&lt;/code&gt;. But it’s definitely not what I want to see. To
summarize, there’re two types of certificate under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/lib/kubelet/pki&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Client certificate, which is for kubelet to initiate connections to apiserver,
and is auto-rotated by kubelet by default.&lt;/li&gt;
  &lt;li&gt;Server certificate, which is serving with kubelet’s own API, and is &lt;strong&gt;not&lt;/strong&gt;
auto-rotated by kubelet by default.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;serving-certificates-signed-by-cluster-ca&quot;&gt;Serving Certificates Signed by Cluster CA&lt;/h2&gt;

&lt;p&gt;After doing some research, it is quite simple to solve this issue. The main idea
is to make the serving certificate of kubelet signed by cluster certificate
authority (CA), and it’s better to have an auto-rotate mechanism. Two steps are
required:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;serverTLSBootstrap: true&lt;/code&gt; in cluster’s kubelet ConfigMap
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubelet-config&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;serverTLSBootstrap: true&lt;/code&gt; into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.yaml&lt;/code&gt; under
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/lib/kubelet/&lt;/code&gt; on all cluster nodes.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; kube-system describe cm kubelet-config-1.20
Name:         kubelet-config-1.20
Namespace:    kube-system
Labels:       &amp;lt;none&amp;gt;
Annotations:  kubeadm.kubernetes.io/component-config.hash: sha256:306a726156f1e2879bedabbdfa452caae8a63929426a55de71c22fe901fde977

Data
&lt;span class=&quot;o&quot;&gt;====&lt;/span&gt;
kubelet:
&lt;span class=&quot;nt&quot;&gt;----&lt;/span&gt;
apiVersion: kubelet.config.k8s.io/v1beta1
authentication:
  anonymous:
    enabled: &lt;span class=&quot;nb&quot;&gt;false
  &lt;/span&gt;webhook:
    cacheTTL: 0s
    enabled: &lt;span class=&quot;nb&quot;&gt;true
  &lt;/span&gt;x509:
    clientCAFile: /etc/kubernetes/pki/ca.crt
authorization:
  mode: Webhook
  webhook:
    cacheAuthorizedTTL: 0s
    cacheUnauthorizedTTL: 0s
cgroupDriver: cgroupfs
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local
cpuManagerReconcilePeriod: 0s
evictionPressureTransitionPeriod: 0s
fileCheckFrequency: 0s
healthzBindAddress: 127.0.0.1
healthzPort: 10248
httpCheckFrequency: 0s
imageMinimumGCAge: 0s
kind: KubeletConfiguration
logging: &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt;
nodeStatusReportFrequency: 0s
nodeStatusUpdateFrequency: 0s
resolvConf: /run/systemd/resolve/resolv.conf
rotateCertificates: &lt;span class=&quot;nb&quot;&gt;true
&lt;/span&gt;runtimeRequestTimeout: 0s
serverTLSBootstrap: &lt;span class=&quot;nb&quot;&gt;true
&lt;/span&gt;shutdownGracePeriod: 0s
shutdownGracePeriodCriticalPods: 0s
staticPodPath: /etc/kubernetes/manifests
streamingConnectionIdleTimeout: 0s
syncFrequency: 0s
volumeStatsAggPeriod: 0s

Events:  &amp;lt;none&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Why this configuration matters? According to the official documentation:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;serverTLSBootstrap enables server certificate bootstrap. Instead of self
signing a serving certificate, the Kubelet will request a certificate from the
‘&lt;a href=&quot;http://certificates.k8s.io/&quot;&gt;certificates.k8s.io&lt;/a&gt;’ API. This requires an
approver to approve the certificate signing requests (CSR).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So to make the newly added configuration to work, we need to restart kubelet
daemon on all nodes:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl restart kubelet.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then we can check if there’s any CSR created:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl get csr
NAME        AGE   SIGNERNAME                      REQUESTOR                                      CONDITION
csr-9czkl   14s   kubernetes.io/kubelet-serving   system:node:k8s-master.internal.zespre.com     Pending
csr-dl5hl   13s   kubernetes.io/kubelet-serving   system:node:k8s-worker-3.internal.zespre.com   Pending
csr-w6pck   18s   kubernetes.io/kubelet-serving   system:node:k8s-worker-2.internal.zespre.com   Pending
csr-xbwwt   19s   kubernetes.io/kubelet-serving   system:node:k8s-worker-1.internal.zespre.com   Pending
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since I have 4 nodes, I got 4 CSRs to be approved.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl certificate approve csr-9czkl
certificatesigningrequest.certificates.k8s.io/csr-9czkl approved
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl certificate approve csr-dl5hl
certificatesigningrequest.certificates.k8s.io/csr-dl5hl approved
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl certificate approve csr-xbwwt
certificatesigningrequest.certificates.k8s.io/csr-xbwwt approved
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl certificate approve csr-w6pck
certificatesigningrequest.certificates.k8s.io/csr-w6pck approved
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl get csr
NAME        AGE    SIGNERNAME                      REQUESTOR                                      CONDITION
csr-9czkl   3m2s   kubernetes.io/kubelet-serving   system:node:k8s-master.internal.zespre.com     Approved,Issued
csr-dl5hl   3m1s   kubernetes.io/kubelet-serving   system:node:k8s-worker-3.internal.zespre.com   Approved,Issued
csr-w6pck   3m6s   kubernetes.io/kubelet-serving   system:node:k8s-worker-2.internal.zespre.com   Approved,Issued
csr-xbwwt   3m7s   kubernetes.io/kubelet-serving   system:node:k8s-worker-1.internal.zespre.com   Approved,Issued
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After all the CSRs are approved, go check the PKI directory on each node to see
if there’s any serving certificate.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; /var/lib/kubelet/pki/
total 20
&lt;span class=&quot;nt&quot;&gt;-rw-------&lt;/span&gt; 1 root root 1143 Nov 20  2020 kubelet-client-2020-11-20-17-12-03.pem
&lt;span class=&quot;nt&quot;&gt;-rw-------&lt;/span&gt; 1 root root 1147 Aug 27 15:01 kubelet-client-2021-08-27-15-01-25.pem
lrwxrwxrwx 1 root root   59 Aug 27 15:01 kubelet-client-current.pem -&amp;gt; /var/lib/kubelet/pki/kubelet-client-2021-08-27-15-01-25.pem
&lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt; 1 root root 2417 Nov 20  2020 kubelet.crt
&lt;span class=&quot;nt&quot;&gt;-rw-------&lt;/span&gt; 1 root root 1675 Nov 20  2020 kubelet.key
&lt;span class=&quot;nt&quot;&gt;-rw-------&lt;/span&gt; 1 root root 1216 Jan 14 13:40 kubelet-server-2022-01-14-13-40-10.pem
lrwxrwxrwx 1 root root   59 Jan 14 13:40 kubelet-server-current.pem -&amp;gt; /var/lib/kubelet/pki/kubelet-server-2022-01-14-13-40-10.pem
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we got serving certificates signed by cluster CA for kubelet APIs. And due
to the feature gate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RotateKubeletServerCertificate&lt;/code&gt;, which is turned on by
default, kubelet will keep the serving certificate valid (just to remember to
approve the CSRs).&lt;/p&gt;

&lt;h2 id=&quot;verification&quot;&gt;Verification&lt;/h2&gt;

&lt;p&gt;It’s time to go back to our metrics-server!&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl top nodes
NAME                               CPU&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;cores&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;   CPU%   MEMORY&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;bytes&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;   MEMORY%
k8s-master.internal.zespre.com     241m         6%     1666Mi          43%
k8s-worker-1.internal.zespre.com   328m         8%     1449Mi          37%
k8s-worker-2.internal.zespre.com   121m         3%     1692Mi          44%
k8s-worker-3.internal.zespre.com   201m         5%     2730Mi          34%
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl top pods
NAME                         CPU&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;cores&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;   MEMORY&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;bytes&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
dnsutils                     0m           0Mi
hello-k8s-75d8c7c996-492kx   1m           9Mi
hello-k8s-75d8c7c996-9v6br   1m           9Mi
hello-k8s-75d8c7c996-x6x7r   1m           6Mi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Things are back to normal.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/kubernetes/kubeadm/issues/2186&quot;&gt;https://github.com/kubernetes/kubeadm/issues/2186&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet-tls-bootstrapping/#client-and-serving-certificates&quot;&gt;TLS
bootstrapping&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://kubernetes.io/docs/reference/config-api/kubelet-config.v1beta1/&quot;&gt;Kubelet Configuration
(v1beta1)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://particule.io/en/blog/kubeadm-metrics-server/&quot;&gt;Make metrics-server work out of the box with
kubeadm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Zespre Schmidt</name></author><category term="memo" /><summary type="html">As you may have already known, I have a 4-node Kubernetes cluster, which was installed using kubeadm. When I was trying to deploy metrics-server on my cluster using the official Helm chart, I got the following situation:</summary></entry><entry><title type="html">DinD MTU Size Matters</title><link href="https://blog.zespre.com/dind-mtu-size-matters.html" rel="alternate" type="text/html" title="DinD MTU Size Matters" /><published>2021-10-08T00:00:00+00:00</published><updated>2021-10-08T00:00:00+00:00</updated><id>https://blog.zespre.com/dind-mtu-size-matters</id><content type="html" xml:base="https://blog.zespre.com/dind-mtu-size-matters.html">&lt;h2 id=&quot;foreword&quot;&gt;Foreword&lt;/h2&gt;

&lt;p&gt;I use &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt;, a static site generator, to build my
blog. And put the workflow of posting new articles under a CI/CD pipeline with
&lt;a href=&quot;https://www.drone.io/&quot;&gt;Drone CI&lt;/a&gt;, integrated with
&lt;a href=&quot;https://gitea.io/en-us/&quot;&gt;Gitea&lt;/a&gt; via webhook. All of these went very well until
someday I had my home cluster restarted (yeah, the aforementioned components are
all running on my home cluster).&lt;/p&gt;

&lt;h2 id=&quot;the-crime-scene&quot;&gt;The Crime Scene&lt;/h2&gt;

&lt;p&gt;The docker build step of Drone CI was failed randomly due to some network
timeout issues. The step became tremendously time-consuming. Normally it would
take about 3 minutes to complete, but now it takes more than 20 minutes. Even
worse, it sometimes ended up with failure. This is totally not tolerable. I got
to fix this ASAP.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
Step 5/11 : RUN bundle install     &amp;amp;&amp;amp; JEKYLL_ENV=${JEKYLL_ENV} bundle exec jekyll build
 ---&amp;gt; Running in 7608dc4f4951
Fetching source index from https://rubygems.org/
Retrying fetcher due to error (2/4): Bundler::HTTPError Could not fetch specs from https://rubygems.org/

Resolving dependencies...
Network error while fetching
https://rubygems.org/quick/Marshal.4.8/jekyll-3.9.0.gemspec.rz
(Net::OpenTimeout)
The command '/bin/sh -c bundle install     &amp;amp;&amp;amp; JEKYLL_ENV=${JEKYLL_ENV} bundle exec jekyll build' returned a non-zero code: 17
time=&quot;2021-10-08T06:18:50Z&quot; level=fatal msg=&quot;exit status 17&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here’s the related step in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.drone.yaml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;build on push&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;plugins/docker&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;registry&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;registry.internal.zespre.com&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;registry.internal.zespre.com/starbops/blog&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;latest&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${DRONE_COMMIT_SHA:0:7}&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;from_secret&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker_username&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;from_secret&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker_password&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;build_args&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;JEKYLL_ENV=development&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;push&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I use the &lt;a href=&quot;http://plugins.drone.io/drone-plugins/drone-docker/&quot;&gt;Docker plugin of Drone
CI&lt;/a&gt; to build and publish
images to the Docker registry. What this step does is all about project cloning,
image building, and image pushing. For details about image building, here’s the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-docker highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ruby:2.7.1-buster&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;build&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; JEKYLL_ENV=development&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; blog /app&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; /app&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;bundle &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;JEKYLL_ENV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;JEKYLL_ENV&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;jekyll build

&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;nginx:1.19.7-alpine&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;final&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; --from=build /app/_site /usr/share/nginx/html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle install&lt;/code&gt; line is where the story begins. There must be something
wrong deep down there.&lt;/p&gt;

&lt;h2 id=&quot;narrowing-down&quot;&gt;Narrowing Down&lt;/h2&gt;

&lt;p&gt;As a DevOps engineer (sort of), my intuition is to try out the image building on
my local machine since it could be some network issues on the Kubernetes nodes
(I forgot to mention that the Drone CI is running on Kubernetes cluster which is
in my home cluster). And it turned out everything went smoothly. This proves
that the network infrastructure is working as usual, and the source of Ruby gems
is also available for downloading.&lt;/p&gt;

&lt;p&gt;Second thought: why not run the image building on the suspicious Kubernetes
worker node? To get the target node, we need to find out where the Pod was
scheduled (for simplicity, let’s assume that the CI workflow is re-triggered):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; drone get po &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; wide
NAME                                READY   STATUS     RESTARTS   AGE   IP             NODE                               NOMINATED NODE   READINESS GATES
drone-59c4ff89fc-pkwmj              1/1     Running    3          63d   10.244.3.159   k8s-worker-3.internal.zespre.com   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
drone-6e5uk6cu7n7peammeivf          5/7     NotReady   3          36s   10.244.1.113   k8s-worker-1.internal.zespre.com   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
drone-runner-kube-bc87c4fc4-tkmsz   1/1     Running    3          63d   10.244.2.78    k8s-worker-2.internal.zespre.com   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we know the Pod was scheduled to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;worker-1&lt;/code&gt;. Do the same image building
process on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;worker-1&lt;/code&gt;. Unfortunately, the image is built without any
problem. But if we look at it in another angle, we’ve narrowed down the scope
again. It might be the problem inside that specific container, not the worker
node, nor the whole network infrastructure.&lt;/p&gt;

&lt;p&gt;We need to get into that container. But maybe we can take a look at the network
configuration of the worker node before we dive into the container:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ip addr
1: lo: &amp;lt;LOOPBACK,UP,LOWER_UP&amp;gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    &lt;span class=&quot;nb&quot;&gt;link&lt;/span&gt;/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: ens18: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc fq_codel state UP group default qlen 1000
    &lt;span class=&quot;nb&quot;&gt;link&lt;/span&gt;/ether d6:af:03:52:f6:56 brd ff:ff:ff:ff:ff:ff
    inet 192.168.88.112/24 brd 192.168.88.255 scope global ens18
       valid_lft forever preferred_lft forever
    inet6 fe80::d4af:3ff:fe52:f656/64 scope &lt;span class=&quot;nb&quot;&gt;link
       &lt;/span&gt;valid_lft forever preferred_lft forever
&amp;lt;unimportant nics redacted&amp;gt;
5: docker0: &amp;lt;NO-CARRIER,BROADCAST,MULTICAST,UP&amp;gt; mtu 1500 qdisc noqueue state DOWN group default
    &lt;span class=&quot;nb&quot;&gt;link&lt;/span&gt;/ether 02:42:92:85:68:96 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:92ff:fe85:6896/64 scope &lt;span class=&quot;nb&quot;&gt;link
       &lt;/span&gt;valid_lft forever preferred_lft forever
6: flannel.1: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1450 qdisc noqueue state UNKNOWN group default
    &lt;span class=&quot;nb&quot;&gt;link&lt;/span&gt;/ether 6e:45:4a:da:69:49 brd ff:ff:ff:ff:ff:ff
    inet 10.244.1.0/32 brd 10.244.1.0 scope global flannel.1
       valid_lft forever preferred_lft forever
    inet6 fe80::6c45:4aff:feda:6949/64 scope &lt;span class=&quot;nb&quot;&gt;link
       &lt;/span&gt;valid_lft forever preferred_lft forever
7: cni0: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1450 qdisc noqueue state UP group default qlen 1000
    &lt;span class=&quot;nb&quot;&gt;link&lt;/span&gt;/ether c2:52:5c:f0:4a:59 brd ff:ff:ff:ff:ff:ff
    inet 10.244.1.1/24 brd 10.244.1.255 scope global cni0
       valid_lft forever preferred_lft forever
    inet6 fe80::c052:5cff:fef0:4a59/64 scope &lt;span class=&quot;nb&quot;&gt;link
       &lt;/span&gt;valid_lft forever preferred_lft forever
&amp;lt;many veth redacted&amp;gt;
59: veth05da7209@if3: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1450 qdisc noqueue master cni0 state UP group default
    &lt;span class=&quot;nb&quot;&gt;link&lt;/span&gt;/ether d2:0e:75:df:d4:eb brd ff:ff:ff:ff:ff:ff link-netnsid 7
    inet6 fe80::d00e:75ff:fedf:d4eb/64 scope &lt;span class=&quot;nb&quot;&gt;link
       &lt;/span&gt;valid_lft forever preferred_lft forever
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Because this is my production environment, there are plenty containers running
on top of this worker node. As you can see, several veth devices are listed,
only one is in our interest, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;veth05da7209@if3&lt;/code&gt;. It is the veth device which
connects to our target container. And all of them are added to the Linux bridge
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cni0&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;brctl show
bridge name     bridge &lt;span class=&quot;nb&quot;&gt;id               &lt;/span&gt;STP enabled     interfaces
cni0            8000.c2525cf04a59       no              veth05da7209
                                                        veth13c268fe
                                                        veth14fc7282
                                                        veth2ae30c15
                                                        veth376dc057
                                                        veth514198c0
                                                        veth68f59fcb
                                                        veth925c56b8
                                                        vethf1d952ca
docker0         8000.024292856896       no
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;Assume we have time machine, we can go back to the time before the Drone CI
pipeline failed. To be more precise, right at the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker build&lt;/code&gt; step running.
First, we have to make sure which Pod is our target (Drone CI will create a Pod
for each build triggered. Each Pod might contain multiple containers for steps.
The number of containers generated depends on how many steps you specified in
the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.drone.yaml&lt;/code&gt;).&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; drone get po &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; wide
NAME                                READY   STATUS     RESTARTS   AGE   IP             NODE                               NOMINATED NODE   READINESS GATES
drone-59c4ff89fc-pkwmj              1/1     Running    3          63d   10.244.3.159   k8s-worker-3.internal.zespre.com   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
drone-6e5uk6cu7n7peammeivf          5/7     NotReady   3          36s   10.244.1.113   k8s-worker-1.internal.zespre.com   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
drone-runner-kube-bc87c4fc4-tkmsz   1/1     Running    3          63d   10.244.2.78    k8s-worker-2.internal.zespre.com   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In our case, the Pod &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drone-6e5uk6cu7n7peammeivf&lt;/code&gt; has 7 containers. It’s
important to find the correct container which executes the step that went wrong
so that we can go into that container to see what just happened. It is the
container with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plugins/docker&lt;/code&gt; image in this case. You can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl
describe po&lt;/code&gt; to check the details.&lt;/p&gt;

&lt;p&gt;Let’s dive into that container:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; drone &lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; drone-6e5uk6cu7n7peammeivf &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; drone-s9oxt5dc00ii5b9wclv9 &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; /bin/sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Take a look at the network configuration via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ip address&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brctl show&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/drone/src &lt;span class=&quot;c&quot;&gt;# ip a&lt;/span&gt;
1: lo: &amp;lt;LOOPBACK,UP,LOWER_UP&amp;gt; mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    &lt;span class=&quot;nb&quot;&gt;link&lt;/span&gt;/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
3: eth0@if59: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN&amp;gt; mtu 1450 qdisc noqueue state UP
    &lt;span class=&quot;nb&quot;&gt;link&lt;/span&gt;/ether a2:7a:c9:d2:5f:8c brd ff:ff:ff:ff:ff:ff
    inet 10.244.1.113/24 brd 10.244.1.255 scope global eth0
       valid_lft forever preferred_lft forever
4: docker0: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc noqueue state UP
    &lt;span class=&quot;nb&quot;&gt;link&lt;/span&gt;/ether 02:42:76:1b:99:61 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
6: veth19bed8a@if5: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN&amp;gt; mtu 1500 qdisc noqueue master docker0 state UP
    &lt;span class=&quot;nb&quot;&gt;link&lt;/span&gt;/ether 9a:16:01:69:38:ba brd ff:ff:ff:ff:ff:ff
/drone/src &lt;span class=&quot;c&quot;&gt;# brctl show&lt;/span&gt;
bridge name     bridge &lt;span class=&quot;nb&quot;&gt;id               &lt;/span&gt;STP enabled     interfaces
docker0         8000.0242761b9961       no              veth19bed8a
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We can see that the container has one Ethernet interface &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eth0@if59&lt;/code&gt;, one Linux
bridge &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker0&lt;/code&gt;, and one veth (virtual Ethernet device) pair &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;veth19bed8a@if5&lt;/code&gt;.
Also, one end of the veth is plugged into the bridge. It seems that Docker
plugin of Drone CI utilizes Docker in Docker (DinD) to achieve build environment
isolation. We can verify our guess by issuing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker version&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/drone/src &lt;span class=&quot;c&quot;&gt;# docker version                                                                                                                                        [717/2860]&lt;/span&gt;
Client: Docker Engine - Community
 Version:           19.03.8
 API version:       1.40
 Go version:        go1.12.17
 Git commit:        afacb8b7f0
 Built:             Wed Mar 11 01:22:56 2020
 OS/Arch:           linux/amd64
 Experimental:      &lt;span class=&quot;nb&quot;&gt;false

&lt;/span&gt;Server: Docker Engine - Community
 Engine:
  Version:          19.03.8
  API version:      1.40 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;minimum version 1.12&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  Go version:       go1.12.17
  Git commit:       afacb8b7f0
  Built:            Wed Mar 11 01:30:32 2020
  OS/Arch:          linux/amd64
  Experimental:     &lt;span class=&quot;nb&quot;&gt;false
 &lt;/span&gt;containerd:
  Version:          v1.2.13
  GitCommit:        7ad184331fa3e55e52b890ea95e65ba581ae3429
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The versions of both client and server are definitely different from the one
that runs on the Kubernetes worker node. We’re now pretty sure that there’s a
Docker daemon running inside the container. It’s time to find out what’s going
on with the container which runs on DinD.&lt;/p&gt;

&lt;p&gt;This is where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker build&lt;/code&gt; happened:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/drone/src &lt;span class=&quot;c&quot;&gt;# docker container ls&lt;/span&gt;
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
7608dc4f4951        998cf36c9491        &lt;span class=&quot;s2&quot;&gt;&quot;/bin/sh -c 'bundle …&quot;&lt;/span&gt;   29 seconds ago      Up 24 seconds                           frosty_dijkstra
/drone/src &lt;span class=&quot;c&quot;&gt;# docker exec -it frosty_dijkstra /bin/sh&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# ip a&lt;/span&gt;
1: lo: &amp;lt;LOOPBACK,UP,LOWER_UP&amp;gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    &lt;span class=&quot;nb&quot;&gt;link&lt;/span&gt;/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
5: eth0@if6: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc noqueue state UP group default
    &lt;span class=&quot;nb&quot;&gt;link&lt;/span&gt;/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The network configuration of this inner container is relatively simple. It only
has one network interface &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eth0@if6&lt;/code&gt;, which is the other end of the
aforementioned veth pair. And our mission is to execute image building right
here, right now. For convenience, I just execute one of the instructions
specified in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# cd /app&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# bundle install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Guess what? Same timeout errors occurred! The package downloading progress is
intermittent, and with great possibility ends with a failure. Now that we know
we’re close to the answer.&lt;/p&gt;

&lt;h2 id=&quot;finding-the-crux&quot;&gt;Finding the Crux&lt;/h2&gt;

&lt;p&gt;Since it’s definitely a network-related issue and only occurred in the inner
container (DinD), it’s reasonable to look into the network configuration on both
containers:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Outer container, which is running on Kubernetes worker node&lt;/li&gt;
  &lt;li&gt;Inner container, which is running on the outer one&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s check the relationship between these two containers.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/dind-mtu-size-matters/the-relationship-between-two-containers.png&quot; alt=&quot;The Relationship between Two
Containers&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Obviously, there’s something wrong with the MTU size configuration. The MTU size
of veth pair in red (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;veth19bed8a@if5&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eth0@if6&lt;/code&gt;) is configured with 1500
bytes and the other one in green (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;veth05da7209@if3&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eth0@if59&lt;/code&gt;) is
configured with 1450 bytes. The MTU size of the network interface in the inner
container should less than or equal to the one in the outer container. On TCP
connections, which is “downloading Ruby gems” in our case, the initial
connection will be successful: the SYN, SYN/ACK, ACK three-way handshake will
complete since their packet size is rather small. But as soon as the first
packet of greater than 1450 bytes is attempted, the connection may hang if the
MTU mismatch is between two endpoints.&lt;/p&gt;

&lt;p&gt;Luckily, the Docker plugin of Drone CI has a parameter called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mtu&lt;/code&gt; which
configures the MTU setting of the Docker daemon in outer container. Let’s add
the parameter and set it to 1450 in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.drone.yaml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;build on push&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;plugins/docker&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;mtu&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1450&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;registry&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;registry.internal.zespre.com&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;registry.internal.zespre.com/starbops/blog&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;latest&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${DRONE_COMMIT_SHA:0:7}&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;from_secret&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker_username&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;from_secret&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker_password&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;build_args&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;JEKYLL_ENV=development&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;push&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Trigger CI by committing and pushing the change, then wait for the good news.&lt;/p&gt;

&lt;h2 id=&quot;validating-solution&quot;&gt;Validating Solution&lt;/h2&gt;

&lt;p&gt;If you’re not comfortable about sitting and waiting for the result, come with me
and dive into the container just like what we did in the previous section. Find
out the target Pod and container:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; drone get po &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; wide
NAME                                READY   STATUS     RESTARTS   AGE   IP             NODE                               NOMINATED NODE   READINESS GATES
drone-59c4ff89fc-pkwmj              1/1     Running    3          63d   10.244.3.159   k8s-worker-3.internal.zespre.com   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
drone-i6qj31d5aee65xc8br1r          5/7     NotReady   2          23s   10.244.1.117   k8s-worker-1.internal.zespre.com   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
drone-runner-kube-bc87c4fc4-tkmsz   1/1     Running    3          63d   10.244.2.78    k8s-worker-2.internal.zespre.com   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Go into that specific Drone container:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; drone &lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; drone-i6qj31d5aee65xc8br1r &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; drone-m9xyzytz8k0piuldgont &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; /bin/sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Check the MTU size of veth pair. Yes, it is configured in 1450 bytes correctly.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/drone/src &lt;span class=&quot;c&quot;&gt;# ip a&lt;/span&gt;
1: lo: &amp;lt;LOOPBACK,UP,LOWER_UP&amp;gt; mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    &lt;span class=&quot;nb&quot;&gt;link&lt;/span&gt;/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
3: eth0@if63: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN&amp;gt; mtu 1450 qdisc noqueue state UP
    &lt;span class=&quot;nb&quot;&gt;link&lt;/span&gt;/ether ae:f6:14:29:a1:ce brd ff:ff:ff:ff:ff:ff
    inet 10.244.1.117/24 brd 10.244.1.255 scope global eth0
       valid_lft forever preferred_lft forever
4: docker0: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1450 qdisc noqueue state UP
    &lt;span class=&quot;nb&quot;&gt;link&lt;/span&gt;/ether 02:42:a3:0f:07:39 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
6: vethaae3d7e@if5: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN&amp;gt; mtu 1450 qdisc noqueue master docker0 state UP
    &lt;span class=&quot;nb&quot;&gt;link&lt;/span&gt;/ether c6:9b:ab:2e:b9:9d brd ff:ff:ff:ff:ff:ff
/drone/src &lt;span class=&quot;c&quot;&gt;# brctl show&lt;/span&gt;
bridge name     bridge &lt;span class=&quot;nb&quot;&gt;id               &lt;/span&gt;STP enabled     interfaces
docker0         8000.0242a30f0739       no              vethaae3d7e
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And the inner container’s network interface is in 1450, too.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/drone/src &lt;span class=&quot;c&quot;&gt;# docker container ls&lt;/span&gt;
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
694c04367e92        7ca21f856a9d        &lt;span class=&quot;s2&quot;&gt;&quot;/bin/sh -c 'bundle …&quot;&lt;/span&gt;   19 seconds ago      Up 12 seconds                           serene_swartz
/drone/src &lt;span class=&quot;c&quot;&gt;# docker exec -it serene_swartz /bin/sh&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# ip a&lt;/span&gt;
1: lo: &amp;lt;LOOPBACK,UP,LOWER_UP&amp;gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    &lt;span class=&quot;nb&quot;&gt;link&lt;/span&gt;/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
5: eth0@if6: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1450 qdisc noqueue state UP group default
    &lt;span class=&quot;nb&quot;&gt;link&lt;/span&gt;/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Things are going very well. We should see a big green checkmark coming out for
this CI pipeline execution later.&lt;/p&gt;

&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;

&lt;p&gt;Misconfigured MTU size has always been a subtle point when it comes to an
unstable system. It’s relatively hard to discover and kind of random, so
reproducing it could be difficult. The rule of thumb is to make sure the inner
MTU size is smaller than or equal to the outer one. Of course if your system
does not add any extra information to the packet header like tunneling, the size
of MTU might not be an issue.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://liejuntao001.medium.com/fix-docker-in-docker-network-issue-in-kubernetes-cc18c229d9e5&quot;&gt;Fix Docker in docker network issue in
Kubernetes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/docker-library/docker/blob/92d278e671f32a9ee4a3c0668e46a41f4a3b74b0/19.03/dind/dockerd-entrypoint.sh#L170&quot;&gt;docker/dockerd-entrypoint.sh at 92d278e671f32a9ee4a3c0668e46a41f4a3b74b0 ·
docker-library/docker&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.projectcalico.org/networking/mtu&quot;&gt;Configure MTU to maximize network
performance&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://ithelp.ithome.com.tw/articles/10223308&quot;&gt;[Day16] CNI - Flannel 封包傳輸原理 - VXLAN分析 - iT
邦幫忙::一起幫忙解決難題，拯救 IT
人的一天&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/340747753&quot;&gt;kubernetes之flannel 网络分析&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://serverfault.com/questions/818784/how-to-match-both-sides-of-a-virtual-ethernet-link&quot;&gt;How to match both sides of a virtual ethernet
link?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://community.hpe.com/t5/Networking/What-detail-symptoms-will-I-be-getting-if-MTU-size-mismatch/td-p/6909407&quot;&gt;What detail symptoms will I be getting if MTU size
mismatch?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/101811974&quot;&gt;IPv4 Fragmentation, MTU, MSS 和
PMTUD&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Zespre Schmidt</name></author><category term="memo" /><summary type="html">Foreword</summary></entry><entry><title type="html">A Tour of Inlets - A Tunnel Built for the Cloud</title><link href="https://blog.zespre.com/inlets-the-cloud-native-tunnel.html" rel="alternate" type="text/html" title="A Tour of Inlets - A Tunnel Built for the Cloud" /><published>2021-08-09T00:00:00+00:00</published><updated>2021-08-09T00:00:00+00:00</updated><id>https://blog.zespre.com/inlets-the-cloud-native-tunnel</id><content type="html" xml:base="https://blog.zespre.com/inlets-the-cloud-native-tunnel.html">&lt;h2 id=&quot;inlets&quot;&gt;Inlets&lt;/h2&gt;

&lt;p&gt;Inlets is a lightweight tunneling tool. It’s not a SaaS product, it’s a
self-hosted tunneling solution. That means you have total control over it.
According to the &lt;a href=&quot;https://docs.inlets.dev/#/&quot;&gt;documentation of Inlets PRO&lt;/a&gt;,
there are various use cases:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Exposing services from a private network&lt;/li&gt;
  &lt;li&gt;Self-hosting HTTP endpoints with Let’s Encrypt integration (so you have
HTTPS)&lt;/li&gt;
  &lt;li&gt;Connecting local Kubernetes with public IP&lt;/li&gt;
  &lt;li&gt;… etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inlets-pro&lt;/code&gt; is the main program where the magic happened. Basically it has two
modes: TCP and HTTP. Both the server side and the client side run with the same
program, but with different subcommands. Whatever you send over the tunnel
between the server and the client &lt;strong&gt;gets encrypted&lt;/strong&gt; in transit through the
built-in TLS encryption. This is done automatically. A bonus feature is that you
do not have to expose your services on the Internet, so can use Inlets like a
VPN or SSH tunnel.&lt;/p&gt;

&lt;h3 id=&quot;architecture&quot;&gt;Architecture&lt;/h3&gt;

&lt;p&gt;As mentioned above, there must be two endpoints in a tunnel: one end is Inlets
PRO server, the other end is Inlets PRO client. The node where Inlets PRO server
runs is called &lt;strong&gt;exit-server&lt;/strong&gt;. It means that all the responses coming from the
other end of the tunnel will take the exit here. The Inlets PRO server exposes
its control-plane (websocket) to other Inlets PRO clients, so that the clients
can connect and establish the tunnel. You can put Inlets PRO clients on your
private server or even on your local computer, then expose private servers with
public endpoints through the tunnel. Then you can share the public endpoints
with customers to let them access your private services without a hassle.&lt;/p&gt;

&lt;h3 id=&quot;installation&quot;&gt;Installation&lt;/h3&gt;

&lt;p&gt;You can download &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inlets-pro&lt;/code&gt; binary from &lt;a href=&quot;https://github.com/inlets/inlets-pro/releases&quot;&gt;GitHub release
page&lt;/a&gt; directly, or you can try
the handy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inletsctl&lt;/code&gt; tool:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-sLSf&lt;/span&gt; https://inletsctl.inlets.dev | &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;sh
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;inletsctl download &lt;span class=&quot;nt&quot;&gt;--pro&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;inlets-pro version
 _       _      _            _
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;_&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;_ __ | | ___| |_ ___   __| | _____   __
| | &lt;span class=&quot;s1&quot;&gt;'_ \| |/ _ \ __/ __| / _` |/ _ \ \ / /
| | | | | |  __/ |_\__ \| (_| |  __/\ V /
|_|_| |_|_|\___|\__|___(_)__,_|\___| \_/

  PRO edition
Version: 0.8.9 - 7df6fc42cfc14dd56d93c32930262202967d234b
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;tcp-mode&quot;&gt;TCP Mode&lt;/h3&gt;

&lt;p&gt;One of my use case is to establish a secured tunnel between my homelab server
and my office Windows desktop, so I can access office environment through
Windows RDP from my Mac. All I have to do is opening up Microsoft Remote Desktop
app, and connect to my homelab server on port 3389.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/inlets-the-cloud-native-tunnel/using-inlets-pro-to-expose-windows-rdp.png&quot; alt=&quot;Using Inlets PRO to Expose Windows RDP&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Since I prefer commands which will block the terminal to be in the background,
making &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inlets-pro tcp server&lt;/code&gt; a systemd service is the way to go. Be sure to
provide the public IP address to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--auto-tls-san&lt;/code&gt; argument, so the tunnel server
can use it to generate the certificate. The token here is to ensure that no one
but only who knows the token can connect to the tunnel server. It will be used
on both server side and client side.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;TOKEN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; 16 /dev/urandom | shasum | &lt;span class=&quot;nb&quot;&gt;cut&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'-'&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; 1&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
inlets-pro tcp server &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--auto-tls&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--auto-tls-san&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;174.138.21.44&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--token&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$TOKEN&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--generate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;systemd | &lt;span class=&quot;nb&quot;&gt;sudo tee&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; /etc/systemd/system/inlets-pro.service
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl &lt;span class=&quot;nb&quot;&gt;enable &lt;/span&gt;inlets-pro.service
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl start inlets-pro.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In order to make the tunnel client outside of my homelab be able to connect to
the tunnel server on control-plane (port 8123 for example), I have to setup a
port-forwarding rule on my router/firewall. So that anyone connect to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;public-ip&amp;gt;:8123&lt;/code&gt; will reach &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;private-ip&amp;gt;:8123&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On the client side, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inlets-pro.exe tcp client&lt;/code&gt; along with a valid license
key, the aforementioned token, the URL of the tunnel server, and the ports that
you want to expose, i.e. port 3389 for Windows RDP service.&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;C:\Program&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Files\Inlets&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;PRO&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;inlets-pro.exe&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tcp&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--license&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;INLETS-PRO-LICENSE-KEY&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;wss://174.138.21.44:8123&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--token&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;fe6f868d72123701326e31d3179a07208cc5d80d&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--ports&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3389&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2021&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;/08/09&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;10:51:30&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Starting&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;TCP&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;client.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;0.8.8&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;57580545a321dc7549a26e8008999e12cb7161de&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2021&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;/08/09&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;10:51:31&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Licensed&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;redacted&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Gumroad&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;subscription&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2021&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;/08/09&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;10:51:31&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Upstream&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;server:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;localhost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ports:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;3389&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inlets-pro&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;client.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Copyright&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;OpenFaaS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Ltd&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;2021&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2021/08/09 10:51:31&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Connecting to proxy&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;wss://174.138.21.44:8123/connect&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2021/08/09 10:51:31&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Connection established.. OK.&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now that the tunnel has been established. The tunnel server listens on port 3389
for any incoming RDP connection. I can connect to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;private-ip&amp;gt;:3389&lt;/code&gt; for my
office Windows desktop in home without exposing it to the entire world.&lt;/p&gt;

&lt;h3 id=&quot;http-mode&quot;&gt;HTTP Mode&lt;/h3&gt;

&lt;p&gt;Here I will use “local file sharing via HTTP server” as an example.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Local computer&lt;/strong&gt; is the place (my M1 Mac mini) where the files we wanna
share, resides in a private network&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Exit server&lt;/strong&gt; is a machine with publicly accessible IP address, usually
resides on the cloud&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the &lt;strong&gt;exit-server&lt;/strong&gt;, start an Inlets PRO HTTP server. It will listens on port
8123 and 8000 by default as control port and data port respectively. One can
change the ports by providing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--control-port&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--port&lt;/code&gt; if there are port
conflicts.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;inlets-pro http server &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--auto-tls&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--auto-tls-san&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;188.166.208.238&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--token&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;fe6f868d72123701326e31d3179a07208cc5d80d&quot;&lt;/span&gt;
2021/08/17 16:08:45 Starting HTTP client. Version 0.8.9 - 7df6fc42cfc14dd56d93c32930262202967d234b
2021/08/17 16:08:45 Wrote: /tmp/certs/ca.crt
2021/08/17 16:08:45 Wrote: /tmp/certs/ca.key
2021/08/17 16:08:45 Wrote: /tmp/certs/server.crt
2021/08/17 16:08:45 Wrote: /tmp/certs/server.key
2021/08/17 16:08:45 TLS: 188.166.208.238, expires &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;: 2491.999991 days
2021/08/17 16:08:45 Data Plane Listening on 0.0.0.0:8000
2021/08/17 16:08:45 Control Plane Listening with TLS on 0.0.0.0:8123
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On my &lt;strong&gt;local computer&lt;/strong&gt;, setup an Inlets PRO HTTP fileserver (something similar
to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;python3 -m http.server&lt;/code&gt; but with more features such as directory browsing
toggle, basic authentication, etc.).&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;inlets-pro http fileserver &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--webroot&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/Projects&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--allow-browsing&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--token&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;supersecret&quot;&lt;/span&gt;
Starting inlets PRO fileserver. Version: 0.8.9-18-gf4fc15b - f4fc15b9604efd0b0ca3cc604c19c200ae6a1d7b
2021/08/17 16:11:13 Serving: /Users/starbops/Projects, on 127.0.0.1:8080, browsing: &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;, auth: &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since the Inlets PRO HTTP fileserver binds to local interface, I’ll start the
Inlets PRO HTTP client on the same &lt;strong&gt;local computer&lt;/strong&gt; and specify &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost&lt;/code&gt;
as upstream. So the client will establish the tunnel to the &lt;strong&gt;exit-server&lt;/strong&gt;,
and forward related HTTP requests back to the fileserver running on my &lt;strong&gt;local
computer&lt;/strong&gt;.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;inlets-pro http client &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;wss://188.166.208.238:8123&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--token&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;fe6f868d72123701326e31d3179a07208cc5d80d&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--upstream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;localhost:8080&quot;&lt;/span&gt;
Starting HTTP client. Version: 0.8.9-18-gf4fc15b - f4fc15b9604efd0b0ca3cc604c19c200ae6a1d7b
2021/08/17 16:10:59 Licensed to: &amp;lt;redacted&amp;gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Gumroad subscription&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
2021/08/17 16:10:59 Upstream:  &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; http://localhost:8080
INFO[2021/08/17 16:10:59] Connecting to proxy                           &lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;wss://188.166.208.238:8123/connect&quot;&lt;/span&gt;
INFO[2021/08/17 16:10:59] Connection established                        &lt;span class=&quot;nv&quot;&gt;client_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;5482d06cafa4404786d92eb44a916903
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;By the way, thanks to &lt;a href=&quot;https://twitter.com/alexellisuk&quot;&gt;@alex&lt;/a&gt;, I have the
opportunity to test on the RC version of the Darwin ARM64 build. It works on my
M1 Mac mini perfectly.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;inlets-pro version
 _       _      _            _
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;_&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;_ __ | | ___| |_ ___   __| | _____   __
| | &lt;span class=&quot;s1&quot;&gt;'_ \| |/ _ \ __/ __| / _` |/ _ \ \ / /
| | | | | |  __/ |_\__ \| (_| |  __/\ V /
|_|_| |_|_|\___|\__|___(_)__,_|\___| \_/

  PRO edition
Version: 0.8.9-18-gf4fc15b - f4fc15b9604efd0b0ca3cc604c19c200ae6a1d7b
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And we are all set. Open a browser then navigate to the address of the
&lt;strong&gt;exit-server&lt;/strong&gt; with the designated port (data port specified by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--port&lt;/code&gt; with
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inlets-pro http server&lt;/code&gt; command). A familiar authentication dialog should pop
up. Sign in with the default username (which is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin&lt;/code&gt;) and the specified
password, the directory list will show up.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/inlets-the-cloud-native-tunnel/basic-auth-of-inlets-http-fileserver.png&quot; alt=&quot;Basic Auth of Inlets HTTP Fileserver&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/inlets-the-cloud-native-tunnel/inlets-http-fileserver.png&quot; alt=&quot;Inlets HTTP Fileserver&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;inletsctl&quot;&gt;Inletsctl&lt;/h2&gt;

&lt;p&gt;As mentioned above, you can download the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inlets-pro&lt;/code&gt; binary through
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inletsctl&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;inletsctl download &lt;span class=&quot;nt&quot;&gt;--pro&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;While &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inlets-pro&lt;/code&gt; is dedicated to tunnel establishing, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inletsctl&lt;/code&gt; provides a
bunch of additional features about cloud service provider integrations. With
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inletsctl&lt;/code&gt;, one can easily provision an inlets-pro-ready cloud instance
(usually VM) as exit-server like a breeze. For example:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;inletsctl create &lt;span class=&quot;nt&quot;&gt;--provider&lt;/span&gt; digitalocean &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--region&lt;/span&gt; sgp1 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--access-token-file&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-access-token&lt;/span&gt;
Using provider: digitalocean
Requesting host: epic-feynman8 &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;sgp1, from digitalocean
2021/08/09 11:41:59 Provisioning host with DigitalOcean
Host: 258759207, status:
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;1/500] Host: 258759207, status: new
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;2/500] Host: 258759207, status: new
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;3/500] Host: 258759207, status: new
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;4/500] Host: 258759207, status: new
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;5/500] Host: 258759207, status: new
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;6/500] Host: 258759207, status: new
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;7/500] Host: 258759207, status: new
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;8/500] Host: 258759207, status: new
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;9/500] Host: 258759207, status: new
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;10/500] Host: 258759207, status: new
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;11/500] Host: 258759207, status: new
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;12/500] Host: 258759207, status: new
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;13/500] Host: 258759207, status: new
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;14/500] Host: 258759207, status: new
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;15/500] Host: 258759207, status: new
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;16/500] Host: 258759207, status: new
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;17/500] Host: 258759207, status: active
inlets PRO TCP &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;0.8.6&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; server summary:
  IP: 159.89.204.81
  Auth-token: EJW4btMsNaC5CKIl9cZ6qGP3baMztheIvW8GtU1zifXkkxuBr3EwtmVI7hM1bmsK

Command:

&lt;span class=&quot;c&quot;&gt;# Obtain a license at https://inlets.dev&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Store it at $HOME/.inlets/LICENSE or use --help for more options&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;LICENSE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/.inlets/LICENSE&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Give a single value or comma-separated&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PORTS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;8000&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Where to route traffic from the inlets server&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;UPSTREAM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;localhost&quot;&lt;/span&gt;

inlets-pro tcp client &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;wss://159.89.204.81:8123&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--token&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;EJW4btMsNaC5CKIl9cZ6qGP3baMztheIvW8GtU1zifXkkxuBr3EwtmVI7hM1bmsK&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--upstream&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$UPSTREAM&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--ports&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$PORTS&lt;/span&gt;

To delete:
  inletsctl delete &lt;span class=&quot;nt&quot;&gt;--provider&lt;/span&gt; digitalocean &lt;span class=&quot;nt&quot;&gt;--id&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;258759207&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The IP address and the token is shown in the output after finishing the
deployment of cloud instance. Use the provided information in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inlets-pro tcp
client&lt;/code&gt; command:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;UPSTREAM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;nuclear.internal.zespre.com&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PORTS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;3000&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;inlets-pro tcp client &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;wss://159.89.204.81:8123&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--token&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;EJW4btMsNaC5CKIl9cZ6qGP3baMztheIvW8GtU1zifXkkxuBr3EwtmVI7hM1bmsK&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--upstream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$UPSTREAM&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--ports&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PORTS&lt;/span&gt;
2021/08/09 12:00:00 Starting TCP client. Version 0.8.8 - 57580545a321dc7549a26e8008999e12cb7161de
2021/08/09 12:00:00 Licensed to: &amp;lt;redacted&amp;gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Gumroad subscription&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
2021/08/09 12:00:00 Upstream server: nuclear.internal.zespre.com, &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;ports: 3000
inlets-pro client. Copyright OpenFaaS Ltd 2021
INFO[2021/08/09 12:00:01] Connecting to proxy                           &lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;wss://159.89.204.81:8123/connect&quot;&lt;/span&gt;
INFO[2021/08/09 12:00:01] Connection established.. OK.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Just remember to put a valid Inlets PRO license under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$HOME/.inlets/LICENSE&lt;/code&gt; so
you don’t need to specify the license in the command line.&lt;/p&gt;

&lt;p&gt;Delete the cloud instance if the tunnel is no longer needed, otherwise it’ll
cost your money:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;inletsctl delete &lt;span class=&quot;nt&quot;&gt;--provider&lt;/span&gt; digitalocean &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--access-token-file&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-access-token&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--ip&lt;/span&gt; 159.89.204.81
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;inlets-operator&quot;&gt;Inlets-operator&lt;/h2&gt;

&lt;p&gt;IMO Inlets-operator is the most brilliant part over all these cloud native
tunneling use cases. As an operator of Inlets PRO in Kubernetes, it monitors on
Service resources (especially on LoadBalancer type of Service) and manages
Tunnel resource and exit servers. This is perfect for a private/local deployment
of Kubernetes cluster. Think about exposing your application running on a
Raspberry Pi powered K8s in your homelab, it could be a real pain in the ass.
However, with Inlets-operator, things will be different! For more details, check
out &lt;a href=&quot;https://iximiuz.com/en/posts/kubernetes-operator-pattern/&quot;&gt;Ivan Velichko’s article about Kubernetes operator pattern and
Inlets-operator&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;installation-1&quot;&gt;Installation&lt;/h3&gt;

&lt;p&gt;Install CRDs and Helm charts of Inlets-operator. Here I choose DigitalOcean as
my cloud service provider, you can choose whatever suits you best.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/inlets/inlets-operator.git
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;inlets-operator/
helm repo add inlets https://inlets.github.io/inlets-operator/
helm repo update
kubectl apply &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; ./artifacts/crds/
kubectl create ns inlets-operator
helm upgrade inlets-operator &lt;span class=&quot;nt&quot;&gt;--install&lt;/span&gt; inlets/inlets-operator &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--set&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;digitalocean &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--set&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;region&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;sgp1 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--set&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;inletsProLicense&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;/.inlets/LICENSE&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--set&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;annotatedOnly&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt; inlets-operator
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If there is any problem, check the logs generated in Inlets-operator Deployment:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; inlets-operator logs deploy/inlets-operator &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;deploying-example-application&quot;&gt;Deploying Example Application&lt;/h3&gt;

&lt;p&gt;Now the Inlets-operator is ready, let’s deploy an example application to see how
it works. Spin up a Deployment with Nginx web server:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt; | kubectl apply -f -
&amp;gt; apiVersion: apps/v1
&amp;gt; kind: Deployment
&amp;gt; metadata:
&amp;gt;   name: nginx-1
&amp;gt;   labels:
&amp;gt;     app: nginx
&amp;gt; spec:
&amp;gt;   replicas: 1
&amp;gt;   selector:
&amp;gt;     matchLabels:
&amp;gt;       app: nginx
&amp;gt;   template:
&amp;gt;     metadata:
&amp;gt;       labels:
&amp;gt;         app: nginx
&amp;gt;     spec:
&amp;gt;       containers:
&amp;gt;       - name: nginx
&amp;gt;         image: nginx:1.14.2
&amp;gt;         ports:
&amp;gt;         - containerPort: 80
&amp;gt; EOF
deployment.apps/nginx-1 created
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;exposing-private-service&quot;&gt;Exposing Private Service&lt;/h3&gt;

&lt;p&gt;With Nginx running in a Pod, we still need to expose the port on which Nginx
listens, so that users can access it. Here we use LoadBalancer type of Service
which means Inlets-operator will provision a cloud instance with a public IP
address as a load-balancer, then establish a secured tunnel between the cloud
instance and the auxiliary Pod running alongside with Nginx Pod on the local K8s
for us.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt; | kubectl apply -f -
&amp;gt; apiVersion: v1
&amp;gt; kind: Service
&amp;gt; metadata:
&amp;gt;   name: nginx-1
&amp;gt;   annotations:
&amp;gt;     metallb.universe.tf/address-pool: &quot;dummy&quot;
&amp;gt;     dev.inlets.manage: &quot;true&quot;
&amp;gt; spec:
&amp;gt;   type: LoadBalancer
&amp;gt;   selector:
&amp;gt;     app: nginx
&amp;gt;   ports:
&amp;gt;   - name: http
&amp;gt;     protocol: TCP
&amp;gt;     port: 80
&amp;gt;     targetPort: 80
&amp;gt; EOF
service/nginx-1 created
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s worth mentioning that since in my deployment of K8s, there is
&lt;a href=&quot;https://metallb.universe.tf/&quot;&gt;MetalLB&lt;/a&gt; running as a bare-metal load-balancer,
which allocates IP addresses from a private network segment (192.168.xx.0/24)
to LoadBalancer Services. So I have to make MetalLB and Inlets-operator know
which LoadBalancer type of Service they should take care of. The solution by far
is to install Inlets-operator with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;annotatedOnly=true&lt;/code&gt; and add two annotations
in every LoadBalancer type of Service:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;metallb.universe.tf/address-pool: &quot;dummy&quot;&lt;/code&gt;: “dummy” can be replaced with any
other terms which isn’t a valid address pool name in your MetalLB setup. This
will make MetalLB ignore the request of load-balancer IP address.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dev.inlets.manage: &quot;true&quot;&lt;/code&gt;: Inlets-operator will only take action on
Services with this annotated&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;accessing-from-outside&quot;&gt;Accessing from Outside&lt;/h3&gt;

&lt;p&gt;After the Service created, we can see that Inlets-operator is provisioning the
tunnel for us. And the external IP address is still in the pending status
because the cloud instance is spawning.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl get svc,tunnel
NAME                 TYPE           CLUSTER-IP       EXTERNAL-IP   PORT&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;S&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;        AGE
service/nginx-1      LoadBalancer   10.97.233.179    &amp;lt;pending&amp;gt;     80:30555/TCP   6s

NAME                                      SERVICE   TUNNEL   HOSTSTATUS     HOSTIP   HOSTID
tunnel.inlets.inlets.dev/nginx-1-tunnel   nginx-1            provisioning            261289194
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-w&lt;/code&gt; option to watch the progress. Soon there is a public IP address
shown up.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl get svc &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt;
NAME         TYPE           CLUSTER-IP       EXTERNAL-IP   PORT&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;S&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;        AGE
nginx-1      LoadBalancer   10.97.233.179    &amp;lt;pending&amp;gt;     80:30555/TCP   28s
nginx-1      LoadBalancer   10.97.233.179    178.128.221.76   80:30555/TCP   72s
nginx-1      LoadBalancer   10.97.233.179    178.128.221.76,178.128.221.76   80:30555/TCP   72s
nginx-1      LoadBalancer   10.97.233.179    178.128.221.76                  80:30555/TCP   72s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you check out the Deployment and Pod lists you’ll notice that there is a new
Pod created by Inlets-operator automatically. Actually it is the auxiliary Pod
which runs Inlets PRO client! All the tedious works such as generating token,
figuring out where the tunnel server and upstream are, are handled by
Inlets-operator. It just works.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl get deploy,po
NAME                                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-1                 1/1     1            1           5m35s
deployment.apps/nginx-1-tunnel-client   1/1     1            1           2m26s

NAME                                        READY   STATUS    RESTARTS   AGE
pod/nginx-1-66b6c48dd5-dksx9                1/1     Running   0          5m35s
pod/nginx-1-tunnel-client-d9cd96c74-ks7vg   1/1     Running   3          2m25s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now you can access the private Nginx with the above listed public IP address
from anywhere (with Internet connection)!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/inlets-the-cloud-native-tunnel/exposing-private-nginx.png&quot; alt=&quot;Exposing Private Nginx&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Inlets PRO is a swiss army knife. There are various use cases listed in the
documents. It can replace SSH tunneling with so much ease. Users can expose
their private services efficiently like never before. It is also possible
bringing remote services to local using Inlets PRO. Unlike SaaS tunneling
solutions like &lt;a href=&quot;https://ngrok.com/&quot;&gt;Ngrok&lt;/a&gt;, you have total control over your
infrastructure without traffic throttling. And the data flow through the tunnel
is secured out of the box. If you have a local K8s deployment, definitely &lt;a href=&quot;https://gumroad.com/a/751932531/HGlxA&quot;&gt;give
it a try!&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/metallb/metallb/issues/685&quot;&gt;Ignore a service of type=LoadBalancer · Issue #685 ·
metallb/metallb&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Zespre Schmidt</name></author><category term="memo" /><summary type="html">Inlets</summary></entry><entry><title type="html">Zabbix/Grafana Installation and SNMP Trap Setup</title><link href="https://blog.zespre.com/zabbix-grafana-installation-and-snmp-trap-setup.html" rel="alternate" type="text/html" title="Zabbix/Grafana Installation and SNMP Trap Setup" /><published>2020-05-29T00:00:00+00:00</published><updated>2020-05-29T00:00:00+00:00</updated><id>https://blog.zespre.com/zabbix-grafana-installation-and-snmp-trap-setup</id><content type="html" xml:base="https://blog.zespre.com/zabbix-grafana-installation-and-snmp-trap-setup.html">&lt;p&gt;CentOS 7 + Zabbix 4.0&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# yum install epel-release&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# yum install yum-utils&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# yum-config-manager --enable rhel-7-server-optional-rpms&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;zabbix-installation&quot;&gt;Zabbix Installation&lt;/h2&gt;

&lt;h3 id=&quot;adding-zabbix-repository&quot;&gt;Adding Zabbix Repository&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# rpm -Uvh https://repo.zabbix.com/zabbix/4.0/rhel/7/x86_64/zabbix-release-4.0-1.el7.noarch.rpm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;mysql&quot;&gt;MySQL&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# rpm -Uvh https://repo.mysql.com/mysql80-community-release-el7-3.noarch.rpm&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# sed -i 's/enabled=1/enabled=0/' /etc/yum.repos.d/mysql-community.repo&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# yum --enablerepo=mysql80-community install mysql-community-server&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# systemctl start mysqld.service&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# grep &quot;A temporary password&quot; /var/log/mysqld.log&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# mysql_secure_installation&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# systemctl restart mysqld.service&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# systemctl enable mysqld.service&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;mysql&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;database&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;zabbix&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;character&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;utf8&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;collate&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;utf8_bin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;mysql&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'zabbix'&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'localhost'&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;identified&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&amp;lt;password&amp;gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;mysql&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;grant&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;all&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;privileges&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;zabbix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'zabbix'&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'localhost'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;mysql&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'zabbix'&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'localhost'&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;identified&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mysql_native_password&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&amp;lt;password&amp;gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;mysql&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;zabbix-server&quot;&gt;Zabbix Server&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# yum install zabbix-server-mysql zabbix-web-mysql&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# zcat /usr/share/doc/zabbix-server-mysql*/create.sql.gz | mysql -uzabbix -p zabbix&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# firewall-cmd --zone=public --add-port=80/tcp --permanent&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# firewall-cmd --reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/zabbix/zabbix_server.conf&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;DBHost=localhost
DBName=zabbix
DBUser=zabbix
DBPassword=&amp;lt;password&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/httpd/conf.d/zabbix.conf&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;php_value max_execution_time 300
php_value memory_limit 128M
php_value post_max_size 16M
php_value upload_max_filesize 2M
php_value max_input_time 300
php_value max_input_vars 10000
php_value always_populate_raw_post_data -1
php_value date.timezone Asia/Taipei
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Following up by frontend installation steps.&lt;/p&gt;

&lt;h3 id=&quot;selinux-configuration&quot;&gt;SELinux Configuration&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# setsebool -P httpd_can_connect_zabbix on&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;zabbix-agent&quot;&gt;Zabbix Agent&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# yum install zabbix-agent&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# systemctl enable zabbix-agent.service&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# systemctl start zabbix-agent.service&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;grafana-installation&quot;&gt;Grafana Installation&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/yum.repos.d/grafana.repo&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[grafana]
name=grafana
baseurl=https://packages.grafana.com/oss/rpm
repo_gpgcheck=1
enabled=1
gpgcheck=1
gpgkey=https://packages.grafana.com/gpg.key
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# yum install grafana&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# yum install fontconfig freetype* urw-fonts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# systemctl enable grafana-server.service&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# systemctl start grafana-server.service&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# firewall-cmd --zone=public --add-port=3000/tcp --permanent&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# firewall-cmd --reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# grafana-cli plugins install alexanderzobnin-zabbix-app&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# systemctl restart grafana-server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The plugins are installed under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/lib/grafana/plugins&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Zabbix HTTP URL is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&amp;lt;zabbix-server-ip&amp;gt;/zabbix/api_jsonrpc.php&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;snmp-trap-setup&quot;&gt;SNMP Trap Setup&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# yum install net-snmp net-snmp-utils net-snmp-perl&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# yum install perl-Config-IniFiles perl-Sys-Syslog&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.zabbix.com/documentation/4.0/manual/installation/install_from_packages/rhel_centos&quot;&gt;Zabbix Documentation 4.0&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/59356695/i-received-an-zabbix-server-start-error-and-shared-solition&quot;&gt;i received an zabbix server start error and shared solition&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.fosslinux.com/8328/how-to-install-and-configure-grafana-on-centos-7.htm&quot;&gt;How to install and configure Grafana on CentOS 7 | FOSS Linux&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://techexpert.tips/zabbix/zabbix-ipmi-monitor/&quot;&gt;Tutorial - Zabbix IPMI Monitor Configuration&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.zabbix.com/snmp-traps-in-zabbix/&quot;&gt;SNMP Traps in Zabbix&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://qiita.com/mgmjoke/items/0cedf8eee419b7504a09&quot;&gt;Zabbix4.0 をRHEL8 へインストール（仮）- SNMPTT設定もするよ - Qiita&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://support.nagios.com/kb/article.php?id=557&quot;&gt;SNMP Traps - Standard Handler vs Embedded Handler&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.xuite.net/aflyfish/blog/86126735-%5B+NetSNMP+%5D+snmptrapd.conf+設定&quot;&gt;[ NetSNMP ] snmptrapd.conf 設定&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Zespre Schmidt</name></author><category term="memo" /><summary type="html">CentOS 7 + Zabbix 4.0</summary></entry><entry><title type="html">Automate Let’s Encrypt DNS Challenge with Certbot and Gandi.net</title><link href="https://blog.zespre.com/lets-encrypt-dns-challenge.html" rel="alternate" type="text/html" title="Automate Let’s Encrypt DNS Challenge with Certbot and Gandi.net" /><published>2020-05-28T00:00:00+00:00</published><updated>2020-05-28T00:00:00+00:00</updated><id>https://blog.zespre.com/lets-encrypt-dns-challenge</id><content type="html" xml:base="https://blog.zespre.com/lets-encrypt-dns-challenge.html">&lt;p&gt;It’s always recommended to view web pages through HTTPS connections, even it’s
just a static HTML page. So, as a content provider, it’s my duty to host
websites with HTTPS. To enable HTTPS on the web server like Apache or Nginx,
valid certificates are required. In my case, I have bought and configured a
domain name on &lt;a href=&quot;https://gandi.net&quot;&gt;Gandi.net&lt;/a&gt; for my home cluster. It’s better
to have different certificates for each service than having a single wildcard
certificate for all the services due to security concerns. However, I still use
wildcard certificate for one reason (I’ll talk about it later). So in this
article I’m going to explain how to get TLS wildcard certificates with Let’s
Encrypt using DNS validation.&lt;/p&gt;

&lt;h2 id=&quot;how-dns-validation-of-acme-protocol-works&quot;&gt;How DNS Validation of ACME Protocol Works&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://letsencrypt.org&quot;&gt;Let’s Encrypt&lt;/a&gt; is a well-known open project and
nonprofit certificate authority that provides TLS certificates to hundreds of
thousands of websites around the world. Let’s Encrypt uses the ACME (Automatic
Certificate Management Environment) protocol to verify that one controls a given
domain name and to issue a certificate. There are mainly two ways to do that:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;HTTP validation&lt;/li&gt;
  &lt;li&gt;DNS validation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Also, there are two types of domain name:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Fully-qualified domain names&lt;/li&gt;
  &lt;li&gt;Wildcard domain names&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It’s worth mentioning that currently, in API version 2, the only one way to get
certificates for wildcard domain names is through DNS validation. And I’m going
to get certificates for my services running inside of a private network, which
means I can only use DNS validation since the domain names I configured for my
services are not publicly reachable. Those DNS A records are mapped to private
IP addresses, so the HTTP validation is not applicable.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I know it’s not a good practice to expose your internal network architecture
through registering public DNS records for private IP addresses. It leaks a
great amount of valued information of your environment. But I have to say,
it’s super fuxking convenient! A legit way to do that is to have your own
private DNS service which serves the private DNS records, and use it
internally.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, how does DNS validation of ACME protocol work? It’s basically done by
manipulating TXT records. If you know how HTTP validation works (you should!),
it’s the same. It makes you put a specific value in a TXT record so that you can
prove the ownership of the domain name you’re requesting for a certificate.
After the validation is done, you clean up the TXT record. The website of Let’s
Encrypt has a &lt;a href=&quot;https://letsencrypt.org/docs/challenge-types/#dns-01-challenge&quot;&gt;good
explaination&lt;/a&gt; of
it.&lt;/p&gt;

&lt;h2 id=&quot;certbot-installation&quot;&gt;Certbot Installation&lt;/h2&gt;

&lt;p&gt;First we need to install ACME client software to help us get the certificates.
There’re &lt;a href=&quot;https://letsencrypt.org/docs/client-options/&quot;&gt;various implementation out
there&lt;/a&gt;, and we choose the
recommended one, which is &lt;a href=&quot;https://certbot.eff.org&quot;&gt;certbot&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt update
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;software-properties-common
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;add-apt-repository universe
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;add-apt-repository ppa:certbot/certbot
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt update
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;certbot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now the software has been installed, we should be able to get certificates
easily. For DNS validation, it will guide us to get the certificate in an
interactive way. I’ll skip that since it’s pretty straightforward.&lt;/p&gt;

&lt;h2 id=&quot;you-need-automation-hooks&quot;&gt;You Need Automation: Hooks&lt;/h2&gt;

&lt;p&gt;After going through the process of DNS validation manually we noticed that it’s
just a pain in the ass. The steps are trivial and time-consuming. There must be
a way to automate that. And you’re right!&lt;/p&gt;

&lt;p&gt;You can use one of certbot’s &lt;a href=&quot;https://certbot.eff.org/docs/using.html?highlight=dns#dns-plugins&quot;&gt;DNS
plugins&lt;/a&gt; to
achieve this. But unfortunately, my DNS provider, Gandi.net, does not provide a
usable plugin.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;By the time of writing, there’s a &lt;a href=&quot;https://github.com/obynio/certbot-plugin-gandi&quot;&gt;third-party
plugin&lt;/a&gt; which is not
officially backed by Gandi.net. I’ll give it a shot in the future. The value
of this article is to show you how to do the automation with the APIs provided
by Gandi.net and integrates it with certbot.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The way of plugin is not working, though. Certbot supports pre and post
validation hooks when running in manual mode. The hooks are external scripts
executed by certbot to perform the tasks related to DNS validation. It includes
two command line options &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--manual-auth-hook&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--manual-cleanup-hook&lt;/code&gt;. Both
flags should be filled with the path to the hook scripts respectively. The main
idea is to place the ACME challenge to TXT record using Gandi.net’s LiveDNS
API. And of course, to clean up the TXT record when the validation is done.&lt;/p&gt;

&lt;p&gt;Here’s the auth hook &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;authenticator.sh&lt;/code&gt; written in Bash:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/usr/bin/env sh&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;APIKEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'your-api-key-here'&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CERTBOT_DOMAIN&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CERTBOT_VALIDATION&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CERTBOT_DOMAIN&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | rev | &lt;span class=&quot;nb&quot;&gt;cut&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;.&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f1-2&lt;/span&gt; | rev&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;subdomain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CERTBOT_DOMAIN&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
        &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'Same!'&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else
        &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'Different!'&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;subdomain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CERTBOT_DOMAIN&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | rev | &lt;span class=&quot;nb&quot;&gt;cut&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;.&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f3-&lt;/span&gt; | rev&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;subdomain_with_dot&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;subdomain&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fi

&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Authorization: Apikey &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$APIKEY&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; https://api.gandi.net/v5/livedns/domains/&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/records/_acme-challenge&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;subdomain_with_dot&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; | python &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; json.tool&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;[]&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
        &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'Newly created!'&lt;/span&gt;
        curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; POST https://api.gandi.net/v5/livedns/domains/&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/records/_acme-challenge&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;subdomain_with_dot&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Authorization: Apikey &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$APIKEY&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;--data&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'{&quot;rrset_type&quot;: &quot;TXT&quot;, &quot;rrset_values&quot;: [&quot;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CERTBOT_VALIDATION&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&quot;], &quot;rrset_ttl&quot;: &quot;300&quot;}'&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;else
        &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'Append!'&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;previous_validation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | python &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;import sys, json; print json.load(sys.stdin)[0]['rrset_values'][0]&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;tr&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&quot;'&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'previsou_validation: '&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;previous_validation&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;
        curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; PUT https://api.gandi.net/v5/livedns/domains/&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/records/_acme-challenge&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;subdomain_with_dot&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Authorization: Apikey &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$APIKEY&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;--data&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'{&quot;items&quot;: [{&quot;rrset_type&quot;: &quot;TXT&quot;, &quot;rrset_values&quot;: [&quot;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;previous_validation&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&quot;, &quot;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CERTBOT_VALIDATION&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&quot;], &quot;rrset_ttl&quot;: &quot;300&quot;}]}'&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fi

&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sleep &lt;/span&gt;30
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And the cleanup hook &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cleanup.sh&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/usr/bin/env sh&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;APIKEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'your-api-key-here'&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CERTBOT_DOMAIN&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CERTBOT_DOMAIN&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | rev | &lt;span class=&quot;nb&quot;&gt;cut&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;.&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f1-2&lt;/span&gt; | rev&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;subdomain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CERTBOT_DOMAIN&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
        &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'Same!'&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else
        &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'Different!'&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;subdomain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CERTBOT_DOMAIN&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | rev | &lt;span class=&quot;nb&quot;&gt;cut&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;.&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f3-&lt;/span&gt; | rev&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;subdomain_with_dot&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;subdomain&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fi

&lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; DELETE https://api.gandi.net/v5/livedns/domains/&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/records/_acme-challenge&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;subdomain_with_dot&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Authorization: Apikey &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$APIKEY&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Just remember to put your valid &lt;a href=&quot;https://docs.gandi.net/en/domain_names/advanced_users/api.html&quot;&gt;Gandi.net API
token&lt;/a&gt; into the
scripts. I won’t cover that, either.&lt;/p&gt;

&lt;p&gt;Giving them executable bit:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;chmod&lt;/span&gt; +x authenticator.sh cleanup.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The work has been done. It’s time to integrate these hooks scripts with certbot
itself.&lt;/p&gt;

&lt;h2 id=&quot;generating-new-certificates&quot;&gt;Generating New Certificates&lt;/h2&gt;

&lt;p&gt;To generate a wildcard certificate for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;internal.zespre.com&lt;/code&gt;, try this:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;certbot certonly &lt;span class=&quot;nt&quot;&gt;--manual&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.internal.zespre.com &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; internal.zespre.com &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--manual-auth-hook&lt;/span&gt; authenticator.sh &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--manual-cleanup-hook&lt;/span&gt; cleanup.sh &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--preferred-challenges&lt;/span&gt; dns
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Obtaining a new certificate
Performing the following challenges:
dns-01 challenge &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;internal.zespre.com
dns-01 challenge &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;internal.zespre.com

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NOTE: The IP of this machine will be publicly logged as having requested this
certificate. If you&lt;span class=&quot;s1&quot;&gt;'re running certbot in manual mode on a machine that is not
your server, please ensure you'&lt;/span&gt;re okay with that.

Are you OK with your IP being logged?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Y&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;es/&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;N&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;o: Y
Output from authenticator.sh:
internal.zespre.com
0DrYgNTYVzuMexfppEz03-bL0H8V91Z7qi1NHfefZgs
Different!
Newly created!
&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;message&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;DNS Record Created&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
Output from authenticator.sh:
internal.zespre.com
vaTN_2ze9AJmBeW1Pe3_NUKsnOBakus8sN8mHmYHAkE
Different!
Append!
previsou_validation: 0DrYgNTYVzuMexfppEz03-bL0H8V91Z7qi1NHfefZgs
&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;message&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;DNS Record Created&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
Waiting &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;verification...
Cleaning up challenges
Output from cleanup.sh:
internal.zespre.com
Different!

Output from cleanup.sh:
internal.zespre.com
Different!

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/internal.zespre.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/internal.zespre.com/privkey.pem
   Your cert will expire on 2021-03-14. To obtain a new or tweaked
   version of this certificate &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;the future, simply run certbot
   again. To non-interactively renew &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;all&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; of your certificates, run
   &lt;span class=&quot;s2&quot;&gt;&quot;certbot renew&quot;&lt;/span&gt;
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let&lt;span class=&quot;s1&quot;&gt;'s Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you look into the output you’ll find that there are some messages about the
process of the validation. Here’s a brief explanation:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/lets-encrypt-dns-challenge/dns-challenge-validation.png&quot; alt=&quot;The Process of ACME DNS Challenge
Validation&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now that the newly generated private key and issued certificates are under the
directory &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/letsencrypt/live/&amp;lt;your-domain&amp;gt;/&lt;/code&gt;. Make good use of them!&lt;/p&gt;

&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;

&lt;p&gt;In this article we have shown that how ACME DNS validation works, and adding
automation to certificate generation with the ability of certbot validation
hooks. So we can obtain wildcard certificates for our services running inside
private network. Hope you like it!&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://letsencrypt.org/how-it-works/&quot;&gt;How It Works&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://medium.com/walkout/%E5%88%A9%E7%94%A8-lets-encrypt-%E4%BE%86%E8%87%AA%E5%8B%95%E7%B0%BD%E7%BD%B2%E4%B8%A6%E6%9B%B4%E6%96%B0-ssl-%E6%86%91%E8%AD%89-wildcard-26b49114bf73&quot;&gt;利用 Let’s Encrypt 來自動簽署並更新 SSL 憑證
(wildcard)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://haway.30cm.gg/certbot-plugins-dns-gandi-livedns/&quot;&gt;Certbot 自動續約，自動驗證 DNS 域名所有權 - LiveDNS |
哈部落&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://api.gandi.net/docs/livedns/&quot;&gt;LiveDNS API&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://certbot.eff.org/docs/using.html?highlight=hook#hooks&quot;&gt;User Guide - Certbot 1.7.0.dev0
documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://andyyou.github.io/2019/04/13/how-to-use-certbot/&quot;&gt;解析 Certbot（Let’s encrypt）
使用方式&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/14368658/need-to-suck-tld-out-of-list-of-fqdns-using-bash-script&quot;&gt;Need to suck TLD out of list of FQDN’s using BASH
script&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Zespre Schmidt</name></author><category term="memo" /><summary type="html">It’s always recommended to view web pages through HTTPS connections, even it’s just a static HTML page. So, as a content provider, it’s my duty to host websites with HTTPS. To enable HTTPS on the web server like Apache or Nginx, valid certificates are required. In my case, I have bought and configured a domain name on Gandi.net for my home cluster. It’s better to have different certificates for each service than having a single wildcard certificate for all the services due to security concerns. However, I still use wildcard certificate for one reason (I’ll talk about it later). So in this article I’m going to explain how to get TLS wildcard certificates with Let’s Encrypt using DNS validation.</summary></entry><entry><title type="html">Cloud-Init Overview</title><link href="https://blog.zespre.com/cloud-init-overview.html" rel="alternate" type="text/html" title="Cloud-Init Overview" /><published>2020-05-15T00:00:00+00:00</published><updated>2020-05-15T00:00:00+00:00</updated><id>https://blog.zespre.com/cloud-init-overview</id><content type="html" xml:base="https://blog.zespre.com/cloud-init-overview.html">&lt;p&gt;Cloud-init’s behavior can be configured via user-data. User-data can be given by
the user at instance launch time.&lt;/p&gt;

&lt;h2 id=&quot;boot-stages&quot;&gt;Boot Stages&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;Generator&lt;/li&gt;
  &lt;li&gt;Local&lt;/li&gt;
  &lt;li&gt;Network&lt;/li&gt;
  &lt;li&gt;Config&lt;/li&gt;
  &lt;li&gt;Final&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;generator&quot;&gt;Generator&lt;/h3&gt;

&lt;p&gt;When booting under systemd, a generator will run that determines if
cloud-init.target should be included in the boot goals. By default, this
generator will enable cloud-init.&lt;/p&gt;

&lt;h3 id=&quot;local&quot;&gt;Local&lt;/h3&gt;

&lt;p&gt;The purpose of the local stage is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Locate “local” data sources&lt;/li&gt;
  &lt;li&gt;Apply networking configuration to the system (including “Fallback”)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This stage must block network bring-up or any stale configuration might already
have been applied. That could have negative effects such as DHCP hooks or
broadcast of an old hostname. It would also put the system in an odd state to
recover from as it may then have to restart network devices.&lt;/p&gt;

&lt;h3 id=&quot;network&quot;&gt;Network&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;systemd service: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cloud-init.service&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;modules: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cloud_init_modules&lt;/code&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/cloud/cloud.cfg&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This stage requires all configured networking to be online, as it will fully
process any user-data that is found. This stage runs the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;disk_setup&lt;/code&gt; and
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mounts&lt;/code&gt; modules which may partition and format disks and configure mount points
(such as in /etc/fstab). Those modules cannot run earlier as they may receive
configuration input from sources only available via network. For example, a user
may have provided user-data in a network resource that describes how local
mounts should be done.&lt;/p&gt;

&lt;h3 id=&quot;config&quot;&gt;Config&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;systemd service: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cloud-config.service&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;modules: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cloud_config_modules&lt;/code&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/cloud/cloud.cfg&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This stage runs config modules only. Modules that do not really have an effect
on other stages of boot are run here.&lt;/p&gt;

&lt;h3 id=&quot;final&quot;&gt;Final&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;systemd service: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cloud-final.service&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;modules: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cloud_final_modules&lt;/code&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/cloud/cloud.cfg&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This stage runs as late in boot as possible. Any scripts that a user is
accustomed to running after logging into a system should run correctly here.&lt;/p&gt;

&lt;h2 id=&quot;fallback-network-configuration&quot;&gt;Fallback Network Configuration&lt;/h2&gt;

&lt;p&gt;Cloud-init will attempt to determine which of any attached network devices is
most likely to have a connection and then generate a network configuration to
issue a DHCP request on that interface.&lt;/p&gt;

&lt;p&gt;Cloud-init&lt;/p&gt;

&lt;h1 id=&quot;metadata&quot;&gt;Metadata&lt;/h1&gt;

&lt;p&gt;Nova presents configuration information to instances it starts via a mechanism
called metadata. These mechanisms are widely used via helpers such as cloud-init
to specify things like the root password the instance should use.&lt;/p&gt;

&lt;p&gt;This metadata is made available via either a config driver or the metadata
service and can be somewhat customized by the user using the user data feature.&lt;/p&gt;

&lt;h2 id=&quot;metadata-service&quot;&gt;Metadata Service&lt;/h2&gt;

&lt;p&gt;Metadata service lets an instance retrieve instance specific information, e.g.
hostname, IP, routes, SSH keys, user-data, vendor-data and various default
settings even commands and shell scripts. All of these are generally handled by
a service in the instance like cloud-init.&lt;/p&gt;

&lt;h3 id=&quot;supported-versions&quot;&gt;Supported Versions&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;starbops@shitcoin ~]&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl http://169.254.169.254/openstack
2012-08-10
2013-04-04
2013-10-17
2015-10-15
2016-06-30
2016-10-06
latest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;endpoints-of-metadata-service&quot;&gt;Endpoints of Metadata Service&lt;/h3&gt;

&lt;p&gt;List all meta-data service’s endpoints by requesting
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://169.254.169.254/latest/meta-data&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;starbops@shitcoin ~]&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl http://169.254.169.254/latest/meta-data
ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/
&lt;span class=&quot;nb&quot;&gt;hostname
&lt;/span&gt;instance-action
instance-id
instance-type
local-hostname
local-ipv4
placement/
public-hostname
public-ipv4
reservation-id
security-groups
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For example, getting floating IP address bond with the instance:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;starbops@shitcoin ~]&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl http://169.254.169.254/latest/meta-data/public-ipv4
100.74.37.104
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To see what user-data have been filled:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;starbops@shitcoin ~]&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl http://169.254.169.254/latest/user-data
&lt;span class=&quot;c&quot;&gt;#cloud-config&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#packages:&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#  - htop&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#ssh_authorized_keys:&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#  - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkgolYx6IoPSYnhZMdGivUJOm4AMtI0gkjFkY/53V4idbliQHAMJHGdMGYdlEm5ThOCw3DDblsQDNy7EZJaa9+T1imwrnUg0fYU13u+Tfq7Fg+TIn4hf4uG/ei2r1MLp2/lO/6dEPUGv2TiBQ+SVfB8yt2IUVIGgqNhGWJi/p5uw9O5KiAPN1UmT3CvpWYVFnfqvnDwnOMJkXg9xN8AbTkAHS1YDIljNMwBisaOvI5cjgZ5a+ovp2pdHBxZWyPAb7Y5NvlQHGtJQIlbWTcxIBu8/1YPbZlkTcgB0ghDf0upgKunqFHh/Zq3sdEEUyQ2Xr6qdVyaXwNQJhV8Kge196r ubuntu@controller&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;users&lt;/span&gt;:
  - default
  - name: starbops
    gecos: Zespre Schmidt
    &lt;span class=&quot;nb&quot;&gt;sudo&lt;/span&gt;: &lt;span class=&quot;nv&quot;&gt;ALL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;ALL&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; NOPASSWD:ALL
    &lt;span class=&quot;c&quot;&gt;#lock_passwd: false&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#passwd: $6$rounds=4096$lDq1KTfs/T/e7$EfyJlD0aqyO7W8oSOumQySoYxfqBC5OxzGXnGrFV6AIMms5QnE0pwdwJttTw2iliFKoKfnFnGLfgauLVF2yoa1&lt;/span&gt;
    ssh_authorized_keys:
      - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkgolYx6IoPSYnhZMdGivUJOm4AMtI0gkjFkY/53V4idbliQHAMJHGdMGYdlEm5ThOCw3DDblsQDNy7EZJaa9+T1imwrnUg0fYU13u+Tfq7Fg+TIn4hf4uG/ei2r1MLp2/lO/6dEPUGv2TiBQ+SVfB8yt2IUVIGgqNhGWJi/p5uw9O5KiAPN1UmT3CvpWYVFnfqvnDwnOMJkXg9xN8AbTkAHS1YDIljNMwBisaOvI5cjgZ5a+ovp2pdHBxZWyPAb7Y5NvlQHGtJQIlbWTcxIBu8/1YPbZlkTcgB0ghDf0upgKunqFHh/Zq3sdEEUyQ2Xr6qdVyaXwNQJhV8Kge196r ubuntu@controller

&lt;span class=&quot;c&quot;&gt;#ssh_pwauth: true&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#runcmd:&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#  - sed -i -e '$aAllowUsers starbops' /etc/ssh/sshd_config&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#  - systemctl restart ssh.service&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;hostname&lt;/span&gt;: shitcoin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;how-it-works&quot;&gt;How It Works&lt;/h3&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;instance --&amp;gt; neutron-ns-metadata-proxy --&amp;gt; neutron-metadata-agent --&amp;gt; nova-api
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;instance&quot;&gt;Instance&lt;/h4&gt;

&lt;p&gt;Instance makes request to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://169.254.169.254&lt;/code&gt;. Because the destination does
hit any rules in the routing table of the instance, it just falls back to the
default route. The request follows the default route to router namespace. Here
is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iptables&lt;/code&gt; rule for this situation:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;REDIRECT  tcp  --  0.0.0.0/0      169.254.169.254  tcp dpt:80 redir ports 9697
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The request being redirected is still inside the same router namespace.&lt;/p&gt;

&lt;h4 id=&quot;neutron-ns-metadata-proxy&quot;&gt;neutron-ns-metadata-proxy&lt;/h4&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neutron-ns-metadata-proxy&lt;/code&gt; is running and listening on TCP 9697 port in each
router namespaces. While processed by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neutron-ns-metadata-proxy&lt;/code&gt;, following
headers are added to the request:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;X-forwarded-for: &amp;lt;Instance IP&amp;gt;
X-neutron-router-id: &amp;lt;Router UUID&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And the request is forwarded through UNIX socket to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neutron-metadata-agent&lt;/code&gt;.&lt;/p&gt;

&lt;h4 id=&quot;neutron-metadata-agent&quot;&gt;neutron-metadata-agent&lt;/h4&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neutron-metadata-agent&lt;/code&gt; listens on the socket &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/lib/neutron/metadata_proxy&lt;/code&gt;
and communicates with public OpenStack APIs for more information. More headers
are added:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;X-Instance-ID: &amp;lt;Instance UUID&amp;gt;
X-Tenant-ID: &amp;lt;Tenant UUID&amp;gt;
X-Instance-ID-Signature: &amp;lt;HMAC secret, instance_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;nova-api&quot;&gt;nova-api&lt;/h4&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux_OpenStack_Platform/4/html/End_User_Guide/user-data.html&quot;&gt;2.9. Configuring instances at boot time&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://cloudinit.readthedocs.io/en/latest/topics/network-config.html&quot;&gt;Network Configuration - Cloud-Init 18.4 documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.openstack.org/nova/latest/user/metadata.html&quot;&gt;OpenStack Docs: Metadata&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Zespre Schmidt</name></author><category term="note" /><summary type="html">Cloud-init’s behavior can be configured via user-data. User-data can be given by the user at instance launch time.</summary></entry></feed>