
In diesem Artikel möchte ich Ihnen eine coole Technologie zeigen, die ich erfolgreich für Kubernetes verwende. Es kann sehr nützlich sein, um große Cluster zu erstellen.
Von nun an müssen Sie nicht mehr an die Installation des Betriebssystems und der separaten Pakete für jeden Knoten denken. Warum? Sie können dies alles automatisch über die Docker-Datei tun!
Die Tatsache, dass Sie Hunderte neuer Server kaufen, zu Ihrer Arbeitsumgebung hinzufügen und sie fast sofort einsatzbereit machen können, ist wirklich erstaunlich!
Fasziniert? Sprechen wir jetzt über alles in der richtigen Reihenfolge.
Zusammenfassung
Um loszulegen, müssen wir genau verstehen, wie diese Schaltung funktioniert.
Kurz gesagt, für alle Knoten bereiten wir ein einzelnes Image mit dem Betriebssystem, Docker, Kubelet und allem anderen vor.
Dieses System-Image wird zusammen mit dem Kernel automatisch von CI mithilfe der Docker-Datei erstellt.
Endknoten laden das Betriebssystem und den Kernel von diesem Image direkt über das Netzwerk.
Knoten verwenden Overlays als Root-Dateisystem, sodass bei einem Neustart alle Änderungen verloren gehen (ebenso wie bei Docker-Containern).
Es gibt eine Hauptkonfiguration, in der Sie die Mountpunkte und einige Befehle beschreiben können, die ausgeführt werden sollen, wenn der Knoten geladen wird (z. B. ein Befehl zum Hinzufügen eines kubeadm join
Schlüssels und eines kubeadm join
).
Bildvorbereitungsprozess
Wir werden das LTSP-Projekt verwenden, da es uns alles bietet, was wir zum Organisieren des Netzwerkstarts benötigen.
Im Allgemeinen ist LTSP ein Bündel von Shell-Skripten, die unser Leben viel einfacher machen.
Es enthält ein initramfs-Modul, mehrere Hilfsskripte und eine Art Konfigurationssystem, das das System in einem frühen Stadium des Ladens vorbereitet, noch bevor init aufgerufen wird.
So sieht die Bildvorbereitung aus:
- Stellen Sie das Basissystem in einer Chroot-Umgebung bereit.
- Wir nehmen die notwendigen Änderungen vor, installieren Software.
- Führen Sie den
ltsp-build-image
Gleich danach erhalten Sie ein komprimiertes Bild von dieser Chroot mit der gesamten darin installierten Software.
Jeder Knoten lädt dieses Image beim Booten herunter und verwendet es als rootfs.
Zum Aktualisieren starten Sie einfach den Knoten neu. Das neue Image wird heruntergeladen und für rootfs verwendet.
Serverkomponenten
Der Serverteil von LTSP enthält in unserem Fall nur zwei Komponenten:
- TFTP-Server - TFTP ist ein Initialisierungsprotokoll, mit dem der Kernel, initramfs und die Hauptkonfiguration - lts.conf geladen werden.
- NBD-Server - NBD-Protokoll, mit dem Clients ein komprimiertes Rootfs-Image bereitgestellt werden. Dies ist die schnellste Methode, kann jedoch auf Wunsch durch NFS oder AoE ersetzt werden.
Sie müssen auch haben:
- DHCP-Server - Er verteilt die IP-Konfiguration und mehrere zusätzliche Optionen, die unsere Clients benötigen, damit sie von unserem LTSP-Server booten können.
Knotenladevorgang
Beschreibung des Knotenladevorgangs
- Zunächst fordert der Knoten die DHCP-IP-Adresse und die Optionen für den
filename
next-server
. - Dann übernimmt der Knoten die Einstellungen und lädt den Bootloader (pxelinux oder grub) herunter.
- Der Bootloader lädt die Konfiguration herunter und lädt sie mit dem Kernel und initramfs.
- Anschließend werden der Kernel und initramfs mit bestimmten Optionen geladen, die für den Kernel angegeben wurden.
- Beim Booten verarbeiten die initramfs-Module die Parameter von cmdline und führen einige Aktionen aus, z. B. das Anschließen eines nbd-Geräts, das Vorbereiten von Overlay-Rootfs usw.
- Danach wird anstelle des üblichen init ein spezielles ltsp-init aufgerufen.
- Die ltsp-init-Skripte bereiten das System frühzeitig vor, bevor der Hauptinit aufgerufen wird. Grundsätzlich werden hier die Optionen aus lts.conf (der Hauptkonfigurationsdatei) verwendet: Hiermit werden Datensätze in fstab und rc.local usw. aktualisiert.
- Anschließend wird der Hauptinit (systemd) aufgerufen, der das bereits konfigurierte System wie gewohnt lädt, gemeinsam genutzte Ressourcen von fstab bereitstellt, Ziele und Dienste startet und Befehle von rc.local ausführt.
- Als Ergebnis erhalten wir ein vollständig konfiguriertes und geladenes System, das für weitere Aktionen bereit ist.
Servervorbereitung
Wie gesagt, ich bereite den LTSP-Server mit dem gequetschten Bild automatisch mithilfe der Docker-Datei vor. Diese Methode ist nicht schlecht, da alle Schritte zum Erstellen in Ihrem Git-Repository beschrieben werden können. Sie können Versionen steuern, Tags verwenden, CI anwenden und alles, was Sie für die Vorbereitung üblicher Docker-Projekte verwenden würden.
Auf der anderen Seite können Sie den LTSP-Server manuell bereitstellen, indem Sie alle Schritte manuell ausführen. Dies kann für Schulungszwecke und zum Verständnis der Grundprinzipien hilfreich sein.
Führen Sie die im Artikel aufgeführten Befehle manuell aus, wenn Sie LTSP nur ohne Docker-Datei ausprobieren möchten.
Liste der verwendeten Patches
Im Moment hat LTSP einige Mängel, und die Autoren des Projekts sind nicht sehr bereit, Korrekturen zu akzeptieren. Glücklicherweise ist LTSP leicht anpassbar, so dass ich einige Patches für mich vorbereitet habe, die ich hier geben werde.
Vielleicht werde ich eines Tages in der Gabel reifen, wenn die Community meine Entscheidung herzlich akzeptiert.
- feature-grub.diff
Standardmäßig unterstützt LTSP EFI nicht, daher habe ich einen Patch vorbereitet, der GRUB2 mit EFI-Unterstützung hinzufügt. - feature_preinit.diff
Dieser Patch fügt der Option lts.conf die Option PREINIT hinzu, mit der Sie beliebige Befehle ausführen können, bevor Sie den Hauptinit aufrufen. Dies kann nützlich sein, um Systemeinheiten und Netzwerkeinstellungen zu ändern. Es ist bemerkenswert, dass alle Variablen aus der Boot-Umgebung gespeichert werden und Sie sie in Ihren Skripten verwenden können, die über diese Option aufgerufen werden. - feature_initramfs_params_from_lts_conf.diff
Behebt das Problem mit der deaktivierten Option NBD_TO_RAM. Nach diesem Patch können Sie es in lts.conf in chroot angeben. (nicht die im tftp-Verzeichnis) - nbd-server-wrapper.sh
Dies ist kein Patch, sondern nur ein Shell-Skript, mit dem Sie den nbd-Server im Vordergrund ausführen können. Sie benötigen ihn, wenn Sie den nbd-Server im Docker-Container ausführen möchten.
Dockerfile-Stufen
Wir werden Stage Building in unserer Docker-Datei verwenden, um nur die erforderlichen Teile unseres Docker-Images zu speichern. Die verbleibenden nicht verwendeten Teile werden vom endgültigen Image ausgeschlossen.
ltsp-base ( ltsp ) | |---basesystem | ( chroot- ) | | | |---builder | | ( , ) | | | '---ltsp-image | ( , docker, kubelet squashed ) | '---final-stage ( squashed , initramfs stage)
Stufe 1: ltsp-Basis
OK, fangen wir an, dies ist der erste Teil unserer Docker-Datei:
FROM ubuntu:16.04 as ltsp-base ADD nbd-server-wrapper.sh /bin/ ADD /patches/feature-grub.diff /patches/feature-grub.diff RUN apt-get -y update \ && apt-get -y install \ ltsp-server \ tftpd-hpa \ nbd-server \ grub-common \ grub-pc-bin \ grub-efi-amd64-bin \ curl \ patch \ && sed -i 's|in_target mount|in_target_nofail mount|' \ /usr/share/debootstrap/functions \
In unserem Docker-Image ist derzeit bereits Folgendes installiert:
- NBD-Server
- TFTP-Server
- LTSP-Skripte mit Unterstützung für Grub-Bootloader (für EFI)
Stufe 2: Basissystem
In dieser Phase bereiten wir die Chroot-Umgebung mit dem Basissystem vor und installieren die Hauptsoftware mit dem Kernel.
Wir werden den üblichen Debootstrap anstelle von ltsp-build-client verwenden , um das Image vorzubereiten, da ltsp-build-client die GUI und einige andere unnötige Dinge installiert, die für die Bereitstellung von Servern offensichtlich nicht nützlich sind.
FROM ltsp-base as basesystem ARG DEBIAN_FRONTEND=noninteractive
Bitte beachten Sie, dass bei einigen Paketen, wie z. B. lvm2, Probleme auftreten können. Sie sind nicht vollständig für die Installation in einer nicht privilegierten Chroot optimiert. Ihre Skripte nach der Installation versuchen, privilegierte Befehle aufzurufen, die möglicherweise fehlschlagen, und die Installation des gesamten Pakets zu blockieren.
Lösung:
- Einige können problemlos installiert werden, wenn Sie sie vor der Installation des Kernels installieren (z. B. lvm2).
- Bei einigen von ihnen müssen Sie diese Problemumgehung verwenden, um ohne Nachinstallationsskript zu installieren.
Stufe 3: Erbauer
In dieser Phase können wir alle erforderlichen Software- und Kernelmodule aus den Quellen sammeln. Es ist sehr cool, dass dies in dieser Phase im vollautomatischen Modus möglich ist.
Überspringen Sie diesen Schritt, wenn Sie nichts von den Künstlern sammeln müssen.
Ich werde ein Beispiel für die Installation der neuesten Version des MLNX_EN-Treibers geben:
FROM basesystem as builder
Stufe 4: ltsp-Bild
In dieser Phase werden wir feststellen, was wir im vorherigen Schritt gesammelt haben:
FROM basesystem as ltsp-image
Jetzt nehmen wir zusätzliche Änderungen vor, um unser LTSP-Image zu vervollständigen:
Machen Sie jetzt ein quadratisches Bild von unserer Chroot:
Stufe 5: Endstufe
In der letzten Phase speichern wir nur unser gequetschtes Image und unseren Kernel mit initramfs
FROM ltsp-base COPY --from=ltsp-image /opt/ltsp/images /opt/ltsp/images COPY --from=ltsp-image /etc/nbd-server/conf.d /etc/nbd-server/conf.d COPY --from=ltsp-image /var/lib/tftpboot /var/lib/tftpboot
Ok, jetzt haben wir ein Docker-Image, das Folgendes enthält:
- TFTP-Server
- NBD-Server
- konfigurierter Bootloader
- Kernel mit initramfs
- gequetschtes rootfs Bild
Verwenden Sie
OK, jetzt, da unser Docker-Image mit dem LTSP-Server, dem Kernel, den Initramfs und den Squashed Rootfs vollständig fertig ist, können wir die Bereitstellung damit ausführen.
Wir können dies wie gewohnt tun, aber es gibt noch ein anderes Problem, das wir lösen müssen.
Leider können wir den üblichen Kubernetes-Dienst nicht für unsere Bereitstellung verwenden, da die Knoten zum Startzeitpunkt nicht Teil des Kubernetes-Clusters sind und externalIP verwenden müssen. Kubernetes verwendet jedoch immer NAT für externalIP und es gibt derzeit keine Möglichkeit, dieses Verhalten zu ändern.
Ich kenne zwei Möglichkeiten, um dies zu vermeiden: Verwenden Sie hostNetwork: true
oder verwenden Sie hostNetwork: true
Die zweite Option bietet uns auch Fehlertoleranz Im Fehlerfall wird die IP-Adresse mit dem Container auf einen anderen Knoten verschoben. Leider sind Rohrleitungen keine native und weniger sichere Methode.
Wenn Sie eine geeignetere Lösung kennen, teilen Sie uns dies bitte mit.
Hier ist eine Beispielbereitstellung mit hostNetwork:
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: ltsp-server labels: app: ltsp-server spec: selector: matchLabels: name: ltsp-server replicas: 1 template: metadata: labels: name: ltsp-server spec: hostNetwork: true containers: - name: tftpd image: registry.example.org/example/ltsp:latest command: [ "/usr/sbin/in.tftpd", "-L", "-u", "tftp", "-a", ":69", "-s", "/var/lib/tftpboot" ] lifecycle: postStart: exec: command: ["/bin/sh", "-c", "cd /var/lib/tftpboot/ltsp/amd64; ln -sf config/lts.conf ." ] volumeMounts: - name: config mountPath: "/var/lib/tftpboot/ltsp/amd64/config" - name: nbd-server image: registry.example.org/example/ltsp:latest command: [ "/bin/nbd-server-wrapper.sh" ] volumes: - name: config configMap: name: ltsp-config
Wie Sie vielleicht bemerkt haben, wird hier auch configmap mit der Datei lts.conf verwendet.
Als Beispiel werde ich einen Teil meiner Konfiguration angeben:
apiVersion: v1 kind: ConfigMap metadata: name: ltsp-config data: lts.conf: | [default] KEEP_SYSTEM_SERVICES = "ssh ureadahead dbus-org.freedesktop.login1 systemd-logind polkitd cgmanager ufw rpcbind nfs-kernel-server" PREINIT_00_TIME = "ln -sf /usr/share/zoneinfo/Europe/Prague /etc/localtime" PREINIT_01_FIX_HOSTNAME = "sed -i '/^127.0.0.2/d' /etc/hosts" PREINIT_02_DOCKER_OPTIONS = "sed -i 's|^ExecStart=.*|ExecStart=/usr/bin/dockerd -H fd:// --storage-driver overlay2 --iptables=false --ip-masq=false --log-driver=json-file --log-opt=max-size=10m --log-opt=max-file=5|' /etc/systemd/system/docker.service" FSTAB_01_SSH = "/dev/data/ssh /etc/ssh ext4 nofail,noatime,nodiratime 0 0" FSTAB_02_JOURNALD = "/dev/data/journal /var/log/journal ext4 nofail,noatime,nodiratime 0 0" FSTAB_03_DOCKER = "/dev/data/docker /var/lib/docker ext4 nofail,noatime,nodiratime 0 0" # Each command will stop script execution when fail RCFILE_01_SSH_SERVER = "cp /rofs/etc/ssh/*_config /etc/ssh; ssh-keygen -A" RCFILE_02_SSH_CLIENT = "mkdir -p /root/.ssh/; echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBSLYRaORL2znr1V4a3rjDn3HDHn2CsvUNK1nv8+CctoICtJOPXl6zQycI9KXNhANfJpc6iQG1ZPZUR74IiNhNIKvOpnNRPyLZ5opm01MVIDIZgi9g0DUks1g5gLV5LKzED8xYKMBmAfXMxh/nsP9KEvxGvTJB3OD+/bBxpliTl5xY3Eu41+VmZqVOz3Yl98+X8cZTgqx2dmsHUk7VKN9OZuCjIZL9MtJCZyOSRbjuo4HFEssotR1mvANyz+BUXkjqv2pEa0I2vGQPk1VDul5TpzGaN3nOfu83URZLJgCrX+8whS1fzMepUYrbEuIWq95esjn0gR6G4J7qlxyguAb9 admin@kubernetes' >> /root/.ssh/authorized_keys" RCFILE_03_KERNEL_DEBUG = "sysctl -w kernel.unknown_nmi_panic=1 kernel.softlockup_panic=1; modprobe netconsole netconsole=@/vmbr0,@10.9.0.15/" RCFILE_04_SYSCTL = "sysctl -w fs.file-max=20000000 fs.nr_open=20000000 net.ipv4.neigh.default.gc_thresh1=80000 net.ipv4.neigh.default.gc_thresh2=90000 net.ipv4.neigh.default.gc_thresh3=100000" RCFILE_05_FORWARD = "echo 1 > /proc/sys/net/ipv4/ip_forward" RCFILE_06_MODULES = "modprobe br_netfilter" RCFILE_07_JOIN_K8S = "kubeadm join --token 2a4576.504356e45fa3d365 10.9.0.20:6443 --discovery-token-ca-cert-hash sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBSLYRaORL2znr1V4a3rjDn3HDHn2CsvUNK1nv8 + CctoICtJOPXl6zQycI9KXNhANfJpc6iQG1ZPZUR74IiNhNIKvOpnNRPyLZ5opm01MVIDIZgi9g0DUks1g5gLV5LKzED8xYKMBmAfXMxh / nsP9KEvxGvTJB3OD + / bBxpliTl5xY3Eu41 + VmZqVOz3Yl98 + X8cZTgqx2dmsHUk7VKN9OZuCjIZL9MtJCZyOSRbjuo4HFEssotR1mvANyz + BUXkjqv2pEa0I2vGQPk1VDul5TpzGaN3nOfu83URZLJgCrX + 8whS1fzMepUYrbEuIWq95esjn0gR6G4J7qlxyguAb9 admin @ Kubernetes' >> /root/.ssh/authorized_keys" apiVersion: v1 kind: ConfigMap metadata: name: ltsp-config data: lts.conf: | [default] KEEP_SYSTEM_SERVICES = "ssh ureadahead dbus-org.freedesktop.login1 systemd-logind polkitd cgmanager ufw rpcbind nfs-kernel-server" PREINIT_00_TIME = "ln -sf /usr/share/zoneinfo/Europe/Prague /etc/localtime" PREINIT_01_FIX_HOSTNAME = "sed -i '/^127.0.0.2/d' /etc/hosts" PREINIT_02_DOCKER_OPTIONS = "sed -i 's|^ExecStart=.*|ExecStart=/usr/bin/dockerd -H fd:// --storage-driver overlay2 --iptables=false --ip-masq=false --log-driver=json-file --log-opt=max-size=10m --log-opt=max-file=5|' /etc/systemd/system/docker.service" FSTAB_01_SSH = "/dev/data/ssh /etc/ssh ext4 nofail,noatime,nodiratime 0 0" FSTAB_02_JOURNALD = "/dev/data/journal /var/log/journal ext4 nofail,noatime,nodiratime 0 0" FSTAB_03_DOCKER = "/dev/data/docker /var/lib/docker ext4 nofail,noatime,nodiratime 0 0" # Each command will stop script execution when fail RCFILE_01_SSH_SERVER = "cp /rofs/etc/ssh/*_config /etc/ssh; ssh-keygen -A" RCFILE_02_SSH_CLIENT = "mkdir -p /root/.ssh/; echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBSLYRaORL2znr1V4a3rjDn3HDHn2CsvUNK1nv8+CctoICtJOPXl6zQycI9KXNhANfJpc6iQG1ZPZUR74IiNhNIKvOpnNRPyLZ5opm01MVIDIZgi9g0DUks1g5gLV5LKzED8xYKMBmAfXMxh/nsP9KEvxGvTJB3OD+/bBxpliTl5xY3Eu41+VmZqVOz3Yl98+X8cZTgqx2dmsHUk7VKN9OZuCjIZL9MtJCZyOSRbjuo4HFEssotR1mvANyz+BUXkjqv2pEa0I2vGQPk1VDul5TpzGaN3nOfu83URZLJgCrX+8whS1fzMepUYrbEuIWq95esjn0gR6G4J7qlxyguAb9 admin@kubernetes' >> /root/.ssh/authorized_keys" RCFILE_03_KERNEL_DEBUG = "sysctl -w kernel.unknown_nmi_panic=1 kernel.softlockup_panic=1; modprobe netconsole netconsole=@/vmbr0,@10.9.0.15/" RCFILE_04_SYSCTL = "sysctl -w fs.file-max=20000000 fs.nr_open=20000000 net.ipv4.neigh.default.gc_thresh1=80000 net.ipv4.neigh.default.gc_thresh2=90000 net.ipv4.neigh.default.gc_thresh3=100000" RCFILE_05_FORWARD = "echo 1 > /proc/sys/net/ipv4/ip_forward" RCFILE_06_MODULES = "modprobe br_netfilter" RCFILE_07_JOIN_K8S = "kubeadm join --token 2a4576.504356e45fa3d365 10.9.0.20:6443 --discovery-token-ca-cert-hash sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
- KEEP_SYSTEM_SERVICES - Während des Startvorgangs löscht LTSP automatisch einige Dienste. Diese Variable wird benötigt, um dieses Verhalten für die hier aufgeführten Dienste zu verhindern.
- PREINIT_ * - Die hier aufgeführten Befehle werden ausgeführt, bevor systemd ausgeführt wird (diese Funktion wurde durch den Patch feature_preinit.diff hinzugefügt).
- FSTAB_ * - Die hier aufgeführten Zeilen werden der Datei
/etc/fstab
hinzugefügt.
Möglicherweise stellen Sie fest, dass ich die Option nofail
. Sie führt zu folgendem Verhalten: Wenn der Abschnitt nicht vorhanden ist, wird der Download ohne Fehler fortgesetzt. - RCFILE_ * - Diese Befehle werden der Datei
rc.local
hinzugefügt, die beim Booten von systemd aufgerufen wird.
Hier lade ich die erforderlichen Kernelmodule, kubeadm join
einige sysctl-Einstellungen aus und kubeadm join
dann den Befehl kubeadm join
, der den Knoten zum kubernetes-Cluster hinzufügt.
Ausführlichere Informationen zu allen Variablen finden Sie auf der Handbuchseite lts.conf .
Jetzt können Sie Ihr DHCP konfigurieren. Im Wesentlichen müssen Sie dort die Optionen für den next-server
und den filename
angeben.
Ich verwende einen ISC-DHCP-Server. Ich werde ein Beispiel für dhcpd.conf
:
shared-network ltsp-netowrk { subnet 10.9.0.0 netmask 255.255.0.0 { authoritative; default-lease-time -1; max-lease-time -1; option domain-name "example.org"; option domain-name-servers 10.9.0.1; option routers 10.9.0.1; next-server ltsp-1; # write ltsp-server hostname here if option architecture = 00:07 { filename "/ltsp/amd64/grub/x86_64-efi/core.efi"; } else { filename "/ltsp/amd64/grub/i386-pc/core.0"; } range 10.9.200.0 10.9.250.254; }
Sie können damit beginnen, aber ich habe mehrere LTSP-Server und konfiguriere für jeden Knoten eine separate statische IP-Adresse und die erforderlichen Optionen mithilfe eines Ansible-Playbooks.
Versuchen Sie, Ihren ersten Knoten zu starten. Wenn alles korrekt ausgeführt wurde, wird ein geladenes System darauf installiert. Der Knoten wird auch dem Kubernetes-Cluster hinzugefügt.
Jetzt können Sie versuchen, Ihre eigenen Änderungen vorzunehmen.
Wenn Sie mehr benötigen, beachten Sie, dass LTSP sehr einfach an Ihre Bedürfnisse angepasst werden kann. Fühlen Sie sich frei, in die Quelle zu schauen, dort finden Sie viele Antworten.
Treten Sie unserem Telegrammkanal bei: @ltsp_ru .