
Dans cet article, je voudrais vous montrer une technologie cool, je l'ai utilisée avec succès pour Kubernetes. Il peut être très utile pour créer de grands clusters.
Désormais, vous n'avez plus à penser à installer le système d'exploitation et des packages séparés pour chaque nœud. Pourquoi? Vous pouvez faire tout cela automatiquement via le Dockerfile!
Le fait que vous puissiez acheter des centaines de nouveaux serveurs, les ajouter à votre environnement de travail et les préparer presque immédiatement à l'utilisation est vraiment incroyable!
Intrigué? Parlons maintenant de tout dans l'ordre.
Résumé
Pour commencer, nous devons comprendre exactement comment ce circuit fonctionne.
En bref, pour tous les nœuds, nous préparons une seule image avec le système d'exploitation, Docker, Kubelet et tout le reste.
Cette image système avec le noyau est automatiquement créée par CI à l'aide du Dockerfile.
Les nœuds d'extrémité chargent le système d'exploitation et le noyau à partir de cette image directement via le réseau.
Les nœuds utilisent overlayfs comme système de fichiers racine, donc en cas de redémarrage, toutes les modifications seront perdues (ainsi que dans le cas des conteneurs Docker).
Il y a une configuration principale, dans laquelle vous pouvez décrire les points de montage et certaines commandes qui doivent être exécutées lorsque le nœud est chargé (par exemple, une commande pour ajouter une clé ssh et kubeadm join
)
Processus de préparation d'image
Nous utiliserons le projet LTSP car il nous donne tout ce dont nous avons besoin pour organiser le démarrage du réseau.
En général, LTSP est un ensemble de scripts shell qui nous facilite la vie.
Il fournit un module initramfs, plusieurs scripts d'assistance et une sorte de système de configuration qui prépare le système à un stade précoce du chargement, avant même d'appeler init.
Voici à quoi ressemble la procédure de préparation d'image:
- Déployez le système de base dans un environnement chroot.
- Nous apportons les modifications nécessaires, installons le logiciel.
- Exécutez la commande
ltsp-build-image
Juste après cela, vous obtiendrez une image compressée de ce chroot avec tous les logiciels installés à l'intérieur.
Chaque nœud téléchargera cette image au démarrage et l'utilisera comme rootfs.
Pour mettre à jour, redémarrez simplement le nœud, la nouvelle image sera téléchargée et utilisée pour rootfs.
Composants serveur
La partie serveur de LTSP dans notre cas ne comprend que deux composants:
- Serveur TFTP - TFTP est un protocole d'initialisation, il est utilisé pour charger le noyau, initramfs et la configuration principale - lts.conf.
- Serveur NBD - Protocole NBD utilisé pour fournir une image rootfs compressée aux clients. C'est la méthode la plus rapide, mais si vous le souhaitez, elle peut être remplacée par NFS ou AoE.
Vous devez également avoir:
- Serveur DHCP - il distribuera la configuration IP et plusieurs options supplémentaires dont nos clients auront besoin pour démarrer à partir de notre serveur LTSP.
Processus de chargement des nœuds
Description du processus de chargement des nœuds
- Tout d'abord, le nœud demandera l'adresse IP DHCP et les options pour le
next-server
, filename
. - Ensuite, le nœud appliquera les paramètres et téléchargera le chargeur de démarrage (pxelinux ou grub)
- Le chargeur de démarrage téléchargera et chargera la configuration avec le noyau et initramfs.
- Ensuite, il chargera le noyau et initramfs avec des options spécifiques spécifiées pour le noyau.
- Au démarrage, les modules initramfs traiteront les paramètres de cmdline et effectueront certaines actions, telles que la connexion d'un périphérique nbd, la préparation des superpositions rootfs, etc.
- Après cela, au lieu de l'init habituel, un ltsp-init spécial sera appelé.
- Les scripts ltsp-init prépareront le système très tôt avant que l'init principal soit appelé. Fondamentalement, les options de lts.conf (le fichier de configuration principal) sont utilisées ici: il s'agit de mettre à jour les enregistrements dans fstab et rc.local, etc.
- Ensuite, il y aura un appel à l'init principal (systemd), qui chargera le système déjà configuré comme d'habitude, montera les ressources partagées à partir de fstab, lancera les cibles et les services et exécutera les commandes à partir de rc.local.
- En conséquence, nous obtenons un système entièrement configuré et chargé, prêt pour de nouvelles actions.
Préparation du serveur
Comme je l'ai dit, je prépare automatiquement le serveur LTSP avec l'image écrasée à l'aide du Dockerfile. Cette méthode n'est pas mauvaise, car toutes les étapes de construction peuvent être décrites dans votre référentiel git. Vous pouvez contrôler les versions, utiliser des balises, appliquer CI et tout ce que vous utiliseriez pour la préparation de projets Docker habituels.
D'un autre côté, vous pouvez déployer le serveur LTSP manuellement en suivant toutes les étapes manuellement, cela peut être utile à des fins de formation et pour comprendre les principes de base.
Exécutez les commandes répertoriées dans l'article manuellement si vous souhaitez simplement essayer LTSP sans Dockerfile.
Liste des patchs utilisés
Pour le moment, LTSP a quelques défauts, et les auteurs du projet ne sont pas très disposés à accepter des corrections. Heureusement, LTSP est facilement personnalisable, j'ai donc préparé quelques correctifs pour moi, je vais les donner ici.
Peut-être qu'un jour je mûrirai dans la fourchette si la communauté accepte chaleureusement ma décision.
- feature-grub.diff
Par défaut, LTSP ne prend pas en charge EFI, j'ai donc préparé un correctif qui ajoute GRUB2 avec prise en charge EFI. - feature_preinit.diff
Ce correctif ajoute l'option PREINIT à lts.conf, qui vous permet d'exécuter des commandes arbitraires avant d'appeler l'init principal. Cela peut être utile pour modifier les unités systemd et les paramètres réseau. Il est à noter que toutes les variables de l'environnement de démarrage sont enregistrées et vous pouvez les utiliser dans vos scripts appelés via cette option. - feature_initramfs_params_from_lts_conf.diff
Résout le problème avec l'option NBD_TO_RAM désactivée, après ce correctif, vous pouvez le spécifier dans lts.conf à l'intérieur de chroot. (pas celui du répertoire tftp) - nbd-server-wrapper.sh
Ce n'est pas un patch, mais juste un script shell, il vous permet d'exécuter le serveur nbd dans Foregroud, vous en aurez besoin si vous souhaitez exécuter le serveur nbd à l'intérieur du conteneur Docker.
Étapes Dockerfile
Nous utiliserons la création de scènes dans notre Dockerfile pour enregistrer uniquement les parties nécessaires de notre image Docker, les parties restantes non utilisées seront exclues de l'image finale.
ltsp-base ( ltsp ) | |---basesystem | ( chroot- ) | | | |---builder | | ( , ) | | | '---ltsp-image | ( , docker, kubelet squashed ) | '---final-stage ( squashed , initramfs stage)
Étape 1: ltsp-base
OK, commençons, c'est la première partie de notre Dockerfile:
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 \
À l'heure actuelle, notre image Docker a déjà installé les éléments suivants:
- Serveur NBD
- Serveur TFTP
- Scripts LTSP avec prise en charge du chargeur de démarrage grub (pour EFI)
Étape 2: système de base
A ce stade, nous allons préparer l'environnement chroot avec le système de base et installer le logiciel principal avec le noyau.
Nous utiliserons le debootstrap habituel au lieu de ltsp-build-client pour préparer l'image, car ltsp-build-client installera l'interface graphique et certaines autres choses inutiles que nous ne serons évidemment pas utiles pour déployer des serveurs.
FROM ltsp-base as basesystem ARG DEBIAN_FRONTEND=noninteractive
Veuillez noter que certains packages, tels que lvm2, peuvent rencontrer des problèmes. Ils ne sont pas entièrement optimisés pour une installation dans un chroot non privilégié. Leurs scripts de post-installation tentent d'appeler des commandes privilégiées qui peuvent échouer et bloquer l'installation de l'ensemble du package.
Solution:
- Certains peuvent être installés sans problème si vous les installez avant d'installer le noyau (par exemple, lvm2)
- Mais pour certains d'entre eux, vous devrez utiliser cette solution de contournement pour installer sans script de post-installation.
Étape 3: constructeur
À ce stade, nous pouvons collecter tous les logiciels et modules de noyau nécessaires à partir des sources, il est très cool qu'il soit possible de le faire à ce stade, en mode entièrement automatique.
Ignorez cette étape si vous n'avez pas besoin de collecter quoi que ce soit auprès des artistes.
Je vais donner un exemple d'installation de la dernière version du pilote MLNX_EN:
FROM basesystem as builder
Étape 4: ltsp-image
À ce stade, nous établirons ce que nous avons collecté à l'étape précédente:
FROM basesystem as ltsp-image
Nous allons maintenant apporter des modifications supplémentaires pour compléter notre image LTSP:
Faites maintenant une image au carré à partir de notre chroot:
Étape 5: Étape finale
Au stade final, nous enregistrons uniquement notre image et notre noyau écrasés avec 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, nous avons maintenant une image docker qui comprend:
- Serveur TFTP
- Serveur NBD
- chargeur de démarrage configuré
- noyau avec initramfs
- image rootfs écrasée
Utiliser
OK, maintenant que notre image Docker avec le serveur LTSP, le noyau, les initramfs et les rootfs écrasés est complètement prête, nous pouvons exécuter le déploiement avec.
Nous pouvons le faire comme d'habitude, mais il y a un autre problème que nous devons résoudre.
Malheureusement, nous ne pouvons pas utiliser le service Kubernetes habituel pour notre déploiement, car au démarrage, les nœuds ne font pas partie du cluster Kubernetes et ils doivent utiliser externalIP, mais Kubernetes utilise toujours NAT pour externalIP et il n'y a actuellement aucun moyen de modifier ce comportement.
Je connais deux façons d'éviter cela: utilisez hostNetwork: true
ou utilisez la tuyauterie , la deuxième option nous fournira également une tolérance aux pannes en cas d'échec, l'adresse IP se déplacera vers un autre noeud avec le conteneur. Malheureusement, la tuyauterie n'est pas une méthode native et moins sûre.
Si vous connaissez une solution plus appropriée, veuillez nous en parler.
Voici un exemple de déploiement avec 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
Comme vous l'avez peut-être remarqué, configmap avec le fichier lts.conf est également utilisé ici.
A titre d'exemple, je vais donner une partie de ma config:
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 - lors du démarrage LTSP supprime automatiquement certains services, cette variable est nécessaire pour empêcher ce comportement pour les services répertoriés ici.
- PREINIT_ * - les commandes répertoriées ici seront exécutées avant d'exécuter systemd (cette fonctionnalité a été ajoutée par le correctif feature_preinit.diff )
- FSTAB_ * - les lignes répertoriées ici seront ajoutées au fichier
/etc/fstab
.
Vous remarquerez peut-être que j'utilise l'option nofail
, elle donne le comportement suivant: si la section n'existe pas, le téléchargement se poursuit sans erreur. - RCFILE_ * - ces commandes seront ajoutées au fichier
rc.local
, qui sera appelé par systemd au démarrage.
Ici, je charge les modules du noyau nécessaires, exécute certains paramètres sysctl, puis exécute la commande kubeadm join
, qui ajoute le nœud au cluster kubernetes.
Vous pouvez obtenir des informations plus détaillées sur toutes les variables à partir de la page de manuel lts.conf .
Vous pouvez maintenant configurer votre DHCP. Essentiellement, ce dont vous avez besoin est de spécifier les options de next-server
et de filename
.
J'utilise un serveur ISC-DHCP, je vais donner un exemple de 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; }
Vous pouvez commencer par cela, mais pour moi, j'ai plusieurs serveurs LTSP et pour chaque nœud, je configure une adresse IP statique distincte et les options nécessaires à l'aide d'un playbook Ansible.
Essayez de démarrer votre premier nœud et si tout a été fait correctement, vous obtiendrez un système chargé dessus. Le nœud sera également ajouté au cluster Kubernetes.
Vous pouvez maintenant essayer de faire vos propres modifications.
Si vous avez besoin de quelque chose de plus, notez que LTSP peut être très facilement personnalisé pour s'adapter à vos besoins. N'hésitez pas à consulter la source, vous y trouverez de nombreuses réponses.
Rejoignez notre chaîne Telegram: @ltsp_ru .