文章目录

0 k8s集群的架构

IP 虚拟机名称 域名 角色
192.168.56.101 rockylinux10-1 rockylinux10-1 control plane node
192.168.56.102 rockylinux10-2 rockylinux10-2 worker node
192.168.56.103 rockylinux10-3 rockylinux10-3 worker node

1 准备虚拟机 rockylinux10-1

1.1 下载最新版的 VirtualBox

VirtualBox 官网 https://www.virtualbox.org/ 。从官网上下载好之后,安装到宿主机上。

1.2 下载最新版的 RockyLinux dvd.iso 镜像

Rocky Linux 官网 https://rockylinux.org
阿里云镜像 https://developer.aliyun.com/mirror/rockylinux
找到适合宿主机的CPU架构的镜像,比如:https://mirrors.aliyun.com/rockylinux/10.2/isos/aarch64/Rocky-10.2-aarch64-dvd1.iso

1.3 新建虚拟机 rockylinux10-1

在这里插入图片描述

1.4 设置内存和CPU

在这里插入图片描述

1.5 设置磁盘

在这里插入图片描述

1.6 设置双网卡

1.6.1 网卡1的设置

在这里插入图片描述

上图的 HostNetwork 这个名称,来自于下图所示内容,VirtualBox 会自带这个网络。如果没有自带的话,就自己创建一个。

在这里插入图片描述

1.6.2 网卡2的设置

在这里插入图片描述

1.6.3 双网卡设置好后点【确定】按钮

1.7 启动虚拟机 rockylinux10-1

在这里插入图片描述

1.8 安装 RockyLinux

按照《VirtualBox安装RockyLinux并使用ssh访问》 2.7节的方式,把 RockyLinux 安装好。

1.9 关闭防火墙

systemctl stop firewalld
systemctl disable firewalld

1.10 检查 ssh 是否安装

# 输入 ssh 命令后,直接回车,看有没有报错信息
ssh

# 如果提示没有 ssh 命令,那么就安装它
dnf install openssh-server
# 安装完成后,查看 ssh 状态。一般 ssh 是开机自启动的
systemctl status sshd
# 如果不是自启动,设置成自启动
systemctl enable sshd

1.11 ssh 允许远程登录

# 编辑如下文件,如果 vim 还没有安装的话,可以使用 dnf install vim 命令来安装一下
vim /etc/ssh/sshd_config
# 没有 vim 命令的话,用 dnf install vim 安装
# 在 vim 编辑此文件时,可以用 yy 复制一行,然后用 p 向下粘贴一行
# 找到# Authentication,如下图:

在这里插入图片描述

1.12 重启虚拟机 rockylinux10-1

reboot

1.13 重启之后,可以在宿主机上用 ssh 连接虚拟机 rockylinux10-1

在虚拟机上 ip a 查看 ip 地址,比如是 192.168.56.3 。然后在宿主机的 terminal 上远程连接 root@192.168.56.3 。

1.14 设置静态IP

ip a 可以看到如下内容:

[root@localhost ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute 
       valid_lft forever preferred_lft forever
2: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:3b:5e:5a brd ff:ff:ff:ff:ff:ff
    altname enx0800273b5e5a
    inet 192.168.56.3/24 brd 192.168.56.255 scope global dynamic noprefixroute enp0s8
       valid_lft 3192sec preferred_lft 3192sec
    inet6 fd25:e1ed:cc49:b2ad:a00:27ff:fe3b:5e5a/64 scope global dynamic noprefixroute 
       valid_lft 2591878sec preferred_lft 604678sec
    inet6 fe80::a00:27ff:fe3b:5e5a/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
3: enp0s9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:b2:12:9e brd ff:ff:ff:ff:ff:ff
    altname enx080027b2129e
    inet 10.0.3.15/24 brd 10.0.3.255 scope global dynamic noprefixroute enp0s9
       valid_lft 85992sec preferred_lft 85992sec
    inet6 fd17:625c:f037:3:a00:27ff:feb2:129e/64 scope global dynamic noprefixroute 
       valid_lft 86313sec preferred_lft 14313sec
    inet6 fe80::a00:27ff:feb2:129e/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever

由此可知,仅主机(Host-Only)网络就是 enp0s8 ,因为它的 IP 正好是 192.168.56.0/24 这个网段儿的。接下来,我们就设置 enp0s8 为静态 IP 192.168.56.101

1.14.1 执行 nmtui 命令

1.14.2 编辑一个连接

在这里插入图片描述

1.14.3 编辑 enp0s8

在这里插入图片描述

1.14.4 设置静态 IP

在这里插入图片描述

1.14.5 重启虚拟机 rockylinux10-1 来让静态 IP 的配置生效

reboot

1.15 重启之后,宿主机和虚拟机要互相 ping 通

# 宿主机 ping 虚拟机
ping 192.168.56.101
# 虚拟机 ping 宿主机,也就是要 ping 外网
ping www.baidu.com
# 虚拟机上查看路由表的话,应该只有一个 enp0s9 的
[root@localhost ~]# ip route show default
default via 10.0.3.2 dev enp0s9 proto dhcp src 10.0.3.15 metric 100

1.16 设置 /etc/hosts

vim /etc/hosts
# 在文件末尾,添加如下配置
192.168.56.101 rockylinux10-1
192.168.56.102 rockylinux10-2
192.168.56.103 rockylinux10-3

# 192.168.124.10 是宿主机的 ip
# 宿主机上 docker 私有仓库的设置
192.168.124.10 myprivate.docker.registry
# 宿主机上 daocloud 的本地缓存仓库设置
192.168.124.10 m.daocloud.io.localcache.registry

上面的宿主机上 docker 私有仓库的设置,可以参考我的另一篇文章 《01_手动构建自己开发学习所需的镜像》https://blog.csdn.net/ShiJunzhiCome/article/details/161364867使用 docker compose 启动 registry:3.1.1 和 joxit/docker-registry-ui:2.6.0部署 daocloud 的本地缓存 这2个小节的内容。

1.17 设置 hotstname 主机名

hostnamectl set-hostname rockylinux10-1

1.18 关闭 selinux

setenforce 0
sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config

1.19 关闭 swap

swapoff -a
yes | cp /etc/fstab /etc/fstab_bak

vim /etc/fstab
#注释与 swap 有关的一行,比如:
#UUID=b154ad02-c85b-4301-a28a-7074e24c39e6 none  swap  defaults 0 0

1.20 设置阿里 repo 源

# 先执行如下命令
sed -e 's|^mirrorlist=|#mirrorlist=|g' \
    -e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.aliyun.com/rockylinux|g' \
    -i.bak \
    /etc/yum.repos.d/rocky*.repo

# 然后再执行
dnf makecache

1.21 重启虚拟机 rockylinux10-1

reboot

重启之后,可以在宿主机上,用 terminal 再次远程连接 root@192.168.56.101 。此时,terminal 就会从 [root@localhost~]# 变成 [root@rockylinux10-1 ~]#

1.22 关闭虚拟机 rockylinux10-1

shutdown -h now

1.23 VirtualBox 生成个快照 rockylinux10-init

备份名称

rockylinux10-init

备份描述

rockylinux 10
内存4G  CPU2个 50GB磁盘
配置好了双网卡:hostOnly 和 NAT
关闭了防火墙 firewalld
设置好了静态ip 192.168.56.101/24
主机可以访问虚拟机
虚拟机可以访问外网
设置好了 /etc/hosts
设置好了 hotstname 主机名为 rockylinux10-1
关闭了 selinux
关闭了 swap
配置好了阿里的repo源

如下图:

在这里插入图片描述

2 虚拟机 rockylinux10-1 上安装 Docker

2.1 启动虚拟机 rockylinux10-1

2.2 安装 docker

按照 https://docs.docker.com/engine/install/rhel/ 官网的说明,分别执行以下命令

# 1 Set up the repository
dnf -y install dnf-plugins-core
dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo

# 2 Install Docker Engine
dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 3 Start Docker Engine
systemctl enable --now docker

然后设置 /etc/docker/daemon.json,设置 cgroup 的 driver 是 systemd,设置 dockerhub 的国内镜像 https://docker.m.daocloud.io

{
  "exec-opts": [
    "native.cgroupdriver=systemd"
  ],
  "registry-mirrors": [
    "https://docker.m.daocloud.io"
  ],
  "insecure-registries": [
    "myprivate.docker.registry:55000",
    "m.daocloud.io.localcache.registry:65000"
  ]
}

重启 docker

systemctl restart docker

2.3 设置时间同步

# 查看服务状态,应该是 active 的状态,并且开机自启
# chronyd 会自动同步时间
systemctl status chronyd

# 一般 chronyd 是已安装的,并且是开机自动启动的,如果没有安装,则安装它
dnf install chrony
# 安装之后设置开机自动启动
systemctl enable --now chronyd

# 查看详细的同步信息,关注 System time 和 Last offset 等字段
chronyc tracking
# 查看当前可用的NTP时间源
chronyc sources -v

#### 使用国内的 ntp 服务器
vim /etc/chrony.conf
#### 在文件中找到 pool 或 server 开头的行,这些是当前使用的 NTP 源。注释掉(在行首加 #)它们,然后添加国内常用的 NTP 服务器。如下:
# pool 2.rocky.pool.ntp.org iburst
# use china npt
server ntp.aliyun.com iburst
server ntp.tencent.com iburst
server ntp.ntsc.ac.cn iburst
#### 其他配置不变
#### 重启 chronyd 服务并检查状态
systemctl restart chronyd
chronyc tracking
chronyc sources -v
#### 验证时间同步,确保输出 System clock synchronized: yes
timedatectl

2.4 关闭虚拟机 rockylinux10-1

shutdown -h now

2.5 VirtualBox 生成个快照 rockylinux10-installed-docker

备份名称

rockylinux10-installed-docker

备份描述

rockylinux 10
安装了 docker 并且自启动
配置了 /etc/docker/daemon.json
设置了时间同步 chronyd 用国内 ntp

3 虚拟机 rockylinux10-1 上安装 k8s

3.1 启动虚拟机 rockylinux10-1

3.2 设置 k8s 的 repo 源

使用华为的源,注意,我这里的 k8s 版本是 v1.36

cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.huaweicloud.com/kubernetes-new/core:/stable:/v1.36/rpm/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.huaweicloud.com/kubernetes-new/core:/stable:/v1.36/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF

😁为什么不用阿里的源?原因是👉阿里的 k8s repo 源 https://mirrors.aliyun.com/kubernetes-new/,内容没有华为的全。

3.3 设置 br_netfilter 和 bridge-nf-call-iptables

3.3.1 相关命令

# 1. 加载模块并设置开机自启
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
br_netfilter
EOF

# 2. 设置内核参数
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF

# 3. 立即生效
sysctl --system
# 4. 重启系统
reboot

# 5. 重启后,验证是否生效(应输出 1)
sysctl net.bridge.bridge-nf-call-iptables

3.3.2 为什么要设置 br_netfilter 和 bridge-nf-call-iptables?

简单来说,这是为了让 Linux 网桥(Bridge) 转发的数据包,能够被 iptables 的规则“拦截”并处理。之所以需要这么做,是因为网桥和 iptables 工作在网络的不同层次,而 Kubernetes 的 Service 机制恰恰依赖于此。

  1. 它们工作在“不同层次”
    Linux 网桥:是一个虚拟的二层(数据链路层)网络设备,它根据 MAC 地址在同一网段内转发数据包。
    iptables:是 Linux 内核 netfilter 框架的一部分,工作在三层(网络层)和四层(传输层)。
    默认情况下,数据包在网桥内转发时,走的是二层“快捷通道”,不会经过三层的 iptables 进行处理。br_netfilter 内核模块和 bridge-nf-call-iptables 参数的作用,就是强制打开这个“通道”,让所有经过网桥的数据包也必须经过 iptables 的检查。

  2. Kubernetes 为什么必须开启?
    这能解决一个非常实际的网络问题:Kubernetes Service 的同节点通信问题。
    Kubernetes 的 Service 机制依赖于 iptables 规则来实现负载均衡(DNAT)。当 Pod 访问 Service 时:
    请求数据包必须经过 iptables 规则,其目标 IP(ClusterIP)会被转换为某个 Pod 的 IP(PodIP)。
    如果这个目标 Pod 恰好在同一个节点上,那么回包时,由于源和目的 IP 都在同一个网桥段内,默认会被网桥直接二层转发回去,而不会再次经过 iptables。
    这就导致连接跟踪(conntrack) 信息不完整,客户端和服务端认为它们不在一个“连接”里,通信失败。
    开启 bridge-nf-call-iptables 后,回包也必须经过 iptables,从而保证了连接跟踪信息的完整性,解决了通信问题。这是 Kubernetes 官方要求的必备网络条件。

3.4 设置 docker 安装后 自带的 containerd

3.4.1 编辑配置文件

vim /etc/containerd/config.toml

修改之后的完整的文件内容如下:

#   Copyright 2018-2022 Docker Inc.

#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at

#       http://www.apache.org/licenses/LICENSE-2.0

#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

# 【新增】这个 version = 2 是必须的,要不然用 containerd config dump | grep -E "SystemdCgroup|192.168" 这个命令,就会看到许多 error
version = 2

# disabled_plugins = ["cri"]
# 【修改】把 cri 去掉
disabled_plugins = []

#root = "/var/lib/containerd"
#state = "/run/containerd"
#subreaper = true
#oom_score = 0

#[grpc]
#  address = "/run/containerd/containerd.sock"
#  uid = 0
#  gid = 0

#[debug]
#  address = "/run/containerd/debug.sock"
#  uid = 0
#  gid = 0
#  level = "info"

# 【新增】启用 CRI 插件(如果存在 disabled_plugins,确保没有 "cri")
[plugins."io.containerd.grpc.v1.cri"]
  # 启用 CRI 服务
  [plugins."io.containerd.grpc.v1.cri".containerd]
    # 默认使用 runc
    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
      [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
        runtime_type = "io.containerd.runc.v2"
        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
          SystemdCgroup = true   # 关键:与 kubelet 的 cgroup 驱动保持一致
# 【新增】私有 http 镜像仓库的配置
# 这个配置是可选的,用于连接本地的私有镜像仓库,防止拉取镜像的时候,报 https 相关的错误
[plugins."io.containerd.cri.v1.images"]
  [plugins."io.containerd.cri.v1.images".registry]
    [plugins."io.containerd.cri.v1.images".registry.mirrors]
      [plugins."io.containerd.cri.v1.images".registry.mirrors."myprivate.docker.registry:55000"]
        endpoint = ["http://myprivate.docker.registry:55000"]
      [plugins."io.containerd.cri.v1.images".registry.mirrors."m.daocloud.io.localcache.registry:65000"]
        endpoint = ["http://m.daocloud.io.localcache.registry:65000"]

3.4.2 重启 containerd 服务

systemctl restart containerd
# 查看版本号,验证重启是否成功
ctr -n k8s.io version

用下面的命令,验证 containerd 是否配置好:

[root@rockylinux10-1 ~]# containerd config dump | grep -E "SystemdCgroup|myprivate|daocloud"
        [plugins.'io.containerd.cri.v1.images'.registry.mirrors.'m.daocloud.io.localcache.registry:65000']
          endpoint = ['http://m.daocloud.io.localcache.registry:65000']
        [plugins.'io.containerd.cri.v1.images'.registry.mirrors.'myprivate.docker.registry:55000']
          endpoint = ['http://myprivate.docker.registry:55000']
            SystemdCgroup = true
# 输出的结果中,不应该有 error

3.4.3 与 containerd 相关一些命令

# 查看 containerd 版本号
ctr -n k8s.io version

# 查看 k8s 里面有哪些镜像
ctr -n k8s.io images list
# k8s 拉取镜像
ctr -n k8s.io images pull xxx-image:xxx-tag
# k8s 给镜像打 tag
ctr -n k8s.io images tag source-image:aaa-tag target-image:bbb-tag

# 查看 k8s 里面有哪些正在运行的容器
ctr -n k8s.io containers list

3.5 安装 kubelet, kubeadm 和 kubectl

通过 dnf --version 命令,来看自己的 linux 是用的 DNF 还是 DNF5,如下,很明显,是 DNF 4的版本,也就是 DNF。

[root@rockylinux10-1 ~]# dnf --version
4.20.0
  Installed: dnf-0:4.20.0-22.el10_2.rocky.0.1.noarch at Mon 27 Jul 2026 09:54:53 AM GMT
  Built    : Rocky Linux Build System <releng@rockylinux.org> at Tue 19 May 2026 02:06:23 PM GMT

  Installed: rpm-0:4.19.1.1-23.el10.aarch64 at Mon 27 Jul 2026 09:54:53 AM GMT
  Built    : Rocky Linux Build System <releng@rockylinux.org> at Sat 07 Feb 2026 02:50:24 PM GMT

根据 k8s 官方文档 https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/ ,开始安装,命令如下:

# For systems with DNF:
# 我们是 DNF4 也就是 DNF,所以执行下面的命令
dnf install -y kubelet kubeadm kubectl --disableexcludes=kubernetes

# For systems with DNF5:
# 我们是 DNF4 不是5,所以不用下面的命令
dnf install -y kubelet kubeadm kubectl --setopt=disable_excludes=kubernetes

安装完成后执行(这个很重要)

systemctl enable kubelet.service

3.6 手动拉取阿里云的 pause 镜像并打上官方标签

为什么要手动拉取?因为后面的 kubeadmin init 和 安装 CNI 网络插件会从 k8s 官方源 registry.k8s.io 拉取镜像 pause:3.10.1。但是国内访问不了 registry.k8s.io,所以只能手动拉取,然后打 tag,命令如下:

# 拉取阿里云上的 pause(版本 3.10,与 3.10.1 基本一致)
ctr -n k8s.io images pull registry.aliyuncs.com/google_containers/pause:3.10

# 给这个镜像打上 kubelet 期望的标签(这样 kubelet 拉取时会直接命中本地镜像)
ctr -n k8s.io images tag registry.aliyuncs.com/google_containers/pause:3.10 registry.k8s.io/pause:3.10.1

# 验证镜像已存在
ctr -n k8s.io images list | grep pause
# 应该有一行是 registry.k8s.io/pause:3.10.1

3.7 关闭虚拟机 rockylinux10-1

shutdown -h now

3.8 VirtualBox 生成个快照 rockylinux10-prepare-k8s

备份名称

rockylinux10-prepare-k8s

备份描述

rockylinux 10
设置了 k8s 的华为 repo 源
设置了 br_netfilter 和 bridge-nf-call-iptables
设置了 docker 安装后 自带的 containerd
安装了 kubelet, kubeadm 和 kubectl
手动拉取了阿里云的 pause 镜像并打上官方标签

4 虚拟机 rockylinux10-1 上 kubeadmin init 初始化

4.1 启动虚拟机 rockylinux10-1

4.2 执行初始化命令

kubeadm init --kubernetes-version=1.36.3 \
    --apiserver-advertise-address=192.168.56.101 \
    --image-repository registry.aliyuncs.com/google_containers \
    --service-cidr=10.1.0.0/16 \
    --pod-network-cidr=10.244.0.0/16

4.2.1 --service-cidr 和 --pod-network-cidr 是做什么的?

参数 用途 谁在使用
--service-cidr 为 Kubernetes Service(服务) 分配 ClusterIP 的地址池。每个 Service 会从这个网段中获取一个虚拟 IP。 kube-apiserver、kube-proxy
--pod-network-cidr 为 Kubernetes Pod 分配 IP 的地址池。每个 Pod 启动时,CNI 插件(如 Calico)会从这个网段中分配一个 IP。 CNI 插件(Calico/Flannel)

4.2.2 --service-cidr 和 --pod-network-cidr 不能随便写! 有如下要求

4.2.2.1 不能与宿主机网段重叠

宿主机网段是:

  • 192.168.56.0/24(enp0s8)
  • 10.0.3.0/24(enp0s9)

那么 --service-cidr--pod-network-cidr 不能是 192.168.56.0/24 或 10.0.3.0/24 的子集,否则网络会混乱。前面选的 10.1.0.0/16 和 10.244.0.0/16 与这两个网段都不重叠,是安全的。

4.2.2.2 两个 CIDR 之间不能重叠

Service IP 和 Pod IP 必须互不干扰。10.1.0.0/16 和 10.244.0.0/16 是独立的两个网段,没有问题。

4.2.2.3 要满足集群规模需求
  • 10.1.0.0/16:最多可分配 65536 个 Service ClusterIP,通常足够。

  • 10.244.0.0/16:最多可分配 65536 个 Pod IP,通常也足够。

如果你有超大规模集群(数千节点),可能需要更大的网段(如 /15 或 /14),但一般用不到。

4.2.3 k8s 初始化失败❗️,如何清理现场,方便再次尝试❓

kubeadm reset -f \
&& rm -rf /etc/kubernetes/ \
&& rm -rf /var/lib/kubelet/ \
&& systemctl restart containerd \
&& systemctl restart kubelet

4.2.4 k8s 初始化成功✌️👏👏👏

可以看到如下结果,记得保存好 kubeadmin join 的那行命令:

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.56.101:6443 --token px86b6.tb097rswj6cpy09r \
	--discovery-token-ca-cert-hash sha256:85cd76a1b57e2c5eab17dfb5f7467a5f9205b59375754e9368edfbab87809188

4.3 k8s 初始化成功后的操作

4.3.1 解决 kubectl get nodes 报错的问题

# 执行如下命令
kubectl get nodes
# 第一种情况:输出结果可能是一堆报错信息,如下:
E0730 09:42:53.796351    2645 memcache.go:265] "Unhandled Error" err="couldn't get current server API group list: Get \"http://localhost:8080/api?timeout=32s\": dial tcp [::1]:8080: connect: connection refused"
E0730 09:42:53.796568    2645 memcache.go:265] "Unhandled Error" err="couldn't get current server API group list: Get \"http://localhost:8080/api?timeout=32s\": dial tcp [::1]:8080: connect: connection refused"
E0730 09:42:53.797997    2645 memcache.go:265] "Unhandled Error" err="couldn't get current server API group list: Get \"http://localhost:8080/api?timeout=32s\": dial tcp [::1]:8080: connect: connection refused"
E0730 09:42:53.798197    2645 memcache.go:265] "Unhandled Error" err="couldn't get current server API group list: Get \"http://localhost:8080/api?timeout=32s\": dial tcp [::1]:8080: connect: connection refused"
E0730 09:42:53.799655    2645 memcache.go:265] "Unhandled Error" err="couldn't get current server API group list: Get \"http://localhost:8080/api?timeout=32s\": dial tcp [::1]:8080: connect: connection refused"
The connection to the server localhost:8080 was refused - did you specify the right host or port?
# 第二种情况:输出结果比较正常,如下:
NAME             STATUS   ROLES           AGE   VERSION
rockylinux10-1   NotReady control-plane   66s   v1.36.3

如果是第一种情况的错误信息的话,那么接下来,我们按如下步骤解决它。此问题产生的原因是:kubectl 没有找到正确的 kubeconfig 配置文件,kubeadm init 成功后,会在 /etc/kubernetes/admin.conf 生成管理员 kubeconfig 文件,但你需要将它配置到 kubectl 的默认路径或设置环境变量。

  1. 确认集群是否已成功初始化:执行命令 ls -l /etc/kubernetes/admin.conf 如果该文件存在,说明 kubeadm init 已成功生成集群凭证,只是 kubectl 没有正确读取;
  2. 配置 kubectl 使用该 kubeconfig:最好选方法一
# 方法一(推荐):复制到默认路径
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
# 方法二:设置环境变量
echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bash_profile
source ~/.bash_profile
  1. 验证 kubectl 是否正常工作:执行命令 kubectl get nodes 这回应该不会报错,会输出状态是 NotReady

4.3.2 安装 CNI 网络插件

上面输出的 STATUS NotReady 表明集群已启动,其中 NotReady 是因为尚未安装 CNI 网络插件。CNI 网络插件有2种 Calico 和 Flannel,它们的异同如下:

特性 Calico Flannel
网络模型 基于BGP协议的路由网络,数据包直接路由,性能接近物理网络。 Overlay(覆盖网络),通过VXLAN等技术封装数据包,有一定性能损耗。
NetworkPolicy 原生支持,可实现Pod间微隔离。 不支持,无法实现Pod级网络策略。
性能 高(BGP模式无封装)。 中等(VXLAN模式有封装开销)。
复杂度 较复杂,组件多,有学习成本。 简单,轻量,部署即用。
适用场景 中大规模集群,对性能、安全、策略有高要求的场景。 中小规模集群,对功能和性能要求不高,追求简单快速的场景。

这里随便选一个,就选 Calico,反正是学习用😄。Calico 的 github 官网是 https://github.com/projectcalico/calico 。为了方便访问,我们可以把 https://github.com/projectcalico/calico.git 导入到 gitee 仓库中。

在导入好的 gitee 仓库中,选择最新的 release 了的分支,比如现在最新是 v3.32.1,然后下载代码仓库的 zip 包。解压 zip 包后,进入到解压后的目录,可以找到 manifests/calico.yaml 文件。

然后把此 calico.yaml 文件上传到 rockylinux10-1 虚拟机中,执行如下命令:

cat calico.yaml |grep image
# 可以看到,都使用了 quay.io 的镜像
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

根据 DaoCloud 在 gitee 上的官方仓库 https://gitee.com/daocloud/public-image-mirror 上所说,我们可以把 quay.io 替换成 quay.m.daocloud.io 相关命令如下:

sed -i 's?quay.io?quay.m.daocloud.io?g' calico.yaml

# 替换好之后,再 cat calico.yaml |grep image 就可以看到,都是 quay.m.daocloud.io 了

然后执行如下命令,

kubectl apply -f calico.yaml

# 执行完成后,等上5分钟左右,再次 kubectl get node 应该是如下结果,STATUS 是 Ready 了,如下:
[root@rockylinux10-1 ~]# kubectl get node
NAME             STATUS   ROLES           AGE   VERSION
rockylinux10-1   Ready    control-plane   7m20s   v1.36.3

# 执行 kubectl get pod -n kube-system 命令,得到的 STATUS 应该都是 Running,如下:
[root@rockylinux10-1 ~]# kubectl get pod -n kube-system
NAME                                      READY   STATUS    RESTARTS   AGE
calico-kube-controllers-f5d69fdff-rftz6   1/1     Running   0          3m5s
calico-node-8bx86                         1/1     Running   0          3m5s
coredns-6b5f954497-622g2                  1/1     Running   0          6m43s
coredns-6b5f954497-qhdpx                  1/1     Running   0          6m43s
etcd-rockylinux10-1                       1/1     Running   0          6m51s
kube-apiserver-rockylinux10-1             1/1     Running   0          6m51s
kube-controller-manager-rockylinux10-1    1/1     Running   0          6m51s
kube-proxy-vn7p7                          1/1     Running   0          6m43s
kube-scheduler-rockylinux10-1             1/1     Running   0          6m51s

4.4 关闭虚拟机 rockylinux10-1

shutdown -h now

4.5 VirtualBox 生成个快照 rockylinux10-k8s-init-success

备份名称

rockylinux10-k8s-init-success

备份描述

rockylinux 10
kubeadmin init 初始化了 rockylinux10-1 节点
解决了 kubectl get nodes 报错的问题
安装了 CNI 网络插件

5 虚拟机 rockylinux10-2

5.1 从虚拟机 rockylinux10-1 中复制出 rockylinux10-2

从虚拟机 rockylinux10-1 之前创建的快照 rockylinux10-prepare-k8s 中复制(clone)出 rockylinux10-2,如下图:

在这里插入图片描述

5.2 启动虚拟机 rockylinux10-2

5.3 修改 rockylinux10-2 的静态 IP

使用 nmtui 命令修改,如下图,IP 设置为 192.168.56.102/24

在这里插入图片描述

5.4 重启虚拟机 rockylinux10-2 让 IP 修改生效

reboot
# 重启后 ssh root@192.168.56.102 后 ip a 查看到的 IP 应该是 192.168.56.102/24 了

5.5 设置 hotstname 主机名

hostnamectl set-hostname rockylinux10-2

5.4 重启虚拟机 rockylinux10-2

reboot
# 重启后 ssh root@192.168.56.102 后应该可以看到 [root@rockylinux10-2 ~]#

5.5 启动虚拟机 rockylinux10-1

5.6 解决虚拟机 rockylinux10-2 kubectl get nodes 报错的问题

在 rockylinux10-2 上,执行如下命令:

mkdir -p $HOME/.kube

在 rockylinux10-1 上,执行如下命令:

scp /etc/kubernetes/admin.conf root@192.168.56.102:/root/.kube/config

在 rockylinux10-2 上,执行如下命令:

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

# 再执行如下命令
[root@rockylinux10-2 ~]# kubectl get nodes
NAME             STATUS   ROLES           AGE   VERSION
rockylinux10-1   Ready    control-plane   57m   v1.36.3
[root@rockylinux10-2 ~]# kubectl get pod -n kube-system
NAME                                      READY   STATUS    RESTARTS        AGE
calico-kube-controllers-f5d69fdff-rftz6   1/1     Running   1 (7m42s ago)   54m
calico-node-8bx86                         1/1     Running   1 (7m42s ago)   54m
coredns-6b5f954497-622g2                  1/1     Running   1 (7m42s ago)   58m
coredns-6b5f954497-qhdpx                  1/1     Running   1 (7m42s ago)   58m
etcd-rockylinux10-1                       1/1     Running   1 (7m42s ago)   58m
kube-apiserver-rockylinux10-1             1/1     Running   1 (7m42s ago)   58m
kube-controller-manager-rockylinux10-1    1/1     Running   1 (7m42s ago)   58m
kube-proxy-vn7p7                          1/1     Running   1 (7m42s ago)   58m
kube-scheduler-rockylinux10-1             1/1     Running   1 (7m42s ago)   58m

5.7 rockylinux10-2 加入 rockylinux10-1

kubeadm join 192.168.56.101:6443 --token px86b6.tb097rswj6cpy09r \
	--discovery-token-ca-cert-hash sha256:85cd76a1b57e2c5eab17dfb5f7467a5f9205b59375754e9368edfbab87809188

5.8 虚拟机 rockylinux10-2 就算创建成功✌️👏👏👏

6 虚拟机 rockylinux10-3

参照虚拟机 rockylinux10-2 的创建过程,把虚拟机 rockylinux10-3 也创建好。注意,开工之前,先把虚拟机 rockylinux10-1 关闭掉。因为担心两个 IP 都是 192.168.56.101 的虚拟机,会相互冲突。

虚拟机 rockylinux10-3 的静态 IP 是 192.168.56.103 ,hostname 是 rockylinux10-3

7 虚拟机 rockylinux10-1 上修改 calico 的配置

虚拟机 rockylinux10-2 和 rockylinux10-3 加入 rockylinux10-1 之后,

kubectl get pod -n kube-system
# 发现有2个 calico 一直是 CrashLoopBackOff
calico-node-w67bb     0/1     CrashLoopBackOff   5 (35s ago)      6m14s
calico-node-zjhgz     0/1     CrashLoopBackOff   5 (48s ago)      6m37s

然后 kubectl logs -n kube-system calico-node-w67bb 发现

2026-08-01 07:13:57.649 [INFO][9] startup/startup.go 517: Clearing out-of-date IPv4 address from this node IP="10.0.3.15/24"
2026-08-01 07:13:57.651 [ERROR][9] startup/startup.go 368: Failed to configure IP addresses error=conflicting node detected: IPv4 address conflict
2026-08-01 07:13:57.651 [WARNING][9] startup/utils.go 47: Terminating
Calico node failed to start

根据日志,Calico 启动失败是因为 IP 地址冲突:worker 节点 rockylinux10-3 的 enp0s9 接口 IP 10.0.3.15/24 已被 master 节点 rockylinux10-1 占用。Calico 要求每个节点的 IP 必须唯一。

解决方案:让 Calico 使用 enp0s8 接口(Host-Only 网络)。也就是要修改 Calico DaemonSet,指定 IP 检测接口。在 rockylinux10-1 节点执行以下命令,编辑 Calico DaemonSet:

kubectl edit daemonset calico-node -n kube-system

spec.template.spec.containers[0].env 中添加环境变量(如果已存在则修改):

- name: IP_AUTODETECTION_METHOD
  value: "interface=enp0s8"

保存并退出。等待 Pod 滚动更新并验证:

kubectl get pod -n kube-system -w | grep calico-node

当所有 calico-node Pod 变为 RunningREADY 1/1 后,检查节点状态:

kubectl get nodes

应该全部为 Ready。

8 安装 Headlamp

Headlamp 是 k8s 官方推荐的,用于替代 Dashboard 的东东。

8.1 使用 yaml 安装

8.1.1 执行 kubectl apply -f 命令

可以使用 yaml 安装 Headlamp,在虚拟机 rockylinux10-1 上执行:

kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/headlamp/main/kubernetes-headlamp.yaml
# 会输出
service/headlamp created
deployment.apps/headlamp created
secret/headlamp-admin created

如果访问 github 失败的话,可以把 https://github.com/kubernetes-sigs/headlamp.git 导入到 gitee 中,然后下载标签是 v0.44.0 的代码 zip 包,然后把 kubernetes-headlamp.yaml 通过 scp 命令传到虚拟机 rockylinux10-1 上,然后再:

kubectl apply -f kubernetes-headlamp.yaml

8.1.2 使用 NodePort 暴露服务

将 Headlamp Service 类型改为 NodePort:

kubectl patch svc headlamp -n kube-system -p '{"spec":{"type":"NodePort"}}'

查看分配的 NodePort 端口:

kubectl get svc -n kube-system headlamp
# 比如输出
NAME       TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
headlamp   NodePort   10.1.151.236   <none>        80:31293/TCP   11m

在宿主机的浏览器上,通过 http://192.168.56.101:31293 访问即可

8.1.3 创建访问 Token

Headlamp 本身不提供用户系统,而是直接使用 Kubernetes 的 RBAC 权限进行认证。所以需要创建一个 ServiceAccount 并获取其 Token 用于登录。

8.1.3.1 创建 ServiceAccount

创建一个拥有管理员权限的 ServiceAccount(例如 headlamp-admin):

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: headlamp-admin
  namespace: kube-system
EOF
8.1.3.2 绑定 cluster-admin 权限

为刚才创建的 ServiceAccount 绑定集群管理员权限:

cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: headlamp-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: headlamp-admin
  namespace: kube-system
EOF
8.1.3.3 生成 Token

生成一个长期有效的 Token(例如 1 年)。如果不指定 --duration,默认 Token 有效期为 1 小时,建议延长。

kubectl create token headlamp-admin -n kube-system --duration=8760h
# 比如输出
eyJhbGciOiJSUzI1NiIsImtpZCI6Ik5fTHZYa2dERU8zMGxrQm1NaDRwVXhuajVKeGFaa3NRVXhtbGxsM1hOMEkifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxODE3MTA2ODUyLCJpYXQiOjE3ODU1NzA4NTIsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiYmNlNmJlMjUtZTYxYS00MGZjLWJkNDQtNDI1MzFjYjJhMDIxIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJoZWFkbGFtcC1hZG1pbiIsInVpZCI6ImI2ZWE0YmFkLTFjYzAtNDM4ZS05ZjYwLTc3MDJjYzZkYWU1YyJ9fSwibmJmIjoxNzg1NTcwODUyLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06aGVhZGxhbXAtYWRtaW4ifQ.Qy7__DIgbhHD2iNoPDA9RZlSGprqMj-DQWydWUpd5BDKmf1aB4FxZ9dJH4w8CVuj6CBs1f044vfnkvBwjudl1hsxvQ4dJ6eRFuuWUGOG_Cj80i54N4XaXIErzhwa_IwezXifeAAR2TsfUqPZLvzCA-7lsDD0Ky91SROi0iPporivmeUeHxXQgi8E2UT3HewC7UAWTB3qu2aORQu5C1zRQN6Mz7EvQVA6brITxCX_kJhX_FUq_o66V-2A8vdfk2FtKm-6Hldmmp_vE-lq4bgri1pw0cusod9Wlp5GfY8dvmNhF86kMqSAyztBIDeHFEgwvw3biutBC_SGABeVT6L_1g

8.1.4 在宿主机的浏览器上访问

如果浏览器上访问失败,那么利用 SSH 隧道,将虚拟机上的 Headlamp Service 端口(NodePort 31293)转发到宿主机本地端口

在宿主机终端执行:

# 下面的第一个 31293 表示的是宿主机的端口;
# 第二个 31293 表示的是虚拟机的端口
ssh -L 31293:localhost:31293 root@192.168.56.101

然后浏览器访问 http://localhost:31293 ,然后输入前面的 token 即可,登录成功后,如下图:

在这里插入图片描述

8.1.5 如何得知 Headlamp 被安装到了哪个节点?

执行如下命令:

[root@rockylinux10-1 ~]# kubectl get pod -n kube-system -o wide | grep headlamp
headlamp-bdbf5b45d-kn4f5                  1/1     Running   0              19m     10.244.145.1     rockylinux10-3   <none>           <none>

🙏完结撒花🎉感谢阅读

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐