侧边栏壁纸
  • 累计撰写 23 篇文章
  • 累计创建 5 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录
k8s

Kubernetes-二进制部署文档

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 节点部署(控制平面 + 节点组件)

多节点集群

快速部署


部署架构

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 --versiondocker 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-keykubectl logskubectl 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.crtkubectl logskubectl 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 组件有严格的启动顺序:

  1. etcd:先启动数据库,存储所有状态
  2. kube-apiserver:连接 etcd,提供 API 服务
  3. kube-controller-manager:连接 API Server,执行控制逻辑
  4. kube-scheduler:连接 API Server,执行调度逻辑
  5. kubelet:向 API Server 注册节点
  6. kube-proxy:设置网络规则
  7. 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/2CrashLoopBackOff,可能原因:

  1. CNI 配置不存在:确保 /etc/cni/net.d/10-flannel.conflist 已创建(上面的步骤)
  2. 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
    
  3. 镜像拉取失败:检查镜像是否已导入
    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 的排查方案
0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区