<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="http://infrabitsnbloops.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="http://infrabitsnbloops.dev/" rel="alternate" type="text/html" /><updated>2026-06-27T20:33:18+00:00</updated><id>http://infrabitsnbloops.dev/feed.xml</id><title type="html">Infra Bits &amp;amp; Bloops: Personal Blog</title><subtitle>Infra Bits &amp; Bloops is my personal tech blog where I record my learnings of different technologies related to building infrastracture in Cloud.</subtitle><entry><title type="html">Kubernetes Architecture Explained</title><link href="http://infrabitsnbloops.dev/kubernetes/2026/06/21/kubernetes-architecture-explained.html" rel="alternate" type="text/html" title="Kubernetes Architecture Explained" /><published>2026-06-21T00:00:00+00:00</published><updated>2026-06-21T00:00:00+00:00</updated><id>http://infrabitsnbloops.dev/kubernetes/2026/06/21/kubernetes-architecture-explained</id><content type="html" xml:base="http://infrabitsnbloops.dev/kubernetes/2026/06/21/kubernetes-architecture-explained.html"><![CDATA[<h1 id="introduction">Introduction</h1>
<p>Before I dive into Kubernetes The Hard Way and get my hands dirty by bringing up a new K8S cluster, I thought I’d create a few pages covering Kubernetes theory, architecture, and related concepts.</p>

<p>So, let’s start with the obvious first candidate: Kubernetes architecture.</p>

<h1 id="k8s-architecture-overview">K8S Architecture Overview</h1>
<p>There are two types of Kubernetes nodes. Some nodes run applications, while others control when, how, and where those applications run. The first group of nodes, which runs applications (or containers), is called Worker nodes, while the second group consists of servers called Control Plane (or Master) nodes.</p>

<p>When it comes to the tasks each group performs, the workers’ responsibilities are fairly straightforward: they accept requests to run applications, spin up containers using the specified container runtime, and provide networking and storage. On the other hand, Control Plane nodes perform more complex tasks to ensure the requested state is achieved. Control Plane nodes need to:</p>
<ul>
  <li>place containers on the appropriate worker nodes</li>
  <li>determine how to place those containers</li>
  <li>identify the most suitable worker node</li>
  <li>store information about each worker node and monitor its state and availability</li>
  <li>and more…</li>
</ul>

<p>Let’s now look at the individual services that run on each group of hosts.</p>

<h2 id="components-that-run-on-worker-nodes">Components that run on Worker nodes</h2>
<p>Worker nodes are responsible for running kubelet and the container runtime, as well as providing networking (CNI) and storage.</p>

<p>Kubelet is a service that runs on each worker node and manages all activities on that node:</p>
<ul>
  <li>informs Control Plane nodes that a new worker node is ready to join the group</li>
  <li>listens for requests coming from Control Plane nodes, such as information of containers that will be placed on that node</li>
  <li>reports back to Control Plane nodes the status of each container (deployed, running, failed to run, destroyed, etc)</li>
</ul>

<p>Of course, something needs to deploy containers. That responsibility belongs to the container runtime, such as containerd, rkt, or Docker.</p>

<p>Kube-proxy is another service that runs on Worker nodes. It ensures that containers can communicate with each other—for example, allowing a web server to send requests to a database server running in another container. Kube-proxy accomplishes this by maintaining networking rules using iptables.</p>

<p>Next, there are CNI plugins that provide… well… networking. More specifically, CNI plugins implement the Kubernetes networking model. There are many CNI plugins available, including Calico, Cilium, Flannel, and Weave.</p>

<p>An important note about networking: if a CNI plugin implements packet forwarding for Services itself and provides functionality equivalent to kube-proxy, then kube-proxy does not need to run on the nodes. In that case, kube-proxy becomes optional rather than required.</p>

<p>Lastly, worker nodes are also responsible for providing storage for running pods. Kubernetes supports a number of <a href="https://kubernetes.io/docs/concepts/storage/volumes/">volume types</a>:</p>
<ul>
  <li>populating a configuration file based on a ConfigMap or a Secret</li>
  <li>providing some temporary scratch space for a Pod</li>
  <li>sharing a filesystem between two different containers in the same Pod</li>
  <li>sharing a filesystem between two different Pods (even if those Pods run on different nodes)</li>
  <li>durably storing data so that it stays available even if the Pod restarts or is replaced</li>
  <li>passing configuration information to an app running in a container, based on details of the Pod the container is - in (for example: telling a sidecar container what namespace the Pod is running in)</li>
  <li>providing read-only access to data in a different container image</li>
</ul>

<h2 id="components-that-run-on-control-plane-nodes">Components that run on Control Plane nodes</h2>
<p>There are four main services that run on Control Plane nodes.</p>

<p>The kube-apiserver is the primary management component and is responsible for orchestrating all operations within the cluster. It communicates with all other components on the Control Plane and also sends instructions to Worker nodes.</p>

<p>Next, there is the etcd cluster. etcd stores all cluster information in a key-value (KV) store.</p>

<p>Then, there is kube-scheduler, which is responsible for scheduling Pods. It watches for newly created Pods that have not yet been assigned to a worker node and determines the most suitable node for each Pod. Factors it considers include resource requirements, policy constraints, affinity rules, and more.</p>

<p>Lastly, kube-controller-manager runs different controller processes. Some examples of them are:</p>
<ul>
  <li>Node controller: Responsible for noticing and responding when nodes go down.</li>
  <li>Job controller: Watches for Job objects that represent one-off tasks, then creates Pods to run those tasks to completion.</li>
  <li>EndpointSlice controller: Populates EndpointSlice objects (to provide a link between Services and Pods).</li>
  <li>ServiceAccount controller: Create default ServiceAccounts for new namespaces.</li>
</ul>

<p>The following image was taken from the official Kubernetes architecture page:
<img src="/assets/images/kubernetes-cluster-architecture.svg" alt="image-center" class="align-center" /></p>

<p>I have also asked an AI image generator to visualise all components:
<img src="/assets/images/k8s-arch.png" alt="image-center" class="align-center" /></p>]]></content><author><name>Nikola Pajtic</name></author><category term="Kubernetes" /><category term="kubernetes" /><summary type="html"><![CDATA[Introduction Before I dive into Kubernetes The Hard Way and get my hands dirty by bringing up a new K8S cluster, I thought I’d create a few pages covering Kubernetes theory, architecture, and related concepts.]]></summary></entry><entry><title type="html">Generate QCOW2 images with Packer</title><link href="http://infrabitsnbloops.dev/cloud/2026/04/05/generate-qcow2-images-with-packer.html" rel="alternate" type="text/html" title="Generate QCOW2 images with Packer" /><published>2026-04-05T00:00:00+00:00</published><updated>2026-04-05T00:00:00+00:00</updated><id>http://infrabitsnbloops.dev/cloud/2026/04/05/generate-qcow2-images-with-packer</id><content type="html" xml:base="http://infrabitsnbloops.dev/cloud/2026/04/05/generate-qcow2-images-with-packer.html"><![CDATA[<h1 id="introduction">Introduction</h1>
<p>This post explores how to build QCOW2 images with Packer. Once built, they can be uploaded to different cloud providers and provision new instances.</p>

<p>This post is the first post in a series of Kubernetes The Hard Way. I built my own images to simplify the process of provisioning new Kubernetes clusters. I really did not want to repeat all pre-requisit steps every time a new host comes up (or I mess up some configs somewhere and therefore need to re-bootstrap a host).</p>

<p>All the steps bellow assume the Ubuntu 22.04.05 LTS release.</p>

<h1 id="qcow2-packer-all-the-way">Qcow2 Packer all the way</h1>
<h2 id="step-1-install-packer">Step 1: Install Packer</h2>
<p>On a local machine:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt update
<span class="nb">sudo </span>apt <span class="nb">install</span> <span class="nt">-y</span> packer
</code></pre></div></div>
<h2 id="step-2-set-up-the-project-and-its-structure">Step 2: Set up the project and its structure</h2>
<p>The project directory looks like this:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root_dir
|
|-- builds
   |
   |-- dev.pkrvars.hcl   
|
|-- files
   |
   |-- 99-custom.conf   
|
|-- http
   |
   |-- meta-data   
|
|-- packer.pkr.hcl
|-- variables.pkr.hcl
|-- user-data.tpl
|-- build_packer_image.sh
</code></pre></div></div>

<h2 id="step-3-the-packer-file">Step 3: the packer file</h2>
<p>The main packer file is an HCL file, which in this case was ubuntu.pkr.hcl and it should be created with:</p>
<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">packer</span> <span class="p">{</span>
  <span class="nx">required_plugins</span> <span class="p">{</span>
    <span class="nx">qemu</span> <span class="p">=</span> <span class="p">{</span>
      <span class="nx">source</span>  <span class="p">=</span> <span class="s2">"github.com/hashicorp/qemu"</span>
      <span class="nx">version</span> <span class="p">=</span> <span class="s2">"&gt;= 1.0.0"</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># ---- QEMU SOURCE ----</span>
<span class="nx">source</span> <span class="s2">"qemu"</span> <span class="s2">"ubuntu"</span> <span class="p">{</span>
  <span class="nx">iso_url</span>      <span class="p">=</span> <span class="s2">"https://releases.ubuntu.com/22.04.5/ubuntu-22.04.5-live-server-amd64.iso"</span>
  <span class="nx">iso_checksum</span> <span class="p">=</span> <span class="s2">"sha256:9bc6028870aef3f74f4e16b900008179e78b130e6b0b9a140635434a46aa98b0"</span>

  <span class="nx">output_directory</span> <span class="p">=</span> <span class="s2">"output-${var.image_name}"</span>
  <span class="nx">vm_name</span>          <span class="p">=</span> <span class="s2">"${var.image_name}.qcow2"</span>

  <span class="nx">disk_size</span>   <span class="p">=</span> <span class="s2">"10G"</span>
  <span class="nx">format</span>      <span class="p">=</span> <span class="s2">"qcow2"</span>
  <span class="nx">accelerator</span> <span class="p">=</span> <span class="s2">"kvm"</span>
  <span class="nx">disk_interface</span> <span class="p">=</span> <span class="s2">"virtio"</span>
  <span class="nx">net_device</span> <span class="p">=</span> <span class="s2">"virtio-net"</span>

  <span class="nx">memory</span> <span class="p">=</span> <span class="mi">2048</span>
  <span class="nx">cpus</span>   <span class="p">=</span> <span class="mi">2</span>

  <span class="nx">ssh_username</span>         <span class="p">=</span> <span class="nx">var</span><span class="err">.</span><span class="nx">username</span>
  <span class="nx">ssh_password</span>         <span class="p">=</span> <span class="nx">var</span><span class="err">.</span><span class="nx">password</span>
  <span class="nx">ssh_timeout</span>          <span class="p">=</span> <span class="s2">"20m"</span>

  <span class="nx">http_directory</span> <span class="p">=</span> <span class="s2">"http"</span>

  <span class="nx">boot_wait</span> <span class="p">=</span> <span class="s2">"20s"</span>

  <span class="nx">boot_command</span> <span class="p">=</span> <span class="p">[</span>
    <span class="s2">"&lt;wait&gt;"</span><span class="p">,</span>
    <span class="s2">"c"</span><span class="p">,</span>
    <span class="s2">"&lt;wait&gt;"</span><span class="p">,</span>
    <span class="s2">"linux /casper/vmlinuz autoinstall ip=dhcp ds=nocloud-net</span><span class="err">\\</span><span class="s2">;s=http://:/ ---"</span><span class="p">,</span>
    <span class="s2">"&lt;enter&gt;"</span><span class="p">,</span>
    <span class="s2">"initrd /casper/initrd"</span><span class="p">,</span>
    <span class="s2">"&lt;enter&gt;"</span><span class="p">,</span>
    <span class="s2">"boot"</span><span class="p">,</span>
    <span class="s2">"&lt;enter&gt;"</span>
  <span class="p">]</span>

  <span class="nx">shutdown_command</span> <span class="p">=</span> <span class="s2">"sudo shutdown -P now"</span>
<span class="p">}</span>

<span class="c1"># ---- BUILD ----</span>
<span class="nx">build</span> <span class="p">{</span>
  <span class="nx">sources</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"source.qemu.ubuntu"</span><span class="p">]</span>

  <span class="nx">provisioner</span> <span class="s2">"file"</span> <span class="p">{</span>
    <span class="nx">source</span>      <span class="p">=</span> <span class="s2">"files/99-custom.conf"</span>
    <span class="nx">destination</span> <span class="p">=</span> <span class="s2">"/tmp/99-custom.conf"</span>
  <span class="p">}</span>

  <span class="c1"># k8s setup</span>
  <span class="nx">provisioner</span> <span class="s2">"shell"</span> <span class="p">{</span>
    <span class="nx">inline</span> <span class="p">=</span> <span class="p">[</span>
      <span class="s2">"sudo apt-get update"</span><span class="p">,</span>
      <span class="s2">"sudo apt-get install -y apt-transport-https ca-certificates curl wget curl vim openssl git net-tools"</span><span class="p">,</span>

      <span class="s2">"echo 'datasource_list: [ OpenStack, ConfigDrive, NoCloud ]' | sudo tee /etc/cloud/cloud.cfg.d/99-datasource.cfg"</span><span class="p">,</span>

      <span class="c1"># Remove installer config</span>
      <span class="s2">"sudo rm -f /etc/netplan/00-installer-config.yaml"</span><span class="p">,</span>

      <span class="c1"># Disable swap</span>
      <span class="s2">"sudo swapoff -a"</span><span class="p">,</span>
      <span class="s2">"sudo sed -i '/ swap / s/^/#/' /etc/fstab"</span><span class="p">,</span>

      <span class="c1"># Load kernel modules</span>
      <span class="s2">"echo 'overlay' | sudo tee /etc/modules-load.d/containerd.conf"</span><span class="p">,</span>
      <span class="s2">"echo 'br_netfilter' | sudo tee -a /etc/modules-load.d/containerd.conf"</span><span class="p">,</span>
      <span class="s2">"sudo modprobe overlay"</span><span class="p">,</span>
      <span class="s2">"sudo modprobe br_netfilter"</span><span class="p">,</span>

      <span class="c1"># Sysctl settings</span>
      <span class="s2">"echo 'net.bridge.bridge-nf-call-iptables = 1' | sudo tee /etc/sysctl.d/k8s.conf"</span><span class="p">,</span>
      <span class="s2">"echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/k8s.conf"</span><span class="p">,</span>
      <span class="s2">"echo 'net.bridge.bridge-nf-call-ip6tables = 1' | sudo tee -a /etc/sysctl.d/k8s.conf"</span><span class="p">,</span>
      <span class="s2">"sudo sysctl --system"</span> 
    <span class="p">]</span>
  <span class="p">}</span>

  <span class="c1"># SSH hardening</span>
  <span class="nx">provisioner</span> <span class="s2">"shell"</span> <span class="p">{</span>
    <span class="nx">inline</span> <span class="p">=</span> <span class="p">[</span>
      <span class="s2">"sudo mv /tmp/99-custom.conf /etc/ssh/sshd_config.d/99-custom.conf"</span><span class="p">,</span>
      <span class="s2">"sudo chown root:root /etc/ssh/sshd_config.d/99-custom.conf"</span><span class="p">,</span>
      <span class="s2">"sudo chmod 644 /etc/ssh/sshd_config.d/99-custom.conf"</span><span class="p">,</span>

      <span class="c1"># validate config</span>
      <span class="s2">"sudo sshd -t"</span>
    <span class="p">]</span>
  <span class="p">}</span>

  <span class="c1"># cleanup</span>
  <span class="nx">provisioner</span> <span class="s2">"shell"</span> <span class="p">{</span>
   <span class="nx">inline</span> <span class="p">=</span> <span class="p">[</span>
    <span class="c1"># guest agent</span>
    <span class="s2">"sudo systemctl enable qemu-guest-agent"</span><span class="p">,</span>

    <span class="c1"># cleanup</span>
    <span class="s2">"sudo apt-get clean"</span><span class="p">,</span>
    <span class="s2">"sudo rm -rf /var/lib/apt/lists/*"</span><span class="p">,</span>

    <span class="c1"># reset identity</span>
    <span class="s2">"sudo truncate -s 0 /etc/machine-id"</span><span class="p">,</span>
    <span class="s2">"sudo rm -f /var/lib/dbus/machine-id"</span><span class="p">,</span>
    <span class="s2">"sudo rm -f /etc/ssh/ssh_host_*"</span><span class="p">,</span>

    <span class="s2">"sudo cloud-init clean --logs"</span><span class="p">,</span>
    <span class="s2">"sudo rm -rf /var/lib/cloud/*"</span><span class="p">,</span>

    <span class="c1"># UFW</span>
    <span class="s2">"sudo ufw default deny incoming"</span><span class="p">,</span>
    <span class="s2">"sudo ufw default allow outgoing"</span><span class="p">,</span>
    <span class="s2">"sudo ufw allow OpenSSH"</span><span class="p">,</span>
    <span class="s2">"sudo ufw disable"</span><span class="p">,</span>

    <span class="s2">"sudo rm -f /home/admin/.ssh/authorized_keys"</span><span class="p">,</span>

    <span class="s2">"sync"</span>
  <span class="p">]</span>
 <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Next, let us have some variables in variables.pkr.hcl file:</p>
<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">variable</span> <span class="s2">"ssh_ip"</span> <span class="p">{</span>
  <span class="nx">type</span>        <span class="p">=</span> <span class="nx">string</span>
  <span class="nx">description</span> <span class="p">=</span> <span class="s2">"Allowed SSH source IP"</span>
  <span class="nx">default</span>     <span class="p">=</span> <span class="s2">"1.1.1.1"</span>
<span class="p">}</span>

<span class="nx">variable</span> <span class="s2">"hostname"</span> <span class="p">{</span>
  <span class="nx">type</span>    <span class="p">=</span> <span class="nx">string</span>
  <span class="nx">default</span> <span class="p">=</span> <span class="s2">"ubuntu"</span>
<span class="p">}</span>

<span class="nx">variable</span> <span class="s2">"username"</span> <span class="p">{</span>
  <span class="nx">type</span>    <span class="p">=</span> <span class="nx">string</span>
  <span class="nx">default</span> <span class="p">=</span> <span class="s2">"username"</span>
<span class="p">}</span>

<span class="nx">variable</span> <span class="s2">"password"</span> <span class="p">{</span>
  <span class="nx">type</span>    <span class="p">=</span> <span class="nx">string</span>
  <span class="nx">default</span> <span class="p">=</span> <span class="s2">"topsecret"</span>
<span class="p">}</span>

<span class="nx">variable</span> <span class="s2">"region"</span> <span class="p">{</span>
  <span class="nx">type</span>    <span class="p">=</span> <span class="nx">string</span>
  <span class="nx">default</span> <span class="p">=</span> <span class="s2">"contabo"</span>
<span class="p">}</span>

<span class="nx">variable</span> <span class="s2">"image_name"</span> <span class="p">{</span>
  <span class="nx">type</span>    <span class="p">=</span> <span class="nx">string</span>
  <span class="nx">default</span> <span class="p">=</span> <span class="s2">"ubuntu-base"</span>
<span class="p">}</span>

<span class="nx">variable</span> <span class="s2">"ssh_public_key_path"</span> <span class="p">{</span>
  <span class="nx">type</span>        <span class="p">=</span> <span class="nx">string</span>
  <span class="nx">description</span> <span class="p">=</span> <span class="s2">"Path to SSH public key"</span>
<span class="p">}</span>

<span class="nx">variable</span> <span class="s2">"shapassword"</span> <span class="p">{</span>
  <span class="nx">type</span>        <span class="p">=</span> <span class="nx">string</span>
  <span class="nx">default</span> <span class="p">=</span> <span class="s2">""</span>
<span class="p">}</span>

</code></pre></div></div>
<p>Variables can be overridden through builds/ files. In this example, let’s set up an override with:</p>
<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">hostname</span>       <span class="err">=</span> <span class="s2">"ubuntu"</span>
<span class="nx">username</span>       <span class="err">=</span> <span class="s2">"username"</span>
<span class="nx">password</span>       <span class="err">=</span> <span class="s2">"topsecret"</span>
<span class="nx">image_name</span>     <span class="err">=</span> <span class="s2">"dev-ubuntu-22.04-base"</span>
<span class="nx">ssh_public_key_path</span>  <span class="err">=</span> <span class="s2">"~/.ssh/dev_id_rsa.pub"</span>
</code></pre></div></div>
<p>Next, we need to create user-data and meta-data files in http/ directory, which will be served to cloud-init during the build phase. I decided to template a user-data file via an external script and a tpl file. The user-data.tpl looks like this:</p>
<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#cloud-config</span>
<span class="nx">autoinstall</span><span class="err">:</span>
  <span class="nx">version</span><span class="err">:</span> <span class="mi">1</span>

  <span class="nx">identity</span><span class="err">:</span>
    <span class="nx">hostname</span><span class="err">:</span> <span class="nx">$hostname</span>
    <span class="nx">username</span><span class="err">:</span> <span class="nx">$username</span>
    <span class="nx">password</span><span class="err">:</span> <span class="s2">"$shapassword"</span>

  <span class="nx">ssh</span><span class="err">:</span>
    <span class="nx">install</span><span class="err">-</span><span class="nx">server</span><span class="err">:</span> <span class="kc">true</span>
    <span class="nx">allow</span><span class="err">-</span><span class="nx">pw</span><span class="err">:</span> <span class="kc">true</span>
    <span class="nx">authorized_keys</span><span class="err">:</span>
     <span class="err">-</span> <span class="nx">$ssh_public_key</span>


  <span class="nx">storage</span><span class="err">:</span>
    <span class="nx">layout</span><span class="err">:</span>
      <span class="nx">name</span><span class="err">:</span> <span class="nx">direct</span>

  <span class="nx">packages</span><span class="err">:</span>
    <span class="err">-</span> <span class="nx">qemu</span><span class="err">-</span><span class="nx">guest</span><span class="err">-</span><span class="nx">agent</span>
    <span class="err">-</span> <span class="nx">ufw</span>

  <span class="nx">late</span><span class="err">-</span><span class="nx">commands</span><span class="err">:</span>
    <span class="err">-</span> <span class="nx">curtin</span> <span class="nx">in</span><span class="err">-</span><span class="nx">target</span> <span class="err">--</span> <span class="nx">systemctl</span> <span class="nx">enable</span> <span class="nx">ssh</span>
    <span class="err">-</span> <span class="nx">curtin</span> <span class="nx">in</span><span class="err">-</span><span class="nx">target</span> <span class="err">--</span> <span class="nx">sh</span> <span class="err">-</span><span class="nx">c</span> <span class="s2">"echo '$username ALL=(ALL) NOPASSWD:ALL' &gt; /etc/sudoers.d/${username}"</span>

<span class="c1"># ---- runtime cloud-init (Contabo-compatible) ----</span>

<span class="nx">users</span><span class="err">:</span>
  <span class="err">-</span> <span class="nx">default</span>

<span class="nx">disable_root</span><span class="err">:</span> <span class="kc">true</span>
<span class="nx">preserve_hostname</span><span class="err">:</span> <span class="kc">false</span>

<span class="nx">system_info</span><span class="err">:</span>
  <span class="nx">default_user</span><span class="err">:</span>
    <span class="nx">name</span><span class="err">:</span> <span class="nx">$username</span>
    <span class="nx">lock_passwd</span><span class="err">:</span> <span class="kc">false</span>
    <span class="nx">groups</span><span class="err">:</span> <span class="p">[</span><span class="nx">sudo</span><span class="p">]</span>
    <span class="nx">sudo</span><span class="err">:</span> <span class="p">[</span><span class="s2">"ALL=(ALL) NOPASSWD:ALL"</span><span class="p">]</span>
    <span class="nx">shell</span><span class="err">:</span> <span class="err">/</span><span class="nx">bin</span><span class="err">/</span><span class="nx">bash</span>

<span class="nx">ssh_pwauth</span><span class="err">:</span> <span class="kc">true</span>

<span class="c1"># -- network</span>
<span class="nx">network</span><span class="err">:</span>
  <span class="nx">version</span><span class="err">:</span> <span class="mi">2</span>
  <span class="nx">ethernets</span><span class="err">:</span>
    <span class="nx">all</span><span class="err">:</span>
      <span class="nx">match</span><span class="err">:</span>
        <span class="nx">name</span><span class="err">:</span> <span class="s2">"*"</span>
      <span class="nx">dhcp4</span><span class="err">:</span> <span class="kc">true</span>

<span class="c1"># ---- firewall (SAFE stage) ----</span>

<span class="nx">runcmd</span><span class="err">:</span>
  <span class="c1"># reset UFW (clean state)</span>
  <span class="err">-</span> <span class="nx">ufw</span> <span class="err">--</span><span class="nx">force</span> <span class="nx">reset</span>

  <span class="c1"># default policies</span>
  <span class="err">-</span> <span class="nx">ufw</span> <span class="nx">default</span> <span class="nx">deny</span> <span class="nx">incoming</span>
  <span class="err">-</span> <span class="nx">ufw</span> <span class="nx">default</span> <span class="nx">allow</span> <span class="nx">outgoing</span>

  <span class="c1"># TEMP safety rule (avoid lockout during setup)</span>
  <span class="err">-</span> <span class="nx">ufw</span> <span class="nx">allow</span> <span class="mi">22</span><span class="err">/</span><span class="nx">tcp</span>

  <span class="c1"># allow SSH only from your IP(s)</span>
  <span class="err">-</span> <span class="nx">ufw</span> <span class="nx">allow</span> <span class="nx">from</span> <span class="mf">1.1</span><span class="err">.</span><span class="mf">1.1</span><span class="err">/</span><span class="mi">20</span> <span class="nx">to</span> <span class="nx">any</span> <span class="nx">port</span> <span class="mi">22</span> <span class="nx">proto</span> <span class="nx">tcp</span>
  <span class="err">-</span> <span class="nx">ufw</span> <span class="nx">allow</span> <span class="nx">from</span> <span class="mf">10.0</span><span class="err">.</span><span class="mf">0.0</span><span class="err">/</span><span class="mi">8</span> <span class="nx">to</span> <span class="nx">any</span> <span class="nx">port</span> <span class="mi">22</span> <span class="nx">proto</span> <span class="nx">tcp</span>
  <span class="err">-</span> <span class="nx">ufw</span> <span class="nx">allow</span> <span class="nx">from</span> <span class="mf">172.16</span><span class="err">.</span><span class="mf">0.0</span><span class="err">/</span><span class="mi">12</span> <span class="nx">to</span> <span class="nx">any</span> <span class="nx">port</span> <span class="mi">22</span> <span class="nx">proto</span> <span class="nx">tcp</span>
  <span class="err">-</span> <span class="nx">ufw</span> <span class="nx">allow</span> <span class="nx">from</span> <span class="mf">192.168</span><span class="err">.</span><span class="mf">0.0</span><span class="err">/</span><span class="mi">16</span> <span class="nx">to</span> <span class="nx">any</span> <span class="nx">port</span> <span class="mi">22</span> <span class="nx">proto</span> <span class="nx">tcp</span>

  <span class="c1"># enable firewall</span>
  <span class="err">-</span> <span class="nx">ufw</span> <span class="err">--</span><span class="nx">force</span> <span class="nx">enable</span>

  <span class="c1"># remove temporary open SSH rule</span>
  <span class="err">-</span> <span class="nx">ufw</span> <span class="nx">delete</span> <span class="nx">allow</span> <span class="mi">22</span><span class="err">/</span><span class="nx">tcp</span>
</code></pre></div></div>
<p>And the shell script that would generate http/user-data file:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="nb">set</span> <span class="nt">-euo</span> pipefail

<span class="nv">VARS_FILE</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">1</span><span class="k">:-</span><span class="nv">pkvars</span><span class="p">.hcl</span><span class="k">}</span><span class="s2">"</span>

<span class="c"># ========== RENDER USER-DATA ====================</span>
<span class="nb">echo</span> <span class="s2">"Rendering user-data from </span><span class="k">${</span><span class="nv">VARS_FILE</span><span class="k">}</span><span class="s2">..."</span>

<span class="nv">VARS_JSON</span><span class="o">=</span><span class="si">$(</span>hcl2json &lt; <span class="s2">"</span><span class="nv">$VARS_FILE</span><span class="s2">"</span><span class="si">)</span>

<span class="nb">export hostname</span><span class="o">=</span><span class="si">$(</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$VARS_JSON</span><span class="s2">"</span> | jq <span class="nt">-r</span> <span class="s1">'.hostname'</span><span class="si">)</span>
<span class="nb">export </span><span class="nv">username</span><span class="o">=</span><span class="si">$(</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$VARS_JSON</span><span class="s2">"</span> | jq <span class="nt">-r</span> <span class="s1">'.username'</span><span class="si">)</span>

<span class="nv">password</span><span class="o">=</span><span class="si">$(</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$VARS_JSON</span><span class="s2">"</span> | jq <span class="nt">-r</span> <span class="s1">'.password'</span><span class="si">)</span>
<span class="nb">export </span><span class="nv">shapassword</span><span class="o">=</span><span class="si">$(</span>mkpasswd <span class="nt">-m</span> sha-512 <span class="nv">$password</span><span class="si">)</span>

<span class="nv">ssh_key_path</span><span class="o">=</span><span class="si">$(</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$VARS_JSON</span><span class="s2">"</span> | jq <span class="nt">-r</span> <span class="s1">'.ssh_public_key_path'</span><span class="si">)</span>

<span class="nv">ssh_key_path</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">ssh_key_path</span><span class="p">/#\~/</span><span class="nv">$HOME</span><span class="k">}</span><span class="s2">"</span>
<span class="nb">export </span><span class="nv">ssh_public_key</span><span class="o">=</span><span class="si">$(</span><span class="nb">cat</span> <span class="s2">"</span><span class="nv">$ssh_key_path</span><span class="s2">"</span><span class="si">)</span>

envsubst &lt; user-data.tpl <span class="o">&gt;</span> http/user-data

<span class="nb">echo</span> <span class="s2">"http/user-data generated"</span>

<span class="c"># ========== PACKER BUILD =======================</span>
<span class="nb">echo</span> <span class="s2">"Starting packer build from </span><span class="k">${</span><span class="nv">VARS_FILE</span><span class="k">}</span><span class="s2">..."</span>
packer build <span class="nt">-var-file</span><span class="o">=</span><span class="k">${</span><span class="nv">VARS_FILE</span><span class="k">}</span> <span class="nb">.</span>
</code></pre></div></div>
<p>Lastly, the http/meta-data file looks fairly generic:</p>
<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">instance</span><span class="err">-</span><span class="nx">id</span><span class="err">:</span> <span class="nx">iid</span><span class="err">-</span><span class="nx">local01</span>
<span class="nx">local</span><span class="err">-</span><span class="nx">hostname</span><span class="err">:</span> <span class="nx">ubuntu</span>
</code></pre></div></div>
<p>Finally, to build an image, run the script as:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./build_packer_image.sh builds/dev.pkrvars.hcl
</code></pre></div></div>
<p>Once the build is complete, there will be a separate output/ directory created with a new qcow2 image.</p>

<h2 id="step-4-compress--optimize-qcow2">Step 4: Compress &amp; Optimize QCOW2</h2>
<p>The final step is to compress and optimize the image and get it ready for uploading into a cloud provider. This process will reduce the size significantly.</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>qemu-img convert <span class="nt">-O</span> qcow2 <span class="nt">-c</span> output/ubuntu-custom.qcow2 output/ubuntu-final.qcow2
</code></pre></div></div>
<h2 id="step-5-upload-the-image">Step 5: Upload the image</h2>
<p>If you are running your infrastructure in AWS or GCP, you can store your images within each given platform.</p>

<p>However, in my case, I was using Contabo to run my K8s learning clusters on a few VPS instances. In order to use my newly built images, I had to somehow upload them into Contabo. The problem was - to upload an image, you had to provide a public URL to it, which Contabo will then use to pull the image into their image store.</p>

<p>Since I had no other VPS laying around that would serve as network storage, I used an S3 bucket with additional features:</p>

<h3 id="step-51-create-a-bucket-and-upload-an-image">Step 5.1: Create a bucket and upload an image</h3>
<p>First, log into AWS, create a public bucket and then upload an image there</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws s3 <span class="nb">cp </span>ubuntu-final.qcow2 s3://your-bucket/
</code></pre></div></div>
<h3 id="step-52-generate-a-signed-url-with-a-ttl-on-it">Step 5.2: Generate a signed URL with a TTL on it</h3>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws s3 presign s3://your-bucket/ubuntu-final.qcow2 <span class="nt">--expires-in</span> 3600
</code></pre></div></div>
<p>The result would look like:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://your-bucket.s3.amazonaws.com/ubuntu-final.qcow2?X-Amz-Signature<span class="o">=</span>...
</code></pre></div></div>
<p>Which I then gave to Contabo to pull my image into their local store.</p>

<p>It goes without saying that this approach should not be used for production-grade systems. I used Contabo VPS for my own learning and K8s test environments, so there was no actual production data nor senstive information.</p>

<h3 id="53-a-local-http-server">5.3: A local http server</h3>
<p>If you can serve an HTTP traffic from your local machine (port forwarding configured, etc etc), a small local http server could do the trick:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python3 <span class="nt">-m</span> http.server 8000
</code></pre></div></div>

<h1 id="summary">Summary</h1>
<p>The steps above described a simple build that generated a single base image. This is fine for small setups. However, for any larger project, I strongly recommend using Jsonnet instead of HCL2. Jsonnet gives a lot more flexibility and templating options, while avoiding code repetitions that come with HCL.</p>]]></content><author><name>Nikola Pajtic</name></author><category term="Cloud" /><category term="QCOW2" /><category term="Packer" /><summary type="html"><![CDATA[Introduction This post explores how to build QCOW2 images with Packer. Once built, they can be uploaded to different cloud providers and provision new instances.]]></summary></entry></feed>