离线二进制部署 Kubernetes(单控制面,master1 + 0~N worker,Kubernetes v1.36.4)
本文档是「离线二进制(手动)部署」的初始部署篇:在完全离线 环境下,用原始二进制 + systemd 手工部署一套单控制面 K8s 集群(master1 运行 etcd + apiserver + controller-manager + scheduler,去污点可承载业务),再用 kubelet + kube-proxy 纳管工作节点,最后部署 Calico / CoreDNS / metrics-server。
不使用 kubeadm :证书用 cfssl 手工签发,kubeconfig 手工生成,所有组件以 systemd 单元运行。这让你完全掌控每个组件的配置与证书。
文档遵循「说明 → 下载资源 → 准备环境 → 部署(证书/kubeconfig → etcd → 控制面 → 节点 → 网络/DNS/监控)→ 访问验证」结构。
扩容到 3 控制面高可用见同级
../扩容kubernetes/;升级见../升级kubernetes/。
我先加载可视化指引,然后给你一份结构化的对比分析(含一个可对照的对比表可视化)。
下面先给你一个两模式对照卡,再展开完整对比。
kubeadm_vs_binary_compare <![CDATA[
kubeadm 与二进制部署方式对比
kubeadm 部署
kubeadm init / join · 控制面以静态 Pod 运行
- · 证书由 kubeadm 自动签发(默认 1 年)
- · 控制面 / etcd / kube-proxy 由 kubelet 托管为静态 Pod
- · 升级
kubeadm upgrade apply近一键完成 - · 扩容
kubeadm join一行命令 - · 需导入控制面+etcd+kube-proxy 等全套镜像
- · 社区标准、CKA 对齐、排障资料最丰富
- · 隐藏状态多、深度定制受限
二进制部署 完全可控
手工二进制 + systemd + cfssl 自签证书
- · 证书全手工签发(可自定 10 年 / 策略)
- · 控制面 / etcd / kube-proxy 跑 systemd 二进制
- · 升级 = 替换二进制 + 滚动重启(需自写流程)
- · 扩容 = 手工加节点 + 签证书 + systemd
- · 仅需 pause / Calico / CoreDNS / metrics 镜像
- · 可审计、透明、深度定制、离线最稳
- · 运维门槛高、需自建脚本与校验
]]> "整理两种部署模式对照","渲染对比卡片" 基于你 `v1/`(kubeadm 单控/HA)与 `v3/`(二进制手工部署)的实际文档,下面给出严格对照的对比。先上一个能力维度对照图,再展开文字。
下面这张图把两类方案在 10 个维度上量化对照(5=最优,分数越高越友好),直观看二者强弱分布。
一、核心差异对照(基于 v1 kubeadm / v3 二进制实际文档)
| 维度 | kubeadm 部署(v1/v2) | 二进制部署(v3) |
|---|---|---|
| 部署原理 | kubeadm init 生成静态 Pod 清单,kubeadm join 引导节点 |
手工下载二进制 + systemd unit 直接运行每个组件 |
| 控制面运行形态 | 静态 Pod(由 kubelet 托管),日志走 crictl logs |
systemd 服务,日志走 journalctl,可设重启/OOM 策略 |
| 证书管理 | 内置 CA 自动签发(默认 1 年),kubeadm certs renew 管理 |
cfssl 手工签发(默认 10 年),每个组件独立 CSR,SAN 全自定义 |
| kubeconfig | 自动生成 /etc/kubernetes/*.conf |
kubectl 手工 set-cluster/set-credentials/set-context 生成 |
| etcd 形态 | 容器化(stacked Pod) | systemd 二进制,独立 server/peer 证书 + 生产参数(配额/压缩) |
| kube-proxy 形态 | DaemonSet(Pod) | systemd 二进制 |
| 离线镜像需求 | 需导入 ~10+ 个组件镜像(含 apiserver/controller/scheduler/etcd/proxy) | 仅需 6 个(pause + Calico + CoreDNS + metrics-server),控制面不消费镜像 |
| 升级方式 | kubeadm upgrade plan/apply 自动换静态 Pod 镜像 |
停服务→替换二进制→起服务,逐组件可控 |
| 扩容方式 | kubeadm join / join --control-plane(自动复制证书) |
手工签发证书 + 复制二进制 + systemd,完全脚本化 |
| 回滚/备份对象 | 备份 /etc/kubernetes + etcd 快照;升级失败回退有限 |
直接换回旧二进制 + etcd 快照,对象清晰无隐藏状态 |
| 透明/可审计 | 证书与清单在 /etc/kubernetes,但证书策略不可细控 |
几乎无隐藏状态,所有 flag/证书/路径显式可见 |
| 学习/运维成本 | 低(CKA 对齐,自动化) | 高(需理解每个组件参数) |
二、适合的业务场景
kubeadm 更适合:
- 标准云/IDC 环境快速搭建(开发、测试、预发、中小生产)
- 团队初接触 K8s、需要流程与 CKA 认证对齐
- 集群规模中等、SLA 非极严苛、希望"按官方标准不出意外"
- 需频繁跟随社区做小版本升级
二进制更适合:
- 强合规/等保/金融:证书策略、审计日志、Secret 加密静态存储需完全自定并留痕(v3 已内置可选审计/加密说明)
- 信创/国产化栈:需精确锁定组件版本与依赖,规避 kubeadm 隐式行为
- 超大规模(数百节点+):需精细调参、可控的升级节奏与逐组件灰度
- 完全 air-gapped 且镜像分发受限:离线镜像最少(仅 6 个)
- 已有成熟 SRE/运维体系,能承担手工运维成本,把 K8s 当"可审计底层"
三、各自的价值
- kubeadm 的价值:标准化、自动化、低门槛、社区官方背书、CKA 对齐、小版本升级平滑、故障排查资料丰富。核心价值是"用最低成本拿到一个符合官方规范、可被社区支持的集群"。
- 二进制的价值:完全可控、透明可审计、离线最稳、深度定制自由、无隐藏状态、升级/回滚对象清晰、对底层依赖一清二楚。核心价值是"把 K8s 变成团队自己完全掌握、可审计、可裁剪的底座"。
四、结合你现有生态(v1 / v2 / v3)的建议
三者不是替代关系,而是"标准 → HA → 可控"的梯度:
- v1(kubeadm 单控):学习、测试、小生产,最快上手。
- v2(kubeadm HA + Mayastor):标准生产高可用,省心、社区支持好。
- v3(二进制):当你进入强合规、信创、超大规模或深度定制阶段时切换;其离线镜像最少、证书/参数全显式,正好补足 kubeadm 在"可审计/可控"上的短板。
实际生产中二者也常组合:用 kubeadm 起集群,再以二进制思维做证书策略与参数审计。需要的话,我可以把 v3 的命令进一步抽成可一键执行的 shell 脚本,或补上 OpenEBS LocalPV/Higress 在二进制栈上的叠加方式。
说明
适用范围
| 项 | 内容 |
|---|---|
| 集群规模 | 初始 1 控制面 + 0~N 工作节点;后续可扩到 3 控制面(见扩容文档) |
| 使用场景 | 开发测试、演示验证、小范围业务、内网隔离环境,以及需要完全掌控组件细节的场景 |
| 部署诉求 | 不依赖 kubeadm,用二进制 + systemd 显式部署,离线可复现 |
| 不适用 | 超大规模集群(建议 kubeadm / 商业发行版) |
版本锁定
| 组件 | 版本 | 说明 |
|---|---|---|
| Kubernetes | v1.36.4 | apiserver/controller-manager/scheduler/kubelet/kubectl 同版本 |
| etcd | 3.6.8 | 独立二进制(与 kubeadm 内置 3.6.8-0 同源) |
| cfssl / cfssljson | v1.6.4 | 证书签发工具 |
| containerd | 2.3.3(nerdctl-full 2.3.5) | 容器运行时 |
| Calico | v3.32.2 | CNI |
| CoreDNS | v1.14.2 | 集群 DNS |
| metrics-server | v0.9.0 | 监控指标 |
| pause | 3.10.2 | 沙箱镜像 |
示例 IP 与目录约定(全文全局替换)
- master1 =
172.16.0.19(控制面,去污点可承载业务) - worker1 =
172.16.0.20(工作节点);worker2/3 等按172.16.0.23/24类推 - Service 网段
10.96.0.0/12;Pod 网段192.168.0.0/16(与 Calico 一致) - 数据盘根目录
/data(实际挂载点不同则全文替换);证书/配置统一放/etc/kubernetes - 离线资源树:
/data/k8s-offline/deploy/{files,bin,images}
下载资源(联网阶段)
目标:在联网机器把部署所需的一切提前备齐,分类放进目录树,再整目录摆渡到部署节点。下载机(Ubuntu 22.04)负责 etcd/cfssl 二进制与容器镜像;操作机(Windows)优先用浏览器直连官网下载 k8s server tarball 与清单。
目录结构
text
k8s-offline/
└── deploy/
├── files/ # cfssl、kube-vip(扩容用)、calico.yaml、coredns.yaml、metrics-server-components.yaml、kube-proxy 配置等
├── bin/ # kubernetes-server-linux-amd64.tar.gz、etcd-v3.6.8-linux-amd64.tar.gz、cfssl、cfssljson
└── images/ # k8s-offline-images.tar(容器镜像打包)
文件清单(组件 / 版本 / 下载地址)
第 7 项 CoreDNS 官方
coredns.yaml.sed为模板(含sed占位符),本文在部署节直接给出一份去掉占位的可用coredns.yaml,避免离线环境sed出错;操作机无需单独下载第 7 项,部署时在 master1 直接写文件即可。
命名修正 :下载机/操作机取到 cfssl 二进制后改名为 cfssl / cfssljson(去掉版本与平台后缀),便于后续命令统一引用:
bash
mv cfssl_1.6.4_linux_amd64 cfssl
mv cfssljson_1.6.4_linux_amd64 cfssljson
chmod +x cfssl cfssljson
容器镜像(下载机执行)
重要(二进制部署与 kubeadm 的关键差异) :本文所有控制面组件(apiserver / controller-manager / scheduler)、etcd、kube-proxy 均以 systemd 二进制 运行,不消费容器镜像 。因此离线镜像 tar 只需包含真正被容器运行时拉起的镜像 :pause(沙箱)、Calico、CoreDNS、metrics-server。apiserver / controller-manager / scheduler / kube-proxy / etcd 的「镜像」切勿列入,它们由
bin/下的二进制提供。
需打包的镜像(下载机起临时 containerd 拉取后 save):
text
registry.k8s.io/pause:3.10.2
registry.k8s.io/coredns/coredns:v1.14.2
quay.io/calico/cni:v3.32.2
quay.io/calico/node:v3.32.2
quay.io/calico/kube-controllers:v3.32.2
registry.k8s.io/metrics-server/metrics-server:v0.9.0
(共 6 个;如需 OpenEBS LocalPV / Higress,按其镜像追加到同一 tar,按 v1 文档叠加。)
bash
# 下载机:起临时 containerd
cd /data/k8s-offline/deploy/files
tar Cxzvvf /usr/local nerdctl-full-2.3.5-linux-amd64.tar.gz
mkdir -p /etc/containerd && containerd config default > /etc/containerd/config.toml
systemctl daemon-reload && systemctl enable --now containerd
# 逐条拉取(仅 6 个真正被容器运行时消费的镜像;离线无法直连时可配置 1ms.run 加速,仅改取数通道,不改 tag)
nerdctl pull registry.k8s.io/pause:3.10.2
nerdctl pull registry.k8s.io/coredns/coredns:v1.14.2
nerdctl pull quay.io/calico/cni:v3.32.2
nerdctl pull quay.io/calico/node:v3.32.2
nerdctl pull quay.io/calico/kube-controllers:v3.32.2
nerdctl pull registry.k8s.io/metrics-server/metrics-server:v0.9.0
# 打包(不加 --all-platforms)
cd /data/k8s-offline/deploy/images
nerdctl save -o k8s-offline-images.tar \
registry.k8s.io/pause:3.10.2 \
registry.k8s.io/coredns/coredns:v1.14.2 \
quay.io/calico/cni:v3.32.2 \
quay.io/calico/node:v3.32.2 \
quay.io/calico/kube-controllers:v3.32.2 \
registry.k8s.io/metrics-server/metrics-server:v0.9.0
ls -lh k8s-offline-images.tar
传输到离线节点
操作机把 deploy/ 整目录(bin + files + images)经 SFTP 上传到 master1 与每台 worker 的 /data/k8s-offline/deploy/:
bash
# 操作机(Git Bash),逐节点
sftp root@172.16.0.19
sftp> mkdir /data/k8s-offline
sftp> put -r D:/k8s-offline/deploy /data/k8s-offline/deploy
sftp> exit
# worker1 等节点同样上传
准备环境(master1 与所有 worker 均执行)
建议全程 root;普通用户命令前加 sudo。示例 IP 按真实环境替换。
(1) 关闭防火墙
bash
systemctl disable --now ufw 2>/dev/null || true
ufw status # Status: inactive
(2) 关闭 swap
bash
swapoff -a
sed -i 's/.*swap.*/#&/' /etc/fstab
free -h # swap 必须为 0
(3) 主机名与 hosts 解析
bash
hostnamectl set-hostname master1 # worker 上改为对应名
cat >> /etc/hosts <<'EOF'
172.16.0.19 master1
172.16.0.20 worker1
EOF
(4) 内核模块与转发
必须加载的内核模块(Kubernetes 官方与 kubeasz 均要求):
br_netfilter:让桥接流量经过 iptables/nftables 规则(kube-proxy、Calico 依赖)。overlay:containerd 默认用 overlayfs 作快照驱动,缺此模块容器/镜像无法创建。ip_vs*(ip_vs、ip_vs_rr、ip_vs_wrr、ip_vs_sh):kube-proxy 切到 IPVS 模式时必需;iptables 模式不强制但加载无害。nf_conntrack:连接跟踪,iptables/IPVS 模式均依赖。
bash
cat <<'EOF' | tee /etc/modules-load.d/k8s.conf
br_netfilter
overlay
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
nf_conntrack
EOF
for m in br_netfilter overlay ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack; do
modprobe $m || echo "modprobe $m 失败,请确认内核已开启该模块"
done
lsmod | grep -E 'br_netfilter|overlay|ip_vs|nf_conntrack'
cat <<'EOF' | tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.netfilter.nf_conntrack_max = 1048576
EOF
sysctl --system
# 确认生效
sysctl net.bridge.bridge-nf-call-iptables net.ipv4.ip_forward
(5) 时间同步(master1 为 NTP 服务端,worker 向其同步)
bash
# master1(先校正真实时间再启动 chrony 授时)
timedatectl set-ntp false
timedatectl set-timezone Asia/Shanghai # 统一时区,证书/etcd 对时间敏感
timedatectl set-time "2026-09-30 09:00:00" # 改为部署时真实时间
cat > /etc/chrony/chrony.conf <<'EOF'
local stratum 10
allow 0.0.0.0/0
bindaddress 0.0.0.0
EOF
systemctl enable --now chrony
chronyc tracking
# worker
timedatectl set-ntp false
timedatectl set-timezone Asia/Shanghai
cat > /etc/chrony/chrony.conf <<'EOF'
server master1 iburst
EOF
systemctl enable --now chrony
sleep 5; chronyc sources # 应见 master1 带 * 号
(6) 专用数据目录
bash
mkdir -p /data/containerd /data/kubelet /data/etcd
if [ -e /var/lib/kubelet ] && [ ! -L /var/lib/kubelet ]; then rm -rf /var/lib/kubelet; fi
ln -s /data/kubelet /var/lib/kubelet
(7) 安装容器运行时 containerd(两节点)
bash
cd /data/k8s-offline/deploy/files
tar Cxzvvf /usr/local nerdctl-full-2.3.5-linux-amd64.tar.gz
mkdir -p /opt/cni/bin
find /usr/local/libexec/cni -maxdepth 1 -type f -perm -u+x -exec cp -v {} /opt/cni/bin/ \;
systemctl daemon-reload
which containerd nerdctl runc
mkdir -p /etc/containerd /data/containerd
containerd config default > /etc/containerd/config.toml
sed -i 's#^root = .*#root = "/data/containerd"#' /etc/containerd/config.toml
sed -i 's#^ sandbox_image = .*# sandbox_image = "registry.k8s.io/pause:3.10.2"#' /etc/containerd/config.toml
sed -i 's#SystemdCgroup = false#SystemdCgroup = true#g' /etc/containerd/config.toml
systemctl enable --now containerd
systemctl status containerd --no-pager
grep -E '^root = |sandbox_image = |SystemdCgroup = ' /etc/containerd/config.toml
(8) 导入镜像(两节点)
bash
cd /data/k8s-offline/deploy/images
ctr -n k8s.io images import k8s-offline-images.tar # 不加 --all-platforms
ctr -n k8s.io images ls | grep -E '3.10.2|v1.14.2|calico|metrics-server'
(9) 安装 Kubernetes 二进制(两节点)
bash
cd /data/k8s-offline/deploy/bin
tar xzf kubernetes-server-linux-amd64.tar.gz
cp -v kubernetes/server/bin/{kube-apiserver,kube-controller-manager,kube-scheduler,kubelet,kubectl} /usr/local/bin/
kube-apiserver --version && kubelet --version && kubectl version --client
(10) 安装 etcd 与 cfssl 二进制(两节点,master 需 etcd,worker 仅需 cfssl 无需)
bash
cd /data/k8s-offline/deploy/bin
tar xzf etcd-v3.6.8-linux-amd64.tar.gz
cp -v etcd-v3.6.8-linux-amd64/etcd etcd-v3.6.8-linux-amd64/etcdctl etcd-v3.6.8-linux-amd64/etcdutl /usr/local/bin/
cp -v cfssl cfssljson /usr/local/bin/
etcd --version && etcdutl version && cfssl version
部署 · 第一步:生成证书与 kubeconfig(master1 执行)
所有证书用 cfssl 签发,统一放
/etc/kubernetes/pki,kubeconfig 放/etc/kubernetes。证书有效期默认 10 年。
1.1 cfssl 配置与 CA
bash
mkdir -p /root/cert /etc/kubernetes/pki/etcd
cd /root/cert
# CA 签名策略
cat > ca-config.json <<'EOF'
{
"signing": {
"default": { "expiry": "87600h" },
"profiles": {
"kubernetes": {
"expiry": "87600h",
"usages": ["signing", "key encipherment", "server auth", "client auth"]
}
}
}
}
EOF
# Kubernetes CA
cat > ca-csr.json <<'EOF'
{
"CN": "kubernetes",
"key": { "algo": "rsa", "size": 2048 },
"names": [{ "C": "CN", "L": "Beijing", "O": "kubernetes", "OU": "system" }]
}
EOF
cfssl gencert -initca ca-csr.json | cfssljson -bare /etc/kubernetes/pki/ca
# etcd CA(独立 CA,便于 etcd 单独轮换)
cat > etcd-ca-csr.json <<'EOF'
{
"CN": "etcd-ca",
"key": { "algo": "rsa", "size": 2048 },
"names": [{ "C": "CN", "L": "Beijing", "O": "etcd", "OU": "system" }]
}
EOF
cfssl gencert -initca etcd-ca-csr.json | cfssljson -bare /etc/kubernetes/pki/etcd/ca
# front-proxy CA(聚合 API 用)
cat > front-proxy-ca-csr.json <<'EOF'
{
"CN": "front-proxy-ca",
"key": { "algo": "rsa", "size": 2048 },
"names": [{ "O": "front-proxy", "OU": "system" }]
}
EOF
cfssl gencert -initca front-proxy-ca-csr.json | cfssljson -bare /etc/kubernetes/pki/front-proxy-ca
1.2 各组件证书
apiserver(服务端证书,SAN 含 VIP 占位,便于后续扩容无需重签)
若部署时即规划未来扩到 3 控制面高可用 ,应在此把 master2(
172.16.0.21)、master3(172.16.0.22)的 IP 与主机名一并写进hosts,这样扩容时无需对已运行的 master1 重签 apiserver 证书(扩容文档 §2.1 仍会为新增 master2/3 单独签发各自的 apiserver 证书)。单控制面场景保留下方默认(master1 + VIP)即可。
bash
cat > apiserver-csr.json <<'EOF'
{
"CN": "kube-apiserver",
"hosts": [
"10.96.0.1",
"127.0.0.1",
"172.16.0.19",
"172.16.0.10",
"kubernetes",
"kubernetes.default",
"kubernetes.default.svc",
"kubernetes.default.svc.cluster.local",
"master1"
],
"key": { "algo": "rsa", "size": 2048 }
}
EOF
cfssl gencert -ca=/etc/kubernetes/pki/ca.pem -ca-key=/etc/kubernetes/pki/ca-key.pem \
-config=ca-config.json -profile=kubernetes apiserver-csr.json \
| cfssljson -bare /etc/kubernetes/pki/apiserver
apiserver-kubelet-client(apiserver 访问 kubelet 用)
bash
cat > apiserver-kubelet-client-csr.json <<'EOF'
{
"CN": "system:apiserver-kubelet-client",
"key": { "algo": "rsa", "size": 2048 },
"names": [{ "O": "system:masters" }]
}
EOF
cfssl gencert -ca=/etc/kubernetes/pki/ca.pem -ca-key=/etc/kubernetes/pki/ca-key.pem \
-config=ca-config.json -profile=kubernetes apiserver-kubelet-client-csr.json \
| cfssljson -bare /etc/kubernetes/pki/apiserver-kubelet-client
front-proxy-client
bash
cat > front-proxy-client-csr.json <<'EOF'
{
"CN": "front-proxy-client",
"key": { "algo": "rsa", "size": 2048 }
}
EOF
cfssl gencert -ca=/etc/kubernetes/pki/front-proxy-ca.pem -ca-key=/etc/kubernetes/pki/front-proxy-ca-key.pem \
-config=ca-config.json -profile=kubernetes front-proxy-client-csr.json \
| cfssljson -bare /etc/kubernetes/pki/front-proxy-client
etcd 三张证书(server / peer / apiserver-etcd-client)
bash
cat > etcd-server-csr.json <<'EOF'
{
"CN": "etcd",
"hosts": ["172.16.0.19", "127.0.0.1", "localhost", "master1"],
"key": { "algo": "rsa", "size": 2048 },
"names": [{ "O": "etcd" }]
}
EOF
cfssl gencert -ca=/etc/kubernetes/pki/etcd/ca.pem -ca-key=/etc/kubernetes/pki/etcd/ca-key.pem \
-config=ca-config.json -profile=kubernetes etcd-server-csr.json \
| cfssljson -bare /etc/kubernetes/pki/etcd/server
cat > etcd-peer-csr.json <<'EOF'
{
"CN": "etcd-peer",
"hosts": ["172.16.0.19", "127.0.0.1", "localhost", "master1"],
"key": { "algo": "rsa", "size": 2048 },
"names": [{ "O": "etcd" }]
}
EOF
cfssl gencert -ca=/etc/kubernetes/pki/etcd/ca.pem -ca-key=/etc/kubernetes/pki/etcd/ca-key.pem \
-config=ca-config.json -profile=kubernetes etcd-peer-csr.json \
| cfssljson -bare /etc/kubernetes/pki/etcd/peer
cat > etcd-client-csr.json <<'EOF'
{
"CN": "kube-apiserver-etcd-client",
"key": { "algo": "rsa", "size": 2048 },
"names": [{ "O": "system:masters" }]
}
EOF
cfssl gencert -ca=/etc/kubernetes/pki/etcd/ca.pem -ca-key=/etc/kubernetes/pki/etcd/ca-key.pem \
-config=ca-config.json -profile=kubernetes etcd-client-csr.json \
| cfssljson -bare /etc/kubernetes/pki/etcd/apiserver-etcd-client
controller-manager / scheduler / admin / kube-proxy 客户端证书
bash
for n in controller-manager scheduler; do
cat > ${n}-csr.json <<EOF
{
"CN": "system:${n}",
"key": { "algo": "rsa", "size": 2048 },
"names": [{ "O": "system:masters" }]
}
EOF
cfssl gencert -ca=/etc/kubernetes/pki/ca.pem -ca-key=/etc/kubernetes/pki/ca-key.pem \
-config=ca-config.json -profile=kubernetes ${n}-csr.json \
| cfssljson -bare /etc/kubernetes/pki/${n}
done
cat > admin-csr.json <<'EOF'
{
"CN": "kubernetes-admin",
"key": { "algo": "rsa", "size": 2048 },
"names": [{ "O": "system:masters" }]
}
EOF
cfssl gencert -ca=/etc/kubernetes/pki/ca.pem -ca-key=/etc/kubernetes/pki/ca-key.pem \
-config=ca-config.json -profile=kubernetes admin-csr.json \
| cfssljson -bare /etc/kubernetes/pki/admin
cat > kube-proxy-csr.json <<'EOF'
{
"CN": "system:kube-proxy",
"key": { "algo": "rsa", "size": 2048 },
"names": [{ "O": "system:node-proxier" }]
}
EOF
cfssl gencert -ca=/etc/kubernetes/pki/ca.pem -ca-key=/etc/kubernetes/pki/ca-key.pem \
-config=ca-config.json -profile=kubernetes kube-proxy-csr.json \
| cfssljson -bare /etc/kubernetes/pki/kube-proxy
kubelet 客户端证书(每个节点一张,这里先给 master1;worker 在扩容/加入时按 §部署·节点 生成)
关键 :kubelet 的这张证书同时用作「访问 apiserver 的客户端证书」和「kubelet 对外提供 10250 端口的服务端证书」 (kubelet 未单独指定
--tls-cert-file时,回退使用 kubeconfig 里的客户端证书充当服务端证书)。因此hosts必须包含本节点 IP、主机名、127.0.0.1 ,否则 apiserver 反向连接https://<节点IP>:10250(用于kubectl logs/exec/port-forward、metrics-server 取数)时 x509 报certificate is valid for ... not for <节点IP>。
bash
cat > kubelet-master1-csr.json <<'EOF'
{
"CN": "system:node:master1",
"hosts": ["172.16.0.19", "127.0.0.1", "localhost", "master1"],
"key": { "algo": "rsa", "size": 2048 },
"names": [{ "O": "system:nodes" }]
}
EOF
cfssl gencert -ca=/etc/kubernetes/pki/ca.pem -ca-key=/etc/kubernetes/pki/ca-key.pem \
-config=ca-config.json -profile=kubernetes kubelet-master1-csr.json \
| cfssljson -bare /etc/kubernetes/pki/kubelet-master1
1.3 ServiceAccount 密钥对
bash
openssl genrsa -out /etc/kubernetes/pki/sa.key 2048
openssl rsa -in /etc/kubernetes/pki/sa.key -pubout -out /etc/kubernetes/pki/sa.pub
1.4 生成 kubeconfig
bash
cd /etc/kubernetes
KUBE_API="https://172.16.0.19:6443" # 单控制面直连 master1;扩容后改为 VIP
# admin
kubectl config set-cluster kubernetes \
--certificate-authority=/etc/kubernetes/pki/ca.pem \
--server=${KUBE_API} --kubeconfig=/etc/kubernetes/admin.kubeconfig
kubectl config set-credentials kubernetes-admin \
--client-certificate=/etc/kubernetes/pki/admin.pem \
--client-key=/etc/kubernetes/pki/admin-key.pem \
--kubeconfig=/etc/kubernetes/admin.kubeconfig
kubectl config set-context kubernetes \
--cluster=kubernetes --user=kubernetes-admin \
--kubeconfig=/etc/kubernetes/admin.kubeconfig
kubectl config use-context kubernetes --kubeconfig=/etc/kubernetes/admin.kubeconfig
# controller-manager
kubectl config set-cluster kubernetes \
--certificate-authority=/etc/kubernetes/pki/ca.pem \
--server=https://127.0.0.1:6443 --kubeconfig=/etc/kubernetes/controller-manager.kubeconfig
kubectl config set-credentials system:controller-manager \
--client-certificate=/etc/kubernetes/pki/controller-manager.pem \
--client-key=/etc/kubernetes/pki/controller-manager-key.pem \
--kubeconfig=/etc/kubernetes/controller-manager.kubeconfig
kubectl config set-context kubernetes \
--cluster=kubernetes --user=system:controller-manager \
--kubeconfig=/etc/kubernetes/controller-manager.kubeconfig
kubectl config use-context kubernetes --kubeconfig=/etc/kubernetes/controller-manager.kubeconfig
# scheduler
kubectl config set-cluster kubernetes \
--certificate-authority=/etc/kubernetes/pki/ca.pem \
--server=https://127.0.0.1:6443 --kubeconfig=/etc/kubernetes/scheduler.kubeconfig
kubectl config set-credentials system:kube-scheduler \
--client-certificate=/etc/kubernetes/pki/scheduler.pem \
--client-key=/etc/kubernetes/pki/scheduler-key.pem \
--kubeconfig=/etc/kubernetes/scheduler.kubeconfig
kubectl config set-context kubernetes \
--cluster=kubernetes --user=system:kube-scheduler \
--kubeconfig=/etc/kubernetes/scheduler.kubeconfig
kubectl config use-context kubernetes --kubeconfig=/etc/kubernetes/scheduler.kubeconfig
# kube-proxy
kubectl config set-cluster kubernetes \
--certificate-authority=/etc/kubernetes/pki/ca.pem \
--server=${KUBE_API} --kubeconfig=/etc/kubernetes/kube-proxy.kubeconfig
kubectl config set-credentials kube-proxy \
--client-certificate=/etc/kubernetes/pki/kube-proxy.pem \
--client-key=/etc/kubernetes/pki/kube-proxy-key.pem \
--kubeconfig=/etc/kubernetes/kube-proxy.kubeconfig
kubectl config set-context kubernetes \
--cluster=kubernetes --user=kube-proxy \
--kubeconfig=/etc/kubernetes/kube-proxy.kubeconfig
kubectl config use-context kubernetes --kubeconfig=/etc/kubernetes/kube-proxy.kubeconfig
# kubelet(master1)
kubectl config set-cluster kubernetes \
--certificate-authority=/etc/kubernetes/pki/ca.pem \
--server=${KUBE_API} --kubeconfig=/etc/kubernetes/kubelet.kubeconfig
kubectl config set-credentials system:node:master1 \
--client-certificate=/etc/kubernetes/pki/kubelet-master1.pem \
--client-key=/etc/kubernetes/pki/kubelet-master1-key.pem \
--kubeconfig=/etc/kubernetes/kubelet.kubeconfig
kubectl config set-context kubernetes \
--cluster=kubernetes --user=system:node:master1 \
--kubeconfig=/etc/kubernetes/kubelet.kubeconfig
kubectl config use-context kubernetes --kubeconfig=/etc/kubernetes/kubelet.kubeconfig
# 给 kubectl 默认使用 admin
cp /etc/kubernetes/admin.kubeconfig /root/.kube/config
echo 'export KUBECONFIG=/etc/kubernetes/admin.kubeconfig' >> /root/.bashrc
部署 · 第二步:etcd(master1 执行)
bash
mkdir -p /data/etcd
cat > /etc/systemd/system/etcd.service <<'EOF'
[Unit]
Description=etcd
After=network.target
[Service]
Type=notify
ExecStart=/usr/local/bin/etcd \
--name=master1 \
--data-dir=/data/etcd \
--listen-peer-urls=https://172.16.0.19:2380 \
--listen-client-urls=https://172.16.0.19:2379,https://127.0.0.1:2379 \
--advertise-client-urls=https://172.16.0.19:2379 \
--initial-advertise-peer-urls=https://172.16.0.19:2380 \
--initial-cluster=master1=https://172.16.0.19:2380 \
--initial-cluster-token=etcd-k8s-offline \
--initial-cluster-state=new \
--client-cert-auth \
--trusted-ca-file=/etc/kubernetes/pki/etcd/ca.pem \
--cert-file=/etc/kubernetes/pki/etcd/server.pem \
--key-file=/etc/kubernetes/pki/etcd/server-key.pem \
--peer-client-cert-auth \
--peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.pem \
--peer-cert-file=/etc/kubernetes/pki/etcd/peer.pem \
--peer-key-file=/etc/kubernetes/pki/etcd/peer-key.pem \
--quota-backend-bytes=8589934592 \
--auto-compaction-mode=periodic \
--auto-compaction-retention=1 \
--max-request-bytes=10485760 \
--snapshot-count=50000
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
OOMScoreAdjust=-999
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now etcd
systemctl status etcd --no-pager
校验 etcd 健康
bash
ETCDCTL_API=3 etcdctl \
--cacert=/etc/kubernetes/pki/etcd/ca.pem \
--cert=/etc/kubernetes/pki/etcd/apiserver-etcd-client.pem \
--key=/etc/kubernetes/pki/etcd/apiserver-etcd-client-key.pem \
--endpoints=https://127.0.0.1:2379 endpoint health
# 期望:https://127.0.0.1:2379 is healthy
部署 · 第三步:控制面三组件(master1 执行)
3.1 kube-apiserver
bash
cat > /etc/systemd/system/kube-apiserver.service <<'EOF'
[Unit]
Description=Kubernetes API Server
After=network.target etcd.service
[Service]
ExecStart=/usr/local/bin/kube-apiserver \
--advertise-address=172.16.0.19 \
--bind-address=0.0.0.0 \
--secure-port=6443 \
--service-cluster-ip-range=10.96.0.0/12 \
--service-node-port-range=30000-32767 \
--etcd-cafile=/etc/kubernetes/pki/etcd/ca.pem \
--etcd-certfile=/etc/kubernetes/pki/etcd/apiserver-etcd-client.pem \
--etcd-keyfile=/etc/kubernetes/pki/etcd/apiserver-etcd-client-key.pem \
--etcd-servers=https://172.16.0.19:2379 \
--tls-cert-file=/etc/kubernetes/pki/apiserver.pem \
--tls-private-key-file=/etc/kubernetes/pki/apiserver-key.pem \
--client-ca-file=/etc/kubernetes/pki/ca.pem \
--service-account-key-file=/etc/kubernetes/pki/sa.pub \
--service-account-signing-key-file=/etc/kubernetes/pki/sa.key \
--service-account-issuer=https://kubernetes.default.svc.cluster.local \
--kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.pem \
--kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client-key.pem \
--kubelet-certificate-authority=/etc/kubernetes/pki/ca.pem \
--enable-admission-plugins=NodeRestriction \
--authorization-mode=RBAC,Node \
--enable-bootstrap-token-auth=true \
--proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.pem \
--proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client-key.pem \
--requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.pem \
--requestheader-allowed-names=front-proxy-client \
--requestheader-extra-headers-prefix=X-Remote-Extra- \
--requestheader-group-headers=X-Remote-Group \
--requestheader-username-headers=X-Remote-User \
--allow-privileged=true \
--event-ttl=24h \
--enable-aggregator-routing=true \
--v=2
Restart=on-failure
RestartSec=5
Type=notify
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF
可选加固 :对纯内网集群可进一步加
--anonymous-auth=false(拒绝匿名请求,所有访问必须带证书);本文默认保留匿名(便于健康检查/聚合层探针),与 kubeadm 一致。需要更严格审计可叠加前文「可选增强·apiserver 审计日志」。
3.2 kube-controller-manager
bash
cat > /etc/systemd/system/kube-controller-manager.service <<'EOF'
[Unit]
Description=Kubernetes Controller Manager
After=network.target kube-apiserver.service
[Service]
ExecStart=/usr/local/bin/kube-controller-manager \
--authentication-kubeconfig=/etc/kubernetes/controller-manager.kubeconfig \
--authorization-kubeconfig=/etc/kubernetes/controller-manager.kubeconfig \
--kubeconfig=/etc/kubernetes/controller-manager.kubeconfig \
--leader-elect=true \
--cluster-signing-cert-file=/etc/kubernetes/pki/ca.pem \
--cluster-signing-key-file=/etc/kubernetes/pki/ca-key.pem \
--root-ca-file=/etc/kubernetes/pki/ca.pem \
--service-account-private-key-file=/etc/kubernetes/pki/sa.key \
--use-service-account-credentials=true \
--controllers=*,bootstrapsigner,tokencleaner \
--allocate-node-cidrs=true \
--cluster-cidr=192.168.0.0/16 \
--node-cidr-mask-size-ipv4=24 \
--bind-address=127.0.0.1 \
--secure-port=10257 \
--port=0 \
--tls-cert-file=/etc/kubernetes/pki/controller-manager.pem \
--tls-private-key-file=/etc/kubernetes/pki/controller-manager-key.pem \
--authentication-skip-lookup=true \
--v=2
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF
3.3 kube-scheduler
bash
cat > /etc/systemd/system/kube-scheduler.service <<'EOF'
[Unit]
Description=Kubernetes Scheduler
After=network.target kube-apiserver.service
[Service]
ExecStart=/usr/local/bin/kube-scheduler \
--authentication-kubeconfig=/etc/kubernetes/scheduler.kubeconfig \
--authorization-kubeconfig=/etc/kubernetes/scheduler.kubeconfig \
--kubeconfig=/etc/kubernetes/scheduler.kubeconfig \
--leader-elect=true \
--bind-address=127.0.0.1 \
--secure-port=10259 \
--port=0 \
--tls-cert-file=/etc/kubernetes/pki/scheduler.pem \
--tls-private-key-file=/etc/kubernetes/pki/scheduler-key.pem \
--authentication-skip-lookup=true \
--v=2
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
3.4 启动控制面
bash
systemctl daemon-reload
systemctl enable --now kube-apiserver kube-controller-manager kube-scheduler
systemctl status kube-apiserver kube-controller-manager kube-scheduler --no-pager
# 确认组件就绪
kubectl get componentstatuses
kubectl get namespaces
kubectl get componentstatuses显示 scheduler/controller-managerUnhealthy多半是因为它们只监听127.0.0.1:10259/10257(安全端口),而componentstatuses默认探0.0.0.0:10251/10252(非安全端口,已关闭)。属正常;用systemctl status与日志确认即可。
部署 · 第四步:kubelet + kube-proxy(master1 先跑,worker 复用)
4.1 kubelet 配置与单元(master1)
bash
cat > /etc/kubernetes/kubelet-config.yaml <<'EOF'
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local
# 以下为生产建议项(与 kubeadm 默认一致):
# 关闭 10255 只读端口、禁止匿名访问、鉴权走 Webhook(由 apiserver 做 SubjectAccessReview)
readOnlyPort: 0
protectKernelDefaults: true
failSwapOn: true
authentication:
anonymous:
enabled: false
webhook:
enabled: true
x509:
clientCAFile: /etc/kubernetes/pki/ca.pem
authorization:
mode: Webhook
EOF
cat > /etc/systemd/system/kubelet.service <<'EOF'
[Unit]
Description=Kubernetes Kubelet
After=network.target containerd.service
[Service]
ExecStart=/usr/local/bin/kubelet \
--kubeconfig=/etc/kubernetes/kubelet.kubeconfig \
--config=/etc/kubernetes/kubelet-config.yaml \
--container-runtime-endpoint=unix:///run/containerd/containerd.sock \
--pod-infra-container-image=registry.k8s.io/pause:3.10.2 \
--hostname-override=master1 \
--node-ip=172.16.0.19 \
--node-labels=node-role.kubernetes.io/control-plane="" \
--v=2
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now kubelet
systemctl status kubelet --no-pager
master1 未加
--register-with-taints,默认可承载业务 (架构 A)。若希望 master 纯控制面,加--register-with-taints=node-role.kubernetes.io/control-plane:NoSchedule。
证书策略说明(静态证书 vs TLS Bootstrap) :本文采用静态 kubelet 客户端证书 ------每个节点的 kubelet 证书由 cfssl 手工签发后分发(见 §部署·第一步与各扩容节),因此 apiserver 的--enable-bootstrap-token-auth=true实际未被使用(保留无害,便于以后切到 Bootstrap)。另一种官方做法是 TLS Bootstrap :kubelet 首次启动用一次性bootstrap-kubeconfig(含 token)向 apiserver 申请,由csr-approver自动签发带本机 IP SAN 的证书(kubeasz 即采用此方式,能自动把节点 IP 写进 SAN)。两者皆可;静态证书更直观、完全离线可控,缺点是新节点需手工签发+分发。若改用 TLS Bootstrap,需创建 bootstrap token、分发bootstrap-kubeconfig、并放行system:node-bootstrapper/system:certificates.k8s.io:certificatesigningrequests:nodeclient的 CSR 自动批准。
4.2 kube-proxy 配置与单元(master1)
bash
cat > /etc/kubernetes/kube-proxy-config.yaml <<'EOF'
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: iptables
clusterCIDR: 192.168.0.0/16
hostnameOverride: master1
bindAddress: 0.0.0.0
EOF
cat > /etc/systemd/system/kube-proxy.service <<'EOF'
[Unit]
Description=Kubernetes Kube-Proxy
After=network.target
[Service]
ExecStart=/usr/local/bin/kube-proxy \
--config=/etc/kubernetes/kube-proxy-config.yaml \
--kubeconfig=/etc/kubernetes/kube-proxy.kubeconfig \
--v=2
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now kube-proxy
systemctl status kube-proxy --no-pager
kube-proxy 模式选择
默认 iptables 模式(上文配置):兼容性最好,小规模集群足够。已加载的
br_netfilter、nf_conntrack是其依赖。生产推荐 IPVS 模式:大规模 Service/Endpoints 下转发性能与连接数显著优于 iptables。切换到 IPVS 需先加载
ip_vs、ip_vs_rr、ip_vs_wrr、ip_vs_sh内核模块(本文 §准备环境(4) 已加载),并将kube-proxy-config.yaml的mode: iptables改为mode: ipvs,按需追加:
yamlmode: ipvs ipvs: scheduler: rr excludeCIDRs: null clusterCIDR: 192.168.0.0/16切换后
systemctl restart kube-proxy,用ipvsadm -ln验证规则是否生成(需apt install ipvsadm)。
4.3 确认 master1 就绪
bash
kubectl get nodes -o wide
# 期望 master1 Ready;ROLES=control-plane
部署 · 第五步:网络 / DNS / 监控
5.1 Calico(CNI)
bash
cd /data/k8s-offline/deploy/files
kubectl apply -f calico.yaml
kubectl -n kube-system get pod -l k8s-app=calico-node -w
多网卡环境必须指定网卡探测方式 (否则 Calico 可能选错网卡导致 Pod 跨节点不通)。在
calico.yaml中calico-node的IP_AUTODETECTION_METHOD环境变量设置,例如单网卡直连二层网络用skip-interface(排除非业务网卡)或指定具体网卡:
yaml# calico.yaml 中 calico-node DaemonSet 的 env 段 - name: IP_AUTODETECTION_METHOD value: "interface=eth0" # 仅用 eth0 的地址作为节点 PodIP;多网卡可改为 cidr=192.168.0.0/16 等单网卡、集群二层互通时保留默认
autodetect即可。修改后kubectl apply -f calico.yaml重建 calico-node。
5.2 CoreDNS
在 master1 写一份可用 coredns.yaml(离线环境去掉官方模板占位符;forward 暂指 127.0.0.1 或注释,待可联网再改):
bash
cat > /etc/kubernetes/coredns.yaml <<'EOF'
apiVersion: v1
kind: ServiceAccount
metadata:
name: coredns
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels: { kubernetes.io/bootstrapping: rbac-defaults }
name: system:coredns
rules:
- apiGroups: [""]
resources: ["endpoints", "services", "pods", "namespaces"]
verbs: ["list", "watch"]
- apiGroups: ["discovery.k8s.io"]
resources: ["endpointslices"]
verbs: ["list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations: { kubernetes.io/bootstrapping: rbac-defaults }
name: system:coredns
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:coredns
subjects:
- kind: ServiceAccount
name: coredns
namespace: kube-system
---
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
cache 30
loop
reload
loadbalance
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: coredns
namespace: kube-system
labels: { k8s-app: kube-dns, kubernetes.io/name: CoreDNS }
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
selector:
matchLabels: { k8s-app: kube-dns }
template:
metadata:
labels: { k8s-app: kube-dns }
spec:
priorityClassName: system-cluster-critical
serviceAccountName: coredns
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
nodeSelector: { kubernetes.io/os: linux }
containers:
- name: coredns
image: registry.k8s.io/coredns/coredns:v1.14.2
imagePullPolicy: IfNotPresent
resources:
limits: { memory: 170Mi }
requests: { cpu: 100m, memory: 70Mi }
args: ["-conf", "/etc/coredns/Corefile"]
volumeMounts:
- name: config-volume
mountPath: /etc/coredns
readOnly: true
ports:
- containerPort: 53
name: dns
protocol: UDP
- containerPort: 53
name: dns-tcp
protocol: TCP
- containerPort: 9153
name: metrics
protocol: TCP
securityContext:
allowPrivilegeEscalation: false
capabilities: { add: ["NET_BIND_SERVICE"], drop: ["all"] }
readOnlyRootFilesystem: false
livenessProbe:
httpGet: { path: /health, port: 8080, scheme: HTTP }
initialDelaySeconds: 60
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 5
readinessProbe:
httpGet: { path: /ready, port: 8181, scheme: HTTP }
volumes:
- name: config-volume
configMap: { name: coredns, items: [{ key: Corefile, path: Corefile }] }
---
apiVersion: v1
kind: Service
metadata:
name: kube-dns
namespace: kube-system
annotations: { prometheus.io/port: "9153", prometheus.io/scrape: "true" }
labels: { k8s-app: kube-dns, kubernetes.io/cluster-service: "true", kubernetes.io/name: CoreDNS }
spec:
selector: { k8s-app: kube-dns }
clusterIP: 10.96.0.10
ports:
- name: dns
port: 53
protocol: UDP
- name: dns-tcp
port: 53
protocol: TCP
- name: metrics
port: 9153
protocol: TCP
EOF
kubectl apply -f /etc/kubernetes/coredns.yaml
kubectl -n kube-system get pod -l k8s-app=kube-dns -w
离线环境 CoreDNS 无上游 DNS,
forward已省略,仅提供集群内解析;可联网后补forward . 223.5.5.5并重建 Pod。
5.3 metrics-server
bash
cd /data/k8s-offline/deploy/files
sed -i 's# - --metric-resolution=15s# - --metric-resolution=15s\n - --kubelet-insecure-tls#' metrics-server-components.yaml
kubectl apply -f metrics-server-components.yaml
kubectl -n kube-system get pod -l k8s-app=metrics-server
访问验证
集群状态
bash
kubectl get nodes -o wide
kubectl get pod -A -o wide
kubectl cluster-info
网络与 DNS
bash
kubectl run net-test -it --rm --image=registry.k8s.io/pause:3.10.2 -- sh -c 'nslookup kubernetes.default.svc.cluster.local && echo DNS_OK'
# 期望 DNS_OK
apiserver → kubelet 连通(验证证书 SAN 正确)
这一步确认 apiserver 能反向连上 kubelet 的 10250(依赖 §部署·第一步 kubelet 证书的 IP SAN 与 §3.1 的
--kubelet-certificate-authority)。若 SAN 缺失,下面命令会直接报 x509 错误。
bash
kubectl run exec-test --rm -it --image=registry.k8s.io/pause:3.10.2 -- sh -c 'echo KUBELET_OK'
# 期望 KUBELET_OK(说明 apiserver 经 kubelet 拉起/exec 容器成功)
kubectl get nodes -o wide # 各节点 ROLES 正常、状态 Ready
监控
bash
kubectl top nodes
kubectl top pod -A
控制面组件日志排障
bash
journalctl -u kube-apiserver -u kube-controller-manager -u kube-scheduler -u etcd -u kubelet -u kube-proxy -f
故障排查速查表
| 现象 | 可能原因 | 处理 |
|---|---|---|
etcd 起不来,报 tls: failed to verify client certificate |
etcd CA / 客户端证书不匹配 | 复核 etcd-ca.pem 与 apiserver-etcd-client 是否同源签发 |
apiserver 起不来,报 x509: certificate is valid for ... not for ... |
apiserver SAN 缺当前 IP/主机名 | 在 apiserver-csr.json hosts 补齐后重签 apiserver.pem 并重启 |
kubectl get nodes 为空 / Node NotReady |
kubelet 未启动或容器运行时异常 | systemctl status kubelet containerd;查 journalctl -u kubelet |
| 跨节点 Pod 不通 | IPIP(协议 4)被防火墙拦截 | 放通 IP 协议 4,或 Calico 改 VXLAN(放通 UDP 4789) |
CoreDNS Pod CrashLoopBackOff |
forward 上游不可达 |
注释 forward 段(见 5.2)后删 Pod 重建 |
| kube-proxy 不生效,Service 不通 | kube-proxy.kubeconfig 证书/O 组错误 | 确认 CN=system:kube-proxy、O=system:node-proxier |
kubectl top 报错 Metrics API 不可用 |
metrics-server 未就绪 | 确认已加 --kubelet-insecure-tls |
| 证书过期 | 默认 10 年 | 用 cfssl 重签对应证书并重启组件 |
可选增强(生产加固)
以下两项为生产增强,默认文档未启用;按需叠加,均不影响基本功能。
apiserver 审计日志
创建审计策略与配置,apiserver 增加参数:
bash
cat > /etc/kubernetes/audit-policy.yaml <<'EOF'
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
verbs: ["create", "update", "delete"]
omitStages: ["RequestReceived"]
- level: None
EOF
# kube-apiserver.service 的 ExecStart 追加:
# --audit-log-path=/var/log/kubernetes/audit/audit.log \
# --audit-policy-file=/etc/kubernetes/audit-policy.yaml \
# --audit-log-maxage=7 --audit-log-maxbackup=10 --audit-log-maxsize=100
mkdir -p /var/log/kubernetes/audit
systemctl daemon-reload && systemctl restart kube-apiserver
Secret 加密静态存储(Encryption at Rest)
用本地生成的密钥对 etcd 中的 Secret 做 envelope 加密:
bash
head -c 32 /dev/urandom | base64 > /etc/kubernetes/encryption/enc.key # 妥善备份此密钥
cat > /etc/kubernetes/encryption/encryption-config.yaml <<'EOF'
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources: ["secrets"]
providers:
- aescbc:
keys:
- name: key1
secret: $(cat /etc/kubernetes/encryption/enc.key)
- identity: {}
EOF
chmod 600 /etc/kubernetes/encryption/encryption-config.yaml
# kube-apiserver.service 的 ExecStart 追加:
# --encryption-provider-config=/etc/kubernetes/encryption/encryption-config.yaml
systemctl daemon-reload && systemctl restart kube-apiserver
# 已有 Secret 重新加密:kubectl get secrets --all-namespaces -o json | kubectl replace -f -
运维附录
证书有效期与续期
全量证书默认 10 年(87600h)。查看剩余有效期:
bash
openssl x509 -in /etc/kubernetes/pki/apiserver.pem -noout -dates
续期:用对应 -csr.json 重新 cfssl gencert(CA 不变则只重签叶子证书),替换 .pem 后重启对应组件 systemd 单元。
集群重置(单控制面)
bash
# master1
systemctl stop kube-proxy kubelet kube-scheduler kube-controller-manager kube-apiserver etcd
rm -rf /etc/kubernetes /data/etcd /data/kubelet /var/lib/kubelet /data/containerd
# worker
systemctl stop kube-proxy kubelet
rm -rf /etc/kubernetes /data/kubelet /var/lib/kubelet