
在本文中,我想向您展示一种很酷的技术,我成功地将其用于Kubernetes。 这对于构建大型集群非常有用。
从现在开始,您不再需要考虑为每个节点安装操作系统和单独的软件包。 怎么了 您可以通过Dockerfile自动完成所有这些操作!
您可以购买数百台新服务器,将它们添加到您的工作环境中,并几乎立即将它们准备好使用,这一事实的确令人惊奇!
感兴趣吗? 现在让我们按顺序讨论一切。
总结
首先,我们需要确切了解该电路的工作原理。
简而言之,对于所有节点,我们正在使用OS,Docker,Kubelet和其他所有内容准备单个映像。
CI使用Dockerfile自动将该系统映像与内核一起构建。
终端节点直接通过网络从该映像加载OS和内核。
节点使用overlayfs作为根文件系统,因此,在重新引导的情况下,所有更改都会丢失(以及docker容器)。
有一个主要的配置,您可以在其中描述安装点以及在加载节点时应执行的一些命令(例如,添加ssh键和kubeadm join
的命令)
图像准备过程
我们将使用LTSP项目,因为它为我们提供了组织网络引导所需的一切。
通常,LTSP是一堆shell脚本,使我们的生活更加轻松。
它提供了一个initramfs模块,几个帮助程序脚本以及某种配置系统,该系统可以在加载的早期(甚至在调用init之前)为系统做准备。
这是图像准备过程的样子:
- 在chroot环境中部署基本系统。
- 我们进行必要的更改,安装软件。
- 运行
ltsp-build-image
命令
之后,您将从此chroot获取压缩的映像,其中包含所有安装的软件。
每个节点都将在引导时下载该映像并将其用作rootfs。
要进行更新,只需重新启动节点,新映像将被下载并用于rootfs。
服务器组件
在我们的案例中,LTSP的服务器部分仅包含两个组件:
- TFTP服务器 -TFTP是初始化协议,用于加载内核,initramfs和主要配置-lts.conf。
- NBD服务器 -NBD协议用于将压缩的rootfs映像传递给客户端。 这是最快的方法,但是如果需要,可以将其替换为NFS或AoE。
您还需要具备:
- DHCP服务器 -它将分发IP配置以及我们的客户端所需的其他几个选项,以便它们可以从我们的LTSP服务器启动。
节点加载过程
节点加载过程说明
- 首先,该节点将为
next-server
filename
请求DHCP IP地址和选项。 - 然后,该节点将应用设置并下载引导程序(pxelinux或grub)
- 引导程序将下载并使用内核和initramfs加载配置。
- 然后,它将使用为内核指定的特定选项加载内核和initramfs。
- 在启动时,initramfs模块将处理来自cmdline的参数并执行一些操作,例如连接nbd设备,准备覆盖rootfs等。
- 之后,将调用特殊的ltsp-init,而不是通常的init。
- ltsp-init脚本将在调用主init之前尽早准备系统。 基本上,这里使用lts.conf(主配置文件)中的选项:这是更新fstab和rc.local等中的记录。
- 然后将调用主init(systemd),它将照常加载已配置的系统,从fstab挂载共享资源,启动目标和服务,并从rc.local执行命令。
- 结果,我们得到了一个完整配置并已加载的系统,可以采取进一步的措施。
服务器准备
如我所说,我使用Dockerfile自动为LTSP服务器准备了压缩的映像。 这种方法还不错,因为所有构建步骤都可以在git存储库中描述。 您可以控制版本,使用标签,应用CI以及用于准备常规Docker项目的所有功能。
另一方面,您可以通过手动执行所有步骤来手动部署LTSP服务器,这对于培训目的和理解基本原理很有用。
如果只想在没有Dockerfile的情况下尝试LTSP,请手动运行本文中列出的命令。
使用的补丁列表
目前,LTSP存在一些缺陷,该项目的作者不太愿意接受更正。 幸运的是,LTSP易于定制,因此我为自己准备了一些补丁,我将在此处提供。
如果社区热烈接受我的决定,也许有一天我会成熟起来。
Dockerfile阶段
我们将在Dockerfile中使用阶段构建来仅保存Docker映像的必要部分,其余未使用的部分将从最终映像中排除。
ltsp-base ( ltsp ) | |---basesystem | ( chroot- ) | | | |---builder | | ( , ) | | | '---ltsp-image | ( , docker, kubelet squashed ) | '---final-stage ( squashed , initramfs stage)
阶段1:以ltsp为基础
好的,让我们开始吧,这是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 \
目前,我们的docker映像已经安装了以下内容:
- NBD服务器
- TFTP服务器
- 具有grub引导加载程序支持的LTSP脚本(用于EFI)
阶段2:基本系统
在这一阶段,我们将使用基本系统准备chroot环境,并使用内核安装主要软件。
我们将使用通常的debootstrap而不是ltsp-build-client来准备映像,因为ltsp-build-client将安装GUI和一些其他不必要的东西,这些显然对部署服务器没有用。
FROM ltsp-base as basesystem ARG DEBIAN_FRONTEND=noninteractive
请注意,某些软件包(例如lvm2)可能会遇到问题。 它们尚未完全优化,无法安装在无特权的chroot中。 他们的安装后脚本尝试调用特权命令,这些命令可能会失败并阻止整个软件包的安装。
解决方案:
- 如果在安装内核之前先安装它们,则可以成功安装其中的一些(例如,lvm2)
- 但是对于其中一些,您将需要使用此替代方法来安装,而无需安装后脚本。
第三阶段:建造者
在这个阶段,我们可以从源头收集所有必要的软件和内核模块,这是很酷的事情,它可以在这个阶段以全自动模式进行。
如果您不需要从艺术家那里收集任何东西,请跳过此步骤。
我将举一个安装最新版本的MLNX_EN驱动程序的示例:
FROM basesystem as builder
阶段4:ltsp-image
在这一阶段,我们将确定上一步收集的内容:
FROM basesystem as ltsp-image
现在,我们将进行其他更改以完成LTSP映像:
现在,从我们的chroot制作一个压缩图像:
阶段5:最后阶段
在最后阶段,我们仅使用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
好的,现在我们有了一个docker映像,其中包括:
- TFTP服务器
- NBD服务器
- 配置的引导程序
- initramfs的内核
- 压缩的rootfs图像
使用方法
好的,现在我们的带有LTSP服务器,内核,initramfs和压缩后的rootfs的Docker映像已完全准备就绪,我们可以使用它运行部署。
我们可以照常做,但是还有另一个问题需要解决。
不幸的是,我们无法在部署中使用常规的Kubernetes服务,因为在启动时节点不是Kubernetes集群的一部分,它们需要使用externalIP,但是Kubernetes始终将NAT用于externalIP,并且目前无法更改此行为。
我知道两种避免这种情况的方法:使用hostNetwork: true
或使用pipework ,第二个选项还将为我们提供容错能力,因为 如果发生故障,该IP地址将与容器一起移至另一个节点。 不幸的是,管道工程不是一种本机且不太安全的方法。
如果您知道任何更合适的解决方案,请告诉我们。
这是使用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
您可能已经注意到,这里还使用了带有lts.conf文件的configmap。
例如,我将给出部分配置:
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 @ 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-引导LTSP期间会自动删除某些服务,需要此变量以防止此处列出的服务出现此行为。
- PREINIT_ * -将在运行systemd之前执行此处列出的命令(此功能由feature_preinit.diff补丁添加)
- FSTAB_ * -此处列出的行将添加到
/etc/fstab
文件中。
您可能会注意到,我正在使用nofail
选项,它具有以下行为:如果该部分不存在,则下载将继续而不会出现错误。 - RCFILE_ * -这些命令将添加到
rc.local
文件,该文件将在引导时由systemd调用。
在这里,我加载必要的内核模块,运行一些sysctl设置,然后运行kubeadm join
命令,该命令将节点添加到kubernetes集群中。
您可以从lts.conf手册页中获取有关所有变量的更多详细信息。
现在,您可以配置DHCP。 本质上,您需要指定next-server
和filename
选项。
我使用的是ISC-DHCP服务器,下面以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; }
您可以从这里开始,但是就我而言,我有数台LTSP服务器,并且使用Ansible剧本为每个节点配置了单独的静态IP地址和必要的选项。
尝试启动第一个节点,如果一切都正确完成,则将在该节点上加载一个系统。 该节点也将添加到Kubernetes集群中。
现在,您可以尝试进行自己的更改。
如果您还需要其他东西,请注意,可以很容易地定制LTSP以满足您的需求。 随时查看源代码,您可以在其中找到很多答案。
加入我们的电报频道: @ltsp_ru 。