Kubernetes 1.28 离线部署(Ansible)
| 项目 | 说明 |
|---|---|
| 源码仓库(Gitee) | https://gitee.com/wxd_ops/k8s-1.28-ansible-offline |
| 默认分支 | master |
| 克隆命令 | git clone https://gitee.com/wxd_ops/k8s-1.28-ansible-offline.git |
| Kubernetes 版本 | 1.28.15 |
| 配套文档 | 随 CSDN 教程发布;Ansible 剧本与模板以 Gitee 为准 |
CSDN 读者 :请先 Star / Fork Gitee 仓库获取最新代码。Git 因体积与单文件大小限制不包含 离线镜像 tar、Harbor 安装包等大文件(约 3GB+),需按 §0.2 放入
roles/*/files/后再部署。
基于 Ansible 的 Kubernetes 1.28.15 离线安装方案,支持:
| 类型 | Playbook |
|---|---|
| 单 Master + Worker | kubernetes-single.yml |
| 三 Master HA(内置/外部 etcd) | kubernetes-ha.yml |
| Worker 扩容 | add_node.yml |
| 单独 Harbor / HAProxy / Keepalived | harbor.yml / haproxy.yml / keepalive.yml |
| 预检 | preflight.yml |
组件:Harbor 私有仓库 · Calico(Operator/Manifests)或 Flannel · Ingress Nginx · Metrics Server · NFS Provisioner
0. 源码仓库与离线包
0.1 代码仓库(Gitee)
| 项 | 内容 |
|---|---|
| 仓库地址 | https://gitee.com/wxd_ops/k8s-1.28-ansible-offline |
| 所有者 | wxd_ops |
| 分支 | master |
| 包含内容 | playbook/、roles/(任务与模板)、group_vars/、hosts_* 模板、tools/、manifest.json、Readme.md |
| 不包含 | roles/*/files/ 下离线 tar/rpm(见 §0.2) |
bash
# 克隆源码
git clone https://gitee.com/wxd_ops/k8s-1.28-ansible-offline.git
cd k8s-1.28-ansible-offline
# 或仅拉取 master
git clone -b master https://gitee.com/wxd_ops/k8s-1.28-ansible-offline.git
CSDN 文章引用示例(可复制到文末):
完整 Ansible 源码:Gitee --- k8s-1.28-ansible-offline
克隆:
git clone https://gitee.com/wxd_ops/k8s-1.28-ansible-offline.git离线大包请按仓库 Readme §0.2 准备,与
manifest.json校验一致后再执行ansible-playbook。
0.2 离线大包(不在 Git 中)
Gitee 对单文件与仓库体积有限制,镜像 tar、Harbor 离线包、RPM 等约 3GB+ 未提交 Git 。部署前需将离线包放到 manifest.json 所列路径,例如:
| 目录 | 示例文件 |
|---|---|
roles/k8s-master/files/ |
kube-apiserver.v1.28.15.tar、cni.v3.25.1.tar、etcd.v3.5.9.tar ... |
roles/k8s-node/files/ |
node.v3.25.1.tar、flannel.v0.25.1.tar ... |
roles/kube-bin/files/ |
kube1.28.15.tar.gz、kubeadm / kubectl / kubelet |
roles/harbor/files/ |
harbor-offline-installer-v2.2.0.tgz |
roles/add-on/files/ |
calicoctl(与 calico_version 一致,如 v3.25.0) |
roles/add-cniPlugin-on/files/ |
cni-plugins-linux-amd64-v1.6.2.tgz |
获取方式(任选):
- CSDN 教程附件 / 百度网盘(与文章同步发布)
- Gitee Release(若作者上传离线包压缩包)
- 自行按 §3.4、§3.4.1 准备 (有网环境下载镜像并
docker save)
校验:
bash
bash tools/verify_manifest.sh
ansible-playbook playbook/preflight.yml
0.3 仓库目录速览
k8s-1.28-ansible-offline/
├── playbook/ # kubernetes-single.yml / kubernetes-ha.yml 等
├── roles/ # Ansible 角色(files/ 下离线包需自行补全)
├── group_vars/all/ # vars.yml、vault.yml.example
├── hosts_* # inventory 模板
├── tools/ # verify_manifest、generate_manifest、prepare_kube_bin
├── manifest.json # 离线包清单与 SHA256
└── Readme.md # 本文档
1. 准备
1.1 上传与解压
将离线包上传至部署机,解压到目标目录(默认 /export/ops/k8s):
bash
mkdir -p /export/ops
tar -xf k8s-offline.tar -C /export/ops/
cd /export/ops/k8s
若解压路径不是 /export/ops/k8s,修改 group_vars/all/vars.yml 中的 deploy_base_dir。
1.2 环境要求
- 控制节点(部署机) :已安装 Ansible 2.9+、sshpass (使用
ansible_ssh_pass密码登录时需要);在 k8s 项目目录下执行 playbook,支持从任意控制机远程部署 [install]组节点 :存放 Harbor 证书、kubeadm 配置、join 脚本(deploy_tmp_*目录),与控制机之间同样需 SSH 可达- 目标节点:Kylin / CentOS 系,root SSH 可达(22 端口开放)
- 每节点建议:≥ 2 核 CPU、≥ 2GB 内存、数据盘(可选,
export_disk/mount_dir) - HA 场景:提前准备 HAProxy 或 Keepalived VIP
控制机 SSH 访问(是否需要免密?)
结论:部署前不需要控制机与各节点预先配置 SSH 免密。
| 阶段 | 要求 |
|---|---|
| 部署前 | 控制机能以 root + 密码 SSH 登录 hosts 中所有节点即可;在 inventory 写 ansible_ssh_user=root、ansible_ssh_pass="..." |
| 部署过程中 | common / common-node role 会向各节点 root 的 authorized_keys 写入项目内置 ops 运维公钥 (见 roles/common/tasks/users.yml),便于持对应私钥的人员登录各节点------不会自动把控制机当前 SSH 公钥分发到各节点 |
| 部署后 | 若需从控制机免密登录,可:(1) 继续使用 ansible_ssh_pass;(2) 部署前自行 ssh-copy-id;(3) 使用与内置 ops 公钥匹配的私钥 |
ansible.cfg 中配置了 private_key_file = id_rsa:若项目目录下存在匹配私钥,Ansible 会优先尝试密钥登录;没有私钥或未配置免密时,仍依赖 ansible_ssh_pass。
预检 SSH 连通性(部署前建议执行):
bash
ansible all -m ping
# 使用 Vault 时:ansible all -m ping --ask-vault-pass
常见失败:控制机未装 sshpass、目标节点 root 密码错误、防火墙拦截 22 端口。
1.3 Inventory 模板
| 文件 | 用途 |
|---|---|
hosts_single |
单 Master + Worker 全量部署 |
hosts_ha |
三 Master HA + Worker(含 LB/etcd 组) |
hosts_add_node |
扩容 Worker |
hosts_harbor |
单独部署 Harbor |
hosts_haproxy |
单独部署 HAProxy |
hosts_keepalive |
单独部署 Keepalived VIP |
hosts |
当前使用的 inventory(由上述模板复制而来) |
选用模板后复制为 hosts 并编辑 IP、密码、变量:
bash
cp hosts_single hosts
vi hosts
1.4 全局变量与 Vault(可选但生产推荐)
结论:部署前不强制要求 vault.yml。 若 hosts 里已写明文 ansible_ssh_pass、harbor_pwd,可直接部署。生产环境建议用 Vault 管理 Harbor 密码等敏感变量。
| 方式 | 适用 | 部署命令 |
|---|---|---|
| 明文(Lab/测试) | hosts 中直接写密码 |
ansible-playbook playbook/kubernetes-single.yml |
| Vault(生产推荐) | 密码放在加密 vault.yml |
ansible-playbook ... --ask-vault-pass |
使用 Vault 的步骤
bash
# 1. 从示例复制并编辑(填写真实 Harbor 密码等)
cp group_vars/all/vault.yml.example group_vars/all/vault.yml
vi group_vars/all/vault.yml
# 2. 加密(只需执行一次;之后改内容用 ansible-vault edit)
ansible-vault encrypt group_vars/all/vault.yml
# 3. 部署前验证 Vault 能否正常解密、变量能否被引用(见 §1.5 步骤 4)
ansible-playbook playbook/preflight.yml --ask-vault-pass
hosts / hosts_single 中保持 Vault 引用写法(未加密 vault 时会走 default 兜底):
ini
harbor_pwd={{ vault_harbor_pwd | default('changeme') }}
可选:在 ansible.cfg 中配置 vault_password_file = /path/to/vault_pass 免交互输入。
完整单 Master 部署命令(含 verify_manifest + preflight + 主 playbook)见 §1.6。
注意 :vault.yml不存在或未传--ask-vault-pass时,vault_harbor_pwd未定义,harbor_pwd会使用default(...)中的明文默认值------请确认该默认值与 Harbor 实际密码一致,或改用 Vault。
1.5 部署前检查清单(推荐顺序)
按下列顺序执行,全部通过后再跑主 playbook:
| 步骤 | 是否必须 | 命令 / 操作 | 说明 |
|---|---|---|---|
| 1 | 必须 | 解压离线包到 deploy_base_dir |
默认 /export/ops/k8s |
| 2 | 必须 | cp hosts_<场景> hosts 并编辑 IP、网卡、域名 |
见 §1.3 |
| 3 | 强烈推荐 | bash tools/verify_manifest.sh |
全量 SHA256,见 §3.1 |
| 4 | Vault 用户必须 | ansible-playbook playbook/preflight.yml --ask-vault-pass |
验证解密 + 预检;明文部署则去掉 --ask-vault-pass |
| 4 | 非 Vault 用户推荐 | ansible-playbook playbook/preflight.yml |
检查 CPU/内存/磁盘/网卡/关键离线包 |
| 5 | 可选 | 确认 harbor_force_regenerate_certs |
首次或改 domain_name 时设 true,见 §5.5 / Harbor 证书说明 |
| 6 | 必须 | 执行对应场景 playbook | 见 §4 |
Vault 部署前快速自检(任选其一):
bash
# A. 跑预检(推荐,同时验证 SSH 与离线包)
ansible-playbook playbook/preflight.yml --ask-vault-pass
# B. 仅验证 inventory 变量能否加载(不连远程改配置)
ansible-inventory --list -y | grep -A2 vault_harbor_pwd
# 需配合 --ask-vault-pass;若能看到解密后的值说明 Vault 正常
# C. 对单节点测 SSH(替换为 install 组某 IP)
ansible install -m ping --ask-vault-pass
常见失败原因:
- 使用了加密
vault.yml但忘记--ask-vault-pass→harbor_pwd变成 default 兜底值,Harbor push 401 vault.yml未创建但hosts引用了vault_harbor_pwd→ 同上,依赖 default 密码- 未跑
verify_manifest.sh→ 部署中途才发现 tar 包缺失或损坏
主 playbook 已内置 preflight (tag preflight),步骤 4 可合并进全量部署的第一步;仍建议首次部署前单独跑一遍,便于提前发现问题。
1.6 单 Master + Vault 完整部署示例
以下假设已解压离线包并 cd 到项目目录(默认 /export/ops/k8s):
bash
# 0. 准备 inventory
cp hosts_single hosts
vi hosts # 修改 IP、ansible_ssh_pass、domain_name 等
# 1. Vault 敏感变量(生产推荐;Lab 可跳过,改用 hosts 明文 harbor_pwd)
cp group_vars/all/vault.yml.example group_vars/all/vault.yml
vi group_vars/all/vault.yml
ansible-vault encrypt group_vars/all/vault.yml
# 2. 离线包完整性校验
bash tools/verify_manifest.sh
# 3. 预检(验证 Vault 解密 + SSH + 资源/离线包)
ansible-playbook playbook/preflight.yml --ask-vault-pass
# 4. 全量部署(单 Master + Worker)
ansible-playbook playbook/kubernetes-single.yml --ask-vault-pass
明文部署 (不用 Vault):去掉所有 --ask-vault-pass,并确保 hosts 中 harbor_pwd 的 default('...') 与 Harbor 实际密码一致。
2. 部署流程图
2.1 总体流程(部署前 → 部署后)
#mermaid-svg-Q1ou9h2X7BTdgCxs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Q1ou9h2X7BTdgCxs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Q1ou9h2X7BTdgCxs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Q1ou9h2X7BTdgCxs .error-icon{fill:#552222;}#mermaid-svg-Q1ou9h2X7BTdgCxs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Q1ou9h2X7BTdgCxs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Q1ou9h2X7BTdgCxs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Q1ou9h2X7BTdgCxs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Q1ou9h2X7BTdgCxs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Q1ou9h2X7BTdgCxs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Q1ou9h2X7BTdgCxs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Q1ou9h2X7BTdgCxs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Q1ou9h2X7BTdgCxs .marker.cross{stroke:#333333;}#mermaid-svg-Q1ou9h2X7BTdgCxs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Q1ou9h2X7BTdgCxs p{margin:0;}#mermaid-svg-Q1ou9h2X7BTdgCxs .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Q1ou9h2X7BTdgCxs .cluster-label text{fill:#333;}#mermaid-svg-Q1ou9h2X7BTdgCxs .cluster-label span{color:#333;}#mermaid-svg-Q1ou9h2X7BTdgCxs .cluster-label span p{background-color:transparent;}#mermaid-svg-Q1ou9h2X7BTdgCxs .label text,#mermaid-svg-Q1ou9h2X7BTdgCxs span{fill:#333;color:#333;}#mermaid-svg-Q1ou9h2X7BTdgCxs .node rect,#mermaid-svg-Q1ou9h2X7BTdgCxs .node circle,#mermaid-svg-Q1ou9h2X7BTdgCxs .node ellipse,#mermaid-svg-Q1ou9h2X7BTdgCxs .node polygon,#mermaid-svg-Q1ou9h2X7BTdgCxs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Q1ou9h2X7BTdgCxs .rough-node .label text,#mermaid-svg-Q1ou9h2X7BTdgCxs .node .label text,#mermaid-svg-Q1ou9h2X7BTdgCxs .image-shape .label,#mermaid-svg-Q1ou9h2X7BTdgCxs .icon-shape .label{text-anchor:middle;}#mermaid-svg-Q1ou9h2X7BTdgCxs .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Q1ou9h2X7BTdgCxs .rough-node .label,#mermaid-svg-Q1ou9h2X7BTdgCxs .node .label,#mermaid-svg-Q1ou9h2X7BTdgCxs .image-shape .label,#mermaid-svg-Q1ou9h2X7BTdgCxs .icon-shape .label{text-align:center;}#mermaid-svg-Q1ou9h2X7BTdgCxs .node.clickable{cursor:pointer;}#mermaid-svg-Q1ou9h2X7BTdgCxs .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Q1ou9h2X7BTdgCxs .arrowheadPath{fill:#333333;}#mermaid-svg-Q1ou9h2X7BTdgCxs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Q1ou9h2X7BTdgCxs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Q1ou9h2X7BTdgCxs .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Q1ou9h2X7BTdgCxs .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Q1ou9h2X7BTdgCxs .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Q1ou9h2X7BTdgCxs .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Q1ou9h2X7BTdgCxs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Q1ou9h2X7BTdgCxs .cluster text{fill:#333;}#mermaid-svg-Q1ou9h2X7BTdgCxs .cluster span{color:#333;}#mermaid-svg-Q1ou9h2X7BTdgCxs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Q1ou9h2X7BTdgCxs .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Q1ou9h2X7BTdgCxs rect.text{fill:none;stroke-width:0;}#mermaid-svg-Q1ou9h2X7BTdgCxs .icon-shape,#mermaid-svg-Q1ou9h2X7BTdgCxs .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Q1ou9h2X7BTdgCxs .icon-shape p,#mermaid-svg-Q1ou9h2X7BTdgCxs .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Q1ou9h2X7BTdgCxs .icon-shape .label rect,#mermaid-svg-Q1ou9h2X7BTdgCxs .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Q1ou9h2X7BTdgCxs .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Q1ou9h2X7BTdgCxs .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Q1ou9h2X7BTdgCxs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
否
是
上传离线包并解压
cp hosts_xxx hosts 并编辑
使用 Vault?
配置并加密 vault.yml
bash tools/verify_manifest.sh
校验通过?
ansible-playbook preflight.yml
ansible-playbook 选择场景
kubernetes-single.yml
kubernetes-ha.yml
add_node.yml
harbor.yml / haproxy.yml / keepalive.yml
preflight.yml
部署后验证 kubectl get nodes
组件就绪 / 继续部署 K8s
预检报告
2.2 单节点部署流程
Playbook:playbook/kubernetes-single.yml
#mermaid-svg-YWPKOHMOzRZJXgWk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YWPKOHMOzRZJXgWk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YWPKOHMOzRZJXgWk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YWPKOHMOzRZJXgWk .error-icon{fill:#552222;}#mermaid-svg-YWPKOHMOzRZJXgWk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YWPKOHMOzRZJXgWk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YWPKOHMOzRZJXgWk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YWPKOHMOzRZJXgWk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YWPKOHMOzRZJXgWk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YWPKOHMOzRZJXgWk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YWPKOHMOzRZJXgWk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YWPKOHMOzRZJXgWk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YWPKOHMOzRZJXgWk .marker.cross{stroke:#333333;}#mermaid-svg-YWPKOHMOzRZJXgWk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YWPKOHMOzRZJXgWk p{margin:0;}#mermaid-svg-YWPKOHMOzRZJXgWk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YWPKOHMOzRZJXgWk .cluster-label text{fill:#333;}#mermaid-svg-YWPKOHMOzRZJXgWk .cluster-label span{color:#333;}#mermaid-svg-YWPKOHMOzRZJXgWk .cluster-label span p{background-color:transparent;}#mermaid-svg-YWPKOHMOzRZJXgWk .label text,#mermaid-svg-YWPKOHMOzRZJXgWk span{fill:#333;color:#333;}#mermaid-svg-YWPKOHMOzRZJXgWk .node rect,#mermaid-svg-YWPKOHMOzRZJXgWk .node circle,#mermaid-svg-YWPKOHMOzRZJXgWk .node ellipse,#mermaid-svg-YWPKOHMOzRZJXgWk .node polygon,#mermaid-svg-YWPKOHMOzRZJXgWk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YWPKOHMOzRZJXgWk .rough-node .label text,#mermaid-svg-YWPKOHMOzRZJXgWk .node .label text,#mermaid-svg-YWPKOHMOzRZJXgWk .image-shape .label,#mermaid-svg-YWPKOHMOzRZJXgWk .icon-shape .label{text-anchor:middle;}#mermaid-svg-YWPKOHMOzRZJXgWk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YWPKOHMOzRZJXgWk .rough-node .label,#mermaid-svg-YWPKOHMOzRZJXgWk .node .label,#mermaid-svg-YWPKOHMOzRZJXgWk .image-shape .label,#mermaid-svg-YWPKOHMOzRZJXgWk .icon-shape .label{text-align:center;}#mermaid-svg-YWPKOHMOzRZJXgWk .node.clickable{cursor:pointer;}#mermaid-svg-YWPKOHMOzRZJXgWk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YWPKOHMOzRZJXgWk .arrowheadPath{fill:#333333;}#mermaid-svg-YWPKOHMOzRZJXgWk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YWPKOHMOzRZJXgWk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YWPKOHMOzRZJXgWk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YWPKOHMOzRZJXgWk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YWPKOHMOzRZJXgWk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YWPKOHMOzRZJXgWk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YWPKOHMOzRZJXgWk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YWPKOHMOzRZJXgWk .cluster text{fill:#333;}#mermaid-svg-YWPKOHMOzRZJXgWk .cluster span{color:#333;}#mermaid-svg-YWPKOHMOzRZJXgWk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YWPKOHMOzRZJXgWk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YWPKOHMOzRZJXgWk rect.text{fill:none;stroke-width:0;}#mermaid-svg-YWPKOHMOzRZJXgWk .icon-shape,#mermaid-svg-YWPKOHMOzRZJXgWk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YWPKOHMOzRZJXgWk .icon-shape p,#mermaid-svg-YWPKOHMOzRZJXgWk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YWPKOHMOzRZJXgWk .icon-shape .label rect,#mermaid-svg-YWPKOHMOzRZJXgWk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YWPKOHMOzRZJXgWk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YWPKOHMOzRZJXgWk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YWPKOHMOzRZJXgWk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 插件 tag: addon / nfs
nfs-server 各节点
add-on Master 部署 Calico/Ingress/Metrics/NFS
集群 tag: k8s
k8s-master 导入镜像 push Harbor
k8s-node 导入镜像 push Harbor
add-cniPlugin-on 安装 CNI 二进制
kubeadm-single init Master
kubeadm-shell-single 生成 join 命令
kubeadm-node Worker join
upgrade-parameter 调整 apiserver 参数
Harbor 镜像仓库 tag: harbor
docker-ce + docker-compose
harbor-ssl 生成证书
harbor 安装
预检与基础
preflight 预检
common / kube-bin / containerd
install 创建 tmp 目录
2.3 高可用部署流程
Playbook:playbook/kubernetes-ha.yml
#mermaid-svg-ycDm0kPy5QKz9vLX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ycDm0kPy5QKz9vLX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ycDm0kPy5QKz9vLX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ycDm0kPy5QKz9vLX .error-icon{fill:#552222;}#mermaid-svg-ycDm0kPy5QKz9vLX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ycDm0kPy5QKz9vLX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ycDm0kPy5QKz9vLX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ycDm0kPy5QKz9vLX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ycDm0kPy5QKz9vLX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ycDm0kPy5QKz9vLX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ycDm0kPy5QKz9vLX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ycDm0kPy5QKz9vLX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ycDm0kPy5QKz9vLX .marker.cross{stroke:#333333;}#mermaid-svg-ycDm0kPy5QKz9vLX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ycDm0kPy5QKz9vLX p{margin:0;}#mermaid-svg-ycDm0kPy5QKz9vLX .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ycDm0kPy5QKz9vLX .cluster-label text{fill:#333;}#mermaid-svg-ycDm0kPy5QKz9vLX .cluster-label span{color:#333;}#mermaid-svg-ycDm0kPy5QKz9vLX .cluster-label span p{background-color:transparent;}#mermaid-svg-ycDm0kPy5QKz9vLX .label text,#mermaid-svg-ycDm0kPy5QKz9vLX span{fill:#333;color:#333;}#mermaid-svg-ycDm0kPy5QKz9vLX .node rect,#mermaid-svg-ycDm0kPy5QKz9vLX .node circle,#mermaid-svg-ycDm0kPy5QKz9vLX .node ellipse,#mermaid-svg-ycDm0kPy5QKz9vLX .node polygon,#mermaid-svg-ycDm0kPy5QKz9vLX .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ycDm0kPy5QKz9vLX .rough-node .label text,#mermaid-svg-ycDm0kPy5QKz9vLX .node .label text,#mermaid-svg-ycDm0kPy5QKz9vLX .image-shape .label,#mermaid-svg-ycDm0kPy5QKz9vLX .icon-shape .label{text-anchor:middle;}#mermaid-svg-ycDm0kPy5QKz9vLX .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ycDm0kPy5QKz9vLX .rough-node .label,#mermaid-svg-ycDm0kPy5QKz9vLX .node .label,#mermaid-svg-ycDm0kPy5QKz9vLX .image-shape .label,#mermaid-svg-ycDm0kPy5QKz9vLX .icon-shape .label{text-align:center;}#mermaid-svg-ycDm0kPy5QKz9vLX .node.clickable{cursor:pointer;}#mermaid-svg-ycDm0kPy5QKz9vLX .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ycDm0kPy5QKz9vLX .arrowheadPath{fill:#333333;}#mermaid-svg-ycDm0kPy5QKz9vLX .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ycDm0kPy5QKz9vLX .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ycDm0kPy5QKz9vLX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ycDm0kPy5QKz9vLX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ycDm0kPy5QKz9vLX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ycDm0kPy5QKz9vLX .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ycDm0kPy5QKz9vLX .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ycDm0kPy5QKz9vLX .cluster text{fill:#333;}#mermaid-svg-ycDm0kPy5QKz9vLX .cluster span{color:#333;}#mermaid-svg-ycDm0kPy5QKz9vLX div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ycDm0kPy5QKz9vLX .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ycDm0kPy5QKz9vLX rect.text{fill:none;stroke-width:0;}#mermaid-svg-ycDm0kPy5QKz9vLX .icon-shape,#mermaid-svg-ycDm0kPy5QKz9vLX .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ycDm0kPy5QKz9vLX .icon-shape p,#mermaid-svg-ycDm0kPy5QKz9vLX .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ycDm0kPy5QKz9vLX .icon-shape .label rect,#mermaid-svg-ycDm0kPy5QKz9vLX .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ycDm0kPy5QKz9vLX .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ycDm0kPy5QKz9vLX .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ycDm0kPy5QKz9vLX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 插件
nfs-server
unscheduler cordon 不可调度节点
add-on 部署网络与插件
集群 tag: k8s
kubeadm_master1 导入/推送 Master 镜像
k8s-node 导入/推送 Node 镜像
add-cniPlugin-on
kubeadm-master1 首个 Master init
kubeadm-shell-ha 生成 join 脚本
kubeadm-master-other 其他 Master join
kubeadm-node Worker join
upgrade-parameter
外部 etcd tag: etcd 仅 nocontainer 模式
etcd-ssl 证书
etcd 集群启动
Harbor tag: harbor
docker-ce
harbor-ssl
harbor
负载均衡 tag: lb 可选
haproxy
keepalive VIP
预检与基础
preflight
common + common-ha + kube-bin + containerd
install
etcd_install_mode=container(默认)时跳过外部 etcd 段,使用 kubeadm 内置 etcd。
2.4 Worker 扩容流程
Playbook:playbook/add_node.yml(集群已存在)
#mermaid-svg-lk3t6pOPUYfxjSkI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lk3t6pOPUYfxjSkI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lk3t6pOPUYfxjSkI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lk3t6pOPUYfxjSkI .error-icon{fill:#552222;}#mermaid-svg-lk3t6pOPUYfxjSkI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lk3t6pOPUYfxjSkI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lk3t6pOPUYfxjSkI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lk3t6pOPUYfxjSkI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lk3t6pOPUYfxjSkI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lk3t6pOPUYfxjSkI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lk3t6pOPUYfxjSkI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lk3t6pOPUYfxjSkI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lk3t6pOPUYfxjSkI .marker.cross{stroke:#333333;}#mermaid-svg-lk3t6pOPUYfxjSkI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lk3t6pOPUYfxjSkI p{margin:0;}#mermaid-svg-lk3t6pOPUYfxjSkI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lk3t6pOPUYfxjSkI .cluster-label text{fill:#333;}#mermaid-svg-lk3t6pOPUYfxjSkI .cluster-label span{color:#333;}#mermaid-svg-lk3t6pOPUYfxjSkI .cluster-label span p{background-color:transparent;}#mermaid-svg-lk3t6pOPUYfxjSkI .label text,#mermaid-svg-lk3t6pOPUYfxjSkI span{fill:#333;color:#333;}#mermaid-svg-lk3t6pOPUYfxjSkI .node rect,#mermaid-svg-lk3t6pOPUYfxjSkI .node circle,#mermaid-svg-lk3t6pOPUYfxjSkI .node ellipse,#mermaid-svg-lk3t6pOPUYfxjSkI .node polygon,#mermaid-svg-lk3t6pOPUYfxjSkI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lk3t6pOPUYfxjSkI .rough-node .label text,#mermaid-svg-lk3t6pOPUYfxjSkI .node .label text,#mermaid-svg-lk3t6pOPUYfxjSkI .image-shape .label,#mermaid-svg-lk3t6pOPUYfxjSkI .icon-shape .label{text-anchor:middle;}#mermaid-svg-lk3t6pOPUYfxjSkI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lk3t6pOPUYfxjSkI .rough-node .label,#mermaid-svg-lk3t6pOPUYfxjSkI .node .label,#mermaid-svg-lk3t6pOPUYfxjSkI .image-shape .label,#mermaid-svg-lk3t6pOPUYfxjSkI .icon-shape .label{text-align:center;}#mermaid-svg-lk3t6pOPUYfxjSkI .node.clickable{cursor:pointer;}#mermaid-svg-lk3t6pOPUYfxjSkI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lk3t6pOPUYfxjSkI .arrowheadPath{fill:#333333;}#mermaid-svg-lk3t6pOPUYfxjSkI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lk3t6pOPUYfxjSkI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lk3t6pOPUYfxjSkI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lk3t6pOPUYfxjSkI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lk3t6pOPUYfxjSkI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lk3t6pOPUYfxjSkI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lk3t6pOPUYfxjSkI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lk3t6pOPUYfxjSkI .cluster text{fill:#333;}#mermaid-svg-lk3t6pOPUYfxjSkI .cluster span{color:#333;}#mermaid-svg-lk3t6pOPUYfxjSkI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lk3t6pOPUYfxjSkI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lk3t6pOPUYfxjSkI rect.text{fill:none;stroke-width:0;}#mermaid-svg-lk3t6pOPUYfxjSkI .icon-shape,#mermaid-svg-lk3t6pOPUYfxjSkI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lk3t6pOPUYfxjSkI .icon-shape p,#mermaid-svg-lk3t6pOPUYfxjSkI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lk3t6pOPUYfxjSkI .icon-shape .label rect,#mermaid-svg-lk3t6pOPUYfxjSkI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lk3t6pOPUYfxjSkI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lk3t6pOPUYfxjSkI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lk3t6pOPUYfxjSkI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} common-node + kube-bin + containerd
install tmp 目录
k8s-node 导入镜像 push Harbor
master: kubeadm-shell-get-token 生成 join 命令
nfs-server 新节点
add-cniPlugin-on 安装 CNI 二进制
add-node 执行 kubeadm join
2.5 add-on 插件部署细节
仅在 Master 上执行 add-on role;CNI 二进制由 add-cniPlugin-on 提前装到所有节点。
#mermaid-svg-9tc5HmIqhDBs7vNT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9tc5HmIqhDBs7vNT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9tc5HmIqhDBs7vNT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9tc5HmIqhDBs7vNT .error-icon{fill:#552222;}#mermaid-svg-9tc5HmIqhDBs7vNT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9tc5HmIqhDBs7vNT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9tc5HmIqhDBs7vNT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9tc5HmIqhDBs7vNT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9tc5HmIqhDBs7vNT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9tc5HmIqhDBs7vNT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9tc5HmIqhDBs7vNT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9tc5HmIqhDBs7vNT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9tc5HmIqhDBs7vNT .marker.cross{stroke:#333333;}#mermaid-svg-9tc5HmIqhDBs7vNT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9tc5HmIqhDBs7vNT p{margin:0;}#mermaid-svg-9tc5HmIqhDBs7vNT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9tc5HmIqhDBs7vNT .cluster-label text{fill:#333;}#mermaid-svg-9tc5HmIqhDBs7vNT .cluster-label span{color:#333;}#mermaid-svg-9tc5HmIqhDBs7vNT .cluster-label span p{background-color:transparent;}#mermaid-svg-9tc5HmIqhDBs7vNT .label text,#mermaid-svg-9tc5HmIqhDBs7vNT span{fill:#333;color:#333;}#mermaid-svg-9tc5HmIqhDBs7vNT .node rect,#mermaid-svg-9tc5HmIqhDBs7vNT .node circle,#mermaid-svg-9tc5HmIqhDBs7vNT .node ellipse,#mermaid-svg-9tc5HmIqhDBs7vNT .node polygon,#mermaid-svg-9tc5HmIqhDBs7vNT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9tc5HmIqhDBs7vNT .rough-node .label text,#mermaid-svg-9tc5HmIqhDBs7vNT .node .label text,#mermaid-svg-9tc5HmIqhDBs7vNT .image-shape .label,#mermaid-svg-9tc5HmIqhDBs7vNT .icon-shape .label{text-anchor:middle;}#mermaid-svg-9tc5HmIqhDBs7vNT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9tc5HmIqhDBs7vNT .rough-node .label,#mermaid-svg-9tc5HmIqhDBs7vNT .node .label,#mermaid-svg-9tc5HmIqhDBs7vNT .image-shape .label,#mermaid-svg-9tc5HmIqhDBs7vNT .icon-shape .label{text-align:center;}#mermaid-svg-9tc5HmIqhDBs7vNT .node.clickable{cursor:pointer;}#mermaid-svg-9tc5HmIqhDBs7vNT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9tc5HmIqhDBs7vNT .arrowheadPath{fill:#333333;}#mermaid-svg-9tc5HmIqhDBs7vNT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9tc5HmIqhDBs7vNT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9tc5HmIqhDBs7vNT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9tc5HmIqhDBs7vNT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9tc5HmIqhDBs7vNT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9tc5HmIqhDBs7vNT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9tc5HmIqhDBs7vNT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9tc5HmIqhDBs7vNT .cluster text{fill:#333;}#mermaid-svg-9tc5HmIqhDBs7vNT .cluster span{color:#333;}#mermaid-svg-9tc5HmIqhDBs7vNT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9tc5HmIqhDBs7vNT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9tc5HmIqhDBs7vNT rect.text{fill:none;stroke-width:0;}#mermaid-svg-9tc5HmIqhDBs7vNT .icon-shape,#mermaid-svg-9tc5HmIqhDBs7vNT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9tc5HmIqhDBs7vNT .icon-shape p,#mermaid-svg-9tc5HmIqhDBs7vNT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9tc5HmIqhDBs7vNT .icon-shape .label rect,#mermaid-svg-9tc5HmIqhDBs7vNT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9tc5HmIqhDBs7vNT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9tc5HmIqhDBs7vNT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9tc5HmIqhDBs7vNT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Master 节点
渲染 templates/*.j2
kubectl 部署网络插件
Metrics Server
Ingress Nginx
NFS Provisioner
所有 K8s 节点
add-cniPlugin-on
/opt/cni/bin/
| 步骤 | Role | 执行节点 | 作用 |
|---|---|---|---|
| CNI 二进制 | add-cniPlugin-on |
Master + Worker | 解压 cni-plugins 到 /opt/cni/bin/ |
| 网络插件 | add-on |
Master | Calico Operator / Flannel |
| 监控 | add-on |
Master | Metrics Server |
| 入口 | add-on |
Master | Ingress Nginx |
| 存储 | add-on |
Master | NFS Client Provisioner |
3. manifest.json 离线包清单(Linux 用法)
manifest.json 记录 roles/*/files/ 下所有离线包的路径、大小和 SHA256 ,并包含 component_versions 版本摘要,用于:
- 解压后检查包是否完整、是否被篡改
- 快速查看当前离线包对应的组件版本
- 更新离线包后重新生成清单
- 交付前做一致性校验
当前清单包含 56 个离线包(含 containerd 1.7.22、CNI plugins v1.6.2 等),CNI 包唯一路径为 roles/add-cniPlugin-on/files/。
3.1 部署前校验(推荐)
在 k8s 目录下执行:
bash
cd /export/ops/k8s
bash tools/verify_manifest.sh
输出 All offline packages verified. 表示离线包齐全且哈希一致;若有缺失或损坏会列出具体文件并返回非 0 退出码。
指定其他 manifest 路径:
bash
bash tools/verify_manifest.sh /path/to/manifest.json
3.2 手动抽查单个文件
从 manifest 中查某个包的 sha256,与本地文件对比:
bash
# 查看某包期望哈希
python3 -c "
import json
data=json.load(open('manifest.json'))
for p in data['packages']:
if 'kube-apiserver' in p['path']:
print(p['path'], p['sha256'])
"
# 计算本地文件实际哈希
sha256sum roles/k8s-master/files/kube-apiserver.v1.28.15.tar
3.3 列出缺失的包
bash
python3 - <<'PY'
import json, os
data = json.load(open('manifest.json'))
for p in data['packages']:
if not os.path.exists(p['path']):
print('MISSING:', p['path'])
PY
3.4 更新离线包后重新生成 manifest
在 Linux 上:
bash
cd /export/ops/k8s
bash tools/generate_manifest.sh # 扫描离线包并写入 SHA256
# generate_manifest.sh 会自动调用 enrich_manifest.py 补充 component_versions
在 Windows 上:
powershell
powershell -ExecutionPolicy Bypass -File tools/generate_manifest.ps1
python tools/enrich_manifest.py # 若仅需刷新版本元数据
查看版本摘要:
bash
python3 -c "import json; d=json.load(open('manifest.json')); print(json.dumps(d.get('component_versions',{}), indent=2))"
生成后建议再次校验:
bash
bash tools/verify_manifest.sh
3.4.1 替换 kube-bin 为 kubernetes_version(kubeadm / kubectl / kubelet 对齐)
当 kubernetes_version 与离线 RPM(如旧版 kube1.28.2.tar.gz)不一致时,在 有网络的 Linux 上执行(默认使用阿里云镜像,无需访问 dl.k8s.io / pkgs.k8s.io):
bash
cd /export/ops/k8s
K8S_VERSION=1.28.15 bash tools/prepare_kube_bin.sh
# 可选镜像: KUBE_MIRROR=tsinghua | aliyun-ecs(阿里云 ECS 内网)
bash tools/generate_manifest.sh
bash tools/verify_manifest.sh
国内 RPM 镜像地址 (脚本内 KUBE_MIRROR 切换):
| KUBE_MIRROR | RPM 基址 |
|---|---|
aliyun(默认) |
https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.28/rpm/x86_64/ |
aliyun-ecs |
http://mirrors.cloud.aliyuncs.com/kubernetes-new/core/stable/v1.28/rpm/x86_64/ |
tsinghua |
https://mirrors.tuna.tsinghua.edu.cn/kubernetes/core:/stable:/v1.28/rpm/x86_64/ |
脚本会:
- 从上述国内镜像下载 1.28.15 的 kubeadm/kubectl/kubelet RPM
- 用
rpm2cpio从 RPM 提取三份二进制到roles/kube-bin/files/(与 RPM 同版本,无需 dl.k8s.io) - 保留
kube/目录内 conntrack、cri-tools 等依赖 RPM(可从旧kube1.28.2.tar.gz解压) - 打包为
roles/kube-bin/files/kube1.28.15.tar.gz
prepare_kube_bin.sh不会被 Ansible 自动调用。 它是部署前在控制机上手动执行的离线包准备脚本;Ansible 只使用脚本生成好的文件,不会联网下载。
| 阶段 | 操作 | 执行方式 |
|---|---|---|
| 部署前(一次性 / 升级版本时) | prepare_kube_bin.sh → generate_manifest.sh |
手动在控制机执行 |
| 部署前检查 | preflight / verify_manifest.sh |
手动或 playbook;仅检查文件是否存在,不下载 |
| 部署时 | kube-bin role:解压 tar → yum install RPM → 覆盖二进制 → 版本 assert |
Ansible 自动 (playbook 跑到 base / kube-bin 时) |
部署时 kube-bin role 先 yum install RPM(提供 kubelet systemd),再覆盖三份二进制并 assert 版本与 kubernetes_version 一致 。若未先跑 prepare_kube_bin.sh,preflight 会报离线包缺失。
3.5 与 Ansible preflight 的关系
主 playbook 内置 preflight 角色,会检查部分关键离线包是否存在。verify_manifest.sh 做全量 SHA256 校验,建议在首次部署前手动执行一次。
bash
# 仅跑预检
ansible-playbook playbook/preflight.yml
# 或部署时只跑 preflight 段
ansible-playbook playbook/kubernetes-single.yml --tags preflight
4. 全部署方式
所有命令均在 k8s 项目目录下执行 (ansible.cfg 使用相对路径)。通用前置步骤:
bash
cp hosts_<场景> hosts # 选择下方对应 inventory 模板
vi hosts # 修改 IP、ansible_ssh_pass、domain_name、pod_cidr 等
# (生产推荐)Vault 敏感变量 --- 完整流程见 §1.6
cp group_vars/all/vault.yml.example group_vars/all/vault.yml
vi group_vars/all/vault.yml
ansible-vault encrypt group_vars/all/vault.yml
bash tools/verify_manifest.sh
ansible-playbook playbook/preflight.yml --ask-vault-pass
# 明文部署时去掉 --ask-vault-pass
单 Master 全量部署(Vault 方式,与 §1.6 一致):
bash
ansible-playbook playbook/kubernetes-single.yml --ask-vault-pass
4.0 部署方式总览
| # | 场景 | Playbook | Inventory 模板 | 说明 |
|---|---|---|---|---|
| 1 | 单 Master + Worker | kubernetes-single.yml |
hosts_single |
最小集群,含 Harbor + 全插件 |
| 2 | 三 Master HA(内置 etcd) | kubernetes-ha.yml |
hosts_ha |
默认 etcd_install_mode=container |
| 3 | 三 Master HA(外部 etcd) | kubernetes-ha.yml |
hosts_ha |
etcd_install_mode=nocontainer + [etcd] 组 |
| 4 | Worker 扩容 | add_node.yml |
hosts_add_node |
集群已存在,仅加节点 |
| 5 | 单独部署 Harbor | harbor.yml |
hosts_harbor |
仅装私有镜像仓库 |
| 6 | 单独部署 HAProxy | haproxy.yml |
hosts_haproxy |
HA 前置,转发 apiserver 6443 |
| 7 | 单独部署 Keepalived | keepalive.yml |
hosts_keepalive |
HA 前置,提供 VIP |
| 8 | 仅预检 | preflight.yml |
任意 hosts |
检查资源、网卡、离线包 |
HA 推荐组合顺序 (可写入同一 hosts 或分步执行):
keepalive.yml/haproxy.yml(可选,提供 VIP + 四层负载)harbor.yml或随kubernetes-ha.yml一并部署 Harborkubernetes-ha.yml拉起集群- 后续
add_node.yml扩容 Worker
#mermaid-svg-TRcV4AnqE3LK4maS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TRcV4AnqE3LK4maS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TRcV4AnqE3LK4maS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TRcV4AnqE3LK4maS .error-icon{fill:#552222;}#mermaid-svg-TRcV4AnqE3LK4maS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TRcV4AnqE3LK4maS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TRcV4AnqE3LK4maS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TRcV4AnqE3LK4maS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TRcV4AnqE3LK4maS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TRcV4AnqE3LK4maS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TRcV4AnqE3LK4maS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TRcV4AnqE3LK4maS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TRcV4AnqE3LK4maS .marker.cross{stroke:#333333;}#mermaid-svg-TRcV4AnqE3LK4maS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TRcV4AnqE3LK4maS p{margin:0;}#mermaid-svg-TRcV4AnqE3LK4maS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-TRcV4AnqE3LK4maS .cluster-label text{fill:#333;}#mermaid-svg-TRcV4AnqE3LK4maS .cluster-label span{color:#333;}#mermaid-svg-TRcV4AnqE3LK4maS .cluster-label span p{background-color:transparent;}#mermaid-svg-TRcV4AnqE3LK4maS .label text,#mermaid-svg-TRcV4AnqE3LK4maS span{fill:#333;color:#333;}#mermaid-svg-TRcV4AnqE3LK4maS .node rect,#mermaid-svg-TRcV4AnqE3LK4maS .node circle,#mermaid-svg-TRcV4AnqE3LK4maS .node ellipse,#mermaid-svg-TRcV4AnqE3LK4maS .node polygon,#mermaid-svg-TRcV4AnqE3LK4maS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TRcV4AnqE3LK4maS .rough-node .label text,#mermaid-svg-TRcV4AnqE3LK4maS .node .label text,#mermaid-svg-TRcV4AnqE3LK4maS .image-shape .label,#mermaid-svg-TRcV4AnqE3LK4maS .icon-shape .label{text-anchor:middle;}#mermaid-svg-TRcV4AnqE3LK4maS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TRcV4AnqE3LK4maS .rough-node .label,#mermaid-svg-TRcV4AnqE3LK4maS .node .label,#mermaid-svg-TRcV4AnqE3LK4maS .image-shape .label,#mermaid-svg-TRcV4AnqE3LK4maS .icon-shape .label{text-align:center;}#mermaid-svg-TRcV4AnqE3LK4maS .node.clickable{cursor:pointer;}#mermaid-svg-TRcV4AnqE3LK4maS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TRcV4AnqE3LK4maS .arrowheadPath{fill:#333333;}#mermaid-svg-TRcV4AnqE3LK4maS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TRcV4AnqE3LK4maS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TRcV4AnqE3LK4maS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TRcV4AnqE3LK4maS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TRcV4AnqE3LK4maS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TRcV4AnqE3LK4maS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TRcV4AnqE3LK4maS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TRcV4AnqE3LK4maS .cluster text{fill:#333;}#mermaid-svg-TRcV4AnqE3LK4maS .cluster span{color:#333;}#mermaid-svg-TRcV4AnqE3LK4maS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TRcV4AnqE3LK4maS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TRcV4AnqE3LK4maS rect.text{fill:none;stroke-width:0;}#mermaid-svg-TRcV4AnqE3LK4maS .icon-shape,#mermaid-svg-TRcV4AnqE3LK4maS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TRcV4AnqE3LK4maS .icon-shape p,#mermaid-svg-TRcV4AnqE3LK4maS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TRcV4AnqE3LK4maS .icon-shape .label rect,#mermaid-svg-TRcV4AnqE3LK4maS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TRcV4AnqE3LK4maS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TRcV4AnqE3LK4maS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TRcV4AnqE3LK4maS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 扩展
核心
HA 可选前置
keepalive VIP
haproxy 6443
Harbor
kubernetes-ha.yml
add_node.yml
4.1 Inventory 组说明
| 组名 | 用途 | 出现场景 |
|---|---|---|
all_hosts |
所有参与 K8s 的机器 | single / ha |
k8s_master |
Kubernetes Master 节点 | single / ha |
k8s_node |
Kubernetes Worker 节点 | single / ha |
kubeadm_master1 |
首个 init 的 Master | ha |
kubeadm_master_other |
后续 join 的 Master | ha |
install |
证书/配置/join 脚本存放节点 | 全部 |
harbor |
Harbor 安装节点 | single / ha / harbor |
haproxy |
HAProxy 节点 | ha / haproxy |
keepalive |
Keepalived 节点 | ha / keepalive |
etcd |
外部 etcd 节点 | ha(nocontainer) |
unscheduler |
需 cordon 的 Master IP | ha |
nodes |
待扩容 Worker | add_node |
master |
已有集群 Master(取 token) | add_node |
[install]组必填 :Harbor 证书、kubeadm 配置、join 脚本均保存在 install 节点的deploy_tmp_*目录,其他 role 通过 slurp 跨节点读取。
4.2 方式一:单 Master + Worker
适用:开发测试、最小生产(1 Master + 1+ Worker)。
Vault 方式(推荐生产) --- 完整命令见 §1.6:
bash
cp hosts_single hosts && vi hosts
cp group_vars/all/vault.yml.example group_vars/all/vault.yml && vi group_vars/all/vault.yml
ansible-vault encrypt group_vars/all/vault.yml
bash tools/verify_manifest.sh
ansible-playbook playbook/preflight.yml --ask-vault-pass
ansible-playbook playbook/kubernetes-single.yml --ask-vault-pass
明文方式(Lab/测试):
bash
cp hosts_single hosts
vi hosts
bash tools/verify_manifest.sh
ansible-playbook playbook/preflight.yml
ansible-playbook playbook/kubernetes-single.yml
Inventory 要点 (hosts_single):
ini
[k8s_master] # 1 台
[k8s_node] # 1+ 台,可与 master 同 IP(all-in-one)
[install] # 建议与 harbor 同机,存放 deploy_tmp
[harbor] # Harbor 安装节点
关键变量:
| 变量 | 建议值 | 说明 |
|---|---|---|
install_calico |
true |
false 则装 Flannel |
install_calico_method |
operator / manifests |
离线推荐先试 manifests |
k8s_master_schedulable |
false |
单节点 all-in-one 可设 true |
install_metrics / install_ingress / install_nfs |
true |
Addon 开关,false 跳过 push 与部署 |
coredns_replicas |
2(HA)/ 1(单 Master) |
CoreDNS 副本数 |
nfs_use_local |
true |
自动用 k8s_master[0] 作 NFS 服务端 |
部署内容:preflight → 基础环境 → Harbor → 推镜像 → CNI 二进制 → kubeadm init/join → NFS → Calico/Flannel + Ingress + Metrics + NFS Provisioner。
跳过 Harbor(使用已有仓库):
bash
ansible-playbook playbook/kubernetes-single.yml --skip-tags harbor
4.3 方式二:三 Master 高可用(内置 etcd,推荐)
适用 :生产 HA,etcd 由 kubeadm 以静态 Pod 管理(etcd_install_mode=container)。
bash
cp hosts_ha hosts
vi hosts
bash tools/verify_manifest.sh
ansible-playbook playbook/kubernetes-ha.yml
Inventory 要点 (hosts_ha):
ini
[k8s_master] # 3 台 Master
[kubeadm_master1] # 首个 init(1 台)
[kubeadm_master_other] # 其余 join(2 台)
[k8s_node] # Worker
[install] # 通常为首 Master
[harbor] # 可独立节点
[haproxy] # 可选,也可事先用 haproxy.yml 部署
[keepalive] # 可选 VIP
[unscheduler] # 不可调度 Pod 的 Master IP
HA 必改变量:
ini
[all:vars]
lb=10.241.241.19 # 必须与 keepalived_vip 或 HAProxy 地址一致
lb_port=6443
keepalived_vip=10.241.241.19 # [keepalive:vars]
etcd_install_mode=container # 默认,内置 etcd
k8s_master1_ip=...
k8s_master2_ip=...
k8s_master3_ip=...
isdomain=true # 可选,写 VIP 域名到 /etc/hosts
k8s_vip_domain=k8smaster.deploy.local
与方式三的区别 :无需 [etcd] 组;kubernetes-ha.yml 会跳过 etcd-ssl / etcd role。
4.4 方式三:三 Master 高可用(外部 etcd)
适用:etcd 独立部署、与 control-plane 分离。
bash
cp hosts_ha hosts
vi hosts
# 修改:
# etcd_install_mode=nocontainer
# 配置 [etcd] 组及 etcdname=etcd1/2/3
bash tools/verify_manifest.sh
ansible-playbook playbook/kubernetes-ha.yml
额外 Inventory:
ini
[etcd]
10.241.241.137 etcdname=etcd1
10.241.241.138 etcdname=etcd2
10.241.241.139 etcdname=etcd3
[all:vars]
etcd_install_mode=nocontainer
etcd_local_dataDir=/export/etcd
流程差异 :playbook 会执行 etcd-ssl(install 节点生成证书)→ etcd role(各 etcd 节点从 install slurp 证书)→ 使用 kubeadm-config-nocontainer.yaml init。
4.5 方式四:Worker 节点扩容
适用:集群已运行,新增 Worker。
bash
cp hosts_add_node hosts
vi hosts
ansible-playbook playbook/add_node.yml
Inventory 要点 (hosts_add_node):
ini
[nodes] # 新 Worker(可多台)
[install] # 与初次部署相同的 install 节点(存 join 脚本)
[master] # 已有集群任一 Master(生成 token)
前提 :Harbor 可达、集群 Master 正常、/etc/kubernetes/admin.conf 可用。
执行内容 :preflight → common → containerd → k8s-node 推镜像 → master 生成 add_node.sh → NFS → CNI → kubeadm join。
4.6 方式五:单独部署 Harbor
适用:先建镜像仓库,再部署 K8s;或 Harbor 独立升级。
bash
cp hosts_harbor hosts
vi hosts
ansible-playbook playbook/harbor.yml
Inventory 要点:
ini
[harbor] # Harbor 安装目标
[install] # 证书生成节点(harbor-ssl)
[all:vars]
domain_name=harbor.deploy.local
project_name=registry
harbor_user=admin
harbor_pwd=...
说明 :不含 containerd / K8s;Harbor 证书由 install 节点生成后 slurp 到 harbor 节点。后续部署 K8s 时可 --skip-tags harbor。
首次部署或修改域名后:
ini
harbor_force_regenerate_certs=true # group_vars 或 hosts,完成后改回 false
4.7 方式六:单独部署 HAProxy
适用:为三 Master apiserver 提供四层负载,配合 Keepalived VIP 使用。
bash
cp hosts_haproxy hosts
vi hosts
ansible-playbook playbook/haproxy.yml
Inventory 要点 (hosts_haproxy):
ini
[haproxy]
10.241.241.160
[haproxy:vars]
k8s_master1_ip=10.241.241.137
k8s_master2_ip=10.241.241.138
k8s_master3_ip=10.241.241.139
lb_port=6443
说明 :仅安装 HAProxy + 转发规则;VIP 由 Keepalived 提供时,hosts_ha 中 lb 填 VIP 而非 HAProxy 机器 IP。
4.8 方式七:单独部署 Keepalived
适用:为 HA 提供 VIP(常与 HAProxy 配合)。
bash
cp hosts_keepalive hosts
vi hosts
ansible-playbook playbook/keepalive.yml
Inventory 要点 (hosts_keepalive):
ini
[keepalive]
10.0.117.144 state=MASTER priority="99"
10.0.117.145 state=BACKUP priority="20"
[keepalive:vars]
keepalived_vip=10.0.117.146
install_keepalive=true
说明 :playbook 内 common-node 已注释,可按需取消注释做基础初始化。
4.9 方式八:仅预检
bash
ansible-playbook playbook/preflight.yml
# 或随主 playbook 只跑 preflight 段
ansible-playbook playbook/kubernetes-single.yml --tags preflight
检查项:CPU/内存、数据盘、网卡、kubeadm 离线包、apiserver 端口占用等。
4.10 分步 / 按需部署(Tags)
主 playbook 支持按 tag 执行或跳过:
bash
# 跳过 Harbor(已有仓库)
ansible-playbook playbook/kubernetes-single.yml --skip-tags harbor
# 只装基础 + containerd
ansible-playbook playbook/kubernetes-single.yml --tags base
# 只装 Harbor
ansible-playbook playbook/kubernetes-single.yml --tags harbor
# 只 init/join 集群
ansible-playbook playbook/kubernetes-single.yml --tags k8s
# 只装 CNI 二进制
ansible-playbook playbook/kubernetes-single.yml --tags cni
# 只装网络插件(Calico/Flannel)
ansible-playbook playbook/kubernetes-single.yml --tags calico,network
# 只装 Ingress
ansible-playbook playbook/kubernetes-single.yml --tags ingress
# 只装 Metrics Server
ansible-playbook playbook/kubernetes-single.yml --tags metrics
# 只装 NFS
ansible-playbook playbook/kubernetes-single.yml --tags nfs
# 跳过 LB 段(HA)
ansible-playbook playbook/kubernetes-ha.yml --skip-tags lb
# 跳过外部 etcd 段
ansible-playbook playbook/kubernetes-ha.yml --skip-tags etcd
| Tag | 说明 |
|---|---|
preflight |
预检 |
always |
与 preflight/base 等绑定,通常不单独跳过 |
base |
系统初始化、kube-bin、containerd、install 目录 |
harbor |
Harbor SSL + docker-ce + Harbor 安装 |
lb |
HAProxy / Keepalived(仅 HA playbook) |
etcd |
外部 etcd 证书与集群(nocontainer 时) |
k8s |
镜像导入、push、kubeadm init/join、upgrade-parameter |
nfs |
各节点 NFS Server 配置 |
network / cni |
CNI plugins 二进制 |
addon |
add-on 全部插件 |
calico |
网络插件(Calico/Flannel) |
ingress |
Ingress Nginx |
metrics |
Metrics Server |
nfs-provisioner |
NFS Client Provisioner |
4.11 网络插件选型
在 hosts [all:vars] 中配置:
| 目标 | 配置 |
|---|---|
| Calico Operator(默认) | install_calico=true + install_calico_method=operator |
| Calico Manifests(离线更稳) | install_calico=true + install_calico_method=manifests |
| Flannel | install_calico=false |
Calico 网络模式:calico_net_mode=vxlan 或 IPIP;网卡名 interface=ens33。
4.12 Vault 加密部署
Vault 不是部署前置硬性条件 ;启用后必须在每次 playbook 中带 --ask-vault-pass(或配置 vault_password_file)。
1. 初始化(一次性)
bash
cp group_vars/all/vault.yml.example group_vars/all/vault.yml
vi group_vars/all/vault.yml # 填写 vault_harbor_pwd 等
ansible-vault encrypt group_vars/all/vault.yml
vault.yml.example 当前支持:
| 变量 | 用途 |
|---|---|
vault_harbor_pwd |
Harbor admin 密码,供 `harbor_pwd={``{ vault_harbor_pwd |
vault_ansible_ssh_pass |
可选,若不想在 hosts 写 SSH 密码(需配合 host_vars) |
2. 部署前验证(必做,若已启用 Vault)
bash
ansible-playbook playbook/preflight.yml --ask-vault-pass
ansible install -m ping --ask-vault-pass
两条均成功,说明 Vault 解密、SSH、harbor_pwd 引用正常。
3. 正式部署
单 Master 完整流程(含前置步骤):
bash
cp hosts_single hosts && vi hosts
cp group_vars/all/vault.yml.example group_vars/all/vault.yml && vi group_vars/all/vault.yml
ansible-vault encrypt group_vars/all/vault.yml
bash tools/verify_manifest.sh
ansible-playbook playbook/preflight.yml --ask-vault-pass
ansible-playbook playbook/kubernetes-single.yml --ask-vault-pass
其他场景仅替换最后一行 playbook:
bash
ansible-playbook playbook/kubernetes-ha.yml --ask-vault-pass # HA
ansible-playbook playbook/add_node.yml --ask-vault-pass # 扩容 Worker
4. 修改已加密变量
bash
ansible-vault edit group_vars/all/vault.yml
5. 与明文部署对比
| 项目 | 明文 | Vault |
|---|---|---|
是否需要 vault.yml |
否 | 是(加密) |
| playbook 参数 | 无 | --ask-vault-pass |
harbor_pwd 来源 |
hosts 或 Jinja default(...) |
vault_harbor_pwd |
| 部署前 preflight | ansible-playbook playbook/preflight.yml |
同上 + --ask-vault-pass |
hosts 中推荐写法:
ini
harbor_pwd={{ vault_harbor_pwd | default('你的Harbor密码') }}
明文测试时可不写 vault.yml,但请把 default('...') 改成与 Harbor 安装脚本一致的密码。
5. 变量说明
变量分两层:group_vars/all/vars.yml(全局默认) 与 hosts 的 [all:vars](按环境覆盖)。
同名变量以 hosts / hosts_single 的 [all:vars] 为准(优先级更高)。例如:
| 变量 | vars.yml 默认 |
hosts_single 覆盖 |
实际生效 |
|---|---|---|---|
containerd_root |
/export/containerd |
/export/containerd |
相同,任一处改即可 |
coredns_replicas |
2 |
未写 | 用 vars.yml 的 2 |
calico_version |
v3.25.0 |
v3.25.0 |
若在 hosts 改则 hosts 优先 |
建议:通用默认值放 vars.yml,与环境相关的(IP、域名、密码、网卡、是否 HA)放 hosts_* 的 [all:vars]。
5.1 全局默认(group_vars/all/vars.yml)
| 变量 | 默认值 | 说明 |
|---|---|---|
deploy_base_dir |
/export/ops/k8s |
Ansible 项目解压路径 |
containerd_version |
1.7.22 |
containerd 离线包版本 |
containerd_root |
/export/containerd |
containerd 数据根目录,写入 config.toml |
kubernetes_version |
1.28.15 |
集群版本,须与离线镜像 tag 一致 |
kube_proxy_mode |
ipvs |
kube-proxy 模式:iptables / ipvs |
kube_proxy_ipvs_scheduler |
rr |
ipvs 调度算法(rr / wrr / lc 等) |
kube_proxy_ipvs_strict_arp |
true |
ipvs strictARP,Ingress/LB 场景建议开启 |
project_root |
{``{ playbook_dir }}/.. |
项目根目录,preflight 离线包检查使用 |
calico_version |
v3.25.0 |
Calico 组件 Harbor push tag |
calico_operator_version |
v1.29.3 |
Tigera Operator 版本 |
cni_plugins_version |
v1.6.2 |
CNI plugins 离线包版本 |
ingress_nginx_version |
v1.9.6 |
Ingress Controller 镜像版本 |
harbor_force_regenerate_certs |
false |
首次部署或改 domain 后临时设 true |
k8s_master_schedulable |
false |
Master 是否去掉污点、允许调度业务 Pod |
install_metrics |
true |
是否部署 metrics-server |
install_ingress |
true |
是否部署 ingress-nginx |
install_nfs |
true |
是否部署 NFS 客户端 Provisioner 与 nfs-server 角色 |
coredns_replicas |
2 |
CoreDNS Deployment 副本数(单 Master 建议 1) |
harbor_project_public |
true |
Harbor 项目是否公开;false 时启用私有仓库认证 |
harbor_image_pull_secret_name |
harbor-registry |
私有 Harbor 时各命名空间的 imagePullSecret 名称 |
kubeadm_bootstrap_token |
abcdef.0123456789abcdef |
仅 kubeadm init 用;join 脚本由 kubeadm token create 动态生成 |
enable_resource_allocatable |
true |
kubelet 系统/K8s 资源预留 |
kube_reserved_* / system_reserved_* |
见 §5.7 | 资源预留配额 |
5.2 Inventory 常用变量(hosts [all:vars])
ini
[all:vars]
# Harbor
domain_name=harbor.deploy.local
project_name=registry
harbor_user=admin
harbor_pwd={{ vault_harbor_pwd | default('changeme') }}
# containerd / K8s
containerd_root=/export/containerd
# kubernetes_version=1.28.15 # 可选,覆盖 group_vars
calico_version=v3.25.0 # 须与 Operator 内置 Calico 版本一致
k8s_master_schedulable=false # 单节点 all-in-one 可设 true
coredns_replicas=1 # 单 Master 建议 1;HA 建议 2+
install_metrics=true
install_ingress=true
install_nfs=true
harbor_project_public=true # false=私有 Harbor,自动配置 containerd auth + imagePullSecret
# harbor_image_pull_secret_name=harbor-registry
pod_cidr=10.244.0.0/16
service_cidr=10.96.0.0/12
etcd_local_dataDir=/export/etcd
interface=ens33
# kube-proxy(默认 ipvs,见 vars.yml)
kube_proxy_mode=ipvs
# kube_proxy_ipvs_scheduler=rr
# kube_proxy_ipvs_strict_arp=true
# 网络插件(二选一)
install_calico=true
install_calico_method=operator # operator 或 manifests
calico_net_mode=vxlan # vxlan / IPIP
# NFS
nfs_use_local=true # true 时使用 k8s_master[0] 作为 NFS 服务端
nfs_path=/export/nfs
nfs_client_cidr=192.168.56.0/24
5.3 版本与离线包对应关系
| 组件 | 变量 / 版本 | 离线包位置 |
|---|---|---|
| containerd | containerd_version |
roles/containerd/files/containerd/ |
| runc | 1.2.0(role 默认) |
roles/containerd/files/containerd/runc.amd64_1.2.0 |
| CNI plugins | cni_plugins_version |
roles/add-cniPlugin-on/files/cni-plugins-linux-amd64-v1.6.2.tgz |
| kube 控制面镜像 | kubernetes_version |
roles/k8s-master/files/kube-*.v1.28.15.tar |
| Calico | calico_version |
roles/k8s-master/files/cni.v3.25.1.tar 等(tar 文件名可与 push tag 不同) |
| Operator | calico_operator_version |
roles/k8s-master/files/operator.v1.29.3.tar |
| kubeadm / kubectl / kubelet | kubernetes_version |
roles/kube-bin/files/kubeadm、kubectl、kubelet(二进制) |
| kube RPM 包 | kubernetes_version |
roles/kube-bin/files/kube{``{ kubernetes_version }}.tar.gz(含 systemd/kubelet RPM) |
Calico Operator 离线说明 :
custom-resource.yaml.j2已配置registry/imagePath指向{``{ domain_name }}/{``{ project_name }}/,Operator 拉取cni:{``{ calico_version }}等镜像。Harbor push tag 由calico_version控制,与 Operator v1.29.3 内置的 v3.25.0 保持一致。
5.4 Addon 离线镜像
Addon 相关镜像 由 k8s-node 角色 push 到 Harbor (run_once);Ingress Controller 镜像由 k8s-master push。preflight 在对应开关为 true 时会校验离线包是否存在。
| Addon | 开关 | 离线包位置 |
|---|---|---|
| metrics-server | install_metrics |
roles/k8s-node/files/metrics-server.v0.7.2.tar |
| ingress-nginx(controller) | install_ingress |
roles/k8s-master/files/controller.v1.9.6.tar |
| ingress admission | install_ingress |
roles/k8s-node/files/kube-webhook-certgen.v1.4.4.tar |
| NFS Provisioner | install_nfs |
roles/k8s-node/files/nfs-subdir-external-provisioner.v4.0.2.tar |
关闭某 Addon 时,在 hosts 设 install_*=false,可跳过 Harbor push 与 kubectl apply;对应 tar 可不再准备。
5.5 私有 Harbor(harbor_project_public=false)
设为私有后,项目会自动完成三层认证,无需手工 kubectl create secret:
| 层级 | 实现 |
|---|---|
| 节点 containerd | config.toml.j2 写入 registry.configs."{``{ domain_name }}".auth(harbor_user / harbor_pwd),供 kubeadm 静态 Pod 与 kubelet 拉镜像 |
| Harbor 项目 | k8s-master 创建项目时 public: "false" |
| Kubernetes Secret | add-on 在 kube-system、ingress-nginx、nfs-provisioner、tigera-operator 等命名空间创建 harbor-registry Secret |
| Workload | addon 模板与 Calico Installation CR 注入 imagePullSecrets;并 patch kube-system 的 default / coredns ServiceAccount |
启用方式(hosts):
ini
harbor_project_public=false
harbor_user=admin
harbor_pwd={{ vault_harbor_pwd | default('changeme') }}
若 Harbor 项目已是公开 且想改为私有,需在 Harbor UI 改为 Private,或删除项目后设
harbor_project_public=false重新部署 push。
containerd cgroup :config.toml.j2已设systemd_cgroup = true,与 kubelet--cgroup-driver=systemd一致。
5.6 kube-proxy 模式
通过 kube_proxy_mode 控制集群 Service 转发模式,在 kubeadm init 配置 与 upgrade-parameter 阶段写入 kube-system/kube-proxy ConfigMap 并滚动重启 DaemonSet。
| 变量 | 默认值 | 说明 |
|---|---|---|
kube_proxy_mode |
ipvs |
iptables 或 ipvs |
kube_proxy_ipvs_scheduler |
rr |
仅 ipvs 生效 |
kube_proxy_ipvs_strict_arp |
true |
仅 ipvs 生效;配合 Ingress 建议 true |
涉及文件:
| 阶段 | 文件 |
|---|---|
| kubeadm init | roles/kubeadm-single/templates/init.default.yaml |
| kubeadm init (HA) | roles/etcd-ssl/templates/kubeadm-config-*.yaml |
| 集群就绪后 | roles/upgrade-parameter/templates/kube-proxy-config.conf.j2(ConfigMap 内 config.conf) |
| 集群就绪后 | roles/upgrade-parameter/templates/kube-proxy-configmap.yaml.j2(完整 ConfigMap manifest,kubectl apply) |
| 节点内核模块 | roles/common/tasks/kube-proxy-sysctl.yml(ipvs 时加载 ip_vs 等) |
切换为 iptables 时在 hosts 设 kube_proxy_mode=iptables;common 会跳过 ipvs 模块加载。
部署后 rendered 文件位于 Master 节点 /root/sshm/kube-proxy-configmap.yaml,便于审计与手工 kubectl apply -f。
5.7 kubelet 资源预留(resource-allocatable)
通过 enable_resource_allocatable 为 kubelet 配置系统/集群资源预留,避免业务 Pod 挤占 kubelet 与系统组件资源。
| 变量 | 默认值 | 说明 |
|---|---|---|
enable_resource_allocatable |
true |
设为 false 跳过 |
kube_reserved_cpu |
500m |
预留给 K8s 组件 |
kube_reserved_memory |
2Gi |
|
system_reserved_cpu |
600m |
预留给 OS |
system_reserved_memory |
1Gi |
实现位置:
| 阶段 | 文件 / Role |
|---|---|
| kubeadm init | init.default.yaml / kubeadm-config-*.yaml 的 KubeletConfiguration |
| init/join 后 | roles/resource-allocatable(systemd cgroup slice + /var/lib/kubelet/config.yaml blockinfile) |
| 模板 | roles/resource-allocatable/templates/kubelet-resource-reserved.yaml.j2 |
已接入 playbook:kubernetes-single.yml、kubernetes-ha.yml、add_node.yml(tag base/k8s)。
5.8 幂等与重跑说明
- kubeadm init/join :检测
/etc/kubernetes/admin.conf/kubelet.conf,已初始化/已加入则跳过 - kube-proxy / upgrade-parameter :
lineinfile使用regexp幂等;kube-proxy 通过kube-proxy-configmap.yaml.j2全量 apply - containerd:已安装目标版本时跳过解压/覆盖二进制
- resource-allocatable :
blockinfilemarker 幂等;重复执行不会叠加预留配置 - Harbor :首次
install.sh,已安装则docker-compose up -d;push 前等待/api/v2.0/health,已存在镜像跳过 push - NFS 服务端 :仅在
nfs_use_local的首个 master(或nfs_server指定主机)安装 - addon :CNI 失败会
fail终止;ingress/coredns 仅在 manifest 变更时重启;使用kubectl wait等待就绪 - 扩容 :
hosts_add_node须复制主集群[all:vars];add_node.yml不再在 worker 上装 NFS 服务端
6. 部署后验证
bash
kubectl get nodes
kubectl get pods -A
calicoctl node status # Calico 时
kubectl get sc # NFS StorageClass
7. 目录结构
k8s/
├── ansible.cfg
├── hosts / hosts_single / hosts_ha / ...
├── group_vars/all/vars.yml
├── manifest.json # 离线包 SHA256 清单
├── playbook/
│ ├── kubernetes-single.yml # 单 Master 全量部署
│ ├── kubernetes-ha.yml # 三 Master HA 全量部署
│ ├── add_node.yml # Worker 扩容
│ ├── harbor.yml # 单独 Harbor
│ ├── haproxy.yml # 单独 HAProxy
│ ├── keepalive.yml # 单独 Keepalived VIP
│ └── preflight.yml # 仅预检
├── hosts / hosts_single / hosts_ha / hosts_add_node
│ / hosts_harbor / hosts_haproxy / hosts_keepalive
├── roles/
└── tools/
├── generate_manifest.sh # Linux 生成清单
├── enrich_manifest.py # 补充 component_versions 元数据
├── verify_manifest.sh # Linux 校验清单
└── generate_manifest.ps1 # Windows 生成清单
8. 常见问题
Q: 部署前是否必须先配置 vault.yml?
A: 不强制。 Lab/测试可在 hosts 写明文 harbor_pwd、ansible_ssh_pass 直接部署。生产环境建议 cp vault.yml.example → vault.yml → ansible-vault encrypt,部署与 preflight 均加 --ask-vault-pass。若启用了加密 vault 却忘记传 vault 密码,harbor_pwd 会落到 Jinja default(...) 兜底值,导致 Harbor 401。完整命令见 §1.6、§4.12。
Q: 部署机是否需要与各节点 SSH 免密?
A: 不需要。 部署前在 hosts 配置 ansible_ssh_user=root 与 ansible_ssh_pass 即可(控制机需安装 sshpass)。部署过程中 common role 会向各节点写入项目内置 ops 公钥 ,用于运维登录,不会自动配置控制机免密。详见 §1.2。
Q: verify_manifest.sh 报 MISSING
A: 离线包未完整解压,或 roles/*/files/ 下文件缺失,重新解压或补全文件后重跑。
Q: verify_manifest.sh 报 MISMATCH
A: 文件内容与生成 manifest 时不一致(被替换或损坏),重新获取正确离线包或重新 generate_manifest.sh。
Q: prepare_kube_bin.sh 会在 Ansible 部署时自动执行吗?
A: 不会。 该脚本用于部署前在控制机手动 准备离线包(下载 RPM、提取二进制、打包 tar)。Ansible 的 kube-bin role 只会使用 roles/kube-bin/files/ 下已有文件进行安装与版本校验;preflight 也仅检查文件是否存在,不会联网下载。详见 §3.4.1。
Q: kubeadm / kubectl / kubelet 版本不一致?
A: 旧离线包 kube1.28.2.tar.gz 内 RPM 为 1.28.2,而集群镜像为 1.28.15。执行 K8S_VERSION=1.28.15 bash tools/prepare_kube_bin.sh 替换二进制与 RPM tar,再 generate_manifest.sh。kube-bin role 会覆盖三份二进制并校验与 kubernetes_version 一致。详见 §3.4.1。若仍出现 init 版本警告,确认三份二进制与 kube1.28.15.tar.gz 均已更新。
Q: Calico Operator 部署失败 / ImagePullBackOff
A: 确认 domain_name / project_name 与 Harbor 一致;calico_version 须与 Operator 内置版本匹配(v1.29.3 → v3.25.0);检查 custom-resource.yaml 中 registry / imagePath 是否已渲染为 Harbor 地址;节点 containerd 已信任 Harbor CA。
Q: CNI 插件找不到
A: 离线包应位于 roles/add-cniPlugin-on/files/cni-plugins-linux-amd64-v1.6.2.tgz,由 add-cniPlugin-on role 安装到 /opt/cni/bin/(不再由 add-on 重复安装)。
Q: containerd 数据目录不对
A: 在 hosts 中设置 containerd_root=/export/containerd(或其他大盘路径),会写入 /etc/containerd/config.toml 的 root 字段。
Q: Harbor 证书被反复删除
A: 确认 harbor_force_regenerate_certs=false(首次部署或修改 domain 后临时设 true,完成后改回 false)。
Q: Harbor 证书找不到 / Harbor 安装失败
A: 证书由 [install] 组节点上的 harbor-ssl 生成;harbor role 会通过 slurp 从 install 节点拉取证书。确认 [install] 组已定义且可 SSH 访问。
Q: join 脚本找不到
A: join 脚本保存在 install 节点的 {``{ deploy_tmp_k8s }}/ 下,join role 从 install 节点读取,无需控制机本地路径。
Q: kube-proxy 如何指定 iptables / ipvs 模式?
A: 在 hosts 或 vars.yml 设置 kube_proxy_mode=ipvs(默认)或 iptables。kubeadm 配置与 upgrade-parameter 会通过 kube-proxy-configmap.yaml.j2 写入 ConfigMap 并 rollout。详见 §5.6。
Q: kubelet 资源预留如何配置?
A: 默认 enable_resource_allocatable=true,可在 hosts 调整 kube_reserved_* / system_reserved_*。kubeadm init 与 resource-allocatable role 双路径写入。详见 §5.7。
Q: HA 环境 apiserver 连接失败
A: 确认 lb 与 keepalived_vip 或 HAProxy 地址一致(hosts_ha 已默认引用 keepalived_vip)。
Q: NFS PVC Pending
A: 检查 nfs_use_local / nfs_server、nfs_path,以及各节点 nfs-server role 是否成功。
Q: 从 Gitee 克隆后 preflight 报离线包 MISSING?
A: Git 仓库仅含 Ansible 代码 ,不含 roles/*/files/ 大文件。请按 §0.2 补全离线包并执行 bash tools/verify_manifest.sh。
9. 源码与反馈
| 项 | 链接 |
|---|---|
| Gitee 源码 | https://gitee.com/wxd_ops/k8s-1.28-ansible-offline |
| Issue / 建议 | 在 Gitee 仓库提交 Issue |
| CSDN | 教程正文以 Gitee master 分支为准;版本更新以仓库提交记录为准 |