离线二进制部署-Kubernetes-v1.36.4

离线二进制部署 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(容器镜像打包)

文件清单(组件 / 版本 / 下载地址)

# 文件名(最终名) 组件/版本 下载地址 存放目录
1 kubernetes-server-linux-amd64.tar.gz K8s v1.36.4 server 包(含 apiserver/controller-manager/scheduler/kubelet/kubectl) https://dl.k8s.io/v1.36.4/kubernetes-server-linux-amd64.tar.gz 操作机 deploy/bin/
2 etcd-v3.6.8-linux-amd64.tar.gz etcd 3.6.8 https://github.com/etcd-io/etcd/releases/download/v3.6.8/etcd-v3.6.8-linux-amd64.tar.gz 操作机 deploy/bin/
3 cfssl_1.6.4_linux_amd64 cfssl 1.6.4 https://github.com/cloudflare/cfssl/releases/download/v1.6.4/cfssl_1.6.4_linux_amd64 操作机 deploy/bin/
4 cfssljson_1.6.4_linux_amd64 cfssljson 1.6.4 https://github.com/cloudflare/cfssl/releases/download/v1.6.4/cfssljson_1.6.4_linux_amd64 操作机 deploy/bin/
5 nerdctl-full-2.3.5-linux-amd64.tar.gz containerd 2.3.3 全家桶 https://github.com/containerd/nerdctl/releases/download/v2.3.5/nerdctl-full-2.3.5-linux-amd64.tar.gz 操作机 deploy/files/
6 calico.yaml Calico v3.32.2 https://raw.githubusercontent.com/projectcalico/calico/v3.32.2/manifests/calico.yaml 操作机 deploy/files/
7 coredns.yaml CoreDNS v1.14.2 https://github.com/coredns/deployment/raw/master/kubernetes/coredns.yaml.sed (或下方部署节生成的简化版) 操作机 deploy/files/
8 metrics-server-components.yaml metrics-server v0.9.0 https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.9.0/components.yaml 操作机 deploy/files/

第 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-manager Unhealthy 多半是因为它们只监听 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,按需追加:

    yaml 复制代码
    mode: 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
相关推荐
天衍四九-3 小时前
Docker容器实战系列(八):Docker生产最佳实践与避坑指南,系列终章
运维·docker·容器
Zhou1411365 小时前
Docker_03_DockerCompose多容器编排
运维·docker·容器
程序猿阿越5 小时前
containerd如何拉取镜像
后端·kubernetes·源码阅读
Elastic 中国社区官方博客5 小时前
Kubernetes attributes processor v1:它对 EDOT Collector 意味着什么
java·大数据·elasticsearch·搜索引擎·贪心算法·kubernetes·全文检索
啊哈一半醒6 小时前
Docker 底层知识:从 Namespace 到 UnionFS
运维·docker·容器
fruge9 小时前
飞牛OS部署Flare导航页:Docker Compose、YAML书签配置与cpolar公网访问
运维·docker·容器
Zhou1411369 小时前
Docker_02_Dockerfile与存储网络
网络·docker·容器
bllovepigpig10 小时前
操作系统个人学习笔记(四):K8s基础
笔记·学习·kubernetes
键盘鼓手苏苏12 小时前
ESLint 规则渐进式升级:从 0 警告到全面开启的迁移策略(续篇)
云原生·kubernetes·k8