使用 cri-docker 搭建 Kubernetes 集群完整教程(v1.36.3)

前言

本文基于 cri-docker(Docker 作为 CRI 容器运行时) 搭建一套 Kubernetes v1.36.3 集群,节点规划为 1 个 Master + 2 个 Node ,网络插件采用 Calico(关闭 IPIP/VXLAN,使用 BGP 模式)。操作命令来自真实实验环境,按步骤复制粘贴即可,适合初学 Kubernetes 的同学照着做。

小提示 :cri-docker 与 containerd 是两种 CRI 容器运行时,二选一安装即可,不要同时安装。本文主体走 cri-docker 路线(4.2.1),4.2.2 同时附上 containerd 方式供参考。


一、环境准备与节点规划

开始前先用 VMware/VirtualBox 克隆三台 CentOS/RHEL 虚拟机 ,分别作为 Master、Node01、Node02。三台主机的 IP 与主机名规划如下(文中以 192.168.194.x 网段为例,请按你实际网段修改):

角色 主机名 计划 IP 别名
Master k8s-master01 192.168.194.11 m1
Node01 k8s-node01 192.168.194.12 n1
Node02 k8s-node02 192.168.194.13 n2
Harbor hb.reg.com 192.168.194.20 harbor

文中用到 m1n1n2harbor 这些别名,方便后续 scp 拷贝文件时短命令直接使用。


二、集群准备(Master / Node01 / Node02)

2.1 配置 Master 节点

克隆一台 k8s-1 虚拟机用作 Master,按下面步骤配置。

2.1.1 修改主机名
bash 复制代码
[root@localhost ~]# hostnamectl hostname k8s-master01 && bash
2.1.2 修改网卡

先查看当前网卡信息:

bash 复制代码
[root@k8s-master01 ~]# ls /etc/NetworkManager/system-connections/
ens160.nmconnection
[root@k8s-master01 ~]# ip a
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:0c:29:eb:22:8e brd ff:ff:ff:ff:ff:ff

修改 ens160 的 IP 为静态 IP:

bash 复制代码
[root@k8s-master01 ~]# nmcli c m ens160 ipv4.method manual ipv4.addresses 192.168.194.11/24 ipv4.dns "223.5.5.5 8.8.8.8" ipv4.gateway 192.168.10.2 connection.autoconnect yes
[root@k8s-master01 ~]# nmcli c up ens160
Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/4)

⚠️ 网关地址(gateway)请按你实际路由器的地址修改,文中 192.168.10.2 仅为示例。

2.1.3 配置主机映射
bash 复制代码
[root@k8s-master01 ~]# cat > /etc/hosts <<EOF
192.168.194.11 k8s-master01 m1
192.168.194.12 k8s-node01 n1
192.168.194.13 k8s-node02 n2
192.168.194.20 hb.reg.com harbor
EOF

配置完成后,给 Master 做个快照(防止后续操作失误,方便回滚)。

2.2 配置 Node01 节点

克隆一台 k8s-2 虚拟机用作 Node01。

2.2.1 修改主机名
bash 复制代码
[root@localhost ~]# hostnamectl hostname k8s-node01 && bash
2.2.2 修改网卡
bash 复制代码
[root@k8s-node01 ~]# ls /etc/NetworkManager/system-connections/
ens160.nmconnection
[root@k8s-node01 ~]# ip a
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:0c:29:eb:22:8e brd ff:ff:ff:ff:ff:ff
    altname enp3s0
    inet 192.168.10.134/24 brd 192.168.10.255 scope global dynamic noprefixroute ens160

修改 ens160 的 IP:

bash 复制代码
[root@k8s-node01 ~]# nmcli c m ens160 ipv4.method manual ipv4.addresses 192.168.194.12/24 ipv4.dns "223.5.5.5 8.8.8.8" ipv4.gateway 192.168.194.2 connection.autoconnect yes
[root@k8s-node01 ~]# nmcli c up ens160
Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/4)
2.2.3 配置主机映射
bash 复制代码
[root@k8s-node01 ~]# cat > /etc/hosts <<EOF
192.168.194.11 k8s-master01 m1
192.168.194.12 k8s-node01 n1
192.168.194.13 k8s-node02 n2
192.168.194.20 hb.reg.com harbor
EOF

配置好后,给 Node01 做一个快照

2.3 配置 Node02 节点

克隆一台 k8s-3 虚拟机用作 Node02。

2.3.1 修改主机名
bash 复制代码
[root@192 ~]# hostnamectl hostname k8s-node02 && bash
2.3.2 修改网卡
bash 复制代码
[root@k8s-node02 ~]# ls /etc/NetworkManager/system-connections/
ens160.nmconnection
[root@k8s-node02 ~]# ip a
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:0c:29:eb:22:8e brd ff:ff:ff:ff:ff:ff
    altname enp3s0
    inet 192.168.10.135/24 brd 192.168.10.255 scope global dynamic noprefixroute ens160

修改 ens160 的 IP(注意此处是单数 address,写法略有差异,但效果一致):

bash 复制代码
[root@k8s-node02 ~]# nmcli c m ens160 ipv4.method manual ipv4.address 192.168.194.13/24 ipv4.dns "223.5.5.5 8.8.8.8" ipv4.gateway 192.168.194.2 connection.autoconnect yes
[root@k8s-node02 ~]# nmcli c up ens160
Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/4)
2.3.3 配置主机映射
bash 复制代码
[root@k8s-node02 ~]# cat > /etc/hosts <<EOF
192.168.194.11 k8s-master01 m1
192.168.194.12 k8s-node01 n1
192.168.194.13 k8s-node02 n2
192.168.194.20 hb.reg.com harbor
EOF

配置好后,给 Node02 做一个快照


三、搭建 Kubernetes 集群

⚠️ 本节 3.1、3.2、3.3 的安装操作需要在 k8s-1、k8s-2、k8s-3 三台机器上全部执行(除非特别标注只作用于 Master)。

3.1 安装 Docker

3.1.1 添加 docker 源
bash 复制代码
dnf install yum-utils -y
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/rhel/docker-ce.repo
3.1.2 安装 docker
bash 复制代码
dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
3.1.3 配置 docker
bash 复制代码
cat > /etc/docker/daemon.json <<EOF
{
    "default-ipc-mode": "shareable",
    "data-root": "/data/docker",
    "exec-opts": ["native.cgroupdriver=systemd"],
    "log-driver": "json-file",
    "log-opts": {
        "max-size": "100m",
        "max-file": "50"
    },
    "insecure-registries": ["https://hb.reg.com"],
    "registry-mirrors": [
        "https://docker.m.daocloud.io",
        "https://docker.ims.run"
    ]
}
EOF

解释:data-root 指定 Docker 数据目录;exec-opts 设置 cgroup 驱动为 systemd(与 kubelet 保持一致,避免 cgroupfs 冲突);insecure-registries 用于访问自建 Harbor 私仓;registry-mirrors 为国内镜像加速。

3.1.4 启动 docker
bash 复制代码
systemctl enable --now docker
3.1.5 验证 docker
bash 复制代码
[root@localhost ~]# docker version
Client: Docker Engine - Community
 Version:           29.6.2
 API version:       1.55
 Go version:        go1.26.5
 Git commit:        dfc4efb
 Built:             Thu Jul 16 16:15:33 2026
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          29.6.2
  API version:      1.55 (minimum version 1.40)
  Go version:       go1.26.5
  Git commit:       3d80467
  Built:            Thu Jul 16 16:11:37 2026
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v2.2.6
  GitCommit:        11ce9d5f3c68c941867e82890e93e815c1304f1b
 runc:
  Version:          1.3.6
  GitCommit:        v1.3.6-0-g491b69ba
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

看到 Server: 那段能正常输出,说明 Docker 已成功启动。

3.2 安装 CRI 容器运行时

CRI 为容器运行时接口,常见有 cri-dockercontainerd 两种,二选一安装即可,不要都装。这里给出两条路线。

3.2.1 方案一:安装 cri-docker(本文主线)

(1)下载 cri-docker

bash 复制代码
wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.4.6/cri-dockerd-0.4.6.amd64.tgz

(2)解压文件

bash 复制代码
tar -xf cri-dockerd-0.4.6.amd64.tgz

(3)将文件复制到 /usr/bin 目录

bash 复制代码
cp cri-docker/cri-docker /usr/bin/

(4)设置可执行权限

bash 复制代码
chmod +x /usr/bin/cri-docker

(5)配置 cri-docker 服务

bash 复制代码
cat > /usr/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 firewalld.service docker.service
Wants=network-online.target
Requires=cri-docker.socket

[Service]
Type=notify
ExecStart=/usr/bin/cri-dockerd --container-runtime-endpoint fd:// --network-plugin=cni --pod-infra-container-image=registry.k8s.io/pause:3.10.2
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always

StartLimitBurst=3

StartLimitInterval=60s

LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity

TasksMax=infinity
Delegate=yes
KillMode=process

[Install]
WantedBy=multi-user.target
EOF

💡 进阶说明 :这个 unit 文件也可以直接从 cri-dockerd-0.4.6.amd64.tgz 包内的 packaging/systemd/ 目录获取,然后修改 /etc/systemd/system/cri-docker.service,为 Service.ExecStart 增加 --network-plugin--pod-infra-container-image 两个选项。其中 --pod-infra-container-image(pause 根容器镜像)的 tag 需根据你的 Kubernetes 版本来定,例如本教程为 3.10.2

(6)配置 cri-docker 套接字

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

[Socket]
ListenStream=%t/cri-dockerd.sock
SocketMode=0660
SocketUser=root
SocketGroup=docker

[Install]
WantedBy=sockets.target
EOF

拷贝 cri-docker 服务与套接字到 Node 节点

bash 复制代码
[root@k8s-master01 ~]# scp cri-docker.service cri-docker.socket n1:/usr/lib/systemd/system
cri-docker.service                                                                                                         100%  665   939.5KB/s   00:00
cri-docker.socket                                                                                                          100%  204   584.0KB/s   00:00
[root@k8s-master01 ~]# scp cri-docker.service cri-docker.socket n2:/usr/lib/systemd/system
cri-docker.service                                                                                                         100%  665     1.3MB/s   00:00
cri-docker.socket                                                                                                          100%  204   675.6KB/s   00:00

上传并加载 pause 根容器镜像

bash 复制代码
[root@k8s-master01 ~]# docker load -i pause_3.10.2.tar.gz
Loaded image: registry.k8s.io/pause:3.10.2
[root@k8s-master01 ~]# docker images
                                                                                                                              i Info →   U  In Use
IMAGE                          ID             DISK USAGE   CONTENT SIZE   EXTRA
registry.k8s.io/pause:3.10.2   9aec5a02507b       1.06MB          318kB

(7)启动 cri-docker 服务

bash 复制代码
systemctl daemon-reload
systemctl enable --now cri-docker
systemctl is-active cri-docker

systemctl is-active 返回 active 即启动成功。


3.2.2 方案二:安装 containerd(二选一,供参考)

这种方式见这篇文章:

containerd安装kubenetes集群

3.3 安装 Kubernetes

3.3.1 配置仓库

(1)添加 Kubernetes 源

官方阿里云镜像站:https://developer.aliyun.com/mirror/kubernetes

bash 复制代码
cat <<EOF | tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.36/rpm/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.36/rpm/repodata/repomd.xml.key
EOF

(2)安装 kubernetes

bash 复制代码
yum install -y kubelet kubeadm kubectl

三个工具的作用分别如下:

工具 作用
kubeadm 集群初始化工具,用于一键搭建 / 加入集群
kubelet 节点代理服务,运行在每个节点上,负责管理容器并上报状态
kubectl 命令行客户端,用户向集群下发指令的交互工具

(3)让 kubelet 开机自启

bash 复制代码
systemctl enable kubelet

注意:此时 kubelet 会因缺少配置而启动失败,属正常现象,kubeadm init 会补齐配置,无需担心。

(4)验证安装

bash 复制代码
[root@k8s-master01 ~]# kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"36", EmulationMajor:"", EmulationMinor:"", MinCompatibilityMajor:"", MinCompatibilityMinor:"", GitVersion:"v1.36.3", GitCommit:"0f29094e5b73085e3802ecc1298ecae13866bfe6", GitTreeState:"clean", BuildDate:"2026-07-22T18:09:52Z", GoVersion:"go1.26.5", Compiler:"gc", Platform:"linux/amd64"}

(5)查看所需镜像

bash 复制代码
[root@k8s-master01 ~]# kubeadm config images list
registry.k8s.io/kube-apiserver:v1.36.3
registry.k8s.io/kube-controller-manager:v1.36.3
registry.k8s.io/kube-scheduler:v1.36.3
registry.k8s.io/kube-proxy:v1.36.3
registry.k8s.io/coredns/coredns:v1.14.2
registry.k8s.io/pause:3.10.2
registry.k8s.io/etcd:3.6.8-0

# 获取镜像:从阿里云镜像仓库拉取后,改 tag 推送到自建 Harbor
images=$(kubeadm config images list --kubernetes-version=1.36.3 | awk -F "/" '{print $NF}')
for i in $(images)
do
    docker pull registry.aliyuncs.com/google_containers/$i
    docker tag registry.aliyuncs.com/google_containers/$i harbor.registry.com/k8s/$i
    docker push harbor.registry.com/k8s/$i
    docker rmi registry.aliyuncs.com/google_containers/$i
done

📌 注意 :Master 节点需要导入全部 镜像;Node 节点只需导入 kube-proxycorednspause 这三个镜像。

3.4 初始化主节点

此操作只在 k8s-master01 节点执行。

官方文档:https://kubernetes.io/zh-cn/docs/reference/setup-tools/kubeadm/kubeadm-init/#custom-image

bash 复制代码
kubeadm init --apiserver-advertise-address=192.168.194.11 \
--image-repository=registry.aliyuncs.com/google_containers \
--kubernetes-version=1.36.3 \
--service-cidr=10.10.0.0/12 \
--pod-network-cidr=10.244.0.0/16 \
--ignore-preflight-errors=all \
--cri-socket unix:///var/run/cri-dockerd.sock

⚠️ 红色(加粗)部分 --apiserver-advertise-address 改成你自己的 Master 节点 IP。

各参数含义:

参数 说明
--apiserver-advertise-address 指定 Master 主机的 IP 地址
--image-repository 指定控制平面镜像仓库(默认 registry.k8s.io),此处用阿里云镜像加速
--kubernetes-version=1.36.3 固定集群版本,避免自动升级导致兼容性问题
--service-cidr=10.10.0.0/12 指定 Service 资源的网络范围
--pod-network-cidr=10.244.0.0/16 指定 Pod 的网络范围,需与后面 Calico 配置保持一致
--ignore-preflight-errors=all 跳过所有前置检查错误
--cri-socket unix:///var/run/cri-dockerd.sock 指定 CRI 的 socket 路径(cri-docker 用这个;若用 containerd 则为 unix:///run/containerd/containerd.sock

命令执行后输出(关键日志):

bash 复制代码
[init] Using Kubernetes version: v1.36.3
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action beforehand using 'kubeadm config images pull'
W0727 10:53:18.622170    5240 checks.go:907] detected that the sandbox image "registry.k8s.io/pause:3.10.2" of the container runtime is inconsistent with that used by kubeadm. It is recommended to use "registry.aliyuncs.com/google_containers/pause:3.10.2" as the CRI sandbox image.
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [k8s-master01 kubernetes kubernetes.default kubernetes.default.svc.cluster.local] and IPs [10.0.0.1 192.168.194.11]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [k8s-master01 localhost] and IPs [192.168.194.11 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [k8s-master01 localhost] and IPs [192.168.194.11 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "super-admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/instance-config.yaml"
[patches] Applied patch of type "application/strategic-merge-patch+json" to target "kubeletconfiguration"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Starting the kubelet
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests"
[kubelet-check] Waiting for a healthy kubelet at http://127.0.0.1:10248/healthz. This can take up to 4m0s
[kubelet-check] The kubelet is healthy after 539.349µs
[control-plane-check] Waiting for healthy control plane components. This can take up to 4m0s
[control-plane-check] Checking kube-apiserver at https://192.168.194.11:6443/livez
[control-plane-check] Checking kube-controller-manager at https://127.0.0.1:10257/healthz
[control-plane-check] Checking kube-scheduler at https://127.0.0.1:10259/livez
[control-plane-check] kube-scheduler is healthy after 3.451971ms
[control-plane-check] kube-controller-manager is healthy after 4.228132ms
[control-plane-check] kube-apiserver is healthy after 1.50375793s
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config" in namespace kube-system with the configuration for the kubelets in the cluster
[upload-certs] Skipping phase. Please see --upload-certs
[mark-control-plane] Marking the node k8s-master01 as control-plane by adding the labels: [node-role.kubernetes.io/control-plane node.kubernetes.io/exclude-from-external-load-balancers]
[mark-control-plane] Marking the node k8s-master01 as control-plane by adding the taints [node-role.kubernetes.io/control-plane:NoSchedule]
[bootstrap-token] Using token: bkey2t.b5mr4s5qvtimzbdi
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to get nodes
[bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] Configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] Configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstrap-token] Configured RBAC rules to allow the API server kubelet client certificate to access the kubelet API
[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

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 192.168.194.11:6443 --token bkey2t.b5mr4s5qvtimzbdi \
        --discovery-token-ca-cert-hash sha256:a65e6e61df6e25fa81ad15e88d4c531484779ceb1e840e7a86c77c110baaddd8

上面这段日志的核心解读 (便于理解 kubeadm init 到底做了什么):

  • 初始化阶段 [init]:声明使用的 K8s 版本。
  • 预检查阶段 [preflight]:检查系统环境是否满足要求(swap 关闭、内核版本、端口占用等),并拉取控制平面镜像。这里会有一条关于 pause 镜像不一致的告警,属正常提示,可忽略(不影响功能)。
  • 证书阶段 [certs]:在 /etc/kubernetes/pki 生成集群各类证书,包括 ca(根证书)、apiserveretcdfront-proxysa(Service Account)等。
  • kubeconfig 阶段 [kubeconfig]:生成 admin.confsuper-admin.confkubelet.confcontroller-manager.confscheduler.conf 等不同身份的访问配置文件。
  • 控制平面阶段 [control-plane]:生成 kube-apiserverkube-controller-managerkube-scheduleretcd静态 Pod(static Pod)清单 ,放在 /etc/kubernetes/manifests/,由 kubelet 自动拉起。
  • 健康检查 [control-plane-check]:等待 kube-apiserver 等控制平面组件就绪(分别探测 :6443/livez:10257/healthz:10259/livez)。
  • 控制面标签/污点 [mark-control-plane]:给 Master 打上 node-role.kubernetes.io/control-plane 标签,并添加 NoSchedule 污点,阻止普通 Pod 调度到 Master。
  • 引导令牌(bootstrap-token):生成加入集群需要的 token,并配置相关 RBAC 规则。
  • 附加组件:部署 CoreDNS(集群 DNS,负责服务发现)和 kube-proxy(负责网络代理与负载均衡)。

最关键的初始化成功标志 是打印出 Your Kubernetes control-plane has initialized successfully!,以及末尾的 kubeadm join ... 命令(用于工作节点加入)。

3.5 创建 .kube 目录

根据提示,在 Master 家目录下创建 .kube 目录,并将 admin.conf 复制过去:

bash 复制代码
[root@k8s-master01 ~]# mkdir -p $HOME/.kube
[root@k8s-master01 ~]# sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
[root@k8s-master01 ~]# sudo chown $(id -u):$(id -g) $HOME/.kube/config

执行后查看节点:

bash 复制代码
[root@k8s-master01 ~]# kubectl get node
NAME           STATUS     ROLES           AGE   VERSION
k8s-master01   NotReady   control-plane   75s   v1.36.3

可以看到集群已出现一个节点(此时还是 NotReady,因为还没装网络插件,属正常)。

3.6 增加工作节点

在 Node01 和 Node02 上分别执行上面的 kubeadm join 命令加入集群。

Node01 节点:

bash 复制代码
[root@k8s-node01 ~]# kubeadm join 192.168.194.11:6443 --token bkey2t.b5mr4s5qvtimzbdi \
        --discovery-token-ca-cert-hash sha256:a65e6e61df6e25fa81ad15e88d4c531484779ceb1e840e7a86c77c110baaddd8 \
        --cri-socket unix:///var/run/cri-dockerd.sock
[preflight] Running pre-flight checks
[preflight] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"...
[preflight] Use 'kubeadm init phase upload-config kubeadm --config your-config-file' to re-upload it.
W0727 10:57:08.683757    4627 utils.go:69] The recommended value for "bindAddress" in "KubeProxyConfiguration" is: ::; the provided value is: 0.0.0.0
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/instance-config.yaml"
[patches] Applied patch of type "application/strategic-merge-patch+json" to target "kubeletconfiguration"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-check] Waiting for a healthy kubelet at http://127.0.0.1:10248/healthz. This can take up to 4m0s
[kubelet-check] The kubelet is healthy after 501.014458ms
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap

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.

⚠️ 末尾的 --cri-socket unix:///var/run/cri-dockerd.sock 必须加,因为底层容器运行时是 Docker(cri-docker);如果用的是原生 containerd 就不需要加这段。

Node02 节点:

bash 复制代码
[root@k8s-node02 ~]# kubeadm join 192.168.194.11:6443 --token bkey2t.b5mr4s5qvtimzbdi \
        --discovery-token-ca-cert-hash sha256:a65e6e61df6e25fa81ad15e88d4c531484779ceb1e840e7a86c77c110baaddd8 \
        --cri-socket unix:///var/run/cri-dockerd.sock
[preflight] Running pre-flight checks
[preflight] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"...
[preflight] Use 'kubeadm init phase upload-config kubeadm --config your-config-file' to re-upload it.
W0727 10:57:12.008529    4626 utils.go:69] The recommended value for "bindAddress" in "KubeProxyConfiguration" is: ::; the provided value is: 0.0.0.0
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/instance-config.yaml"
[patches] Applied patch of type "application/strategic-merge-patch+jst" to target "kubeletconfiguration"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-check] Waiting for a healthy kubelet at http://127.0.0.1:10248/healthz. This can take up to 4m0s
[kubelet-check] The kubelet is healthy after 500.884942ms
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap

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.

join 命令参数解析:

参数 说明
kubeadm join <masterIP>:6443 指定 Master 节点 IP 和 API 端口
--token 引导令牌;若过期,用 kubeadm token create --print-join-command 重新生成
--discovery-token-ca-cert-hash 用于验证 Master 证书的哈希值
--cri-socket unix:///var/run/cri-dockerd.sock 指定 CRI 的 socket 路径

3.7 查看集群状态

在 Master 上查看当前集群节点:

bash 复制代码
[root@k8s-master01 ~]# kubectl get node
NAME           STATUS     ROLES           AGE     VERSION
k8s-master01   NotReady   control-plane   4m21s   v1.36.3
k8s-node01     NotReady   <none>          52s     v1.36.3
k8s-node02     NotReady   <none>          49s     v1.36.3

三个节点都已创建,但都处于 NotReady 状态------因为还没装网络插件(CNI)。接下来部署 Calico。


四、部署 Calico 网络插件

Kubernetes 集群要正常工作,所有容器必须工作在一个扁平的网络空间里,这就需要部署网络插件(CNI)。下面用 Calico 实现,只需在 Master 节点安装即可。

4.1 下载配置文件

浏览器打开 Calico 官方安装文档:

text 复制代码
https://docs.tigera.io/calico/latest/getting-started/kubernetes/self-managed-onprem/onpremises#install-calico

点击 Manifest 下的 "Install Calico with Kubernetes API datastore, more than 50 nodes"

复制命令将配置文件下载到本地:

bash 复制代码
# 1. 下载配置文件
[root@k8s-master01 ~]# curl https://raw.githubusercontent.com/projectcalico/calico/v3.32.1/manifests/calico-typha.yaml -o calico.yaml
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  347k  100  347k    0     0    99k      0  0:00:03  0:00:03 --:--:--   99k

查看是否下载成功:

bash 复制代码
[root@k8s-master01 ~]# ls
calico.yaml  etcd_3.6.8.tar.gz  k8s-images_v1.36.3.tar.gz

查看所需镜像:

bash 复制代码
[root@k8s-master01 ~]# grep image calico.yaml
          image: quay.io/calico/cni:v3.32.1
          imagePullPolicy: IfNotPresent
          image: quay.io/calico/cni:v3.32.1
          imagePullPolicy: IfNotPresent
          image: quay.io/calico/node:v3.32.1
          imagePullPolicy: IfNotPresent
          image: quay.io/calico/node:v3.32.1
          imagePullPolicy: IfNotPresent
          image: quay.io/calico/kube-controllers:v3.32.1
          imagePullPolicy: IfNotPresent
        - image: quay.io/calico/typha:v3.32.1
          imagePullPolicy: IfNotPresent

4.2 修改配置文件

calico.yaml 中的 CALICO_IPV4POOL_CIDR 改为初始化集群时 --pod-network-cidr=10.244.0.0/16 指定的地址,并把 CALICO_IPV4POOL_IPIPCALICO_IPV4POOL_VXLAN 都关闭(使用 BGP 模式):

bash 复制代码
[root@k8s-master01 ~]# vim calico.yaml
kind: DaemonSet
# Enable IPIP
- name: CALICO_IPV4POOL_IPIP
#  value: "Always"
  value: "Off"      # 这里 Always 改为 Off
# Enable or Disable VXLAN on the default IP pool.
- name: CALICO_IPV4POOL_VXLAN
  value: "Never"
# Enable or Disable VXLAN on the default IPv6 IP pool.
- name: CALICO_IPV6POOL_VXLAN
  value: "Never"

# no effect. This should fall within `--cluster-cidr`
# - name: CALICO_IPV4POOL_CIDR
#   value: "192.168.0.0/16"
- name: CALICO_IPV4POOL_CIDR
  value: "10.244.0.0/16"       # 将 192.168.0.0/16 改为 10.244.0.0/16

# Disable file logging so `kubectl logs` works.

修改完成后保存退出。

4.3 上传镜像文件

由于 Calico 镜像在国内很难下载,可提前下载好并上传到网盘,再从网盘下载到本地导入集群中。注意:三个节点都需要导入。 需要导入的镜像 calico-images_v3.32.1.tar.gz 分别放在三个节点的家目录:

bash 复制代码
# 1. master01 节点
[root@k8s-master01 ~]# ls
anaconda-ks.cfg  calico-images_v3.32.1.tar.gz  calico.yaml

# 2. node01 节点
[root@k8s-node01 ~]# ls
anaconda-ks.cfg  calico-images_v3.32.1.tar.gz

# 3. node02 节点
[root@k8s-node02 ~]# ls
anaconda-ks.cfg  calico-images_v3.32.1.tar.gz

4.4 导入镜像文件

在三个节点上分别通过 docker load 导入 Calico 镜像:

bash 复制代码
# 1. master01 节点
[root@k8s-master01 ~]# docker load -i calico-images_v3.32.1.tar.gz
37c1775e0302: Loading layer [==================================================>]  3.693MB/3.693MB
9ad9e3f4f50f: Loading layer [==================================================>]  78.75MB/78.75MB
Loaded image: calico/kube-controllers:v3.32.1
f3dac1398271: Loading layer [==================================================>]  70.16MB/70.16MB
Loaded image: calico/typha:v3.32.1
ac1e39af291c: Loading layer [==================================================>]  216.1MB/216.1MB
5f70bf18a086: Loading layer [==================================================>]  1.024kB/1.024kB
Loaded image: calico/cni:v3.32.1
55c8cc0817d5: Loading layer [==================================================>]  380.4MB/380.4MB
Loaded image: calico/node:v3.32.1

# 2. node01 节点
[root@k8s-node01 ~]# docker load -i calico-images_v3.32.1.tar.gz
37c1775e0302: Loading layer [==================================================>]  3.693MB/3.693MB
9ad9e3f4f50f: Loading layer [==================================================>]  78.75MB/78.75MB
Loaded image: calico/kube-controllers:v3.32.1
f3dac1398271: Loading layer [==================================================>]  70.16MB/70.16MB
Loaded image: calico/typha:v3.32.1
ac1e39af291c: Loading layer [==================================================>]  216.1MB/216.1MB
5f70bf18a086: Loading layer [==================================================>]  1.024kB/1.024kB
Loaded image: calico/cni:v3.32.1
55c8cc0817d5: Loading layer [==================================================>]  380.4MB/380.4MB
Loaded image: calico/node:v3.32.1

# 3. node02 节点
[root@k8s-node02 ~]# docker load -i calico-images_v3.32.1.tar.gz
37c1775e0302: Loading layer [==================================================>]  3.693MB/3.693MB
9ad9e3f4f50f: Loading layer [==================================================>]  78.75MB/78.75MB
Loaded image: calico/kube-controllers:v3.32.1
f3dac1398271: Loading layer [==================================================>]  70.16MB/70.16MB
Loaded image: calico/typha:v3.32.1
ac1e39af291c: Loading layer [==================================================>]  216.1MB/216.1MB
5f70bf18a086: Loading layer [==================================================>]  1.024kB/1.024kB
Loaded image: calico/cni:v3.32.1
55c8cc0817d5: Loading layer [==================================================>]  380.4MB/380.4MB
Loaded image: calico/node:v3.32.1

4.5 安装网络插件

在 master01 节点上执行:

bash 复制代码
[root@k8s-master01 ~]# kubectl apply -f calico.yaml
poddisruptionbudget.policy/calico-kube-controllers created
poddisruptionbudget.policy/calico-typha created
serviceaccount/calico-kube-controllers created
serviceaccount/calico-node created
serviceaccount/calico-cni-plugin created
configmap/calico-config created
customresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/bgpfilters.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/bgppeers.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/blockaffinities.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/caliconodestatuses.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/clusterinformations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/felixconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworksets.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/hostendpoints.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamblocks.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamconfigs.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamhandles.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ippools.crd.projectcalico.org created
Warning: unrecognized format "cidr"
customresourcedefinition.apiextensions.k8s.io/ipreservations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/kubecontrollersconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networksets.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/stagedglobalnetworkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/stagedkubernetesnetworkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/stagednetworkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/tiers.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/clusternetworkpolicies.policy.networking.k8s.io created
clusterrole.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrole.rbac.authorization.k8s.io/calico-node created
clusterrole.rbac.authorization.k8s.io/calico-cni-plugin created
clusterrole.rbac.authorization.k8s.io/calico-tier-getter created
clusterrolebinding.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrolebinding.rbac.authorization.k8s.io/calico-node created
clusterrolebinding.rbac.authorization.k8s.io/calico-cni-plugin created
clusterrolebinding.rbac.authorization.k8s.io/calico-tier-getter created
service/calico-typha created
daemonset.apps/calico-node created
deployment.apps/calico-kube-controllers created
deployment.apps/calico-typha created

五、验证集群

安装完成后,查看当前集群状态:

bash 复制代码
[root@k8s-master01 ~]# kubectl get node
NAME           STATUS   ROLES           AGE    VERSION
k8s-master01   Ready    control-plane   3h27m  v1.36.3
k8s-node01     Ready    <none>          3h14m  v1.36.3
k8s-node02     Ready    <none>          3h14m  v1.36.3

此时三个节点全部处于 Ready 状态,说明集群搭建成功!🎉

可以再执行 kubectl get pods -A 查看系统组件运行情况:

bash 复制代码
kubectl get pods -A

看到 kube-system 命名空间下的 corednscalicokube-proxy 等 Pod 都处于 Running 状态,即可确认集群完全可用。


常见坑点与注意事项

  1. 容器运行时二选一 :cri-docker 和 containerd 不要同时安装,否则会冲突。
  2. cri-socket 参数 :初始化/加入集群时,若用 cri-docker 必须带 --cri-socket unix:///var/run/cri-dockerd.sock;用 containerd 则用 unix:///run/containerd/containerd.sock
  3. Pod 网段一致kubeadm init--pod-network-cidr(如 10.244.0.0/16)必须与 Calico 配置里的 CALICO_IPV4POOL_CIDR 保持一致,否则网络异常。
  4. Aliyun/sandbox 镜像告警可忽略 :初始化时那条 pause 镜像不一致的 W 告警,不影响功能,可忽略。
  5. 节点 NotReady :若 join 后节点仍 NotReady,通常是网络插件未部署或镜像未导入,检查 kubectl get pods -A
  6. kubelet 启动失败属正常 :安装阶段 systemctl enable kubelet 后 kubelet 会因为缺配置报错,kubeadm init 会补齐,属正常现象。
  7. 网关/网段按实际修改 :文中的 IP 网段 192.168.194.x、网关、hosts 映射都是示例,务必改成你环境的真实值。
相关推荐
鹤落晴春6 小时前
【K8s】从 ConfigMap 到 Argo CD:Kubernetes 配置管理链路
运维·云原生·容器·kubernetes
ezreal_pan7 小时前
Pod 重启多场景复盘:OOM、日志阻塞、并发 map 读写导致程序崩溃
docker·k8s
玉宇夕落8 小时前
Docker + Milvus,数据库永久存放记忆,让对话历史永不丢失
数据库·docker·llm
名字还没想好☜10 小时前
Docker 镜像瘦身进阶:用 distroless/scratch 把 Go 服务打到 10MB,以及没 shell 怎么调试
运维·docker·容器·golang·kubernetes
JavaPub-rodert10 小时前
Docker 深入理解:从容器原理到生产环境最佳实践
运维·docker·容器
陈聪.12 小时前
Kubernetes 集群网络插件迁移:从 Flannel 到 Calico 实战总结
云原生·kubernetes
暖核12 小时前
Docker 入门|吃透容器生态与底层原理
运维·docker·容器
滕州市燕猫虎计算机科技工作室个体工商户12 小时前
Docker常用命令
运维·docker·容器
wdfk_prog13 小时前
lely-canopen-rtt:为什么推荐在 RT-Thread 上使用 Lely CANopen 构建主站
java·运维·缓存·docker·容器
智_永无止境14 小时前
一个Docker命令,40万首古诗词API开箱即用
docker·诗泉