K8s 架构与核心组件
Master 组件
- API Server:集群对外的统一入口,以 RESTful API 方式做操作
- Scheduler:通过算法算出 Pod 调度到哪个节点
- Controller Manager:管理一系列的 Controller(Deployment、StatefulSet、Namespace 等)
- etcd:存储系统
Worker Node 组件
- kubelet:节点代理,负责 Pod 生命周期管理
- kube-proxy:负责 Service 的网络代理和负载均衡
- CoreDNS:集群 DNS 服务
- Calico:网络插件,负责 Pod IP 分配和网络策略
- containerd:容器运行时(K8s 1.24+ 已移除 dockershim,不再支持 Docker 作为直接运行时)
本文版本信息
- 操作系统:Rocky Linux 10(基于 RHEL 10)
- Kubernetes:1.35.x
- 容器运行时:containerd 2.x(配置格式 version = 3)
- kubeadm 配置 API:v1beta4(v1beta3 已废弃)
- 网络插件:Calico(最新版)
- 包管理器:DNF5(Rocky Linux 10 默认)
环境准备
实验环境规划
- 操作系统:Rocky Linux 10(下载地址:https://rockylinux.org/download,选择 Minimal ISO)
- 网络:NAT 模式,可以确保机器在任何网络都能 SSH 连接
| K8S 集群角色 | IP | 主机名 |
|---|---|---|
| 控制节点 | 192.168.164.100 | rm1 |
| 工作节点 | 192.168.164.101 | rn1 |
| 工作节点 | 192.168.164.102 | rn2 |
最低配置要求
- 控制节点:2 CPU / 2 GB RAM / 20 GB 磁盘
- 工作节点:2 CPU / 2 GB RAM / 20 GB 磁盘
- 所有节点需要网络互通,MAC 地址和 product_uuid 必须唯一
安装前准备
以下操作若无特殊说明,所有节点均需执行
1. 配置基础源
Rocky Linux 10 国内镜像源配置(使用阿里云镜像):
# 查看当前 repo 文件
ls /etc/yum.repos.d/
# 替换为阿里云镜像源
sed -e 's|^mirrorlist=|#mirrorlist=|g' \
-e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.aliyun.com/rockylinux|g' \
-i.bak \
/etc/yum.repos.d/rocky-*.repo
dnf makecache
换源说明
可以去阿里云镜像站(developer.aliyun.com/mirror)搜索 "Rocky Linux" 找到对应系统的换源操作。若阿里云暂未支持 Rocky Linux 10,可尝试 USTC(mirrors.ustc.edu.cn)或网易(mirrors.163.com)镜像源,或暂时使用官方源。
2. 安装基础软件包
Rocky Linux 10 使用 DNF5 作为默认包管理器,yum 作为兼容别名仍可使用:
dnf update -y
dnf install -y wget net-tools nfs-utils lrzsz gcc gcc-c++ make cmake \
libxml2-devel openssl-devel curl curl-devel unzip sudo libaio-devel \
vim ncurses-devel autoconf automake epel-release openssh-server \
telnet coreutils iputils iproute nmap-ncat jq psmisc git bash-completion \
yum-utils device-mapper-persistent-data lvm2 bind-utils
3. 配置网络
# 查看网卡名
ip addr
# 以 ens160 为例,修改为静态 IP(各节点 IP 不同,按实际修改)
nmcli connection modify ens160 ipv4.method manual connection.autoconnect yes
# 各节点 IP 不同,按实际修改
nmcli connection modify ens160 ipv4.addresses 192.168.164.100/24
nmcli connection modify ens160 ipv4.gateway 192.168.164.2
nmcli con mod "ens160" ipv4.dns "114.114.114.114,8.8.8.8,192.168.164.2"
nmcli connection down ens160 && nmcli connection up ens160
systemctl restart NetworkManager
reboot
# 验证 DNS 解析正常(能解析外部域名才能拉取镜像)
nslookup registry.cn-hangzhou.aliyuncs.com
DNS 配置说明
公共 DNS 必须放在 NAT 网关 DNS 前面。containerd(Go 语言 resolver)只使用第一个 nameserver 解析,
如果 192.168.164.2 在最前面,它会对外部域名返回 NXDOMAIN(域名不存在),Go resolver 收到明确的否定应答后不会再尝试后续 DNS,导致 kubeadm init 时所有镜像拉取失败。
- 114.114.114.114:国内公共 DNS,放第一位,解析快
- 8.8.8.8:Google DNS,作为备用
- 192.168.164.2:NAT 网关,放最后,仅解析内网域名
如果 nslookup 验证失败,说明网络不通,需先排查 NAT 网络配置,不要继续后续步骤。
Tip
NetworkManager 会把修改写入 /etc/NetworkManager/system-connections/ens160.nmconnection;手动编辑该文件也可,但改完需执行 nmcli connection reload
4. 关闭 SELinux
# 永久关闭(需要重启)
sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
# 立即临时关闭(不需要重启)
setenforce 0
# 验证
getenforce
5. 配置主机名
# 控制节点执行
hostnamectl set-hostname rm1 && bash
# 工作节点执行
hostnamectl set-hostname rn1 && bash
6. 配置 /etc/hosts
所有节点执行:
tee -a /etc/hosts <<'EOF'
192.168.164.100 rm1
192.168.164.101 rn1
192.168.164.102 rn2
EOF
7. 配置 SSH 互信
在控制节点执行:
ssh-keygen -t rsa -b 4096 -N ""
ssh-copy-id rn1
ssh-copy-id rn2
8. 关闭 Swap
重要
执行删除 swap LV 操作前,请先打虚拟机快照!如删除后无法启动,可从快照恢复。
# 临时关闭
swapoff -a && sysctl -w vm.swappiness=0
# 永久关闭(注释 fstab 中的 swap 行)
sed -ri '/^[^#]*swap/s@^@#@' /etc/fstab
systemctl daemon-reload
可选:释放 swap LV 空间并扩容 root 分区:
lvs # 查看 swap 占用的 LV
lvdisplay # 获取 swap LV Path(如 /dev/rl_rm1/swap)
lvremove /dev/rl_rm1/swap # 删除 swap LV
vgs # 查看 VG 剩余空间(VFree)
lvextend -L +<SIZE>G /dev/rl_rm1/home # 扩容 home LV(替换 SIZE 为实际空闲大小)
xfs_growfs /dev/mapper/rl_rm1-home # 扩容 XFS 文件系统
df -h # 验证
删除 swap 分区后必须重新配置 grub、BLS 引导条目、dracut 和 initramfs,否则重启会卡住或报 "找不到 swap" 错误:
# 1. 查看当前 GRUB_CMDLINE_LINUX 的完整内容(确认要删的参数)
grep GRUB_CMDLINE_LINUX /etc/default/grub
# 典型输出(VG 名含主机名,如 rl_rm1):
# GRUB_CMDLINE_LINUX="crashkernel=... resume=/dev/mapper/rl_rm1-swap rd.lvm.lv=rl_rm1/root rd.lvm.lv=rl_rm1/swap rhgb quiet"
# 2. 修改 /etc/default/grub,删除 swap 相关参数(影响未来新装内核的默认参数)
# 删除 resume= 参数(指向 swap 设备,用于休眠恢复)
sed -i 's/ resume=[^ "]*//' /etc/default/grub
# 删除 rd.lvm.lv=.../swap 参数(告诉 dracut 激活 swap LV)
sed -i 's/ rd\.lvm\.lv=[^ "]*swap//' /etc/default/grub
# 3. 验证 /etc/default/grub 修改结果
grep GRUB_CMDLINE_LINUX /etc/default/grub
# 4.【关键】使用 grubby 更新已有的 BLS 引导条目
# Rocky Linux 10 使用 BLS(Boot Loader Specification),内核启动参数存储在
# /boot/loader/entries/*.conf 中。仅修改 /etc/default/grub 不会更新已有的 BLS 条目!
# 必须用 grubby 删除所有已有内核条目中的 swap 相关参数:
grubby --update-kernel=ALL --remove-args="resume rd.lvm.lv=rl_rm1/swap"
# 注意:rd.lvm.lv=rl_rm1/swap 中的 rl_rm1 替换为实际 VG 名(通过 vgs 查看)
# 验证 BLS 条目已更新(确认 resume 和 swap 参数已消失)
grubby --info=ALL | grep -E "resume|swap" || echo "BLS 条目已清理干净"
# 5. 清除 dracut 的 resume 模块配置
# dracut 有独立的 resume 配置,仅清理引导参数不够!
# dracut -f 时 resume 模块仍会自动将已删除的 swap 设备写入 initramfs,
# 导致启动时 initramfs 尝试激活不存在的 swap LV 用于休眠恢复,系统卡住。
rm -f /etc/dracut.conf.d/resume.conf
# 显式禁用 resume 模块,防止 dracut 自动探测残留的 swap 引用
echo 'omit_dracutmodules+=" resume "' > /etc/dracut.conf.d/noresume.conf
# 如果存在 systemd-hibernate-resume 服务也一并禁用
systemctl disable systemd-hibernate-resume@*.service 2>/dev/null || true
# 6. 重新生成 initramfs(此步不可跳过,必须在上一步清除 resume 配置之后执行)
dracut -f
# 7. 重新生成 grub 配置
# Rocky Linux 10 的 UEFI 引导使用 wrapper 机制:
# /boot/efi/EFI/rocky/grub.cfg 只是一个薄包装,实际配置始终在 /boot/grub2/grub.cfg
# 因此无论 UEFI 还是 Legacy BIOS,统一使用以下命令:
grub2-mkconfig -o /boot/grub2/grub.cfg
# 8. 重启验证
init 6
9. 修改内核参数
# 加载必要的内核模块(overlay 用于 containerd,br_netfilter 用于 k8s 网络)
modprobe overlay
modprobe br_netfilter
# 持久化模块加载配置
cat > /etc/modules-load.d/k8s.conf <<'EOF'
overlay
br_netfilter
EOF
# 配置内核网络参数(开启包转发和过滤)
tee /etc/sysctl.d/k8s.conf <<'EOF'
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF
sysctl -p /etc/sysctl.d/k8s.conf
10. 关闭防火墙和 dnsmasq
systemctl stop firewalld && systemctl disable firewalld
systemctl disable --now dnsmasq 2>/dev/null || true
11. 配置时间同步
dnf install -y chrony
systemctl enable chronyd --now
tee -a /etc/chrony.conf <<'EOF'
server ntp1.aliyun.com iburst
server ntp2.aliyun.com iburst
server ntp1.tencent.com iburst
server ntp2.tencent.com iburst
EOF
systemctl restart chronyd
# 验证同步状态
chronyc tracking
12. 启用 IPVS
cat > /etc/modules-load.d/ipvs.conf <<'EOF'
br_netfilter
nf_conntrack
ip_vs
ip_vs_lc
ip_vs_wlc
ip_vs_rr
ip_vs_wrr
ip_vs_lblc
ip_vs_lblcr
ip_vs_dh
ip_vs_sh
ip_vs_fo
ip_vs_nq
ip_vs_sed
ip_vs_ftp
ip_tables
ip_set
xt_set
ipt_set
ipt_rpfilter
ipt_REJECT
ipip
EOF
# 安装 IPVS 相关工具
dnf install -y ipvsadm ipset sysstat conntrack-tools libseccomp
# 重启模块加载服务使配置生效
systemctl restart systemd-modules-load.service
# 验证
lsmod | grep -e ip_vs -e nf_conntrack
Rocky Linux 10 变更
ip_conntrack 模块在新内核中已更名为 nf_conntrack,请使用新名称。conntrack 软件包也已更名为 conntrack-tools。
13. 设置文件句柄数上限
ulimit -SHn 65535
cat >> /etc/security/limits.conf <<'EOF'
* soft nofile 655360
* hard nofile 655360
* soft nproc 655350
* hard nproc 655350
* soft memlock unlimited
* hard memlock unlimited
EOF
# 验证
ulimit -a
14. 【可选】系统内核优化
针对 8C8G 虚拟机,其余配置可相应调整:
cat > /etc/sysctl.d/k8s_better.conf <<'EOF'
vm.swappiness=0
vm.overcommit_memory=1
vm.panic_on_oom=0
fs.inotify.max_user_instances=16384
fs.inotify.max_user_watches=2097152
fs.file-max=104857600
fs.nr_open=104857600
net.ipv6.conf.all.disable_ipv6=1
net.netfilter.nf_conntrack_max=4621440
EOF
sysctl -p /etc/sysctl.d/k8s_better.conf
参数说明:
-vm.swappiness=0:禁用 swap,K8s 要求禁用 swap
-vm.overcommit_memory=1:允许内存过量分配,提高容器密度
-vm.panic_on_oom=0:OOM 时不让系统崩溃,而是杀死进程
-fs.inotify.*:增加文件系统监控限制,防止 "too many open files" 错误
-net.netfilter.nf_conntrack_max=4621440:增加网络连接跟踪表大小
15. 配置 Docker CE 阿里云 repo 源
# 安装 yum-utils(提供 yum-config-manager 命令)
dnf install -y yum-utils
# 添加阿里云 Docker CE 源
# Rocky Linux 10 基于 RHEL 10,使用 rhel 路径(不能用 centos 路径,centos 路径只有 el7/8/9)
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/rhel/docker-ce.repo
dnf clean all && dnf makecache
为何从 centos 改为 rhel
1.33 文档使用的是 centos/docker-ce.repo,其仓库只有 el7/el8/el9 的包。Rocky Linux 10 的 $releasever=10,解析后找不到对应包会报错。改为 rhel/docker-ce.repo 后,Docker CE 官方为 RHEL 10 提供了 el10 的包,阿里云同步了该镜像源,可以正常安装。
组件安装
Docker
Info
Docker 和 containerd 不冲突,安装 Docker 是为了能基于 Dockerfile 构建镜像。K8s 的容器运行时使用 containerd,与 Docker 无关。
# 安装最新版 Docker CE(无需像 1.33 那样固定 24.0.6 版本,
# 因为我们希望 containerd 也随之升级到最新版本)
dnf install -y docker-ce
systemctl enable docker --now
# 配置 Docker 镜像加速器(所有节点均需配置)
tee /etc/docker/daemon.json <<'EOF'
{
"registry-mirrors": [
"https://docker.m.daocloud.io"
]
}
EOF
systemctl daemon-reload && systemctl restart docker && systemctl status docker
containerd
# 从 Docker CE repo 安装最新版 containerd.io(无需固定版本号)
dnf install -y containerd.io
cd /etc/containerd
rm -rf *
# 加载必要模块
cat > /etc/modules-load.d/containerd.conf <<'EOF'
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter
sysctl --system
配置 containerd(使用 sed 修改关键参数,兼容 containerd 1.x 和 2.x):
mkdir -p /etc/containerd
containerd config default | tee /etc/containerd/config.toml
# 1. 启用 SystemdCgroup
sed -i 's#SystemdCgroup = false#SystemdCgroup = true#g' /etc/containerd/config.toml
# 2. 替换 sandbox (pause) 镜像为国内镜像(k8s 1.35.6 对应 pause 3.10.1)
sed -i 's#registry.k8s.io/pause#registry.cn-hangzhou.aliyuncs.com/google_containers/pause#g' /etc/containerd/config.toml
# 3. 配置 registry 镜像加速目录(containerd 1.7+ 支持此方式)
sed -i 's#config_path = ""#config_path = "/etc/containerd/certs.d"#g' /etc/containerd/config.toml
config_path 与 mirrors 互斥
当 config_path 不为空时,不能再配置 mirrors 段(即 [plugins."...".registry.mirrors]),二者互斥。containerd 2.x 的 registry 插件路径也有变化:
- V2:plugins."io.containerd.cri.v1.images".registry
- V1(旧版):plugins."io.containerd.grpc.v1.cri".registry
本文使用的 config_path + hosts.toml 方式同时兼容 1.7+ 和 2.x,是推荐做法。
配置镜像加速(目录方式,兼容 containerd 1.7+ 和 2.x):
# docker.io 镜像加速
mkdir -p /etc/containerd/certs.d/docker.io
cat > /etc/containerd/certs.d/docker.io/hosts.toml <<'EOF'
server = "https://registry-1.docker.io"
[host."https://docker.m.daocloud.io"]
capabilities = ["pull", "resolve"]
EOF
# registry.k8s.io 镜像加速(拉取 k8s 核心组件镜像)
mkdir -p /etc/containerd/certs.d/registry.k8s.io
cat > /etc/containerd/certs.d/registry.k8s.io/hosts.toml <<'EOF'
server = "https://registry.k8s.io"
[host."https://registry.cn-hangzhou.aliyuncs.com/google_containers"]
capabilities = ["pull", "resolve"]
EOF
# gcr.io 镜像加速
mkdir -p /etc/containerd/certs.d/gcr.io
cat > /etc/containerd/certs.d/gcr.io/hosts.toml <<'EOF'
server = "https://gcr.io"
[host."https://gcr.m.daocloud.io"]
capabilities = ["pull", "resolve"]
EOF
# ghcr.io 镜像加速(GitHub Container Registry)
mkdir -p /etc/containerd/certs.d/ghcr.io
cat > /etc/containerd/certs.d/ghcr.io/hosts.toml <<'EOF'
server = "https://ghcr.io"
[host."https://ghcr.m.daocloud.io"]
capabilities = ["pull", "resolve"]
EOF
# quay.io 镜像加速(Red Hat Quay)
mkdir -p /etc/containerd/certs.d/quay.io
cat > /etc/containerd/certs.d/quay.io/hosts.toml <<'EOF'
server = "https://quay.io"
[host."https://quay.m.daocloud.io"]
capabilities = ["pull", "resolve"]
EOF
# mcr.microsoft.com 镜像加速(Microsoft Container Registry)
mkdir -p /etc/containerd/certs.d/mcr.microsoft.com
cat > /etc/containerd/certs.d/mcr.microsoft.com/hosts.toml <<'EOF'
server = "https://mcr.microsoft.com"
[host."https://mcr.m.daocloud.io"]
capabilities = ["pull", "resolve"]
EOF
如果有私有仓库,额外添加配置。根据证书情况选择不同方式:
方式一:跳过证书校验(适合测试环境,如 HTTP 或自签证书不想验证)
mkdir -p /etc/containerd/certs.d/harbor.hanxux.local
cat > /etc/containerd/certs.d/harbor.hanxux.local/hosts.toml <<'EOF'
server = "http://harbor.hanxux.local"
[host."http://harbor.hanxux.local"]
capabilities = ["pull", "resolve", "push"]
skip_verify = true
EOF
方式二:使用自签 CA 证书验证(推荐生产环境)
# 以 HTTPS 私有仓库 hub.test.local 为例
mkdir -p /etc/containerd/certs.d/hub.test.local
# 将自签证书的根证书复制到对应目录
cp ca.crt /etc/containerd/certs.d/hub.test.local/
cat > /etc/containerd/certs.d/hub.test.local/hosts.toml <<'EOF'
server = "https://hub.test.local"
[host."https://hub.test.local"]
capabilities = ["pull", "resolve", "push"]
ca = "/etc/containerd/certs.d/hub.test.local/ca.crt"
[host."https://hub.test.local".header]
Authorization = "Basic <BASE64_USER_PASS>"
EOF
# 注:Authorization 值为 "用户名:密码" 的 base64 编码
# 生成方式:echo -n "admin:password" | base64
验证私有仓库拉取
crictl pull hub.test.local/镜像名:标签
启动并验证:
systemctl daemon-reload && systemctl start containerd && systemctl enable containerd
systemctl restart containerd
# 验证 containerd 本身(此时 crictl 尚未安装,需等 kubelet 安装后才可用)
containerd --version
# 检查关键插件状态(overlayfs 和 cri 都要是 ok)
ctr plugin ls | grep -E "overlayfs|cri"
K8s 1.35
配置 Kubernetes 仓库源
# 方式一:使用阿里云镜像源(国内推荐,速度快)
cat > /etc/yum.repos.d/kubernetes.repo <<'EOF'
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.35/rpm/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.35/rpm/repodata/repomd.xml.key
EOF
dnf clean all && dnf makecache
# 方式二:使用官方源(pkgs.k8s.io,需要能访问境外网络)
cat > /etc/yum.repos.d/kubernetes.repo <<'EOF'
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.35/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.35/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF
dnf clean all && dnf makecache
安装 kubeadm / kubelet / kubectl
# Rocky Linux 10 使用 DNF5,--disableexcludes 参数可能需要改为 --setopt=disable_excludes
# 安装时自动识别最新的 1.35.x 版本
dnf install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
# 若使用了 DNF5(Rocky Linux 10 默认),则用以下命令:
# dnf install -y kubelet kubeadm kubectl --setopt=disable_excludes=kubernetes
# 若要安装指定小版本:
# dnf install -y kubelet-1.35.6 kubeadm-1.35.6 kubectl-1.35.6 --disableexcludes=kubernetes
# 启动 kubelet(初始化前会反复重启,属于正常现象)
systemctl enable kubelet --now
# cri-tools(含 crictl)作为 kubelet 的依赖已自动安装,现在配置 crictl
cat > /etc/crictl.yaml <<'EOF'
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
debug: false
EOF
# 如果crictl没默认安装上
dnf install cri-tools -y
# 验证 crictl 与 containerd 的连通性
crictl info
离线安装
如果无法访问在线源,可以从以下地址下载 rpm 文件手动安装:
- https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.35/rpm/x86_64/
# 将 rpm 文件下载到 k8s-1.35 目录后:
rpm -ivh ./*.rpm
kubeadm 初始化 K8s 集群
kubeadm vs 二进制安装
- kubeadm:官方工具,快速部署,所有组件以 Pod 方式运行,具备故障自恢复能力,推荐自动化场景
- 二进制:手动安装,对 Kubernetes 理解更深,适合深度定制
- 两者均适合生产环境
初始化集群
在控制节点(rm1)上执行:
# 查看默认配置模板(可用于了解所有可配置字段)
kubeadm config print init-defaults
创建 kubeadm 配置文件(注意:K8s 1.35 使用 v1beta4 API):
tee kubeadm.yaml <<'EOF'
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 192.168.164.100 # 控制节点的 IP
bindPort: 6443
nodeRegistration:
criSocket: unix:///run/containerd/containerd.sock # 指定 containerd 运行时
imagePullPolicy: IfNotPresent
imagePullSerial: false # v1beta4 新增:false 表示并行拉取镜像,加快初始化速度
name: rm1 # 控制节点主机名
taints: null
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controllerManager: {}
dns: {}
etcd:
local:
dataDir: /var/lib/etcd
imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers # 国内镜像源
kubernetesVersion: 1.35.6 # 指定 K8s 版本
networking:
dnsDomain: cluster.local
podSubnet: 10.244.0.0/16 # Pod 网段(与 Calico 配置保持一致)
serviceSubnet: 10.96.0.0/12 # Service 网段
scheduler: {}
# v1beta4 新增:在初始化时就设置更长的证书有效期,避免日后频繁续签
# certificateValidityPeriod 控制非 CA 证书(apiserver、etcd 等),默认 8760h(1 年)
# caCertificateValidityPeriod 控制 CA 证书,默认 87600h(10 年)
certificateValidityPeriod: 87600h
caCertificateValidityPeriod: 175200h
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: ipvs
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
EOF
kubeadm v1beta4 重要变更(相比 v1beta3)
- API 版本:
kubeadm.k8s.io/v1beta4(v1beta3 已废弃,1.35 不再支持) - 新增类型:
ResetConfiguration、UpgradeConfiguration imagePullSerial:新字段,false= 并行拉取,加快初始化extraArgs:从 Map 类型改为结构化参数,支持重复键timeouts:新增超时配置块,替代ClusterConfiguration.timeoutForControlPlane- 证书有效期:新增
certificateValidityPeriod(默认 1 年)和caCertificateValidityPeriod(默认 10 年)字段,在kubeadm init时即可签发长期证书,建议直接设为 10 年以上,减少后期手动续签频率(但不能完全替代续签脚本,存量集群到期后仍需手动续签)
# 【推荐】init 前先验证 DNS 和镜像仓库的可达性,避免 init 中途因网络失败
nslookup registry.cn-hangzhou.aliyuncs.com
curl -s -o /dev/null -w "%{http_code}" https://registry.cn-hangzhou.aliyuncs.com/v2/
# 【推荐】提前拉取所需镜像,拉取成功再 init,省去 init 阶段的等待和不确定性
kubeadm config images pull \
--image-repository registry.cn-hangzhou.aliyuncs.com/google_containers \
--kubernetes-version 1.35.6
# 初始化 master 节点(仅在第一个 master 上执行)
kubeadm init --config=kubeadm.yaml --ignore-preflight-errors=SystemVerification
IPVS 模式说明
相比默认的 iptables 模式,IPVS 是高性能负载均衡器,在大规模集群下有更好的性能和可靠性,支持四层/七层负载均衡,生产环境建议开启。
初始化成功后,输出的 join 命令需要保存下来,用于工作节点加入集群。
配置 kubeconfig
# admin.conf 是集群的访问凭证,安全起见仅在 master 节点配置
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
# 配置 kubectl alias 和自动补全
tee -a ~/.bashrc <<'EOF'
alias k='kubectl'
source <(kubectl completion bash)
complete -o default -F __start_kubectl k
source /etc/profile
EOF
source ~/.bashrc
# 验证集群状态
kubectl get nodes
kubectl get pods -A
扩容工作节点
在控制节点(rm1)上获取 join 命令:
kubeadm token create --print-join-command
在工作节点(rn1、rn2)上分别执行返回的 join 命令:
# 示例(替换为实际输出的命令)
kubeadm join 192.168.164.100:6443 \
--token <token> \
--discovery-token-ca-cert-hash sha256:<hash> \
--ignore-preflight-errors=SystemVerification
在控制节点给工作节点打角色标签:
kubectl label nodes rn1 node-role.kubernetes.io/worker=worker
kubectl label nodes rn2 node-role.kubernetes.io/worker=worker
# 验证
kubectl get nodes
安装 Calico
从官网(https://docs.tigera.io/calico/latest)获取最新版 Calico的manifest:
# 下载最新版 calico.yaml(以 v3.31.4 为例,请替换为实际最新版)
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.31.4/manifests/calico.yaml
用 shell 命令指定正确的网卡名称(防止 Calico 使用 lo 地址,导致跨节点通信失败):
# 设置一个环境变量,替换为实际网卡名(通过 ip addr 查看)
IFACE="ens160"
# 在 CLUSTER_TYPE 配置段下方插入 IP_AUTODETECTION_METHOD
# 逻辑:匹配 CLUSTER_TYPE 行 → n 跳到下一行(value: "k8s,bgp")→ 在该行末追加两行新内容
# 加幂等检查,避免重复执行时重复插入
if ! grep -q "IP_AUTODETECTION_METHOD" calico.yaml; then
sed -i "/- name: CLUSTER_TYPE/{n;s/\(.*\)/\1\n - name: IP_AUTODETECTION_METHOD\n value: \"interface=${IFACE}\"/}" calico.yaml
fi
# 验证插入结果
grep -A2 "CLUSTER_TYPE" calico.yaml
kubectl apply -f calico.yaml
# 等待 Calico pod 就绪(所有 pod 变为 Running 状态)
kubectl get pods -n kube-system -w
验证网络连通性:
kubectl run busybox --image busybox:1.28 \
--image-pull-policy=IfNotPresent \
--restart=Never --rm -it busybox -- sh
# 容器内测试:
# ping www.baidu.com
# nslookup kubernetes.default.svc.cluster.local
配置 NFS(nfs-provisioner 前置依赖)
Info
如果需要部署 nfs-provisioner 来提供动态 PV,需要先在集群中配置 NFS 服务。PV 本质是先挂载到宿主机、再挂载到 Pod,因此所有节点都需要安装 NFS 客户端。
安装 NFS 服务(所有节点)
# nfs-utils 已在基础软件包中安装,如未安装则执行:
dnf install -y nfs-utils
systemctl start rpcbind && systemctl enable rpcbind
systemctl start nfs-server && systemctl enable nfs-server
配置 NFS 共享目录(仅 master节点)
# 创建 nfs-provisioner 数据目录
mkdir -p /data/nfs_pro
# 添加 NFS 导出配置
echo "/data/nfs_pro *(rw,no_root_squash)" >> /etc/exports
# 使配置生效
exportfs -arv
网段限制说明
如果需要限制可访问的网段,应填写宿主机网段(如 192.168.164.0/24)而非 Pod 网段,因为 PV 是挂载到宿主机上再挂载到 Pod 中的。
安装homebrew
为了更方便的安装k9s等app,宿主机上可以装一个homebrew:
# homebrew不让用root装,切换普通用户装
useradd -m hx && echo 'hx:hx' | chpasswd && echo 'hx ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/hx
su - hx
# 前置依赖
sudo dnf groupinstall -y "Development Tools"
sudo dnf install -y procps-ng curl file git
# 安装
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 加到PATH
echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> ~/.bashrc
source ~/.bashrc
# 换中科大源
export HOMEBREW_API_DOMAIN="https://mirrors.ustc.edu.cn/homebrew-bottles/api"
export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.ustc.edu.cn/homebrew-bottles"
export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.ustc.edu.cn/brew.git"
export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.ustc.edu.cn/homebrew-core.git"
# 写入 bashrc 持久化
cat >> ~/.bashrc << 'EOF'
export HOMEBREW_API_DOMAIN="https://mirrors.ustc.edu.cn/homebrew-bottles/api"
export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.ustc.edu.cn/homebrew-bottles"
export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.ustc.edu.cn/brew.git"
export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.ustc.edu.cn/homebrew-core.git"
EOF
安装k9s
# 方法1:webi装。升级的话就跑一遍同样的命令
curl -sS https://webi.sh/k9s | sh
# 方法2:brew装
brew install k9s
安装helm
下载地址: https://github.com/helm/helm/releases
# 对于k8s 1.23版本,小于等于3.11.x版本的helm是支持的
wget https://get.helm.sh/helm-v3.16.2-linux-amd64.tar.gz
tar zxvf helm-v3.16.2-linux-amd64.tar.gz
cp linux-amd64/helm /bin/ #/bin/是默认的环境变量路径之一,所以移动后你可以在任何位置运行这个二进制文件。
# 查看helm版本
helm version
【可选】安装 Addon
# 杜宽老师的 Addon 仓库(含 calico、dashboard、krm 多集群管理工具)
git clone https://gitee.com/dukuan/k8s-ha-install.git
cd k8s-ha-install/
git branch -a
git checkout manual-installation-v1.35.x # 切换到对应版本分支
cd single/
kubectl apply -f comp.yaml dashboard-user.yaml dashboard.yaml krm.yaml
# krm 默认用户名密码:admin/admin,在集群管理里添加集群
【可选】延长 K8s 证书
v1beta4 新字段与手动续签的关系
kubeadm v1beta4 新增了 certificateValidityPeriod 和 caCertificateValidityPeriod 字段,可以在 kubeadm init 初始化时直接签发长期证书(本文已在 kubeadm.yaml 中设为 10 年 / 20 年)。
但手动续签脚本仍然有价值,适用于以下场景:
- 初始化时未设置长有效期、证书已接近到期的存量集群
- kubeadm certs renew 内置命令续签时遵循配置的有效期(默认仍是 1 年),要续签到 100 年只能依赖本脚本
- 其他意外导致证书失效需要重新签发的情况
查看证书过期时间
# 方式一:使用 kubeadm(推荐)
kubeadm certs check-expiration
# 方式二:使用 openssl
openssl x509 -in /etc/kubernetes/pki/ca.crt -noout -text | grep -i Not
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text | grep -i Not
延长证书有效期脚本
文件命名为 update-kubeadm-cert.sh:
与 1.33 版本脚本的区别
本脚本移除了对 docker 命令的依赖(纯 containerd 环境没有 docker),改为使用 crictl 重启 static pod,kubelet 会自动重新创建它们。
#!/bin/bash
set -o errexit
set -o pipefail
# set -o xtrace
log::err() {
printf "[$(date +'%Y-%m-%dT%H:%M:%S.%N%z')]: \033[31mERROR: \033[0m$@\n"
}
log::info() {
printf "[$(date +'%Y-%m-%dT%H:%M:%S.%N%z')]: \033[32mINFO: \033[0m$@\n"
}
log::warning() {
printf "[$(date +'%Y-%m-%dT%H:%M:%S.%N%z')]: \033[33mWARNING: \033[0m$@\n"
}
check_file() {
if [[ ! -r ${1} ]]; then
log::err "can not find ${1}"
exit 1
fi
}
# get x509v3 subject alternative name from the old certificate
cert::get_subject_alt_name() {
local cert=${1}.crt
check_file "${cert}"
local alt_name=$(openssl x509 -text -noout -in ${cert} | grep -A1 'Alternative' | tail -n1 | sed 's/[[:space:]]*Address//g')
printf "${alt_name}\n"
}
# get subject from the old certificate
cert::get_subj() {
local cert=${1}.crt
check_file "${cert}"
local subj=$(openssl x509 -text -noout -in ${cert} | grep "Subject:" | sed 's/Subject:/\//g;s/\,/\//;s/[[:space:]]//g')
printf "${subj}\n"
}
cert::backup_file() {
local file=${1}
if [[ ! -e ${file}.old-$(date +%Y%m%d) ]]; then
cp -rp ${file} ${file}.old-$(date +%Y%m%d)
log::info "backup ${file} to ${file}.old-$(date +%Y%m%d)"
else
log::warning "does not backup, ${file}.old-$(date +%Y%m%d) already exists"
fi
}
cert::gen_cert() {
local cert_name=${1}
local cert_type=${2}
local subj=${3}
local cert_days=${4}
local alt_name=${5}
local cert=${cert_name}.crt
local key=${cert_name}.key
local csr=${cert_name}.csr
local csr_conf="distinguished_name = dn\n[dn]\n[v3_ext]\nkeyUsage = critical, digitalSignature, keyEncipherment\n"
check_file "${key}"
check_file "${cert}"
case "${cert_type}" in
client)
openssl req -new -key ${key} -subj "${subj}" -reqexts v3_ext \
-config <(printf "${csr_conf} extendedKeyUsage = clientAuth\n") -out ${csr}
openssl x509 -in ${csr} -req -CA ${CA_CERT} -CAkey ${CA_KEY} -CAcreateserial -extensions v3_ext \
-extfile <(printf "${csr_conf} extendedKeyUsage = clientAuth\n") -days ${cert_days} -out ${cert}
log::info "generated ${cert}"
;;
server)
openssl req -new -key ${key} -subj "${subj}" -reqexts v3_ext \
-config <(printf "${csr_conf} extendedKeyUsage = serverAuth\nsubjectAltName = ${alt_name}\n") -out ${csr}
openssl x509 -in ${csr} -req -CA ${CA_CERT} -CAkey ${CA_KEY} -CAcreateserial -extensions v3_ext \
-extfile <(printf "${csr_conf} extendedKeyUsage = serverAuth\nsubjectAltName = ${alt_name}\n") -days ${cert_days} -out ${cert}
log::info "generated ${cert}"
;;
peer)
openssl req -new -key ${key} -subj "${subj}" -reqexts v3_ext \
-config <(printf "${csr_conf} extendedKeyUsage = serverAuth, clientAuth\nsubjectAltName = ${alt_name}\n") -out ${csr}
openssl x509 -in ${csr} -req -CA ${CA_CERT} -CAkey ${CA_KEY} -CAcreateserial -extensions v3_ext \
-extfile <(printf "${csr_conf} extendedKeyUsage = serverAuth, clientAuth\nsubjectAltName = ${alt_name}\n") -days ${cert_days} -out ${cert}
log::info "generated ${cert}"
;;
*)
log::err "unknow, unsupported etcd certs type: ${cert_type}, supported type: client, server, peer"
exit 1
esac
rm -f ${csr}
}
cert::update_kubeconf() {
local cert_name=${1}
local kubeconf_file=${cert_name}.conf
local cert=${cert_name}.crt
local key=${cert_name}.key
check_file ${kubeconf_file}
grep "client-key-data" ${kubeconf_file} | awk {'print$2'} | base64 -d > ${key}
grep "client-certificate-data" ${kubeconf_file} | awk {'print$2'} | base64 -d > ${cert}
local subj=$(cert::get_subj ${cert_name})
cert::gen_cert "${cert_name}" "client" "${subj}" "${CAER_DAYS}"
local cert_base64=$(base64 -w 0 ${cert})
sed -i 's/client-certificate-data:.*/client-certificate-data: '${cert_base64}'/g' ${kubeconf_file}
log::info "generated new ${kubeconf_file}"
rm -f ${cert}
rm -f ${key}
if [[ ${cert_name##*/} == "admin" ]]; then
mkdir -p ~/.kube
cp -fp ${kubeconf_file} ~/.kube/config
log::info "copy the admin.conf to ~/.kube/config for kubectl"
fi
}
# 使用 crictl 重启 static pod(containerd 环境,不依赖 docker)
cert::restart_static_pod() {
local component=${1}
local pod_id=$(crictl pods --name "${component}" -q 2>/dev/null | head -1)
if [[ -n "${pod_id}" ]]; then
crictl stopp "${pod_id}" && crictl rmp "${pod_id}"
log::info "stopped ${component} pod, kubelet will restart it automatically"
else
log::warning "could not find ${component} pod via crictl, kubelet should restart it"
fi
}
cert::update_etcd_cert() {
PKI_PATH=${KUBE_PATH}/pki/etcd
CA_CERT=${PKI_PATH}/ca.crt
CA_KEY=${PKI_PATH}/ca.key
check_file "${CA_CERT}"
check_file "${CA_KEY}"
CART_NAME=${PKI_PATH}/server
subject_alt_name=$(cert::get_subject_alt_name ${CART_NAME})
cert::gen_cert "${CART_NAME}" "peer" "/CN=etcd-server" "${CAER_DAYS}" "${subject_alt_name}"
CART_NAME=${PKI_PATH}/peer
subject_alt_name=$(cert::get_subject_alt_name ${CART_NAME})
cert::gen_cert "${CART_NAME}" "peer" "/CN=etcd-peer" "${CAER_DAYS}" "${subject_alt_name}"
CART_NAME=${PKI_PATH}/healthcheck-client
cert::gen_cert "${CART_NAME}" "client" "/O=system:masters/CN=kube-etcd-healthcheck-client" "${CAER_DAYS}"
PKI_PATH=${KUBE_PATH}/pki
CA_CERT=${PKI_PATH}/ca.crt
CA_KEY=${PKI_PATH}/ca.key
check_file "${CA_CERT}"
check_file "${CA_KEY}"
CART_NAME=${PKI_PATH}/apiserver-etcd-client
cert::gen_cert "${CART_NAME}" "client" "/O=system:masters/CN=kube-apiserver-etcd-client" "${CAER_DAYS}"
cert::restart_static_pod "etcd"
log::info "restarted etcd"
}
cert::update_master_cert() {
PKI_PATH=${KUBE_PATH}/pki
CA_CERT=${PKI_PATH}/ca.crt
CA_KEY=${PKI_PATH}/ca.key
check_file "${CA_CERT}"
check_file "${CA_KEY}"
CART_NAME=${PKI_PATH}/apiserver
subject_alt_name=$(cert::get_subject_alt_name ${CART_NAME})
cert::gen_cert "${CART_NAME}" "server" "/CN=kube-apiserver" "${CAER_DAYS}" "${subject_alt_name}"
CART_NAME=${PKI_PATH}/apiserver-kubelet-client
cert::gen_cert "${CART_NAME}" "client" "/O=system:masters/CN=kube-apiserver-kubelet-client" "${CAER_DAYS}"
cert::update_kubeconf "${KUBE_PATH}/controller-manager"
cert::update_kubeconf "${KUBE_PATH}/scheduler"
cert::update_kubeconf "${KUBE_PATH}/admin"
set +e
grep kubelet-client-current.pem /etc/kubernetes/kubelet.conf > /dev/null 2>&1
kubelet_cert_auto_update=$?
set -e
if [[ "$kubelet_cert_auto_update" == "0" ]]; then
log::warning "does not need to update kubelet.conf"
else
cert::update_kubeconf "${KUBE_PATH}/kubelet"
fi
CA_CERT=${PKI_PATH}/front-proxy-ca.crt
CA_KEY=${PKI_PATH}/front-proxy-ca.key
check_file "${CA_CERT}"
check_file "${CA_KEY}"
CART_NAME=${PKI_PATH}/front-proxy-client
cert::gen_cert "${CART_NAME}" "client" "/CN=front-proxy-client" "${CAER_DAYS}"
# 重启控制面各组件(containerd 环境使用 crictl,kubelet 会自动重新创建 static pod)
cert::restart_static_pod "kube-apiserver"
log::info "restarted kube-apiserver"
cert::restart_static_pod "kube-controller-manager"
log::info "restarted kube-controller-manager"
cert::restart_static_pod "kube-scheduler"
log::info "restarted kube-scheduler"
systemctl restart kubelet
log::info "restarted kubelet"
}
main() {
local node_tpye=$1
KUBE_PATH=/etc/kubernetes
CAER_DAYS=36500
# 备份 /etc/kubernetes 目录
cert::backup_file "${KUBE_PATH}"
case ${node_tpye} in
etcd)
cert::update_etcd_cert
;;
master)
cert::update_master_cert
;;
all)
cert::update_etcd_cert
cert::update_master_cert
;;
*)
log::err "unknow, unsupported certs type: ${node_tpye}, supported type: all, etcd, master"
printf "Documentation:
example:
'\033[32m./update-kubeadm-cert.sh all\033[0m' update all etcd certificates, master certificates and kubeconf
'\033[32m./update-kubeadm-cert.sh etcd\033[0m' update only etcd certificates
'\033[32m./update-kubeadm-cert.sh master\033[0m' update only master certificates and kubeconf
"
exit 1
esac
}
main "$@"
运行脚本
chmod +x update-kubeadm-cert.sh
./update-kubeadm-cert.sh all
# 检查 kube-system pod 状态
kubectl get pods -n kube-system
# 检查证书有效期
kubeadm certs check-expiration
openssl x509 -in /etc/kubernetes/pki/ca.crt -noout -text | grep -i Not
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text | grep -i Not
集群卸载
kubeadm reset -f
rm -rf ~/.kube/
rm -rf /etc/kubernetes/
rm -rf /etc/systemd/system/kubelet.service.d
rm -rf /etc/systemd/system/kubelet.service
rm -rf /usr/bin/kube*
rm -rf /etc/cni
rm -rf /opt/cni
rm -rf /var/lib/etcd
rm -rf /var/etcd
# 清理 containerd 数据(可选,会删除所有镜像和容器)
# rm -rf /var/lib/containerd
Windows 下安装 kubectl
推荐使用 scoop 安装:
- 安装 scoop,参考 helmv3-安装与使用
- 安装 kubectl:
scoop install kubectl kubelogin
制作 K8s 安装所需离线源
Info
全内网环境安装 K8s 时,需要在有网络的机器上提前下载好所有资源。
制作 containerd 离线包
# 在联网机器下载 containerd 及其依赖
dnf download --resolve containerd.io --destdir=/mnt/containerd
# 打包并传输到内网
tar czf containerd-offline.tar.gz -C /mnt containerd
# 在内网机器安装
cd /path/to/containerd && rpm -Uvh ./*.rpm --nodeps --force
制作 K8s 命令行工具离线包
# 下载 K8s 组件 rpm(需先配置 kubernetes repo)
dnf download --resolve kubelet kubeadm kubectl --destdir=/mnt/k8s \
--disableexcludes=kubernetes
# 或从阿里云镜像站直接下载
# https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.35/rpm/x86_64/
# 在内网机器安装
rpm -Uvh ./*.rpm --nodeps --force
制作离线容器镜像
# 查看 K8s 1.35 所需镜像列表
kubeadm config images list \
--kubernetes-version 1.35.6 \
--image-repository registry.cn-hangzhou.aliyuncs.com/google_containers
# 拉取所需镜像
kubeadm config images pull \
--kubernetes-version 1.35.6 \
--image-repository registry.cn-hangzhou.aliyuncs.com/google_containers
# 使用 containerd 导出镜像(ctr 方式)
IMAGE_LIST=$(ctr -n=k8s.io images ls -q)
ctr -n=k8s.io images export k8s-1.35.6-images.tar.gz ${IMAGE_LIST}
# 在内网机器导入镜像
ctr -n=k8s.io images import k8s-1.35.6-images.tar.gz
# 验证
ctr -n=k8s.io images ls