在裸机内网环境中部署 Kubernetes 时,最容易卡住的通常不是 Pod 本身,而是入口流量、LoadBalancer 地址分配、证书、Service 与 Gateway 之间的关系。本文记录一套两节点开发环境的完整落地路径:从 kubeadm 初始化集群,到安装 Calico、MetalLB、Envoy Gateway,再到通过独立域名暴露 Kite 管理界面。

适用环境

本文示例基于以下环境:

  • Ubuntu Server 26.04
  • Kubernetes v1.36.x
  • kubeadm
  • containerd
  • Calico CNI
  • Envoy Gateway
  • MetalLB Layer 2
  • Harbor
  • Kite Kubernetes Web UI
  • 1 个控制平面节点和 1 个工作节点
  • 裸机内网,节点位于同一个二层网段

示例拓扑如下:

角色 主机名 IP
控制平面 k8s-master-1 192.168.110.200
工作节点 k8s-node-1 192.168.110.201
工作节点 k8s-node-2 192.168.110.202

MetalLB 地址池建议预留:

1
192.168.110.230-192.168.110.240

这些 IP 必须满足以下条件:

  • 不与任何主机冲突
  • 不在 DHCP 地址池内
  • 与 Kubernetes 节点处于同一个 VLAN 或二层网络
  • 已经和网络管理员确认可以使用

总体架构

开发环境推荐链路如下:

1
2
3
4
5
6
7
8
9
10
11
Calico
|
MetalLB Layer 2
|
Envoy Gateway
|
HTTPRoute / TLSRoute
|
Kubernetes Service
|
Deployment

组件选择:

  • Gateway API 使用标准 CRD 通道。
  • Gateway Controller 使用 Envoy Gateway。
  • 裸机 LoadBalancer 使用 MetalLB Layer 2。
  • TLS 在开发环境使用自签名证书,测试和生产环境使用 cert-manager 或企业 CA。
  • Kite 使用单独域名访问,不建议挂在 /kite 子路径下。

当前场景不建议仅为了 Gateway API 更换 Calico。只有当项目计划同时引入 eBPF、Cilium NetworkPolicy、Cilium Service LB 和 Cilium Gateway API 时,再考虑整体迁移到 Cilium。

Harbor 前置要求

在 Harbor 中创建项目:

1
项目名:k8s

建议将项目设为 Public,但通过防火墙、路由或访问控制限制 Harbor 仅允许内网访问。 这是为了让 kubeadm 启动的静态 Pod 能在节点重启后自动拉取核心镜像,不依赖 Kubernetes 内的 imagePullSecrets

先在三台节点确认 Harbor 证书、DNS 和网络正常:

1
2
curl -sS -o /dev/null -w '%{http_code}\n' \
https://harbor.meatball.org.cn/v2/

返回 200401 都说明 TLS 与网络正常。

若返回 x509: certificate signed by unknown authority,说明节点本身尚未信任 Harbor CA。将 CA 根证书安装到三台节点:

1
2
3
4
5
install -Dm0644 /path/to/harbor-ca.crt \
/usr/local/share/ca-certificates/harbor-meatball.crt

update-ca-certificates
systemctl restart containerd

不要配置 insecure_skip_verify


节点规划

主机名 IP 角色
k8s-master-1 192.168.110.200 控制平面
k8s-node-1 192.168.110.201 工作节点
k8s-node-2 192.168.110.202 工作节点

分别在对应节点设置变量:

1
2
3
# Master
export NODE_NAME=k8s-master-1
export NODE_IP=192.168.110.200
1
2
3
# Node 1
export NODE_NAME=k8s-node-1
export NODE_IP=192.168.110.201
1
2
3
# Node 2
export NODE_NAME=k8s-node-2
export NODE_IP=192.168.110.202

三台节点初始化

三台节点都执行:

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
set -euo pipefail

hostnamectl set-hostname "${NODE_NAME}"

grep -qF '192.168.110.200 k8s-master-1' /etc/hosts || \
echo '192.168.110.200 k8s-master-1' >> /etc/hosts

grep -qF '192.168.110.201 k8s-node-1' /etc/hosts || \
echo '192.168.110.201 k8s-node-1' >> /etc/hosts

grep -qF '192.168.110.202 k8s-node-2' /etc/hosts || \
echo '192.168.110.202 k8s-node-2' >> /etc/hosts

timedatectl set-ntp true

swapoff -a
sed -ri '/\sswap\s/s/^/#/' /etc/fstab

systemctl disable --now ufw 2>/dev/null || true

modprobe overlay
modprobe br_netfilter

cat >/etc/modules-load.d/kubernetes.conf <<'EOF'
overlay
br_netfilter
EOF

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

sysctl --system

验证:

1
2
3
4
5
hostnamectl --static
swapon --show
lsmod | grep -E 'overlay|br_netfilter'
sysctl net.ipv4.ip_forward
ip -4 addr show ens18

swapon --show 必须没有输出。


安装并配置 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
apt-get update

apt-get install -y \
ca-certificates \
curl \
gpg \
apt-transport-https \
containerd \
conntrack \
socat \
ipset \
iptables \
iproute2 \
ethtool \
ebtables \
ipvsadm \
cri-tools

mkdir -p /etc/containerd

containerd config default >/etc/containerd/config.toml

sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' \
/etc/containerd/config.toml

systemctl enable --now containerd
systemctl restart containerd

确认:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
containerd --version

grep -n -A5 -B2 'SystemdCgroup' \
/etc/containerd/config.toml

grep -n 'disabled_plugins' /etc/containerd/config.toml

systemctl is-active containerd

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 info >/dev/null && echo 'containerd CRI 正常'

SystemdCgroup = true 是必须项;同时确认 disabled_plugins 中不包含 cri。(kubernetes.io)


安装 Kubernetes v1.36.3

三台节点执行:

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
export K8S_VERSION=v1.36.3

mkdir -p -m 0755 /etc/apt/keyrings

curl -fsSL \
https://pkgs.k8s.io/core:/stable:/v1.36/deb/Release.key \
| gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.36/deb/ /' \
>/etc/apt/sources.list.d/kubernetes.list

apt-get update

K8S_DEB_VERSION="$(apt-cache madison kubeadm \
| awk '$3 ~ /^1\.36\.3-/ {print $3; exit}')"

test -n "${K8S_DEB_VERSION}"

echo "安装 Debian 包版本:${K8S_DEB_VERSION}"

apt-get install -y \
kubelet="${K8S_DEB_VERSION}" \
kubeadm="${K8S_DEB_VERSION}" \
kubectl="${K8S_DEB_VERSION}"

apt-mark hold kubelet kubeadm kubectl

systemctl enable kubelet

验证:

1
2
3
4
kubeadm version
kubelet --version
kubectl version --client
apt-mark showhold

同步 Kubernetes 与 Calico 镜像到 Harbor

只需在一台可访问外网和 Harbor 的机器执行,建议使用 k8s-master-1

1
2
3
4
5
6
7
8
9
10
11
export HARBOR=harbor.meatball.org.cn
export HARBOR_PROJECT=k8s
export K8S_VERSION=v1.36.3
export K8S_REPO="${HARBOR}/${HARBOR_PROJECT}/kubernetes"
export CALICO_REPO="${HARBOR}/${HARBOR_PROJECT}"
export SOURCE_REPO="registry.aliyuncs.com/google_containers"
export TARGET_REPO="harbor.meatball.org.cn/k8s" # 您的私有仓库项目路径

apt-get install -y skopeo

skopeo login "${HARBOR}"

输入 Harbor 账号或 Robot Account 的用户名和 Token。

Kubernetes 核心镜像

1
2
3
4
5
6
7
8
mkdir -p /root/kubeadm

kubeadm config images list \
--kubernetes-version "${K8S_VERSION}" \
--image-repository "${SOURCE_REPO}" \
| tee /root/kubeadm/kubernetes-images.txt

cat /root/kubeadm/kubernetes-images.txt

同步:

1
2
3
4
5
6
7
8
9
10
11
while read -r SOURCE_IMAGE; do
# 去掉源仓库前缀,得到类似 "kube-apiserver:v1.36.3" 或 "pause:3.10.1"
RELATIVE_IMAGE="${SOURCE_IMAGE#${SOURCE_REPO}/}"
# 目标镜像:Harbor/k8s/kubernetes/<镜像名>
TARGET_IMAGE="${K8S_REPO}/${RELATIVE_IMAGE}"

echo "同步:${SOURCE_IMAGE} -> ${TARGET_IMAGE}"
skopeo copy --all \
"docker://${SOURCE_IMAGE}" \
"docker://${TARGET_IMAGE}"
done </root/kubeadm/kubernetes-images.txt

Calico 镜像

Calico 官方的 v3.32.1 私有仓库清单包含以下镜像;本文会只启用基础网络组件,但建议全部同步,便于后续启用 Calico API、Whisker 或网关功能。(docs.tigera.io)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cat >/root/kubeadm/calico-images.txt <<'EOF'
quay.io/tigera/operator:v1.42.3
quay.io/calico/node:v3.32.1
quay.io/calico/cni:v3.32.1
quay.io/calico/typha:v3.32.1
quay.io/calico/csi:v3.32.1
quay.io/calico/node-driver-registrar:v3.32.1
quay.io/calico/apiserver:v3.32.1
quay.io/calico/kube-controllers:v3.32.1
quay.io/calico/envoy-gateway:v3.32.1
quay.io/calico/envoy-proxy:v3.32.1
quay.io/calico/envoy-ratelimit:v3.32.1
quay.io/calico/dikastes:v3.32.1
quay.io/calico/pod2daemon-flexvol:v3.32.1
quay.io/calico/key-cert-provisioner:v3.32.1
quay.io/calico/goldmane:v3.32.1
quay.io/calico/whisker:v3.32.1
quay.io/calico/whisker-backend:v3.32.1
EOF
1
2
3
4
5
6
7
8
9
10
while read -r SOURCE_IMAGE; do
RELATIVE_IMAGE="${SOURCE_IMAGE#quay.io/}"
TARGET_IMAGE="${CALICO_REPO}/${RELATIVE_IMAGE}"

echo "同步:${SOURCE_IMAGE} -> ${TARGET_IMAGE}"

skopeo copy --all \
"docker://${SOURCE_IMAGE}" \
"docker://${TARGET_IMAGE}"
done </root/kubeadm/calico-images.txt

验证 Harbor 镜像:

1
2
3
4
5
6
7
skopeo inspect \
"docker://${K8S_REPO}/kube-apiserver:$(kubeadm config images list --kubernetes-version "${K8S_VERSION}" | awk -F: '/kube-apiserver/ {print $NF}')" \
--format '{{.Name}}:{{.Tag}}'

skopeo inspect \
"docker://${CALICO_REPO}/calico/node:v3.32.1" \
--format '{{.Name}}:{{.Tag}}'

在三台节点分别验证 containerd 能从 Harbor 拉取:

1
2
3
4
5
6
7
8
9
10
export HARBOR=harbor.meatball.org.cn
export K8S_REPO="${HARBOR}/k8s/kubernetes"
export K8S_VERSION=v1.36.3

crictl pull "$(
kubeadm config images list \
--kubernetes-version "${K8S_VERSION}" \
--image-repository "${K8S_REPO}" \
| head -n 1
)"

初始化控制平面

只在 k8s-master-1 执行:

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
export HARBOR=harbor.meatball.org.cn
export K8S_VERSION=v1.36.3
export K8S_REPO="${HARBOR}/k8s/kubernetes"

mkdir -p /root/kubeadm

cat >/root/kubeadm/kubeadm-config.yaml <<EOF
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 192.168.110.200
bindPort: 6443
nodeRegistration:
name: k8s-master-1
criSocket: unix:///run/containerd/containerd.sock
kubeletExtraArgs:
- name: node-ip
value: 192.168.110.200
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: ${K8S_VERSION}
imageRepository: ${K8S_REPO}
controlPlaneEndpoint: 192.168.110.200:6443
networking:
podSubnet: 192.168.0.0/16
serviceSubnet: 10.96.0.0/12
dnsDomain: cluster.local
apiServer:
certSANs:
- 192.168.110.200
- k8s-master-1
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
EOF

修改 sandbox_image 为 Harbor 地址
编辑 /etc/containerd/config.toml,找到 sandbox_image 字段,改为:

1
2
[plugins.'io.containerd.cri.v1.images'.pinned_images]
sandbox = 'harbor.meatball.org.cn/k8s/kubernetes/pause:3.10.2'

重启 containerd

1
2
systemctl daemon-reload
systemctl restart containerd

验证配置是否生效

1
containerd config dump | grep -A5 pinned_images

应显示 Harbor 地址。

验证配置与 Harbor 镜像地址:

1
2
3
4
5
kubeadm config validate \
--config /root/kubeadm/kubeadm-config.yaml

kubeadm config images list \
--config /root/kubeadm/kubeadm-config.yaml

预拉镜像:

1
2
3
4
kubeadm config images pull \
--config /root/kubeadm/kubeadm-config.yaml

crictl images | grep harbor.meatball.org.cn

初始化:

1
2
3
4
kubeadm init \
--config /root/kubeadm/kubeadm-config.yaml \
--upload-certs \
| tee /root/kubeadm/kubeadm-init.log

配置 kubectl:

1
2
3
4
5
6
7
8
9
10
mkdir -p "${HOME}/.kube"

cp -f /etc/kubernetes/admin.conf "${HOME}/.kube/config"

chown "$(id -u):$(id -g)" "${HOME}/.kube/config"

export KUBECONFIG="${HOME}/.kube/config"

kubectl get nodes
kubectl get pods -n kube-system

此时 k8s-master-1 处于 NotReady 是正常的,因为尚未安装 CNI。


加入两个工作节点

在 master 生成加入命令:

1
2
3
4
5
6
7
8
9
10
11
12
kubeadm token create \
--ttl 24h \
--print-join-command \
| tee /root/kubeadm/join-worker.sh

sed -i \
's#$# --cri-socket unix:///run/containerd/containerd.sock#' \
/root/kubeadm/join-worker.sh

chmod 700 /root/kubeadm/join-worker.sh

cat /root/kubeadm/join-worker.sh

将输出的完整命令分别在 k8s-node-1k8s-node-2 执行。

回到 master 验证:

1
kubectl get nodes -o wide

安装 Calico

Calico 要使用私有仓库,必须:

  1. 将 Operator Manifest 中的 quay.io 改为 Harbor;
  2. Installation.spec.registry 配置 Harbor 镜像前缀。(docs.tigera.io)

只在 master 执行:

1
2
3
4
5
6
7
8
9
10
cd /root/kubeadm

curl -fsSLo tigera-operator.yaml \
https://raw.githubusercontent.com/projectcalico/calico/v3.32.1/manifests/tigera-operator.yaml

sed -i \
's#quay.io/#harbor.meatball.org.cn/k8s/#g' \
tigera-operator.yaml

grep -n 'image:' tigera-operator.yaml

输出必须包含:

1
harbor.meatball.org.cn/k8s/tigera/operator:v1.42.3

创建 Calico 网络配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cat >/root/kubeadm/calico-installation.yaml <<'EOF'
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
name: default
spec:
variant: Calico
registry: harbor.meatball.org.cn/k8s
calicoNetwork:
ipPools:
- name: default-ipv4-ippool
blockSize: 26
cidr: 192.168.0.0/16
encapsulation: VXLAN
natOutgoing: Enabled
nodeSelector: all()
EOF

安装:

1
2
3
4
5
6
7
kubectl apply -f /root/kubeadm/tigera-operator.yaml

kubectl rollout status deployment/tigera-operator \
-n tigera-operator \
--timeout=5m

kubectl apply -f /root/kubeadm/calico-installation.yaml

观察状态:

1
kubectl get pods -A -w

另开一个终端执行:

1
2
3
4
5
6
7
8
kubectl get tigerastatus

kubectl get nodes -o wide

kubectl get pods -n calico-system

kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{" "}{.metadata.name}{" "}{.spec.containers[*].image}{"\n"}{end}' \
| grep harbor.meatball.org.cn

最终三台节点应为 Ready


集群网络验证

同步测试镜像到 Harbor:

1
2
3
4
5
6
7
8
9
10
11
skopeo copy --all \
docker://swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/e2e-test-images/jessie-dnsutils:1.3 \
docker://harbor.meatball.org.cn/k8s/test/dnsutils:1.3

skopeo copy --all \
docker://swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/nginx:1.26.3-alpine \
docker://harbor.meatball.org.cn/k8s/library/nginx:1.26.3-alpine

skopeo copy --all \
docker://swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/curlimages/curl:8.21.0 \
docker://harbor.meatball.org.cn/k8s/curlimages/curl:8.21.0

DNS 验证:

1
2
3
4
5
kubectl run dns-test \
--image=harbor.meatball.org.cn/k8s/test/dnsutils:1.3 \
--restart=Never \
--rm -it \
-- nslookup kubernetes.default

跨工作节点的服务验证:

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
47
48
49
50
51
cat >/root/kubeadm/network-test.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-test
spec:
replicas: 2
selector:
matchLabels:
app: nginx-test
template:
metadata:
labels:
app: nginx-test
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: nginx-test
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: harbor.meatball.org.cn/k8s/library/nginx:1.26.3-alpine
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-test
spec:
selector:
app: nginx-test
ports:
- port: 80
targetPort: 80
EOF

kubectl apply -f /root/kubeadm/network-test.yaml

kubectl rollout status deployment/nginx-test --timeout=5m

kubectl get pods -l app=nginx-test -o wide

kubectl run curl-test \
--image=harbor.meatball.org.cn/k8s/curlimages/curl:8.21.0 \
--restart=Never \
--rm -it \
-- curl -fsS http://nginx-test.default.svc.cluster.local

清理测试资源:

1
kubectl delete -f /root/kubeadm/network-test.yaml

Helm安装

1
2
3
4
export POD_NAME=$(kubectl get pods --namespace kube-system -l "app.kubernetes.io/name=kite,app.kubernetes.io/instance=kite" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace kube-system $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace kube-system port-forward $POD_NAME 8080:$CONTAINER_PORT --address 0.0.0.0

MetalLB v0.16.1 离线部署(适配私有 Harbor harbor.meatball.org.cn/k8s)

集群环境:k8s v1.36,containerd‑2.x,Calico;Harbor 项目k8s;helm>=3.8;使用 FRR‑K8s(helm chart 默认模式,官方推荐)。
MetalLB 组件镜像来源:quay.io/metallb/*,helm chart 是 helm repo 类型,先下载 chart 包,再同步业务镜像到私有 harbor。

🔴前置条件(必须先做,IPVS 模式必开 strictARP)

你的 k8s 集群 kube‑proxy 如果是 ipvs 模式,必须开启 strictARP=true,否则 MetalLB L2 模式 ARP 异常,LoadBalancer 无法正常工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
#预览修改,看diff输出
kubectl get configmap kube-proxy -n kube-system -o yaml | \
sed -e "s/strictARP: false/strictARP: true/" | \
kubectl diff -f - -n kube-system

#确认diff只修改这一行后执行应用
kubectl get configmap kube-proxy -n kube-system -o yaml | \
sed -e "s/strictARP: false/strictARP: true/" | \
kubectl apply -f - -n kube-system

#重启kube‑proxy daemonset加载配置
kubectl rollout restart daemonset kube-proxy -n kube-system
kubectl get pods -n kube-system -l k8s-app=kube-proxy -w

iptables 模式集群可以跳过上面 strictARP 步骤。

下载 metallb helm chart,离线保存 tgz

1
2
3
4
5
6
helm repo add metallb https://metallb.github.io/metallb
helm repo update

#下载v0.16.1 chart到本地,不解压
helm pull metallb/metallb --version 0.16.1 --untar=false
ls -lh metallb-0.16.1.tgz

查看 chart 需要哪些容器镜像

1
helm show values metallb-0.16.1.tgz

v0.16.1 FRR‑K8s 模式用到镜像清单:

  • quay.io/metallb/controller:v0.16.1
  • quay.io/metallb/speaker:v0.16.1
  • quay.io/frrouting/frr:9.1.0

使用 skopeo 同步全部业务镜像到你的 harbor:

使用 DaoCloud quay 代理规避 quay.io 匿名限流报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# controller
skopeo copy --all \
docker://quay.m.daocloud.io/metallb/controller:v0.16.1 \
docker://harbor.meatball.org.cn/k8s/metallb/controller:v0.16.1

# speaker
skopeo copy --all \
docker://quay.m.daocloud.io/metallb/speaker:v0.16.1 \
docker://harbor.meatball.org.cn/k8s/metallb/speaker:v0.16.1

# frr init镜像 10.4.3
skopeo copy --all \
docker://quay.m.daocloud.io/frrouting/frr:10.4.3 \
docker://harbor.meatball.org.cn/k8s/frrouting/frr:10.4.3

# frr‑k8s reloader init镜像
skopeo copy --all \
docker://quay.m.daocloud.io/metallb/frr-k8s:v0.0.25 \
docker://harbor.meatball.org.cn/k8s/metallb/frr-k8s:v0.0.25

校验镜像同步完成:

1
2
3
skopeo inspect docker://harbor.meatball.org.cn/k8s/metallb/controller:v0.16.1
skopeo inspect docker://harbor.meatball.org.cn/k8s/metallb/speaker:v0.16.1
skopeo inspect docker://harbor.meatball.org.cn/k8s/frrouting/frr:9.1.0

编写 values‑metallb.yaml,替换全部镜像仓库指向私有 harbor

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
cat >/root/kubeadm/values-metallb.yaml <<'EOF'
controller:
image:
repository: harbor.meatball.org.cn/k8s/metallb/controller
tag: v0.16.1
speaker:
image:
repository: harbor.meatball.org.cn/k8s/metallb/speaker
tag: v0.16.1
frrk8s:
image:
repository: harbor.meatball.org.cn/k8s/frrouting/frr
tag: 10.4.3
# ---- 新增下面两行,修改init容器cp‑reloader镜像
reloaderImage:
repository: harbor.meatball.org.cn/k8s/metallb/frr-k8s
tag: v0.0.25

namespaceLabels:
podSecurityContext:
runAsNonRoot: true
runAsUser: 65534
fsGroup: 65534
namespaceLabels:
pod-security.kubernetes.io/enforce: privileged
pod-security.kubernetes.io/audit: privileged
pod-security.kubernetes.io/warn: privileged
EOF

helm 离线安装 metallb

–create‑namespace 自动创建metallb‑system命名空间

1
2
3
4
helm install metallb ./metallb-0.16.1.tgz \
-n metallb-system \
--create-namespace \
-f values-metallb.yaml

校验 pod 启动状态

1
kubectl get pods -n metallb-system -w

预期 pod:

  • metallb‑controller‑xxxx (1/1 Running)
  • metallb‑speaker‑xxxx(每个节点一个 pod,1/1 Running)
  • frrk8s‑frr‑xxxx(每个节点一个 frr pod,1/1 Running)

简单验证 LoadBalancer 功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Service
metadata:
name: lb-demo
spec:
type: LoadBalancer
selector:
app: nginx-test
ports:
- port: 80
targetPort: 80
EOF

kubectl get svc lb-demo

如果 EXTERNAL‑IP 拿到192.168.110.21x地址,代表 MetalLB 工作正常。

卸载命令(后续清理)

1
2
helm uninstall metallb -n metallb-system
kubectl delete ns metallb-system

kite安装

前置:同步 Kite OCI‑Helm Chart 到私有 Harbor

1
2
3
4
5
6
7
8
# 拉取原始chart
helm pull oci://ghcr.io/kite-org/charts/kite --untar=false

# 登录harbor helm registry
helm registry login harbor.meatball.org.cn

# 推送chart到harbor k8s项目
helm push kite-*.tgz oci://harbor.meatball.org.cn/k8s

skopeo 同步 kite 镜像到 harbor

1
2
3
skopeo copy --all \
docker://swr.cn-north-4.myhuaweicloud.com/ddn-k8s/ghcr.io/kite-org/kite:v0.15.0 \
docker://harbor.meatball.org.cn/k8s/kite:v0.15.0

更新 helm release,替换镜像仓库

  • 当前镜像:ghcr.io/kite‑org/kite:v0.15.0
  • 目标镜像:harbor.meatball.org.cn/k8s/kite:v0.15.0
    helm upgrade(直接覆盖 image.repository)
1
2
3
helm install kite oci://harbor.meatball.org.cn/k8s/kite \
-n kube-system \
--set image.repository=harbor.meatball.org.cn/k8s/kite

部署校验

1
2
3
4
5
6
7
8
#查看helm release状态
helm list -n kube-system

#校验deployment镜像是否已经切到私有harbor
kubectl get deploy kite -n kube-system -o jsonpath='{.spec.template.spec.containers[0].image}'

#观察pod启动,等待1/1 Running
kubectl get pods -n kube-system -w

MetalLB LoadBalancer 内网 VIP 访问

前提:metallb 的 speaker/controller 全部 Pod 处于 Running,IPAddressPool、L2Advertisement CR 已经部署完成

1
2
3
4
5
#修改service类型为LoadBalancer
kubectl patch svc kite -n kube-system -p '{"spec":{"type":"LoadBalancer","ports":[{"port":8080,"targetPort":"http"}]}}'

#等待metallb分配EXTERNAL‑IP内网VIP
kubectl get svc kite -n kube-system -w

拿到EXTERNAL‑IP例如192.168.110.210,局域网浏览器直接访问:http://192.168.110.210:8080

注意:MetalLB L2 只能分配内网网段 IP;互联网外网访问需要路由器做 DNAT 端口转发。


💡实验环境完成后,测试/生产环境建议升级为至少 3 个控制平面节点并使用负载均衡 VIP;同时增加 etcd 快照备份、监控、Ingress、存储和镜像漏洞扫描。


Nacos部署

Nacos 版本:v3.2.3(官方稳定版)
用到镜像:

  1. docker.io/nacos/nacos-server:v3.2.3 主服务镜像
  2. docker.io/nacos/nacos‑peer‑finder‑plugin:1.1 集群选主插件镜像

两种部署方式:①helm chart 方式(推荐,同 kite 操作逻辑);②原生 yaml 方式。

克隆官方 nacos‑k8s 仓库

1
2
3
4
5
git clone https://github.com/nacos-group/nacos-k8s.git
cd nacos-k8s/helm
# 本地打包chart为tgz
helm package .
ls -lh nacos-*.tgz

推送到 harbor OCI 仓库

1
2
helm registry login harbor.meatball.org.cn
helm push nacos-*.tgz oci://harbor.meatball.org.cn/k8s

skopeo 同步 2 个容器镜像(之前命令不变)

1
2
3
4
5
6
7
skopeo copy --all \
docker://docker.m.daocloud.io/nacos/nacos-server:v3.2.3 \
docker://harbor.meatball.org.cn/k8s/nacos/nacos-server:v3.2.3

skopeo copy --all \
docker://docker.m.daocloud.io/nacos/nacos-peer-finder-plugin:1.1 \
docker://harbor.meatball.org.cn/k8s/nacos/nacos-peer-finder-plugin:1.1

生成values-nacos.yaml

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
cat >/root/kubeadm/values-nacos.yaml <<'EOF'
global:
mode: standalone

nacos:
replicaCount: 1
image:
repository: harbor.meatball.org.cn/k8s/nacos/nacos-server
tag: v3.2.3
pullPolicy: IfNotPresent
plugin:
enable: true
image:
repository: harbor.meatball.org.cn/k8s/nacos/nacos-peer-finder-plugin
tag: 1.1
pullPolicy: IfNotPresent

authEnable: true
authToken: "TXlTdXBlclNlY3JldE5hY29zS2V5MjAyNmFiY2RlZmdoaWprbG1ub3BxcnN0dXY="
authIdentityKey: "nacos"
authIdentityValue: "nacos"
admin:
username: nacos
password: "nacos123456"

service:
type: LoadBalancer
loadBalancerIP: 192.168.110.230
ports:
http:
port: 8848
targetPort: 8848
EOF

从私有 harbor OCI 安装 nacos

1
2
3
helm install nacos oci://harbor.meatball.org.cn/k8s/nacos \
-n nacos --create-namespace \
-f values.yaml

校验:

1
2
3
helm list -n nacos
kubectl get statefulset nacos -n nacos -o jsonpath='{.spec.template.spec.containers[0].image}'
kubectl get pods -n nacos -w

查看分配的IP

1
kubectl get svc nacos -n nacos