距离上一次部署K8S集群,已经是8年前的事情了,这次记录下在Ubuntu 24.04上部署Kubernetes 1.36集群。

环境要求

  • Ubuntu 24.04
  • Kubernetes v1.36
  • Rancher v2.15
IP HostName 角色
10.10.10.60 k8s-master 控制节点
10.10.10.61 k8s-worker-01 Worker节点
10.10.10.62 k8s-worker-02 Worker节点

部署前配置

Hosts

所有服务器均配置

1
2
3
4
5
6
7
sudo vim /etc/hosts

# 填入如下内容

10.10.10.60 k8s-master
10.10.10.61 k8s-worker-01
10.10.10.62 k8s-worker-02

关闭Swap

所有服务器都需要配置。

1
2
3
4
5
6
# 临时关闭
sudo swapoff -a
# 修改挂载配置,永久生效,个别系统因安装方式不同,有可能会有差异,可以编辑/etc/fstab,确认将/swap注释了即可
sudo sed -i '/\sswap\s/s/^/#/' /etc/fstab
# 验证
free -h

修改前:

1
2
3
4
root@k8s-master:~# free -h
total used free shared buff/cache available
Mem: 538Mi 380Mi 229Mi 3.1Mi 111Mi 157Mi
Swap: 2.0Gi 32Mi 2.0Gi

修改后

1
2
3
4
root@k8s-master:~# free -h
total used free shared buff/cache available
Mem: 538Mi 413Mi 196Mi 3.9Mi 112Mi 124Mi
Swap: 0B 0B 0B

屏蔽Systemd Swap单元

1
2
3
4
# 屏蔽Systemd swap单元
sudo systemctl mask swap.target
# 校验,没任何输出就对了
swapon --show

加载内核模块

所有服务器都需要配置。

1
2
3
4
5
6
7
sudo cat > /etc/modules-load.d/k8s.conf <<'EOF'
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

验证

1
lsmod | egrep 'overlay|br_netfilter'

修改内核参数

所有服务器都需要配置。

1
2
3
4
5
6
7
8
9
sudo cat > /etc/sysctl.d/99-kubernetes.conf <<'EOF'
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
EOF

sudo sysctl --system

验证

1
2
sudo sysctl net.ipv4.ip_forward
sudo sysctl net.bridge.bridge-nf-call-iptables

配置SELinux和防火墙

Ubuntu默认不启用SELinux,无需配置。

对于防火墙ufw,可以使用如下命令关闭。

1
2
3
4
5
6
7
sudo systemctl stop ufw
sudo systemctl disable ufw

# 验证 输出inactive即可
sudo ufw status
# 提示 Command 'getenforce' not found, but can be installed with:表示压根没有安装
getenforce

containerd运行时配置

安装containerd

Kubernetes 最新版推荐使用containerd,所以先安装它,Ubuntu官方源自带了这个容器,直接使用如下命令安装即可。

1
2
sudo apt update
sudo apt install -y containerd

生成默认配置

1
2
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml

修改 cgroup 驱动为 systemd

1
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

kubeadm 管理的 kubelet 默认使用 systemd cgroup 驱动,containerd 也必须保持一致。如果不改,kubeadm init 大概率会失败。

配置国内加速地址

编辑/etc/containerd/config.toml,找到对应版本下的[plugins."...registry"]段落,设置config_path指向/etc/containerd/certs.d

由于本次安装的containerd的版本是2.2.1,对于2.x,请使用如下配置:

1
2
3
4
5
6
7
8
# 示例配置
[plugins."io.containerd.cri.v1.images".registry]
config_path = "/etc/containerd/certs.d"


# 生成默认配置时,已经有这个配置了,如下所示,这样的就不用单独配置
[plugins.'io.containerd.cri.v1.images'.registry]
config_path = '/etc/containerd/certs.d:/etc/docker/certs.d'

修改sandbox的镜像地址

编辑/etc/containerd/config.toml,找到sandbox的配置

1
2
[plugins.'io.containerd.cri.v1.images'.pinned_images]
sandbox = 'registry.k8s.io/pause:3.10.1'

将其修改为如下:

1
2
[plugins.'io.containerd.cri.v1.images'.pinned_images]
sandbox = 'registry.aliyuncs.com/google_containers/pause:3.10.2'

创建加速器hosts.toml文件

为需要加速的仓库创建对应的目录和配置文件。以 docker.io 为例

1
2
sudo mkdir -p /etc/containerd/certs.d/docker.io
sudo vim /etc/containerd/certs.d/docker.io/hosts.toml

在hosts.toml中填入如下内容:

1
2
3
4
server = "https://docker.io"

[host."<加速器地址>"]
capabilities = ["pull", "resolve"]

例如本次的加速地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
server = "https://docker.io"

[host."https://docker.1panel.live"]
capabilities = ["pull", "resolve"]

[host."https://docker.sparkcr.cn"]
capabilities = ["pull", "resolve"]

[host."https://hub.rat.dev"]
capabilities = ["pull", "resolve"]

[host."https://dockerproxy.net"]
capabilities = ["pull", "resolve"]

containerd 会按顺序尝试这些 [host],第一个成功的即被使用。

重启并设置开机自启

1
2
sudo systemctl restart containerd
sudo systemctl enable containerd

测试加速地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
sudo ctr images pull --hosts-dir "/etc/containerd/certs.d" docker.io/library/nginx:latest


docker.io/library/nginx:latest saved
└──index (abe47724e466) already exists
├──manifest (e57665aa5d61) already exists
│ └──config (4c1e47d5156e) already exists
├──manifest (918e8d119b7c) already exists
│ └──config (24e95a0b421e) already exists
├──manifest (80e8942d76c2) already exists
│ └──config (07954515bed9) complete |++++++++++++++++++++++++++++++++++++++|
├──manifest (a3f4937fe245) already exists
│ └──config (111065a1fc90) already exists
├──manifest (7c0a84ff6658) already exists
│ └──config (9b02f35d286f) complete |++++++++++++++++++++++++++++++++++++++|
├──manifest (9c0f39aa1c46) already exists
│ ├──config (e9567a94eddc) already exists
│ ├──layer (46243d3234ed) extracted |++++++++++++++++++++++++++++++++++++++|
│ ├──layer (2056b40bae09) extracted |++++++++++++++++++++++++++++++++++++++|
│ ├──layer (6b37362b3da7) extracted |++++++++++++++++++++++++++++++++++++++|
│ ├──layer (f802f27d954b) extracted |++++++++++++++++++++++++++++++++++++++|
│ ├──layer (3326c3817340) extracted |++++++++++++++++++++++++++++++++++++++|
│ ├──layer (afa8dec48454) extracted |++++++++++++++++++++++++++++++++++++++|
│ └──layer (f1169c633cbc) extracted |++++++++++++++++++++++++++++++++++++++|
├──manifest (86753677ff59) already exists
│ └──config (1428287eb324) already exists
├──manifest (11e4aa39f602) already exists
│ └──config (9298f24a2e15) already exists
├──manifest (1ad1d69bf75e) already exists
│ └──config (0fecf3ebfb0e) already exists
├──manifest (9f1d10e21b69) already exists
│ └──config (7f608302f44f) already exists
├──manifest (fdd27b1339a4) already exists
│ └──config (d5dd4cf6ff02) already exists
├──manifest (9a536b164be3) already exists
│ └──config (ee1cc8f2f941) already exists
├──manifest (752ced86f64e) already exists
│ └──config (a7f7e2385f4e) already exists
├──manifest (7091b89b716b) already exists
│ └──config (474fe4d961f5) already exists
├──manifest (b70a461b4fe9) already exists
│ └──config (15438ba8ed88) complete |++++++++++++++++++++++++++++++++++++++|
└──manifest (aebba161d93e) already exists
└──config (9e6692401de7) already exists
application/vnd.oci.image.index.v1+json sha256:abe47724e466aeab9a345d8e46a221c2fa8953c7848bb4a3bd9976a7199f8cf2
Pulling from OCI Registry (docker.io/library/nginx:latest) elapsed: 19.8s total: 60.6 M (3.1 MiB/s)

部署Kubernetes

替换清华大学源

1
2
3
4
5
6
7
8
9
# 备份当前源
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak

# 使用清华源替换 Ubuntu 官方源
sudo sed -i "s@http://.*archive.ubuntu.com@https://mirrors.tuna.tsinghua.edu.cn@g" /etc/apt/sources.list
sudo sed -i "s@http://.*security.ubuntu.com@https://mirrors.tuna.tsinghua.edu.cn@g" /etc/apt/sources.list

# 更新
sudo apt update

添加清华大学K8S源,并且指定v1.36

1
2
3
4
5
6
7
8
9
10
# 创建 keyring 目录
sudo mkdir -p /etc/apt/keyrings

# 下载并导入 v1.36 的签名密钥
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.36/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
sudo chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg

# 添加 v1.36 源
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.36/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo chmod 644 /etc/apt/sources.list.d/kubernetes.list

安装k8s

安装指定版本的组件,并且锁定

1
2
3
4
5
6
7
sudo apt update

# 安装 v1.36 系列的 kubelet、kubeadm、kubectl
sudo apt install -y kubelet kubeadm kubectl

# 锁定版本,防止后续 apt upgrade 意外升级
sudo apt-mark hold kubelet kubeadm kubectl

确认kubeadm和kubectl版本一致

1
2
3
4
5
kubeadm version
kubelet --version

kubeadm version: &version.Info{Major:"1", Minor:"36", EmulationMajor:"", EmulationMinor:"", MinCompatibilityMajor:"", MinCompatibilityMinor:"", GitVersion:"v1.36.5", GitCommit:"ad950d1cc78b0183c476bd4d3f1934c104229727", GitTreeState:"clean", BuildDate:"2026-09-23T17:09:21Z", GoVersion:"go1.26.8", Compiler:"gc", Platform:"linux/amd64"}
Kubernetes v1.36.5

如果版本不一致,kubeadm init 会报错。

初始化集群

1
2
3
4
5
sudo kubeadm init \
--apiserver-advertise-address=10.10.10.60 \
--image-repository=registry.aliyuncs.com/google_containers \
--kubernetes-version=v1.36.5 \
--pod-network-cidr=192.168.0.0/16
  • --apiserver-advertise-address k8s master的IP地址
  • --image-repository 镜像仓库,推荐使用阿里云的
  • --kubernetes-version k8s版本与之前kubeadm version输出一致
  • --pod-network-cidr 必须与后续 CNI 插件要求的网段一致,Calico 默认用 192.168.0.0/16,也可以设置为其他IP段,不要与现有的网络重复即可,推荐使用默认地址。

集群创建成功时,会提示如下信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Your Kubernetes control-plane has 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 10.10.10.60:6443 --token hqk9jq.bkgaawc0eiclcfcj \
--discovery-token-ca-cert-hash sha256:709f98f4cf9c4a2dfb712ae7772219b5bed93ca0f6d822cd76d63051e150b26b

如果创建失败,请删除配置重新创建

1
2
3
4
sudo kubeadm reset -f
sudo rm -rf /etc/kubernetes/manifests/etcd.yaml
sudo rm -rf /var/lib/etcd
sudo rm -rf /etc/kubernetes/pki/etcd

然后安装cri-tools,并配置

1
2
3
4
5
6
7
sudo apt install -y cri-tools
sudo tee /etc/crictl.yaml <<EOF
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
debug: false
EOF

查看所有控制平面容器状态

1
2
3
sudo crictl ps -a | grep -E 'etcd|kube-apiserver'
# 如果 etcd 容器存在,查看它的日志(假设容器ID为 <etcd-container-id>)
sudo crictl logs <etcd-container-id>

或者通过如下方式查看日志:

1
2
3
4
sudo ctr plugins ls | grep cri
sudo crictl version
sudo cat /var/lib/kubelet/kubeadm-flags.env
sudo journalctl -u kubelet --no-pager | head -50

或者带上详细信息去创建集群

1
2
3
4
5
6
sudo kubeadm init \
--apiserver-advertise-address=10.10.10.60 \
--image-repository=registry.aliyuncs.com/google_containers \
--kubernetes-version=v1.36.5 \
--pod-network-cidr=192.168.0.0/16 \
--v=5

配置kubectl

根据安装成功的提示,执行如下命令即可

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

验证:

1
2
3
4
kubectl get nodes

NAME STATUS ROLES AGE VERSION
k8s-master NotReady control-plane 7m24s v1.36.5

安装CNI插件(Calico)

在master节点10.10.10.60执行:

1
2
3
4
# 为了方便替换国内源,后续部分组件需要先下载下来,然后替换,统一放在~/k8s目录
mkdir ~/k8s
cd ~/k8s
wget https://ghproxy.net/https://raw.githubusercontent.com/projectcalico/calico/v3.32.2/manifests/calico.yaml

我已经将对应的镜像推送到harbor中,下面进行地址替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 替换前
grep "image:" calico.yaml
image: quay.io/calico/cni:v3.32.2
image: quay.io/calico/cni:v3.32.2
image: quay.io/calico/node:v3.32.2
image: quay.io/calico/node:v3.32.2
image: quay.io/calico/kube-controllers:v3.32.2

# 替换地址
sed -i 's#quay.io/calico#harbor.dev.net.cn/k8s/quay.io/calico#g' calico.yaml

# 替换后
grep "image:" calico.yaml
image: harbor.dev.net.cn/k8s/quay.io/calico/cni:v3.32.2
image: harbor.dev.net.cn/k8s/quay.io/calico/cni:v3.32.2
image: harbor.dev.net.cn/k8s/quay.io/calico/node:v3.32.2
image: harbor.dev.net.cn/k8s/quay.io/calico/node:v3.32.2
image: harbor.dev.net.cn/k8s/quay.io/calico/kube-controllers:v3.32.2

尝试pull镜像

也可以提前全部拉去,加快速度

1
sudo crictl pull harbor.dev.net.cn/k8s/quay.io/calico/node:v3.32.2

应用服务

1
2
3
4
5
6
7
8
9
10
11
12
13
kubectl apply -f calico.yaml
kubectl get pods -n kube-system -w

NAME READY STATUS RESTARTS AGE
calico-kube-controllers-95cb87bb-m4mqt 1/1 Running 0 5m9s
calico-node-4jnt5 1/1 Running 0 5m9s
coredns-6b5f954497-2r679 1/1 Running 0 36m
coredns-6b5f954497-wgx57 1/1 Running 0 36m
etcd-k8s-master 1/1 Running 1 36m
kube-apiserver-k8s-master 1/1 Running 0 36m
kube-controller-manager-k8s-master 1/1 Running 1 36m
kube-proxy-xtgv9 1/1 Running 0 36m
kube-scheduler-k8s-master 1/1 Running 1 36m

添加worker节点

将之前master节点初始化集群时,给出的信息分别在另外两台服务器上执行一遍

1
2
kubeadm join 10.10.10.60:6443 --token hqk9jq.bkgaawc0eiclcfcj \
--discovery-token-ca-cert-hash sha256:709f98f4cf9c4a2dfb712ae7772219b5bed93ca0f6d822cd76d63051e150b26b

等待拉去镜像后即可加入集群,期间可通过如下命令查看是否成功

1
2
3
4
5
6
7
8
# 查看集群所有节点
kubectl get nodes

root@k8s-master:~/k8s# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready control-plane 44m v1.36.5
k8s-worker-01 Ready <none> 5m8s v1.36.5
k8s-worker-02 Ready <none> 5m17s v1.36.5

刚加入集群应该是NotReady,下载镜像还需要时间。等下载完镜像后就可以成功启动

1
2
3
4
5
6
# 查看calico是否创建成功
root@k8s-master:~/k8s# kubectl get pods -n kube-system -o wide | grep calico
calico-kube-controllers-95cb87bb-m4mqt 1/1 Running 0 9m4s 192.168.235.193 k8s-master <none> <none>
calico-node-4jnt5 1/1 Running 0 9m4s 10.10.10.60 k8s-master <none> <none>
calico-node-tt2lp 0/1 Init:0/3 0 68s 10.10.10.61 k8s-worker-01 <none> <none>
calico-node-zhdvl 0/1 Init:0/3 0 77s 10.10.10.62 k8s-worker-02 <none> <none>

过一会再执行一次查看节点状态

1
2
3
4
5
root@k8s-master:~/k8s# kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-master Ready control-plane 42m v1.36.5 10.10.10.60 <none> Ubuntu 24.04.5 LTS 6.8.0-146-generic (amd64) containerd://2.2.1
k8s-worker-01 Ready <none> 3m5s v1.36.5 10.10.10.61 <none> Ubuntu 24.04.5 LTS 6.8.0-146-generic (amd64) containerd://2.2.1
k8s-worker-02 Ready <none> 3m14s v1.36.5 10.10.10.62 <none> Ubuntu 24.04.5 LTS 6.8.0-146-generic (amd64) containerd://2.2.1

此时就表示worker节点已经成功加入集群。一般不需要Master节点参与pod的调度(生产环境一定不要),对于测试环境可通过如下方式让master节点也参与调度(切记不可用于生产环境)

1
kubectl taint nodes k8s-master node-role.kubernetes.io/control-plane:NoSchedule-

配置StorageClass持久化存储

由于本次部署需要使用HPC,所以会采用Lustre,对于测试环境,可以使用NFS。

本次部署是实验环境,采用Rancher出品的local-path-provisioner

创建数据目录

1
mkdir -p /data/k8s-local-pv

下载配置

1
2
3
4
5
6
curl -fL -o local-path-storage.yaml \
https://ghproxy.net/https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.37/deploy/local-path-storage.yaml

# 或者
curl -fL -o local-path-storage.yaml \
https://cdn.jsdelivr.net/gh/rancher/local-path-provisioner@v0.0.37/deploy/local-path-storage.yaml

替换存储地址

1
2
3
4
5
6
7
8
# 查看默认地址
grep -n '/opt/local-path-provisioner' local-path-storage.yaml

# 替换成刚才创建到地址
sed -i 's#/opt/local-path-provisioner#/data/k8s-local-pv#g' local-path-storage.yaml

# 确认
grep -n -E 'k8s-local-pv|/opt/local-path-provisioner' local-path-storage.yaml

替换国内加速镜像

1
2
3
4
5
# 替换 provisioner 镜像
sed -i 's#image: rancher/local-path-provisioner:v0.0.37#image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/rancher/local-path-provisioner:v0.0.37#' local-path-storage.yaml

# 替换 busybox 镜像
sed -i 's#image: busybox#image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/library/busybox:latest#' local-path-storage.yaml

部署

1
2
3
4
5
6
7
8
9
10
# 部署
kubectl apply -f local-path-storage.yaml

# 查看
kubectl -n local-path-storage rollout status deployment/local-path-provisioner --timeout=180s

# 查看 pod状态
kubectl -n local-path-storage get pods

kubectl get storageclass

部署成功后,可以做一个测试:

创建一个测试PVC,观察它在被 Pod 使用前是否保持 Pending:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-local-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 128Mi
EOF

kubectl get pvc test-local-pvc

此时应该显示 Pending(因为还没有 Pod 使用它)。

然后创建一个 Pod 挂载这个 PVC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: test-local-pod
spec:
containers:
- name: test
image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/library/busybox:latest
command: ["sh", "-c", "echo hello > /data/test.txt && sleep 3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: test-local-pvc
EOF

kubectl get pvc test-local-pvc -w

Pod 创建后,PVC 应该从 Pending 变为 Bound。确认绑定后,可以查看数据是否写入:

1
kubectl exec test-local-pod -- cat /data/test.txt

如果输出 hello,说明存储读写正常。

清理测试资源

1
2
kubectl delete pod test-local-pod
kubectl delete pvc test-local-pvc

由于 RECLAIMPOLICY 是 Delete,删除 PVC 后对应的存储目录会被自动清理。

注意:Local Path Provisioner只需要在master节点部署即可,调度到其他节点时会自动创建

安装Metrics Server

配置Gateway API(暂时不用)

Insgress已经不在维护,需要换成官方推荐的Gateway API

安装Gateway API标准CRDs

1
curl -fL -o gateway-standard-install.yaml https://ghproxy.net/https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.2/standard-install.yaml

部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kubectl apply -f gateway-standard-install.yaml

# 验证CRDs是否安装成功
kubectl get crd | grep gateway.networking.k8s.io

# 输出如下即可
backendtlspolicies.gateway.networking.k8s.io 2026-10-05T01:18:43Z
gatewayclasses.gateway.networking.k8s.io 2026-10-05T01:18:43Z
gateways.gateway.networking.k8s.io 2026-10-05T01:18:43Z
grpcroutes.gateway.networking.k8s.io 2026-10-05T01:18:44Z
httproutes.gateway.networking.k8s.io 2026-10-05T01:18:44Z
listenersets.gateway.networking.k8s.io 2026-10-05T01:18:44Z
referencegrants.gateway.networking.k8s.io 2026-10-05T01:18:44Z
tcproutes.gateway.networking.k8s.io 2026-10-05T01:18:44Z
tlsroutes.gateway.networking.k8s.io 2026-10-05T01:18:44Z
udproutes.gateway.networking.k8s.io 2026-10-05T01:18:44Z

安装Envoy Gateway控制器

对于Gateway API的实现,还有另外几个:Istio、Cilium Gateway API、NGINX Gateway Fabric。目前

使用 Helm 从 OCI 仓库安装。注意,如果你的集群已经安装了 Gateway API v1.5 或更高版本的 CRDs,安装 Envoy Gateway 时必须加上 --skip-crds 参数。因为 Envoy Gateway 的 Helm Chart 可能自带旧版 CRDs,直接安装会尝试降级,被 Gateway API 的 ValidatingAdmissionPolicy 阻止。

1
kubectl apply -f https://ghproxy.net/https://github.com/envoyproxy/gateway/releases/download/v1.8.5/install.yaml

安装Cert-Manager

由于 Rancher 强制要求全局 HTTPS 访问,安装Rancher前需要安装证书管理器(Cert-Manager)

上一步已经添加了对应的仓库,只需要安装即可

1
2
3
4
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true

有如下提示即表示安装成功

1
cert-manager v1.21.2 has been deployed successfully!

安装ingress-nginx

1
2
curl -fL --retry 3 -o ingress-nginx.yaml \
https://ghproxy.net/https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.15.1/deploy/static/provider/baremetal/deploy.yaml

查看镜像地址

1
2
3
4
root@k8s-master:~/k8s# grep "image:" ingress-nginx.yaml
image: registry.k8s.io/ingress-nginx/controller:v1.15.1@sha256:594ceea76b01c592858f803f9ff4d2cb40542cae2060410b2c95f75907d659e1
image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.6.9@sha256:01038e7de14b78d702d2849c3aad72fd25903c4765af63cf16aa3398f5d5f2dd
image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.6.9@sha256:01038e7de14b78d702d2849c3aad72fd25903c4765af63cf16aa3398f5d5f2dd

批量修改镜像地址

1
2
3
4
# 替换 controller 镜像(包含摘要的完整地址)
sed -i 's#registry.k8s.io/ingress-nginx/controller:v1.15.1@sha256:594ceea76b01c592858f803f9ff4d2cb40542cae2060410b2c95f75907d659e1#swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/ingress-nginx/controller:v1.15.1#g' ingress-nginx.yaml
# 替换 webhook certgen 镜像(包含摘要的完整地址)
sed -i 's#registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.6.9@sha256:01038e7de14b78d702d2849c3aad72fd25903c4765af63cf16aa3398f5d5f2dd#swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.6.9#g' ingress-nginx.yaml

确认镜像地址已被修改

1
2
3
4
grep "image:" ingress-nginx.yaml
image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/ingress-nginx/controller:v1.15.1
image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.6.9
image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.6.9

部署和验证

1
2
3
4
5
6
7
8
9
10
11
12
kubectl apply -f ingress-nginx.yaml

kubectl get pods -n ingress-nginx -w

NAME READY STATUS RESTARTS AGE
ingress-nginx-controller-7d97db8899-gg7pj 1/1 Running 0 54s


kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller NodePort 10.107.178.45 <none> 80:30434/TCP,443:31442/TCP 106s
ingress-nginx-controller-admission ClusterIP 10.96.17.28 <none> 443/TCP 106s

安装MetalLB负载均衡

1
curl -fL --retry 3 -o metallb-frr-k8s.yaml \ https://ghproxy.net/https://raw.githubusercontent.com/metallb/metallb/v0.16.1/config/manifests/metallb-frr-k8s.yaml

替换国内源:

1
2
3
4
5
6
7
8
9
10
11
12
13
cat metallb-frr-k8s.yaml |grep "image:"
image: quay.io/metallb/controller:v0.16.1
image: quay.io/metallb/frr-k8s:v0.0.25
image: quay.io/metallb/frr-k8s:v0.0.25
image: quay.io/frrouting/frr:10.4.3
image: quay.io/frrouting/frr:10.4.3
image: quay.io/frrouting/frr:10.4.3
image: quay.io/frrouting/frr:10.4.3
image: quay.io/frrouting/frr:10.4.3
image: quay.io/metallb/frr-k8s:v0.0.25
image: quay.io/metallb/frr-k8s:v0.0.25
image: quay.io/metallb/frr-k8s:v0.0.25
image: quay.io/metallb/speaker:v0.16.1

替换

1
2
3
4
5
6
7
sed -i \
-e 's#quay.io/metallb/#harbor.dev.net.cn/k8s/quay.io/metallb/#g' \
metallb-frr-k8s.yaml

sed -i \
-e 's#quay.io/frrouting/#harbor.dev.net.cn/k8s/quay.io/frrouting/#g' \
metallb-frr-k8s.yaml

对于本地三节点的服务器,推荐使用原生模式(二层模式)

1
2
3
4
5
6
7
8
9
10
11
12
wget -O metallb-native.yaml \
https://ghproxy.net/https://raw.githubusercontent.com/metallb/metallb/v0.16.1/config/manifests/metallb-native.yaml


sed -i \
's#quay.io/metallb/#harbor.dev.net.cn/k8s/quay.io/metallb/#g' \
metallb-native.yaml

grep "image:" metallb-native.yaml

image: harbor.dev.net.cn/k8s/quay.io/metallb/controller:v0.16.1
image: harbor.dev.net.cn/k8s/quay.io/metallb/speaker:v0.16.1

部署

1
2
3
4
5
6
7
8
9
kubectl apply -f metallb-native.yaml

# 验证
root@k8s-master:~/k8s# kubectl get pods -n metallb-system -w
NAME READY STATUS RESTARTS AGE
controller-78b9cdcfb5-fvwn7 1/1 Running 0 2m35s
speaker-8cpc7 1/1 Running 0 112s
speaker-m8vv8 1/1 Running 0 103s
speaker-tfwgw 1/1 Running 0 97s

配置

确保10.10.10.70-10.10.10.79未被使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cat > metallb-config.yaml <<'EOF'
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: lan-pool
namespace: metallb-system
spec:
addresses:
- 10.10.10.70-10.10.10.79
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: lan
namespace: metallb-system
spec:
ipAddressPools:
- lan-pool
EOF

kubectl apply -f metallb-config.yaml

将ingress-nginx修改为LoadBalancer

1
2
3
kubectl patch svc ingress-nginx-controller \
-n ingress-nginx \
-p '{"spec":{"type":"LoadBalancer"}}'

查看分配地址

1
2
3
4
5
kubectl get svc ingress-nginx-controller -n ingress-nginx -w

root@k8s-master:~/k8s# kubectl get svc ingress-nginx-controller -n ingress-nginx -w
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller LoadBalancer 10.107.178.45 10.10.10.70 80:30434/TCP,443:31442/TCP 39m

后面就可以访问https://rancher.10.10.10.70.sslip.io这个地址了。

Rancher切换为metallb

由于我是先安装的Rancher,所以需要做这一步,如果是新安装的,直接指定到新的地址即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# -----------------------------------------------
# 这个命令不一定执行,请看情况,下面的命令无法执行时才需要
helm repo add jetstack https://charts.jetstack.io
helm repo update

helm upgrade --install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true

kubectl get pods -n cert-manager
kubectl get crd | grep cert-manager.io

certificaterequests.cert-manager.io 2026-10-05T05:14:12Z
certificates.cert-manager.io 2026-10-05T05:14:12Z
challenges.acme.cert-manager.io 2026-10-05T05:14:12Z
clusterissuers.cert-manager.io 2026-10-05T05:14:12Z
issuers.cert-manager.io 2026-10-05T05:14:13Z
orders.acme.cert-manager.io 2026-10-05T05:14:12Z

# ----------------------------------------------

helm upgrade rancher rancher-stable/rancher \
--namespace cattle-system \
--reuse-values \
--set hostname=rancher.10.10.10.70.sslip.io \
--set ingress.ingressClassName=nginx

安装Helm

下载

1
wget https://mirrors.huaweicloud.com/helm/v3.22.0/helm-v3.22.0-linux-amd64.tar.gz

安装

1
2
3
4
5
6
7
tar -zxvf helm-v3.22.0-linux-amd64.tar.gz

# 将其移动到/usr/local/bin/
sudo mv linux-amd64/helm /usr/local/bin/helm

# 清理不用的文件
rm -fr linux-amd64/

验证

1
2
helm version
version.BuildInfo{Version:"v3.22.0", GitCommit:"144ca65f8501953fa8b41cd1d37c7223051c85b7", GitTreeState:"clean", GoVersion:"go1.26.8"}

添加仓库

1
2
helm repo add rancher-stable https://releases.rancher.com/server-charts/stable
helm repo add jetstack https://charts.jetstack.io

验证

1
2
helm repo list
helm repo update

安装Rancher管理集群

创建Rancher专用命名空间

1
kubectl create namespace cattle-system

安装Rancher

1
2
3
4
5
6
7
8
9
helm install rancher rancher-stable/rancher \
--namespace cattle-system \
--set hostname=rancher.10.10.10.60.sslip.io \
--set bootstrapPassword=P@ssw0rd123 \
--set global.cattle.psp.enabled=false \
--set replicas=1 \
--set ingress.tls.source=rancher \
--set ingress.ingressClassName=nginx \
--set systemDefaultRegistry=docker.m.daocloud.io
  • --namespace 刚才创建的命名空间
  • hostname 访问Rancher的入口,没有域名的话可以使用开源的DNS映射服务sslip.io或nip.io,如果是线上环境,则填写域名即可。
  • bootstrapPassword 登录Rancher的密码。
  • replicas 副本,测试环境设置为1(只启动一个pod),生产环境需要设置为3
  • global.cattle.psp.enabled 必加项,显式关闭 PodSecurityPolicy (PSP),否则可能会提示API找不到的错误
  • ingress.tls.source 指定使用 Rancher 内置的 cert-manager 模式自动生成 TLS 证书
  • systemDefaultRegistry 镜像加速地址,否则会出现镜像拉取失败

以本教程为例:访问rancher的地址是:https://rancher.10.10.10.60.sslip.io,密码是P@ssw0rd123。


Update

如果安装了Metallb负载均衡,这里的hostname应当是负载均衡的地址:例如rancher.10.10.10.70.sslip.io


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
NAME: rancher
LAST DEPLOYED: Mon Oct 5 02:18:12 2026
NAMESPACE: cattle-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Rancher Server has been installed. Rancher may take several minutes to fully initialize.

Please standby while Certificates are being issued, Containers are started and the Ingress rule comes up.

Check out our docs at https://rancher.com/docs/

## First Time Login

If you provided your own bootstrap password during installation, browse to https://rancher.10.10.10.60.sslip.io to get started.
If this is the first time you installed Rancher, get started by running this command and clicking the URL it generates:

```
echo https://rancher.10.10.10.60.sslip.io/dashboard/?setup=$(kubectl get secret --namespace cattle-system bootstrap-secret -o go-template='{{.data.bootstrapPassword|base64decode}}')
```

To get just the bootstrap password on its own, run:

```
kubectl get secret --namespace cattle-system bootstrap-secret -o go-template='{{.data.bootstrapPassword|base64decode}}{{ "\n" }}'
```


Happy Containering!

如果失败,可以先卸载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 卸载rancher
helm uninstall rancher -n cattle-system

# 1. 删除 Deployment 控制器
kubectl delete deployment rancher -n cattle-system --force --grace-period=0

# 2. 强制删除所有卡在 ImagePullBackOff 的 Pod
kubectl delete pod -n cattle-system --all --force --grace-period=0

# 3. 删除残留的 Job(如果有)
kubectl delete job -n cattle-system --all --force --grace-period=0

# 手动拉取镜像
crictl pull docker.m.daocloud.io/rancher/rancher:v2.15.2
# 或者使用containerd的原生命令
ctr -n k8s.io i pull docker.m.daocloud.io/rancher/rancher:v2.15.2

# 如果能够拉取,则建议在所有服务器上拉取一份。或者在安装时指定systemDefaultRegistry

由于安装了ingress-nginx,所以需要将rancher的Ingress指定为 IngressClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看ingress的CLASS
kubectl get ingress -n cattle-system
NAME CLASS HOSTS ADDRESS PORTS AGE
rancher <none> rancher.10.10.10.60.sslip.io 80, 443 95m

kubectl patch ingress rancher \
-n cattle-system \
--type=merge \
-p '{"spec":{"ingressClassName":"nginx"}}'

# 继续验证
kubectl get ingress -n cattle-system
NAME CLASS HOSTS ADDRESS PORTS AGE
rancher nginx rancher.10.10.10.60.sslip.io 80, 443 98m
  • 如果是NodePort: 此时打开https://rancher.10.10.10.60.sslip.io:31442/

  • 如果是LoadBalancer,此时打开https://rancher.10.10.10.70.sslip.io

即可登录rancher。

参考资料

安装 :: MetalLB,Kubernetes 裸机负载均衡器

Installing Rancher :: SUSE® Rancher Manager

Release Local Path Provisioner v0.0.37 · rancher/local-path-provisioner

Release controller-v1.15.1 · kubernetes/ingress-nginx

Releases · envoyproxy/gateway

Release v1.6.2 · kubernetes-sigs/gateway-api

Kubernetes