公司动态

K8s 节点初始化自动化:从裸金属到 Ready 节点的零干预

📅 2026/7/22 0:00:43
K8s 节点初始化自动化:从裸金属到 Ready 节点的零干预
K8s 节点初始化自动化从裸金属到 Ready 节点的零干预一、手动加节点装系统、配内核、装 Docker、配 K8s…新同事折腾了两天才加进去三台手动向 K8s 集群添加节点的流程之繁琐是运维人员的老大难问题。裸金属服务器要装 OS、配内核参数、装 container runtime、配网络、join 集群。出了任何一步配置错误——比如忘记关 swap、cgroup driver 不一致——节点 join 失败但 kubectl 的错误信息通常不会有你的 swap 没关这样的提示。节点初始化的自动化是所有 K8s 集群运维的基础。核心链路是裸金属上架 → PXE/iLO 安装 OS → Cloud-init/Ignition 做系统配置 → 安装 K8s 组件 → 自动 join 集群 → 自动打标签和 taint。目标是从服务器上电到kubectl get nodes显示 Ready全程零人工干预。关键的技术选型是自动化工具的选择Ansible 适合静态节点服务器列表固定、Terraform 适合云上节点AWS/GCP 有现成 module、Cluster API 适合声明式管理节点生命周期。对于混合环境的裸金属节点管理更推荐 Ansible PXE 的组合。二、底层机制与原理剖析K8s 节点初始化自动化流程节点初始化的关键步骤和常见坑Swap 必须关闭Kubelet 从 1.8 开始要求 swap off。理由是K8s 的调度和驱逐依赖内存使用量的精确跟踪swap 的存在让内存看起来比实际多。不是建议关而是必须关。在/etc/fstab中注释 swap 行并执行swapoff -a。cgroup driver 必须一致最常见的 K8s 入门坑。Docker 默认用 cgroupfsKubelet 推荐用 systemd。两个不一致会导致 Kubelet 重启时资源统计丢失、甚至 OOM Kill 行为异常。解决方案在 Docker daemon.json 中设置exec-opts: [native.cgroupdriversystemd]Kubelet 配置中也设置cgroupDriver: systemd。Bootstrap Token 管理Kubeadm join 需要一个 bootstrap token。token 的 TTL 默认 24 小时。对于自动化流程新节点可能在 token 创建 23 小时后才 join需要创建长期有效的 token 或通过自动化在 join 前重新生成。三、生产级代码实现Ansible Playbook——节点初始化主力工具# ansible/init-k8s-node.yml # K8s 节点初始化 Ansible Playbook # 支持 Ubuntu 22.04 / CentOS 8 # 设计决策所有配置有幂等性保证可重复执行而不产生副作用 - name: Initialize K8s Worker Node hosts: new_nodes become: yes vars: k8s_version: 1.29 container_runtime: containerd control_plane_endpoint: k8s-api.internal:6443 # Bootstrap Token —— 从控制平面动态获取 bootstrap_token: {{ lookup(env, K8S_BOOTSTRAP_TOKEN) }} ca_cert_hash: {{ lookup(env, K8S_CA_CERT_HASH) }} tasks: # Step 1: 系统配置 - name: 关闭 Swap shell: | swapoff -a sed -i / swap / s/^/#/ /etc/fstab args: executable: /bin/bash - name: 加载内核模块 modprobe: name: {{ item }} state: present loop: - overlay - br_netfilter - name: 配置 sysctl 参数 sysctl: name: {{ item.key }} value: {{ item.value }} sysctl_file: /etc/sysctl.d/99-kubernetes.conf reload: yes loop: # 允许 iptables 检查桥接流量CNI 必需 - { key: net.bridge.bridge-nf-call-iptables, value: 1 } - { key: net.bridge.bridge-nf-call-ip6tables, value: 1 } # 启用 IP 转发 - { key: net.ipv4.ip_forward, value: 1 } # Step 2: 安装 Container Runtime - name: 安装 containerd block: - name: 添加 Docker 仓库 ansible.builtin.apt_repository: repo: deb https://download.docker.com/linux/ubuntu jammy stable state: present when: ansible_distribution Ubuntu - name: 安装 containerd apt: name: containerd.io state: present update_cache: yes when: ansible_distribution Ubuntu - name: 配置 containerd copy: dest: /etc/containerd/config.toml content: | version 2 [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc] runtime_type io.containerd.runc.v2 [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] # systemd cgroup driver与 Kubelet 保持一致 SystemdCgroup true notify: restart containerd - name: 启动 containerd systemd: name: containerd state: started enabled: yes # Step 3: 安装 K8s 组件 - name: 添加 Kubernetes APT 仓库 ansible.builtin.apt_repository: repo: deb https://pkgs.k8s.io/core:/stable:/v{{ k8s_version }}/deb/ / state: present filename: kubernetes when: ansible_distribution Ubuntu - name: 安装 kubelet, kubeadm, kubectl apt: name: - kubelet{{ k8s_version }}.* - kubeadm{{ k8s_version }}.* - kubectl{{ k8s_version }}.* state: present update_cache: yes when: ansible_distribution Ubuntu # 锁定版本防止 apt upgrade 自动升级 K8s - name: Hold K8s packages dpkg_selections: name: {{ item }} selection: hold loop: - kubelet - kubeadm - kubectl # Step 4: 配置 Kubelet - name: 配置 Kubelet copy: dest: /etc/default/kubelet content: | KUBELET_EXTRA_ARGS--cgroup-driversystemd \\ --node-ip{{ ansible_default_ipv4.address }} # Step 5: Join 集群 - name: Join 集群 shell: | kubeadm join {{ control_plane_endpoint }} \ --token {{ bootstrap_token }} \ --discovery-token-ca-cert-hash sha256:{{ ca_cert_hash }} \ --node-name {{ inventory_hostname }} register: join_result # 幂等性已经在集群中的节点跳过 failed_when: - join_result.rc ! 0 - already exists not in join_result.stderr # Step 6: 节点标签 - name: 给节点打标签从控制平面执行 delegate_to: {{ groups[control_plane][0] }} shell: | kubectl label node {{ inventory_hostname }} \ node-role.kubernetes.io/worker \ topology.kubernetes.io/zone{{ node_zone | default(default) }} \ --overwrite when: join_result is changed handlers: - name: restart containerd systemd: name: containerd state: restarted daemon_reload: yesBootstrap token 自动管理脚本#!/bin/bash # 生成和分发新的 Bootstrap Token # 在控制平面节点上执行 TOKEN$(kubeadm token create --ttl 72h --description Node auto-join token) CA_HASH$(openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | \ openssl rsa -pubin -outform der 2/dev/null | \ openssl dgst -sha256 -hex | sed s/^.* //) echo K8S_BOOTSTRAP_TOKEN$TOKEN echo K8S_CA_CERT_HASH$CA_HASH # 存储到 CI/CD 的 Secret Manager # 或通过 ansible-vault 加密存储节点就绪检查脚本#!/bin/bash # 验证新节点是否完全就绪 # 检查清单Node Ready CNI Pod Running CSI Pod Running NODE_NAME${1:?请指定节点名称} echo 节点就绪检查: $NODE_NAME # 1. Node Ready 检查 echo -n 1. Node Ready: READY$(kubectl get node $NODE_NAME -o jsonpath{.status.conditions[?(.typeReady)].status}) if [ $READY True ]; then echo 通过 else echo 失败 — 节点未 Ready exit 1 fi # 2. CNI Pod 检查 echo -n 2. CNI Pods Running: CNI_PODS$(kubectl get pods -n kube-system --field-selector spec.nodeName$NODE_NAME \ -l app in (calico-node, cilium, flannel) \ -o jsonpath{.items[?(.status.phaseRunning)].metadata.name}) if [ -n $CNI_PODS ]; then echo 通过 ($(echo $CNI_PODS | wc -w | tr -d ) pods) else echo 失败 — 未找到 Running 的 CNI Pod exit 1 fi # 3. CSI Pod 检查如果有 echo -n 3. CSI Pods Running: CSI_PODS$(kubectl get pods -n kube-system --field-selector spec.nodeName$NODE_NAME \ -l app in (ebs-csi-node, efs-csi-node) \ -o jsonpath{.items[?(.status.phaseRunning)].metadata.name} 2/dev/null) if [ -n $CSI_PODS ]; then echo 通过 ($(echo $CSI_PODS | wc -w | tr -d ) pods) else echo 无可能没有安装 CSI 驱动 fi echo echo 节点 $NODE_NAME 已就绪可以接受调度。四、边界分析与架构权衡PXE 引导的复杂度PXE 网络引导需要 DHCP TFTP 服务器。对于小规模集群 20 节点PXE 的维护成本可能与手动装机不相上下。20 节点以上才明显划算。如果没有 PXE 基础设施可以退而使用预装 OS 的镜像 Ansible 后置。自动化加入集群的安全考量Bootstrap Token 是敏感信息需要在网络传输中加密。Ansible 用lookup(env, ...)从环境变量读取适合在 CI Pipeline 中通过 Secret Manager 注入。不要在 Playbook 中硬编码 Token。适用边界最适合节点频繁扩缩容的集群——深夜自动扩容节点、白天自动回收。也适合新集群搭建时的批量节点初始化一次性加入 10 节点。长期稳定运行的集群半年不加新节点自动化脚本容易过期——K8s 版本升级后 Playbook 可能需要适配。禁用场景不适合完全使用托管 K8sEKS/AKS/GKE 的 Managed Node Group的场景。托管服务已经内置了节点自动加入和生命周期管理。五、总结K8s 节点初始化的自动化路径PXE 装机 → Cloud-init 系统配置 → Ansible 安装组件 → Kubeadm Join 集群 → 自动标签。关键踩坑点swap 必须关闭、cgroup driver 必须统一为 systemd、Bootstrap Token 需要管理 TTL。Ansible Playbook 的幂等性保证了脚本可以重复跑——已经 join 的节点不会被重复 join。适合节点频繁变化的集群弹性伸缩、混合云目标是从服务器上电到 Ready 零人工干预。