本文章仍在施工中。。。。

# 搭建 K8S 集群

操作系统:Centos7.9

Docker:20+

K8S:1.23.6

# 搭建 Kubernetes 集群

# 初始化操作

创建好虚拟机后,对三台虚拟机都进行进行初始化操作

在操作时能够听到喇叭提示音,如果不想听到提示音,可以通过在内核模块中移除 pcspkr 模块来完全禁用 PC 喇叭: rmmod pcspkr

# 修改主机名称

[root@localhost ~]# hostnamectl set-hostname Kubernetes-Master
[root@localhost ~]# hostnamectl
   Static hostname: kubernetes-master
   Pretty hostname: Kubernetes-Master
         Icon name: computer-vm
           Chassis: vm
        Machine ID: 4e60493f0355468f9c57a968a0973490
           Boot ID: 0b3047df130f40b1b7c29e7e8738f812
    Virtualization: vmware
  Operating System: CentOS Linux 7 (Core)
       CPE OS Name: cpe:/o:centos:centos:7
            Kernel: Linux 3.10.0-1160.el7.x86_64
      Architecture: x86-64

# 修改 IP 地址

[root@kubernetes-master ~]# vim /etc/sysconfig/network-scripts/ifcfg-ens33
[root@kubernetes-master ~]# systemctl restart network

# 关闭 firewalld

[root@localhost ~]# systemctl stop firewalld.service
[root@localhost ~]# systemctl disable firewalld.service
Removed symlink /etc/systemd/system/multi-user.target.wants/firewalld.service.
Removed symlink /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.

# 关闭 selinux

# 临时关闭
[root@localhost ~]# setenforce 0
# 永久关闭
[root@localhost ~]# sed -i's/enforcing/disabled/' /etc/selinux/config

# 关闭 swap

# 临时关闭
[root@localhost ~]# swapoff -a
# 永久关闭
[root@localhost ~]# sed -ri's/.*swap.*/#&/' /etc/fstab
# 重启虚拟机
[root@localhost ~]# reboot

# 添加 hosts

[root@kubernetes-master ~]# cat >> /etc/hosts << EOF
192.168.0.200 kubernetes-master
192.168.0.201 kubernetes-node1
192.168.0.202 kubernetes-node2
EOF

# 加载 br_netfilter

配置的 br_netfilter 实现桥接流量透传,将桥接的 IPV4 流量传递到 iptables 的链,加载 br_netfilter 内核模块

[root@kubernetes-master ~]# cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
[root@kubernetes-master ~]# modprobe br_netfilter
[root@kubernetes-master ~]# echo "br_netfilter" > /etc/modules-load.d/k8s.conf
[root@kubernetes-master ~]# sysctl --system

# 开启 ip_forward

配置 net.ipv4.ip_forward 实现 IP 层转发,docker 部署时会自动开启, containerd 需要手动开启

# 修改 sysctl 配置文件
cat >> /etc/sysctl.conf << EOF
net.ipv4.ip_forward = 1
EOF
# 重载 sysctl 配置使修改生效
sysctl -p /etc/sysctl.conf
# 验证是否开启(输出 net.ipv4.ip_forward = 1)
sysctl net.ipv4.ip_forward

# 配置 yum 源

[root@kubernetes-master ~]# mv /etc/yum.repos.d/CentOS-Base.repo/etc/yum.repos.d/CentOS-Base.repo.bak
[root@kubernetes-master ~]# cd /etc/yum.repos.d/
[root@kubernetes-master yum.repos.d]# wget -nc https://mirrors.aliyun.com/repo/Centos-7.repo
[root@kubernetes-master yum.repos.d]# mv Centos-7.repo CentOS-Base.repo
[root@kubernetes-master yum.repos.d]#
# 设置 docker 仓库(阿里云镜像)
[root@kubernetes-master ~]# yum install -y yum-utils
[root@kubernetes-master ~]# yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
[root@kubernetes-master yum.repos.d]# yum clean all
[root@kubernetes-master yum.repos.d]# yum update -y
[root@kubernetes-master yum.repos.d]# yum list
[root@kubernetes-master yum.repos.d]# yum makecache

# 设置时间

[root@kubernetes-master ~]# timedatectl set-ntp true
[root@kubernetes-master ~]# timedatectl set-timezone Asia/Shanghai
[root@kubernetes-master ~]# timedatectl status

# container-runtime

container-runtime(容器运行时)是 Kubernetes 集群中负责管理容器生命周期(创建、启动、停止、销毁)的底层软件,需遵循 K8S 定义的容器运行时接口(CRI)标准,为 Pod 内的容器提供隔离的运行环境。常见的 container-runtime 包括:

  • containerd:目前 K8S 默认推荐的容器运行时(从 1.24 版本起移除对 Docker 的直接支持),轻量且稳定,专注于容器生命周期管理;
  • CRI-O:专为 K8S 设计的轻量级运行时,完全兼容 CRI 标准,无 Docker 依赖;
  • Docker:早期主流运行时,因未原生实现 CRI(需通过 dockershim 适配),现已被 K8S 逐步弃用,实际底层依赖 containerd 提供容器运行能力。

以下介绍 dockercontainerd 安装方法,任选一种即可

# docker

# 安装 docker

yum install -y \
  docker-ce-20.10.24 \
  docker-ce-cli-20.10.24 \
  containerd.io-1.6.20 \
  --nogpgcheck

# 设置 docker 开机启动

[root@kubernetes-master ~]# systemctl start docker
[root@kubernetes-master ~]# systemctl enable docker
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.
[root@kubernetes-master ~]#
[root@kubernetes-master ~]# docker version
Client: Docker Engine - Community
 Version:           20.10.24
 API version:       1.41
 Go version:        go1.19.7
 Git commit:        297e128
 Built:             Tue Apr  4 18:22:57 2023
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true
Server: Docker Engine - Community
 Engine:
  Version:          20.10.24
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.19.7
  Git commit:       5d6db84
  Built:            Tue Apr  4 18:21:02 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.20
  GitCommit:        2806fc1057397dbaeefbea0e4e17bddfbd388f38
 runc:
  Version:          1.1.5
  GitCommit:        v1.1.5-0-gf19387a
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
[root@kubernetes-master ~]#

# 配置镜像源 & 修改 cgroup

配置 docker 的镜像源,同时 Docker 的 cgroup 驱动改为 systemd,和 kubelet 一致

[root@kubernetes-master ~]# mkdir -p /etc/docker  
[root@kubernetes-master ~]# vim /etc/docker/daemon.json
# /etc/docker/daemon.json
{
  "registry-mirrors": ["https://docker.1ms.run"], 
  "exec-opts": ["native.cgroupdriver=systemd"]
}
[root@kubernetes-master ~]# systemctl daemon-reload
[root@kubernetes-master ~]# systemctl restart docker
[root@kubernetes-master ~]#
# 验证 Docker 驱动已改为 systemd(输出必须是 systemd)
[root@kubernetes-master ~]# docker info | grep -i cgroup
 Cgroup Driver: systemd
 Cgroup Version: 1

# containerd

# 安装 containerd

# 安装依赖
yum install -y yum-utils device-mapper-persistent-data lvm2
# 配置 containerd yum 源
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 安装 containerd
yum install -y containerd.io-1.6.20 --nogpgcheck

# 配置 containerd

# 生成默认配置文件
containerd config default > /etc/containerd/config.toml
# 修改配置:替换 sandbox 镜像(国内源)、设置 cgroup 驱动为 systemd、配置镜像加速
vim /etc/containerd/config.toml
# 1. 替换 sandbox
    sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.6"
# 2. 配置镜像加速
      [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
          [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
                 endpoint = ["https://docker.1ms.run"]
# 3. 设置 cgroup 驱动为 systemd
sed -i's/SystemdCgroup \= false/SystemdCgroup \= true/g' /etc/containerd/config.toml
# 启动并设置开机自启 containerd
systemctl start containerd
systemctl enable containerd
# 验证 containerd 版本(输出 1.6.20)
containerd --version
# 验证 cgroup 驱动(输出 SystemdCgroup = true)
grep -n "SystemdCgroup" /etc/containerd/config.toml

修改 /etc/containerd/config.toml 的配置内容:

  • 替换 sandbox 镜像(pause 镜像)地址: [plugins."io.containerd.grpc.v1.cri".sandbox_image]sandbox_image 字段的 "k8s.gcr.io/pause:3.6" 替换为 "registry.aliyuncs.com/google_containers/pause:3.6"

pause 镜像 是 K8S 每个 Pod 的基础根容器,原地址 k8s.gcr.io 国内无法访问,替换为阿里云镜像源,保证 Pod 能正常创建。

  • 设置 containerdcgroup 驱动为 systemd[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] 段落的 SystemdCgroup 字段设置为 true ,让 containerd 的 cgroup 驱动和 kubelet 保持一致

  • 添加 Docker 镜像加速: [plugins."io.containerd.grpc.v1.cri".registry.mirrors] 段落下方新增镜像地址

[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
    endpoint = ["https://docker.1ms.run"]  # 新增的镜像加速地址

# 指定 sock 文件

K8s 1.24 及以上版本已移除 dockershim (Docker 与 K8s 的适配层),默认容器运行时改为 containerd ,但 crictl 的默认配置仍指向 dockershim.sock ,导致连接失败;需配置 crictl 指向 containerd 的正确套接字

# 创建 crictl 配置文件
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 ps 即可正常输出
[root@kubernetes-node2 ~]# crictl ps
CONTAINER           IMAGE               CREATED             STATE               NAME                ATTEMPT             POD ID              POD
1b0e6c805fe95       e237e85065092       2 hours ago         Running             kube-flannel        1                   aa524eec34630       kube-flannel-ds-jlmrb
0e8ababb73adb       4c03754524064       2 hours ago         Running             kube-proxy          1                   d3b955d0325af       kube-proxy-ph76x
[root@kubernetes-node2 ~]#

# 安装 kubernetes

[root@kubernetes-master ~]# cat > /etc/yum.repos.d/kubernetes.repo << EOF
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-key.gpg
EOF
[root@kubernetes-master ~]# yum install -y kubelet-1.23.6 kubeadm-1.23.6 kubectl-1.23.6
[root@kubernetes-master ~]# systemctl enable kubelet && systemctl start kubelet

# Master 节点初始化

  • docker:在 Master 节点上进行初始化操作,docker 按照以下执行
[root@kubernetes-master ~]# kubeadm init --apiserver-advertise-address=192.168.0.200 --image-repository registry.aliyuncs.com/google_containers --kubernetes-version v1.23.6 --service-cidr=10.96.0.0/12 --pod-network-cidr=10.244.0.0/16
# 安装完成后检查
# 查看 kubelet 状态(应为 active (running))
[root@kubernetes-master ~]# systemctl status kubelet
# 验证健康检查端口(10248 通)
[root@kubernetes-master ~]# curl -sSL http://localhost:10248/healthz  # 输出 ok
# 查看控制平面容器(应有 kube-apiserver/etcd 等)
[root@kubernetes-master ~]# docker ps | grep kube
  • containerd:在 Master 节点上进行初始化操作,containerd 按照以下执行
# 初始化集群,指定 containerd 的套接字路径
[root@kubernetes-master ~]# kubeadm init \
--apiserver-advertise-address=192.168.0.200 \
--image-repository registry.aliyuncs.com/google_containers \
--kubernetes-version v1.23.6 \
--service-cidr=10.96.0.0/12 \
--pod-network-cidr=10.244.0.0/16 \
--cri-socket=unix:///run/containerd/containerd.sock
# 查看 kubelet 状态(应为 active (running))
[root@kubernetes-master ~]# systemctl status kubelet
# 验证健康检查端口(10248 通)
[root@kubernetes-master ~]# curl -sSL http://localhost:10248/healthz
# 查看控制平面容器(应有 kube-apiserver/etcd 等)
[root@kubernetes-master ~]# docker ps | grep kube

如果上述操作报错,按照如下流程处理

# 查看 kubelet 状态,大概率输出 active (failed) 或 inactive (dead)
systemctl status kubelet
# 查看详细日志
journalctl -xeu kubelet | grep -i error
# 常见报错关键词:
- Failed to load kubelet config file /var/lib/kubelet/config.yaml: no such file or directory 第一次 init 失败后该文件未生成 / 被清理。
- cgroup driver mismatch:Docker 和 kubelet 的 cgroup 驱动不一致;
- SELinux is enabled:SELinux 未关闭;
- read-only file system:文件系统权限问题;
- br_netfilter not loaded:内核模块未加载;
- port 10248 is in use:端口被占用(极少)。
# 重置 kubeadm 残留状态(清理失败的 init 残留)
kubeadm reset -f  # 强制重置,清空所有 k8s 残留配置
rm -rf /var/lib/kubelet/*  # 清空 kubelet 目录(包括无效配置)
rm -rf /etc/kubernetes/*   # 清空 k8s 证书 / 配置目录
# 修复 Docker 的 cgroup 驱动(改为 systemd,和 kubelet 一致)
cat > /etc/docker/daemon.json << EOF
{
  "registry-mirrors": ["https://docker.1ms.run"],
  "exec-opts": ["native.cgroupdriver=systemd"]
}
EOF
# 重启 Docker 生效,验证 Docker 驱动已改为 systemd(输出必须是 systemd)
systemctl daemon-reload
systemctl restart docker
docker info | grep -i cgroup
# 关闭 swap(kubelet 强制要求)
swapoff -a
sed -i '/swap/s/^/#/' /etc/fstab  # 永久关闭
# 加载 br_netfilter 内核模块
modprobe br_netfilter
echo "br_netfilter" > /etc/modules-load.d/k8s.conf
# 确保内核参数生效
sysctl --system
# 关闭 SELinux 和防火墙
setenforce 0
sed -i's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
systemctl stop firewalld && systemctl disable firewalld

# Master 节点初始化配置

[root@kubernetes-master ~]# mkdir -p $HOME/.kube
[root@kubernetes-master ~]# cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
[root@kubernetes-master ~]# chown $(id -u):$(id -g) $HOME/.kube/config

# Node 节点加入 Master 节点

将从节点加入主节点:

  • 在 Master 节点获取 kubeadm join 命令(包含 token 和哈希值)
  • 在 Node 节点执行 kubeadm join 命令,自动生成 kubelet 配置文件; kubeadm join 会自动启动 kubelet 并加入集群。

token 仅用于 Node 节点首次加入集群时的身份验证,一旦 Node 节点成功加入集群并生成了本地证书( /var/lib/kubelet/pki/ ),后续通信不再依赖 token—— 即使 token 过期,已加入的 Node 节点也能正常工作,无需重新 join。

# 在 master 上获取 `kubeadm join` 命令
# 查看 token 时间
[root@kubernetes-master ~]# kubeadm token list
# 方式 1:如果 Master 刚执行完 init,直接查看
[root@kubernetes-master ~]# kubeadm token create --print-join-command
# 方式 2:如果 token 过期,重新生成永久 token(有效期 365 天)
[root@kubernetes-master ~]# kubeadm token create --ttl 0  # 生成永不过期 token
[root@kubernetes-master ~]# openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed's/^.* //'  # 获取 ca 哈希值
# 在 node 节点上执行,join 命令(替换为 master 上的信息):
[root@kubernetes-node ~]# kubeadm join 192.168.0.200:6443 --token < 生成的 token> --discovery-token-ca-cert-hash sha256:<ca 哈希值 >

在 Node 节点执行 kubeadm join 命令

[root@kubernetes-node2 ~]# kubeadm join 192.168.0.200:6443 --token ah7f8n.4buw5tt3edn876ro --discovery-token-ca-cert-hash sha256:583c34ccb826e29c604bb4a1e204251ed0854e0fb37607c1f0153f4cc8f6dfdb
[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[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-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.
[root@kubernetes-node2 ~]#

在 master 节点上查看

[root@kubernetes-master ~]# kubectl get nodes
NAME                STATUS     ROLES                  AGE    VERSION
kubernetes-master   NotReady   control-plane,master   80m    v1.23.6
kubernetes-node1    NotReady   <none>                 22m    v1.23.6
kubernetes-node2    NotReady   <none>                 109s   v1.23.6
[root@kubernetes-master ~]#

# 部署 CNI 网络插件

在主节点上使用 kubectl get no 查看 node 状态,可以发现状态均为 NotReady ,现在先使用 kubectl 做一些基础检查

排查发现,当前集群所有节点 NotReadycoredns 处于 Pending 状态,核心原因是未部署网络插件(flannel/calico),K8s 集群必须部署网络插件才能完成节点网络初始化、Pod 网络分配,进而让节点进入 Ready 状态、 coredns 调度运行。

# 查看 node 信息
[root@kubernetes-master ~]# kubectl get nodes
NAME                STATUS     ROLES                  AGE   VERSION
kubernetes-master   NotReady   control-plane,master   96m   v1.23.6
kubernetes-node1    NotReady   <none>                 38m   v1.23.6
kubernetes-node2    NotReady   <none>                 17m   v1.23.6
# 查看组件状态,此为 kubectl get componentstatus 的缩写
[root@kubernetes-master ~]# kubectl get cs
Warning: v1 ComponentStatus is deprecated in v1.19+
NAME                 STATUS    MESSAGE                         ERROR
scheduler            Healthy   ok
controller-manager   Healthy   ok
etcd-0               Healthy   {"health":"true","reason":""}
[root@kubernetes-master ~]#
# 查看命名空间
[root@kubernetes-master ~]# kubectl get namespaces
NAME              STATUS   AGE
default           Active   94m
kube-node-lease   Active   94m
kube-public       Active   94m
kube-system       Active   94m
# 查看 kube-system 下的 pod 信息
[root@kubernetes-master ~]# kubectl get pod -n kube-system
NAME                                        READY   STATUS    RESTARTS      AGE
coredns-6d8c4cb4d-2jffp                     0/1     Pending   0             94m
coredns-6d8c4cb4d-lwcpw                     0/1     Pending   0             94m
etcd-kubernetes-master                      1/1     Running   1 (86m ago)   95m
kube-apiserver-kubernetes-master            1/1     Running   1 (86m ago)   95m
kube-controller-manager-kubernetes-master   1/1     Running   1 (86m ago)   95m
kube-proxy-2dd5k                            1/1     Running   0             37m
kube-proxy-8tcp2                            1/1     Running   1 (86m ago)   94m
kube-proxy-dljq6                            1/1     Running   0             16m
kube-scheduler-kubernetes-master            1/1     Running   1 (86m ago)   95m
[root@kubernetes-master ~]#

# 部署 flannel

下载 flannel 配置文件

[root@kubernetes-master ~]# mkdir /opt/k8s
[root@kubernetes-master ~]# mkdir /opt/k8s/flannel
[root@kubernetes-master ~]# cd /opt/k8s/flannel/
[root@kubernetes-master flannel]# curl -o kube-flannel.yml https://raw.githubusercontent.com/flannel-io/flannel/v0.19.0/Documentation/kube-flannel.yml

查看配置文件中使用的镜像源

# 配置文件里实际生效的镜像是国内 rancher 源,无需替换
[root@kubernetes-master flannel]# grep -n "image:" kube-flannel.yml | grep -v "#"
126:        image: rancher/mirrored-flannelcni-flannel-cni-plugin:v1.1.0
138:        image: rancher/mirrored-flannelcni-flannel:v0.18.1
153:        image: rancher/mirrored-flannelcni-flannel:v0.18.1

部署网络插件

[root@kubernetes-master flannel]# kubectl apply -f kube-flannel.yml

验证 flannel 部署状态

# 查看 flannel 命名空间下的 DaemonSet(期望数 = 节点数,当前数逐步匹配)
[root@kubernetes-master flannel]# kubectl get ds -n kube-flannel
NAME              DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
kube-flannel-ds   3         3         0       3            0           <none>          9s
# 查看 flannel Pod(等待 30 秒,状态变为 Running)
[root@kubernetes-master flannel]# kubectl get pods -n kube-flannel
NAME                    READY   STATUS    RESTARTS   AGE
kube-flannel-ds-qqg7p   1/1     Running   0          2m34s
kube-flannel-ds-vt655   1/1     Running   0          2m34s
kube-flannel-ds-zj5xl   1/1     Running   0          2m34s
# 等待 1-2 分钟(flannel 配置节点网络需要时间),查看集群节点状态
[root@kubernetes-master flannel]# kubectl get nodes
NAME                STATUS   ROLES                  AGE    VERSION
kubernetes-master   Ready    control-plane,master   129m   v1.23.6
kubernetes-node1    Ready    <none>                 71m    v1.23.6
kubernetes-node2    Ready    <none>                 50m    v1.23.6
# 查看 pods 状态(coredns 从 Pending → Running)
[root@kubernetes-master flannel]#  kubectl get pods -n kube-system
NAME                                        READY   STATUS    RESTARTS       AGE
coredns-6d8c4cb4d-2jffp                     1/1     Running   0              134m
coredns-6d8c4cb4d-lwcpw                     1/1     Running   0              134m
etcd-kubernetes-master                      1/1     Running   1 (126m ago)   134m
kube-apiserver-kubernetes-master            1/1     Running   1 (126m ago)   134m
kube-controller-manager-kubernetes-master   1/1     Running   1 (126m ago)   134m
kube-proxy-2dd5k                            1/1     Running   0              76m
kube-proxy-8tcp2                            1/1     Running   1 (126m ago)   134m
kube-proxy-dljq6                            1/1     Running   0              55m
kube-scheduler-kubernetes-master            1/1     Running   1 (126m ago)   134m

# 运行测试

创建一个简单的 nginx 服务,测试 kubernetes 集群

# 创建 nginx 服务
[root@kubernetes-master ~]# kubectl create deployment nginx --image=nginx
deployment.apps/nginx created
# 暴露端口
[root@kubernetes-master ~]# kubectl expose deployment nginx --port=80 --type=NodePort
service/nginx exposed
# 查看 pod 和 service 信息
[root@kubernetes-master ~]# kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
nginx-85b98978db-lhmzl   1/1     Running   0          20s
[root@kubernetes-master ~]# kubectl get pod,svc
NAME                         READY   STATUS    RESTARTS   AGE
pod/nginx-85b98978db-lhmzl   1/1     Running   0          24s
NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT (S)        AGE
service/kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP        3h20m
service/nginx        NodePort    10.101.21.137   <none>        80:32639/TCP   13s

# 错误排查

在创建 nginx 容器时,我们发现 pod 状态为 ImagePullBackOff ,使用 kubectl describe 可以查看详情信息,根据如下信息,可以看出是因为 docker 一直在拉取 "nginx" 镜像,可能是网络原因导致。

[root@kubernetes-master ~]# kubectl get pod
NAME                     READY   STATUS             RESTARTS   AGE
nginx-85b98978db-xmx57   0/1     ImagePullBackOff   0          9m2s
[root@kubernetes-master ~]# kubectl describe pod nginx-85b98978db-xmx57
Name:             nginx-85b98978db-xmx57
Namespace:        default
Priority:         0
Service Account:  default
Node:             kubernetes-node1/192.168.0.201
Start Time:       Sun, 21 Dec 2025 22:56:37 +0800
Labels:           app=nginx
                  pod-template-hash=85b98978db
Annotations:      <none>
Status:           Pending
IP:               10.244.1.2
IPs:
  IP:           10.244.1.2
Controlled By:  ReplicaSet/nginx-85b98978db
Containers:
  nginx:
    Container ID:
    Image:          nginx
    Image ID:
    Port:           <none>
    Host Port:      <none>
    State:          Waiting
      Reason:       ImagePullBackOff
    Ready:          False
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-zcrdc (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             False
  ContainersReady   False
  PodScheduled      True
Volumes:
  kube-api-access-zcrdc:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type     Reason     Age                  From               Message
  ----     ------     ----                 ----               -------
  Normal   Scheduled  9m29s                default-scheduler  Successfully assigned default/nginx-85b98978db-xmx57 to kubernetes-node1
  Warning  Failed     58s                  kubelet            Failed to pull image "nginx": rpc error: code = Unknown desc = context canceled
  Warning  Failed     58s                  kubelet            Error: ErrImagePull
  Normal   BackOff    57s                  kubelet            Back-off pulling image "nginx"
  Warning  Failed     57s                  kubelet            Error: ImagePullBackOff
  Normal   Pulling    42s (x2 over 9m28s)  kubelet            Pulling image "nginx"
[root@kubernetes-master ~]#

# 删除 Deployment

如果需要删除 nginx Deployment,可以使用

kubectl delete deployment nginx

删除 Deployment 后,Pod 显示 Terminating 状态但未消失,是因为 K8s 的 优雅终止机制(默认等待 30 秒),或 Pod 存在「终止阻塞」(如镜像拉取进程未退出、网络资源未释放)。如果需要强制删除

# --grace-period=0:跳过 30 秒优雅等待,立即终止;--force:强制删除(针对卡住的 Pod)。
kubectl delete pod nginx-85b98978db-xmx57 --force --grace-period=0
# 清理残留的 Service
kubectl delete svc nginx

# 手动加载镜像

针对上述 nginx 拉取慢的问题,我们可以尝试在 master 节点先拉取镜像,然后将 master 的镜像通过 scp 传入各 node 节点

在 Master 节点拉取镜像并保存

# 拉取 nginx 镜像
docker pull nginx:latest
# 保存镜像为压缩包
docker save nginx:alpine > nginx.tar

拷贝镜像到所有 Node 节点

scp nginx.tar root@kubernetes-node1:/opt/
scp nginx.tar root@kubernetes-node2:/opt/

在每个 Node 节点加载镜像

# 登录 node1 执行
docker load < /opt/nginx.tar
# 登录 node2 执行
docker load < /opt/nginx.tar

# 转发流量详解

通过 docker ps 可以看到,仅在 node2 上才有 nginx 的 pod,那为什么访问 3 台设备的对应端口都能看到 nginx 页面呢?原因是 K8s 的 kube-proxy 组件 + 网络插件(flannel) 共同实现的「集群级端口转发 + 跨节点网络互通」,这也是 K8s 区别于单机 Docker 的核心特性。

我们可以将流量走向拆分为三步:

NodePort 服务提供集群级端口暴露

示例中创建的 NodePort 类型 Service 不是 “绑定某一个节点的端口”,而是:

  1. K8s 会在集群所有节点(Master/Node1/Node2)上监听同一个 NodePort 端口;
  2. 这个端口由每个节点上的 kube-proxy 进程负责监听(在所有节点用 ss -tnlp | grep < 端口号 > 能看到 kube-proxy 监听);
  3. 无论访问哪个节点的这个端口, kube-proxy 都会把请求转发到 nginx Pod 所在的节点(Node2)。
# kube-proxy 监听所有节点的 NodePort
[root@kubernetes-master ~]# ss -tnlp | grep 32639
LISTEN     0      128          *:32639                    *:*                   users:(("kube-proxy",pid=2526,fd=13))

kube-proxy 的转发逻辑(iptables/IPVS 模式)

K8s 默认用 iptables 模式实现 Service 转发,核心逻辑:

  1. 当用户访问 192.168.0.200:30773 (Master):
    • Master 节点的 kube-proxy 已通过 K8s API Server 知道 nginx Pod 运行在 Node2(IP:10.244.2.2);
    • kube-proxy 预先在 Master 节点的 iptables 中配置了转发规则:将访问 30773 端口的流量,转发到 nginx Pod 的 IP:80(10.244.2.2:80)。
  2. 同理,访问 192.168.0.201:30773 (Node1)时:
    • Node1 的 kube-proxy 也会通过 iptables 规则,把流量转发到 Node2 上的 nginx Pod。
# 基于 iptables 规则的分层的逻辑, KUBE-SERVICES 会把 NodePort 流量转发到 KUBE-NODEPORTS 链
[root@kubernetes-master ~]# iptables -t nat -L KUBE-SERVICES
Chain KUBE-SERVICES (2 references)
target     prot opt source               destination
KUBE-SVC-2CMXP7HKUVJN7L6M  tcp  --  anywhere             10.101.21.137        /* default/nginx cluster IP */tcp dpt:http
KUBE-SVC-NPX46M4PTMTKRN6Y  tcp  --  anywhere             10.96.0.1            /* default/kubernetes:https cluster IP */tcp dpt:https
KUBE-SVC-TCOU7JCQXEZGVUNU  udp  --  anywhere             10.96.0.10           /* kube-system/kube-dns:dns cluster IP */udp dpt:domain
KUBE-SVC-ERIFXISQEP7F7OF4  tcp  --  anywhere             10.96.0.10           /* kube-system/kube-dns:dns-tcp cluster IP */tcp dpt:domain
KUBE-SVC-JD5MR3NA4I4DYORP  tcp  --  anywhere             10.96.0.10           /* kube-system/kube-dns:metrics cluster IP */tcp dpt:9153
KUBE-NODEPORTS  all  --  anywhere             anywhere             /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL
# 进一步查看 KUBE-NODEPORTS 链,确认会将访问 32639 端口的流量转发到 KUBE-SVC-2CMXP7HKUVJN7L6M 链,最终指向 nginx Pod 的 IP:80)。
[root@kubernetes-master ~]# iptables -t nat -L KUBE-NODEPORTS
Chain KUBE-NODEPORTS (1 references)
target     prot opt source               destination
KUBE-SVC-2CMXP7HKUVJN7L6M  tcp  --  anywhere             anywhere             /* default/nginx */tcp dpt:32639

flannel 保证跨节点网络互通

转发的流量要能从 Master/Node1 到达 Node2 上的 nginx Pod,依赖 flannel 实现的「Pod 网络互通」:

  1. flannel 会为每个节点分配一个 Pod 网段(比如 Node2 是 10.244.2.0/24),并在所有节点配置路由规则;
  2. 当 Master/Node1 要访问 Node2 上的 Pod IP(10.244.2.2)时,flannel 会通过 VXLAN 隧道(基于节点间的物理网络),把流量封装后发送到 Node2;
  3. Node2 收到流量后解封装,转发到本地的 nginx Pod。
# Node2 的 Pod 网段(10.244.2.0/24),Master 访问该网段的流量会通过 flannel.1 转发到 Node2
[root@kubernetes-master ~]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.0.1     0.0.0.0         UG    100    0        0 ens33
10.244.0.0      0.0.0.0         255.255.255.0   U     0      0        0 cni0
10.244.1.0      10.244.1.0      255.255.255.0   UG    0      0        0 flannel.1
10.244.2.0      10.244.2.0      255.255.255.0   UG    0      0        0 flannel.1
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
192.168.0.0     0.0.0.0         255.255.255.0   U     100    0        0 ens33

# 在任意节点使用 kubectl

根据 k8s 架构, kubectl 实际上是在通过 api-server 接口调用 k8s 服务,即 kubectl 命令实际上是在访问 api-server ,上述操作完成后,在 node 节点上使用 kubectl 我们能看到如下输出

[root@localhost containerd]# kubectl get node
The connection to the server localhost:8080 was refused - did you specify the right host or port?

因为在 master 节点初始化时,我们已经在 ~/.kube/config 文件中写入了关于 kubernetes 的配置信息,为了让从节点也能使用 kubectl ,我们还需要做以下操作

  1. 将 master 节点中 /etc/kuber/admin.config 拷贝到从节点对应目录下

  2. 在从节点上配置环境变量

# 使用 scp 将配置文件由主节点(192.168.0.200)拷贝到从节点
[root@kubernetes-node1 ~]# scp root@192.168.0.200:/etc/kubernetes/admin.conf/etc/kubernetes/admin.conf
admin.conf                                                                         100% 5641     5.4MB/s   00:00
[root@kubernetes-node1 ~]# echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bash_profile
[root@kubernetes-node1 ~]# source ~/.bash_profile
[root@kubernetes-node1 ~]# kubectl get node
NAME                STATUS   ROLES                  AGE   VERSION
kubernetes-master   Ready    control-plane,master   8d    v1.23.6
kubernetes-node1    Ready    <none>                 8d    v1.23.6
kubernetes-node2    Ready    <none>                 8d    v1.23.6

# Helm 安装

Helm 是 Kubernetes(K8s)的官方包管理器,类比于 Linux 系统的 apt/yum (Debian/Ubuntu/CentOS 包管理)、Python 的 pip 、Node.js 的 npm —— 它将 Kubernetes 中分散的多个资源(Deployment、Service、ConfigMap、Ingress 等)打包成一个可复用的 “软件包”(称为 Chart),解决了手动管理 K8s YAML 配置的痛点,实现应用的一键部署、版本升级、回滚、卸载。

# 安装 Helm

安装 Helm 可以参考 Helm 官方文档,此处选择兼容 K8s 1.23 的 Helm3 稳定版本(如 v3.14.0),适配 linux/amd64 架构

  • 下载 heml: wget https://get.helm.sh/helm-v3.2.3-linux-amd64.tar.gz
  • 解压 tar -zxvf helm-v3.2.3-linux-amd64.tar.gz
  • 将解压目录下的程序移动到 /usr/local/bin
  • 添加阿里云 heml 仓库
[root@kubernetes-master ~]# mkdir helm
[root@kubernetes-master ~]# cd helm/
# 下载 heml
[root@kubernetes-master helm]# wget https://get.helm.sh/helm-v3.14.0-linux-amd64.tar.gz
--2026-01-11 18:17:26--  https://get.helm.sh/helm-v3.2.3-linux-amd64.tar.gz
正在解析主机 get.helm.sh (get.helm.sh)... 13.107.213.74, 13.107.246.74, 2620:1ec:46::73, ...
正在连接 get.helm.sh (get.helm.sh)|13.107.213.74|:443... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度:12924654 (12M) [application/x-tar]
正在保存至: “helm-v3.2.3-linux-amd64.tar.gz”
# 解压
[root@kubernetes-master helm]# tar -zxvf helm-v3.2.3-linux-amd64.tar.gz
linux-amd64/
linux-amd64/README.md
linux-amd64/LICENSE
linux-amd64/helm
[root@kubernetes-master helm]# ls
helm-v3.2.3-linux-amd64.tar.gz  linux-amd64
# 将 heml 程序移动到 /usr/local/bin
[root@kubernetes-master helm]# cd linux-amd64/
[root@kubernetes-master linux-amd64]# ls
helm  LICENSE  README.md
[root@kubernetes-master linux-amd64]# cp helm /usr/local/bin/

设置 helm 自动补全

$ source < (helm completion bash)
$ echo'source <(helm completion bash)' >> ~/.bashrc
$ KUBECONFIG=/root/.kube/config