本文档基于用户AVICIALIFE的xshell操作导出文件生成的操作文档实例,所有操作与命令回执均源自于用户本身 经GPT3代为整理,值得说明的是 文中部分命令 与笔者xshell的操作引导导出文件有一定出入 例如vim的操作项目用sed i 代替 旨在于让文章代码块的命令更规范,通用。
后半部分的脚本编程部分是基于前半部分的操作 自动化生成脚本 只针对特定环境实例有效 且经过笔者有效使用无逻辑bug 本文重点部分 是笔者陈述对整个架构搭建的理解。


第一部分:基础概念与组件说明

容器

容器就是一台机器上运行的一个独立单元。这个单元里面包含了程序本身、运行这个程序需要的所有系统库、配置文件、环境变量等等。这个单元跟机器上的其他单元是隔离开的,它有自己的文件系统、自己的网络设置、自己的进程空间。你可以在一台机器上同时跑很多个这样的单元,它们互相不干扰,每个单元只用自己的那一套东西。

容器是最小的运行单位,程序跑在容器里,不是直接跑在机器上。一个容器里可以跑一个程序,也可以跑多个程序,但通常建议一个容器只跑一个程序。

Docker

Docker 是管理这些容器的工具。它不生产容器,但它负责拉取镜像、创建容器、启动容器、停掉容器、删除容器。你在机器上装了 Docker 之后,通过命令行告诉 Docker 要干什么,Docker 就去执行。

Docker 本身是一个后台服务,它一直在运行,等着接收指令。你敲的命令会发给这个服务,服务收到后去做对应的事情。它负责分配资源给容器,比如 CPU 用多少、内存用多少。它也负责隔离,保证不同的容器之间互相看不到对方的东西。

所以容器是实际干活的最小单元,Docker 是管控这些单元的工具。

容器和 Docker

容器是东西本身,Docker 是操作这个东西的工具。就像你要用一块硬盘,硬盘本身是存储东西的介质,但你需要有个文件系统来管理这块硬盘上的文件怎么存放、怎么读取。容器就是那块硬盘,Docker 就是文件系统。没有 Docker,容器就只是一个打包好的文件目录,没法真正跑起来。有了 Docker,你才能让这个打包好的目录变成一个运行中的进程。

Docker 和 Kubernetes

Kubernetes 是管理多台机器上的大量容器的工具。Docker 只管单台机器上的容器,你在一台机器上装了 Docker,能在这台机器上跑容器。但如果有一百台机器,每台上面都有 Docker,你总不能一台一台去敲命令告诉它们要跑什么容器、容器挂了怎么办。Kubernetes 就是来解决这个问题的。

Kubernetes 把所有机器当成一个整体来看。你告诉 Kubernetes 说我要跑三个容器,Kubernetes 自己决定这三台分别放到哪台机器上。其中一个容器挂了,Kubernetes 发现数量不够了,自动在合适的机器上再起一个补上。程序要升级版本,Kubernetes 可以一台一台换,保证整个过程服务不中断。

Docker 做的是具体执行,Kubernetes 做的是统一指挥。Kubernetes 发出指令,说现在要在某台机器上启动某个容器,那台机器上的 Docker 就去做这个事。Docker 不听 Kubernetes 的也可以,但多台机器协调不了。

镜像仓库

镜像仓库是存放镜像的地方。镜像打好之后,它是一个文件包,你不能到处复制。有了仓库,你把镜像推上去,所有需要的人从仓库拉下来。Harbor 就是这个仓库,它部署在你自己的内网里,取镜像走内网,速度比去外网仓库快很多。

cri-dockerd

Kubernetes 发指令说要在某台机器上启动一个容器,但这句话不是说给 Docker 听的,Kubernetes 用的是标准接口叫 CRI。Docker 听不懂 CRI 标准接口的指令,所以中间需要有一个翻译。cri-dockerd 就是做这个翻译的。

Kubernetes 用 CRI 标准格式告诉 cri-dockerd 要拉一个镜像、要启动一个容器,cri-dockerd 把这些话翻译成 Docker 能听懂的格式,然后转给 Docker。Docker 收到后就去拉镜像、启动容器。没有这个翻译,Kubernetes 的话 Docker 听不懂,容器就起不来。

网络插件

网络插件解决的是跨机器通信的问题。没有网络插件之前,情况是这样的:

每台机器上的容器,Docker 会给它分配一个 IP 地址。但是这台机器上的容器 IP,只有这台机器自己知道,别的机器不知道。举个例子,机器 A 上有个容器 IP 是 172.17.0.2,机器 B 上的容器想访问这个地址,它根本不知道怎么走,因为它不知道 172.17.0.2 在哪台机器上。机器 B 发出去的数据包到了交换机,交换机也不知道 172.17.0.2 是哪台机器,数据包就丢了。

所以没有网络插件的时候,不同机器上的容器完全没法通信。你可以在单台机器上跑一堆容器互相访问,但只要容器分散到不同机器上,它们之间就是断的。

有了网络插件之后,情况变了。网络插件会在整个集群里规划一个大网段,比如 10.244.0.0/16,然后给每台机器分配一段子网。比如机器 A 拿到 10.244.1.0/24,机器 B 拿到 10.244.2.0/24。机器 A 上的容器从 10.244.1.0/24 里取 IP,机器 B 上的容器从 10.244.2.0/24 里取 IP。

这样所有容器的 IP 都在 10.244.0.0/16 这个大网里,每台机器只负责自己子网里的那一小段。

网络插件还做一件事:每台机器上都有一个代理,它知道整个集群的分配情况。机器 A 的代理知道 10.244.2.0/24 在机器 B 上,机器 B 的代理知道 10.244.1.0/24 在机器 A 上。

当机器 A 上的容器要访问 10.244.2.10 这个 IP 时,数据包先到机器 A 的代理那里,代理看到目标在机器 B 的网段里,就把这个数据包加上一层外包装,外包装上写的是机器 B 的物理 IP,通过物理网络发到机器 B。机器 B 收到后拆开外包装,看到里面的真正目标是 10.244.2.10,就把它交给对应的容器。

这样就实现了跨机器的通信。从容器角度看,所有容器都在同一个大网里,互相能直接访问。从物理网络角度看,真正在线上传输的是机器之间的数据包,容器通信的数据被包在里头。

这就是网络插件的原理。它做两件事:第一,统一分配 IP,让所有容器的 IP 在同一个大网里不冲突;第二,做数据包封装,让容器之间的通信能跨过物理机器的边界。没有它,跨机器的容器就是孤岛,谁也找不到谁。

每个安装包是干什么的

整个安装过程中需要用到四个包,每个包的用处不一样,下面一个一个说清楚。


harbor-offline-installer-v2.5.4.tgz

这个包是 Harbor 镜像仓库的离线安装包。

Harbor 本身不是单个程序,它是由九个容器服务组成的。这九个容器分别负责不同的事情,有负责提供网页界面的,有负责存储镜像文件的,有负责保存用户信息和权限数据的,有负责做缓存加速的,有负责做反向代理的,还有负责扫描漏洞的。所有这些容器加起来才组成一个完整的 Harbor。

离线包的意思就是这个包里已经包含了这九个容器需要的所有镜像文件,你不用再去外网下载任何东西。解压之后,里面有一个配置文件模板和一个安装脚本。你先把配置文件里的域名、证书路径、管理员密码改成你自己的,然后执行安装脚本,脚本就会根据配置文件启动所有容器,Harbor 就运行起来了。

这个包只在 Harbor 这台机器上用,其他机器不需要。


cri-dockerd-0.3.25.amd64.tgz

这个包里只有一个东西,就是 cri-dockerd 这个二进制程序文件。

它的作用是做翻译。Kubernetes 发指令的时候用的是 CRI 标准接口格式,Docker 听不懂这种格式。cri-dockerd 负责把 Kubernetes 的指令翻译成 Docker 能听懂的话,然后转给 Docker 去执行。

每台机器都要装这个。不管是主控节点还是工作节点,只要这台机器上要跑容器,就必须有 cri-dockerd。没有它,Kubernetes 跟 Docker 之间对不上话,容器起不来。

装完之后它是以系统服务的形式在后台运行的,机器启动后它会自动起来,一直等着接收 Kubernetes 的指令。


kube-flannel-v0.28.9.tar.gz

这个包是 Flannel 网络插件的镜像文件包。

Flannel 解决的是跨机器通信的问题。没有它,不同机器上的容器互相访问不了。它的作用是给整个集群规划一个大网段,然后给每台机器分配一段子网,所有容器的 IP 都从各自机器的子网里取,这样所有容器的 IP 都在同一个大网里不冲突。

同时它还在每台机器上跑一个代理程序,这个代理知道整个集群的 IP 分配情况。当一台机器上的容器要访问另一台机器上的容器时,代理负责把数据包封装好,通过物理网络发到目标机器,目标机器的代理再拆开交给对应的容器。

这个包里的镜像就是用来启动这个代理程序的。拿到包之后要先解压,然后用 docker load 命令把镜像装进 Docker,再推送到 Harbor 仓库里。后面部署 Flannel 的时候,集群会自动从 Harbor 拉取这个镜像来启动代理。

这个包只在主控节点上用。主控节点把镜像推到 Harbor 之后,工作节点需要的时候直接从 Harbor 拉就行了。


kubeadm、kubelet、kubectl

这三个不是单独的文件包,是通过软件源在线安装的。

kubeadm 是集群初始化工具,只在搭集群的时候用一次。它负责生成所有证书、创建配置文件、启动主控组件。主控节点上执行完 kubeadm init 之后,它的任务就完成了,后面不再需要它。工作节点上执行 kubeadm join 也只是用一下它的加入功能。

kubelet 是每台机器上长期在后台运行的服务。它负责接收主控节点派下来的任务,然后调用 cri-dockerd 去拉镜像、启动容器。它还不停地向主控节点汇报这台机器上所有容器的运行状态。只要机器还在集群里,kubelet 就必须一直运行。它要设成开机自启,机器重启后自动恢复。

kubectl 是操作集群的命令行工具。你想查看集群里有哪些节点、哪些容器在跑、某个容器的日志是什么、要部署一个新程序、要更新一个程序,所有这些操作都是通过 kubectl 命令来完成的。一般只在主控节点上装,因为你操作集群的时候是在主控节点上敲命令。工作节点不需要装,它们只管执行任务。

主控节点上这三个都要装。工作节点上只装 kubeadm 和 kubelet,kubectl 不需要。


总结:四个东西各自用在哪

Harbor 离线包用在 Harbor 机器上。

cri-dockerd 包用在每一台机器上,包括 Harbor 机器、主控节点、所有工作节点,每台都必须装,装完作为系统服务一直运行。

Flannel 包用在主控节点上,把镜像推到 Harbor 之后,部署 Flannel 的时候集群会用这个镜像启动网络组件。

kubeadm、kubelet、kubectl 这三个通过软件源安装,主控节点装全部三个,工作节点只装前两个,装完 kubelet 作为系统服务一直运行。

先后逻辑

第一步装 Docker 搭 Harbor,解决镜像来源问题。

第二步把基础镜像塞到 Harbor 里,让 Harbor 有内容。

第三步所有节点配 Harbor 访问环境,让每台机器都能连 Harbor。

第四步所有节点装 cri-dockerd,让每台机器都能被 Kubernetes 指挥。

第五步主控节点初始化,让大脑启动,然后装 Flannel 让网络通。

第六步工作节点加入,让手脚就位。

前三步本质上都是在为第四步和第五步做准备。镜像来源有了,Harbor 能访问了,翻译官装好了,主控节点的初始化才能成功。主控节点成功后,装 Flannel 打通网络,最后工作节点才能顺利加入。

每一步都在解决一个特定的障碍,障碍扫清了,后面的步骤才能走通。

在机器上的执行顺序

Harbor 机器:第一步、第二步的一部分。先把 Harbor 搭起来,然后从外网拉基础镜像推到 Harbor。

所有节点:第三步、第四步。配 Harbor 访问环境,装 cri-dockerd。

主控节点:第五步。初始化集群,装 Flannel。

工作节点:第六步。加入集群。

整个思路的核心

整个安装过程,本质上是在解决一个连锁依赖:容器需要镜像,镜像来自 Harbor,Harbor 本身跑在容器里。这个循环必须找一个突破口,这个突破口就是先在 Harbor 机器上装 Docker,然后用离线包把 Harbor 跑起来。Harbor 跑起来之后,循环就解开了,后面的步骤就顺了。

每一步都在为后面的步骤扫清障碍,扫清障碍的方法就是先把当前需要的条件准备好。条件满足了,后面的命令才能执行成功。这就是整个安装过程的思路。


第二部分:环境规划与前置准备

一、环境规划

主机名IP 地址角色
AVC-harbor172.25.254.200Harbor 镜像仓库
AVC-master172.25.254.100Kubernetes 控制平面
AVC-node1172.25.254.10Kubernetes 工作节点
AVC-node2172.25.254.20Kubernetes 工作节点

默认配置:

  • 域名:reg.avc.harbor.org
  • 管理员密码:avc
  • K8s 版本:v1.35.7
  • Flannel 版本:v0.28.9
  • Pod 网段:10.244.0.0/16

二、前置准备(所有主机)

以下操作在 AVC-harborAVC-masterAVC-node1AVC-node2 上分别执行。

2.1 系统环境调整
[root@AVC-harbor ~]# swapoff -a
[root@AVC-harbor ~]# sed -i '/swap/s/^/#/g' /etc/fstab
[root@AVC-harbor ~]# systemctl mask swap.target
Created symlink /etc/systemd/system/swap.target → /dev/null.

[root@AVC-harbor ~]# systemctl stop firewalld
[root@AVC-harbor ~]# systemctl disable firewalld
Removed /etc/systemd/system/multi-user.target.wants/firewalld.service.

[root@AVC-harbor ~]# setenforce 0
[root@AVC-harbor ~]# sed -i 's/^SELINUX=.*/SELINUX=permissive/' /etc/selinux/config

验证(每台主机):

[root@AVC-harbor ~]# swapon --show
# 无任何输出,表示 swap 已禁用

[root@AVC-harbor ~]# getenforce
Permissive

[root@AVC-harbor ~]# systemctl status firewalld | grep -q "inactive" && echo "firewalld 已禁用"
firewalld 已禁用

第三部分:手动部署步骤

三、部署 Harbor(仅在 AVC-harbor 执行)

3.1 安装 Docker
[root@AVC-harbor ~]# cat > /etc/yum.repos.d/docker.repo <<EOF
[docker]
name=docker
baseurl=https://mirrors.aliyun.com/docker-ce/linux/rhel/9.6/x86_64/stable/
gpgcheck=0
EOF

[root@AVC-harbor ~]# dnf install -y docker-ce
...(依赖安装过程省略)...
Installed:
  docker-ce-3:26.1.3-1.el9.x86_64

[root@AVC-harbor ~]# systemctl enable --now docker
Created symlink /etc/systemd/system/multi-user.target.wants/docker.service → /usr/lib/systemd/system/docker.service.

验证:

[root@AVC-harbor ~]# systemctl status docker | grep "active (running)"
   Active: active (running) since ... 
3.2 配置内核参数
[root@AVC-harbor ~]# echo br_netfilter > /etc/modules-load.d/docker_mod.conf
[root@AVC-harbor ~]# modprobe br_netfilter

[root@AVC-harbor ~]# cat > /etc/sysctl.d/docker.conf <<EOF
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
net.ipv4.ip_forward=1
EOF

[root@AVC-harbor ~]# sysctl --system
* Applying /etc/sysctl.d/docker.conf ...
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1

验证:

[root@AVC-harbor ~]# sysctl net.bridge.bridge-nf-call-iptables | grep "= 1"
net.bridge.bridge-nf-call-iptables = 1
3.3 生成 SSL 证书
[root@AVC-harbor ~]# mkdir -p /data/certs
[root@AVC-harbor ~]# DOMAIN=reg.avc.harbor.org

[root@AVC-harbor ~]# openssl req -newkey rsa:4096 -nodes -sha256 \
>   -keyout /data/certs/${DOMAIN}.key \
>   -addext "subjectAltName = DNS:${DOMAIN}" \
>   -x509 -days 365 -out /data/certs/${DOMAIN}.crt \
>   -subj "/C=CN/ST=Shannxi/L=Xi'an/O=kubernetes/OU=harbor/CN=${DOMAIN}/emailAddress=admin@${DOMAIN}"
Generating a RSA private key
......+
writing new private key to '/data/certs/reg.avc.harbor.org.key'
-----

验证:

[root@AVC-harbor ~]# ls -l /data/certs/reg.avc.harbor.org.crt
-rw-r--r-- 1 root root 2050 ... /data/certs/reg.avc.harbor.org.crt
3.4 部署 Harbor
[root@AVC-harbor ~]# tar zxf /root/harbor-offline-installer-v2.5.4.tgz -C /opt/
[root@AVC-harbor ~]# cd /opt/harbor
[root@AVC-harbor harbor]# cp harbor.yml.tmpl harbor.yml

# 使用 sed 修改关键配置项
[root@AVC-harbor harbor]# sed -i "s/^hostname: .*/hostname: reg.avc.harbor.org/" harbor.yml
[root@AVC-harbor harbor]# sed -i "s|^  certificate: .*|  certificate: /data/certs/reg.avc.harbor.org.crt|" harbor.yml
[root@AVC-harbor harbor]# sed -i "s|^  private_key: .*|  private_key: /data/certs/reg.avc.harbor.org.key|" harbor.yml
[root@AVC-harbor harbor]# sed -i "s/^harbor_admin_password: .*/harbor_admin_password: avc/" harbor.yml

[root@AVC-harbor harbor]# ./install.sh --with-chartmuseum
...(容器拉取和启动过程省略)...
✔ ----Harbor has been installed and started successfully.----

验证:

[root@AVC-harbor harbor]# docker ps --filter "name=harbor" --format "{{.Names}}" | grep harbor-core
harbor-core
3.5 设置 Harbor 开机自启
[root@AVC-harbor harbor]# cat > /lib/systemd/system/harbor.service <<'EOF'
[Unit]
Description=harbor with Docker Compose
After=docker.service network-online.target
Requires=docker.service
[Service]
Type=oneshot
WorkingDirectory=/opt/harbor
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF

[root@AVC-harbor harbor]# systemctl daemon-reload
[root@AVC-harbor harbor]# systemctl enable --now harbor
Created symlink /etc/systemd/system/multi-user.target.wants/harbor.service → /lib/systemd/system/harbor.service.

验证:

[root@AVC-harbor harbor]# systemctl is-enabled harbor
enabled

在这里插入图片描述

3.6 配置本机 Docker 信任并登录
[root@AVC-harbor harbor]# mkdir -p /etc/docker/certs.d/reg.avc.harbor.org
[root@AVC-harbor harbor]# cp /data/certs/reg.avc.harbor.org.crt /etc/docker/certs.d/reg.avc.harbor.org/ca.crt
[root@AVC-harbor harbor]# echo "172.25.254.200   reg.avc.harbor.org" >> /etc/hosts
[root@AVC-harbor harbor]# systemctl restart docker

[root@AVC-harbor harbor]# sleep 30
[root@AVC-harbor harbor]# docker login reg.avc.harbor.org -u admin -p avc
Login Succeeded

在这里插入图片描述

3.7 创建 Harbor 项目(Web 界面)

浏览器访问 https://reg.avc.harbor.org,登录 admin/avc,创建两个公开项目:

  • k8s
  • flannel-io

验证(命令行):

[root@AVC-harbor harbor]# curl -k -s -u admin:avc https://reg.avc.harbor.org/api/v2.0/projects | grep -q '"name":"k8s"' && echo "k8s OK"
k8s OK
[root@AVC-harbor harbor]# curl -k -s -u admin:avc https://reg.avc.harbor.org/api/v2.0/projects | grep -q '"name":"flannel-io"' && echo "flannel-io OK"
flannel-io OK

四、配置集群节点(AVC-master, AVC-node1, AVC-node2)

以下命令在三台主机上分别执行。

4.1 安装 Docker
[root@AVC-master ~]# cat > /etc/yum.repos.d/docker.repo <<EOF
[docker]
name=docker
baseurl=https://mirrors.aliyun.com/docker-ce/linux/rhel/9.6/x86_64/stable/
gpgcheck=0
EOF

[root@AVC-master ~]# dnf install -y docker-ce
...(安装过程省略)...
Installed: docker-ce-3:26.1.3-1.el9.x86_64

[root@AVC-master ~]# systemctl enable --now docker
Created symlink /etc/systemd/system/multi-user.target.wants/docker.service → /usr/lib/systemd/system/docker.service.

验证:

[root@AVC-master ~]# systemctl status docker | grep "active (running)"
   Active: active (running) since ...
4.2 配置 Harbor 证书和 hosts
[root@AVC-master ~]# DOMAIN=reg.avc.harbor.org
[root@AVC-master ~]# HARBOR_IP=172.25.254.200

[root@AVC-master ~]# mkdir -p /etc/docker/certs.d/${DOMAIN}
[root@AVC-master ~]# scp root@${HARBOR_IP}:/data/certs/${DOMAIN}.crt /etc/docker/certs.d/${DOMAIN}/ca.crt
root@172.25.254.200's password: 
ca.crt                                   100% 2050     2.0KB/s   00:00

[root@AVC-master ~]# cat >> /etc/hosts <<EOF
172.25.254.200   reg.avc.harbor.org
172.25.254.100   AVC-master
172.25.254.10    AVC-node1
172.25.254.20    AVC-node2
EOF

验证:

[root@AVC-master ~]# ls -l /etc/docker/certs.d/reg.avc.harbor.org/ca.crt
-rw-r--r-- 1 root root 2050 ... /etc/docker/certs.d/reg.avc.harbor.org/ca.crt

[root@AVC-master ~]# ping -c 1 reg.avc.harbor.org
PING reg.avc.harbor.org (172.25.254.200) ... 64 bytes from ... time=0.123 ms
4.3 配置镜像加速器(指向 Harbor)
[root@AVC-master ~]# cat > /etc/docker/daemon.json <<EOF
{
  "registry-mirrors": ["https://reg.avc.harbor.org"]
}
EOF

[root@AVC-master ~]# systemctl restart docker

验证:

[root@AVC-master ~]# docker info | grep -q "reg.avc.harbor.org" && echo "OK"
OK
4.4 配置 Kubernetes yum 源
[root@AVC-master ~]# cat > /etc/yum.repos.d/kubernetes.repo <<EOF
[kubernetes]
name=kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.35/rpm/
gpgcheck=0
EOF

[root@AVC-master ~]# dnf list kubelet
...(列出 kubelet 包信息)...
4.5 安装 cri-dockerd
[root@AVC-master ~]# cd /root
[root@AVC-master ~]# wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.25/cri-dockerd-0.3.25.amd64.tgz
...(下载完成)...

[root@AVC-master ~]# tar zxf cri-dockerd-0.3.25.amd64.tgz
[root@AVC-master ~]# cd cri-dockerd
[root@AVC-master cri-dockerd]# install -o root -g root -m 0755 cri-dockerd /usr/local/bin/cri-dockerd

[root@AVC-master cri-dockerd]# cat > /lib/systemd/system/cri-docker.service <<EOF
[Unit]
Description=CRI Interface for Docker Application Container Engine
Documentation=https://docs.mirantis.com
After=network-online.target docker.service
Wants=docker.service
Requires=cri-docker.socket

[Service]
Type=notify
ExecStart=/usr/local/bin/cri-dockerd --network-plugin=cni --pod-infra-container-image=reg.avc.harbor.org/k8s/pause:3.10.1 --container-runtime-endpoint fd://
ExecReload=/bin/kill -s HUP \$MAINPID
TimeoutSec=0
RestartSec=2
Restart=always

[Install]
WantedBy=multi-user.target
EOF

[root@AVC-master cri-dockerd]# cat > /lib/systemd/system/cri-docker.socket <<EOF
[Unit]
Description=CRI Docker Socket for the API
PartOf=cri-docker.service

[Socket]
ListenStream=/var/run/cri-dockerd.sock
SocketMode=0660
SocketUser=root
SocketGroup=root

[Install]
WantedBy=sockets.target
EOF

[root@AVC-master cri-dockerd]# systemctl daemon-reload
[root@AVC-master cri-dockerd]# systemctl enable --now cri-docker.service
Created symlink /etc/systemd/system/multi-user.target.wants/cri-docker.service → /lib/systemd/system/cri-docker.service.

验证:

[root@AVC-master cri-dockerd]# systemctl status cri-docker | grep "active (running)"
   Active: active (running) since ...
[root@AVC-master cri-dockerd]# ls -l /var/run/cri-dockerd.sock
srw-rw---- 1 root root 0 ... /var/run/cri-dockerd.sock

五、Master 节点初始化(仅在 AVC-master 执行)

5.1 安装 Kubernetes 组件
[root@AVC-master ~]# dnf install -y kubelet kubeadm kubectl
...(安装过程省略)...

[root@AVC-master ~]# systemctl enable --now kubelet
Created symlink /etc/systemd/system/multi-user.target.wants/kubelet.service → /usr/lib/systemd/system/kubelet.service.

[root@AVC-master ~]# echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bashrc
[root@AVC-master ~]# export KUBECONFIG=/etc/kubernetes/admin.conf

验证:

[root@AVC-master ~]# kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"35", GitVersion:"v1.35.7", ...}
[root@AVC-master ~]# echo $KUBECONFIG
/etc/kubernetes/admin.conf
5.2 推送基础镜像到 Harbor
[root@AVC-master ~]# K8S_VERSION=v1.35.7

[root@AVC-master ~]# docker login reg.avc.harbor.org -u admin -p avc
Login Succeeded

# 拉取 K8s 基础镜像(从阿里云)
[root@AVC-master ~]# kubeadm config images pull --image-repository registry.aliyuncs.com/google_containers --kubernetes-version ${K8S_VERSION} --cri-socket=unix:///var/run/cri-dockerd.sock
[config/images] Pulled registry.aliyuncs.com/google_containers/kube-apiserver:v1.35.7
[config/images] Pulled registry.aliyuncs.com/google_containers/kube-controller-manager:v1.35.7
[config/images] Pulled registry.aliyuncs.com/google_containers/kube-scheduler:v1.35.7
[config/images] Pulled registry.aliyuncs.com/google_containers/kube-proxy:v1.35.7
[config/images] Pulled registry.aliyuncs.com/google_containers/pause:3.10.1
[config/images] Pulled registry.aliyuncs.com/google_containers/etcd:3.5.18-0
[config/images] Pulled registry.aliyuncs.com/google_containers/coredns:v1.11.3

# 批量打标签并推送至 Harbor 项目 k8s
[root@AVC-master ~]# docker images --format "{{.Repository}}:{{.Tag}}" | awk -F "/" '/google/{system("docker tag "$0" reg.avc.harbor.org/k8s/"$3)}'
[root@AVC-master ~]# docker images --format "{{.Repository}}:{{.Tag}}" | awk -F "/" '/reg.avc.harbor.org/{system("docker push "$0)}'
...(推送进度条省略)...

验证(应输出 7 个镜像):

[root@AVC-master ~]# docker images | grep reg.avc.harbor.org/k8s | wc -l
7
5.3 初始化集群
[root@AVC-master ~]# kubeadm init --pod-network-cidr=10.244.0.0/16 \
>   --image-repository reg.avc.harbor.org/k8s \
>   --kubernetes-version ${K8S_VERSION} \
>   --cri-socket=unix:///var/run/cri-dockerd.sock \
>   --ignore-preflight-errors=CRI
[init] Using Kubernetes version: v1.35.7
[preflight] Running pre-flight checks
...(中间过程省略)...
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has been initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 172.25.254.100:6443 --token abcdef.0123456789abcdef \
        --discovery-token-ca-cert-hash sha256:1234567890abcdef... 

⚠️ 请务必保存上面输出的 kubeadm join 命令,稍后用于 Worker 节点加入。

验证:

[root@AVC-master ~]# kubectl get nodes | grep -q control-plane && echo "OK"
OK
5.4 安装 Flannel(使用离线包)
[root@AVC-master ~]# FLANNEL_FILE="/root/kube-flannel-v0.28.9.gz"

[root@AVC-master ~]# if tar zxf $FLANNEL_FILE -C /root/ 2>/dev/null; then
>     echo "tar 解压成功"
> elif gunzip -k $FLANNEL_FILE 2>/dev/null; then
>     echo "gunzip 解压成功"
> else
>     echo "解压失败!"
>     exit 1
> fi
tar 解压成功

[root@AVC-master ~]# cd /root/flannel

# 加载本地镜像
[root@AVC-master flannel]# docker load -i *.tar
Loaded image: ghcr.io/flannel-io/flannel-cni-plugin:v1.9.1-flannel3
Loaded image: ghcr.io/flannel-io/flannel:v0.28.9

# 打标签并推送至 Harbor
[root@AVC-master flannel]# docker tag ghcr.io/flannel-io/flannel-cni-plugin:v1.9.1-flannel3 reg.avc.harbor.org/flannel-io/flannel-cni-plugin:v1.9.1-flannel3
[root@AVC-master flannel]# docker tag ghcr.io/flannel-io/flannel:v0.28.9 reg.avc.harbor.org/flannel-io/flannel:v0.28.9

[root@AVC-master flannel]# docker push reg.avc.harbor.org/flannel-io/flannel-cni-plugin:v1.9.1-flannel3
...(推送完成)...
[root@AVC-master flannel]# docker push reg.avc.harbor.org/flannel-io/flannel:v0.28.9
...(推送完成)...

# 替换 YAML 中镜像地址并应用
[root@AVC-master flannel]# sed -i 's|ghcr.io/flannel-io/|reg.avc.harbor.org/flannel-io/|g' kube-flannel.yml
[root@AVC-master flannel]# kubectl apply -f kube-flannel.yml
namespace/kube-flannel created
clusterrole.rbac.authorization.k8s.io/flannel created
...
daemonset.apps/kube-flannel-ds created

验证:

[root@AVC-master flannel]# kubectl get pods -n kube-flannel
NAME                    READY   STATUS    RESTARTS   AGE
kube-flannel-ds-xxxxx   1/1     Running   0          30s
5.5 持久化内核模块及 sysctl
[root@AVC-master ~]# modprobe br_netfilter
[root@AVC-master ~]# echo "br_netfilter" > /etc/modules-load.d/k8s.conf

[root@AVC-master ~]# cat > /etc/sysctl.d/k8s.conf <<EOF
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
net.ipv4.ip_forward=1
EOF

[root@AVC-master ~]# sysctl --system
...(应用成功)...

验证:

[root@AVC-master ~]# lsmod | grep br_netfilter
br_netfilter           32768  0
[root@AVC-master ~]# sysctl net.bridge.bridge-nf-call-iptables
net.bridge.bridge-nf-call-iptables = 1

六、Worker 节点加入集群(AVC-node1, AVC-node2)

6.1 安装 kubeadm 和 kubelet
[root@AVC-node1 ~]# dnf install -y kubeadm kubelet
...(安装过程省略)...

[root@AVC-node1 ~]# systemctl enable --now kubelet
Created symlink /etc/systemd/system/multi-user.target.wants/kubelet.service → /usr/lib/systemd/system/kubelet.service.

验证:

[root@AVC-node1 ~]# kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"35", GitVersion:"v1.35.7", ...}
6.2 执行 join 命令

使用 Master 初始化时保存的 join 命令,并追加 --cri-socket=unix:///var/run/cri-dockerd.sock

[root@AVC-node1 ~]# kubeadm join 172.25.254.100:6443 --token abcdef.0123456789abcdef \
>   --discovery-token-ca-cert-hash sha256:1234567890abcdef... \
>   --cri-socket=unix:///var/run/cri-dockerd.sock
[preflight] Running pre-flight checks
...(中间过程省略)...
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

AVC-node2 上重复相同操作。

6.3 在 Master 上验证节点状态
[root@AVC-master ~]# kubectl get nodes
NAME          STATUS   ROLES           AGE     VERSION
AVC-master    Ready    control-plane   5m      v1.35.7
AVC-node1     Ready    <none>          1m      v1.35.7
AVC-node2     Ready    <none>          1m      v1.35.7

等待约 1-2 分钟,所有节点状态应为 Ready


七、最终验证(在 AVC-master 执行)

[root@AVC-master ~]# export KUBECONFIG=/etc/kubernetes/admin.conf

[root@AVC-master ~]# kubectl get nodes -o wide
NAME          STATUS   ROLES           AGE   VERSION   INTERNAL-IP       EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION          CONTAINER-RUNTIME
AVC-master    Ready    control-plane   8m    v1.35.7   172.25.254.100    <none>        Rocky Linux 9.6         5.14.0-427.el9.x86_64   docker://26.1.3
AVC-node1     Ready    <none>          4m    v1.35.7   172.25.254.10     <none>        Rocky Linux 9.6         5.14.0-427.el9.x86_64   docker://26.1.3
AVC-node2     Ready    <none>          4m    v1.35.7   172.25.254.20     <none>        Rocky Linux 9.6         5.14.0-427.el9.x86_64   docker://26.1.3

[root@AVC-master ~]# kubectl get pods -A
NAMESPACE      NAME                                      READY   STATUS    RESTARTS   AGE
kube-flannel   kube-flannel-ds-abcde                     1/1     Running   0          6m
kube-flannel   kube-flannel-ds-fghij                     1/1     Running   0          3m
kube-flannel   kube-flannel-ds-klmno                     1/1     Running   0          3m
kube-system    coredns-xxxxxxxxx-xxxxx                   1/1     Running   0          8m
kube-system    coredns-xxxxxxxxx-xxxxx                   1/1     Running   0          8m
kube-system    etcd-AVC-master                           1/1     Running   0          8m
kube-system    kube-apiserver-AVC-master                 1/1     Running   0          8m
kube-system    kube-controller-manager-AVC-master        1/1     Running   0          8m
kube-system    kube-proxy-xxxxx                          1/1     Running   0          8m
kube-system    kube-proxy-yyyyy                          1/1     Running   0          4m
kube-system    kube-proxy-zzzzz                          1/1     Running   0          4m
kube-system    kube-scheduler-AVC-master                 1/1     Running   0          8m

[root@AVC-master ~]# docker login reg.avc.harbor.org -u admin -p avc
Login Succeeded

在这里插入图片描述


第四部分:持久化验证与故障排查

八、持久化验证脚本(重启后检查)

将以下脚本保存为 /root/check_persistence.sh,重启后执行。

[root@AVC-master ~]# cat > /root/check_persistence.sh <<'EOF'
#!/bin/bash
# ============================================================
# 持久化验证脚本(重启后执行)
# 输出每项 OK/FAIL,最后返回 1(全部正常)或 0(有问题)
# ============================================================

RED='\033[31m'; GREEN='\033[32m'; NC='\033[0m'
ALL_OK=1

echo "========== 持久化验证 =========="

echo -n "swap 禁用: "
swapon --show | grep -q . && echo -e "${RED}FAIL${NC}" || echo -e "${GREEN}OK${NC}"

for svc in docker cri-docker kubelet; do
    echo -n "服务 $svc (enabled): "
    systemctl is-enabled $svc >/dev/null 2>&1 && echo -e "${GREEN}OK${NC}" || { echo -e "${RED}FAIL${NC}"; ALL_OK=0; }
done

echo -n "br_netfilter 模块: "
lsmod | grep -q br_netfilter && echo -e "${GREEN}OK${NC}" || { echo -e "${RED}FAIL${NC}"; ALL_OK=0; }

echo -n "sysctl bridge-nf-call-iptables: "
sysctl net.bridge.bridge-nf-call-iptables 2>/dev/null | grep -q "= 1" && echo -e "${GREEN}OK${NC}" || { echo -e "${RED}FAIL${NC}"; ALL_OK=0; }

echo -n "Harbor 证书: "
[ -f /etc/docker/certs.d/reg.avc.harbor.org/ca.crt ] && echo -e "${GREEN}OK${NC}" || { echo -e "${RED}FAIL${NC}"; ALL_OK=0; }

echo -n "kubectl 连接: "
kubectl cluster-info >/dev/null 2>&1 && echo -e "${GREEN}OK${NC}" || { echo -e "${RED}FAIL${NC}"; ALL_OK=0; }

echo -n "所有节点 Ready: "
kubectl get nodes 2>/dev/null | grep -q "NotReady" && { echo -e "${RED}FAIL${NC}"; ALL_OK=0; } || echo -e "${GREEN}OK${NC}"

echo -n "Flannel Pod Running: "
kubectl get pods -n kube-flannel 2>/dev/null | grep -v NAME | grep -q "Running" && echo -e "${GREEN}OK${NC}" || { echo -e "${RED}FAIL${NC}"; ALL_OK=0; }

echo "=============================================="
[ $ALL_OK -eq 1 ] && echo -e "${GREEN}结果: 1(全部完好)${NC}" || echo -e "${RED}结果: 0(存在问题)${NC}"
EOF

[root@AVC-master ~]# chmod +x /root/check_persistence.sh

重启后运行:

[root@AVC-master ~]# /root/check_persistence.sh
========== 持久化验证 ==========
swap 禁用: OK
服务 docker (enabled): OK
服务 cri-docker (enabled): OK
服务 kubelet (enabled): OK
br_netfilter 模块: OK
sysctl bridge-nf-call-iptables: OK
Harbor 证书: OK
kubectl 连接: OK
所有节点 Ready: OK
Flannel Pod Running: OK
==============================================
结果: 1(全部完好)

九、常见问题速查

现象原因解决方法
dnf install 被 Killed内存不足dd if=/dev/zero of=/swapfile bs=1M count=2048 && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile
Harbor 登录 connection refusedHarbor 容器未完全启动等待 30-60 秒后重试:docker login reg.avc.harbor.org -u admin -p avc
Harbor 登录 no such host/etc/hosts 缺少 Harbor 解析echo "172.25.254.200 reg.avc.harbor.org" >> /etc/hosts
kubeadm init 端口占用残留集群数据kubeadm reset --cri-socket=unix:///var/run/cri-dockerd.sock -f && rm -rf /etc/kubernetes /var/lib/etcd /root/.kube
kubectllocalhost:8080 refused未设置 KUBECONFIGexport KUBECONFIG=/etc/kubernetes/admin.conf
Flannel Pod CrashLoopBackOffbr_netfilter 未加载modprobe br_netfilter && sysctl -w net.bridge.bridge-nf-call-iptables=1
Worker join token 无效token 过期(24h)Master 重新生成:kubeadm token create --print-join-command
Worker 节点 NotReady无法拉取 pause 镜像Worker 执行 docker login reg.avc.harbor.org -u admin -p avc,Master 执行 kubectl delete pods -n kube-flannel --all

常见问题速查表(补充)

现象原因解决方法
dnf install -y docker-ce 被 Killed内存不足dd if=/dev/zero of=/swapfile bs=1M count=2048 && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile
cri-dockerd 服务找不到二进制包未包含 service 文件手动下载 cri-docker.service 和 cri-docker.socket 到 /lib/systemd/system/
Harbor 登录报 connection refusedHarbor 容器未完全启动等待 30 秒后重试 docker login reg.avc.harbor.org -u admin -p avc
Harbor 登录报 no such host/etc/hosts 缺少 Harbor 解析echo "172.25.254.200 reg.avc.harbor.org" >> /etc/hosts
Harbor 离线包解压报 unexpected end of file下载不完整删除后重新下载:rm -f /root/harbor-offline-installer-*.tgz
kubeadm init 报端口占用残留集群数据kubeadm reset --cri-socket=unix:///var/run/cri-dockerd.sock -f && rm -rf /etc/kubernetes /var/lib/etcd /root/.kube
kubeadm init 报 swap 警告swap 未禁用swapoff -a && sed -i '/swap/s/^/#/g' /etc/fstab
kubectllocalhost:8080 refused未设置 KUBECONFIGexport KUBECONFIG=/etc/kubernetes/admin.conf
Flannel Pod CrashLoopBackOffbr_netfilter 未加载modprobe br_netfilter && sysctl -w net.bridge.bridge-nf-call-iptables=1
Flannel 镜像推送 401Harbor 登录过期或项目不存在docker login reg.avc.harbor.org -u admin -p avc,确认 flannel-io 项目已创建且为公开
Worker join 报 kubeadm: command not foundWorker 未安装 kubeadmdnf install -y kubeadm kubelet
Worker join 报 token 无效token 过期(24h)Master 重新生成:kubeadm token create --print-join-command
Worker 节点 NotReady无法拉取 pause 镜像Worker 执行 docker login reg.avc.harbor.org -u admin -p avc,然后 kubectl delete pods -n kube-flannel --all
docker pullnot foundHarbor 中镜像不存在Master 执行 docker push reg.avc.harbor.org/k8s/pause:3.10.1
重启后节点丢失kubelet 未开机自启systemctl enable kubelet --now
Harbor 服务重启后未启动harbor.service 未 enablesystemctl enable harbor --now
check_persistence.sh 返回 0某项配置丢失查看输出中 FAIL 项,针对性修复

附录:最终版脚本集

前置准备:所需包清单与安装位置

在执行任何脚本之前,请确保以下包已准备好:

包名放置位置下载地址说明
harbor-offline-installer-*.tgzharbor 主机 /roothttps://github.com/goharbor/harbor/releasesHarbor 离线安装包(约 500MB),脚本支持 -y 自动下载
kube-flannel-v0.28.9.gzmaster 主机 /roothttps://github.com/flannel-io/flannel/releasesFlannel 网络插件离线包(约 30MB),脚本检测不到会报错并提示
cri-dockerd-*.amd64.tgz各节点 /root(可选)https://github.com/Mirantis/cri-dockerd/releasescri-dockerd 二进制包,脚本会自动下载,也可手动放置

脚本列表

脚本文件执行主机说明
00_prepare.sh所有主机前置环境检测与清理(支持 --clean
distribute.shharbor分发脚本和包到各节点
01_harbor_install.shharbor部署 Harbor(支持 -y 自动下载)
02_node_common.shmaster, node1, node2节点通用配置(Docker、cri-dockerd)
02.5_check_node.shnode1, node2(可选)Join 前前置检查
03_master_install.shmasterMaster 初始化 + Flannel 安装
04_worker_join.shnode1, node2Worker 加入集群(自动修复依赖)
05_final_check.shmaster最终集群验证
check_persistence.shmaster重启后持久化验证

🔗 脚本联动关系与执行顺序

执行顺序:
1. distribute.sh(harbor 执行)→ 分发脚本和包到各节点
   ↓
2. 00_prepare.sh --clean(所有主机执行)→ 清理旧环境
   ↓
3. 01_harbor_install.sh(harbor 执行)→ 部署 Harbor
   ↓ (完成后手动操作)
4. 浏览器访问 Harbor,创建 k8s 和 flannel-io 项目(公开)
   ↓
5. 02_node_common.sh(master, node1, node2 执行)→ 基础配置
   ↓
6. 03_master_install.sh(master 执行)→ 初始化集群 + Flannel
   ↓
7. 04_worker_join.sh(node1, node2 执行)→ 加入集群
   ↓
8. 05_final_check.sh(master 执行)→ 最终验证

1. 00_prepare.sh(所有主机执行)

#!/bin/bash
# ============================================================
# 前置环境检测与清理脚本
# 用法:./00_prepare.sh          # 只读检测
#       ./00_prepare.sh --clean  # 检测并自动清理
# ============================================================

set -e
RED='\033[31m'; GREEN='\033[32m'; YELLOW='\033[33m'; NC='\033[0m'
MODE="check"
[ "$1" == "--clean" ] && MODE="clean"

echo "=============================================="
echo "  Kubernetes 预部署环境检测与清理"
echo "  模式: $MODE"
echo "=============================================="

echo -n "swap: "
if swapon --show | grep -q .; then
    echo -e "${RED}[FAIL]${NC} 已启用"
    [ "$MODE" == "clean" ] && { swapoff -a; sed -i '/swap/s/^/#/g' /etc/fstab; systemctl mask swap.target; echo "  已关闭"; }
else
    echo -e "${GREEN}[OK]${NC} 已禁用"
fi

echo -n "firewalld: "
if systemctl is-active firewalld >/dev/null 2>&1; then
    echo -e "${RED}[FAIL]${NC} 正在运行"
    [ "$MODE" == "clean" ] && { systemctl stop firewalld; systemctl disable firewalld; echo "  已停止"; }
else
    echo -e "${GREEN}[OK]${NC} 未运行"
fi

echo -n "SELinux: "
selinux_mode=$(getenforce)
if [ "$selinux_mode" == "Enforcing" ]; then
    echo -e "${RED}[FAIL]${NC} Enforcing"
    [ "$MODE" == "clean" ] && { setenforce 0; sed -i 's/^SELINUX=.*/SELINUX=permissive/' /etc/selinux/config; echo "  已设为 permissive"; }
else
    echo -e "${GREEN}[OK]${NC} $selinux_mode"
fi

echo -n "Docker: "
if command -v docker >/dev/null 2>&1; then
    echo -e "${YELLOW}[WARN]${NC} 已安装"
    [ "$MODE" == "clean" ] && { systemctl stop docker 2>/dev/null || true; systemctl disable docker 2>/dev/null || true; dnf remove -y docker-ce docker-ce-cli containerd.io 2>/dev/null || true; rm -rf /var/lib/docker /etc/docker; echo "  已卸载"; }
else
    echo -e "${GREEN}[OK]${NC} 未安装"
fi

echo -n "cri-dockerd: "
if [ -f /usr/local/bin/cri-dockerd ]; then
    echo -e "${YELLOW}[WARN]${NC} 已安装"
    [ "$MODE" == "clean" ] && { systemctl stop cri-docker 2>/dev/null || true; systemctl disable cri-docker 2>/dev/null || true; rm -f /usr/local/bin/cri-dockerd /lib/systemd/system/cri-docker.*; rm -f /var/run/cri-dockerd.sock; systemctl daemon-reload; echo "  已清理"; }
else
    echo -e "${GREEN}[OK]${NC} 未安装"
fi

for pkg in kubeadm kubelet kubectl; do
    echo -n "$pkg: "
    if rpm -q $pkg >/dev/null 2>&1; then
        echo -e "${YELLOW}[WARN]${NC} 已安装"
        [ "$MODE" == "clean" ] && { dnf remove -y $pkg 2>/dev/null || true; echo "  已卸载"; }
    else
        echo -e "${GREEN}[OK]${NC} 未安装"
    fi
done

for dir in /etc/kubernetes /var/lib/etcd /root/.kube /etc/cni/net.d /var/lib/kubelet; do
    echo -n "$dir: "
    if [ -e "$dir" ]; then
        echo -e "${YELLOW}[WARN]${NC} 存在"
        [ "$MODE" == "clean" ] && { rm -rf "$dir"; echo "  已删除"; }
    else
        echo -e "${GREEN}[OK]${NC} 不存在"
    fi
done

for repo in /etc/yum.repos.d/docker.repo /etc/yum.repos.d/kubernetes.repo; do
    echo -n "$repo: "
    if [ -f "$repo" ]; then
        echo -e "${YELLOW}[WARN]${NC} 存在"
        [ "$MODE" == "clean" ] && { rm -f "$repo"; echo "  已删除"; }
    else
        echo -e "${GREEN}[OK]${NC} 不存在"
    fi
done

echo "=============================================="
[ "$MODE" == "clean" ] && echo "清理完成" || echo "检测完成,如需清理请加 --clean"
echo "=============================================="

2. distribute.sh(harbor 主机执行)

#!/bin/bash
# ============================================================
# 文件分发脚本(在 harbor 主机执行)
# 用法:./distribute.sh
# ============================================================

echo "=============================================="
echo "  文件分发脚本"
echo "=============================================="

NODES="master node1 node2"
PATTERNS="*.sh harbor-offline-*.tgz kube-flannel-*.gz kube-flannel-*.tar.gz cri-dockerd-*.tgz"

FILES=""
for pattern in $PATTERNS; do
    for file in $(ls -1 $pattern 2>/dev/null); do
        FILES="$FILES $file"
    done
done

if [ -z "$FILES" ]; then
    echo "错误:当前目录下没有找到需要分发的文件。"
    echo "请确保以下文件存在:"
    echo "  - *.sh(所有脚本)"
    echo "  - harbor-offline-*.tgz"
    echo "  - kube-flannel-*.gz 或 *.tar.gz"
    exit 1
fi

echo "将分发以下文件:"
for file in $FILES; do
    echo "  - $file"
done
echo ""

for node in $NODES; do
    echo "正在分发到 $node ..."
    for file in $FILES; do
        scp $file root@$node:/root/
        if [ $? -eq 0 ]; then
            echo "  [OK] $file -> $node:/root/"
        else
            echo "  [FAIL] $file -> $node:/root/"
        fi
    done
    echo ""
done

echo "=============================================="
echo "分发完成!"
echo "=============================================="

3. 01_harbor_install.sh(harbor 主机执行)

#!/bin/bash
# ============================================================
# Harbor 部署脚本
# 依赖包:harbor-offline-installer-*.tgz(/root 目录)
# 下载地址:https://github.com/goharbor/harbor/releases
# 用法:
#   ./01_harbor_install.sh     # 检测本地包,有则用,无则提示退出
#   ./01_harbor_install.sh -y  # 检测本地包,无则自动下载
# ============================================================

set -e
AUTO_DOWNLOAD=false
[ "$1" = "-y" ] && AUTO_DOWNLOAD=true

DOMAIN="${DOMAIN:-reg.avc.harbor.org}"
PASS="${PASS:-avc}"
HARBOR_IP="${HARBOR_IP:-172.25.254.200}"
DOWNLOAD_URL="https://github.com/goharbor/harbor/releases/download/v2.5.4/harbor-offline-installer-v2.5.4.tgz"
HARBOR_EXPECTED_SIZE=400000000

echo "=============================================="
echo "  Harbor 镜像仓库部署脚本"
echo "  域名: $DOMAIN"
echo "  管理员密码: $PASS"
echo "  Harbor IP: $HARBOR_IP"
echo "=============================================="

# ---------- 1. 检查/下载离线包 ----------
echo "检查 Harbor 离线包..."
HARBOR_TAR=""
HARBOR_TAR=$(ls -1 /root/harbor-offline-installer-*.tgz 2>/dev/null | head -1)

if [ -n "$HARBOR_TAR" ]; then
    FILE_SIZE=$(stat -c%s "$HARBOR_TAR" 2>/dev/null || stat -f%z "$HARBOR_TAR" 2>/dev/null || echo 0)
    if [ "$FILE_SIZE" -lt "$HARBOR_EXPECTED_SIZE" ]; then
        echo -e "\033[33m[WARN] 文件不完整,正在删除...\033[0m"
        rm -f "$HARBOR_TAR"
        HARBOR_TAR=""
    else
        echo -e "\033[32m[OK] 找到本地离线包: $HARBOR_TAR\033[0m"
    fi
fi

if [ -z "$HARBOR_TAR" ]; then
    echo -e "\033[33m未找到完整的 Harbor 离线包\033[0m"
    echo "预期文件名: harbor-offline-installer-*.tgz(约 500MB)"
    echo ""
    echo "下载地址: $DOWNLOAD_URL"
    echo ""
    if [ "$AUTO_DOWNLOAD" = true ]; then
        echo "检测到 -y 参数,正在下载..."
        if wget -O /root/harbor-offline-installer-v2.5.4.tgz "$DOWNLOAD_URL" --timeout=120 --tries=3 --progress=bar:force; then
            HARBOR_TAR="/root/harbor-offline-installer-v2.5.4.tgz"
            FILE_SIZE=$(stat -c%s "$HARBOR_TAR" 2>/dev/null || echo 0)
            if [ "$FILE_SIZE" -lt "$HARBOR_EXPECTED_SIZE" ]; then
                echo -e "\033[31m[ERROR] 下载的文件不完整!\033[0m"
                rm -f "$HARBOR_TAR"
                exit 1
            fi
            echo -e "\033[32m[OK] 下载成功\033[0m"
        else
            echo -e "\033[31m[ERROR] 下载失败!\033[0m"
            echo "请手动下载后重新运行: $DOWNLOAD_URL"
            exit 1
        fi
    else
        echo "如需自动下载,请使用: ./01_harbor_install.sh -y"
        echo "或手动下载后重新运行。"
        exit 0
    fi
fi

# ---------- 2. 安装 Docker ----------
echo "安装 Docker..."
cat > /etc/yum.repos.d/docker.repo <<EOF
[docker]
name=docker
baseurl=https://mirrors.aliyun.com/docker-ce/linux/rhel/9.6/x86_64/stable/
gpgcheck=0
EOF
dnf install -y docker-ce
systemctl enable --now docker

# ---------- 3. 配置内核参数 ----------
echo "配置内核参数..."
echo br_netfilter > /etc/modules-load.d/docker_mod.conf
modprobe br_netfilter
cat > /etc/sysctl.d/docker.conf <<EOF
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
net.ipv4.ip_forward=1
EOF
sysctl --system

# ---------- 4. 生成 SSL 证书 ----------
echo "生成 SSL 证书..."
mkdir -p /data/certs
openssl req -newkey rsa:4096 -nodes -sha256 \
  -keyout /data/certs/${DOMAIN}.key \
  -addext "subjectAltName = DNS:${DOMAIN}" \
  -x509 -days 365 -out /data/certs/${DOMAIN}.crt \
  -subj "/C=CN/ST=Shannxi/L=Xi'an/O=kubernetes/OU=harbor/CN=${DOMAIN}/emailAddress=admin@${DOMAIN}"

# ---------- 5. 部署 Harbor ----------
echo "部署 Harbor..."
tar zxf $HARBOR_TAR -C /opt/
cd /opt/harbor
cp harbor.yml.tmpl harbor.yml
sed -i "s/^hostname: .*/hostname: ${DOMAIN}/" harbor.yml
sed -i "s|^  certificate: .*|  certificate: /data/certs/${DOMAIN}.crt|" harbor.yml
sed -i "s|^  private_key: .*|  private_key: /data/certs/${DOMAIN}.key|" harbor.yml
sed -i "s/^harbor_admin_password: .*/harbor_admin_password: ${PASS}/" harbor.yml
./install.sh --with-chartmuseum

# ---------- 6. 设置开机自启 ----------
echo "设置 Harbor 开机自启..."
cat > /lib/systemd/system/harbor.service <<EOF
[Unit]
Description=harbor with Docker Compose
After=docker.service network-online.target
Requires=docker.service
[Service]
Type=oneshot
WorkingDirectory=/opt/harbor
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now harbor

# ---------- 7. 配置本机信任 ----------
echo "配置 Docker 信任 Harbor..."
mkdir -p /etc/docker/certs.d/${DOMAIN}
cp /data/certs/${DOMAIN}.crt /etc/docker/certs.d/${DOMAIN}/ca.crt
echo "${HARBOR_IP}   ${DOMAIN}" >> /etc/hosts
systemctl restart docker

# ---------- 8. 等待 Harbor 服务就绪并验证登录 ----------
echo "等待 Harbor 服务就绪..."
MAX_RETRIES=30
RETRY=0
LOGIN_SUCCESS=false

while [ $RETRY -lt $MAX_RETRIES ]; do
    RETRY=$((RETRY + 1))
    echo -n "尝试 $RETRY/$MAX_RETRIES ... "

    if docker login ${DOMAIN} -u admin -p ${PASS} &>/dev/null; then
        echo -e "\033[32m[OK] Harbor 登录成功\033[0m"
        LOGIN_SUCCESS=true
        break
    fi

    echo -e "\033[33m连接失败,等待 2 秒后重试...\033[0m"
    sleep 2
done

if [ "$LOGIN_SUCCESS" = false ]; then
    echo -e "\033[33m[WARN] Harbor 服务已启动但登录验证超时\033[0m"
    echo ""
    echo "Harbor 容器可能正在初始化,请稍后手动验证:"
    echo "  docker login ${DOMAIN} -u admin -p ${PASS}"
    echo ""
    echo "查看 Harbor 状态:"
    echo "  docker ps --filter 'name=harbor'"
    echo "  docker logs harbor-core --tail 20"
fi

echo "=============================================="
echo -e "\033[32mHarbor 部署完成!\033[0m"
echo ""
echo "【访问信息】"
echo "  地址: https://${DOMAIN}"
echo "  用户名: admin"
echo "  密码: ${PASS}"
echo ""
echo "【后续操作】"
echo "  1. 浏览器访问 https://${DOMAIN} 确认服务正常"
echo "  2. 创建以下公开项目:"
echo "     - k8s"
echo "     - flannel-io"
echo "  3. 验证登录: docker login ${DOMAIN} -u admin -p ${PASS}"
echo "=============================================="

4. 02_node_common.sh(master, node1, node2 执行)

#!/bin/bash
# ============================================================
# 节点通用配置脚本(master, node1, node2)
# 功能:Docker + cri-dockerd + Harbor 证书 + 镜像加速器
# ============================================================

set -e
DOMAIN="${DOMAIN:-reg.avc.harbor.org}"
HARBOR_IP="${HARBOR_IP:-172.25.254.200}"
PASS="${PASS:-avc}"

echo "=============================================="
echo "  节点通用环境配置"
echo "  域名: $DOMAIN"
echo "  Harbor IP: $HARBOR_IP"
echo "=============================================="

# ---------- 1. 安装 Docker ----------
cat > /etc/yum.repos.d/docker.repo <<EOF
[docker]
name=docker
baseurl=https://mirrors.aliyun.com/docker-ce/linux/rhel/9.6/x86_64/stable/
gpgcheck=0
EOF
dnf install -y docker-ce
systemctl enable --now docker

# ---------- 2. 证书和 hosts ----------
mkdir -p /etc/docker/certs.d/${DOMAIN}
echo "从 Harbor 复制证书..."
if scp -o ConnectTimeout=5 -o StrictHostKeyChecking=no root@${HARBOR_IP}:/data/certs/${DOMAIN}.crt /etc/docker/certs.d/${DOMAIN}/ca.crt 2>/dev/null; then
    echo -e "\033[32m[OK] 证书复制成功\033[0m"
else
    echo -e "\033[31m[ERROR] 证书复制失败!\033[0m"
    echo "请手动执行:"
    echo "  scp root@${HARBOR_IP}:/data/certs/${DOMAIN}.crt /etc/docker/certs.d/${DOMAIN}/ca.crt"
    exit 1
fi

cat >> /etc/hosts <<EOF
${HARBOR_IP}   ${DOMAIN}
172.25.254.100   master
172.25.254.10    node1
172.25.254.20    node2
EOF

# ---------- 3. 镜像加速器 ----------
cat > /etc/docker/daemon.json <<EOF
{
  "registry-mirrors": ["https://${DOMAIN}"]
}
EOF
systemctl restart docker

# ---------- 4. Kubernetes yum 源 ----------
cat > /etc/yum.repos.d/kubernetes.repo <<EOF
[kubernetes]
name=kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.35/rpm/
gpgcheck=0
EOF

# ---------- 5. 安装 cri-dockerd ----------
cd /root
CRI_VERSION="v0.3.25"
CRI_FILE="cri-dockerd-${CRI_VERSION#v}.amd64.tgz"

if [ -f "$CRI_FILE" ]; then
    echo "找到本地 cri-dockerd 包,跳过下载。"
else
    echo "下载 cri-dockerd ${CRI_VERSION} ..."
    echo "下载地址: https://github.com/Mirantis/cri-dockerd/releases/download/${CRI_VERSION}/$CRI_FILE"
    if ! wget --timeout=30 --tries=3 https://github.com/Mirantis/cri-dockerd/releases/download/${CRI_VERSION}/$CRI_FILE; then
        echo -e "\033[31m[ERROR] 下载失败!\033[0m"
        echo "请手动下载到 /root 后重新运行。"
        echo "下载地址: https://github.com/Mirantis/cri-dockerd/releases/download/${CRI_VERSION}/$CRI_FILE"
        exit 1
    fi
fi

tar zxf $CRI_FILE
cd cri-dockerd
install -o root -g root -m 0755 cri-dockerd /usr/local/bin/cri-dockerd

# ---------- 6. 创建 systemd 服务 ----------
cat > /lib/systemd/system/cri-docker.service <<EOF
[Unit]
Description=CRI Interface for Docker Application Container Engine
Documentation=https://docs.mirantis.com
After=network-online.target docker.service
Wants=docker.service
Requires=cri-docker.socket

[Service]
Type=notify
ExecStart=/usr/local/bin/cri-dockerd --network-plugin=cni --pod-infra-container-image=${DOMAIN}/k8s/pause:3.10.1 --container-runtime-endpoint fd://
ExecReload=/bin/kill -s HUP \$MAINPID
TimeoutSec=0
RestartSec=2
Restart=always

[Install]
WantedBy=multi-user.target
EOF

cat > /lib/systemd/system/cri-docker.socket <<EOF
[Unit]
Description=CRI Docker Socket for the API
PartOf=cri-docker.service

[Socket]
ListenStream=/var/run/cri-dockerd.sock
SocketMode=0660
SocketUser=root
SocketGroup=root

[Install]
WantedBy=sockets.target
EOF

systemctl daemon-reload
systemctl enable --now cri-docker.service

# ---------- 7. 验证 ----------
echo "=============================================="
echo "验证配置..."
systemctl is-active cri-docker >/dev/null && echo -e "\033[32m[OK] cri-docker 运行中\033[0m" || { echo -e "\033[31m[FAIL] cri-docker 未运行\033[0m"; exit 1; }

docker login ${DOMAIN} -u admin -p ${PASS} || { echo -e "\033[31m[FAIL] Harbor 登录失败\033[0m"; exit 1; }
docker pull ${DOMAIN}/k8s/pause:3.10.1 || echo -e "\033[33m[WARN] pause 镜像拉取失败(Master 尚未推送)\033[0m"

echo -e "\033[32m[OK] 节点通用配置完成!\033[0m"
echo "=============================================="

5. 02.5_check_node.sh(node1, node2 可选执行)

#!/bin/bash
# ============================================================
# Worker 节点 Join 前置检查脚本
# 用法:./02.5_check_node.sh
# ============================================================

DOMAIN="${DOMAIN:-reg.avc.harbor.org}"
PASS="${PASS:-avc}"

echo "=============================================="
echo "  Worker 节点加入前检查"
echo "=============================================="

FAILED=0

echo -n "Harbor 证书: "
[ -f /etc/docker/certs.d/${DOMAIN}/ca.crt ] && echo -e "\033[32mOK\033[0m" || { echo -e "\033[31mFAIL\033[0m"; FAILED=1; }

echo -n "Docker 服务: "
systemctl is-active docker >/dev/null 2>&1 && echo -e "\033[32mOK\033[0m" || { echo -e "\033[31mFAIL\033[0m"; FAILED=1; }

echo -n "Harbor 登录: "
docker login ${DOMAIN} -u admin -p ${PASS} >/dev/null 2>&1 && echo -e "\033[32mOK\033[0m" || { echo -e "\033[31mFAIL\033[0m"; FAILED=1; }

echo -n "拉取 pause 镜像: "
docker pull ${DOMAIN}/k8s/pause:3.10.1 >/dev/null 2>&1 && echo -e "\033[32mOK\033[0m" || { echo -e "\033[31mFAIL\033[0m"; FAILED=1; }

echo -n "br_netfilter 模块: "
lsmod | grep -q br_netfilter && echo -e "\033[32mOK\033[0m" || { echo -e "\033[31mFAIL\033[0m"; FAILED=1; }

echo -n "sysctl 参数: "
sysctl net.bridge.bridge-nf-call-iptables 2>/dev/null | grep -q "= 1" && echo -e "\033[32mOK\033[0m" || { echo -e "\033[31mFAIL\033[0m"; FAILED=1; }

echo "=============================================="
if [ $FAILED -eq 0 ]; then
    echo -e "\033[32m所有检查通过!\033[0m"
    exit 0
else
    echo -e "\033[31m存在失败项,请修复后重试。\033[0m"
    exit 1
fi

6. 03_master_install.sh(master 执行)

#!/bin/bash
# ============================================================
# Master 集群初始化脚本(最终版)
# 依赖文件:/root/kube-flannel-v0.28.9.gz
# 下载地址:https://github.com/flannel-io/flannel/releases
# ============================================================

set -e
DOMAIN="${DOMAIN:-reg.avc.harbor.org}"
PASS="${PASS:-avc}"
K8S_VERSION="${K8S_VERSION:-v1.35.7}"
MASTER_IP="${MASTER_IP:-172.25.254.100}"
FLANNEL_FILE="/root/kube-flannel-v0.28.9.gz"
HARBOR_IP="${HARBOR_IP:-172.25.254.200}"

echo "=============================================="
echo "  Master 节点集群初始化"
echo "  版本信息:"
echo "    K8s: $K8S_VERSION"
echo "    Flannel: v0.28.9"
echo "    cri-dockerd: v0.3.25"
echo "=============================================="

# ---------- 0. 检查 swap ----------
echo -n "检查 swap 状态... "
if swapon --show | grep -q .; then
    echo -e "\033[33m[WARN] swap 已启用,正在禁用...\033[0m"
    swapoff -a
    sed -i '/swap/s/^/#/g' /etc/fstab
    systemctl mask swap.target 2>/dev/null
    echo -e "\033[32m[OK] swap 已禁用\033[0m"
else
    echo -e "\033[32m[OK] swap 已禁用\033[0m"
fi

# ---------- 1. 安装 Kubernetes 组件 ----------
echo "安装 Kubernetes 组件..."
dnf install -y kubelet kubeadm kubectl
systemctl enable --now kubelet

# ---------- 2. 配置 KUBECONFIG(永久生效) ----------
echo "配置 kubectl 环境变量..."
if ! grep -q "export KUBECONFIG=/etc/kubernetes/admin.conf" ~/.bashrc; then
    echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bashrc
    echo -e "\033[32m[OK] KUBECONFIG 已写入 ~/.bashrc\033[0m"
else
    echo -e "\033[32m[OK] KUBECONFIG 已存在于 ~/.bashrc\033[0m"
fi
export KUBECONFIG=/etc/kubernetes/admin.conf
echo -e "\033[32m[OK] kubectl 环境变量已生效\033[0m"

# ---------- 3. 登录 Harbor ----------
echo "登录 Harbor ${DOMAIN} ..."
docker login ${DOMAIN} -u admin -p ${PASS}
if [ $? -ne 0 ]; then
    echo -e "\033[31m[ERROR] Harbor 登录失败!\033[0m"
    echo ""
    echo "可能原因及解决方案:"
    echo "  1. Harbor 服务未启动"
    echo "     执行: ssh root@${HARBOR_IP} 'systemctl start harbor'"
    echo "  2. 网络不通"
    echo "     执行: ping ${DOMAIN}"
    echo "  3. 密码错误"
    echo "     当前密码: ${PASS}"
    echo "     请在 harbor 主机执行: docker login ${DOMAIN} -u admin -p ${PASS}"
    echo ""
    echo "修复后重新运行本脚本。"
    exit 1
fi
echo -e "\033[32m[OK] Harbor 登录成功\033[0m"

# ---------- 4. 推送基础镜像 ----------
if ! docker images | grep -q "${DOMAIN}/k8s/kube-apiserver"; then
    echo "拉取并推送 Kubernetes 基础镜像..."
    kubeadm config images pull --image-repository registry.aliyuncs.com/google_containers --kubernetes-version ${K8S_VERSION} --cri-socket=unix:///var/run/cri-dockerd.sock
    docker images --format "{{.Repository}}:{{.Tag}}" | awk -F "/" '/google/{system("docker tag "$0" '${DOMAIN}'/k8s/"$3)}'
    docker images --format "{{.Repository}}:{{.Tag}}" | awk -F "/" '/'${DOMAIN}'/{system("docker push "$0)}'
    echo -e "\033[32m[OK] 基础镜像推送完成\033[0m"
else
    echo -e "\033[32m[OK] 基础镜像已存在,跳过拉取\033[0m"
fi

# ---------- 5. 验证镜像推送完整性 ----------
EXPECTED=7
PUSHED=$(docker images | grep ${DOMAIN}/k8s | wc -l)
if [ $PUSHED -lt $EXPECTED ]; then
    echo -e "\033[33m[WARN] 只推送了 ${PUSHED}/${EXPECTED} 个镜像\033[0m"
    echo ""
    echo "请检查 Harbor 中 k8s 项目的镜像列表:"
    echo "  浏览器访问 https://${DOMAIN}/harbor/projects/k8s/repositories"
    echo ""
    echo "如果缺少镜像,请手动推送:"
    echo "  docker push ${DOMAIN}/k8s/pause:3.10.1"
    echo "  docker push ${DOMAIN}/k8s/coredns:v1.13.1"
    echo "  ...(其他镜像)"
fi

# ---------- 6. 初始化集群 ----------
if [ ! -f /etc/kubernetes/admin.conf ]; then
    echo "初始化 Kubernetes 集群..."
    kubeadm init --pod-network-cidr=10.244.0.0/16 --image-repository ${DOMAIN}/k8s --kubernetes-version ${K8S_VERSION} --cri-socket=unix:///var/run/cri-dockerd.sock --ignore-preflight-errors=CRI
    echo -e "\033[32m[OK] 集群初始化成功\033[0m"
else
    echo -e "\033[32m[OK] 集群已初始化,跳过 kubeadm init\033[0m"
fi

# ---------- 7. 生成 join 命令 ----------
kubeadm token create --print-join-command > /root/join_command.txt
echo "请在其他节点执行: $(cat /root/join_command.txt) --cri-socket=unix:///var/run/cri-dockerd.sock" > /root/join_instruction.txt
echo -e "\033[32m[OK] Join 命令已生成,查看 /root/join_instruction.txt\033[0m"

# ---------- 8. 检查 Flannel 包 ----------
echo "检查 Flannel 离线包..."
if [ ! -f "$FLANNEL_FILE" ]; then
    echo -e "\033[31m[ERROR] Flannel 离线包不存在!\033[0m"
    echo ""
    echo "预期文件: $FLANNEL_FILE"
    echo "下载地址: https://github.com/flannel-io/flannel/releases"
    echo "当前最新版本: v0.28.9"
    echo ""
    echo "请将文件上传到 /root 后重新运行本脚本。"
    exit 1
fi
echo -e "\033[32m[OK] Flannel 离线包存在\033[0m"

# ---------- 9. 解压并加载 Flannel ----------
echo "解压 Flannel 离线包..."
if tar zxf $FLANNEL_FILE -C /root/ 2>/dev/null; then
    echo -e "\033[32m[OK] tar 解压成功\033[0m"
elif gunzip -k $FLANNEL_FILE 2>/dev/null; then
    echo -e "\033[32m[OK] gunzip 解压成功\033[0m"
else
    echo -e "\033[31m[ERROR] 解压失败!\033[0m"
    echo "文件可能已损坏,请重新下载。"
    exit 1
fi

if [ -d /root/flannel ]; then
    cd /root/flannel
else
    echo -e "\033[33m[WARN] 未找到 /root/flannel 目录,尝试当前目录\033[0m"
fi

echo "加载 Flannel 镜像..."
if docker load -i *.tar 2>/dev/null || docker load -i kube-flannel-*.tar 2>/dev/null; then
    echo -e "\033[32m[OK] Flannel 镜像加载完成\033[0m"
else
    echo -e "\033[31m[ERROR] 加载镜像失败!\033[0m"
    echo "请检查离线包是否完整。"
    exit 1
fi

# ---------- 10. 推送 Flannel 镜像(自动重试) ----------
echo "推送 Flannel 镜像到 Harbor..."
docker tag ghcr.io/flannel-io/flannel-cni-plugin:v1.9.1-flannel3 ${DOMAIN}/flannel-io/flannel-cni-plugin:v1.9.1-flannel3
docker tag ghcr.io/flannel-io/flannel:v0.28.9 ${DOMAIN}/flannel-io/flannel:v0.28.9

# 推送 flannel-cni-plugin(自动重试)
echo "推送 flannel-cni-plugin ..."
PUSH_RETRY=0
while [ $PUSH_RETRY -lt 2 ]; do
    if docker push ${DOMAIN}/flannel-io/flannel-cni-plugin:v1.9.1-flannel3 2>&1; then
        echo -e "\033[32m[OK] flannel-cni-plugin 推送成功\033[0m"
        break
    else
        PUSH_RETRY=$((PUSH_RETRY+1))
        if [ $PUSH_RETRY -lt 2 ]; then
            echo -e "\033[33m[WARN] 推送失败,重新登录 Harbor 后重试...\033[0m"
            docker login ${DOMAIN} -u admin -p ${PASS}
        else
            echo -e "\033[31m[ERROR] Flannel 镜像推送失败!\033[0m"
            echo ""
            echo "可能原因及解决方案:"
            echo "  1. Harbor 中缺少 'flannel-io' 项目"
            echo "     执行: 浏览器访问 https://${DOMAIN},创建公开项目 'flannel-io'"
            echo "  2. Harbor 登录已过期"
            echo "     执行: docker login ${DOMAIN} -u admin -p ${PASS}"
            echo "  3. Harbor 服务未启动"
            echo "     执行: ssh root@${HARBOR_IP} 'systemctl start harbor'"
            echo ""
            echo "修复后执行:"
            echo "  docker push ${DOMAIN}/flannel-io/flannel-cni-plugin:v1.9.1-flannel3"
            echo "  docker push ${DOMAIN}/flannel-io/flannel:v0.28.9"
            echo "  kubectl delete pods -n kube-flannel --all"
            exit 1
        fi
    fi
done

# 推送 flannel(自动重试)
echo "推送 flannel ..."
PUSH_RETRY=0
while [ $PUSH_RETRY -lt 2 ]; do
    if docker push ${DOMAIN}/flannel-io/flannel:v0.28.9 2>&1; then
        echo -e "\033[32m[OK] flannel 推送成功\033[0m"
        break
    else
        PUSH_RETRY=$((PUSH_RETRY+1))
        if [ $PUSH_RETRY -lt 2 ]; then
            echo -e "\033[33m[WARN] 推送失败,重新登录 Harbor 后重试...\033[0m"
            docker login ${DOMAIN} -u admin -p ${PASS}
        else
            echo -e "\033[31m[ERROR] Flannel 镜像推送失败!\033[0m"
            echo ""
            echo "修复后执行:"
            echo "  docker push ${DOMAIN}/flannel-io/flannel:v0.28.9"
            echo "  kubectl delete pods -n kube-flannel --all"
            exit 1
        fi
    fi
done

# ---------- 11. 安装 Flannel ----------
if [ -f kube-flannel.yml ]; then
    sed -i "s|ghcr.io/flannel-io/|${DOMAIN}/flannel-io/|g" kube-flannel.yml
    echo "安装 Flannel..."
    kubectl apply -f kube-flannel.yml
    echo -e "\033[32m[OK] Flannel 安装完成\033[0m"
else
    echo -e "\033[31m[ERROR] 找不到 kube-flannel.yml\033[0m"
    exit 1
fi

cd /root

# ---------- 12. 持久化 br_netfilter ----------
echo "配置 br_netfilter..."
modprobe br_netfilter
echo "br_netfilter" > /etc/modules-load.d/k8s.conf
cat > /etc/sysctl.d/k8s.conf <<EOF
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
net.ipv4.ip_forward=1
EOF
sysctl --system

echo "=============================================="
echo -e "\033[32mMaster 初始化完成!\033[0m"
echo ""
echo "【kubectl 配置】"
echo "  当前终端执行: export KUBECONFIG=/etc/kubernetes/admin.conf"
echo "  或重新登录自动生效"
echo ""
echo "【下一步】"
echo "  在 node1、node2 上执行: ./04_worker_join.sh"
echo "=============================================="

7. 04_worker_join.sh(node1, node2 执行)

#!/bin/bash
# ============================================================
# Worker 节点加入集群脚本(自动修复版)
# 功能:自动检测并修复所有依赖问题
# ============================================================

set -e

DOMAIN="${DOMAIN:-reg.avc.harbor.org}"
PASS="${PASS:-avc}"
MASTER_IP="${MASTER_IP:-172.25.254.100}"
HARBOR_IP="${HARBOR_IP:-172.25.254.200}"

echo "=============================================="
echo "  Worker 节点加入集群(自动修复版)"
echo "=============================================="

# ---------- 1. 检测是否已加入 ----------
if [ -f /var/lib/kubelet/config.yaml ]; then
    echo "本节点已加入集群,无需重复操作。"
    exit 0
fi

# ---------- 2. 自动安装 kubeadm ----------
if ! command -v kubeadm &>/dev/null; then
    echo -e "\033[33m[WARN] kubeadm 未安装,正在自动安装...\033[0m"
    dnf install -y kubeadm kubelet
    systemctl enable --now kubelet
    echo -e "\033[32m[OK] kubeadm 安装完成\033[0m"
else
    echo -e "\033[32m[OK] kubeadm 已安装\033[0m"
fi

# ---------- 3. 启动 kubelet ----------
if ! systemctl is-active kubelet &>/dev/null; then
    echo "启动 kubelet..."
    systemctl start kubelet
fi
echo -e "\033[32m[OK] kubelet 运行中\033[0m"

# ---------- 4. 检查 Harbor 连通性 ----------
if ! ping -c 1 ${DOMAIN} &>/dev/null; then
    echo -e "\033[31m[ERROR] 无法解析 Harbor 域名\033[0m"
    echo "请检查 /etc/hosts 是否包含: ${HARBOR_IP} ${DOMAIN}"
    exit 1
fi
echo -e "\033[32m[OK] Harbor 域名解析正常\033[0m"

# ---------- 5. 自动加载 br_netfilter ----------
if ! lsmod | grep -q br_netfilter; then
    echo -e "\033[33m[WARN] br_netfilter 未加载,正在自动加载...\033[0m"
    modprobe br_netfilter
    echo "br_netfilter" > /etc/modules-load.d/k8s.conf
    echo -e "\033[32m[OK] br_netfilter 已加载\033[0m"
fi

# ---------- 6. 设置 sysctl ----------
cat > /etc/sysctl.d/k8s.conf <<EOF
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
net.ipv4.ip_forward=1
EOF
sysctl --system &>/dev/null
echo -e "\033[32m[OK] sysctl 参数已设置\033[0m"

# ---------- 7. 自动登录 Harbor(含重试) ----------
echo "登录 Harbor ${DOMAIN} ..."
RETRY=0
while [ $RETRY -lt 2 ]; do
    if docker login ${DOMAIN} -u admin -p ${PASS} &>/dev/null; then
        echo -e "\033[32m[OK] Harbor 登录成功\033[0m"
        break
    else
        RETRY=$((RETRY+1))
        if [ $RETRY -lt 2 ]; then
            echo -e "\033[33m[WARN] 登录失败,等待 3 秒后重试...\033[0m"
            sleep 3
        else
            echo -e "\033[31m[ERROR] Harbor 登录失败!\033[0m"
            echo ""
            echo "可能原因:"
            echo "  1. Harbor 服务未启动(在 harbor 主机执行: systemctl start harbor)"
            echo "  2. 密码错误(当前密码: ${PASS})"
            echo "  3. 网络不通(ping ${DOMAIN})"
            echo ""
            echo "请修复后重新运行本脚本。"
            exit 1
        fi
    fi
done

# ---------- 8. 验证镜像拉取(含自动修复) ----------
echo "验证镜像拉取能力..."
RETRY=0
while [ $RETRY -lt 2 ]; do
    if docker pull ${DOMAIN}/k8s/pause:3.10.1 &>/dev/null; then
        echo -e "\033[32m[OK] pause 镜像拉取成功\033[0m"
        break
    else
        RETRY=$((RETRY+1))
        if [ $RETRY -lt 2 ]; then
            echo -e "\033[33m[WARN] 拉取失败,尝试重新登录 Harbor 后重试...\033[0m"
            docker login ${DOMAIN} -u admin -p ${PASS} &>/dev/null
        else
            echo -e "\033[31m[ERROR] pause 镜像拉取失败!\033[0m"
            echo ""
            echo "可能原因及解决方案:"
            echo ""
            echo "  1. Master 未推送基础镜像到 Harbor"
            echo "     在 master 上执行: docker images | grep pause"
            echo "     如果没有,重新执行 ./03_master_install.sh"
            echo ""
            echo "  2. Harbor 中 'k8s' 项目不是公开的"
            echo "     浏览器访问 https://${DOMAIN}/harbor/projects/k8s"
            echo "     点击 '公开' 切换为公开"
            echo ""
            echo "  3. 镜像名称或版本不正确"
            echo "     在 master 上执行: docker images | grep pause"
            echo "     确认 Harbor 中的镜像版本号,然后手动拉取:"
            echo "     docker pull ${DOMAIN}/k8s/pause:3.10.1"
            echo ""
            echo "修复后重新运行本脚本。"
            exit 1
        fi
    fi
done

# ---------- 9. 执行 join ----------
echo ""
echo "请从 Master 获取 join 命令:"
echo "  ssh root@${MASTER_IP} 'cat /root/join_instruction.txt'"
echo ""
echo "或手动输入完整的 kubeadm join 命令(包括 --cri-socket 参数)"
echo -n "请粘贴命令: "
read JOIN_CMD

if [ -z "$JOIN_CMD" ]; then
    echo -e "\033[31m错误:未输入任何命令。\033[0m"
    exit 1
fi

echo ""
echo "即将执行:"
echo "  $JOIN_CMD"
echo ""
echo -n "确认执行?(y/n): "
read CONFIRM
if [ "$CONFIRM" != "y" ] && [ "$CONFIRM" != "Y" ]; then
    echo "已取消操作。"
    exit 0
fi

echo "正在加入集群..."
eval $JOIN_CMD

echo -e "\033[32m=============================================="
echo "加入完成!"
echo "==============================================\033[0m"
echo "请在 Master 上执行: kubectl get nodes"

8. 05_final_check.sh(master 执行)

#!/bin/bash
# ============================================================
# 最终集群验证脚本
# 用法:./05_final_check.sh [-log 日志文件]
# ============================================================

LOG_FILE=""
if [ "$1" == "-log" ] && [ -n "$2" ]; then
    LOG_FILE="$2"
    exec > >(tee -a "$LOG_FILE") 2>&1
fi

echo "========== 最终验收检查 =========="
echo -n "Harbor 容器运行: "
docker ps --filter "name=harbor" --format "{{.Names}}" | grep -q harbor-core && echo "OK" || echo "FAIL"
echo -n "Harbor 登录: "
docker login reg.avc.harbor.org -u admin -p avc >/dev/null 2>&1 && echo "OK" || echo "FAIL"
echo -n "所有节点 Ready: "
kubectl get nodes 2>/dev/null | grep -q "NotReady" && echo "FAIL" || echo "OK"
echo -n "Flannel Pod Running: "
kubectl get pods -n kube-flannel 2>/dev/null | grep -v NAME | grep -q "Running" && echo "OK" || echo "FAIL"
echo -n "CoreDNS Running: "
kubectl get pods -n kube-system 2>/dev/null | grep coredns | grep -q "Running" && echo "OK" || echo "FAIL"
echo -n "br_netfilter 模块: "
lsmod | grep -q br_netfilter && echo "OK" || echo "FAIL"
echo "========================================"

9. check_persistence.sh(master 执行)

#!/bin/bash
# ============================================================
# 持久化验证脚本(重启后执行)
# ============================================================

RED='\033[31m'; GREEN='\033[32m'; NC='\033[0m'
ALL_OK=1

echo "========== 持久化验证 =========="

echo -n "swap 禁用: "
swapon --show | grep -q . && echo -e "${RED}FAIL${NC}" || echo -e "${GREEN}OK${NC}"

for svc in docker cri-docker kubelet; do
    echo -n "服务 $svc (enabled): "
    systemctl is-enabled $svc >/dev/null 2>&1 && echo -e "${GREEN}OK${NC}" || { echo -e "${RED}FAIL${NC}"; ALL_OK=0; }
done

echo -n "br_netfilter 模块: "
lsmod | grep -q br_netfilter && echo -e "${GREEN}OK${NC}" || { echo -e "${RED}FAIL${NC}"; ALL_OK=0; }

echo -n "sysctl bridge-nf-call-iptables: "
sysctl net.bridge.bridge-nf-call-iptables 2>/dev/null | grep -q "= 1" && echo -e "${GREEN}OK${NC}" || { echo -e "${RED}FAIL${NC}"; ALL_OK=0; }

echo -n "Harbor 证书: "
[ -f /etc/docker/certs.d/reg.avc.harbor.org/ca.crt ] && echo -e "${GREEN}OK${NC}" || { echo -e "${RED}FAIL${NC}"; ALL_OK=0; }

echo -n "kubectl 连接: "
kubectl cluster-info >/dev/null 2>&1 && echo -e "${GREEN}OK${NC}" || { echo -e "${RED}FAIL${NC}"; ALL_OK=0; }

echo -n "所有节点 Ready: "
kubectl get nodes 2>/dev/null | grep -q "NotReady" && { echo -e "${RED}FAIL${NC}"; ALL_OK=0; } || echo -e "${GREEN}OK${NC}"

echo -n "Flannel Pod Running: "
kubectl get pods -n kube-flannel 2>/dev/null | grep -v NAME | grep -q "Running" && echo -e "${GREEN}OK${NC}" || { echo -e "${RED}FAIL${NC}"; ALL_OK=0; }

echo "=============================================="
[ $ALL_OK -eq 1 ] && echo -e "${GREEN}结果: 1(全部完好)${NC}" || echo -e "${RED}结果: 0(存在问题)${NC}"

更多推荐