跳转到内容

Go 编写的 K8s 二进制部署安装器

结论

用 Go 从零写了一个 Kubernetes 二进制部署安装器(k8s-installer),替代之前的 bash 脚本方案。已在双节点虚拟机上完成 reset 后裸机从零 init 的全流程验收:一次跑通,双节点 Ready,Calico / CoreDNS 全 Running。

bash 脚本方案 相比的核心差异:

  • 证书由安装器本地用 crypto/x509 签发后 SSH 下发,目标机器不需要装 cfssl
  • 全流程幂等可重跑,中断后从断点继续
  • 支持 worker 节点加入(add-node),bash 方案只能装单 master
  • 离线资产(二进制 + 镜像 tar 包)走本地 assets/ 目录,内网无外网可用

适用范围与环境

  • 运行安装器的机器:Windows / Linux / macOS(交叉编译一个二进制即可)
  • 目标节点:Ubuntu(实测 26.04),root SSH 密码或密钥可达
  • 已验证版本:Kubernetes v1.35.4 / etcd v3.6.10 / containerd 2.2.3 / Calico v3.25.0 / CoreDNS 1.11.3

架构

text
main.go                    命令行入口(flag 子命令分发)
cluster.yaml               集群定义(节点/SSH/代理)
assets/                    离线资产(二进制/镜像/清单)
pki/                       本地 CA 存档(init 生成,add-node 复用;勿泄露、勿删除)
└── etcd/  k8s/            两套独立 CA

internal/
├── config/                cluster.yaml 解析
├── sshx/                  SSH 客户端封装(Run / Upload / UploadFile)
├── pki/                   CA 生成、加载、证书签发(支持 SAN 与 Organization)
└── phase/                 安装阶段(每个函数幂等,带 .installed 完成标记)
    ├── prepare.go         环境初始化
    ├── containerd.go      容器运行时(含代理配置、SystemdCgroup)
    ├── image.go           离线镜像导入(assets/images 存在才执行)
    ├── etcd.go            etcd 单点部署 + 健康验证
    ├── master.go          控制面 5 组件 + 本机 kubelet/kube-proxy + 7 种证书
    ├── cni.go             Calico 部署 + 全节点 Ready 等待
    ├── dns.go             CoreDNS 部署 + rollout 验证
    ├── node.go            worker 加入 + 证书签发 + Ready 等待
    └── remove.go          remove-node / reset 清理逻辑

前置条件

assets 目录

text
assets/
├── containerd/
│   ├── containerd-2.2.3-linux-amd64.tar.gz
│   ├── runc.amd64
│   ├── cni-plugins-linux-amd64-v1.9.1.tgz
│   └── crictl-v1.35.0-linux-amd64.tar.gz
├── etcd/
│   └── etcd-v3.6.10-linux-amd64.tar.gz
├── k8s/
│   ├── kube-apiserver
│   ├── kube-controller-manager
│   ├── kube-scheduler
│   ├── kubelet
│   ├── kube-proxy
│   └── kubectl
├── calico/
│   └── calico.yaml
├── coredns/
│   └── coredns.yaml
└── images/                     # 可选:离线镜像 tar 包,存在则自动导入
    ├── cni-v3.25.0.tar.gz
    ├── node-v3.25.0.tar.gz
    ├── kube-controllers-v3.25.0.tar.gz
    ├── coredns-1.11.3.tar.gz
    └── pause-3.10.1.tar.gz     # 必带,sandbox 镜像

cluster.yaml

yaml
ssh:
  user: root
  port: 22
  password: "<SSH_PASSWORD>"

proxy: "http://192.168.60.1:12450"   # containerd 拉镜像代理,不需要则置空 ""

masters:
  - name: master
    host: 192.168.60.128

workers:
  - name: node
    host: 192.168.60.129

节点 name 会作为主机名设置(hostnamectl),必须满足 RFC 1123(小写字母/数字/中划线)。

命令

命令作用
precheck所有节点环境初始化(主机名/swap/防火墙/hosts/内核模块/内核参数),幂等
init全流程建集群:Prepare → Containerd → 镜像导入 → etcd → 控制面 → Calico → CoreDNS → Workers
add-node把 cluster.yaml 中 workers 列表的节点加入已有集群(复用本地 pki/k8s CA)
remove-node <名称>驱逐并移除指定 worker:master 上 drain + delete node,然后清理该机器
reset销毁整个集群(先 workers 后 masters,需输入 yes 确认),节点回到裸机状态

所有命令支持 -f <路径> 指定配置文件,默认 ./cluster.yaml。注意 flag 解析规则:-f 必须放在子命令参数之前,如 remove-node -f cluster.yaml node

bash
# 本机直接跑
go run . init

# 编译后用
go build -o k8s-installer .
./k8s-installer init -f cluster.yaml

# 交叉编译 Linux 交付版(Windows PowerShell)
$env:GOOS="linux"; $env:GOARCH="amd64"; go build -o k8s-installer-linux .
$env:GOOS="windows"

关键设计

证书体系

  • 两套 CA:pki/etcd/(etcd 集群用)与 pki/k8s/(K8s 组件用),init 时生成,10 年有效期
  • 组件身份靠证书 CN + Organization:adminapiserver-kubelet-client 属于 system:masters 组(超级权限),kubelet 属于 system:nodes
  • CA 私钥保留在安装器本地;唯一例外是 kubernetes-ca-key.pem 须下发到 master(controller-manager 签发 ServiceAccount 证书的架构要求,kubeadm 同样如此)

幂等机制

  • 各阶段完成时在节点上写标记文件(如 /etc/kubernetes/.installed),重跑检测标记跳过
  • 检测的是完成标记而非中间产物——二进制存在不代表安装成功
  • master 阶段的完成标记在 kubelet 注册成功后才写入

重跑安全

  • 上传二进制前先 systemctl stop 相关服务(避免覆盖运行中文件报 Text file busy
  • 文件上传通过 SSH stdin + tee,自动创建父目录(tee 自身不会建目录)

remove-node 流程

  1. master 上 kubectl drain --ignore-daemonsets --delete-emptydir-data --force
  2. kubectl delete node
  3. 目标机器清理 kubelet/kube-proxy/证书/iptables

reset 顺序

先 workers 后 masters(逆安装顺序):业务 Pod 先停,etcd 数据最后销毁,中途中止损失最小。reset 不删除本地 pki/ 目录。

踩坑记录

开发验收过程中实际踩过的坑,均已修复:

  1. tee 不建父目录:上传文件到多级路径失败 → Upload 前加 mkdir -p $(dirname ...)
  2. 幂等误伤:最初用"二进制存在"判断已安装,重跑会跳过未完成的阶段 → 改为完成标记法
  3. Text file busy:重跑覆盖正在运行的二进制 → 上传前先 systemctl stop ... || true
  4. admin 403kubectl get nodesUser "admin" cannot list resource "nodes" → 证书 Organization 是 RBAC 组,admin 必须签 O=system:masters,kubelet 必须签 O=system:nodes
  5. No resources found:控制面起来后立刻查节点是空的 → kubelet 注册是异步的,验证段改循环等待(20×3s)
  6. Device or resource busy:reset 删除 /var/lib/kubelet 失败 → 删除前 lazy umount kubelet/containerd 挂载点
  7. ctr export 管道截断ctr images export - | gzip 导出镜像 tar 内部损坏(gzip -t 测不出来)→ 必须直接导出到文件再 gzip,导出前 tar -tf 验证完整性
  8. CNI 首次启动慢:Calico 镜像 88MB 在线拉取要 6~7 分钟,等待 3 分钟超时误报失败 → 等待延长到 10 分钟 + 离线镜像预导入彻底解决
  9. ctr 命名空间:镜像导出/导入必须加 -n k8s.io,默认命名空间找不到 k8s 镜像

验证清单(init 完成后)

bash
kubectl get nodes -o wide    # 全部 Ready
kubectl get pods -A          # calico / coredns 全 Running
kubectl run dns-test --image=busybox:1.28 --restart=Never --rm -it -- nslookup kubernetes.default
# 期望: Server 10.96.0.10 → Address 10.96.0.1

实测验收输出(双节点,reset 后裸机从零 init):

text
NAME     STATUS   ROLES    VERSION   INTERNAL-IP      CONTAINER-RUNTIME
master   Ready    <none>   v1.35.4   192.168.60.128   containerd://2.2.3
node     Ready    <none>   v1.35.4   192.168.60.129   containerd://2.2.3

kube-system   calico-kube-controllers-xxx   1/1   Running
kube-system   calico-node-xxx               1/1   Running
kube-system   calico-node-yyy               1/1   Running
kube-system   coredns-xxx                   1/1   Running

当前限制

  • 单 master(HA 多 master + VIP 未实现,etcd 为单点)
  • kubelet 使用静态预签证书,非 TLS bootstrap
  • 节点 OS 仅验证过 Ubuntu;内核参数与防火墙命令对其他发行版可能需要调整
  • CNI 阶段对 master 节点执行了去污点(单 master 需要调度系统 Pod);HA 场景需调整该逻辑

基于 MIT 许可发布