Flatcar Container Linux und Docker Swarm (Teil 1)
In diesem Artikel möchte ich euch Flatcar Container Linux vorstellen. Für alle, die bisher noch nichts davon gehört haben, folgt zunächst eine kurze Einführung.
Flatcar Container Linux
Flatcar Container Linux ist ein minimalistisches, unveränderliches (immutable) Linux-Betriebssystem, das speziell für den sicheren und automatisierten Betrieb von Containern entwickelt wurde.
Flatcar ist als Fork des mittlerweile eingestellten CoreOS Container Linux entstanden und eignet sich besonders für den Einsatz in Kubernetes-Clustern sowie in Umgebungen mit hohen Sicherheitsanforderungen.
Durch seine minimale Angriffsfläche und die automatisierten Sicherheitsupdates lässt sich der administrative Aufwand im laufenden Betrieb erheblich reduzieren. Gleichzeitig sorgt das unveränderliche Systemdesign dafür, dass die Systeme konsistent und reproduzierbar bleiben.
Features
- Ignition - zur Provisionierung
- Immutable OS - unveränderliche Systembasis
- Kompakter Fußabdruck - kompakter Ressourcenverbrauch
- Atomare Updates - über Dual-Partitionen
- Automatisches Rollback - bei fehlgeschlagenen Updates
Ein weiterer Vorteil ist die standardisierte Installation. Die Server werden über eine Ignition-Konfiguration eingerichtet und erhalten dadurch eine einheitliche Grundkonfiguration. Dadurch gibt es nur wenige individuelle Abweichungen zwischen den einzelnen Systemen.
Das erleichtert sowohl die Wartung als auch den Austausch einzelner Server erheblich. Ein defekter Host kann beispielsweise ersetzt und mit derselben Konfiguration neu bereitgestellt werden, ohne das System manuell konfigurieren zu müssen.
Meine Testlabor-Umgebung
Das Ziel meines Testlabors ist es, einen funktionierenden Docker-Swarm-Cluster aufzubauen. Der Cluster besteht aus drei Servern, die jeweils die Manager-Rolle übernehmen.
Damit die im Cluster benötigten Daten redundant zur Verfügung stehen, verwende ich GlusterFS. Dafür erstelle ich auf den Hosts jeweils einen sogenannten Brick und binde diesen anschließend als gemeinsamen Daten-Pool ein.
Da ich meine Services möglichst hochverfügbar betreiben möchte, benötige ich zusätzlich eine virtuelle Cluster-IP. Diese IP-Adresse soll im Fehlerfall von einem der verbleibenden Hosts übernommen werden können.
Die Umsetzung dieser Hochverfügbarkeit erfolgt mit keepalived.
Die komplette Konfiguration meiner Testumgebung habe ich in einer Butane-Datei zusammengestellt. Butane wird dabei verwendet, um aus der deklarativen Konfiguration eine Ignition-Datei zu erzeugen.
Diese Ignition-Datei kann anschließend bei der Installation von Flatcar verwendet werden. Dadurch lässt sich die komplette Grundkonfiguration der Server automatisiert und reproduzierbar durchführen – ohne dass jeder Server nach der Installation manuell eingerichtet werden muss.
Anbei die von mir verwendete Datei:
variant: flatcar
version: 1.0.0
passwd:
users:
- name: core
ssh_authorized_keys:
- "DEIN SSHKEY" #SELBST-ANPASSEN
storage:
links:
- path: /etc/localtime
target: ../usr/share/zoneinfo/Europe/Vienna
disks:
- device: /dev/sda
wipe_table: false
partitions:
# ROOT-Partition (Flatcar System) auf 25 GB (25600 MiB) festlegen
- number: 9
label: ROOT
size_mib: 25600
resize: true
# Daten-Partition für den GlusterFS-Brick (Restlicher Speicherplatz)
- number: 10
label: data
filesystems:
- device: /dev/disk/by-partlabel/data
format: ext4
wipe_filesystem: false
label: DATA-BRICK
files:
- path: /etc/hostname
mode: 0644
contents:
inline: FLATCAR01 #SELBST-ANPASSEN
# Wartungsfenster für automatische Updates
- path: /etc/flatcar/update.conf
overwrite: true
mode: 0644
contents:
inline: |
REBOOT_STRATEGY=reboot
LOCKSMITHD_REBOOT_WINDOW_START=02:00
LOCKSMITHD_REBOOT_WINDOW_LENGTH=1h
# Statische IP-Adresse (ACHTUNG: Pro Server anpassen!)
- path: /etc/systemd/network/10-static.network
mode: 0644
contents:
inline: |
[Match]
Name=en* eth*
[Network]
Address=172.16.1.1/24 #SELBST-ANPASSEN
Gateway=172.16.1.254 #SELBST-ANPASSEN
DNS=172.16.1.254 #SELBST-ANPASSEN
# FUSE Kernel-Modul für GlusterFS Mounts beim Booten laden
- path: /etc/modules-load.d/fuse.conf
mode: 0644
contents:
inline: fuse
- path: /etc/keepalived/keepalived.conf
mode: 0644
contents:
inline: |
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100 #SELBST-ANPASSEN: 100 für Master, 90/80 für Backups
advert_int 1
authentication {
auth_type PASS
auth_pass DEINGEHEIMESPASSWORT #SELBST-ANPASSEN
}
virtual_ipaddress {
172.16.1.100 #SELBST-ANPASSEN: Deine Cluster-IP
}
}
systemd:
units:
# 1. ROOT-Dateisystem explizit auf die 25GB Partitionsgröße ausdehnen
- name: rootfs-resize.service
enabled: true
contents: |
[Unit]
Description=Resize ROOT Filesystem to match 25GB partition
After=local-fs.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/resize2fs /dev/disk/by-partlabel/ROOT
[Install]
WantedBy=multi-user.target
# 2. Docker aktivieren
- name: docker.service
enabled: true
# 3. Die ext4-Partition als lokalen "Brick" für GlusterFS mounten
- name: mnt-brick.mount
enabled: true
contents: |
[Unit]
Description=Mount Local Ext4 Brick for GlusterFS
Before=docker.service
[Mount]
What=/dev/disk/by-partlabel/data
Where=/mnt/brick
Type=ext4
[Install]
WantedBy=multi-user.target
# 4. Den GlusterFS Server-Daemon im Container starten
- name: glusterd.service
enabled: true
contents: |
[Unit]
Description=GlusterFS Server
Requires=docker.service mnt-brick.mount
After=docker.service mnt-brick.mount
[Service]
Restart=always
RestartSec=10
# Lokalen Brick-Ordner erstellen
ExecStartPre=/usr/bin/mkdir -p /mnt/brick/gv0
ExecStartPre=-/usr/bin/docker rm -f glusterd
# Start mit Named Volumes, ohne /dev Bind und im Vordergrund (-N)
ExecStart=/usr/bin/docker run --name glusterd \
--net=host \
--privileged \
-v glusterfs_lib:/var/lib/glusterd \
-v glusterfs_etc:/etc/glusterfs \
-v glusterfs_log:/var/log/glusterfs \
-v /mnt/brick:/mnt/brick \
gluster/gluster-centos:latest \
/usr/sbin/glusterd -N
[Install]
WantedBy=multi-user.target
# 5. Den verteilten Gluster-Speicher nach /media/DATA mounten
- name: mount-gluster-volume.service
enabled: true
contents: |
[Unit]
Description=Mount GlusterFS Volume to /media/DATA
Requires=glusterd.service
After=glusterd.service
[Service]
Restart=always
RestartSec=5
ExecStartPre=-/usr/bin/umount -l /media/DATA
ExecStartPre=/usr/bin/mkdir -p /media/DATA
# Container bleibt dauerhaft am Leben, damit der FUSE-Prozess nicht stirbt
ExecStart=/usr/bin/docker run --rm --name gluster-client --net=host --privileged \
-v /media:/media:shared \
gluster/gluster-centos:latest \
/bin/bash -c "mount -t glusterfs localhost:/dockervol /media/DATA && sleep infinity"
# Mount sauber trennen, wenn der Dienst beendet wird
ExecStop=-/usr/bin/umount -l /media/DATA
[Install]
WantedBy=multi-user.target
# 6. Installation für Cluster IP
- name: keepalived.service
enabled: true
contents: |
[Unit]
Description=Keepalived Cluster VIP
After=docker.service
Requires=docker.service
[Service]
TimeoutStartSec=0
Restart=always
ExecStartPre=-/usr/bin/docker stop keepalived
ExecStartPre=-/usr/bin/docker rm keepalived
ExecStartPre=/usr/bin/docker pull osixia/keepalived:latest
ExecStart=/usr/bin/docker run --name keepalived --rm \
--net=host \
--cap-add=NET_ADMIN \
--cap-add=NET_BROADCAST \
--cap-add=NET_RAW \
-v /etc/keepalived/keepalived.conf:/etc/keepalived/keepalived.conf:ro \
osixia/keepalived:latest
ExecStop=/usr/bin/docker stop keepalived
[Install]
WantedBy=multi-user.target
# 7. Docker Swarm Verfügbarkeit beim Booten und Rebooten steuern
- name: docker-swarm-availability.service
enabled: true
contents: |
[Unit]
Description=Docker Swarm Node Availability Manager
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=true
# Beim HOCHFAHREN: Node wieder aktiv schalten
ExecStart=/usr/bin/docker node update --availability active %%H
# Beim HERUNTERFAHREN: Node evakuieren (Draining)
ExecStop=/usr/bin/docker node update --availability drain %%H
[Install]
WantedBy=multi-user.target
Die Datei kann anschließend mit folgendem Befehl umgewandelt werden.
docker run --rm -i quay.io/coreos/butane:release --pretty --strict < FILENAME.bu > FILENAME.ign
Die Datei dann z.B. auf einem Webserver ablegen und anschließend wie folgt in Flatcar aufrufen
wget https://pfadzudeinemfile.ign
sudo install-flatcar -d /dev/sda -i filename.ign
GlusterFS in Betrieb nehmen
Nachdem meine drei Hosts in Betrieb genommen waren habe ich GlusterFS mit den folgenden Befehlen auf dem Primären Host aktiviert.
Prüfen ob die anderen Server erreichbar sind und GlusterFS läuft:
docker exec -it glusterd gluster peer probe 172.16.1.2
docker exec -it glusterd gluster peer probe 172.16.1.3
GlusterFS Volume erstellen und starten
docker exec -it glusterd gluster volume create dockervol replica 3 \
172.16.1.1:/mnt/brick/gv0 \
172.16.1.2:/mnt/brick/gv0 \
172.16.1.3:/mnt/brick/gv0 force
docker exec -it glusterd gluster volume start dockervol
GlusterFS Status prüfen
docker exec -it glusterd gluster peer status
Auf allen Hosts ausführen
sudo systemctl restart mount-gluster-volume.service
Anschließend alle Hosts neu starten. Wer möchte kann dann nach dem Neustart optional noch die Berechtigungen für core setzen.
sudo chown core:core /media/DATA -R
Fazit
Nach all diesen Konfigurationen und Befehlen verfüge ich jetzt über drei Flatcar Hosts, welche alle Daten aus dem Verzeichnis "/media/DATA", untereinander replizieren. Ob alles Synchron ist kann mittels folgendem Befehl geprüft werden.
docker exec -it glusterd gluster volume heal dockervol info summary
Hiermit endet Teil 1 meines Beitrags. Im nächsten Beitrag widme ich mich dann dem Thema Docker Swarm. Wie wird da Ganze installiert, konfiguriert und verwalltet. Funktionieren die Failover Tests und bleiben die Services aktiv?