Kubernetes 纯二进制部署完全指南 (v1.28.15)
什么是纯二进制部署?
想象一下:纯二进制部署就像从零开始组装一台电脑。你需要自己下载 CPU、内存、硬盘等每个零件,然后手动安装到主板上。这种方式虽然比买整机麻烦,但你能完全掌控每个零件的品牌、型号和安装方式。
Kubernetes 的二进制部署也是如此:我们不使用自动化工具(如 kubeadm),而是手动下载每个组件的二进制文件,手动配置证书、启动服务。这样做的好处是让你完全理解每个组件的作用和依赖关系。
本文档记录了在 CentOS 7 上使用纯二进制方式部署 Kubernetes v1.28.15 的完整过程。
文档结构:
| 章节 | 适用节点 | 说明 |
|---|---|---|
| 第一步 ~ 第九步 | Master 节点 | 部署控制平面(etcd、API Server、Controller Manager、Scheduler)+ kubelet + kube-proxy |
| Worker 节点部署 | Worker 节点 | 从 Master 复制证书和配置,仅部署 kubelet + kube-proxy |
| 快速部署 | Master 节点 | 使用离线部署包一键部署 |
适用场景:
| 场景 | 说明 |
|---|---|
| :bulb: 学习 K8s 底层原理 | 理解每个组件如何协作 |
| :bulb: 特殊环境要求 | 无法使用 kubeadm 的环境 |
| :bulb: 精细化控制 | 需要自定义每个组件的配置 |
| :bulb: 生产环境 | 需要完全掌控部署过程 |
目录
Master 节点部署(控制平面 + 节点组件)
- 第一步:系统准备
Master + Worker - 第二步:安装 Docker
Master + Worker - 第三步:下载二进制文件
Master - 第四步:生成证书
Master - 第五步:创建配置文件
Master - 第六步:创建 Systemd 服务
Master - 第七步:启动服务
Master - 第八步:安装 CNI 网络插件
Master + Worker - 第九步:部署 CoreDNS
Master - 集群验证
Master
多节点集群
- Worker 节点部署
Worker
快速部署
部署架构
Kubernetes 的架构是什么?
想象一个大型的物流调度中心:
- Master 节点就像"总部",负责决策和调度
- Worker 节点就像"分站",负责实际执行任务
- etcd就像"仓库",存储所有订单信息
- API Server就像"客服热线",接收所有请求
┌─────────────────────────────────────────────────────────────┐
│ Master 节点 (192.168.174.128) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ API Server │ │ Controller │ │ Scheduler │ │
│ │ (6443) │ │ Manager │ │ (10259) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ └────────────────┼────────────────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ │ etcd │ │
│ │ (2379) │ │
│ └───────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ kubelet │ │ kube-proxy │ │
│ │ (10250) │ │ │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
┌─────┴─────┐
│ Flannel │
│ CNI │
└───────────┘
│
┌─────┴─────┐
│ Docker │
│ (containerd)│
└───────────┘
组件说明
每个组件是什么?
- etcd:就像仓库,存储所有订单(状态)信息
- API Server:就像客服热线,接收所有请求
- Controller Manager:就像调度员,确保订单被正确执行
- Scheduler:就像分配员,决定订单由哪个分站处理
- kubelet:就像分站站长,管理每个节点上的容器
- kube-proxy:就像网络管理员,处理容器间的网络通信
核心组件版本
| 组件 | 版本 | 类型 | 作用 |
|---|---|---|---|
| etcd | v3.5.12 | 二进制 | 分布式键值存储,保存集群状态 |
| kube-apiserver | v1.28.15 | 二进制 | API 网关,所有请求的入口 |
| kube-controller-manager | v1.28.15 | 二进制 | 控制循环,确保期望状态 |
| kube-scheduler | v1.28.15 | 二进制 | 调度器,分配 Pod 到节点 |
| kubelet | v1.28.15 | 二进制 | 节点代理,管理 Pod 生命周期 |
| kube-proxy | v1.28.15 | 二进制 | 网络代理,实现 Service |
| kubectl | v1.28.15 | 二进制 | 命令行工具 |
| Docker | 26.1.4 | 容器 | 容器运行时 |
| containerd | 1.6.33 | 容器 | 容器运行时 |
| Flannel | v0.24.2 | DaemonSet | CNI 网络插件 |
端口清单
| 端口 | 服务 | 方向 | 说明 |
|---|---|---|---|
| 6443 | kube-apiserver | 所有节点 | Kubernetes API 服务 |
| 2379 | etcd | Master 节点 | etcd 客户端端口 |
| 2380 | etcd | Master 节点 | etcd 对等端口 |
| 10250 | kubelet | 所有节点 | kubelet API |
| 10257 | kube-controller-manager | Master 节点 | Controller Manager |
| 10259 | kube-scheduler | Master 节点 | Scheduler |
| 30000-32767 | NodePort | 所有节点 | NodePort 服务范围 |
部署前准备
什么是准备?
就像建房子前要准备好地基、材料和工具一样,安装 Kubernetes 也需要确保系统环境满足要求。
系统要求
| 项目 | 最低要求 | 推荐配置 | 说明 |
|---|---|---|---|
| 操作系统 | CentOS 7 | CentOS 7/8 | 内核版本 3.10+ |
| CPU | 2 核 | 4 核+ | Master 节点建议 4 核+ |
| 内存 | 2GB | 4GB+ | Master 节点建议 4GB+ |
| 磁盘 | 20GB | 50GB+ | 建议使用 SSD |
| 网络 | 互通 | 互通 | 节点间网络畅通 |
网络端口要求
| 端口 | 协议 | 用途 | 方向 |
|---|---|---|---|
| 6443 | TCP | Kubernetes API Server | 所有节点 |
| 2379-2380 | TCP | etcd | Master 节点 |
| 10250 | TCP | kubelet API | 所有节点 |
| 10251 | TCP | kube-scheduler | Master 节点 |
| 10252 | TCP | kube-controller-manager | Master 节点 |
| 30000-32767 | TCP | NodePort 服务 | 所有节点 |
第一步:系统准备
适用于: Master 节点 + Worker 节点
为什么需要系统准备?
就像赛车比赛前要检查轮胎、加油、调整悬挂一样,系统准备确保 Kubernetes 组件能够正常运行。这些配置会影响内核如何处理网络流量和内存。
1.1 禁用 Swap 分区
什么是 Swap?
Swap 是 Linux 的"虚拟内存",当物理内存不够用时,系统会把部分内存数据临时存储到硬盘。Kubernetes 要求禁用 Swap,因为 kubelet 对内存使用有严格要求——它需要精确知道每个 Pod 消耗了多少内存,而 Swap 会干扰这个计算。
# 临时禁用 Swap
# 为什么:立即生效,不需要重启
sudo swapoff -a
# 永久禁用 Swap
# 为什么:重启后仍然禁用
# 怎么做:注释掉 /etc/fstab 中的 swap 行
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
# 验证 Swap 已禁用
# 预期输出:空(无任何内容)
sudo swapon --show
1.2 加载必要的内核模块
什么是内核模块?
内核模块就像乐高积木的连接件,让不同的组件能够互相通信。Kubernetes 需要这些模块来实现网络功能:
- overlay:支持容器网络叠加(像在现有网络上再建一层虚拟网络)
- br_netfilter:让网桥流量经过 iptables 规则
# 加载必要的内核模块
# 为什么:让内核支持容器网络功能
sudo modprobe overlay
sudo modprobe br_netfilter
# 永久加载
# 为什么:重启后自动加载
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
# 验证模块已加载
# 预期输出:显示 overlay 和 br_netfilter 模块
lsmod | grep -E "overlay|br_netfilter"
1.3 配置系统参数
什么是 sysctl 参数?
sysctl 参数就像系统的"开关",控制内核的各种行为。Kubernetes 需要特定的网络配置来实现 Pod 间通信:
- bridge-nf-call-iptables:让网桥流量经过 iptables,实现网络策略
- ip_forward:启用 IP 转发,让 Pod 能跨节点通信
# 配置网络参数
# 为什么:让 Kubernetes 的网络功能正常工作
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
# 应用配置
# 为什么:立即生效,不需要重启
sudo sysctl --system
# 验证配置
# 预期输出:三个参数都是 1
sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward
参数说明:
| 参数 | 值 | 作用 | 为什么需要 |
|---|---|---|---|
net.bridge.bridge-nf-call-iptables |
1 | 让网桥流量经过 iptables | Kubernetes 网络策略需要 |
net.bridge.bridge-nf-call-ip6tables |
1 | 让 IPv6 网桥流量经过 ip6tables | IPv6 网络支持 |
net.ipv4.ip_forward |
1 | 启用 IP 转发 | Pod 间跨节点通信 |
第二步:安装 Docker
适用于: Master 节点 + Worker 节点
什么是容器运行时?
容器运行时就像汽车的发动机,是让容器真正"跑起来"的核心组件。Docker 是最常用的容器运行时之一,它负责:
- 拉取容器镜像
- 创建和运行容器
- 管理容器的生命周期
2.1 安装 Docker
# 安装依赖包
# 为什么:Docker 需要这些工具来添加仓库和管理包
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
# 添加 Docker 仓库
# 为什么:从官方源安装最新稳定版
sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 安装 Docker
# 为什么:Docker 是容器运行时,K8s 需要它来运行容器
sudo yum install -y docker-ce docker-ce-cli containerd.io
# 启动 Docker
# 为什么:让 Docker 服务在后台运行
sudo systemctl daemon-reload
sudo systemctl enable docker
sudo systemctl start docker
2.2 配置 Docker 适配 Kubernetes
为什么需要特殊配置?
Docker 默认配置不完全适合 Kubernetes,就像赛车需要针对不同赛道调整悬挂一样。主要调整:
- cgroup 驱动:改为 systemd,与 kubelet 保持一致
- 日志驱动:配置日志轮转,防止磁盘占满
- 存储驱动:使用 overlay2,性能最佳
# 创建 Docker 配置目录
# 为什么:Docker 的配置文件需要放在这个目录
sudo mkdir -p /etc/docker
# 创建 Docker 配置文件
# 为什么:让 Docker 与 Kubernetes 兼容
cat <<EOF | sudo tee /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOF
# 重启 Docker
# 为什么:让配置生效
sudo systemctl daemon-reload
sudo systemctl restart docker
Docker 配置说明:
| 参数 | 值 | 作用 | 为什么重要 |
|---|---|---|---|
native.cgroupdriver |
systemd | 使用 systemd 管理 cgroup | 与 kubelet 保持一致 |
log-driver |
json-file | 使用 JSON 文件记录日志 | 便于排查问题 |
max-size |
100m | 单个日志文件最大 100MB | 防止磁盘占满 |
storage-driver |
overlay2 | 使用 overlay2 存储驱动 | 性能最佳 |
2.3 验证 Docker 安装
# 检查 Docker 版本
# 预期输出:显示 Docker 版本信息
docker --version
# 检查 Docker 信息
# 预期输出:Cgroup Driver: systemd
docker info | grep -i cgroup
# 测试 Docker 运行(需要联网,离线环境可跳过)
# 预期输出:Hello from Docker!
docker run hello-world
:bulb: 离线环境提示
如果在离线环境无法拉取 hello-world 镜像,可以跳过
docker run hello-world测试,只要docker --version和docker info输出正常即可。
第三步:下载二进制文件
适用于: Master 节点(Worker 节点仅需 kubelet、kube-proxy、kubectl)
什么是二进制安装?
二进制安装就像从官方商店直接下载已编译好的程序,而不是从源代码编译。这种方式更简单、更可靠。我们需要下载:
- etcd:数据库组件
- K8s 核心组件:apiserver, controller-manager, scheduler, kubelet, kube-proxy
- kubectl:命令行工具
3.1 下载 etcd
什么是 etcd?
etcd 就像物流中心的"仓库",存储所有订单信息(K8s 的状态数据)。没有它,整个系统就无法记忆任何信息。
# 创建临时目录
# 为什么:下载的文件需要先存放在这里
cd /tmp
# 下载 etcd
# 为什么:etcd 是 K8s 的数据库,存储集群状态
curl -L "https://github.com/etcd-io/etcd/releases/download/v3.5.12/etcd-v3.5.12-linux-amd64.tar.gz" -o etcd.tar.gz
# 解压
# 为什么:获取二进制文件
tar xzf etcd.tar.gz
# 安装到系统路径
# 为什么:让系统可以全局访问这些命令
cp etcd-v3.5.12-linux-amd64/etcd /usr/local/bin/
cp etcd-v3.5.12-linux-amd64/etcdctl /usr/local/bin/
# 验证安装
# 预期输出:显示 etcd 版本信息
etcd --version
3.2 下载 Kubernetes 组件
这些组件是什么?
- kube-apiserver:就像客服热线,接收所有请求
- kube-controller-manager:就像调度员,确保订单被正确执行
- kube-scheduler:就像分配员,决定订单由哪个分站处理
- kubelet:就像分站站长,管理每个节点上的容器
- kube-proxy:就像网络管理员,处理容器间的网络通信
- kubectl:就像遥控器,让你控制整个集群
# 定义版本变量
# 为什么:方便后续使用和维护
K8S_VERSION="1.28.15"
# 下载核心组件
# 为什么:这些是 K8s 的核心二进制文件
for bin in kube-apiserver kube-controller-manager kube-scheduler kubelet kube-proxy kubectl; do
echo "Downloading ${bin}..."
curl -sL "https://dl.k8s.io/release/v${K8S_VERSION}/bin/linux/amd64/${bin}" -o /usr/local/bin/${bin}
chmod +x /usr/local/bin/${bin}
done
# 验证安装
# 预期输出:显示各组件版本信息
kubectl version --client
第四步:生成证书
适用于: Master 节点(Worker 节点从 Master 复制证书)
为什么需要证书?
就像银行需要验证客户身份一样,K8s 组件之间需要通过证书来验证彼此的身份,确保通信安全。证书就像数字身份证,证明"我是谁"和"我可以访问什么"。
4.1 创建证书目录
# 创建证书目录
# 为什么:存放所有证书文件
mkdir -p /etc/kubernetes/pki
4.2 生成 CA 证书
什么是 CA?
CA(Certificate Authority)就像公安局,负责签发和管理身份证。K8s 使用 CA 来签发其他组件的证书,确保整个系统的信任链。
# 生成 CA 私钥
# 为什么:私钥用于签发其他证书,必须保密
openssl genrsa -out /etc/kubernetes/pki/ca.key 2048
# 生成 CA 证书
# 为什么:CA 证书用于验证其他证书的合法性
openssl req -new -x509 -days 3650 -key /etc/kubernetes/pki/ca.key \
-out /etc/kubernetes/pki/ca.crt -subj "/CN=kubernetes-ca"
4.3 生成 API Server 证书
为什么 API Server 需要特殊证书?
API Server 是所有组件的入口,需要一个包含所有访问地址的证书:
- DNS 名称:kubernetes, kubernetes.default, kubernetes.default.svc.cluster.local
- IP 地址:10.96.0.1 (Service ClusterIP), 192.168.174.128 (节点 IP), 127.0.0.1 (本地)
# 生成 API Server 私钥
openssl genrsa -out /etc/kubernetes/pki/apiserver.key 2048
# 生成证书签名请求
# 为什么:需要指定证书的使用者和备用名称
cat <<CSR > /tmp/apiserver-csr.conf
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
[req_distinguished_name]
[v3_req]
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = kubernetes
DNS.2 = kubernetes.default
DNS.3 = kubernetes.default.svc.cluster.local
IP.1 = 10.96.0.1
IP.2 = 192.168.174.128
IP.3 = 127.0.0.1
CSR
openssl req -new -key /etc/kubernetes/pki/apiserver.key \
-out /etc/kubernetes/pki/apiserver.csr -subj "/CN=kube-apiserver" -config /tmp/apiserver-csr.conf
# 创建扩展配置文件
cat <<EXT > /tmp/apiserver-ext.conf
[v3_req]
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = kubernetes
DNS.2 = kubernetes.default
DNS.3 = kubernetes.default.svc.cluster.local
IP.1 = 10.96.0.1
IP.2 = 192.168.174.128
IP.3 = 127.0.0.1
EXT
# 签发证书
# 为什么:使用 CA 私钥签发,证明这个证书是合法的
openssl x509 -req -days 3650 -in /etc/kubernetes/pki/apiserver.csr \
-CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key \
-CAcreateserial -out /etc/kubernetes/pki/apiserver.crt \
-extensions v3_req -extfile /tmp/apiserver-ext.conf
:bulb: 验证证书 SAN
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text | grep -A1 "Subject Alternative Name" # 预期输出:包含 DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc.cluster.local, IP:10.96.0.1, IP:192.168.174.128, IP:127.0.0.1 # 如果缺少 10.96.0.1,flannel 等 Pod 无法连接 API Server,需要重新生成证书
4.4 生成其他证书
为什么所有证书都要由 CA 签发?
就像所有身份证都要由公安局签发一样,K8s 的所有组件证书都必须由 CA(Certificate Authority)签发。如果证书是自签名的,其他组件就不信任它,导致连接失败。
# Service Account 密钥
# 为什么:用于创建和验证 Service Account Token
openssl genrsa -out /etc/kubernetes/pki/sa.key 2048
openssl req -new -x509 -days 3650 -key /etc/kubernetes/pki/sa.key \
-out /etc/kubernetes/pki/sa.crt -subj "/CN=service-accounts"
# Admin 证书(必须由 CA 签发!)
# 为什么:kubectl 使用这个证书连接 API Server
# 注意:如果使用自签名证书,kubectl 会报 "Unauthorized" 错误
openssl genrsa -out /etc/kubernetes/pki/admin.key 2048
openssl req -new -key /etc/kubernetes/pki/admin.key \
-out /etc/kubernetes/pki/admin.csr -subj "/CN=kubernetes-admin/O=system:masters"
openssl x509 -req -days 3650 -in /etc/kubernetes/pki/admin.csr \
-CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key \
-CAcreateserial -out /etc/kubernetes/pki/admin.crt
# Controller Manager 证书(必须由 CA 签发!)
# 为什么:Controller Manager 使用这个证书连接 API Server
openssl genrsa -out /etc/kubernetes/pki/controller-manager.key 2048
openssl req -new -key /etc/kubernetes/pki/controller-manager.key \
-out /etc/kubernetes/pki/controller-manager.csr \
-subj "/CN=system:kube-controller-manager/O=system:kube-controller-managers"
openssl x509 -req -days 3650 -in /etc/kubernetes/pki/controller-manager.csr \
-CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key \
-CAcreateserial -out /etc/kubernetes/pki/controller-manager.crt
# Scheduler 证书(必须由 CA 签发!)
# 为什么:Scheduler 使用这个证书连接 API Server
openssl genrsa -out /etc/kubernetes/pki/scheduler.key 2048
openssl req -new -key /etc/kubernetes/pki/scheduler.key \
-out /etc/kubernetes/pki/scheduler.csr \
-subj "/CN=system:kube-scheduler/O=system:kube-schedulers"
openssl x509 -req -days 3650 -in /etc/kubernetes/pki/scheduler.csr \
-CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key \
-CAcreateserial -out /etc/kubernetes/pki/scheduler.crt
:bulb: 验证证书:运行以下命令检查证书是否由 CA 签发
openssl x509 -in /etc/kubernetes/pki/admin.crt -noout -issuer # 预期输出:issuer= /CN=kubernetes-ca # 如果显示 issuer= /CN=kubernetes-admin,说明是自签名证书,需要重新生成
第五步:创建配置文件
适用于: Master 节点(Worker 节点从 Master 复制配置)
为什么需要配置文件?
配置文件就像使用说明书,告诉每个组件如何工作。没有配置文件,组件不知道该连接哪个服务器、使用什么证书、监听哪个端口。
5.1 创建 Token 文件
什么是 Token?
Token 就像临时密码,kubelet 使用它来向 API Server 注册自己。注册后,API Server 会签发正式的证书。
# 生成随机 Token
# 为什么:用于 kubelet 的初始认证
TOKEN=$(head -c 16 /dev/urandom | od -An -tx1 | tr -d ' \n')
# 创建 Token 文件
# 为什么:API Server 需要这个文件来验证 kubelet 的身份
cat <<EOF > /etc/kubernetes/token.csv
${TOKEN},kubelet-bootstrap,10001,"system:kubelet-bootstrap"
EOF
5.2 创建 Kubeconfig 文件
什么是 Kubeconfig?
Kubeconfig 就像连接配置文件,告诉 kubectl 或 kubelet 如何连接 API Server:
- 使用什么证书
- 连接哪个服务器
- 使用什么用户身份
# 创建 Bootstrap Kubeconfig
# 为什么:kubelet 使用这个文件向 API Server 注册
cat <<EOF > /etc/kubernetes/bootstrap-kubelet.conf
apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority: /etc/kubernetes/pki/ca.crt
server: https://192.168.174.128:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubelet-bootstrap
name: kubelet-bootstrap@kubernetes
current-context: kubelet-bootstrap@kubernetes
users:
- name: kubelet-bootstrap
user:
token: ${TOKEN}
EOF
# 创建 Admin Kubeconfig
# 为什么:kubectl 使用这个文件连接 API Server
cat <<EOF > /etc/kubernetes/admin.conf
apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority: /etc/kubernetes/pki/ca.crt
server: https://192.168.174.128:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
users:
- name: kubernetes-admin
user:
client-certificate: /etc/kubernetes/pki/admin.crt
client-key: /etc/kubernetes/pki/admin.key
EOF
# 创建 Controller Manager Kubeconfig
# 为什么:Controller Manager 使用这个文件连接 API Server
cat <<EOF > /etc/kubernetes/controller-manager.conf
apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority: /etc/kubernetes/pki/ca.crt
server: https://127.0.0.1:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: system:kube-controller-manager
name: system:kube-controller-manager@kubernetes
current-context: system:kube-controller-manager@kubernetes
users:
- name: system:kube-controller-manager
user:
client-certificate: /etc/kubernetes/pki/controller-manager.crt
client-key: /etc/kubernetes/pki/controller-manager.key
EOF
# 创建 Scheduler Kubeconfig
# 为什么:Scheduler 使用这个文件连接 API Server
cat <<EOF > /etc/kubernetes/scheduler.conf
apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority: /etc/kubernetes/pki/ca.crt
server: https://127.0.0.1:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: system:kube-scheduler
name: system:kube-scheduler@kubernetes
current-context: system:kube-scheduler@kubernetes
users:
- name: system:kube-scheduler
user:
client-certificate: /etc/kubernetes/pki/scheduler.crt
client-key: /etc/kubernetes/pki/scheduler.key
EOF
# 复制到 kubectl 默认路径
# 为什么:让 kubectl 能自动使用正确的配置
mkdir -p $HOME/.kube
cp /etc/kubernetes/admin.conf $HOME/.kube/config
第六步:创建 Systemd 服务
适用于: Master 节点(Worker 节点仅需 kubelet 和 kube-proxy 服务)
为什么需要 Systemd 服务?
Systemd 是 Linux 的服务管理器,就像手机的后台管理器,负责启动、停止和监控各种服务。创建 Systemd 服务让 K8s 组件能够:
- 开机自动启动
- 崩溃后自动重启
- 统一管理所有组件
6.1 etcd 服务
cat <<'EOF' > /etc/systemd/system/etcd.service
[Unit]
Description=etcd
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/etcd \
--name=etcd \
--data-dir=/var/lib/etcd \
--listen-client-urls=http://0.0.0.0:2379 \
--advertise-client-urls=http://192.168.174.128:2379 \
--listen-peer-urls=http://0.0.0.0:2380 \
--initial-advertise-peer-urls=http://192.168.174.128:2380 \
--initial-cluster=etcd=http://192.168.174.128:2380
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
etcd 参数说明:
| 参数 | 值 | 作用 |
|---|---|---|
--name |
etcd | 集群中的节点名称 |
--data-dir |
/var/lib/etcd | 数据存储目录 |
--listen-client-urls |
http://0.0.0.0:2379 | 监听客户端连接 |
--advertise-client-urls |
http://192.168.174.128:2379 | 对外公布的地址 |
6.2 kube-apiserver 服务
cat <<'EOF' > /etc/systemd/system/kube-apiserver.service
[Unit]
Description=Kubernetes API Server
After=network-online.target etcd.service
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/kube-apiserver \
--etcd-servers=http://127.0.0.1:2379 \
--service-cluster-ip-range=10.96.0.0/12 \
--bind-address=0.0.0.0 \
--secure-port=6443 \
--advertise-address=192.168.174.128 \
--allow-privileged=true \
--service-node-port-range=30000-32767 \
--enable-admission-plugins=NodeRestriction \
--authorization-mode=Node,RBAC \
--enable-bootstrap-token-auth=true \
--token-auth-file=/etc/kubernetes/token.csv \
--tls-cert-file=/etc/kubernetes/pki/apiserver.crt \
--tls-private-key-file=/etc/kubernetes/pki/apiserver.key \
--client-ca-file=/etc/kubernetes/pki/ca.crt \
--service-account-key-file=/etc/kubernetes/pki/sa.key \
--service-account-signing-key-file=/etc/kubernetes/pki/sa.key \
--service-account-issuer=https://kubernetes.default.svc \
--kubelet-client-certificate=/etc/kubernetes/pki/admin.crt \
--kubelet-client-key=/etc/kubernetes/pki/admin.key
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
kube-apiserver 参数说明:
| 参数 | 值 | 作用 |
|---|---|---|
--etcd-servers |
http://127.0.0.1:2379 | 连接 etcd 的地址 |
--service-cluster-ip-range |
10.96.0.0/12 | Service 的 ClusterIP 范围 |
--secure-port |
6443 | 安全端口 |
--allow-privileged |
true | 允许特权容器 |
:warning: 必须添加 kubelet 客户端证书参数
如果 kube-apiserver 缺少
--kubelet-client-certificate和--kubelet-client-key,kubectl logs和kubectl exec会报错:
error: You must be logged in to the server (the server has asked for the client to provide credentials)这是因为 API Server 需要通过这些证书代理请求到 kubelet。
6.3 kube-controller-manager 服务
cat <<'EOF' > /etc/systemd/system/kube-controller-manager.service
[Unit]
Description=Kubernetes Controller Manager
After=network-online.target kube-apiserver.service
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/kube-controller-manager \
--kubeconfig=/etc/kubernetes/controller-manager.conf \
--cluster-cidr=10.244.0.0/16 \
--service-cluster-ip-range=10.96.0.0/12 \
--cluster-name=kubernetes \
--cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt \
--cluster-signing-key-file=/etc/kubernetes/pki/ca.key \
--service-account-private-key-file=/etc/kubernetes/pki/sa.key \
--root-ca-file=/etc/kubernetes/pki/ca.crt \
--leader-elect=true \
--allocate-node-cidrs=true
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
6.4 kube-scheduler 服务
cat <<'EOF' > /etc/systemd/system/kube-scheduler.service
[Unit]
Description=Kubernetes Scheduler
After=network-online.target kube-apiserver.service
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/kube-scheduler \
--kubeconfig=/etc/kubernetes/scheduler.conf \
--leader-elect=true
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
6.5 kubelet 服务
为什么 kubelet 需要特殊配置?
kubelet 是每个节点的"站长",需要:
- 使用 bootstrap token 向 API Server 注册
- 等待 CSR 被批准后获得正式证书
- 配置容器运行时端点
# 创建 kubelet 配置文件
# 为什么:kubelet 需要配置 cgroup、DNS 等参数
mkdir -p /var/lib/kubelet
cat <<EOF > /var/lib/kubelet/config.yaml
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local
resolvConf: /run/systemd/resolve/resolv.conf
rotateCertificates: true
serverTLSBootstrap: true
EOF
# 创建 kubelet 服务
cat <<'EOF' > /etc/systemd/system/kubelet.service
[Unit]
Description=kubelet
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/kubelet \
--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf \
--kubeconfig=/var/lib/kubelet/kubeconfig \
--config=/var/lib/kubelet/config.yaml \
--pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.8 \
--node-ip=192.168.174.128 \
--client-ca-file=/etc/kubernetes/pki/ca.crt
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
:warning: 必须添加
--client-ca-file参数如果 kubelet 服务缺少
--client-ca-file=/etc/kubernetes/pki/ca.crt,kubectl logs和kubectl exec会报错:
error: You must be logged in to the server这是因为 kubelet API 需要验证客户端证书,但没有配置 CA 证书就无法验证。
6.6 kube-proxy 服务
为什么 kube-proxy 需要在 flannel 之前启动?
kube-proxy 使用 iptables 模式工作,它需要设置 ClusterIP 的路由规则。如果没有 kube-proxy,ClusterIP (10.96.0.1) 无法访问,flannel 容器就无法连接 API Server。
cat <<'EOF' > /etc/systemd/system/kube-proxy.service
[Unit]
Description=Kubernetes Kube-Proxy
After=network-online.target kube-apiserver.service
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/kube-proxy \
--kubeconfig=/etc/kubernetes/admin.conf \
--cluster-cidr=10.244.0.0/16 \
--proxy-mode=iptables
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
第七步:启动服务
适用于: Master 节点(Worker 节点启动方式不同,见 Worker 节点部署章节)
启动顺序为什么重要?
就像盖房子要先打地基再盖楼一样,K8s 组件有严格的启动顺序:
- etcd:先启动数据库,存储所有状态
- kube-apiserver:连接 etcd,提供 API 服务
- kube-controller-manager:连接 API Server,执行控制逻辑
- kube-scheduler:连接 API Server,执行调度逻辑
- kubelet:向 API Server 注册节点
- kube-proxy:设置网络规则
- flannel:配置容器网络(依赖 kube-proxy)
7.1 启动控制平面
# 重新加载 systemd 配置
# 为什么:让 systemd 识别新创建的服务文件
systemctl daemon-reload
# 启动 containerd(容器运行时)
# 为什么:kubelet 需要容器运行时来管理容器
systemctl enable containerd
systemctl start containerd
# 启动 etcd
# 为什么:etcd 是基础,其他组件都依赖它
systemctl enable etcd
systemctl start etcd
sleep 3
# 验证 etcd 启动
# 预期输出:显示 etcd 版本信息
curl -s http://localhost:2379/version
# 启动 kube-apiserver
# 为什么:API Server 是所有组件的入口
systemctl enable kube-apiserver
systemctl start kube-apiserver
sleep 5
# 验证 API Server 启动
# 预期输出:显示 K8s 版本信息
curl -k https://localhost:6443/version
# 启动 Controller Manager 和 Scheduler
# 为什么:它们都依赖 API Server
systemctl enable kube-controller-manager kube-scheduler
systemctl start kube-controller-manager kube-scheduler
sleep 3
# 验证组件状态
# 预期输出:所有组件都是 Healthy
kubectl get componentstatuses
7.2 启动节点组件
# 创建 resolv.conf
# 为什么:kubelet 和容器需要 DNS 解析
mkdir -p /run/systemd/resolve
cp /etc/resolv.conf /run/systemd/resolve/resolv.conf
# :warning: 清理旧的 pod sandbox(重要!)
# 为什么:如果有之前的部署,旧的挂载点会导致 kubelet 无法正常工作
# 解决方案:停止 containerd,杀死 shim 进程,lazy unmount,删除目录
systemctl stop containerd 2>/dev/null || true
pkill -9 -f "containerd-shim" 2>/dev/null || true
for mount in $(mount 2>/dev/null | grep "kubelet/pods.*projected" | awk '{print $3}'); do
umount -l "$mount" 2>/dev/null || true
done
rm -rf /var/lib/kubelet/pods 2>/dev/null || true
# 重新部署时必须清理旧的 kubelet 证书
# 为什么:旧证书会导致 kubelet 无法向 API Server 注册,报 "Unauthorized" 错误
rm -rf /var/lib/kubelet/pki 2>/dev/null || true
# 重启 containerd
systemctl start containerd
# 创建 RBAC 绑定(必须在启动 kubelet 之前!)
# 为什么:让 kubelet、controller-manager、scheduler 有权限操作资源
# :warning: 如果跳过这一步,kubelet 会报 "forbidden" 错误
kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --user=kubelet-bootstrap 2>/dev/null || true
kubectl create clusterrolebinding kubelet-anon --clusterrole=system:node --user=system:anonymous 2>/dev/null || true
kubectl create clusterrolebinding kubelet-nodes --clusterrole=system:node --group=system:nodes 2>/dev/null || true
kubectl create clusterrolebinding cm-admin --clusterrole=cluster-admin --user=system:kube-controller-manager 2>/dev/null || true
kubectl create clusterrolebinding sched-admin --clusterrole=cluster-admin --user=system:kube-scheduler 2>/dev/null || true
# 重启服务以加载新证书
systemctl restart kube-controller-manager kube-scheduler
sleep 5
# 启动 kubelet
# 为什么:kubelet 是节点的代理,管理 Pod
systemctl enable kubelet
systemctl start kubelet
# 等待 kubelet 启动
sleep 15
# 批准 CSR
# 为什么:kubelet 需要证书才能与 API Server 通信
kubectl get csr
for csr in $(kubectl get csr --no-headers | grep Pending | awk '{print $1}'); do
kubectl certificate approve $csr
done
# 启动 kube-proxy
# 为什么:kube-proxy 设置 ClusterIP 路由,flannel 需要它才能访问 API Server
# :warning: 必须在 flannel 之前启动!
systemctl enable kube-proxy
systemctl start kube-proxy
# 等待节点 Ready
# 预期输出:STATUS 显示 Ready
for i in {1..30}; do
if kubectl get nodes 2>/dev/null | grep -q "Ready"; then
echo "节点已 Ready"
break
fi
sleep 2
done
# 验证 ClusterIP 可访问
# 预期输出:显示 K8s 版本信息
curl -sk https://10.96.0.1:443/version
:warning: RBAC 绑定必须在 kubelet 启动之前创建
如果 RBAC 绑定在 kubelet 启动之后创建,kubelet 会报错:
cannot create certificate signing request: certificatesigningrequests.certificates.k8s.io is forbidden解决方案:确保上面的
kubectl create clusterrolebinding命令在systemctl start kubelet之前执行。
:bulb: 验证节点状态
# 如果节点显示 NotReady,检查以下内容 kubectl get nodes # 检查 CNI 插件 ls -la /opt/cni/bin/ # 检查 containerd 配置(确保 CRI 未被禁用) cat /etc/containerd/config.toml | grep disabled_plugins # 如果有 disabled_plugins = ["cri"],需要注释掉: # sed -i 's/disabled_plugins = \["cri"\]/# disabled_plugins = ["cri"]/' /etc/containerd/config.toml # systemctl restart containerd # 批准所有待处理的 CSR for csr in $(kubectl get csr --no-headers | grep Pending | awk '{print $1}'); do kubectl certificate approve $csr done
第八步:安装 CNI 网络插件
适用于: Master 节点 + Worker 节点
什么是 CNI?
CNI(Container Network Interface)就像容器世界的"交通规则",定义了 Pod 之间如何互相通信。没有 CNI,Pod 之间就像在没有道路的城市里,无法互相访问。
8.1 安装 CNI 插件
# 安装 CNI 插件
# 为什么:kubelet 需要这些插件来配置容器网络
yum install -y containernetworking-plugins
# 复制到 kubelet 期望的路径
# 为什么:kubelet 在 /opt/cni/bin/ 查找 CNI 插件
mkdir -p /opt/cni/bin
cp /usr/libexec/cni/* /opt/cni/bin/
# 验证安装
# 预期输出:显示 flannel, bridge 等插件
ls -la /opt/cni/bin/
8.2 安装 Flannel
什么是 Flannel?
Flannel 就像一个智能的邮政系统,为每个 Pod 分配一个"地址"(IP),并负责处理所有"邮件"(网络流量)的路由。
# :warning: 创建 CNI 配置文件(必须在 Flannel 之前!)
# 为什么:Flannel 的 init 容器需要 CNI 配置已存在才能正常工作
# 如果跳过这一步,Flannel 会卡在 Init 状态
mkdir -p /etc/cni/net.d
cat <<EOF > /etc/cni/net.d/10-flannel.conflist
{
"name": "cbr0",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
EOF
# 安装 Flannel(内联 manifest,无需联网下载)
# 为什么:使用镜像站的 Flannel 镜像,兼容离线环境
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
name: kube-flannel
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: flannel
namespace: kube-flannel
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: flannel
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list"]
- apiGroups: [""]
resources: ["nodes/status"]
verbs: ["patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: flannel
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: flannel
subjects:
- kind: ServiceAccount
name: flannel
namespace: kube-flannel
---
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-flannel-cfg
namespace: kube-flannel
labels:
tier: node
app: flannel
data:
cni-conf.json: |
{
"name": "cbr0",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-flannel-ds
namespace: kube-flannel
labels:
tier: node
app: flannel
spec:
selector:
matchLabels:
app: flannel
template:
metadata:
labels:
tier: node
app: flannel
spec:
serviceAccountName: flannel
initContainers:
- name: install-cni-plugin
image: docker.m.daocloud.io/flannel/flannel-cni-plugin:v1.4.0-flannel1
command: ["cp"]
args: ["-f", "/flannel", "/opt/cni/bin/flannel"]
volumeMounts:
- name: cni-plugin
mountPath: /opt/cni/bin
- name: install-cni
image: docker.m.daocloud.io/flannel/flannel:v0.24.2
command: ["cp"]
args: ["-f", "/etc/kube-flannel/cni-conf.json", "/etc/cni/net.d/10-flannel.conflist"]
volumeMounts:
- name: cni
mountPath: /etc/cni/net.d
- name: flannel-cfg
mountPath: /etc/kube-flannel
containers:
- name: kube-flannel
image: docker.m.daocloud.io/flannel/flannel:v0.24.2
command: ["/opt/bin/flanneld"]
args: ["--ip-masq", "--kube-subnet-mgr"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
resources:
requests:
cpu: 100m
memory: 50Mi
securityContext:
capabilities:
add: ["NET_ADMIN", "NET_RAW"]
volumeMounts:
- name: run
mountPath: /run/flannel
- name: flannel-cfg
mountPath: /etc/kube-flannel
- name: cni
mountPath: /etc/cni/net.d
tolerations:
- operator: Exists
volumes:
- name: run
hostPath:
path: /run/flannel
- name: cni
hostPath:
path: /etc/cni/net.d
- name: flannel-cfg
configMap:
name: kube-flannel-cfg
- name: cni-plugin
hostPath:
path: /opt/cni/bin
EOF
:warning: Flannel 卡在 Init 状态?
如果 Flannel Pod 一直显示
Init:0/2或CrashLoopBackOff,可能原因:
- CNI 配置不存在:确保
/etc/cni/net.d/10-flannel.conflist已创建(上面的步骤)- kube-proxy 未启动:Flannel 需要通过 ClusterIP (10.96.0.1) 连接 API Server
systemctl start kube-proxy curl -sk https://10.96.0.1:443/version # 验证 ClusterIP 可访问 kubectl delete pod -n kube-flannel --all --grace-period=0 --force- 镜像拉取失败:检查镜像是否已导入
docker images | grep flannel
第九步:部署 CoreDNS
适用于: Master 节点
什么是 DNS?
DNS 就像电话簿,让你可以通过名字(如 kubernetes.default.svc.cluster.local)找到对应的 IP 地址。CoreDNS 是 K8s 的 DNS 服务,负责:
- 解析 Service 名称
- 解析 Pod 名称
- 提供服务发现功能
# 创建 CoreDNS RBAC 权限
# 为什么:CoreDNS 需要读取 Service、Endpoint 等资源来提供 DNS 解析
# 如果跳过这一步,CoreDNS 会报 "forbidden" 错误
kubectl create clusterrole coredns --verb=list,watch --resource=services,endpointslices,endpoints,namespaces 2>/dev/null || true
kubectl create clusterrolebinding coredns --clusterrole=coredns --serviceaccount=kube-system:default 2>/dev/null || true
# 获取节点 DNS 服务器(避免 DNS 循环)
# 为什么:必须使用节点的 DNS 服务器,否则 CoreDNS 会形成 DNS 循环
NODE_DNS=$(grep nameserver /etc/resolv.conf | head -1 | awk '{print $2}')
echo "使用节点 DNS 服务器:${NODE_DNS}"
# 部署 CoreDNS
# 为什么:K8s 需要 DNS 服务来实现服务发现
cat <<EOF | kubectl apply -f -
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
forward . ${NODE_DNS} {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}
---
apiVersion: v1
kind: Service
metadata:
name: kube-dns
namespace: kube-system
labels:
k8s-app: kube-dns
spec:
selector:
k8s-app: kube-dns
ports:
- name: dns
port: 53
protocol: UDP
- name: dns-tcp
port: 53
protocol: TCP
clusterIP: 10.96.0.10
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: coredns
namespace: kube-system
labels:
k8s-app: kube-dns
spec:
replicas: 1
selector:
matchLabels:
k8s-app: kube-dns
template:
metadata:
labels:
k8s-app: kube-dns
spec:
priorityClassName: system-cluster-critical
containers:
- name: coredns
image: registry.aliyuncs.com/google_containers/coredns:v1.10.1
args:
- -conf
- /etc/coredns/Corefile
ports:
- containerPort: 53
name: dns
protocol: UDP
- containerPort: 53
name: dns-tcp
protocol: TCP
resources:
limits:
memory: 170Mi
requests:
cpu: 100m
memory: 70Mi
volumeMounts:
- name: config-volume
mountPath: /etc/coredns
readOnly: true
volumes:
- name: config-volume
configMap:
name: coredns
items:
- key: Corefile
path: Corefile
EOF
:warning: 为什么不能使用
forward . /etc/resolv.conf?如果 CoreDNS 使用
/etc/resolv.conf,而容器内的 DNS 指向 CoreDNS 自身(10.96.0.10),就会形成 DNS 循环。必须使用节点的 DNS 服务器(如 192.168.174.2)来避免这个问题。
:warning: CoreDNS 报 forbidden 错误?
如果 CoreDNS 日志显示
forbidden: User "system:serviceaccount:kube-system:default" cannot list resource,说明 RBAC 权限未创建。执行:kubectl create clusterrole coredns --verb=list,watch --resource=services,endpointslices,endpoints,namespaces kubectl create clusterrolebinding coredns --clusterrole=coredns --serviceaccount=kube-system:default kubectl delete pod -n kube-system -l k8s-app=kube-dns # 重启 CoreDNS
:warning: DNS 解析失败(SERVFAIL)?
如果 DNS 测试返回
SERVFAIL,检查:# 查看 CoreDNS 日志 kubectl logs -n kube-system -l k8s-app=kube-dns --tail=20 # 确认 CoreDNS Pod 正在运行 kubectl get pods -n kube-system -l k8s-app=kube-dns # 确认 CoreDNS Service 存在 kubectl get svc kube-dns -n kube-system
集群验证
适用于: Master 节点
为什么要验证?
就像新买的车需要试驾一样,安装完成后需要验证集群是否正常工作,确保所有组件都正确连接。
1. 检查节点状态
# 查看所有节点
# 预期输出:STATUS 为 Ready
kubectl get nodes -o wide
预期输出:
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
test Ready <none> XXm v1.28.15 192.168.174.128 <none> CentOS Linux 7 3.10.0-1160.el7 containerd://1.6.33
2. 检查系统组件
# 查看所有 Pod
# 预期输出:所有 Pod 都是 Running 状态
kubectl get pods -A
预期输出:
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-flannel kube-flannel-ds-xxxxx 1/1 Running 0 XXm
kube-system coredns-xxxxx 1/1 Running 0 XXm
3. 测试部署
# 创建测试部署
# 为什么:验证集群可以正常调度和运行 Pod
kubectl create deployment nginx-test --image=nginx:latest --replicas=2
# 等待 Pod 就绪
kubectl wait --for=condition=ready pod -l app=nginx-test --timeout=120s
# 检查部署状态
kubectl get deployment nginx-test
# 检查 Pod 状态
kubectl get pods -o wide
:bulb: 离线环境提示
nginx:latest镜像需要从网络拉取。离线环境请提前导入镜像:docker load -i nginx.tar # 从离线包导入 # 或使用已有的本地镜像替代,例如: kubectl create deployment nginx-test --image=192.168.174.128:5000/nginx --replicas=2
预期输出:
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-test 2/2 2 2 XXs
4. 测试网络服务
# 创建 Service
# 为什么:验证 Service 可以正常工作
kubectl expose deployment nginx-test --port=80 --type=NodePort
# 获取 Service 信息
kubectl get svc nginx-test
# 测试访问
curl -s http://10.x.x.x:80 | head -5
5. 清理测试资源
kubectl delete deployment nginx-test
kubectl delete svc nginx-test
常见问题速查
| 问题 | 现象 | 出现步骤 | 解决方案 |
|---|---|---|---|
| 节点 NotReady | kubectl get nodes 显示 NotReady |
第七步 | 检查 CNI 插件、containerd 配置、批准 CSR |
| flannel CrashLoopBackOff | flannel Pod 不断重启 | 第八步 | 启动 kube-proxy、检查镜像、验证 ClusterIP |
| RBAC 权限错误 | cannot create resource "replicasets" |
第七步 | 创建 cm-admin 和 sched-admin 绑定 |
| 证书缺少 ClusterIP | certificate is valid for ... not 10.96.0.1 |
第四步 | 重新生成 API Server 证书,确保包含 10.96.0.1 |
| DNS 循环 | Loop detected for zone "." |
第九步 | 使用节点 DNS 服务器,不用 /etc/resolv.conf |
| CoreDNS ConfigMap 缺失 | open /etc/coredns/Corefile: no such file |
第九步 | 检查 Deployment 的 volumeMounts 配置 |
| kubectl logs 401 | You must be logged in to the server |
第六步 | kubelet 添加 --client-ca-file、kube-apiserver 添加 kubelet 客户端证书 |
| CNI 配置为空 | cni plugin not initialized |
第八步 | 手动创建 /etc/cni/net.d/10-flannel.conflist |
| CoreDNS forbidden | cannot list resource "namespaces" |
第九步 | 创建 CoreDNS RBAC 权限 |
:bulb: 遇到问题?
详细的排查步骤已内嵌在各个步骤中,遇到问题时请回到对应步骤查看
:warning:标记的提示。
部署总结
正确的启动顺序
etcd → kube-apiserver → kube-controller-manager → kube-scheduler → kubelet → kube-proxy → flannel → CoreDNS
为什么这个顺序很重要?
- etcd 是基础,存储所有状态
- kube-apiserver 依赖 etcd
- controller-manager/scheduler 依赖 API Server
- kubelet 向 API Server 注册
- kube-proxy 设置网络规则,不依赖 CNI
- flannel 依赖 kube-proxy 的 ClusterIP 路由
- CoreDNS 依赖网络
组件状态速查表
| 组件 | 状态 | 验证命令 |
|---|---|---|
| etcd | ✅ Running | systemctl status etcd |
| kube-apiserver | ✅ Running | systemctl status kube-apiserver |
| kube-controller-manager | ✅ Running | systemctl status kube-controller-manager |
| kube-scheduler | ✅ Running | systemctl status kube-scheduler |
| kubelet | ✅ Running | systemctl status kubelet |
| kube-proxy | ✅ Running | systemctl status kube-proxy |
| Flannel | ✅ Running | kubectl get pods -n kube-flannel |
| CoreDNS | ✅ Running | kubectl get pods -n kube-system |
关键配置参数
| 参数 | 值 | 说明 |
|---|---|---|
| Kubernetes 版本 | v1.28.15 | 稳定版本 |
| Pod CIDR | 10.244.0.0/16 | Pod 网络地址段 |
| Service CIDR | 10.96.0.0/12 | Service 网络地址段 |
| API Server | https://192.168.174.128:6443 | 集群入口 |
| cgroup 驱动 | systemd | 与 kubelet 一致 |
常用命令速查
# 查看节点
kubectl get nodes -o wide
# 查看 Pod
kubectl get pods -A
# 查看日志
kubectl logs -f <pod-name> -n <namespace>
# 进入容器
kubectl exec -it <pod-name> -- /bin/bash
# 查看事件
kubectl get events --sort-by='.lastTimestamp'
# 查看集群信息
kubectl cluster-info
Worker 节点部署(多节点集群)
什么是 Worker 节点?
在之前的步骤中,我们部署了一个 Master 节点,它包含控制平面组件(etcd、API Server、Controller Manager、Scheduler)。Worker 节点只需要运行 kubelet 和 kube-proxy,负责实际运行业务 Pod。
Worker 节点 vs Master 节点
| 组件 | Master 节点 | Worker 节点 |
|---|---|---|
| etcd | ✅ 需要 | ❌ 不需要 |
| kube-apiserver | ✅ 需要 | ❌ 不需要 |
| kube-controller-manager | ✅ 需要 | ❌ 不需要 |
| kube-scheduler | ✅ 需要 | ❌ 不需要 |
| kubelet | ✅ 需要 | ✅ 需要 |
| kube-proxy | ✅ 需要 | ✅ 需要 |
| Docker/containerd | ✅ 需要 | ✅ 需要 |
| CNI 插件 | ✅ 需要 | ✅ 需要 |
准备工作
在 Master 节点 上执行以下操作,生成 Worker 节点需要的文件:
# 创建 Worker 节点部署包目录
mkdir -p /root/k8s-worker-pack/bin
mkdir -p /root/k8s-worker-pack/configs
mkdir -p /root/k8s-worker-pack/packages
mkdir -p /root/k8s-worker-pack/scripts
# 复制 Worker 节点需要的二进制文件
cp /usr/local/bin/kubelet /root/k8s-worker-pack/bin/
cp /usr/local/bin/kube-proxy /root/k8s-worker-pack/bin/
cp /usr/local/bin/kubectl /root/k8s-worker-pack/bin/
# 复制证书文件
cp /etc/kubernetes/pki/ca.crt /root/k8s-worker-pack/configs/
cp /etc/kubernetes/pki/admin.crt /root/k8s-worker-pack/configs/
cp /etc/kubernetes/pki/admin.key /root/k8s-worker-pack/configs/
# 复制 kubeconfig 文件
cp /etc/kubernetes/admin.conf /root/k8s-worker-pack/configs/
cp /etc/kubernetes/bootstrap-kubelet.conf /root/k8s-worker-pack/configs/
# 复制 kubelet 配置
cp /var/lib/kubelet/config.yaml /root/k8s-worker-pack/configs/
# 复制 RPM 包(Docker、containerd、CNI 插件)
cp /root/work/k8s-offline-pack/packages/*.rpm /root/k8s-worker-pack/packages/
echo "Worker 节点部署包已生成:/root/k8s-worker-pack/"
ls -la /root/k8s-worker-pack/
第一步:传输部署包到 Worker 节点
# 在 Master 节点执行,将部署包传输到 Worker 节点
# 将 WORKER_IP 替换为 Worker 节点的实际 IP 地址
scp -r /root/k8s-worker-pack root@WORKER_IP:/root/
第二步:Worker 节点系统准备
在 Worker 节点 上执行(与 Master 节点相同):
# 进入部署包目录
cd /root/k8s-worker-pack
# 禁用 Swap
swapoff -a
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
# 加载内核模块
modprobe overlay
modprobe br_netfilter
cat <<EOF > /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
# 配置系统参数
cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sysctl --system
# 创建 resolv.conf
mkdir -p /run/systemd/resolve
cp /etc/resolv.conf /run/systemd/resolve/resolv.conf
第三步:安装 Docker 和 containerd
# 从离线包安装
rpm -ivh --force packages/*.rpm
# 配置 Docker
mkdir -p /etc/docker
cat <<EOF > /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOF
# 启动 Docker 和 containerd
systemctl daemon-reload
systemctl enable docker containerd
systemctl start docker containerd
第四步:安装二进制文件和配置
# 安装二进制文件
cp bin/* /usr/local/bin/
chmod +x /usr/local/bin/kubelet /usr/local/bin/kube-proxy /usr/local/bin/kubectl
# 安装 CNI 插件
rpm -ivh --force packages/containernetworking-plugins*.rpm 2>/dev/null || true
mkdir -p /opt/cni/bin
cp /usr/libexec/cni/* /opt/cni/bin/ 2>/dev/null || true
# 创建证书目录
mkdir -p /etc/kubernetes/pki
cp configs/ca.crt /etc/kubernetes/pki/
cp configs/admin.crt /etc/kubernetes/pki/
cp configs/admin.key /etc/kubernetes/pki/
# 复制 kubeconfig 文件
cp configs/admin.conf /etc/kubernetes/
cp configs/bootstrap-kubelet.conf /etc/kubernetes/
# 创建 kubelet 配置
mkdir -p /var/lib/kubelet
cp configs/config.yaml /var/lib/kubelet/
# :warning: 修改 kubelet 配置中的 node-ip
# 将 WORKER_IP 替换为 Worker 节点的实际 IP
# 找到当前的 node-ip 配置并修改
:warning: 必须修改 kubelet 配置
Worker 节点的 kubelet 需要配置正确的 node-ip,否则其他节点无法访问该节点上的 Pod。
请将下面的WORKER_IP替换为 Worker 节点的实际 IP 地址。
第五步:创建 Systemd 服务
# kubelet 服务
cat <<'EOF' > /etc/systemd/system/kubelet.service
[Unit]
Description=kubelet
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/kubelet \
--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf \
--kubeconfig=/var/lib/kubelet/kubeconfig \
--config=/var/lib/kubelet/config.yaml \
--pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.8 \
--node-ip=WORKER_IP \
--client-ca-file=/etc/kubernetes/pki/ca.crt
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
# 将 WORKER_IP 替换为 Worker 节点的实际 IP
sed -i "s/WORKER_IP/$(hostname -I | awk '{print $1}')/g" /etc/systemd/system/kubelet.service
# kube-proxy 服务
cat <<'EOF' > /etc/systemd/system/kube-proxy.service
[Unit]
Description=Kubernetes Kube-Proxy
After=network-online.target kube-apiserver.service
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/kube-proxy \
--kubeconfig=/etc/kubernetes/admin.conf \
--cluster-cidr=10.244.0.0/16 \
--proxy-mode=iptables
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
第六步:启动服务
# 重新加载 systemd
systemctl daemon-reload
# 启动 kubelet
systemctl enable kubelet
systemctl start kubelet
# 等待 kubelet 启动并发送 CSR 请求
sleep 15
# 查看 CSR 请求
kubectl get csr
第七步:在 Master 节点批准 CSR
回到 Master 节点 执行:
# 查看待批准的 CSR
kubectl get csr
# 批准所有待处理的 CSR
for csr in $(kubectl get csr --no-headers | grep Pending | awk '{print $1}'); do
kubectl certificate approve $csr
echo "已批准: $csr"
done
# 回到 Worker 节点启动 kube-proxy
# (在 Worker 节点执行)
systemctl enable kube-proxy
systemctl start kube-proxy
# 等待节点 Ready
for i in {1..30}; do
if kubectl get nodes 2>/dev/null | grep -q "Ready"; then
echo "节点已 Ready"
break
fi
sleep 2
done
验证 Worker 节点
# 在 Master 节点执行
kubectl get nodes -o wide
# 预期输出:
# NAME STATUS ROLES AGE VERSION INTERNAL-IP ...
# master Ready <none> XXm v1.28.15 192.168.174.128 ...
# worker1 Ready <none> XXs v1.28.15 192.168.174.129 ...
# 测试跨节点 Pod 调度
kubectl run test-cross-node --image=nginx:latest --replicas=2
kubectl get pods -o wide # 检查 Pod 是否分布在不同节点
:bulb: 离线环境提示
如果 Worker 节点在离线环境,需要提前导入 Flannel 和 CoreDNS 镜像:
docker load -i /root/k8s-offline-pack/images/flannel.tar docker load -i /root/k8s-offline-pack/images/coredns.tar docker load -i /root/k8s-offline-pack/images/pause.tar
:warning: Worker 节点常见问题
节点一直 NotReady?
# 检查 kubelet 日志 journalctl -u kubelet -f # 常见原因: # 1. CSR 未批准 → 回到 Master 批准 CSR # 2. CNI 插件未安装 → 检查 /opt/cni/bin/ 是否有插件 # 3. kubelet 证书问题 → 检查 /var/lib/kubelet/pki/Pod 无法调度到 Worker 节点?
# 检查节点是否 Ready kubectl get nodes # 检查节点标签 kubectl get nodes --show-labels # 检查节点资源 kubectl describe node WORKER_NODE_NAME
快速部署(推荐)
:rocket: 小白用户推荐使用离线部署包
如果你觉得上面的步骤太复杂,可以使用我们打包好的离线部署包,只需一条命令即可完成部署。
离线部署包
# 1. 传输部署包到目标机器
scp k8s-offline-pack-v1.28.15.tar.gz root@目标IP:/root/
# 2. 解压并执行
tar xzf k8s-offline-pack-v1.28.15.tar.gz
cd k8s-offline-pack
# 3. 一键部署
./scripts/install.sh
# 或指定 IP 部署
NODE_IP=192.168.1.100 ./scripts/install.sh
离线部署包内容
| 目录 | 内容 | 说明 |
|---|---|---|
bin/ |
K8s 二进制文件 | etcd, kube-apiserver, kubelet 等 |
packages/ |
RPM 离线包 | Docker, containerd, CNI 插件 |
images/ |
Docker 镜像 | flannel, coredns, pause |
scripts/ |
自动化脚本 | install.sh 一键部署 |
部署总结
正确的启动顺序
containerd → etcd → kube-apiserver → kube-controller-manager → kube-scheduler → kubelet → kube-proxy → flannel → CoreDNS
为什么这个顺序很重要?
- containerd:容器运行时,kubelet 依赖它
- etcd:基础数据库,存储所有状态
- kube-apiserver:依赖 etcd
- controller-manager/scheduler:依赖 API Server
- kubelet:向 API Server 注册
- kube-proxy:设置网络规则,不依赖 CNI
- flannel:依赖 kube-proxy 的 ClusterIP 路由
- CoreDNS:依赖网络
关键注意事项
| :warning: 注意事项 | 说明 |
|---|---|
| 证书必须由 CA 签发 | 自签名证书会导致 Unauthorized 错误 |
| API Server 证书必须包含 10.96.0.1 | 否则 flannel 无法连接 |
| RBAC 绑定必须在启动 kubelet 之前创建 | 否则 kubelet 无法注册节点 |
| kube-proxy 必须在 flannel 之前启动 | 否则 ClusterIP 无法路由 |
| CoreDNS 必须使用节点 DNS | 否则会形成 DNS 循环 |
| kubelet 必须配置 --client-ca-file | 否则 kubectl logs/exec 会报 401 |
| kube-apiserver 必须配置 kubelet 客户端证书 | 否则 API Server 无法代理日志请求 |
| CoreDNS 必须创建 RBAC 权限 | 否则 DNS 解析会报 forbidden 错误 |
组件状态速查表
| 组件 | 状态 | 验证命令 |
|---|---|---|
| containerd | ✅ Running | systemctl status containerd |
| etcd | ✅ Running | systemctl status etcd |
| kube-apiserver | ✅ Running | systemctl status kube-apiserver |
| kube-controller-manager | ✅ Running | systemctl status kube-controller-manager |
| kube-scheduler | ✅ Running | systemctl status kube-scheduler |
| kubelet | ✅ Running | systemctl status kubelet |
| kube-proxy | ✅ Running | systemctl status kube-proxy |
| Flannel | ✅ Running | kubectl get pods -n kube-flannel |
| CoreDNS | ✅ Running | kubectl get pods -n kube-system |
文档版本: v1.4
适用 Kubernetes 版本: v1.28.15
部署环境: CentOS 7 (192.168.174.128)
最后更新: 2026年6月
更新内容:
- v1.4: 证书生成改用临时文件兼容所有 shell、Flannel 改为内联 manifest、添加离线环境提示、添加 kubelet 证书清理
- v1.3: 修复 kubelet --client-ca-file 缺失、kube-apiserver kubelet 客户端证书缺失、RBAC 绑定顺序错误、CNI 配置提前创建、CoreDNS RBAC 权限缺失
- v1.2: 修复证书生成(必须由 CA 签发)、添加 containerd 启动、添加旧 pod 清理、修复 CoreDNS DNS 循环
- v1.1: 新增问题 4-8 的排查方案
评论区