主题
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:
admin和apiserver-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 流程
- master 上
kubectl drain --ignore-daemonsets --delete-emptydir-data --force kubectl delete node- 目标机器清理 kubelet/kube-proxy/证书/iptables
reset 顺序
先 workers 后 masters(逆安装顺序):业务 Pod 先停,etcd 数据最后销毁,中途中止损失最小。reset 不删除本地 pki/ 目录。
踩坑记录
开发验收过程中实际踩过的坑,均已修复:
- tee 不建父目录:上传文件到多级路径失败 → Upload 前加
mkdir -p $(dirname ...) - 幂等误伤:最初用"二进制存在"判断已安装,重跑会跳过未完成的阶段 → 改为完成标记法
- Text file busy:重跑覆盖正在运行的二进制 → 上传前先
systemctl stop ... || true - admin 403:
kubectl get nodes报User "admin" cannot list resource "nodes"→ 证书 Organization 是 RBAC 组,admin 必须签O=system:masters,kubelet 必须签O=system:nodes - No resources found:控制面起来后立刻查节点是空的 → kubelet 注册是异步的,验证段改循环等待(20×3s)
- Device or resource busy:reset 删除
/var/lib/kubelet失败 → 删除前 lazy umount kubelet/containerd 挂载点 - ctr export 管道截断:
ctr images export - | gzip导出镜像 tar 内部损坏(gzip -t测不出来)→ 必须直接导出到文件再 gzip,导出前tar -tf验证完整性 - CNI 首次启动慢:Calico 镜像 88MB 在线拉取要 6~7 分钟,等待 3 分钟超时误报失败 → 等待延长到 10 分钟 + 离线镜像预导入彻底解决
- 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 场景需调整该逻辑