~cghttps://cg.zknt.org/2022-06-03T13:37:00+02:00terraforming Hetzner, dynamische IPs2022-06-03T13:37:00+02:002022-06-03T13:37:00+02:00christag:cg.zknt.org,2022-06-03:/terraforming-hetzner-dynamische-ips.html<h2>kubernetes–API in der Firewall beschränken</h2>
<p>Auf den kubernetes–Nodes lauscht auf Port 6443/tcp die kube–api, über die
beispielsweise unser <code>kubectl</code> von zuhaus mit dem Cluster kommuniziert.</p>
<p>In den bisheringen Beispielen für die Firewall haben wir Ports immer fürs
gesamte Internet geöffnet (<code>0.0.0.0/0</code> & <code>::/0 …</code></p><h2>kubernetes–API in der Firewall beschränken</h2>
<p>Auf den kubernetes–Nodes lauscht auf Port 6443/tcp die kube–api, über die
beispielsweise unser <code>kubectl</code> von zuhaus mit dem Cluster kommuniziert.</p>
<p>In den bisheringen Beispielen für die Firewall haben wir Ports immer fürs
gesamte Internet geöffnet (<code>0.0.0.0/0</code> & <code>::/0</code> als Quellen).</p>
<p>Ist für die kube–api natürlich nicht wirklich notwendig, zumindest in meinem
Setup will erstmal nur ich von zuhause aus kubectl nutzen.</p>
<p>Da der Cluster dank rke nur per legacy IPv4 erreichbar ist ändert sich meine
Adresse alle paar Monate, denn mein ISP weist mir leider keine statische zu.
Ich habe aber einen DNS-Eintrag der immer auf die aktuelle IPv4 zeigt, über
<a href="https://freedns.afraid.org/">afraid.org</a>.</p>
<p>Die Hetzner-Firewall kann (natürlich) nichts mit Hostnames anfangen, sondern
verlangt nach IPs, der hcloud–Provider will sogar zwingend ein Netzbereich.</p>
<p>Ich muss also per terraform zum einen die IPv4 zu meinem Hostname
herausfinden, zum anderen das in ein Netz wandeln, und das dann der Firewall
übergeben.</p>
<h2>DNS–Auflösung in terraform</h2>
<p>Um die IP(s) zu einem Hostname herauszufinden gibt es — einen
terraform–Provider! Und zwar nutze ich <code>hashicorp/dns</code> in Version 3.2.3.
Diesen in terraform.tf wie gewohnt definiert lädt `terraform init ihn wie
gewohnt herunter.</p>
<p>Mit dem installierten Provider erweitere ich dann den Plan für die Firewall:</p>
<p>firewall.tf</p>
<div class="highlight"><pre><span></span><code><span class="kr">data</span><span class="w"> </span><span class="nc">"dns_a_record_set"</span><span class="w"> </span><span class="nv">"kubectl"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">host</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"zknt-hh3.trantuete.net"</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>So ein <code>data</code>–Block (statt wie üblich <code>resource</code>) steht für Operationen in
denen wir etwas nachschlagen, aber nichts verändern.
terraform löst hier also den Hostname auf und speichert die Liste der IPs.
Die Antwort ist immer eine Liste, auch wenn ich weiß das ich nur eine IPv4
habe…</p>
<p>Nun möchte der hcloud–Provider ja Netze, nicht IPs, es muss also "/32"
dahinter stehen. Dazu gehe ich einfach die Liste durch und hänge den String
hinter jeden Eintrag:</p>
<div class="highlight"><pre><span></span><code>locals {
host_cidr = flatten([
for ip in data.dns_a_record_set.ctl.addrs :
"<span class="cp">${</span><span class="n">ip</span><span class="cp">}</span>/32"
])
}
</code></pre></div>
<p>Damit steckt die gewünschte Liste von Netzen in der Variable <code>host_cidr</code> die
ich in der Firewall–Rule direkt nutzen kann:</p>
<div class="highlight"><pre><span></span><code><span class="nb">rule</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">direction</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"in"</span><span class="w"></span>
<span class="w"> </span><span class="na">protocol</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"tcp"</span><span class="w"></span>
<span class="w"> </span><span class="na">port</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"6443"</span><span class="w"></span>
<span class="w"> </span><span class="na">source_ips</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">local.host_cidr</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Und damit ist die API nach dem nächsten <code>plan && apply</code> nur noch von mir
aus nutzbar. Ändert mein ISP mal wieder meine IPv4 muss ich terraform halt
nochmal ausführen…</p>terraforming Hetzner, Pt 42022-06-02T13:37:00+02:002022-06-02T13:37:00+02:00christag:cg.zknt.org,2022-06-02:/terraforming-hetzner-pt-4.html<h2>HA–Cluster mit mehreren Nodes aufbauen</h2>
<p>Nach dem <a href="https://cg.zknt.org/terraforming-hetzner-pt-3.html">letzten Artikel</a> läuft
kubernetes ja schonmal. Um einen High Availability Cluster mit mehreren Nodes
aufzubauen müssen wir tatsächlich nur noch wenige Dinge anpassen.
In der Hauptsache holen wir das <a href="https://cg.zknt.org/terraforming-hetzner-pt-2.html">Beispiel mit den drei Webservern</a>
wieder hervor um mehrere VMs gleichzeitig anzulegen, und …</p><h2>HA–Cluster mit mehreren Nodes aufbauen</h2>
<p>Nach dem <a href="https://cg.zknt.org/terraforming-hetzner-pt-3.html">letzten Artikel</a> läuft
kubernetes ja schonmal. Um einen High Availability Cluster mit mehreren Nodes
aufzubauen müssen wir tatsächlich nur noch wenige Dinge anpassen.
In der Hauptsache holen wir das <a href="https://cg.zknt.org/terraforming-hetzner-pt-2.html">Beispiel mit den drei Webservern</a>
wieder hervor um mehrere VMs gleichzeitig anzulegen, und danach geben wir dem
rke–Provider diese Liste mit.</p>
<h2>Neue Variablen</h2>
<p>variables.tf:</p>
<div class="highlight"><pre><span></span><code><span class="kr">variable</span><span class="w"> </span><span class="nv">"ip_range"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">default</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"10.0.82.0/24"</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kr">variable</span><span class="w"> </span><span class="nv">"control_count"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">default</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">3</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kr">variable</span><span class="w"> </span><span class="nv">"worker_count"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">default</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">3</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Die IP–Range kennen wir schon aus Part 2, die Variable <code>instance_count</code> von
dort teile ich hier auf <code>control_count</code> und <code>worker_count</code> auf — um bei
Bedarf mehr worker–Nodes dazustellen zu können.</p>
<h2>Das interne Netzwerk</h2>
<p>network.tf:</p>
<div class="highlight"><pre><span></span><code><span class="kr">resource</span><span class="w"> </span><span class="nc">"hcloud_network"</span><span class="w"> </span><span class="nv">"k8s-nodes"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"k8s-nodes"</span><span class="w"></span>
<span class="w"> </span><span class="na">ip_range</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.ip_range</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kr">resource</span><span class="w"> </span><span class="nc">"hcloud_network_subnet"</span><span class="w"> </span><span class="nv">"k8s-node-subnet"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">network_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">hcloud_network.k8s-nodes.id</span><span class="w"></span>
<span class="w"> </span><span class="na">type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"cloud"</span><span class="w"></span>
<span class="w"> </span><span class="na">network_zone</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"eu-central"</span><span class="w"></span>
<span class="w"> </span><span class="na">ip_range</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.ip_range</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kr">resource</span><span class="w"> </span><span class="nc">"hcloud_server_network"</span><span class="w"> </span><span class="nv">"k8s-control-network"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">count</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">length</span><span class="p">(</span><span class="nv">hcloud_server.k8s-control</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="na">server_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">hcloud_server.k8s-control[count.index].id</span><span class="w"></span>
<span class="w"> </span><span class="na">subnet_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">hcloud_network_subnet.k8s-node-subnet.id</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kr">resource</span><span class="w"> </span><span class="nc">"hcloud_server_network"</span><span class="w"> </span><span class="nv">"k8s-worker-network"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">count</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">length</span><span class="p">(</span><span class="nv">hcloud_server.k8s-worker</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="na">server_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">hcloud_server.k8s-worker[count.index].id</span><span class="w"></span>
<span class="w"> </span><span class="na">subnet_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">hcloud_network_subnet.k8s-node-subnet.id</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Das interne Netzwerk hat sich gegenüber Part 2 leicht verändert — da die
control– und die worker–Nodes getrennt aufgesetzt werden doppelt sich hier
der Block der die Nodes dem Netzwerk hinzufügt.</p>
<h2>Neue Server</h2>
<p>server.tf:</p>
<div class="highlight"><pre><span></span><code><span class="kr">resource</span><span class="w"> </span><span class="nc">"hcloud_server"</span><span class="w"> </span><span class="nv">"k8s-control"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">count</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.control_count</span><span class="w"></span>
<span class="w"> </span><span class="na">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"k8s-control-${count.index}"</span><span class="w"></span>
<span class="w"> </span><span class="p">...</span><span class="w"></span>
<span class="w"> </span><span class="nb">labels</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"control"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">...</span><span class="w"></span>
<span class="w"> </span><span class="nb">network</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">network_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">hcloud_network.k8s-nodes.id</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">...</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p><code>count</code> und das Looping kennen wir auch schon, neu ist der <code>network</code>–Block,
der aus Server–Richtung nochmal angibt zu welchem Netzwerk die VM gehört.
Das brauchen wir gleich, um im rke–Provider an die IP aus dem internen Netz
zu kommen. Finde ich nicht so schön, habe aber keinen anderen Weg gefunden…
Der ganze Block kommt nochmal für die worker–Nodes, die <a href="https://git.zknt.org/chris/terraform-playground/src/commit/e2fa2be3b84527e1184463fd6d6fb6c2811c47ff/k8s-cluster/server.tf">ungekürzte Datei</a>
liegt im Git–Repo.</p>
<h2>Der große Cluster</h2>
<p>cluster.tf:</p>
<div class="highlight"><pre><span></span><code><span class="n">resource</span><span class="w"> </span><span class="s2">"rke_cluster"</span><span class="w"> </span><span class="s2">"cluster"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">kubernetes_version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">var</span><span class="o">.</span><span class="n">kubernetes_version</span><span class="w"></span>
<span class="w"> </span><span class="n">cluster_name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"hcloud_test"</span><span class="w"></span>
<span class="w"> </span><span class="n">dynamic</span><span class="w"> </span><span class="n">nodes</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">for_each</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">hcloud_server</span><span class="o">.</span><span class="n">k8s</span><span class="o">-</span><span class="n">control</span><span class="w"></span>
<span class="w"> </span><span class="n">iterator</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">node</span><span class="w"></span>
<span class="w"> </span><span class="n">content</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">hostname_override</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">node</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">name</span><span class="w"></span>
<span class="w"> </span><span class="n">address</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">node</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">ipv4_address</span><span class="w"></span>
<span class="w"> </span><span class="n">internal_address</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">node</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">network</span><span class="o">.*.</span><span class="n">ip</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="n">user</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"ansible"</span><span class="w"></span>
<span class="w"> </span><span class="n">role</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s2">"controlplane"</span><span class="p">,</span><span class="w"> </span><span class="s2">"etcd"</span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="n">ssh_key</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"${file("</span><span class="o">../</span><span class="n">ssh</span><span class="o">-</span><span class="n">terraform</span><span class="o">-</span><span class="n">hetzner</span><span class="s2">")}"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">dynamic</span><span class="w"> </span><span class="n">nodes</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="o">...</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Im <code>dynamic</code>–Block für die nodes iterieren wir über die VM–Liste und
erzeugen dabei die Node–Einträge, <code>iterator</code> legt den Variablennamen fest
der in <code>content</code> dann nutzbar ist. Damit die k8s–nodes nicht einfach nach
der IP heissen überschreibt <code>hostname_override</code> diese mit dem Namen,
<code>internal_address</code> setzt die Kommunikation zwischen den Nodes ins interne
Netz. Daher müssen wir auch in der firewall nichts freigeben. :)</p>
<p>Der Block folgt nochmal fast genauso für die worker–Nodes, mit entsprechend
angepasstem <code>for_each</code>, und der <code>role</code> "worker".</p>
<p><code>terraform apply</code> dauert jetzt schon etwas länger, am Ende steht dann aber
wirklich ein ausgewachsener Cluster da.</p>
<p>Alle Dateien liegen im <a href="https://git.zknt.org/chris/terraform-playground/src/branch/trunk/k8s-cluster">git–Repository</a>,
wie gewohnt.</p>terraforming Hetzner, Pt 32022-05-29T13:37:00+02:002022-05-29T13:37:00+02:00christag:cg.zknt.org,2022-05-29:/terraforming-hetzner-pt-3.html<h2>Ein Single–Node kubernetes–Cluster entsteht</h2>
<p>Die Loadbalancer, Netze uns so weiter aus dem <a href="https://cg.zknt.org/terraforming-hetzner-pt-2.html">letzten Artikel</a>
lassen wir erstmal bei Seite und bauen einen all–in–one kubernetes–Cluster auf
einer VM auf.</p>
<p>Vorweg: Im Netz tummeln sich viele Repositories mit terraform–Plänen um
kubernetes aufzubauen, oder mit Modulen um …</p><h2>Ein Single–Node kubernetes–Cluster entsteht</h2>
<p>Die Loadbalancer, Netze uns so weiter aus dem <a href="https://cg.zknt.org/terraforming-hetzner-pt-2.html">letzten Artikel</a>
lassen wir erstmal bei Seite und bauen einen all–in–one kubernetes–Cluster auf
einer VM auf.</p>
<p>Vorweg: Im Netz tummeln sich viele Repositories mit terraform–Plänen um
kubernetes aufzubauen, oder mit Modulen um das Ganze zu kapseln. Während es
mir jetzt natürlich auch nicht ausschließlich darum geht mein eigenes Rad
zu erfinden verwende ich davon doch keines. Ich habe mir einige Sachen
angeschaut, und bin eigentlich immer in irgendwelche Probleme gelaufen, sei
es das Abhängigkeiten ohne Version angegeben waren und es mit aktuellen
Providern nicht mehr lief, oder mit dem hcloud–Provider an Stellen geklemmt
hat, letztlich war ich doch immer wieder dabei zu versuchen den
heruntergeladenen Code zu reparieren bzw anzupassen, der (oft) doch recht
komplex war. Unter "sauber aufbauen und dabei verstehen" stelle ich mir etwas
anderes vor.</p>
<p>Am vielversprechensten sieht mir der <a href="https://registry.terraform.io/providers/rancher/rke/1.3.0/docs">rke–Provider</a>
von <a href="https://rancher.com/">rancher</a> aus — als vom Hersteller unterstütztes,
offizielles Projekt.
Mit rke (aber ohne terraform) habe ich vor einigen Jahren auch schon im Beruf
Cluster aufgebaut und recht gute Erfahrungen gemacht.</p>
<p>Als "sanften" Start wird der erste Cluster nur einen Node haben, und auf
internes Netz und Loadbalancer verzichten.</p>
<h2>Neue Provider</h2>
<p>terraform.tf:</p>
<div class="highlight"><pre><span></span><code><span class="nb">terraform</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nb">required_providers</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nb">hcloud</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">source</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"hetznercloud/hcloud"</span><span class="w"></span>
<span class="w"> </span><span class="na">version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"1.33.2"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="nb">rke</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">source</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"rancher/rke"</span><span class="w"></span>
<span class="w"> </span><span class="na">version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"1.3.0"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="nb">local</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">source</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"hashicorp/local"</span><span class="w"></span>
<span class="w"> </span><span class="na">version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"2.2.3"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="na">required_version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">">= 1.1"</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Neben dem schon erwähnten rke–Provider nutzen wir <code>local</code> um am Ende
die kubeconfig–Datei in eine lokale Datei schreiben zu können.
Nach dem Anpassen <code>terraform init</code> ausführen um die Provider herunterzuladen.</p>
<h2>Neue Variablen</h2>
<p>variables.tf:</p>
<div class="highlight"><pre><span></span><code><span class="kr">variable</span><span class="w"> </span><span class="nv">"server_type"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">default</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"cx21"</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kr">variable</span><span class="w"> </span><span class="nv">"os_type"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">default</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"ubuntu-20.04"</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kr">variable</span><span class="w"> </span><span class="nv">"docker_version"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">default</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"20.10.16"</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kr">variable</span><span class="w"> </span><span class="nv">"containerd_version"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">default</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"1.6.4"</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kr">variable</span><span class="w"> </span><span class="nv">"kubernetes_version"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">default</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"v1.22.4-rancher1-1"</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Damit wir neben dem kubernetes noch etwas Platz haben setze ich den Servertyp
eine Größe hoch, ausserdem nutze ich Ubuntu — denn Debian wird von rke nicht
offiziell unterstützt.</p>
<p>rke setzt den kubernetes–Cluster in Docker auf, daher setzen wir eine
unterstützte docker– & containerd–Version.</p>
<p>Und zu guter letzt die aktuellste unterstützte k8s–Version.</p>
<h2>Änderungen an Firewall und Server</h2>
<p>firewall.tf:</p>
<div class="highlight"><pre><span></span><code><span class="kr">resource</span><span class="w"> </span><span class="nc">"hcloud_firewall"</span><span class="w"> </span><span class="nv">"k8s-single"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"k8s-single"</span><span class="w"></span>
<span class="w"> </span><span class="nb">rule</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">direction</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"in"</span><span class="w"></span>
<span class="w"> </span><span class="na">protocol</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"icmp"</span><span class="w"></span>
<span class="w"> </span><span class="na">source_ips</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="s2">"0.0.0.0/0"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"::/0"</span><span class="w"></span>
<span class="w"> </span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="nb">rule</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">direction</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"in"</span><span class="w"></span>
<span class="w"> </span><span class="na">protocol</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"tcp"</span><span class="w"></span>
<span class="w"> </span><span class="na">port</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"22"</span><span class="w"></span>
<span class="w"> </span><span class="na">source_ips</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="s2">"0.0.0.0/0"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"::/0"</span><span class="w"></span>
<span class="w"> </span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="nb">rule</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">direction</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"in"</span><span class="w"></span>
<span class="w"> </span><span class="na">protocol</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"tcp"</span><span class="w"></span>
<span class="w"> </span><span class="na">port</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"6443"</span><span class="w"></span>
<span class="w"> </span><span class="na">source_ips</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="s2">"0.0.0.0/0"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"::/0"</span><span class="w"></span>
<span class="w"> </span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Neben ICMP und SSH brauchen wir Port 6443 um den Cluster per kubectl zu
erreichen. Wenn Du eine statische IP hast ist es natürlich umso schöner
diese unter <code>source_ips</code> einzutragen und nicht das gesamte Internet zu
erlauben. :)</p>
<p>server.tf:</p>
<div class="highlight"><pre><span></span><code><span class="kr">resource</span><span class="w"> </span><span class="nc">"hcloud_server"</span><span class="w"> </span><span class="nv">"k8s-single"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"k8s-single"</span><span class="w"></span>
<span class="w"> </span><span class="na">image</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.os_type</span><span class="w"></span>
<span class="w"> </span><span class="na">server_type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.server_type</span><span class="w"></span>
<span class="w"> </span><span class="na">location</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.location</span><span class="w"></span>
<span class="w"> </span><span class="nb">labels</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"single"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="na">ssh_keys</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nv">hcloud_ssh_key.default.id</span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="na">user_data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">templatefile</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="s2">"user-data.yaml.tpl"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">ssh_pubkey</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">file</span><span class="p">(</span><span class="s2">"../ssh-terraform-hetzner.pub"</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="na">containerd_version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.containerd_version</span><span class="w"></span>
<span class="w"> </span><span class="na">docker_version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.docker_version</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="na">firewall_ids</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nv">hcloud_firewall.k8s-single.id</span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="nb">connection</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"ssh"</span><span class="w"></span>
<span class="w"> </span><span class="na">user</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"root"</span><span class="w"></span>
<span class="w"> </span><span class="na">host</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">self.ipv6_address</span><span class="w"></span>
<span class="w"> </span><span class="na">private_key</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">file</span><span class="p">(</span><span class="s2">"../ssh-terraform-hetzner"</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="kr">provisioner</span><span class="w"> </span><span class="nv">"remote-exec"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">inline</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="s2">"cloud-init status --wait"</span><span class="w"></span>
<span class="w"> </span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Der angepasste Server, <code>cloud-init</code> bekommt ein paar mehr Parameter, denn
über das cloud–init–Skript installieren wir gleich Docker.
Da wir im nächsten Schritt Docker brauchen muß terraform jetzt warten bis
das Skript durchgelaufen ist. Dazu nutze ich <code>remote-exec</code>, das ausgeführte
Kommando wartet schlicht bis der eigentlich im Hintergrund laufende Job
fertig ist. Damit das Kommando ausgeführt werden kann braucht terraform
noch den Zugang zum Server, dieser ist im <code>connection</code>–Block definiert.
Steckt Dein Provider noch zu tief in der Vergangenheit fest musst du ggf.
<code>ipv4_address</code> nutzen. :)</p>
<p>user-data.yaml.tpl:</p>
<div class="highlight"><pre><span></span><code><span class="c1">#cloud-config</span><span class="w"></span>
<span class="nt">users</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s">"ansible"</span><span class="w"></span>
<span class="w"> </span><span class="nt">groups</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">[</span><span class="s">"sudo"</span><span class="p p-Indicator">]</span><span class="w"></span>
<span class="w"> </span><span class="nt">sudo</span><span class="p">:</span><span class="w"> </span><span class="s">"ALL=(ALL)</span><span class="nv"> </span><span class="s">NOPASSWD:ALL"</span><span class="w"></span>
<span class="w"> </span><span class="nt">shell</span><span class="p">:</span><span class="w"> </span><span class="s">"/bin/bash"</span><span class="w"></span>
<span class="w"> </span><span class="nt">ssh_authorized_keys</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"${ssh_pubkey}"</span><span class="w"></span>
<span class="nt">package_update</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">true</span><span class="w"></span>
<span class="nt">package_upgrade</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">true</span><span class="w"></span>
<span class="nt">runcmd</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">curl -LO https://download.docker.com/linux/ubuntu/dists/focal/pool/stable/amd64/containerd.io_${containerd_version}-1_amd64.deb</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">curl -LO https://download.docker.com/linux/ubuntu/dists/focal/pool/stable/amd64/docker-ce-cli_${docker_version}~3-0~ubuntu-focal_amd64.deb</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">curl -LO https://download.docker.com/linux/ubuntu/dists/focal/pool/stable/amd64/docker-ce_${docker_version}~3-0~ubuntu-focal_amd64.deb</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">dpkg -i *deb</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">usermod -a -G docker ansible</span><span class="w"></span>
</code></pre></div>
<p>Neben dem schon bekannten User und Paketupgrade führen wir per cloud-init
ein paar Kommandos aus, und zwar holen wir die fürs Ubuntu passenden Pakete,
installieren sie, und ermöglichem dem Ansible–User Docker zu nutzen.</p>
<h2>Der k8s–Cluster</h2>
<p>cluster.tf:</p>
<div class="highlight"><pre><span></span><code><span class="l l-Scalar l-Scalar-Plain">resource "rke_cluster" "cluster" {</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">kubernetes_version = var.kubernetes_version</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">nodes {</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">address = hcloud_server.k8s-single.ipv4_address</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">user = "ansible"</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">role = ["controlplane", "worker", "etcd"]</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">ssh_key = "${file("../ssh-terraform-hetzner")}"</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">}</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">addons_include = [</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">"https://raw.githubusercontent.com/kubernetes/dashboard/v2.5.1/aio/deploy/recommended.yaml"</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">]</span><span class="w"></span>
<span class="l l-Scalar l-Scalar-Plain">}</span><span class="w"></span>
<span class="l l-Scalar l-Scalar-Plain">resource "local_sensitive_file" "kube_config" {</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">filename = "${path.root}/kube_config_single.yml"</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">content = "${rke_cluster.cluster.kube_config_yaml}"</span><span class="w"></span>
<span class="l l-Scalar l-Scalar-Plain">}</span><span class="w"></span>
</code></pre></div>
<p>Und, endlich, der kubernetes–Cluster. Viel ist für den single node gar nicht
zu tun, der erste Block setzt den Cluster in gewünschter Version auf,
definiert den einen Node in allen Rollen (rke scheint IPv6 nicht zu
unterstützen) und installiert kubernetes dashboard, der zweite schreibt uns
die dadurch erzeugte kubeconfig in eine Datei.</p>
<p>Ein <code>terraform apply</code> später läuft also der Cluster, und lässt sich mit
kubectl ansprechen:</p>
<p><code>kubectl --kubeconfig kube_config_single.yml version --short</code> gibt die
installierte Version aus.</p>
<p><code>terraform destroy</code> nicht vergessen um die Kostenuhr anzuhalten, und alle
Dateien liegen wieder im <a href="https://git.zknt.org/chris/terraform-playground/src/branch/trunk/k8s-single-node">git–Repository</a> :)</p>
<p>Im <a href="https://cg.zknt.org/terraforming-hetzner-pt-4.html">nächsten Artikel</a> erweitern wir
den Cluster über mehrere Nodes.</p>terraforming Hetzner, Pt 22022-05-25T13:37:00+02:002022-05-25T13:37:00+02:00christag:cg.zknt.org,2022-05-25:/terraforming-hetzner-pt-2.html<h2>Mehrere VMs, internes Netzwerk und Loadbalancer</h2>
<p>Nachdem wir <a href="https://cg.zknt.org/terraforming-hetzner-pt-1.html">eine erste VM automatisiert aufbauen</a> können, erweitern wir unsere
Templates ein wenig: Es werden 3 VMs mit Webservern, die in einem virtuellen
internen Netzwerk stehen sollen und von einem Load Balancer angesprochen
werden.</p>
<h2>Neue Variablen</h2>
<p>variables.tf:</p>
<div class="highlight"><pre><span></span><code><span class="kr">variable</span><span class="w"> </span><span class="nv">"instance_count"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">default</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">3</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kr">variable …</span></code></pre></div><h2>Mehrere VMs, internes Netzwerk und Loadbalancer</h2>
<p>Nachdem wir <a href="https://cg.zknt.org/terraforming-hetzner-pt-1.html">eine erste VM automatisiert aufbauen</a> können, erweitern wir unsere
Templates ein wenig: Es werden 3 VMs mit Webservern, die in einem virtuellen
internen Netzwerk stehen sollen und von einem Load Balancer angesprochen
werden.</p>
<h2>Neue Variablen</h2>
<p>variables.tf:</p>
<div class="highlight"><pre><span></span><code><span class="kr">variable</span><span class="w"> </span><span class="nv">"instance_count"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">default</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">3</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kr">variable</span><span class="w"> </span><span class="nv">"ip_range"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">default</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"10.0.30.0/24"</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Einmal die Anzahl der zu erzeugenden VM–Instanzen, einmal ein Netzwerk–Range
für das interne Netz. Hetzner baut hier leider noch voll auf legacy IP,
also passt irgendwas aus den privaten Ranges aus <a href="https://www.rfc-editor.org/rfc/rfc1918#section-3">RFC 1918</a>.</p>
<h2>Netzwerk und Loadbalancer</h2>
<p>network.tf:</p>
<div class="highlight"><pre><span></span><code><span class="kr">resource</span><span class="w"> </span><span class="nc">"hcloud_network"</span><span class="w"> </span><span class="nv">"tw_private"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"three_web_private"</span><span class="w"></span>
<span class="w"> </span><span class="na">ip_range</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.ip_range</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kr">resource</span><span class="w"> </span><span class="nc">"hcloud_network_subnet"</span><span class="w"> </span><span class="nv">"tw_priv_subnet"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">network_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">hcloud_network.tw_private.id</span><span class="w"></span>
<span class="w"> </span><span class="na">type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"cloud"</span><span class="w"></span>
<span class="w"> </span><span class="na">network_zone</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"eu-central"</span><span class="w"></span>
<span class="w"> </span><span class="na">ip_range</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.ip_range</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kr">resource</span><span class="w"> </span><span class="nc">"hcloud_server_network"</span><span class="w"> </span><span class="nv">"tw_network"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">count</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.instance_count</span><span class="w"></span>
<span class="w"> </span><span class="na">server_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">hcloud_server.web-server[count.index].id</span><span class="w"></span>
<span class="w"> </span><span class="na">subnet_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">hcloud_network_subnet.tw_priv_subnet.id</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Der erste Block definiert das Netzwerk <code>three_web_private</code> mit der vorher
ausgesuchten IP Range, der zweite richtet ein Subnet in der passenden
Netzwerk–Zone ein, die Zone könnte eigentlich noch gut als Variable neben
die <code>location</code> der Server. Der dritte Block verknüpft das Subnet mit den
Servern die wir später noch definieren, über <code>count</code> passiert das 3 mal.</p>
<p>loadbalancer.tf:</p>
<div class="highlight"><pre><span></span><code><span class="kr">resource</span><span class="w"> </span><span class="nc">"hcloud_load_balancer"</span><span class="w"> </span><span class="nv">"tw_lb"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"three-web-load-balancer"</span><span class="w"></span>
<span class="w"> </span><span class="na">load_balancer_type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"lb11"</span><span class="w"></span>
<span class="w"> </span><span class="na">location</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.location</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kr">resource</span><span class="w"> </span><span class="nc">"hcloud_load_balancer_network"</span><span class="w"> </span><span class="nv">"tw_lb_network"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">load_balancer_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">hcloud_load_balancer.tw_lb.id</span><span class="w"></span>
<span class="w"> </span><span class="na">subnet_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">hcloud_network_subnet.tw_priv_subnet.id</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kr">resource</span><span class="w"> </span><span class="nc">"hcloud_load_balancer_target"</span><span class="w"> </span><span class="nv">"tw_lb_target"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"label_selector"</span><span class="w"></span>
<span class="w"> </span><span class="na">load_balancer_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">hcloud_load_balancer.tw_lb.id</span><span class="w"></span>
<span class="w"> </span><span class="na">label_selector</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"type=web"</span><span class="w"></span>
<span class="w"> </span><span class="na">use_private_ip</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="no">true</span><span class="w"></span>
<span class="w"> </span><span class="na">depends_on</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="nv">hcloud_load_balancer_network.tw_lb_network</span><span class="w"></span>
<span class="w"> </span><span class="p">]</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kr">resource</span><span class="w"> </span><span class="nc">"hcloud_load_balancer_service"</span><span class="w"> </span><span class="nv">"lb_service"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">load_balancer_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">hcloud_load_balancer.tw_lb.id</span><span class="w"></span>
<span class="w"> </span><span class="na">protocol</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"http"</span><span class="w"></span>
<span class="w"> </span><span class="na">listen_port</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">80</span><span class="w"></span>
<span class="w"> </span><span class="na">destination_port</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">80</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Ich musste die Variablen etwas kürzen damit das CSS des Blogs damit klarkommt.
Im Repo (ganz unten verlinkt) stehen sie ausgeschrieben :)</p>
<p>Wir legen hier einen Loadbalancer an, den kleinsten (lb11), in der
Location in der auch unsere Server stehen werden.</p>
<p>Der zweite Block fügt den Loadbalancer unserem vorher definierten internen
Subnet zu.</p>
<p>Der dritte setzt als Ziel unsere Server die wir gleich noch definieren, und
zwar über das Label <code>type=web</code>. Wir könnten auch direkt die Server per id
angeben, über die label–Config wäre es aber möglich später dynamisch VMs
hinzuzufügen oder zu entfernen ohne den Loadbalancer umkonfigurieren zu müssen.
<code>use_private_ip</code> sorgt dafür das wir die VMs über das interne Netz ansprechen.</p>
<p>Dadurch gibt es aber auch eine Besonderheit in diesem Block, <code>depends_on</code>.
Um das target in der Hetzner Cloud anlegen zu können muss der Loadbalancer
zu einem Subnet gehören, da in der Resource <code>tw_lb_target</code> aber keins angegeben
würde kann terraform diese Abhängigkeit nicht erkennen und könnte die Resourcen
gleichzeitig oder in falscher Reihenfolge anlegen, was zu einem Fehler führen
würde. Daher sagen wir terraform über <code>depends_on</code> selber das erst die
Netzwerkverknüpfung fertig sein muss, und erst danach das Target angelegt
werden darf.</p>
<p>Die Servicedefinition im vierten Block schließlich setzt stumpf Port 80 am
Loadbalancer auf Port 80 an den VMs um. Durch die Angabe vom <code>protocol</code> wird
automatisch ein Health–Check eingerichtet, der prüft ob die Webserver für <code>/</code>
antworten.</p>
<h2>Neue Server</h2>
<p>server.tf:</p>
<div class="highlight"><pre><span></span><code><span class="kr">resource</span><span class="w"> </span><span class="nc">"hcloud_server"</span><span class="w"> </span><span class="nv">"web-server"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">count</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.instance_count</span><span class="w"></span>
<span class="w"> </span><span class="na">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"web-server-${count.index}"</span><span class="w"></span>
<span class="w"> </span><span class="na">image</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.os_type</span><span class="w"></span>
<span class="w"> </span><span class="na">server_type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.server_type</span><span class="w"></span>
<span class="w"> </span><span class="na">location</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.location</span><span class="w"></span>
<span class="w"> </span><span class="nb">labels</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"web"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="na">ssh_keys</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nv">hcloud_ssh_key.default.id</span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="na">user_data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">templatefile</span><span class="p">(</span><span class="s2">"user-data.yaml.tpl"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">{</span><span class="na">ssh_pubkey</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">file</span><span class="p">(</span><span class="s2">"../ssh-terraform-hetzner.pub"</span><span class="p">)})</span><span class="w"></span>
<span class="w"> </span><span class="na">firewall_ids</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nv">hcloud_firewall.single-firewall.id</span><span class="p">]</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Gegenüber dem einzelnen Server müssen wir gar nicht viel anpassen um mehrere
zu erzeugen. Neu ist <code>count</code>, was den Hauptteil der Magie ausmacht, der
Servername ist angepasst und das Label auf <code>web</code> umgesetzt, damit der
Loadbalancer die VMs findet. Der Rest der Serverdefinition ist unverändert.</p>
<p>user-data.yaml.tpl:</p>
<div class="highlight"><pre><span></span><code><span class="c1">#cloud-config</span><span class="w"></span>
<span class="nt">users</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s">"ansible"</span><span class="w"></span>
<span class="w"> </span><span class="nt">groups</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">[</span><span class="s">"sudo"</span><span class="p p-Indicator">]</span><span class="w"></span>
<span class="w"> </span><span class="nt">sudo</span><span class="p">:</span><span class="w"> </span><span class="s">"ALL=(ALL)</span><span class="nv"> </span><span class="s">NOPASSWD:ALL"</span><span class="w"></span>
<span class="w"> </span><span class="nt">shell</span><span class="p">:</span><span class="w"> </span><span class="s">"/bin/bash"</span><span class="w"></span>
<span class="w"> </span><span class="nt">ssh_authorized_keys</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"${ssh_pubkey}"</span><span class="w"></span>
<span class="nt">packages</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">nginx</span><span class="w"></span>
<span class="nt">package_update</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">true</span><span class="w"></span>
<span class="nt">package_upgrade</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">true</span><span class="w"></span>
<span class="nt">runcmd</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">systemctl enable --now nginx</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">echo "<h1>terraformed</h1>\nthis is $(hostname)" \</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">> /var/www/html/index.html</span><span class="w"></span>
</code></pre></div>
<p>Über cloud-init installieren wir schließlich den Webserver (nginx), starten ihn
und hinterlegen eine Seite die den Hostnamen enthält.</p>
<p>output.tf:</p>
<div class="highlight"><pre><span></span><code><span class="n">output</span><span class="w"> </span><span class="s2">"lb_ip"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">description</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Load balancer IP address"</span><span class="w"></span>
<span class="w"> </span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">hcloud_load_balancer</span><span class="o">.</span><span class="n">tw_lb</span><span class="o">.</span><span class="n">ipv6</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="n">output</span><span class="w"> </span><span class="s2">"web_ips"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">description</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Test VM IP"</span><span class="w"></span>
<span class="w"> </span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">server</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="n">hcloud_server</span><span class="o">.</span><span class="n">web</span><span class="o">-</span><span class="n">server</span><span class="w"> </span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="n">server</span><span class="o">.</span><span class="n">name</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="n">server</span><span class="o">.</span><span class="n">ipv6_address</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="n">output</span><span class="w"> </span><span class="s2">"web_ipv4"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">description</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Test VM legacy IP"</span><span class="w"></span>
<span class="w"> </span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">server</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="n">hcloud_server</span><span class="o">.</span><span class="n">web</span><span class="o">-</span><span class="n">server</span><span class="w"> </span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="n">server</span><span class="o">.</span><span class="n">name</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="n">server</span><span class="o">.</span><span class="n">ipv4_address</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Zuletzt das angepasste output–Template. Neu ist die Ausgabe der IP des
Loadbalancers, und die IPs der Server sind um eine Schleife erweitert um alle
3 Server zu sehen.</p>
<h2>Ausführen und Testen</h2>
<p>Die Ausführung (<code>terraform apply</code>) dieses Plans dauert schon ein wenig länger
als die einzelne VM, und nach dem terraform warte ich nach dem Testen noch
einige Sekunden bis das cloud-init durchgelaufen und alle nginxe installiert
sind.</p>
<p>Letztlich gibt aber terraform über die outputs aus:</p>
<div class="highlight"><pre><span></span><code>lb_ip = "2a01:4f8:1c1d:aee::1"
web_ips = {
"web-server-0" = "2a01:4f8:c0c:77d3::1"
"web-server-1" = "2a01:4f8:1c1e:dbf4::1"
"web-server-2" = "2a01:4f8:1c1e:e2c6::1"
}
web_ipv4 = {
"web-server-0" = "195.201.219.117"
"web-server-1" = "167.235.74.69"
"web-server-2" = "195.201.98.137"
}
</code></pre></div>
<p>und über ein paar wiederholte curls sehen wir das der Loadbalancer
Anfragen verteilt:</p>
<div class="highlight"><pre><span></span><code>$ curl "http://[2a01:4f8:1c1d:aee::1]/"
<span class="nt"><h1></span>terraformed<span class="nt"></h1></span>
this is web-server-0
$ curl "http://[2a01:4f8:1c1d:aee::1]/"
<span class="nt"><h1></span>terraformed<span class="nt"></h1></span>
this is web-server-2
$ curl "http://[2a01:4f8:1c1d:aee::1]/"
<span class="nt"><h1></span>terraformed<span class="nt"></h1></span>
this is web-server-1
...
</code></pre></div>
<p>Am Schluß <code>terraform destroy</code> nicht vergessen, und auch diese Dateien liegen
im <a href="https://git.zknt.org/chris/terraform-playground/src/branch/trunk/three-web-hosts">git–Repository</a> :)</p>terraforming Hetzner, Pt 12022-05-24T13:38:00+02:002022-05-24T13:38:00+02:00christag:cg.zknt.org,2022-05-24:/terraforming-hetzner-pt-1.html<h2>Die erste automatisierte VM</h2>
<p>Erster Teil der <a href="https://cg.zknt.org/terraforming-hetzner-cloud.html">terraform–Serie</a>: Basics und eine einzelne VM.</p>
<p>Zum Zeitpunkt der Drucklegung benutze ich
<a href="https://www.terraform.io/downloads">terraform in Version 1.2.0</a>. Alles weitere
installieren wir im Laufe des Wegs. :)</p>
<h2>Vorbereitungen</h2>
<p>Neben terraform und einem geeigneten Rechner um es auszuführen (btw, I use arch)
brauchen wir …</p><h2>Die erste automatisierte VM</h2>
<p>Erster Teil der <a href="https://cg.zknt.org/terraforming-hetzner-cloud.html">terraform–Serie</a>: Basics und eine einzelne VM.</p>
<p>Zum Zeitpunkt der Drucklegung benutze ich
<a href="https://www.terraform.io/downloads">terraform in Version 1.2.0</a>. Alles weitere
installieren wir im Laufe des Wegs. :)</p>
<h2>Vorbereitungen</h2>
<p>Neben terraform und einem geeigneten Rechner um es auszuführen (btw, I use arch)
brauchen wir einen <a href="https://www.hetzner.com/cloud">Hetzner Cloud</a>–Account. Die
Kosten zum Experimentieren halten sich im Rahmen, ich rechne mit ca. 30–50 cent
für einen Nachmittag/Abend. Wenn Du Maschinen dauerhaft laufen lassen willst
kommt da natürlich mehr zusammen.</p>
<p>Ich vermute dass es nicht viel Aufwand ist die terraform templates auf andere
Provider umzubauen — habe das aber noch nicht getestet.</p>
<p>Im Hetzner Cloud Account müssen wir ein Projekt anlegen, und in diesem Projekt
ein API–Token, dies muss Lese– und Schreibberechtigung haben. Den Token erstmal
irgendwo zwischenspeichern (wo niemand sonst drankommt).</p>
<h2>Provider installieren und Zugang einrichten</h2>
<p>Ich werde den Post über immer meine Quelldateien einbetten und darunter
beschreiben was drin steht. Alles zusammen findest Du ganz unten als Git–Repo
verlinkt.</p>
<p>terraform.tf:</p>
<div class="highlight"><pre><span></span><code><span class="nb">terraform</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nb">required_providers</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nb">hcloud</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">source</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"hetznercloud/hcloud"</span><span class="w"></span>
<span class="w"> </span><span class="na">version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"1.33.2"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="na">required_version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">">= 1.1"</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Zuerst legen wir fest das wir den hcloud–Provider nutzen werden, und setzen die
Version fest. Da sich über die Zeit durchaus Parameter verändern ist nicht gegeben
das die Templates so mit einer zukünftigen Providerversion funktionieren.
<code>required_version</code> bezieht sich auf terraform selbst.</p>
<p>provider.tf:</p>
<div class="highlight"><pre><span></span><code><span class="kr">provider</span><span class="w"> </span><span class="nv">"hcloud"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">token</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.hcloud_token</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Zum Login brauchen wir das oben generiert API–Token. Das könnte hier direkt stehen (statt <code>var...</code>), oder jedes mal eingetippt werden, dann wäre die Konfiguraton so fertig.
Ich will es aber nicht ins gitrepo einchecken, daher ists eine Variable.</p>
<p>variables.tf:</p>
<div class="highlight"><pre><span></span><code><span class="kr">variable</span><span class="w"> </span><span class="nv">"hcloud_token"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">sensitive</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="no">true</span><span class="c1"></span>
<span class="c1"> # default = <defined in secret.auto.tfvars></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Auch hier könnte der Token als default stehen, aber auch die variables.tf will ich git haben, also...</p>
<p>secret.auto.tfvars:</p>
<div class="highlight"><pre><span></span><code><span class="na">hcloud_token</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"hunter2"</span><span class="w"></span>
</code></pre></div>
<p>Hier steht endlich der Token. tfvars–Dateien überschreiben schon gesetzte Variablen, endet der Dateiname auf .auto.tfvars werden sie automatisch geladen. Die Datei kommt ins .gitignore, und der Token bleibt sicher.</p>
<p>Jetzt ist das Projekt soweit das wir den Provider installieren können: <code>terraform init</code></p>
<p>Theoretisch funktioniert jetzt auch <code>terraform apply</code> schon, tut aber noch nichts...</p>
<h2>SSH Public Key und Firewall–Regeln</h2>
<p>ssh.tf:</p>
<div class="highlight"><pre><span></span><code><span class="kr">resource</span><span class="w"> </span><span class="nc">"hcloud_ssh_key"</span><span class="w"> </span><span class="nv">"default"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"terraform"</span><span class="w"></span>
<span class="w"> </span><span class="na">public_key</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">file</span><span class="p">(</span><span class="s2">"../ssh-terraform-hetzner.pub"</span><span class="p">)</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Upload eines SSH–Keys. In terraform wird der hier angelegte Key <code>default</code> heissen, in der Hetzner Console "terraform". Vergiss nicht die Datei vorher zu erzeugen oder den Pfad zu ändern ;)</p>
<p>firewall.tf:</p>
<div class="highlight"><pre><span></span><code><span class="kr">resource</span><span class="w"> </span><span class="nc">"hcloud_firewall"</span><span class="w"> </span><span class="nv">"single-firewall"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"single-firewall"</span><span class="w"></span>
<span class="w"> </span><span class="nb">rule</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">direction</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"in"</span><span class="w"></span>
<span class="w"> </span><span class="na">protocol</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"icmp"</span><span class="w"></span>
<span class="w"> </span><span class="na">source_ips</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="s2">"0.0.0.0/0"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"::/0"</span><span class="w"></span>
<span class="w"> </span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="nb">rule</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">direction</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"in"</span><span class="w"></span>
<span class="w"> </span><span class="na">protocol</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"tcp"</span><span class="w"></span>
<span class="w"> </span><span class="na">port</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"22"</span><span class="w"></span>
<span class="w"> </span><span class="na">source_ips</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="s2">"0.0.0.0/0"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"::/0"</span><span class="w"></span>
<span class="w"> </span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="nb">rule</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">direction</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"in"</span><span class="w"></span>
<span class="w"> </span><span class="na">protocol</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"tcp"</span><span class="w"></span>
<span class="w"> </span><span class="na">port</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"80"</span><span class="w"></span>
<span class="w"> </span><span class="na">source_ips</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="s2">"0.0.0.0/0"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"::/0"</span><span class="w"></span>
<span class="w"> </span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Vorbereitung der Firewall. Eingehender Traffic wird blockiert, ausser ICMP (denn ohne icmp kein Internet), Port 22 für SSH und Port 80 für Web. Auch wenn die Beispiel–VM keinen Webserver haben wird…</p>
<h2>Der eigentliche Server</h2>
<p>Nun kommen wir endlich zum eigentlichen Server. Dazu gibts erstmal ein paar neue Variablen:</p>
<p>variables.tf:</p>
<div class="highlight"><pre><span></span><code><span class="kr">variable</span><span class="w"> </span><span class="nv">"location"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">default</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"nbg1"</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kr">variable</span><span class="w"> </span><span class="nv">"server_type"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">default</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"cx11"</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kr">variable</span><span class="w"> </span><span class="nv">"os_type"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">default</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"debian-11"</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Es wird der kleinste VM–Typ in Nürnberg, mit Debian 11. Andere gültige Werte findest Du in der Dokumentation von Hetzner.</p>
<p>server.tf:</p>
<div class="highlight"><pre><span></span><code><span class="kr">resource</span><span class="w"> </span><span class="nc">"hcloud_server"</span><span class="w"> </span><span class="nv">"single-server1"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"single-server1"</span><span class="w"></span>
<span class="w"> </span><span class="na">image</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.os_type</span><span class="w"></span>
<span class="w"> </span><span class="na">server_type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.server_type</span><span class="w"></span>
<span class="w"> </span><span class="na">location</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">var.location</span><span class="w"></span>
<span class="w"> </span><span class="nb">labels</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"single"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="na">ssh_keys</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nv">hcloud_ssh_key.default.id</span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="na">firewall_ids</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nv">hcloud_firewall.single-firewall.id</span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="na">user_data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">templatefile</span><span class="p">(</span><span class="s2">"user-data.yaml.tpl"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">{</span><span class="na">ssh_pubkey</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">file</span><span class="p">(</span><span class="s2">"../ssh-terraform-hetzner.pub"</span><span class="p">)})</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Der eigentliche Server, heißt im terraform <code>single-server1</code>, in der Hetzner Console auch, einige Werte kommen aus den Variablen. Das Label wird nicht weiter benutzt und ist eigentlich egal, das wird im nächsten Artikel interessant. Wir referenzieren die vorher angelete Firewall und den SSH–Key für den root–Account. Spannend sind die letzten beiden Zeilen, damit binden wir ein Template in cloud-init ein, um den Server nach dem Boot etwas weiter einzurichten.</p>
<p>user-data.yaml.tpl:</p>
<div class="highlight"><pre><span></span><code><span class="c1">#cloud-config</span><span class="w"></span>
<span class="nt">users</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s">"ansible"</span><span class="w"></span>
<span class="w"> </span><span class="nt">groups</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">[</span><span class="s">"sudo"</span><span class="p p-Indicator">]</span><span class="w"></span>
<span class="w"> </span><span class="nt">sudo</span><span class="p">:</span><span class="w"> </span><span class="s">"ALL=(ALL)</span><span class="nv"> </span><span class="s">NOPASSWD:ALL"</span><span class="w"></span>
<span class="w"> </span><span class="nt">shell</span><span class="p">:</span><span class="w"> </span><span class="s">"/bin/bash"</span><span class="w"></span>
<span class="w"> </span><span class="nt">ssh_authorized_keys</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"${ssh_pubkey}"</span><span class="w"></span>
<span class="nt">package_update</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">true</span><span class="w"></span>
<span class="nt">package_upgrade</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">true</span><span class="w"></span>
</code></pre></div>
<p>Per cloud-init legen wir einen Nutzer ansible an, der per sudo alle Befehle ausführen darf, und über die Variable <code>ssh_pubkey</code> aus terraform heraus den public key bekommt.
<em>Wichtig</em>: Die erste Zeile muss dieser schusselige Kommentar sein, sonst sucht ihr eine Stunde lang warum das Skript nicht ausgeführt wird. :)
Und noch ein Hinweis: Das Skript läuft los nachdem terraform schon fertig ist. Es kann also ein paar Sekunden dauern bis der ansible–Nutzer da ist.</p>
<p>Last but not least, output.tf:</p>
<div class="highlight"><pre><span></span><code><span class="kr">output</span><span class="w"> </span><span class="nv">"test_ip"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">description</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Test VM IP"</span><span class="w"></span>
<span class="w"> </span><span class="na">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">hcloud_server.single-server1.ipv6_address</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kr">output</span><span class="w"> </span><span class="nv">"test_ipv4"</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="na">description</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Test VM legacy IP"</span><span class="w"></span>
<span class="w"> </span><span class="na">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">hcloud_server.single-server1.ipv4_address</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Die outputs geben einfach nur am Ende des terraform–Laufs ein paar Daten aus.
Hier sehen wir bequem die IP der neu angelegten VM.</p>
<p>Sind alle Dateien angelegt und ausgefüllt kannst Du mit <code>terraform plan</code> anschauen was passieren wird, und mit <code>terraform apply</code> die VM erstellen. <code>terraform destroy</code> löscht alles wieder. Nicht vergessen, sonst läuft die Kostenuhr weiter. ;)</p>
<p>Die ganzen Dateien liegen auch in meinem <a href="https://git.zknt.org/chris/terraform-playground/src/branch/trunk/single-host">git–Repository</a></p>
<p>Im <a href="https://cg.zknt.org/terraforming-hetzner-pt-2.html">nächsten Artikel</a> fügen wir mehrere VMs, ein internes Netzwerk und Loadbalancer hinzu.</p>terraforming Hetzner Cloud2022-05-24T13:37:00+02:002022-05-24T13:37:00+02:00christag:cg.zknt.org,2022-05-24:/terraforming-hetzner-cloud.html<p>Ein Großteil meiner Infrastruktur läuft in der Hetzner Cloud — automatisiert mit
Ansible und den <a href="https://docs.ansible.com/ansible/latest/collections/hetzner/hcloud/index.html">hcloud</a>–Modulen.</p>
<p>Um mal wieder etwas anderes und aktuelles zu testen möchte ich mal besser in
<a href="https://www.terraform.io/">terraform</a> einarbeiten.</p>
<p>Ich habe erstmal drei verschiedene Szenarien gebaut:</p>
<ul>
<li>Eine einzelne VM in Hetzner Cloud mit Ansible–User die ich …</li></ul><p>Ein Großteil meiner Infrastruktur läuft in der Hetzner Cloud — automatisiert mit
Ansible und den <a href="https://docs.ansible.com/ansible/latest/collections/hetzner/hcloud/index.html">hcloud</a>–Modulen.</p>
<p>Um mal wieder etwas anderes und aktuelles zu testen möchte ich mal besser in
<a href="https://www.terraform.io/">terraform</a> einarbeiten.</p>
<p>Ich habe erstmal drei verschiedene Szenarien gebaut:</p>
<ul>
<li>Eine einzelne VM in Hetzner Cloud mit Ansible–User die ich weiter bespielen
könnte — <a href="https://cg.zknt.org/terraforming-hetzner-pt-1.html">zum Artikel</a></li>
<li>Ein "Cluster" aus 3 Webservern mit Hetzner Loadbalancer davor die eine simple
Webseite liefern — <a href="https://cg.zknt.org/terraforming-hetzner-pt-2.html">zum Artikel</a></li>
<li>Ein kubernetes–Cluster mit Basisdiensten wie
certmanager und ingress.<br>
Erster Schritt: <a href="https://cg.zknt.org/terraforming-hetzner-pt-3.html">Single-Node kubernetes</a><br>
Zweiter Schritt: <a href="https://cg.zknt.org/terraforming-hetzner-pt-4.html">HA Cluster</a><br>
Die Basisworkloads folgen noch in separaten Artikeln.</li>
</ul>
<p>Um es besser zu verstehen beschränke ich mich auf die <a href="https://registry.terraform.io/providers/hetznercloud/hcloud/1.33.2/docs">Doku vom hcloud–Provider</a>
und baue nicht auf einem schon fertigen Repo aus dem Netz auf.</p>
<p>Eine tagge alle Artikel mit "terraform", so das eine Übersicht auf der <a href="https://cg.zknt.org/tag/terraform.html">Tag-Seite</a> entsteht.</p>Mini-Post-Mortem fehlgeschlagene DB-Migration pixelfed.de2020-12-18T15:00:00+01:002020-12-18T15:00:00+01:00christag:cg.zknt.org,2020-12-18:/mini-post-mortem-fehlgeschlagene-db-migration-pixelfedde.html<p>Am Wochenende hatte ich - trotz automatischer Tests - ein fehlgeschlagenes Update von <a href="https://pixelfed.org">pixelfed</a>.</p>
<p>Der Fehler war mir neu, daher will ich kurz aufschreiben was der Grund war.</p>
<h2>Ausfall</h2>
<p>Bei Updates vom <a href="https://github.com/pixelfed/pixelfed">pixelfed-repo</a> baut mein Jenkins automatisch ein neues Docker-Image, pusht es nach <a href="https://hub.docker.com/r/zknt/pixelfed">zknt/pixelfed</a> und deployed mit dem frischen Image ein …</p><p>Am Wochenende hatte ich - trotz automatischer Tests - ein fehlgeschlagenes Update von <a href="https://pixelfed.org">pixelfed</a>.</p>
<p>Der Fehler war mir neu, daher will ich kurz aufschreiben was der Grund war.</p>
<h2>Ausfall</h2>
<p>Bei Updates vom <a href="https://github.com/pixelfed/pixelfed">pixelfed-repo</a> baut mein Jenkins automatisch ein neues Docker-Image, pusht es nach <a href="https://hub.docker.com/r/zknt/pixelfed">zknt/pixelfed</a> und deployed mit dem frischen Image ein Staging-System.
Dort laufen eine Reihe (äußerst oberflächlicher) Selenium-Tests, sind diese OK wird die Image-ID aufs Produktiv-System geschoben und pixelfed.de dort aktualisiert.</p>
<p>Das funktioniert soweit ganz gut, bis auf letztes Wochenende, da meldete das Monitoring plötzlich einen Ausfall.</p>
<p>Ich schaute also kurz ins Log, fehlgeschlagene Datenbank-Migration, da ist nicht auf den ersten Blick sichtbar wo der Fehler liegt. Also erstmal Rollback auf das Vorgänger-Image, Dienst starten, läuft. Prima, weiter Wochenende.</p>
<h2>Fehlersuche</h2>
<p>Am Montagabend wollte ich dann nachschauen was eigentlich passiert ist.</p>
<p>Interessant war ja das der Fehler im Testsystem nicht aufgetreten ist, es also nicht bloß kaputte Software sein sollte.</p>
<p>Die Fehlermeldung war ja eindeutig:</p>
<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span>
<span class="normal">2</span>
<span class="normal">3</span>
<span class="normal">4</span>
<span class="normal">5</span>
<span class="normal">6</span></pre></div></td><td class="code"><div><pre><span></span><code>Migrating: 2020_12_13_203138_add_uuids_to_failed_jobs_table
In Connection.php line 678:
SQLSTATE[42S21]: Column already exists: 1060 Duplicate column name 'uuid' (
SQL: alter table `failed_jobs` add `uuid` varchar(191) null after `id`)
</code></pre></div></td></tr></table></div>
<p>Und die <a href="https://git.zknt.org/mirror/pixelfed/src/commit/8aa5d42d8bce50d76d014247cdeb86f2f570b77f/database/migrations/2020_12_13_203138_add_uuids_to_failed_jobs_table.php#L17">zugehörige Migration</a> ist auch sehr übersichtlich, es wird genau nur diese eine Spalte der Tabelle <code>failed_jobs</code> zugefügt:</p>
<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span>
<span class="normal">2</span>
<span class="normal">3</span>
<span class="normal">4</span>
<span class="normal">5</span>
<span class="normal">6</span></pre></div></td><td class="code"><div><pre><span></span><code>public function up()
{
Schema::table('failed_jobs', function (Blueprint $table) {
$table->string('uuid')->after('id')->nullable()->unique();
});
}
</code></pre></div></td></tr></table></div>
<p>In der Datenbank nachgeschaut ist die Spalte da:</p>
<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span>
<span class="normal">2</span>
<span class="normal">3</span>
<span class="normal">4</span>
<span class="normal">5</span>
<span class="normal">6</span></pre></div></td><td class="code"><div><pre><span></span><code>MariaDB [pixelfed]> describe failed_jobs;
+------------+---------------------+------+-----+---------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------------+------+-----+---------------------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| uuid | varchar(191) | YES | | NULL | |
</code></pre></div></td></tr></table></div>
<p>und anscheinend auch frisch angelegt, denn sie ist komplett leer:</p>
<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span>
<span class="normal">2</span></pre></div></td><td class="code"><div><pre><span></span><code>MariaDB [pixelfed]> select uuid from failed_jobs WHERE uuid != NULL;
Empty set (0.118 sec)
</code></pre></div></td></tr></table></div>
<p>Ich habe also zuerst die Migration händisch zurückgerollt indem ich die Spalte gelöscht habe. Später wird wichtig dass das Ewigkeiten (2:30 Minuten!) gedauert hat:</p>
<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span>
<span class="normal">2</span>
<span class="normal">3</span></pre></div></td><td class="code"><div><pre><span></span><code>MariaDB [pixelfed]> ALTER TABLE failed_jobs DROP COLUMN uuid;
Query OK, 0 rows affected (2 min 25.940 sec)
Records: 0 Duplicates: 0 Warnings: 0
</code></pre></div></td></tr></table></div>
<p>Vom App-Server aus die Migration erneut ausgeführt trat derselbe Fehler auf. Die Migration bricht ab, und beim nächsten Starten die bekannte Fehlermeldung: Die Spalte existiert schon.
Der SQL-Treiber quittiert den Abbruch übrigens mit "Server has gone away.".</p>
<p>Moment, server has gone away, das heisst die Verbindung wurde unterbrochen?
Und das Löschen der Spalte hat Minuten gedauert?</p>
<h2>Die Fehlkonfiguration</h2>
<p>Das ist es, und das ist auch der Unterschied zum Staging-System. Dort steckt die Datenbank in einem Docker-Container neben der Applikation.</p>
<p>Produktiv ist es ein Datenbank-Cluster, vor dem haproxy Last verteilt. Und das mit nahezu Standardwerten bei den Timeouts:</p>
<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span>
<span class="normal">2</span>
<span class="normal">3</span>
<span class="normal">4</span></pre></div></td><td class="code"><div><pre><span></span><code>defaults
.....
timeout client 50000
timeout server 50000
</code></pre></div></td></tr></table></div>
<p>Der Load Balancer hat also schlicht nach 50 Sekunden dicht gemacht. Das Timeout entsprechend hoch gesetzt, und schon läuft die Migration sauber durch.</p>
<p>Änderungen an der Tabelle sind übrigens so lahm da Laravel offensichtlich nie daraus löscht, dort sind alle fehlgeschlagenen Worker-Jobs seit zwei Jahren gespeichert, uns jeder Like/Post/Reply in der Föderation ist ein Job...</p>
<h2>Lessons learned</h2>
<ul>
<li>Ein Staging-System das sich vom Produktivsystem unterscheidet taugt nur bedingt, Überraschung!</li>
<li>Eine Datenbank kann auch mal langlaufende Queries abkriegen, noch eine Überraschung!</li>
</ul>Container Images ohne root bauen2019-06-23T13:37:00+02:002019-06-23T13:37:00+02:00christag:cg.zknt.org,2019-06-23:/container-images-ohne-root-bauen.html<p>Mit <a href="https://podman.io/">Podman</a> und <a href="https://buildah.io/">Buildah</a> existieren Tools um OCI Images und Container zu erstellen, verwalten und betreiben - ganz ähnlich zum bekannten Docker.
Die meiner Meinung nach größte Besonderheit: Die beiden funktionieren ohne Daemon, und auch ohne root-Rechte auf einem Linuxsystem.</p>
<p>Während podman meinen docker noch nicht ganz ersetzt (kein Unterstützung für …</p><p>Mit <a href="https://podman.io/">Podman</a> und <a href="https://buildah.io/">Buildah</a> existieren Tools um OCI Images und Container zu erstellen, verwalten und betreiben - ganz ähnlich zum bekannten Docker.
Die meiner Meinung nach größte Besonderheit: Die beiden funktionieren ohne Daemon, und auch ohne root-Rechte auf einem Linuxsystem.</p>
<p>Während podman meinen docker noch nicht ganz ersetzt (kein Unterstützung für/von docker-compose, da etwas anderer Fokus des Projekts) plane ich meine Build-Pipeline für Container-Images durch buildah zu ersetzen. Dadurch spare ich mir das Mounten des Docker-Sockets in den Container der Images bauen soll. :)</p>
<p>Ein erster Test Images im Container zu bauen lief wunderbar problemlos, ich schreibe hier auf was ich dafür getan habe.</p>
<h2>Buildah im Container</h2>
<p>Buildah kann fuse-overlayfs als Storage-Treiber nutzen, so dass auch die Verwaltung der Layer keine erweiterten Rechte braucht. Dazu muss allerdings das fuse-Device vom Host in den Container gemountet werden. Um die User-Namespaces nutzen zu können braucht es ausserdem mehr erlaubte Syscalls als in der Standardkonfiguration erlaubt.</p>
<p>Start des Containers also:</p>
<div class="highlight"><pre><span></span><code>$ podman run -ti --rm --security-opt <span class="nv">seccomp</span><span class="o">=</span>unconfined --device /dev/fuse fedora bash
</code></pre></div>
<p>Im Container Buildah installieren:</p>
<div class="highlight"><pre><span></span><code><span class="c1"># dnf install -y buildah</span>
</code></pre></div>
<p>Und Fuse-Overlay konfigurieren:</p>
<div class="highlight"><pre><span></span><code><span class="c1"># sed -i -e 's|#mount_program = "/usr/bin/fuse-overlayfs"|mount_program = "/usr/bin/fuse-overlayfs"|' /etc/containers/storage.conf</span>
</code></pre></div>
<p>Als Beispiel baue ich Alpine mit zusätzlichem VIM:</p>
<div class="highlight"><pre><span></span><code><span class="c1"># mkdir alpinewithvim</span>
<span class="c1"># cat > alpinewithvim/Dockerfile <<EOF</span>
FROM alpine
RUN apk add --no-cache vim
EOF
<span class="c1"># buildah bud --isolation chroot -t alpinewithvim alpinewithvim/</span>
</code></pre></div>
<p>Was sauber durch lief:</p>
<div class="highlight"><pre><span></span><code><span class="c1"># buildah images</span>
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/alpinewithvim latest 0ca4abc241e0 <span class="m">6</span> seconds ago <span class="m">36</span>.6 MB
docker.io/library/alpine latest 4d90542f0623 <span class="m">3</span> days ago <span class="m">5</span>.85 MB
</code></pre></div>
<p>Das gebaute Image lässt sich (genau wie bei Docker) in eine Registry schieben:</p>
<div class="highlight"><pre><span></span><code><span class="c1"># buildah tag localhost/alpinewithvim reg.zknt.org/chris/alpinewithvim</span>
<span class="c1"># buildah push --creds chris reg.zknt.org/chris/alpinewithvim</span>
</code></pre></div>
<p>Und damit kann das Image sonstwo geladen und als Container ausgeführt werden:</p>
<div class="highlight"><pre><span></span><code>$ docker run --rm -ti reg.zknt.org/chris/alpinewithvim vim --version
VIM - Vi IMproved <span class="m">8</span>.1 <span class="o">(</span><span class="m">2018</span> May <span class="m">18</span>, compiled Jun <span class="m">5</span> <span class="m">2019</span> <span class="m">08</span>:37:43<span class="o">)</span>
Included patches: <span class="m">1</span>-1365
Compiled by Alpine Linux
...
</code></pre></div>
<h2>Buildah-Image</h2>
<p>Als Image das automatisch ein Image baut und pusht sieht das ganze dann wie folgt aus.</p>
<p>Das Dockerfile:</p>
<div class="highlight"><pre><span></span><code><span class="k">FROM</span><span class="w"> </span><span class="s">fedora</span>
<span class="k">RUN</span><span class="w"> </span>dnf install -y buildah <span class="o">&&</span><span class="se">\</span>
sed -i -e <span class="s1">'s|#mount_program = "/usr/bin/fuse-overlayfs"|mount_program = "/usr/bin/fuse-overlayfs"|'</span> /etc/containers/storage.conf <span class="o">&&</span><span class="se">\</span>
mkdir /build
<span class="k">COPY</span><span class="w"> </span>entrypoint.sh /entrypoint.sh
<span class="k">ENTRYPOINT</span><span class="w"> </span>/entrypoint.sh
</code></pre></div>
<p>und das Entrypoint-Skript:</p>
<div class="highlight"><pre><span></span><code><span class="ch">#!/bin/bash</span>
buildah bud --isolation chroot -t <span class="si">${</span><span class="nv">BUILDAH_IMAGE_TAG</span><span class="si">}</span> /build
buildah images
buildah push --creds <span class="s2">"</span><span class="si">${</span><span class="nv">BUILDAH_USER</span><span class="si">}</span><span class="s2">:</span><span class="si">${</span><span class="nv">BUILDAH_PASSWORD</span><span class="si">}</span><span class="s2">"</span> <span class="si">${</span><span class="nv">BUILDAH_IMAGE_TAG</span><span class="si">}</span>
</code></pre></div>
<p>Mit diesem Image (als <code>buildah</code> getaggt) lassen sich jetzt (wie ich finde recht elegant) Images bauen und pushen:</p>
<div class="highlight"><pre><span></span><code>podman run --rm -t --security-opt <span class="nv">seccomp</span><span class="o">=</span>unconfined --device /dev/fuse -v <span class="k">$(</span><span class="nb">pwd</span><span class="k">)</span>/alpinewithvim:/build -e <span class="nv">BUILDAH_IMAGE_TAG</span><span class="o">=</span>reg.zknt.org/chris/alpinewithvim -e <span class="nv">BUILDAH_USER</span><span class="o">=</span>chris -e <span class="nv">BUILDAH_PASSWORD</span><span class="o">=</span>hunter2 buildah
</code></pre></div>
<p>In <code>./alpinewithvim</code> liegt das sinnfreie Dockerfile von weiter oben.</p>
<p>…jetzt das Ganze nur noch in Jenkins einbauen :)</p>"alprss" - alpine package RSS feed2018-12-11T22:00:00+01:002018-12-11T22:00:00+01:00christag:cg.zknt.org,2018-12-11:/alprss-alpine-package-rss-feed.html<p>Viele meiner Docker-Images basieren auf <a href="https://www.alpinelinux.org/">alpine linux</a>, mit ein bis ein paar zusätzlich installierten Packages.</p>
<p>Um zu sehen wann ich so ein Image mal neu bauen sollte, also wann es ein Package Update gab wollte ich einen RSS-Feed für die Packages haben, hab aber keinen gefunden.</p>
<p>Ich habe mir also …</p><p>Viele meiner Docker-Images basieren auf <a href="https://www.alpinelinux.org/">alpine linux</a>, mit ein bis ein paar zusätzlich installierten Packages.</p>
<p>Um zu sehen wann ich so ein Image mal neu bauen sollte, also wann es ein Package Update gab wollte ich einen RSS-Feed für die Packages haben, hab aber keinen gefunden.</p>
<p>Ich habe mir also selber einen "Adapter" von <a href="https://pkgs.alpinelinux.org/packages">alpines Package-Suche</a> auf RSS gebaut. Das Ganze ist unter https://alprss.zknt.org/ verfügbar, und sollte (hoffentlich) relativ zuverlässig laufen.</p>
<p>Um einen Feed für interessierende Packages zu erhalten einfach die Namen komma-getrennt hintereinander in die URL packen:</p>
<p><a href="https://alprss.zknt.org/rss/vim,emacs">https://alprss.zknt.org/rss/vim,emacs</a></p>
<p>Standardmäßig wird alpine stable (derzeit v3.8) auf x86_64 durchsucht, die Werte können aber auch angepasst werden:</p>
<p><a href="https://alprss.zknt.org/rss/edge/armv7/vim,emacs">https://alprss.zknt.org/rss/edge/armv7/vim,emacs</a></p>
<p>Vielleicht findet das ja noch jemand nützlich. :)</p>
<p>Update: Völlig undokumentierter Code: <a href="https://github.com/hnrd/alprss">https://github.com/hnrd/alprss</a></p>USB Stick für Ubiquity EdgeRouter (Lite, PoE) vorbereiten2018-08-13T08:00:00+02:002018-08-13T08:00:00+02:00christag:cg.zknt.org,2018-08-13:/usb-stick-fur-ubiquity-edgerouter-lite-poe-vorbereiten.html<p>Zum Wochenende funktionierte mein <a href="https://www.ubnt.com/edgemax/edgerouter-poe/">EdgeRouter POE</a> nicht mehr
richtig - laut Syslog Fehler im Dateisystem sowie im Block-Storage. Der Speicher war also hin.
Einige hilfreiche Leute auf <a href="https://chaos.social">Mastodon</a> wiesen mich darauf hin das die EdgeRouter
intern einen USB-Stick haben den man einfach tauschen kann. So weit, so großartig.</p>
<p>Einen passenden Stick …</p><p>Zum Wochenende funktionierte mein <a href="https://www.ubnt.com/edgemax/edgerouter-poe/">EdgeRouter POE</a> nicht mehr
richtig - laut Syslog Fehler im Dateisystem sowie im Block-Storage. Der Speicher war also hin.
Einige hilfreiche Leute auf <a href="https://chaos.social">Mastodon</a> wiesen mich darauf hin das die EdgeRouter
intern einen USB-Stick haben den man einfach tauschen kann. So weit, so großartig.</p>
<p>Einen passenden Stick hatte ich noch. Passend: Der Stick darf nicht nennenswerterweise größer sein
als der USB-Stecker selbst, denn die Buchse sitzt direkt auf der Platine. Die <a href="https://duckduckgo.com/?q=edgerouter+poe+fix&iar=images&iax=images&ia=images">Bildersuche der Wahl</a> hat Fotos auf denen man das recht gut erkennen kann.
Ausserdem sollen wohl keine USB3-Sticks funktionieren. Das habe ich nicht ausprobiert.</p>
<p>Nun muss auf den Stick natürlich die Firmware, bevor der Router sie von ihm laden kann.
Das geht per TFTP (ich hatte aber keine Lust einen Server aufzusetzen), alternativ gibt es ein
kleines Rescue-Image von dem man booten und die Firmware quasi curlen kann. Das ist etwas ungünstig
wenn der Router das einzige Gateway ist was grade zur Hand ist, denn Web ohne Internet ist etwas doof...</p>
<p>Dritte Alternative: Den Stick einfach am Rechner vorbereiten und die Firmware auspacken.</p>
<p>Wie ich das gemacht habe folgt hier, ich habe dazu quasi <a href="https://community.ubnt.com/t5/EdgeRouter/EdgeMax-rescue-kit-now-you-can-reinstall-EdgeOS-from-scratch/m-p/992989/highlight/true#M41084">dieses Script aus dem UBNT-Forum</a> per Hand ausgeführt.</p>
<p>Los geht es damit die Firmware zu besorgen. Zum Zeitpunkt dieses Posts ist die aktuelle Version
v1.10.5 und ist hier verfügbar: <a href="https://www.ubnt.com/download/edgemax/edgerouter-lite">https://www.ubnt.com/download/edgemax/edgerouter-lite</a></p>
<p>Danach den Stick partitioneren, ich gehe bei allen folgenden Befehlen davon aus das der Stick <em>/dev/sdd</em> ist - gegebenfalls bitte anpassen.</p>
<div class="highlight"><pre><span></span><code>parted --script /dev/sdd mktable msdos
parted --script /dev/sdd -- mkpart primary fat32 <span class="m">1</span> 150MB
mkfs.vfat /dev/sdd1
parted --script /dev/sdd -- mkpart primary ext3 150MB -1s
mkfs.ext3 -v /dev/sdd2
</code></pre></div>
<p>Jetzt einige temporäre Verzeichnise anlegen und die eben erstellten Partitionen mounten:</p>
<div class="highlight"><pre><span></span><code>mkdir -p /tmp/edge/boot /tmp/edge/root /tmp/edge/firmware
mount -t vfat /dev/sdd1 /tmp/edge/boot
mount -t ext3 /dev/sdd2 /tmp/edge/root
</code></pre></div>
<p>Und zu guter letzt die Firmware auspacken und die Teile an die richtigen Stellen
kopieren.</p>
<div class="highlight"><pre><span></span><code>tar xzf ER-e100.v1.10.5.5098915.tar -C /tmp/edge/firmware
cp /tmp/edge/firmware/vmlinux.tmp /tmp/edge/boot/vmlinux.64
cp /tmp/edge/firmware/vmlinux.tmp.md5 /tmp/edge/boot/vmlinux.md5
cp /tmp/edge/firmware/squashfs.tmp /tmp/edge/root/squashfs.img
cp /tmp/edge/firmware/squashfs.tmp.md5 /tmp/edge/root/squashfs.md5
cp /tmp/edge/firmware/version.tmp /tmp/edge/root/version
mkdir /tmp/edge/root/w
</code></pre></div>
<p>Und aufräumen</p>
<div class="highlight"><pre><span></span><code>umount /tmp/edge/boot
umount /tmp/edge/root
rm -rf /tmp/edge
</code></pre></div>
<p>Stick in den Router, zuschrauben, anschließen. Der Router kommt mit den Standard-Einstellungen (192.168.1.1/24 auf eth0, ubnt:ubnt im Webinterface)
hoch, über das System-Tab kann das letzte Backup zurückgespielt werden, fertig. :)</p>"Crosscompile" für Ubiquity EdgeRouter2018-05-07T08:00:00+02:002018-05-07T08:00:00+02:00christag:cg.zknt.org,2018-05-07:/crosscompile-fur-ubiquity-edgerouter.html<p>Seit ein paar Jahren nutze ich einen <a href="https://www.ubnt.com/edgemax/edgerouter-poe/">EdgeRouter POE</a> für
mein Netzwerk zuhause, das Gerät tut ziemlich großartig alles was ich möchte komma-aber:</p>
<p>Als unterste Schicht im Router werkelt ein Debian auf mips64, an das man einfach per SSH kommt und
Sachen nachrüsten kann. Das brauche ich auch, denn der …</p><p>Seit ein paar Jahren nutze ich einen <a href="https://www.ubnt.com/edgemax/edgerouter-poe/">EdgeRouter POE</a> für
mein Netzwerk zuhause, das Gerät tut ziemlich großartig alles was ich möchte komma-aber:</p>
<p>Als unterste Schicht im Router werkelt ein Debian auf mips64, an das man einfach per SSH kommt und
Sachen nachrüsten kann. Das brauche ich auch, denn der Router unterstützt nativ kein <a href="https://www.tinc-vpn.org">tinc VPN</a>.
Bisher nutzte ich tinc 1.0, welches einfach per apt nachgezogen werden konnte.
Jetzt stelle ich mein Netz aber auf tinc 1.1 um, welches in den Paketquellen nicht vorhanden ist.
Dazu kommt das der EdgeRouter leider immernoch auf Debian Wheezy fusst. Wheezy auf mips ist inzwischen
als EOL abgekündigt, was bedeutet das die Paketquellen langsam verschwinden (es gibt beispielsweise
bereits keine Security Updates mehr).
Deshalb lässt sich nicht mehr so leicht Software so nachinstallieren.</p>
<p>Ich habe deshalb vom PC aus "crosscompiled". Ich schreibe das immer in ", denn eigentlich emuliere
ich mir einen mips in Qemu und compile einfach dort. So habe ich keinen Ärger mit Dependencies und
ähnlichem.</p>
<p>Dazu braucht es zuerst die Qemu VM (nach <a href="https://community.ubnt.com/t5/EdgeRouter/Cross-Compiling-Tool-Chain/m-p/1628194/highlight/true#M120211">diesem Post</a>):</p>
<div class="highlight"><pre><span></span><code>wget https://people.debian.org/~aurel32/qemu/mips/debian_wheezy_mips_standard.qcow2
wget https://people.debian.org/~aurel32/qemu/mips/vmlinux-3.2.0-4-5kc-malta
qemu-img create -f qcow2 -o <span class="nv">backing_file</span><span class="o">=</span>debian_wheezy_mips_standard.qcow2 disk.qcow2
qemu-system-mips64 -M malta -kernel vmlinux-3.2.0-4-5kc-malta -hda disk.qcow2 -append <span class="s2">"root=/dev/sda1 console=ttyS0 mem=256m@0x0 mem=768m@0x90000000"</span> -nographic -m <span class="m">1024</span> -net nic,macaddr<span class="o">=</span><span class="m">52</span>:54:00:fa:ce:07,model<span class="o">=</span>virtio -net user,hostfwd<span class="o">=</span>tcp:127.0.0.1:2022-:22
ssh -p <span class="m">2022</span> root@localhost
<span class="c1">## password ist "root"</span>
apt-get install build-essential git autoconf libtool bison flex libapt-pkg-dev libboost-dev libperl-dev libboost-filesystem-dev libboost-system-dev libboost-thread-dev libpcre3-dev curl
</code></pre></div>
<p>Danach in der VM ein Verzeichnis in /opt als Ziel fürs ganze compilen anlegen, das Verzeichnis kommt
dann später rüber auf den Router. Im Falle von tinc:</p>
<div class="highlight"><pre><span></span><code>mkdir -p /opt/tinc
cat >/opt/tinc/config.site <span class="s"><<EOF</span>
<span class="s">CPPFLAGS=-I/opt/tinc/include</span>
<span class="s">LDFLAGS=-L/opt/tinc/lib</span>
<span class="s">EOF</span>
<span class="nb">export</span> <span class="nv">CPLUS_INCLUDE_PATH</span><span class="o">=</span>/opt/tinc/include:<span class="nv">$CPLUS_INCLUDE_PATH</span>
<span class="nb">export</span> <span class="nv">C_INCLUDE_PATH</span><span class="o">=</span>/opt/tinc/include:<span class="nv">$C_INCLUDE_PATH</span>
<span class="nb">export</span> <span class="nv">LD_LIBRARY_PATH</span><span class="o">=</span>/opt/tinc/lib:<span class="nv">$LD_LIBRARY_PATH</span>
</code></pre></div>
<p>Jetzt kann das übliche ./configure-make-makeinstall abgespult werden. Ich schreib es für tinc einmal runter:</p>
<h2>Dependencies</h2>
<div class="highlight"><pre><span></span><code>apt-get install libncurses5-dev libreadline6-dev zlib1g-dev liblzo2-dev libssl-dev
</code></pre></div>
<h2>tinc selbst:</h2>
<h3><a href="https://www.tinc-vpn.org/">tinc</a></h3>
<div class="highlight"><pre><span></span><code>curl -O https://www.tinc-vpn.org/packages/tinc-1.1pre15.tar.gz
tar xvzf tinc-1.1pre15.tar.gz
<span class="nb">cd</span> tinc-1.1pre15
./configure --prefix<span class="o">=</span>/opt/tinc --with-curses<span class="o">=</span>/opt/tinc
make<span class="p">;</span> make install
mkdir -p /opt/tinc/var/run
mkdir -p /opt/tinc/etc/tinc
</code></pre></div>
<p>Danach einfach <code>/opt/tinc</code> packen, auf den Router schieben, entpacken, fertig.</p>
<p>Das resultierende tgz habe ich hier hochgeladen: <a href="https://cg.zknt.org/files/tinc-mips-1.1pre15.tgz">tinc-mips-1.1pre15.tgz</a>.
Security-Software als Binary irgendwo runterladen ist natürlich keine gute Idee. ;)</p>
<p>Als letztes noch init: Die von tinc mitgelieferten service-files nutzen natürlich für
Debian oldoldstable recht wenig - aber das init script von anno dazumal das Debian mit
tinc 1.0 liefert funktioniert noch:</p>
<p><a href="https://cg.zknt.org/files/tinc.init">tinc.init</a> (hart auf /opt/tinc verdrahtet)</p>Hello world!2018-04-29T15:00:00+02:002018-04-29T15:00:00+02:00christag:cg.zknt.org,2018-04-29:/hello-world.html<p>Die letzte Iteration dieses Blogs lief mit <a href="https://ghost.org">Ghost</a>.
Das war zwar sehr schick, ist mir aber immer wieder kaputt gegangen.
Mag daran liegen das mir diese Seite so ungefähr alle paar Monate mal
wieder einfällt, und bis dahin einige Updates ins Land gezogen sind.</p>
<p>Durch den Serverumzug jetzt grade ist …</p><p>Die letzte Iteration dieses Blogs lief mit <a href="https://ghost.org">Ghost</a>.
Das war zwar sehr schick, ist mir aber immer wieder kaputt gegangen.
Mag daran liegen das mir diese Seite so ungefähr alle paar Monate mal
wieder einfällt, und bis dahin einige Updates ins Land gezogen sind.</p>
<p>Durch den Serverumzug jetzt grade ist es mir komplett kaputt gegangen,
ich habe es nicht ohne Mühe geschafft die alten Artikel zu importieren.
Also mache ich gleich alles neu, auf Basis von <a href="https://blog.getpelican.com/">Pelican</a>.
Ich hoffe das ist längerfristig stabil. :)</p>root on ZFS on LUKS (on arch)2016-10-03T09:08:00+02:002016-10-03T09:08:00+02:00christag:cg.zknt.org,2016-10-03:/root-on-zfs-on-luks-on-arch.html<p>Update 2018-05-22: Inzwischen gibt es über <a href="https://github.com/archzfs/archzfs">archzfs</a> fertige
Pakete und Tools, alles hier ist vermutlich obsolet.</p>
<hr>
<p>Mir fiel kürzlich ein Thinkpad T430 in die Hände. Da inzwischen natürlich
sämtliche verbaute Hardware in den gängigen Linuxen einfach[tm] funktioniert
muss ich halt mit Software basteln. Ausserdem fasst der Rechner zusätzlich zur …</p><p>Update 2018-05-22: Inzwischen gibt es über <a href="https://github.com/archzfs/archzfs">archzfs</a> fertige
Pakete und Tools, alles hier ist vermutlich obsolet.</p>
<hr>
<p>Mir fiel kürzlich ein Thinkpad T430 in die Hände. Da inzwischen natürlich
sämtliche verbaute Hardware in den gängigen Linuxen einfach[tm] funktioniert
muss ich halt mit Software basteln. Ausserdem fasst der Rechner zusätzlich zur
SSD noch große Mengen drehenden Rost, daher der Wunsch nach einem ordentlichen
Dateisystem.</p>
<p>Damit ich mich nächstes Jahr noch erinnere was ich da verbastelt habe, hier also
mein kleines Rezept:</p>
<h1>/ auf ZFS auf LUKS</h1>
<p>Im Grunde wird dieser Artikel ein Mix aus folgenden Ressourcen. Ein Blick lohnt
sich, vermutlich sind die Informationen dort ab nächster Woche aktueller als
hier:</p>
<ul>
<li><a href="https://wiki.archlinux.org/index.php/Installation_guide">Installation Guide – ArchWiki</a></li>
<li><a href="https://wiki.archlinux.org/index.php/ZFS">ZFS – ArchWiki</a></li>
<li><a href="https://wiki.archlinux.org/index.php/Installing_Arch_Linux_on_ZFS">Arch on ZFS – ArchWiki</a></li>
</ul>
<h2>Installationsmedium</h2>
<p>Überraschungsfrei: Image herunterladen (https://www.archlinux.org/download/),
auf USB-Stick schreiben, von diesem booten. Man könnte jetzt schon die
benötigten ZFS-Packages in ein eigenes Image bauen, ich machte das aber nachher
im gebooteten Live-System.
NB: Ich musste im BIOS einstellen das EFI-Boot vorgezogen wird, sonst wurde das
Live-System im legacy-modus gestartet.</p>
<h2>Live-System starten</h2>
<p>Nach dem Boot vom Stick geht es wie gewohnt weiter:</p>
<p>Keyboard-Layout:</p>
<div class="highlight"><pre><span></span><code><span class="c1"># loadkeys de-latin1</span><span class="w"></span>
</code></pre></div>
<p>WIFI per netctl (<a href="https://wiki.archlinux.org/index.php/Netctl">netctl – ArchWiki</a>):</p>
<div class="highlight"><pre><span></span><code><span class="p">#</span><span class="w"> </span><span class="n">cp</span><span class="w"> </span><span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">netctl</span><span class="o">/</span><span class="n">examples</span><span class="o">/</span><span class="n">wireless</span><span class="o">-</span><span class="n">wpa</span><span class="w"> </span><span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">netctl</span><span class="o">/</span><span class="n">wifi</span><span class="w"></span>
<span class="p">#</span><span class="w"> </span><span class="n">vim</span><span class="w"> </span><span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">netctl</span><span class="o">/</span><span class="n">wifi</span><span class="w"> </span><span class="p">#</span><span class="w"> </span><span class="n">Interface</span><span class="o">-</span><span class="n">Namen</span><span class="w"> </span><span class="n">nicht</span><span class="w"> </span><span class="n">vergessen</span><span class="o">!</span><span class="w"></span>
<span class="p">#</span><span class="w"> </span><span class="n">netctl</span><span class="w"> </span><span class="n">start</span><span class="w"> </span><span class="n">wifi</span><span class="w"></span>
</code></pre></div>
<p>Ich machte die Installation von einem anderen Rechner aus per SSH, also sshd
starten und ein Passwort für root setzen:</p>
<div class="highlight"><pre><span></span><code># systemctl start sshd.service
# passwd
</code></pre></div>
<p>Um den ganzen ZFS-Kram im Live-System installieren zu können braucht es etwas
mehr "Platten"platz:</p>
<div class="highlight"><pre><span></span><code># mount -o remount,size=2G /run/archiso/cowspace
</code></pre></div>
<h2>Partitionierung</h2>
<p>Der Rechner kam mit Windows 7 auf legacy-partitionierter SSD.
Aber das ist man ja schnell los:</p>
<div class="highlight"><pre><span></span><code>#<span class="w"> </span><span class="nv">dd</span><span class="w"> </span><span class="k">if</span><span class="o">=/</span><span class="nv">dev</span><span class="o">/</span><span class="nv">zero</span><span class="w"> </span><span class="nv">of</span><span class="o">=/</span><span class="nv">dev</span><span class="o">/</span><span class="nv">disk</span><span class="o">/</span><span class="nv">by</span><span class="o">-</span><span class="nv">id</span><span class="o">/</span><span class="nv">ata</span><span class="o">-</span><span class="nv">C400</span><span class="o">-</span><span class="nv">MTFDDAK128MAM_00000000122603410E06</span><span class="w"> </span><span class="nv">status</span><span class="o">=</span><span class="nv">progress</span><span class="w"></span>
</code></pre></div>
<p>(NB: Wer noch in /dev/sdX denkt, ls -al /dev/disk/by-id gibt die Übersetzung
dazu aus. Allzu viele Platten tummeln sich im Laptop ja auch üblicherweise
nicht...)</p>
<p>Danach frische Partitionen anlegen:</p>
<div class="highlight"><pre><span></span><code># gdisk /dev/disk/by-id/ata-C400-MTFDDAK128MAM_00000000122603410E06
</code></pre></div>
<p>Wurde nicht die ganze Disk genullt: Auf die Nachfrage ob eine neue GPT erstellt
werden soll ist die Antwort natürlich ja:</p>
<div class="highlight"><pre><span></span><code><span class="mf">1</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">Use</span><span class="w"> </span><span class="n">current</span><span class="w"> </span><span class="n">GPT</span><span class="w"></span>
<span class="mf">2</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">Create</span><span class="w"> </span><span class="n">blank</span><span class="w"> </span><span class="n">GPT</span><span class="w"></span>
<span class="n">Your</span><span class="w"> </span><span class="n">answer</span><span class="p">:</span><span class="w"> </span><span class="o">*</span><span class="mf">2</span><span class="o">*</span><span class="w"></span>
<span class="n">Command</span><span class="w"> </span><span class="p">(</span><span class="err">?</span><span class="w"> </span><span class="kr">for</span><span class="w"> </span><span class="n">help</span><span class="p">):</span><span class="w"></span>
</code></pre></div>
<p>Ich lege auf der SSD 3 Partitionen an, EFI+/boot, Swap, ZFS-root.</p>
<p>Zuerst also die EFI-System-Partition, mit 2 GB Platz mehr als zu groß (Details: <a href="https://wiki.archlinux.org/index.php/EFI_System_Partition">ESP – ArchWiki</a>):</p>
<div class="highlight"><pre><span></span><code>Command <span class="p">(</span><span class="o">?</span> for help<span class="p">):</span> n
Partition number <span class="p">(</span><span class="mi">1</span><span class="err">-</span><span class="mi">128</span><span class="p">,</span> default <span class="mi">1</span><span class="p">):</span> <span class="mi">1</span>
First sector <span class="p">(</span><span class="mi">34</span><span class="err">-</span><span class="mi">250069646</span><span class="p">,</span> <span class="ss">default =</span> <span class="mi">2048</span><span class="p">)</span> <span class="ow">or</span> <span class="p">{</span><span class="o">+</span><span class="err">-</span><span class="p">}</span>size<span class="p">{</span>KMGTP<span class="p">}:</span>
Last sector <span class="p">(</span><span class="mi">2048</span><span class="err">-</span><span class="mi">250069646</span><span class="p">,</span> <span class="ss">default =</span> <span class="mi">250069646</span><span class="p">)</span> <span class="ow">or</span> <span class="p">{</span><span class="o">+</span><span class="err">-</span><span class="p">}</span>size<span class="p">{</span>KMGTP<span class="p">}:</span> <span class="o">+</span><span class="mi">2</span>G
Current type is <span class="err">'</span>Linux filesystem'
Hex code <span class="ow">or</span> GUID <span class="p">(</span>L to show codes<span class="p">,</span> <span class="ss">Enter =</span> <span class="mi">8300</span><span class="p">):</span> EF00
Changed type of partition to <span class="err">'</span>EFI System'
</code></pre></div>
<p>Als nächstes swap, ausreichend groß für hibernate, and them some:</p>
<div class="highlight"><pre><span></span><code>Command <span class="p">(</span><span class="o">?</span> for help<span class="p">):</span> n
Partition number <span class="p">(</span><span class="mi">2</span><span class="err">-</span><span class="mi">128</span><span class="p">,</span> default <span class="mi">2</span><span class="p">):</span>
First sector <span class="p">(</span><span class="mi">34</span><span class="err">-</span><span class="mi">250069646</span><span class="p">,</span> <span class="ss">default =</span> <span class="mi">4196352</span><span class="p">)</span> <span class="ow">or</span> <span class="p">{</span><span class="o">+</span><span class="err">-</span><span class="p">}</span>size<span class="p">{</span>KMGTP<span class="p">}:</span>
Last sector <span class="p">(</span><span class="mi">4196352</span><span class="err">-</span><span class="mi">250069646</span><span class="p">,</span> <span class="ss">default =</span> <span class="mi">250069646</span><span class="p">)</span> <span class="ow">or</span> <span class="p">{</span><span class="o">+</span><span class="err">-</span><span class="p">}</span>size<span class="p">{</span>KMGTP<span class="p">}:</span> <span class="o">+</span><span class="mi">32</span>G
Current type is <span class="err">'</span>Linux filesystem'
Hex code <span class="ow">or</span> GUID <span class="p">(</span>L to show codes<span class="p">,</span> <span class="ss">Enter =</span> <span class="mi">8300</span><span class="p">):</span> <span class="mi">8200</span>
Changed type of partition to <span class="err">'</span>Linux swap'
</code></pre></div>
<p>Den Rest des Platzes bekommt die ZFS-Partition, der Typ ist im Prinzip egal. Ich
nehme bf00, Solaris root. Weil, warum nicht…</p>
<p>Und zuletzt das Dateisystem für die EFI System Partition:</p>
<div class="highlight"><pre><span></span><code># mkfs.fat -F32 /dev/disk/by-id/ata-C400-MTFDDAK128MAM_00000000122603410E06-part1
</code></pre></div>
<h2>Crypto</h2>
<p>Swap + ZFS-root werden vor der Dateisystem-Erzeugung verschlüsselt.</p>
<div class="highlight"><pre><span></span><code># cryptsetup luksFormat /dev/disk/by-id/ata-C400-MTFDDAK128MAM_00000000122603410E06-part2
# cryptsetup luksFormat /dev/disk/by-id/ata-C400-MTFDDAK128MAM_00000000122603410E06-part3
</code></pre></div>
<p>Und danach gleich wieder öffnen:</p>
<div class="highlight"><pre><span></span><code># cryptsetup luksOpen /dev/disk/by-id/ata-C400-MTFDDAK128MAM_00000000122603410E06-part2 crypt_swap
# cryptsetup luksOpen /dev/disk/by-id/ata-C400-MTFDDAK128MAM_00000000122603410E06-part3 crypt_zroot
</code></pre></div>
<p>Swapspace erstellen und einhängen:</p>
<div class="highlight"><pre><span></span><code># mkswap /dev/disk/by-id/dm-name-crypt_swap
# swapon /dev/disk/by-id/dm-name-crypt_swap
</code></pre></div>
<h2>Installation von ZFS im Live-System</h2>
<p>Um die ZFS-Module zu bauen braucht es ein wenig Vorbereitung.
Zuerst das build-system installieren:</p>
<div class="highlight"><pre><span></span><code># pacman -Sy base-devel git kernel-headers
</code></pre></div>
<p>Mein Archiso-Image war schon ein paar Tage alt, daher habe ich mir die passenden
Kernel-Header aus dem <a href="https://wiki.archlinux.org/index.php/Arch_Linux_Archive">Arch Linux Archive</a> holen müssen.</p>
<p>Build-User anlegen:</p>
<div class="highlight"><pre><span></span><code># useradd -m -s /bin/zsh build
# visudo # Da build kein Passwort hat bietet sich NOPASSWD an. ;)
</code></pre></div>
<p>Und dann mit diesem die Module holen und bauen:</p>
<div class="highlight"><pre><span></span><code>#<span class="w"> </span><span class="n">sudo</span><span class="w"> </span><span class="o">-</span><span class="nb">i</span><span class="w"> </span><span class="o">-</span><span class="n">u</span><span class="w"> </span><span class="n">build</span><span class="w"></span>
<span class="c">% curl -O https://aur.archlinux.org/cgit/aur.git/snapshot/spl-dkms.tar.gz</span><span class="w"></span>
<span class="c">% tar xzf spl-dkms.tar.gz</span><span class="w"></span>
<span class="c">% cd spl-dkms</span><span class="w"></span>
<span class="c">% makepkg -dri</span><span class="w"></span>
<span class="c">% cd ~</span><span class="w"></span>
<span class="c">% curl -O https://aur.archlinux.org/cgit/aur.git/snapshot/zfs-dkms.tar.gz</span><span class="w"></span>
<span class="c">% tar xzf zfs-dkms.tar.gz</span><span class="w"></span>
<span class="c">% cd zfs-dkms</span><span class="w"></span>
<span class="c">% makepkg -dri</span><span class="w"></span>
</code></pre></div>
<p>Nun kann ich ZFS laden und die Dateisysteme anlegen:</p>
<div class="highlight"><pre><span></span><code># modprobe zfs
# zpool create -f zroot /dev/disk/by-id/dm-name-crypt_zroot
# zfs create -o mountpoint=none zroot/data
# zfs create -o mountpoint=none zroot/ROOT
# zfs create -o compression=lz4 -o mountpoint=/ zroot/ROOT/default
# zfs create -o compression=lz4 -o mountpoint=/home zroot/data/home
</code></pre></div>
<p>Die dabei ausgegebenen Fehlermeldungen das die Dateisysteme nicht gemountet
werden können sind zu erwarten. Es läuft ja noch das Livesystem...</p>
<p>Bootflag für das root-System:</p>
<div class="highlight"><pre><span></span><code># zpool set bootfs=zroot/ROOT/default zroot
</code></pre></div>
<p>Und den Pool unter /mnt für die Installation reimportieren:</p>
<div class="highlight"><pre><span></span><code><span class="c1"># zpool export zroot</span>
<span class="c1"># zpool import -d /dev/disk/by-id -R /mnt zroot</span>
</code></pre></div>
<p>Und das Cache-File ins Zielsystem kopieren:</p>
<div class="highlight"><pre><span></span><code># mkdir -p /mnt/etc/zfs
# cp /etc/zfs/zpool.cache /mnt/etc/zfs/zpool.cache
</code></pre></div>
<p>Weiter geht es quasi mit der normalen Installationsprozedur:</p>
<div class="highlight"><pre><span></span><code># mkdir /mnt/boot
# mount /dev/disk/by-id/ata-C400-MTFDDAK128MAM_00000000122603410E06-part1 /mnt/boot
# pacstrap /mnt base base-devel linux-headers git vim
</code></pre></div>
<p>Danach einmal die fstab generieren lassen, und gleich wieder ausleeren, da die
ZFS-Dateisysteme sowieso automatisch gemountet werden.</p>
<div class="highlight"><pre><span></span><code># genfstab -U /mnt >> /mnt/etc/fstab
# vim /mnt/etc/fstab
</code></pre></div>
<p>Übrig bleiben:</p>
<div class="highlight"><pre><span></span><code># cat /mnt/etc/fstab
zroot/ROOT/default / zfs rw,relatime,xattr,noacl0 0
UUID=C4E9-3C43 /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro 0 2
/dev/disk/by-id/dm-name-crypt_swap none swap defaults 0 0
</code></pre></div>
<p>Damit swap auch beim booten entschlüsselt wird:</p>
<div class="highlight"><pre><span></span><code># echo "crypt_swap /dev/disk/by-id/ata-C400-MTFDDAK128MAM_00000000122603410E06-part2 none luks" >> /mnt/etc/crypttab
</code></pre></div>
<p>Zeit für den Wechsel ins zu installierende System:</p>
<div class="highlight"><pre><span></span><code><span class="err">#</span><span class="w"> </span><span class="n">arch</span><span class="o">-</span><span class="n">chroot</span><span class="w"> </span><span class="o">/</span><span class="n">mnt</span><span class="w"></span>
<span class="o">[</span><span class="n">root@archiso /</span><span class="o">]</span><span class="err">#</span><span class="w"></span>
</code></pre></div>
<p>Und weiter mit der Installation nach Handbuch:</p>
<div class="highlight"><pre><span></span><code># vi /etc/locale.gen
# locale-gen
# ln -s /usr/share/zoneinfo/Europe/Berlin /etc/localtime
# hwclock --systohc --utc
# echo LANG=en_US.UTF-8 > /etc/locale.conf
# echo KEYMAP=de-latin1 > /etc/vconsole.conf
# echo euterpe.zknt.org > /etc/hostname
# echo 127.0.0.1 euterpe euterpe.zknt.org >> /etc/hosts
# pacman -S netctl wpa_supplicant # netctl-profil kann aus dem live-system kopiert werden
</code></pre></div>
<h2>(nochmal) ZFS installieren</h2>
<p>Im Zielsystem müssen auch nochmal die zfs-packages installiert werden, gleiche
Anleitung wie oben. Ob nochmal mit build-User oder ob gleich der richtige
Nutzeraccount angelegt wird ist Geschmackssache...</p>
<h2>initrd und bootloader</h2>
<p>Als letzte Schritte sind noch init und der bootloader zu konfigurieren.</p>
<p>Zuerst den Bootloader installieren:</p>
<div class="highlight"><pre><span></span><code><span class="c1"># bootctl --path=/boot install</span><span class="w"></span>
<span class="c1"># echo -e "default arch\ntimeout 3\neditor 1" > /boot/loader/loader.conf</span><span class="w"></span>
</code></pre></div>
<p>Wenn hinterher alles funktioniert kann editor hier auf 0 gesetzt werden. Erstmal
bleibt er zur Sicherheit an.</p>
<p>Und /boot/loader/entries/arch.conf entsprechend anpassen:</p>
<div class="highlight"><pre><span></span><code><span class="c1"># cat /boot/loader/entries/arch.conf</span><span class="w"></span>
<span class="n">title</span><span class="w"> </span><span class="n">Arch</span><span class="w"> </span><span class="n">Linux</span><span class="w"></span>
<span class="n">linux</span><span class="w"> </span><span class="o">/</span><span class="n">vmlinuz</span><span class="o">-</span><span class="n">linux</span><span class="w"></span>
<span class="n">initrd</span><span class="w"> </span><span class="o">/</span><span class="n">initramfs</span><span class="o">-</span><span class="n">linux</span><span class="o">.</span><span class="n">img</span><span class="w"></span>
<span class="n">options</span><span class="w"> </span><span class="n">cryptdevice</span><span class="o">=/</span><span class="n">dev</span><span class="o">/</span><span class="n">disk</span><span class="o">/</span><span class="n">by</span><span class="o">-</span><span class="n">id</span><span class="o">/</span><span class="n">ata</span><span class="o">-</span><span class="n">C400</span><span class="o">-</span><span class="n">MTFDDAK128MAM_00000000122603410E06</span><span class="o">-</span><span class="n">part3</span><span class="p">:</span><span class="n">crypt_zroot</span><span class="w"> </span><span class="n">zfs</span><span class="o">=</span><span class="w"></span>
<span class="n">bootfs</span><span class="w"> </span><span class="n">zfs</span><span class="o">=</span><span class="n">zroot</span><span class="w"> </span><span class="n">rw</span><span class="w"></span>
</code></pre></div>
<p>mkinitcpio.conf anpassen:</p>
<div class="highlight"><pre><span></span><code># vi /etc/mkinitcpio.conf
...
HOOKS="base udev autodetect modconf block encrypt zfs filesystems keyboard fsck"
...
# mkinitcpio -p linux
</code></pre></div>
<p>Zeit fürs Root-Passwort setzen und Neustart!</p>
<div class="highlight"><pre><span></span><code><span class="c1"># passwd</span><span class="w"></span>
<span class="c1"># exit</span><span class="w"></span>
<span class="c1"># umount /mnt/boot</span><span class="w"></span>
<span class="c1"># zfs umount -a</span><span class="w"></span>
<span class="c1"># zpool export zroot</span><span class="w"></span>
<span class="c1"># reboot</span><span class="w"></span>
</code></pre></div>
<p>Nach dem ersten Boot
# zpool set cachefile=/etc/zfs/zpool.cache zroot
# systemctl enable zfs.target</p>