Worker Node 批量纳管:从手工部署到 Ansible 自动化
在前面的内容中,Kubernetes 控制平面已经完成初始化,Master 节点也已经具备了最基本的运行能力。但对于一个真正面向 AI 场景的集群来说,完成控制平面部署只是第一步,接下来的重点会迅速转移到 Worker Node。
这是因为,真正承载训练任务、推理服务和各类计算负载的,往往不是 Master,而是数量更多的 Worker 节点。如果这些节点仍然依赖手工逐台配置,那么随着节点数量增加,部署效率、一致性控制以及后续维护成本都会迅速失控。尤其是在 AI 集群场景下,容器运行时、内核模块、sysctl 参数、Kubernetes 三件套、镜像仓库配置以及节点接入过程,都要求节点环境具备较高的一致性。
也正因为如此,从这一节开始,部署方式将从“手工分步骤搭建 Master”进一步推进到“利用 Ansible 对 Worker Node 进行批量自动化处理”。
前面的手工部署并不是多余的,它解决的是理解 Kubernetes 集群是怎么一步一步搭起来的;而这一节的自动化,则解决的是如何让这些步骤在多节点环境中可重复、可批量、可维护地落地。
下面将按照从连通性检查、Ansible 项目构建,到基础环境修正、容器运行时准备、Kubernetes 组件安装、私有镜像仓库配置以及节点加入集群的顺序,逐步完成 Worker Node 的批量纳管。
批量操作前的连通性检查
在正式执行自动化任务之前,首先要确认控制节点与目标 Worker Node 之间具备基础网络连通性。这一步虽然简单,但非常必要,因为后续无论是 SSH 免密分发,还是 Playbook 批量执行,都依赖节点可达这一前提。这里使用 fping 对目标网段进行快速探测,分别确认哪些地址不可达、哪些地址可达,从而为后续主机清单编制和故障排查提供基础信息。
# 安装 fping
sudo apt install -y fping
# 有哪些ping不通?
fping -u -g 10.x.x.0/24 2>/dev/null
# 有哪些ping能通?
fping -a -g 10.x.x.0/24 2>/dev/null
Ansible 项目初始化
要让 Ansible 能够稳定地批量管理 Worker Node,首先需要准备控制节点侧的项目结构,包括工具安装、全局配置、主机清单和 SSH 认证方式。这一步的目标,是把后续分散的节点操作固化为统一、可重复执行的自动化入口。
#1. 在控制节点安装 Ansible
# Ansible 是后续批量管理 Worker Node 的核心工具。这里以 Ubuntu 22.04.5 为例,通过官方推荐方式安装较新的 Ansible 版本。
sudo apt update
sudo apt install software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install ansible
# 安装完成后,建议先检查版本信息,确认 Ansible 已可正常运行。
sudo ansible --version
ansible [core 2.19.4]
config file = /etc/ansible/ansible.cfg
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3/dist-packages/ansible
ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/bin/ansible
python version = 3.12.3 (main, Nov 6 2024, 18:32:19) [GCC 13.2.0] (/usr/bin/python3)
jinja version = 3.1.2
pyyaml version = 6.0.1 (with libyaml v0.2.5)
#2. 定义全局配置:ansible.cfg
ansible.cfg 用于统一定义 Ansible 的默认行为和运行规则,使 Playbook 执行过程更一致、可控。
在多节点场景下,这一步尤其重要,因为它决定了默认 inventory 文件、并发数、SSH 超时以及 Python 解释器选择方式。
vi ansible.cfg
[defaults]
host_key_checking = False
remote_user = xxx
inventory = ./hosts.ini
forks = 20
timeout = 30
interpreter_python = auto_silent
# 这里有几个关键点值得说明:
* 关闭 host_key_checking,可以避免第一次连接时被 yes/no 交互阻塞;
* remote_user 指定后续默认通过哪个用户登录目标节点;
* inventory 明确默认主机清单文件;
* forks 决定批量并发数,节点较多时可以适当调大;
* interpreter_python = auto_silent 可以降低 Python 路径不一致带来的问题。
# 3. 定义主机清单:hosts.ini
hosts.ini 的作用,是明确告诉 Ansible:需要管理哪些主机、这些主机属于哪些分组,以及如何连接它们。
在这里,Worker Node 被统一归入 workers 组,并进一步纳入 all_nodes 聚合组中,方便后续 Playbook 按角色或按全集群执行。
vi hosts.ini
[workers]
gpu-worker-1 ansible_host=10.x.x.1
gpu-worker-1 ansible_host=10.x.x.2
gpu-worker-1 ansible_host=10.x.x.3
[all_nodes:children]
workers
在节点规模较大时,建议保持命名和分组方式统一,这样后续不管是扩容、分批执行还是针对失败节点重试,都会更清晰。
# 4.设置 SSH 免密登录
在正式执行 Playbook 之前,还需要先打通控制节点到目标节点的 SSH 免密访问。
这是后续 Ansible 稳定批量执行的基础前提之一。
# 先检查当前控制节点是否已经存在公钥:
ls ~/.ssh/
# 若没有,执行如下命令,一直回车至结束。
ssh-keygen
# 定义变量,方便后续使用
PUBKEY="$(cat ~/.ssh/id_rsa.pub)"
# 检查变量输出
echo "$PUBKEY"
# 批量下发公钥
ansible all -i hosts.ini -m authorized_key \
-a "user=hpcc key='${PUBKEY}'" \
--ask-pass
# 测试验证ansible控制节点是否可以正常连接至ansible管理节点(由于在ansible.cfg文件里写了inventory所以这里不需要-i hosts.ini)
ansible all -m ping
# 其它
# 如果不做免密登陆可用如下方法,快速手工测试可加 --ask-pass 参数 (option)
ansible all -i hosts.ini -m ping --ask-pass
建立 Playbook 并按步骤执行
在 Kubernetes 节点真正可用之前,首先必须具备稳定可用的容器运行时。这一部分的目标,就是在所有 Worker Node 上批量安装并配置 containerd,并完成基础版本检查。
ansible-playbook playbook/s1_container_runtime_deploy.yml -K
Playbook样例如下
---
- name: K8s Step1 - Install and configure containerd
hosts: workers
become: yes
gather_facts: yes
tasks:
# 1. 更新 apt 缓存
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
# 2. 安装 containerd
- name: Install containerd package
apt:
name: containerd
state: present
# 3. 创建 /etc/containerd 目录
- name: Ensure /etc/containerd directory exists
file:
path: /etc/containerd
state: directory
mode: '0755'
# 4. 生成默认配置(如果不存在就生成一次)
- name: Generate default containerd config if not exists
shell: "containerd config default > /etc/containerd/config.toml"
args:
creates: /etc/containerd/config.toml
# 5. 确保 config.toml 权限与属主正确
- name: Ensure containerd config file ownership and permissions
file:
path: /etc/containerd/config.toml
owner: root
group: root
mode: '0644'
# 6. 重启并开机自启 containerd
- name: Restart and enable containerd service
systemd:
name: containerd
state: restarted
enabled: yes
daemon_reload: yes
# 7. 验证 containerd 版本
- name: Check containerd version
command: containerd --version
register: containerd_version
changed_when: false
failed_when: false # 如果命令异常,不让整个 play 失败,先收集信息
- name: Show containerd version
debug:
msg: "containerd version on {{ inventory_hostname }}: {{ containerd_version.stdout | default('command failed') }}"
# 8. 验证 runc 版本
- name: Check runc version
command: runc --version
register: runc_version
changed_when: false
failed_when: false
- name: Show runc version
debug:
msg: "runc version on {{ inventory_hostname }}: {{ runc_version.stdout | default('command failed') }}"
批量完成 Kubernetes 节点环境准备
在容器运行时就绪之后,还需要进一步统一 Worker Node 的内核模块、网络转发参数、Swap 状态、cgroup 驱动以及 pause 镜像配置。这些内容看似分散,但实际上都是 Kubernetes 能否稳定运行的基础前提。
ansible-playbook playbook/s2_k8s_env_ready.yml -K
Playbook样例如下
---
- name: K8s Step2 - Kernel modules, sysctl, swap, cgroup & containerd tuning
hosts: workers
become: yes
gather_facts: no
tasks:
############################################################
# 第一部分:加载内核模块 + 开机自启
############################################################
- name: Load required kernel modules (overlay, br_netfilter, vxlan)
modprobe:
name: "{{ item }}"
state: present
loop:
- overlay
- br_netfilter
- vxlan
- name: Ensure kernel modules auto-load on boot
copy:
dest: /etc/modules-load.d/k8s.conf
content: |
overlay
br_netfilter
vxlan
- name: Check loaded kernel modules
shell: |
for m in overlay br_netfilter vxlan; do
lsmod | grep -q "$m" && echo "$m loaded" || echo "$m not loaded"
done
register: kernel_check
changed_when: false
- debug:
msg: "{{ kernel_check.stdout_lines }}"
############################################################
# 第二部分:sysctl 网络转发规则
############################################################
- name: Configure sysctl for Kubernetes networking
copy:
dest: /etc/sysctl.d/k8s.conf
content: |
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
- name: Apply sysctl settings
command: sysctl --system
register: sysctl_apply
changed_when: "'Applying /etc/sysctl.d/k8s.conf' in sysctl_apply.stdout"
- name: Verify sysctl values
shell: |
sysctl net.bridge.bridge-nf-call-iptables \\
net.bridge.bridge-nf-call-ip6tables \\
net.ipv4.ip_forward
register: sysctl_check
changed_when: false
- debug:
msg: "{{ sysctl_check.stdout_lines }}"
############################################################
# 第三部分:Swap、Cgroup、Pause image 修复
############################################################
# 关闭 swap(立即生效)
- name: Disable swap temporarily
command: swapoff -a
# 注释 fstab 中所有 swap 行(永久生效)
- name: Disable swap in fstab permanently
replace:
path: /etc/fstab
regexp: '^([^#].*\\sswap\\s.*)$'
replace: '#\\1'
# 检查 swap 是否真的关闭
- name: Check swap status
command: free -m
register: swap_check
changed_when: false
- debug:
msg: "{{ swap_check.stdout_lines }}"
# 修改 containerd SystemdCgroup 为 true
- name: Enable SystemdCgroup in containerd
replace:
path: /etc/containerd/config.toml
regexp: '^\\s*SystemdCgroup = false'
replace: 'SystemdCgroup = true'
# 修改 sandbox image 为阿里云 pause:3.10
- name: Change containerd sandbox_image to pause:3.10
replace:
path: /etc/containerd/config.toml
regexp: 'registry.k8s.io/pause:[^"]*'
replace: 'registry.aliyuncs.com/google_containers/pause:3.10'
# 重启 containerd
- name: Restart containerd
systemd:
name: containerd
state: restarted
enabled: yes
# 检查 SystemdCgroup 修改结果
- name: Verify SystemdCgroup setting
shell: grep 'SystemdCgroup' /etc/containerd/config.toml
register: cgroup_check
changed_when: false
- debug:
msg: "{{ cgroup_check.stdout_lines }}"
# 检查 pause 镜像配置
- name: Verify sandbox_image setting
shell: grep 'pause' /etc/containerd/config.toml
register: pause_check
changed_when: false
- debug:
msg: "{{ pause_check.stdout_lines }}"
# 查看 containerd 服务状态
- name: Check containerd service status
command: systemctl status containerd
register: containerd_status
changed_when: false
- debug:
msg: "{{ containerd_status.stdout_lines }}"
批量安装 Kubernetes 三大组件
在完成运行环境准备之后,下一步就是安装 Kubernetes 的三大核心组件:kubeadm、kubelet 和 kubectl。这一部分的目标,是为所有 Worker Node 提供统一版本的 Kubernetes 节点能力,并尽量避免版本漂移。
ansible-playbook playbook/s3_k8s_3components_install.yml -K
Playbook样例如下
---
- name: K8s Step3 - Install kubeadm, kubelet, kubectl (v1.33.5)
hosts: workers
become: yes
gather_facts: no
tasks:
# 1. 安装前置依赖
- name: Install Kubernetes APT dependencies
apt:
name:
- apt-transport-https
- ca-certificates
- curl
- gpg
state: present
update_cache: yes
# 2. 确保 keyrings 目录存在
- name: Ensure /etc/apt/keyrings directory exists
file:
path: /etc/apt/keyrings
state: directory
mode: '0755'
# 3. 下载 Kubernetes v1.33 的 Release.key 并转换为 gpg keyring
- name: Download Kubernetes v1.33 APT Release key
get_url:
url: <https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key>
dest: /tmp/kubernetes-apt-release.key
mode: '0644'
- name: Convert Kubernetes APT key to keyring
command: >
gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
/tmp/kubernetes-apt-release.key
args:
creates: /etc/apt/keyrings/kubernetes-apt-keyring.gpg
# 4. 写 APT 源配置文件
- name: Configure Kubernetes v1.33 APT repository
copy:
dest: /etc/apt/sources.list.d/kubernetes.list
content: |
deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] <https://pkgs.k8s.io/core:/stable:/v1.33/deb/> /
mode: '0644'
# 5. 更新 APT 索引
- name: Update APT cache after adding Kubernetes repo
apt:
update_cache: yes
# 6. 安装指定版本的 kubelet / kubeadm / kubectl
- name: Install kubeadm, kubelet, kubectl v1.33.5-1.1
apt:
name:
- kubelet=1.33.5-1.1
- kubeadm=1.33.5-1.1
- kubectl=1.33.5-1.1
state: present
allow_downgrade: yes
# 7. 设置版本 hold,带重试逻辑,避免 apt 锁冲突导致失败
- name: Hold kubelet, kubeadm, kubectl versions
command: apt-mark hold kubelet kubeadm kubectl
register: hold_cmd
retries: 5 # 最多重试 5 次
delay: 10 # 每次间隔 10 秒
until: hold_cmd.rc == 0
##################################################
# 检查部分
##################################################
# 8. 检查 kubeadm 版本
- name: Check kubeadm version
command: kubeadm version -o yaml
register: kubeadm_ver
changed_when: false
failed_when: false
- name: Show kubeadm version
debug:
msg: "{{ kubeadm_ver.stdout | default('kubeadm version command failed') }}"
# 9. 检查 kubelet 版本
- name: Check kubelet version
command: kubelet --version
register: kubelet_ver
changed_when: false
failed_when: false
- name: Show kubelet version
debug:
msg: "{{ kubelet_ver.stdout | default('kubelet version command failed') }}"
# 10. 检查 kubectl 客户端版本
- name: Check kubectl client version
command: kubectl version --client --output=yaml
register: kubectl_ver
changed_when: false
failed_when: false
- name: Show kubectl client version
debug:
msg: "{{ kubectl_ver.stdout | default('kubectl version command failed') }}"
# 11. 检查 kubelet 服务状态(未 join 前 inactive 是正常的)
- name: Check kubelet service status
command: systemctl status kubelet
register: kubelet_status
changed_when: false
failed_when: false
- name: Show kubelet service status (Active line)
debug:
msg: "{{ item }}"
loop: "{{ kubelet_status.stdout_lines | default([]) }}"
when: "'Active:' in item"
# 12. 检查 APT 源是否正确配置
- name: Verify Kubernetes APT repository line
shell: grep -R "pkgs.k8s.io/core:/stable:/v1.33" /etc/apt/sources.list.d/ || echo 'k8s repo not found'
register: repo_check
changed_when: false
- name: Show repo check result
debug:
msg: "{{ repo_check.stdout_lines }}"
# 13. 检查 apt-mark hold 状态
- name: Check held packages for kubelet/kubeadm/kubectl
shell: apt-mark showhold | grep -E "kubelet|kubeadm|kubectl" || echo 'no hold found'
register: hold_check
changed_when: false
- name: Show hold status
debug:
msg: "{{ hold_check.stdout_lines }}"
批量将 Worker Node 加入集群
当前面的基础环境、容器运行时、Kubernetes 组件和镜像仓库访问都准备完成之后,就可以正式让 Worker Node 加入集群。
ansible-playbook playbook/s5_k8s_join_workers.yml -K
Playbook样例如下
---
- name: K8s Step4 - kubeadm join workers to cluster
hosts: workers
become: yes
gather_facts: no
vars:
# 按你现在集群的实际情况填
kubeadm_join_command: >-
kubeadm join 10.x.x.200:8443
--token abcdef.xxxdef
--discovery-token-ca-cert-hash sha256:57a047xxxe517db537204
tasks:
# 1. 判断该节点是否已经在集群中(简单依据:kubelet.conf 是否存在)
- name: Check if kubelet.conf exists (joined or initialized)
stat:
path: /etc/kubernetes/kubelet.conf
register: kubelet_conf
- name: Show join status hint
debug:
msg: >
kubelet.conf exists on {{ inventory_hostname }},
this node looks already joined or initialized. Skip join.
when: kubelet_conf.stat.exists
# 2. 执行 kubeadm join(仅在还没 join 的节点上)
- name: Run kubeadm join
command: "{{ kubeadm_join_command }}"
register: join_result
when: not kubelet_conf.stat.exists
# kubeadm join 只会执行一次,不需要认为 changed_when
# 让 Ansible 正常感知 changed 状态即可
- name: Show kubeadm join output
debug:
msg: "{{ join_result.stdout_lines | default(['kubeadm join not executed (node already joined).']) }}"
when: not kubelet_conf.stat.exists
# 3. 确保 kubelet 服务已启用并启动
- name: Enable and start kubelet service
systemd:
name: kubelet
enabled: yes
state: started
# 已 join 与否都可以执行一次,确保 kubelet 在跑
到这里,Worker Node 已经通过 Ansible 完成了批量化的基础环境修正、容器运行时准备、Kubernetes 组件安装、私有镜像仓库配置以及节点加入集群等关键步骤。相比逐台手工处理,这种方式不仅显著降低了重复劳动,也让节点侧配置具备了更好的一致性、可复用性和可维护性。
这一小节更重要的是为了让 Kubernetes 集群的节点纳管过程真正具备工程化能力。前面的 Master 节点部署解决的是“如何把 Kubernetes 一步一步搭起来”;而这里的 Ansible 自动化,解决的是“如何把这些步骤稳定地复制到更多节点上”。
