本文章仍在施工中。。。。
# 搭建 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 提供容器运行能力。
以下介绍 docker 和 containerd 安装方法,任选一种即可
# 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 能正常创建。
设置
containerd的cgroup驱动为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 做一些基础检查
排查发现,当前集群所有节点 NotReady , coredns 处于 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 不是 “绑定某一个节点的端口”,而是:
- K8s 会在集群所有节点(Master/Node1/Node2)上监听同一个 NodePort 端口;
- 这个端口由每个节点上的
kube-proxy进程负责监听(在所有节点用ss -tnlp | grep < 端口号 >能看到 kube-proxy 监听); - 无论访问哪个节点的这个端口,
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 转发,核心逻辑:
- 当用户访问
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)。
- Master 节点的
- 同理,访问
192.168.0.201:30773(Node1)时:- Node1 的
kube-proxy也会通过 iptables 规则,把流量转发到 Node2 上的 nginx Pod。
- Node1 的
# 基于 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 网络互通」:
- flannel 会为每个节点分配一个 Pod 网段(比如 Node2 是 10.244.2.0/24),并在所有节点配置路由规则;
- 当 Master/Node1 要访问 Node2 上的 Pod IP(10.244.2.2)时,flannel 会通过 VXLAN 隧道(基于节点间的物理网络),把流量封装后发送到 Node2;
- 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 ,我们还需要做以下操作
将 master 节点中
/etc/kuber/admin.config拷贝到从节点对应目录下在从节点上配置环境变量
# 使用 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 |