欢迎光临
我们一直在努力

Kubernetes 1.28 离线部署实战(x86_64/amd64):Ansible + Harbor 一键搭建生产级 K8s 集群

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)

# 克隆源码
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 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):

    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 连通性(部署前建议执行):

    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、密码、变量:

    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 的步骤

    # 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 兜底):

    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 部署前快速自检(任选其一):

    # 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):

    # 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 目录下执行:

    cd /export/ops/k8s
    bash tools/verify_manifest.sh

    输出 All offline packages verified. 表示离线包齐全且哈希一致;若有缺失或损坏会列出具体文件并返回非 0 退出码。

    指定其他 manifest 路径:

    bash tools/verify_manifest.sh /path/to/manifest.json

    3.2 手动抽查单个文件

    从 manifest 中查某个包的 sha256,与本地文件对比:

    # 查看某包期望哈希
    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 列出缺失的包

    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 上:

    cd /export/ops/k8s
    bash tools/generate_manifest.sh # 扫描离线包并写入 SHA256
    # generate_manifest.sh 会自动调用 enrich_manifest.py 补充 component_versions

    在 Windows 上:

    powershell ExecutionPolicy Bypass File tools/generate_manifest.ps1
    python tools/enrich_manifest.py # 若仅需刷新版本元数据

    查看版本摘要:

    python3 -c "import json; d=json.load(open('manifest.json')); print(json.dumps(d.get('component_versions',{}), indent=2))"

    生成后建议再次校验:

    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):

    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_MIRRORRPM 基址
    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 校验,建议在首次部署前手动执行一次。

    # 仅跑预检
    ansible-playbook playbook/preflight.yml

    # 或部署时只跑 preflight 段
    ansible-playbook playbook/kubernetes-single.yml –tags preflight


    4. 全部署方式

    所有命令均在 k8s 项目目录下执行(ansible.cfg 使用相对路径)。通用前置步骤:

    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 一致):

    ansible-playbook playbook/kubernetes-single.yml –ask-vault-pass

    4.0 部署方式总览

    #场景PlaybookInventory 模板说明
    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 一并部署 Harbor
  • kubernetes-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:

    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/测试):

    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):

    [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(使用已有仓库):

    ansible-playbook playbook/kubernetes-single.yml –skip-tags harbor


    4.3 方式二:三 Master 高可用(内置 etcd,推荐)

    适用:生产 HA,etcd 由 kubeadm 以静态 Pod 管理(etcd_install_mode=container)。

    cp hosts_ha hosts
    vi hosts
    bash tools/verify_manifest.sh
    ansible-playbook playbook/kubernetes-ha.yml

    Inventory 要点(hosts_ha):

    [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 必改变量:

    [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 分离。

    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:

    [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。

    cp hosts_add_node hosts
    vi hosts
    ansible-playbook playbook/add_node.yml

    Inventory 要点(hosts_add_node):

    [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 独立升级。

    cp hosts_harbor hosts
    vi hosts
    ansible-playbook playbook/harbor.yml

    Inventory 要点:

    [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。

    首次部署或修改域名后:

    harbor_force_regenerate_certs=true # group_vars 或 hosts,完成后改回 false


    4.7 方式六:单独部署 HAProxy

    适用:为三 Master apiserver 提供四层负载,配合 Keepalived VIP 使用。

    cp hosts_haproxy hosts
    vi hosts
    ansible-playbook playbook/haproxy.yml

    Inventory 要点(hosts_haproxy):

    [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 配合)。

    cp hosts_keepalive hosts
    vi hosts
    ansible-playbook playbook/keepalive.yml

    Inventory 要点(hosts_keepalive):

    [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 方式八:仅预检

    ansible-playbook playbook/preflight.yml
    # 或随主 playbook 只跑 preflight 段
    ansible-playbook playbook/kubernetes-single.yml –tags preflight

    检查项:CPU/内存、数据盘、网卡、kubeadm 离线包、apiserver 端口占用等。


    4.10 分步 / 按需部署(Tags)

    主 playbook 支持按 tag 执行或跳过:

    # 跳过 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. 初始化(一次性)

    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 | default(…) }} 引用
    vault_ansible_ssh_pass 可选,若不想在 hosts 写 SSH 密码(需配合 host_vars)
    2. 部署前验证(必做,若已启用 Vault)

    ansible-playbook playbook/preflight.yml –ask-vault-pass
    ansible install -m ping –ask-vault-pass

    两条均成功,说明 Vault 解密、SSH、harbor_pwd 引用正常。

    3. 正式部署

    单 Master 完整流程(含前置步骤):

    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:

    ansible-playbook playbook/kubernetes-ha.yml –ask-vault-pass # HA
    ansible-playbook playbook/add_node.yml –ask-vault-pass # 扩容 Worker

    4. 修改已加密变量

    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 中推荐写法:

    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])

    [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):

    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:blockinfile marker 幂等;重复执行不会叠加预留配置
    • 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. 部署后验证

    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 分支为准;版本更新以仓库提交记录为准
    赞(0)
    未经允许不得转载:171主机测评 » Kubernetes 1.28 离线部署实战(x86_64/amd64):Ansible + Harbor 一键搭建生产级 K8s 集群
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址