叩丁狼k8s - 组件篇

叩丁狼k8s - 组件篇

文章目录

其实很少用叩丁狼视频的部分了,大多数都是整理deepseek!!,大多是文章结构借鉴

一:搭建k8s集群

1:搭建方案(重点)

1.1:​minikube

minikube 是一个工具, 能让你在本地运行 Kubernetes。

minikube 在你的个人计算机(包括 Windows、macOS 和 Linux PC)上运行一个一体化(all-in-one)或多节点的本地 Kubernetes 集群,以便你来尝试 Kubernetes 或者开展每天的开发工作。

官方安装文档

1.2:kubeadm(重点)

你可以使用 kubeadm 工具来创建和管理 Kubernetes 集群。

该工具能够执行必要的动作并用一种用户友好的方式启动一个可用的、安全的集群。

安装 kubeadm 展示了如何安装 kubeadm 的过程。一旦安装了 kubeadm, 你就可以使用它来创建一个集群

🚀 服务器要求

最低配置:2核、2G内存、20G硬盘

最好能联网,不能联网的话需要有提供对应镜像的私有仓库

这里使用虚拟机模拟多节点:

  • k8s-master:192.168.113.120
  • k8s-node1:192.168.113.121
  • k8s-node2:192.168.113.122

🚀 软件环境

  • 操作系统:CentOS 7
  • Docker:20+
  • k8s:1.23.6

🚀 安装步骤

1️⃣ 为所有的节点进行初始化操作

shell 复制代码
# 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld

# 关闭selinux
sed -i 's/enforcing/disabled/' /etc/selinux/config  # 永久
setenforce 0  # 临时

# 关闭swap
swapoff -a  # 临时
sed -ri 's/.*swap.*/#&/' /etc/fstab    # 永久

# 关闭完swap后,一定要重启一下虚拟机!!!
# 根据规划设置主机名
hostnamectl set-hostname <hostname>

# 在master添加hosts
cat >> /etc/hosts << EOF
192.168.113.120 k8s-master
192.168.113.121 k8s-node1
192.168.113.122 k8s-node2
EOF


# 将桥接的IPv4流量传递到iptables的链
cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF

sysctl --system  # 生效


# 时间同步
yum install ntpdate -y
ntpdate time.windows.com

2️⃣ 为所有的节点安装基础软件

  • 安装docker并启动
  • 配置k8s镜像加速
  • 安装 kubeadm、kubelet、kubectl
  • 重启docker
shell 复制代码
# ---- 安装docker ----
# 这里我们安装docker的底层工具,会自动提示我们下载,很快就会完成了
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
# 增加阿里云的docker下载仓库,默认情况下,Docker的官方是从国外的服务器上下载的,下载速度是非常慢的,甚至失败
# 所以在这里我们是使用yum-config-manager组件来指定一个新的下载资源,指向的是阿里云的应用服务器,以此提高下载速度
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
sudo yum makecache fast # centos stream 9这个镜像没有fast参数,可以不要这个
sudo yum -y install docker-ce
# 启动docker
sudo service docker start
# 查看版本验证docker是否安装成功
docker version
# 准备配置aliyun镜像加速https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors

# ----- 配置k8s的镜像加速 -----
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-package-key.gpg
EOF

# ----- 安装 kubeadm、kubelet、kubectl ------
yum install -y kubelet-1.23.6 kubeadm-1.23.6 kubectl-1.23.6
# 允许kubelet开机自启
systemctl enable kubelet
# 配置关闭 Docker 的 cgroups,修改 /etc/docker/daemon.json,加入以下内容
"exec-opts": ["native.cgroupdriver=systemd"]
# 重启 docker
systemctl daemon-reload
systemctl restart docker

3️⃣ 部署k8s master,注意是在master节点上执行如下命令

shell 复制代码
# 在 Master 节点下执行, 通过kubeadm
kubeadm init \
      --apiserver-advertise-address=192.168.113.120 \
      --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

# 安装成功后,复制如下配置并执行
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
kubectl get nodes
shell 复制代码
# 这里参数说明一下
kubeadm init \
    --apiserver-advertise-address=192.168.113.120 \ # 指定master监听的地址,修改为自己的master地址
    --image-repository registry.aliyuncs.com/google_containers \ # 指定为aliyun的下载源,最好用国内的
    --kubernetes-version v1.23.6 \ # 指定k8s版本
    --service-cidr=10.96.0.0/12 \  # 设置集群内部的网络 - 这个不管你的ip是啥都不会变
    --pod-network-cidr=10.244.0.0/16 # 设置pod的网络 - 这个不管你的ip是啥都不会变
# service-cidr 和 pod-network-cidr 最好就用这个,不然需要修改后面的 kube-flannel.yaml 文件

# 这组命令的目的是为当前用户设置kubectl的配置文件,以便能够使用kubectl命令来管理Kubernetes集群。

# 首先,它确保.kube目录存在;
mkdir -p $HOME/.kube

# 然后,将集群管理员的配置文件复制到用户的.kube目录下,并重命名为config;
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config

# 最后,更改这个配置文件的所有权,使其属于当前用户。
sudo chown $(id -u):$(id -g) $HOME/.kube/config

4️⃣ 加入k8s node

下面的命令不能在master操作了,要在两个node(192.168.112.121, 192.168.112.122)中操作

shell 复制代码
# 分别在 k8s-node1 和 k8s-node2 执行
# 下方命令可以在 k8s master 控制台初始化成功后复制 join 命令
# 如果不小心清屏了这个命令就看不到了,所以最好先复制下来再清屏
# 如果超过了24小时,token也过期了,需要重新申请
kubeadm join \
    192.168.113.120:6443 \
    --token w34ha2.66if2c8nwmeat9o7 
    --discovery-token-ca-cert-hash sha256:20e2227554f8883811c01edd850f0cf2f396589d32b57b9984de3353a7389477


# 如果初始化的 token 不小心清空了,可以通过如下命令获取或者重新申请
# 如果 token 已经过期,就重新申请
kubeadm token create

# token 没有过期可以通过如下命令获取
kubeadm token list

# 获取 --discovery-token-ca-cert-hash 值,得到值后需要在前面拼接上 sha256:
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | \
openssl dgst -sha256 -hex | sed 's/^.* //'

5️⃣ 部署 CNI 网络插件

此时我们发现节点状态都是noReady的,同时使用kubectl get pods -n kube-system发现有的没有就绪

shell 复制代码
# 在 master 节点上执行
# 下载 calico 配置文件,可能会网络超时
curl https://docs.projectcalico.org/manifests/calico.yaml -O

# 修改 calico.yaml 文件:
# 1. CALICO_IPV4POOL_CIDR 配置,修改为与初始化的 cidr 相同
# 2. IP_AUTODETECTION_METHOD 下的网卡名称
# 3. 删除镜像 docker.io/ 前缀,避免下载过慢导致失败
sed -i 's#docker.io/##g' calico.yaml

# 执行yaml
kubectl apply -f calico.yaml

安装完CNI网络插件后,三个节点的状态就变成Ready了

同时 kubectl get pod -n kube-system 查看pod状态,可以看到,状态都是Running运行状态。

🚀 然后检查master节点(非健康状态才执行下面的内容)

使用 kubectl get cs 检查master节点状态

如果看到controller-managerscheduler 的状态为 unhealthy,表示不健康的状态。此时我们需要修改配置文件

shell 复制代码
vim /etc/kubernetes/manifests/kube-scheduler.yaml
将 --port=0注释掉

vim /etc/kubernetes/manifests/kube-controller-manager.yaml
将 --port=0注释掉

然后我们再次查询服务是否正常:

  • 使用 kubectl get pods -A 命令查询所有pod是否正常运行
  • 使用 kubectl get cs 命令查询master是否正常
  • 使用 kubectl get nodes 命令查询node节点是否ready

6️⃣ 测试

这里测试下部署nginx, 使用下面的命令部署之后发现在3个节点访问nginx都可以访问到

shell 复制代码
# 创建部署
# 创建一个无状态的应用集群nginx, 指定使用的镜像是nginx
kubectl create deployment nginx --image=nginx

# 暴露端口
# expose暴露80端口
kubectl expose deployment nginx --port=80 --type=NodePort

# 查看 pod 以及服务信息
kubectl get pod,svc

2:kubectl(重点)

Kubernetes 提供 kubectl 是使用 Kubernetes API 与 Kubernetes 集群的控制面板进行通信的命令行工具。

命令非常多,官方命令文档如下:命令

但是肯定不需要全部记住那么多命令,现用现搜索,脑子记住几个关键的就行, 熟练使用--help

shell 复制代码
# 所有命令的目录
$ kubectl --help

# 获取指定命令的详细信息(假设要查询get的详细信息)
$ kubectl get --help

🚀 在工作节点中使用

当前的集群中只有master节点是能够使用kubectl的,要想node节点也能使用,需要配置如下的内容:

1️⃣ 将 master 节点中 /etc/kubernetes/admin.conf 拷贝到需要运行的服务器的 /etc/kubernetes 目录中

shell 复制代码
# 将本地计算机上的 Kubernetes 集群管理配置文件 admin.conf,安全地复制到名为 k8s-node1 的远程服务器的指定目录下
# scp -> 用于在网络上安全地在主机之间传输文件的命令行工具, 通过SSH协议进行通信
# /etc/kubernetes/admin.conf -> 这是源文件路径,即你要复制的"是什么"和"从哪里来"
# root@k8s-node1 -> 目标主机的认证信息,即"复制到哪里去"
#     指定使用 root 用户身份登录到远程主机。因为要向 /etc/kubernetes 系统目录写入文件,通常需要 root 权限
#   这是远程主机的主机名或 IP 地址。你的本地计算机必须能够通过这个主机名解析到正确的 IP 地址
#   @ 和 ::这是固定的语法分隔符。@ 前面是用户名,后面是主机名;: 后面是目标路径
# /etc/kubernetes -> 是目标路径,即文件在远程主机上"放在哪里"。
scp /etc/kubernetes/admin.conf root@k8s-node1:/etc/kubernetes
scp /etc/kubernetes/admin.conf root@k8s-node2:/etc/kubernetes

2️⃣ 在对应的服务器上(node1, node2)配置环境变量

shell 复制代码
# 将admin.conf写入到环境变量中
echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bash_profile

# 刷新
source ~/.bash_profile
2.1:对资源操作

这里简单过一下就行,常用的就是get/create/scale/delete...

前置需要掌握的

🎉 平时接触最多的五类对象,都有对应的别名,可以在输入命令行的时候输入别名简写

资源名称 别名
pods po
nodes no
namespace ns
deployment deploy
services svc

🎉 操作选项说明:

操作选项 含义 说明
-f file 指定使用的资源清单
-o output 指定输出指定的资源清单
-p patch 直接指定json/yaml格式的字符串,定义具体的修改内容
-l label 指定标签

🎉 对于-o选项表示格式化输出的形式

语法 说明
-o json 输出 json 格式
-o name 仅打印资源名称
-o wide 以纯文本格式输出所有信息
-o yaml 输出 yaml 格式

对象创建相关的操作 - create / run

shell 复制代码
# kubectl create -f 资源清单名/资源清单路径  --> 通过资源清单创建资源
# kubectl run 实例名 --image=镜像名       --> 启动 xxx 实例
# kubectl explain 资源名  --> 获取指定资源的文档说明

$ kubectl create -f ./my-manifest.yaml           # 使用my-manifest.yaml创建资源
$ kubectl create -f ./my1.yaml -f ./my2.yaml     # 使用my1.yaml,my2.yaml多个文件创建资源
$ kubectl create -f ./dir                        # 使用目录下的所有清单文件来创建资源
$ kubectl create -f https://git.io/vPieo         # 使用 url 来创建资源
$ kubectl run nginx --image=nginx                # 启动一个 nginx 实例
$ kubectl explain pods,svc                       # 获取 pod 和 svc 的文档

显示和查找资源 - get

shell 复制代码
# kubectl get -> 列出指定的资源,可以跟一些附加参数规定命名空间,详细信息等等
$ kubectl get services                          # 列出所有 namespace 中的所有 service
$ kubectl get pods --all-namespaces             # 列出所有 namespace 中的所有 pod
$ kubectl get pods -o wide                      # 列出所有 pod 并显示详细信息
$ kubectl get deployment my-dep                 # 列出指定 deployment
$ kubectl get pods --include-uninitialized      # 列出该 namespace 中的所有 pod 包括未初始化的
$ kubectl get services --sort-by=.metadata.name  # List Services Sorted by Name
$ kubectl get pods --sort-by='.status.containerStatuses[0].restartCount'  # 根据重启次数排序列出 pod
# 获取所有具有 app=cassandra 的 pod 中的 version 标签
$ kubectl get pods --selector=app=cassandra rc -o \
  jsonpath='{.items[*].metadata.labels.version}'
# 获取所有节点的 ExternalIP
$ kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="ExternalIP")].address}'
# 列出当前 Pod 中使用的 Secret
$ kubectl get pods -o json | jq '.items[].spec.containers[].env[]?.valueFrom.secretKeyRef.name' | grep -v null | sort | uniq
# 查看哪些节点已就绪
$ JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}' \
 && kubectl get nodes -o jsonpath="$JSONPATH" | grep "Ready=True"
# 列出属于某个 PC 的 Pod 的名字
# "jq"命令用于转换复杂的 jsonpath,参考 https://stedolan.github.io/jq/
$ sel=${$(kubectl get rc my-rc --output=json | jq -j '.spec.selector | to_entries | .[] | "\(.key)=\(.value),"')%?}
$ echo $(kubectl get pods --selector=$sel --output=jsonpath={.items..metadata.name})


# kubectl describe -> 使用详细输出来描述命令
$ kubectl describe nodes my-node
$ kubectl describe pods my-pod

更新资源 - rolling-update / replace

shell 复制代码
$ kubectl rolling-update frontend-v1 -f frontend-v2.json           # 通过指定的清单滚动更新 pod frontend-v1
$ kubectl rolling-update frontend-v1 frontend-v2 --image=image:v2  # 更新资源名称并更新镜像
$ kubectl rolling-update frontend --image=image:v2                 # 更新 frontend pod 中的镜像
$ kubectl rolling-update frontend-v1 frontend-v2 --rollback        # 退出已存在的进行中的滚动更新
$ cat pod.json | kubectl replace -f -                              # 基于 stdin 输入的 JSON 替换 pod

# 强制替换,删除后重新创建资源。会导致服务中断。
$ kubectl replace --force -f ./pod.json

# 为 nginx RC 创建服务,启用本地 80 端口连接到容器上的 8000 端口
$ kubectl expose rc nginx --port=80 --target-port=8000

# 更新单容器 pod 的镜像版本(tag)到 v4
$ kubectl get pod mypod -o yaml | sed 's/\(image: myimage\):.*$/\1:v4/' | kubectl replace -f -

$ kubectl label pods my-pod new-label=awesome                      # 添加标签
$ kubectl annotate pods my-pod icon-url=http://goo.gl/XXBTWq       # 添加注解
$ kubectl autoscale deployment foo --min=2 --max=10                # 自动扩展 deployment "foo"

修补资源

shell 复制代码
$ kubectl patch node k8s-node-1 -p '{"spec":{"unschedulable":true}}' # 部分更新节点

# 更新容器镜像; spec.containers[*].name 是必须的,因为这是合并的关键字
$ kubectl patch pod valid-pod -p '{"spec":{"containers":[{"name":"kubernetes-serve-hostname","image":"new image"}]}}'

# 使用具有位置数组的 json 补丁更新容器镜像
$ kubectl patch pod valid-pod --type='json' -p='[{"op": "replace", "path": "/spec/containers/0/image", "value":"new image"}]'

# 使用具有位置数组的 json 补丁禁用 deployment 的 livenessProbe
$ kubectl patch deployment valid-deployment  --type json   -p='[{"op": "remove", "path": "/spec/template/spec/containers/0/livenessProbe"}]'

编辑资源 - edit

shell 复制代码
$ kubectl edit svc/docker-registry   # 编辑名为 docker-registry 的 service
$ KUBE_EDITOR="nano" kubectl edit svc/docker-registry   # 使用其它编辑器

手动扩缩容 - scale

shell 复制代码
$ kubectl scale --replicas=3 rs/foo  # Scale a replicaset named 'foo' to 3
$ kubectl scale --replicas=3 -f foo.yaml  # Scale a resource specified in "foo.yaml" to 3
$ kubectl scale --current-replicas=2 --replicas=3 deployment/mysql  # If the deployment named mysql's current size is 2, scale mysql to 3
$ kubectl scale --replicas=5 rc/foo rc/bar rc/baz  # Scale multiple replication controllers

删除资源 - delete

shell 复制代码
$ kubectl delete -f ./pod.json # 删除 pod.json 文件中定义的类型和名称的 pod
$ kubectl delete pod,service baz foo   # 删除名为"baz"的 pod 和名为"foo"的 service
$ kubectl delete pods,services -l name=myLabel # 删除具有 name=myLabel 标签的 pod 和 serivce
$ kubectl delete pods,services -l name=myLabel --include-uninitialized # 删除具有 name=myLabel 标签的 pod 和 service,包括尚未初始化的
$ kubectl -n my-ns delete po,svc --all # 删除 my-ns namespace 下的所有 pod 和 serivce,包括尚未初始化的
2.2:pod与集群操作

和运行中的pod进行交互,查看pod信息等

  • -c container -> 指定pod中的某个容器
  • -f follow -> 指定跟随模式,表示实时的,持续的输出到控制台
  • -i stdin -> 使用标准输入打开
  • -tty pseudo-TTY -> 分配一个伪终端
  • -- 分隔符 -> 分隔 kubectl 自己的参数和要传递给容器内部执行的命令
shell 复制代码
# 查看pod日志或者pod中指定的容器的日志,-f区分是否实时打印日志
$ kubectl logs my-pod     # dump 输出 pod 的日志(stdout)
$ kubectl logs my-pod -c my-container   # dump 输出 pod 中容器的日志(stdout,pod 中有多个容器的情况下使用)
$ kubectl logs -f my-pod  # 流式输出 pod 的日志(stdout)
$ kubectl logs -f my-pod -c my-container # 流式输出 pod 中容器的日志(stdout,pod 中有多个容器的情况下使用)

# 运行临时pod, 一般测试用
$ kubectl run -i --tty busybox --image=busybox -- sh  # 交互式 shell 的方式运行 pod

# 连接和其他执行命令的操作
$ kubectl attach my-pod -i # 连接到运行中的容器
$ kubectl port-forward my-pod 5000:6000  # 转发 pod 中的 6000 端口到本地的 5000 端口
$ kubectl exec my-pod -- ls /  # 在已存在的容器中执行命令(只有一个容器的情况下)
$ kubectl exec my-pod -c my-container -- ls / # 在已存在的容器中执行命令(pod 中有多个容器的情况下)
$ kubectl top pod POD_NAME --containers  # 显示指定 pod 和容器的指标度量

节点和集群的的相关操作

shell 复制代码
$ kubectl cordon my-node    # 标记 my-node 不可调度
$ kubectl drain my-node      # 清空 my-node 以待维护
$ kubectl uncordon my-node    # 标记 my-node 可调度
$ kubectl top node my-node   # 显示 my-node 的指标度量
$ kubectl cluster-info    # 显示 master 和服务的地址
$ kubectl cluster-info dump # 将当前集群状态输出到 stdout                                    
$ kubectl cluster-info dump --output-directory=/path/to/cluster-state   # 将当前集群状态输出到 /path/to/cluster-state

# 如果该键和影响的污点(taint)已存在,则使用指定的值替换
$ kubectl taint nodes foo dedicated=special-user:NoSchedule

3:API概述(简单了解)

官网文档:https://kubernetes.io/zh-cn/docs/reference/using-api/

REST API是k8s系统的重要部分,组件之间的所有操作和通信均由API Server处理的 REST API调用

大多数情况下,API定义和实现都符合标准的rest格式,可以通过kubectl命令管理工具或其他命令行工具来执行。

3.1:类型说明

alpha

  • 包含 alpha 名称的版本(例如v1alpha1)。
  • 该软件可能包含错误。启用一个功能可能会导致 bug。默认情况下,功能可能会被禁用。
  • 随时可能会丢弃对该功能的支持,恕不另行通知。
  • API 可能在以后的软件版本中以不兼容的方式更改,恕不另行通知。
  • 该软件建议仅在短期测试集群中使用,因为错误的风险增加和缺乏长期支持。

beta

  • 包含 beta 名称的版本(例如 v2beta3)。
  • 该软件经过很好的测试。启用功能被认为是安全的。默认情况下功能是开启的。
  • 细节可能会改变,但功能在后续版本不会被删除
  • 对象的模式或语义在随后的 beta 版本或 Stable 版本中可能以不兼容的方式发生变化。如果这种情况发生时,官方会提供迁移操作指南。这可能需要删除、编辑和重新创建API对象。
  • 该版本在后续可能会更改一些不兼容地方,所以建议用于非关键业务,如果你有多个可以独立升级的集群,你也可以放宽此限制。
  • 大家使用过的 Beta 版本后,可以多给社区反馈,如果此版本在后续更新后将不会有太大变化。

stable

  • 该版本名称命名方式:vX 这里 X 是一个整数。
  • Stable 版本的功能特性,将出现在后续发布的软件版本中。
3.2:废弃api

https://kubernetes.io/zh-cn/docs/reference/using-api/deprecation-guide/

二:深入Pod(重点)

1:Pod的配置文件

yaml 复制代码
apiVersion: v1 # api 文档版本
kind: Pod  # 资源对象类型,也可以配置为像Deployment、StatefulSet这一类的对象

metadata: # Pod 相关的元数据,用于描述 Pod 的数据
  name: nginx-demo # Pod 的名称
  labels: # 定义 Pod 的标签
    type: app # 自定义 label 标签,名字为 type,值为 app
    test: 1.0.0 # 自定义 label 标签,描述 Pod 版本号
  namespace: 'default' # 命名空间的配置

spec: # 期望 Pod 按照这里面的描述进行创建
  containers: # 对于 Pod 中的容器描述
  - name: nginx # 容器的名称
    image: nginx:1.7.9 # 指定容器的镜像
    imagePullPolicy: IfNotPresent # 镜像拉取策略,指定如果本地有就用本地的,如果没有就拉取远程的
    command: # 指定容器启动时执行的命令
    - nginx
    - -g
    - 'daemon off;' # nginx -g 'daemon off;'
    workingDir: /usr/share/nginx/html # 定义容器启动后的工作目录
    ports:
    - name: http # 端口名称
      containerPort: 80 # 描述容器内要暴露什么端口
      protocol: TCP # 描述该端口是基于哪种协议通信的
    - env: # 环境变量
      name: JVM_OPTS # 环境变量名称
      value: '-Xms128m -Xmx128m' # 环境变量的值
    reousrces:
      requests: # 最少需要多少资源
        cpu: 100m # 限制 cpu 最少使用 0.1 个核心
        memory: 128Mi # 限制内存最少使用 128兆
      limits: # 最多可以用多少资源
        cpu: 200m # 限制 cpu 最多使用 0.2 个核心
        memory: 256Mi # 限制 最多使用 256兆
  restartPolicy: OnFailure # 重启策略,只有失败的情况才会重启

2:探针

容器内应用的监测机制,根据不同的探针来判断容器应用当前的状态

2.1:探针的类型

StartupProbe - 是否启动? - 该探针优先,会先禁用其他的探针

k8s 1.16 版本新增的探针,用于判断应用程序是否已经启动了。

当配置了 startupProbe 后,会先禁用其他探针,直到 startupProbe 成功后,其他探针才会继续。

作用:由于有时候不能准确预估应用一定是多长时间启动成功,因此配置另外两种方式不方便配置初始化时长来检测,而配置了 statupProbe 后,只有在应用启动成功了,才会执行另外两种探针,可以更加方便的结合使用另外两种探针使用。

yaml 复制代码
startupProbe:       # 定义了一个启动探针,用于检测容器内的应用程序是否已完成启动。
                    # 它的主要目的是保护慢启动容器,在其完成启动前,暂停存活探针和就绪探针的执行。
  httpGet:          # 指定使用HTTP GET请求的方式进行启动检查。
    path: /api/startup  # HTTP GET请求访问的服务器路径。这里检查的是容器内服务的'/api/startup'端点。
                        # 这个端点通常被设计为在应用程序完全启动成功后才返回成功响应。
    port: 80        # HTTP GET请求访问的容器端口号。这里检查的是容器内80端口(标准的HTTP端口)。

这个配置看起来很简单,因为它只定义了最核心的检查方式(httpGet),而省略了其他参数。Kubernetes 会为省略的参数使用默认值。

未写明但实际生效的重要参数(使用默认值):

  • failureThreshold: 默认值 = 30 (旧版本k8s)或 默认值 = 3 (较新版本k8s,请查阅对应版本文档)
    • 这意味着 kubelet 会尝试非常多次(例如30次)才会放弃并重启容器。这是启动探针最关键的特性,它为慢启动应用提供了充足的启动时间。
  • periodSeconds: 默认值 = 10
    • 每次探测执行的间隔时间是10秒。
  • timeoutSeconds: 默认值 = 1
    • 每次探测请求的超时时间为1秒。
  • successThreshold: 默认值 = 1
    • 只需要1次探测成功,即认为启动成功。
  • initialDelaySeconds: 默认值 = 0
    • 容器启动后立即开始执行启动探测。

这个启动探针的完整行为解读:

  1. 何时启动:容器启动后立即(initialDelaySeconds: 0)开始第一次探测。
  2. 如何检查:向容器内 80 端口的 /api/startup 路径发送 HTTP GET 请求。
  3. 等待响应:期望在 1 秒内(timeoutSeconds: 1)收到成功的 HTTP 响应(状态码 2xx 或 3xx)。
  4. 判定结果:
    • 成功:只要有一次请求成功,kubelet 就认为容器已启动完成。随后,存活探针和就绪探针会开始接管后续的健康检查工作。
    • 失败/超时:如果请求失败或超时,kubelet 会等待 10 秒(periodSeconds: 10)后再次尝试。
  5. 最终失败:如果连续失败了很多次(例如 failureThreshold: 30 * periodSeconds: 10 = 300秒),kubelet 就会放弃等待,并重启容器。

为什么需要 Startup Probe?

它的存在是为了解决一个特定问题:对于那些启动速度非常慢的应用程序(例如大型Java应用、需要初始化大量数据的应用),在启动完成之前,它们的存活探针和就绪探针肯定会失败。

如果没有 startupProbe

  • 存活探针 (livenessProbe) 会在失败多次后,误以为应用启动失败,从而错误地重启这个正在努力启动的容器,导致它永远无法成功启动。

有了 startupProbe

  • 在启动探针成功之前,存活探针和就绪探针都会被禁用。
  • 启动探针拥有非常高的 failureThreshold,允许容器在较长时间内(例如5分钟)持续报告"未启动",而不会触发重启。
  • 一旦应用程序自己准备好了,启动探针成功,控制权就交给 livenessProbereadinessProbe 来负责运行时的健康检查。

简单比喻:

  • Startup Probe:像是给一个正在开机的大型机器一个"请勿打扰"的牌子。在它自己完成开机自检并亮起"准备就绪"的绿灯之前,你不会去碰它,也不会因为它开机时噪音大、灯乱闪就把它电源拔了。
  • 等绿灯亮了(启动探针成功),你才会开始定期检查它是否在正常运转(Liveness)和是否空闲可以接新任务(Readiness)。

livenessProbe - 是否运行(活没活着),如果没有或者将会执行重启策略

用于探测容器中的应用是否运行,如果探测失败,kubelet 会根据配置的重启策略进行重启

若没有配置,默认就认为容器启动成功,不会执行重启策略。

yaml 复制代码
livenessProbe:           # 定义了一个存活探针,用于检测容器是否仍在健康运行。如果探测失败,k8s会重启容器。
  failureThreshold: 5    # 连续探测失败5次后,k8s才会将容器判定为不健康并重启它。这提供了一定的缓冲,避免因临时故障重启。
  httpGet:               # 指定使用HTTP GET请求的方式进行健康检查。
    path: /health        # HTTP GET请求访问的服务器路径。这里检查的是容器内Web服务的'/health'端点。
    port: 8080           # HTTP GET请求访问的容器端口号。这里检查的是容器内8080端口。
    scheme: HTTP         # 用于连接的健康检查的协议(HTTP 或 HTTPS)。这里使用的是HTTP协议。
  initialDelaySeconds: 60 # 容器启动后,等待60秒后才开始第一次执行存活探测。给应用足够的启动时间。
  periodSeconds: 10      # 每次执行存活探测的间隔时间是10秒。即每10秒检查一次。
  successThreshold: 1    # 探测失败后,只要有一次成功,即被认定为探测成功。对于livenessProbe,此值必须为1。
  timeoutSeconds: 5      # 每次探测请求的超时时间为5秒。如果5秒内没有响应,则本次探测判定为失败。
  1. 等待 :容器启动后,先等待 60 秒 (initialDelaySeconds)。
  2. 开始检查 :60秒后,发起第一次健康检查。向容器内 8080 端口的 /health 路径发送一个 HTTP GET 请求。
  3. 等待响应 :它期望在 5 秒内 (timeoutSeconds) 收到一个成功的 HTTP 响应状态码 (2xx 或 3xx)。
  4. 判定结果
    • 如果请求在5秒内成功返回,则探测成功
    • 如果请求超时(5秒内无响应)或返回错误状态码(如4xx, 5xx),则探测失败
  5. 后续检查 :无论第一次成功与否,它都会每 10 秒 (periodSeconds) 重复一次步骤2-4的检查。
  6. 失败处理 :如果连续 5 次 (failureThreshold) 探测都失败了,Kubernetes 就会判定容器不健康,并重启这个容器。
  7. 恢复处理 :一旦失败后(比如连续失败了3次),只要有一次探测成功,失败计数器就会被重置。因为 successThreshold1

readinessProbe - 当前的应用是否能干活(能不能干活)?

用于探测容器内的程序是否健康

它的返回值如果返回 success,那么就认为该容器已经完全启动,并且该容器是可以接收外部流量的。

yaml 复制代码
readinessProbe:         # 定义了一个就绪探针,用于检测容器是否已准备就绪,可以开始接收外部流量。
  failureThreshold: 3   # 连续探测失败3次后,k8s会将容器标记为"未就绪"(Unready)。该容器将从服务的负载均衡器中移除,不再接收任何流量。
  httpGet:              # 指定使用HTTP GET请求的方式进行就绪检查。
    path: /ready        # HTTP GET请求访问的服务器路径。这里检查的是容器内Web服务的'/ready'端点。
    port: 8181          # HTTP GET请求访问的容器端口号。这里检查的是容器内8181端口。
    scheme: HTTP        # 用于连接的健康检查的协议(HTTP 或 HTTPS)。这里使用的是HTTP协议。
  periodSeconds: 10     # 每次执行就绪探测的间隔时间是10秒。即每10秒检查一次容器是否就绪。
  successThreshold: 1   # 对于就绪探针,探测失败后,只要有一次成功,该容器就会被标记为"就绪"(Ready),并重新开始接收流量。
  timeoutSeconds: 1     # 每次探测请求的超时时间为1秒。如果1秒内没有响应,则本次探测判定为失败。

这个 readinessProbe 的行为与 livenessProbe 类似,但目的和后果完全不同

  1. 目的不同:
    • Liveness (存活):检查容器是否活着。失败会重启容器。
    • Readiness (就绪):检查容器是否准备好工作。失败只会停止向该容器发送流量,但不会重启它。
  2. 常见配置差异:
    • initialDelaySeconds:这个就绪探针配置里没有显式设置initialDelaySeconds。Kubernetes 会使用默认值 0,意味着容器启动后立即开始进行就绪检查。这通常适用于启动很快的轻量级服务,或者希望尽快开始检查的服务。如果应用启动慢,最好也加上这个参数。
    • timeoutSeconds:这里的超时时间设置为 1 秒,比存活探针的 5 秒更短。这表明对就绪检查的响应速度要求更高,期望服务能快速反馈其状态。
    • failureThreshold:失败阈值 3 次,与存活探针的 5 次相比,可能意味着希望更快地将不健康的实例从服务中剔除。
  3. 行为总结:
    • 容器启动后立即开始就绪检查。
    • 10 秒向 :8181/ready 发送一次 HTTP 请求,要求 1 秒内得到成功响应。
    • 如果连续 3 次检查失败,Kubernetes 会将 Pod 的状态标记为 Unready,并从所有关联的 Service 的端点列表(Endpoints)中移除该 Pod。流量不会再被路由到这个 Pod。
    • 之后,检查会继续进行。一旦有一次检查成功(successThreshold: 1),Pod 又会被重新标记为 Ready,并加回到服务的端点列表中,重新开始接收流量。

简单比喻:

  • Liveness Probe:像是检查一个人的心脏是否还在跳。不跳了就要采取抢救(重启)。
  • Readiness Probe:像是检查一个人是否睡醒了并且能接电话。没醒就不给他派新任务(流量),但不会去叫醒(重启)他,让他继续睡。
2.2:探针探测的方式

execAction -> 以命令的方式进行探测

在容器内部执行一个命令,如果返回值为 0,则任务容器时健康的。

yaml 复制代码
livenessProbe:
  exec:
    command:
      - cat
      - /health

tcpsocketAction -> 以tcpsocket连接的方式进行探测

通过 tcp 连接监测容器内端口是否开放,如果开放则证明该容器健康

yaml 复制代码
livenessProbe:
  tcpSocket:
    port: 80

httpgetAction -> 通过HTTP请求的方式检测探针

生产环境用的较多的方式,发送 HTTP 请求到容器内的应用程序

如果接口返回的状态码在 200~400 之间,则认为容器健康。

yaml 复制代码
livenessProbe:
  failureThreshold: 5 # 允许失败的阈值
  httpGet:
    path: /health
    port: 8080
    scheme: HTTP # 通过http请求8080端口的/healthAPI,检测容器是否健康
    httpHeaders:
      - name: xxx
        value: xxx
2.3:参数配置
参数 说明
initialDelaySeconds 初始化时间
timeoutSeconds 超时时间
periodSeconds 监测间隔时间
successThreshold 检查 ? 次成功就表示成功
failureThreshold 监测失败 ? 次就表示失败
2.4:使用方式示例
yaml 复制代码
apiVersion: v1 # api 文档版本
kind: Pod  # 资源对象类型,也可以配置为像Deployment、StatefulSet这一类的对象

metadata: # Pod 相关的元数据,用于描述 Pod 的数据
  name: nginx-po # Pod 的名称
  labels: # 定义 Pod 的标签
    type: app # 自定义 label 标签,名字为 type,值为 app
    test: 1.0.0 # 自定义 label 标签,描述 Pod 版本号
  namespace: 'default' # 命名空间的配置

spec: # 期望 Pod 按照这里面的描述进行po
  containers: # 对于 Pod 中的容器描述
  - name: nginx # 容器的名称
    image: nginx:1.7.9 # 指定容器的镜像
    imagePullPolicy: IfNotPresent # 镜像拉取策略,指定如果本地有就用本地的,如果没有就拉取远程的
    startupProde: # 应用启动探针配置
        tcpSocket:
            port: 80
        failureThreshold: 3 # 失败3次才算真正的失败
        periodSecond: 10 # 间隔时间
        successThreshold: 1 # 多少次成功算成功
        timeoutSeconds: 5 # 超时时间
    command: # 指定容器启动时执行的命令
    - nginx
    - -g
    - 'daemon off;' # nginx -g 'daemon off;'
    workingDir: /usr/share/nginx/html # 定义容器启动后的工作目录
    ports:
    - name: http # 端口名称
      containerPort: 80 # 描述容器内要暴露什么端口
      protocol: TCP # 描述该端口是基于哪种协议通信的
    - env: # 环境变量
      name: JVM_OPTS # 环境变量名称
      value: '-Xms128m -Xmx128m' # 环境变量的值
    reousrces:
      requests: # 最少需要多少资源
        cpu: 100m # 限制 cpu 最少使用 0.1 个核心
        memory: 128Mi # 限制内存最少使用 128兆
      limits: # 最多可以用多少资源
        cpu: 200m # 限制 cpu 最多使用 0.2 个核心
        memory: 256Mi # 限制 最多使用 256兆
  restartPolicy: OnFailure # 重启策略,只有失败的情况才会重启
shell 复制代码
# 这部分的常用命令

kubectl create -f nginx-po.yml # 通过指定的资源文件创建资源
kubectl describe po nginx-po   # 查看指定资源的具体情况,在这里可以看到探针的处理情况
kubectl get po nginx-po        # 获取指定pod资源的启动情况
kubectl exec -it nginx-po -c nginx -- cat /inited  # 查看指定pod资源的初始化日志

3:生命周期

初始化 -> 容器创建完成 -> postStart钩子 -> 对容器各种操作 -> preStop钩子 -> 容器销毁

yaml 复制代码
apiVersion: v1 # api 文档版本
kind: Pod  # 资源对象类型,也可以配置为像Deployment、StatefulSet这一类的对象

metadata: # Pod 相关的元数据,用于描述 Pod 的数据
  name: nginx-po # Pod 的名称
  labels: # 定义 Pod 的标签
    type: app # 自定义 label 标签,名字为 type,值为 app
    test: 1.0.0 # 自定义 label 标签,描述 Pod 版本号
  namespace: 'default' # 命名空间的配置

spec: # 期望 Pod 按照这里面的描述进行po

  # 变为删除中的状态后,会给 pod 一个宽限期,让 pod 去执行一些清理或销毁操作。
  terminationGracePeriodSeconds: 30

  containers: # 对于 Pod 中的容器描述
  - name: nginx # 容器的名称
    image: nginx:1.7.9 # 指定容器的镜像
    imagePullPolicy: IfNotPresent # 镜像拉取策略,指定如果本地有就用本地的,如果没有就拉取远程的

    # 可以控制容器创建之后和容器停止之前完成的额外的动作....
    lifecycle:
      postStart: # 容创建完成后执行的动作,不能保证该操作一定在容器的 command 之前执行,一般不使用
        exec: # 可以是 exec / httpGet / tcpSocket
          command:
            - sh
            - -c
            - 'mkdir /data'
      preStop: # 在容器停止前执行的动作
        httpGet: # 发送一个 http 请求
          path: /
          port: 80
        exec: # 执行一个命令
          command:
            - sh
            - -c
            - sleep 9

    startupProde: # 应用启动探针配置
        tcpSocket:
            port: 80
        failureThreshold: 3 # 失败3次才算真正的失败
        periodSecond: 10 # 间隔时间
        successThreshold: 1 # 多少次成功算成功
        timeoutSeconds: 5 # 超时时间
    command: # 指定容器启动时执行的命令
    - nginx
    - -g
    - 'daemon off;' # nginx -g 'daemon off;'
    workingDir: /usr/share/nginx/html # 定义容器启动后的工作目录
    ports:
    - name: http # 端口名称
      containerPort: 80 # 描述容器内要暴露什么端口
      protocol: TCP # 描述该端口是基于哪种协议通信的
    - env: # 环境变量
      name: JVM_OPTS # 环境变量名称
      value: '-Xms128m -Xmx128m' # 环境变量的值
    reousrces:
      requests: # 最少需要多少资源
        cpu: 100m # 限制 cpu 最少使用 0.1 个核心
        memory: 128Mi # 限制内存最少使用 128兆
      limits: # 最多可以用多少资源
        cpu: 200m # 限制 cpu 最多使用 0.2 个核心
        memory: 256Mi # 限制 最多使用 256兆
  restartPolicy: OnFailure # 重启策略,只有失败的情况才会重启

🚀 pod退出流程:endpoint删除pod的ip地址 -> pod变成terminating状态 -> 执行preStop钩子函数(注册中心下线,数据的清理和销毁)

三:控制器-资源调度(重点)

原生的Pod无法进行自动的扩缩容,同时无法直接编辑对应的资源清单,因此我们很少直接操作Pod,而是通过操作控制器的方式实现对容器资源的管理

1:Label 和 Selector

先想一想为啥要有这两个东西?

以RS和Deployment举例:

  • RS是无状态应用的副本集,用于管理pod的扩容和缩容的
  • Deployment是部署RS和Pod用的,里面包含RS,他会创建对应的RS和Pod,对应用进行滚动更新或者回滚,动态的扩容

那么如果将Deployment和RS进行相关联呢?

K8s没有采用强制的硬编码方式关联,因为这样一旦双方中的一方发生改变,映射关系可能就失效了

因此Label和Selector应运而生!!

在RS中定义一些有关当前RS特性的标签,然后Deployment通过Selector选择器选择指定的RS与其形成映射

1.1:标签Label
  • 可以在资源清单中的 metadata.labels 中进行配置
  • 可以使用kubectl进行设置:
    • 创建临时的label -> kubectl label po <资源名称> app=hello
    • 修改已经存在的标签 -> kubectl label po <资源名称> app=hello2 --overwrite
  • 可以使用kubectl进行查询:
    • 按照label单值进行查找 -> kubectl get po -A -l app=hello
    • 查看所有节点的labels -> kubectl get po --show-labels
1.2:选择器selector
  • 可以在各对象的配置 spec.selector 或其他可以写 selector 的属性中编写
  • 可以使用kubectl进行查询
    • 匹配单个值,查找 app=hello 的 pod -> kubectl get po -A -l app=hello
    • 匹配多个值 -> kubectl get po -A -l 'k8s-app in (metrics-server, kubernetes-dashboard)'
    • 查找 version!=1 and app=nginx 的 pod 信息 -> kubectl get po -l version!=1,app=nginx
    • 不等值 + 语句 -> kubectl get po -A -l version!=1,'app in (busybox, nginx)'

2:Deployment

2.1:deploy的创建

对于Deployment的创建,因为我们可能不会资源清单的方式,可以使用kubectl的方式进行创建

shell 复制代码
# 创建一个基于nginx1.7.9镜像的deployment
kubectl create deploy nginx-deploy --image=nginx:1.7.9
# 或执行
kubectl create -f xxx.yaml --record
# --record 会在 annotation 中记录当前命令创建或升级了资源,后续可以查看做过哪些变动操作。

# 查看部署信息
kubectl get deployments

# 查看 rs
kubectl get rs

# 查看 pod 以及展示标签,可以看到是关联的那个 rs
kubectl get pods --show-labels
2.2:滚动更新,回滚,扩容缩容

滚动更新

⚠️ 只有修改了 deployment 配置文件中的 template 中的属性后,才会触发更新操作

shell 复制代码
# 修改 nginx 版本号 -> set image
kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1
# 或者修改
kubectl edit deployment/nginx-deployment 

# 查看滚动更新的过程
kubectl rollout status deploy <deployment_name>

# 查看部署描述,最后展示发生的事件列表也可以看到滚动更新过程
kubectl describe deploy <deployment_name>

# 查看部署信息
通过 kubectl get deployments 获取部署信息,UP-TO-DATE 表示已经有多少副本达到了配置中要求的数目
通过 kubectl get rs 可以看到增加了一个新的 rs
通过 kubectl get pods 可以看到所有 pod 关联的 rs 变成了新的

🎉 假设当前有 5 个 nginx:1.7.9 版本,你想将版本更新为 1.9.1,当更新成功第三个以后,你马上又将期望更新的版本改为 1.9.2,那么此时会立马删除之前的三个,并且立马开启更新 1.9.2 的任务

回滚

有时候你可能想回退一个Deployment,例如,当Deployment不稳定时,比如一直crash looping。

可以通过设置 .spec.revisonHistoryLimit 来指定 deployment 保留多少 revison

如果设置为 0,则不允许 deployment 回退了。

shell 复制代码
kubectl rollout history deployment/nginx-deploy # 可以获取 revison 的列表

kubectl rollout history deployment/nginx-deploy --revision=2 # 可以查看详细信息

# 确认要回退的版本后,可以通过 
kubectl rollout undo deployment/nginx-deploy # 可以回退到上一个版本
kubectl rollout undo deployment/nginx-deploy --to-revision=2 # 也可以回退到指定的 revision

# 检验
kubectl get deployment
kubectl describe deployment

扩容和缩容

shell 复制代码
通过 kube scale 命令可以进行自动扩容/缩容,以及通过 kube edit 编辑 replcas 也可以实现扩容/缩容

扩容与缩容只是直接创建副本数,没有更新 pod template 因此不会创建新的 rs
2.3:暂停和恢复

由于每次对 pod template 中的信息发生修改后,都会触发更新 deployment 操作

那么此时如果频繁修改信息,就会产生多次更新,而实际上只需要执行最后一次更新即可

当出现此类情况时我们就可以暂停 deployment 的 rollout

shell 复制代码
# 实现暂停
kubectl rollout pause deployment <name>

# 检测配置是否发生了更改?
kubectl get deploy <name> -o yaml

# 恢复rollout
kubectl rollout deploy <name>

# 恢复后,我们再次查看 rs 和 po 信息,我们可以看到就开始进行滚动更新操作了
kubectl get rs
kubectl get po
2.4:deploy的配置文件

元数据 + 规约(副本数 + 选择器 + 更新策略 + pod template的元数据 + 规约)

yaml 复制代码
apiVersion: apps/v1 # deployment api 版本
kind: Deployment # 资源类型为 deployment

# 声明命名空间,名称和标签
metadata: # 元信息
  labels: # 标签
    app: nginx-deploy # 具体的 key: value 配置形式
  name: nginx-deploy # deployment 的名字
  namespace: default # 所在的命名空间

# 规约,声明期望的状态
spec:
  replicas: 1 # 期望副本数
  revisionHistoryLimit: 10 # 进行滚动更新后,保留的历史版本数
  selector: # 选择器,用于找到匹配的 RS
    matchLabels: # 按照标签匹配
      app: nginx-deploy # 匹配的标签key/value
  strategy: # 更新策略
    rollingUpdate: # 滚动更新配置
      maxSurge: 25% # 进行滚动更新时,更新的个数最多可以超过期望副本数的个数/比例
      maxUnavailable: 25% # 进行滚动更新时,最大不可用比例更新比例,表示在所有副本数中,最多可以有多少个不更新成功
    type: RollingUpdate # 更新类型,采用滚动更新
  # template模板 -> pod的规约声明
  template: # pod 模板
    metadata: # pod 的元信息
      labels: # pod 的标签
        app: nginx-deploy
    spec: # pod 期望信息
      containers: # pod 的容器
      - image: nginx:1.7.9 # 镜像
        imagePullPolicy: IfNotPresent # 拉取策略
        name: nginx # 容器名称
      restartPolicy: Always # 重启策略
      terminationGracePeriodSeconds: 30 # 删除操作最多宽限多长时间

3:StatefulSet

在 Kubernetes 中,最常见的用于部署应用的工作负载控制器是 Deployment。Deployment 非常适合无状态应用,它的 Pod 是完全相同、可随意替换的。你不需要关心哪个 Pod 在运行,也不需要关心它们启动的顺序和身份。

但是,对于有状态应用,情况就完全不同了。有状态应用(如 MySQL、Kafka、ZooKeeper、Elasticsearch 等)具有以下一个或多个特点:

  • 稳定的、唯一的网络标识符:每个实例都有一个固定的、可被发现的主机名。
  • 稳定的、持久的存储:每个实例都需要自己独立的存储卷,即使 Pod 被重新调度,也需要挂载回同一个存储。
  • 有序的、优雅的部署和扩展:在启动新实例之前,必须确保之前的实例已经准备就绪(例如,数据库主从配置)。
  • 有序的、优雅的删除和终止:在终止实例时,顺序是反向的。
  • 有序的滚动更新:按顺序进行应用版本的更新。

Deployment 无法满足这些需求。为此,Kubernetes 引入了 StatefulSet 这个控制器,专门用于管理有状态应用。

3.1:核心特性

稳定的网络标识

StatefulSet 为每个 Pod 提供一个唯一且固定的名称,格式为:<statefulset-name>-<ordinal-index>

例如,一个名为 kafka 的 StatefulSet,创建 3 个副本,Pod 名称将分别是:kafka-0kafka-1kafka-2

这些 Pod 名称在 Pod 的生命周期内是稳定的。

即使 kafka-1 这个 Pod 被重新调度到另一个节点上,它的名字依然会是 kafka-1

此外,还会创建一个无头 Service,用于管理 Pod 的网络身份。

每个 Pod 会获得一个唯一的 DNS 记录:<pod-name>.<service-name>.<namespace>.svc.cluster.local

集群中的其他应用可以通过这个固定的DNS访问Pod

稳定的持久化存储

这是 StatefulSet 最核心的功能之一。它通过 VolumeClaimTemplate(存储卷申请模板)来实现。

  • 在定义 StatefulSet 时,你可以创建一个 volumeClaimTemplates

  • 当 StatefulSet 创建 Pod 时(例如 kafka-0),它会根据这个模板自动创建一个 PersistentVolumeClaim

    • 名称格式为:<volumeClaimTemplateName>-<podName>
  • 这个 PVC 随后会绑定到一个 PV,从而为 Pod 提供存储。

    • PV:就是图书馆里一本本具体的、物理存在的书。比如《三体》第一册、《哈利波特与魔法石》等等。每一本都是独立的、实实在在的资源。
    • PVC:就是读者提交的借书申请单。申请单上写着:"我想借一本科幻小说,精装版的"。它不关心具体是哪一本《三体》,只要符合要求就行。
  • 每个 Pod(kafka-0, kafka-1, ...)都会获得自己独立的 PVC 和 PV。

  • 当 Pod 被重新调度时,新的 Pod 实例会自动挂载到它原来使用的那个 PV 上,从而保证了数据的持久化和稳定性。

  1. 独立房间:StatefulSet 给每个"手下"(Pod)都分了一个独立的、带锁的房间(PV) 和一把专属的钥匙(PVC),谁也不抢谁的。
  2. 人走房留:即使某个"手下"因为意外情况消失了,他的房间也会一直给他留着,里面的东西不动。
  3. 来人接班:会再来一个同名同姓的新"手下",他用原来的那把专属钥匙,打开原来的那个房间,一切照旧。

这样,就保证了像 Kafka、MySQL 这种"有状态"的应用,每个实例的数据都不会丢,身份也不会乱,从而实现了 "数据的持久化和稳定性"。

有序的 pod 管理

StatefulSet 严格遵循顺序规则,这是其"状态"的另一个体现。

  • 部署/扩容时:按索引顺序从大到小(0, 1, 2, ...) 创建 Pod。必须等前一个 Pod(kafka-0)成功进入 Running 和 Ready 状态后,才会创建下一个 Pod(kafka-1)。
  • 删除/缩容时:按索引顺序从大到小(..., 2, 1, 0) 终止 Pod。必须等 kafka-2 完全终止后,才会终止 kafka-1
  • 滚动更新时:默认策略是 RollingUpdate,它会按与 Pod 索引相反的顺序(从最大的索引开始)逐个地、安全地更新 Pod。

这种顺序性对于有主从关系的集群(如 MySQL、Redis)至关重要,因为它确保了主节点(通常是索引为0的 Pod)首先启动并稳定运行。

3.2:工作原理剖析

要理解 StatefulSet,需要明白它和以下几个 Kubernetes 组件/资源的关系:

  1. StatefulSet Controller:核心控制器,负责创建、更新、删除 Pod,并确保 Pod 的数量和状态符合预期。
  2. 无头 Service:你必须手动创建一个 ClusterIP 类型为 None 的 Service。
    1. 这个 Service 不提供负载均衡,它的作用是告诉 Kubernetes 不要为这些 Pod 分配集群 IP
    2. 而是让 DNS 系统为每个 Pod 返回其独立的 DNS A 记录。这是实现稳定网络发现的基石。
  3. PersistentVolume Provisioner
    1. volumeClaimTemplates 中指定了 storageClassName 时,动态卷制备器会按需自动创建 PV。
3.3:使用场景
  • 数据库集群:MySQL Galera Cluster, PostgreSQL 高可用集群, MongoDB 副本集。
  • 消息队列:Kafka 集群, RabbitMQ 集群。
  • 键值存储:etcd, Zookeeper。
  • 分布式数据存储:Elasticsearch, Redis Cluster。
  • 任何需要持久化数据和稳定网络身份的应用程序。
3.4:使用示例

假设我们要部署一个名为 my-app 的有状态应用,它有三个副本,每个副本都需要自己的存储

先创建一个无头service - 保证稳定网络发现的基石

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: my-app-service
  labels:
    app: my-app
spec:
  ports:
  - port: 80 # 这定义了服务端口。虽然不进行负载均衡,但这个端口信息在 DNS 发现和 Pod 间直接通信时仍然有用。
    name: web
  # 一个普通的 Service 会被分配一个虚拟的 IP 地址(ClusterIP),作为负载均衡器,将请求转发到后端的多个 Pod。
  # 当设置为 None 时,Kubernetes 不会为这个 Service 分配 ClusterIP,也不会进行负载均衡。
  # 它的唯一目的变成了:为匹配的 Pod 提供稳定的网络身份标识
  clusterIP: None # 这就是"无头"的定义
  selector:
    # 这个选择器告诉 Service:"请关注所有带有标签 app: my-app 的 Pod"。
    # 它不负责路由流量,而是负责发现这些 Pod,并为它们创建 DNS 记录
    app: my-app # 这个选择器必须匹配 StatefulSet 中 Pod 的标签

创建statefulSet的资源清单

yaml 复制代码
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: my-app
spec:
  # 你所管理的 Pod 的网络身份,由名为 my-app-service 的无头服务来定义和提供。
  # 有了这个关联,StatefulSet 在创建 Pod 时,才会知道该让哪个 Service 来为这些 Pod 创建 DNS 记录
  serviceName: "my-app-service" # 这是 StatefulSet 和无头服务之间的桥梁,必须指向上面创建的无头 Service
  replicas: 3
  selector:
    matchLabels: # 这组配置是标准的控制器模式。StatefulSet 通过 selector 来识别哪些 Pod 归它管理。
      app: my-app # 必须匹配 template.metadata.labels
	  # 这个标签 app: my-app 也正是无头服务 my-app-service 的 selector 所寻找的标签。
	  # 这就完成了 Pod、StatefulSet 和 Service 三者的关联。

  template:
    metadata:
      labels:
        app: my-app # 这是 Pod 的标签,被 Service 的 selector 使用
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www # 这个名字必须与 volumeClaimTemplates.name 一致
          mountPath: /usr/share/nginx/html # 请把名为 www 的存储卷,挂载到容器内的 /usr/share/nginx/html 路径下

  volumeClaimTemplates: # 核心部分:存储卷申请模板
  - metadata:
      name: www # 这是 StatefulSet 的灵魂。它是一个"模板",用于为每个 Pod 自动创建独立的 PVC
    spec: # 这个 PVC 会去动态或静态地绑定一个 PV,从而为 Pod my-app-0 提供 1Gi 的、独立的、持久的存储。
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "standard" # 假设你的集群有名为 "standard" 的 StorageClass
      resources:
        requests:
          storage: 1Gi
  1. 提交清单 :用户通过 kubectl apply 提交了无头 Service 和 StatefulSet 的配置文件。
  2. 控制器工作:StatefulSet 控制器监测到新的 StatefulSet 资源,开始行动。
  3. 有序创建
    • 控制器按顺序(0, 1, 2)创建 Pod。
    • 在创建每个 Pod(如 my-app-0)时,根据 volumeClaimTemplates 自动创建一个唯一的 PVC(www-my-app-0)。
    • 该 PVC 根据其规格(storageClassName: "standard")动态地制备并绑定一个 PV(如 pv-0)。
    • Pod 的 volumeMounts 将名为 www 的卷(来自于 PVC www-my-app-0)挂载到容器内的指定路径。
  4. 网络注册
    • 无头 Service my-app-service 持续监控带有标签 app: my-app 的 Pod。
    • 每当一个 Pod 创建成功并进入 Ready 状态,Service 就为其在 Kubernetes 的 DNS 中注册一条唯一的 A 记录。
  5. 最终状态
    • 稳定的网络:每个 Pod 都有一个固定的、可通过 DNS 直接访问的域名。
    • 稳定的存储:每个 Pod 都拥有自己独立的、永久的存储卷。即使 Pod my-app-0 被删除重建,新的 my-app-0 Pod 也会通过同名 PVC 挂载回原来的 PV,数据不会丢失。

部署和验证

bash 复制代码
# 应用配置,使用apply -f
kubectl apply -f service.yaml
kubectl apply -f statefulset.yaml

# 观察pod的创建 get
# 你会清晰地看到 Pod 按顺序启动:my-app-0 -> my-app-1 -> my-app-2。
kubectl get pods -w -l app=my-app

# 查看pvc
kubectl get pvc

# 会有如下类似的输出
NAME               STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-my-app-0       Bound    pvc-12345678-90ab-cdef-1234-567890abcdef   1Gi        RWO            standard       5m
www-my-app-1       Bound    pvc-abcdef12-3456-7890-abcd-ef1234567890   1Gi        RWO            standard       4m
www-my-app-2       Bound    pvc-09876543-21ba-dcfe-4321-098765432109   1Gi        RWO            standard       3m

# 在集群内另一个 Pod 中,你可以通过固定的 DNS 名来访问特定的 Pod:
# 进入另一个临时 Pod
kubectl run -it --rm --image=busybox:1.28 test-pod -- /bin/sh

# 在 test-pod 内部执行
nslookup my-app-0.my-app-service
3.5:局限性
  • 存储复杂性:你需要负责管理底层存储的备份、恢复和扩缩容。删除 StatefulSet 时,其关联的 PVC 默认不会被删除,需要手动清理,以防止数据丢失。
  • 网络复杂性 :应用本身需要能够处理集群发现和成员管理(例如,kafka-0 需要知道如何找到 kafka-1kafka-2)。StatefulSet 只提供了稳定的身份,不提供集群组建逻辑。
  • 速度较慢:由于有序性的要求,部署、扩缩容和更新的速度会比 Deployment 慢。
3.6:statefulSet vs Deployment
特性 Deployment(无状态) StatefulSet(有状态)
Pod 名称 随机哈希 固定,有序
网络标识 通过 Service 负载均衡 固定的 DNS 名,可通过无头 Service 直接访问 Pod
存储 共享或临时存储 独立的持久化存储,与 Pod 绑定
部署/删除顺序 并行,无序 严格按顺序进行
适用场景 Web 前端、无状态 API 数据库、消息队列、分布式存储

简单来说,StatefulSet 是 Kubernetes 为有状态应用提供的"一等公民"支持。

StatefulSet 通过稳定的网络标识、持久化存储和有序的 Pod 管理,将有状态应用的复杂需求抽象成了简单的 Kubernetes 资源定义,使得在云原生环境中运行像 MySQL、Kafka 这样的复杂系统成为可能。

4:DaemonSet

DaemonSet 是 Kubernetes 中一个非常核心的工作负载控制器,它确保所有(或部分)节点上都运行一个 Pod 的副本。

当有节点加入集群时,Pod 会在新节点上创建;当有节点从集群移除时,这些 Pod 也会被垃圾回收。

4.1:核心思想

核心思想就是每一个节点一个Pod

可以把 DaemonSet 想象成一个集群级别的"守护进程"部署工具。

  • Deployment:关心的是维护一定数量的、无状态的 Pod 副本,至于这些 Pod 运行在哪个节点上,它不关心。
  • StatefulSet:关心的是维护有状态的、有唯一标识的 Pod 副本,同样不严格关心节点。
  • DaemonSet:关心的是节点本身。它的目标是确保每一个符合条件的节点上都运行一个它管理的 Pod 实例。
4.2:daemonSet典型使用场景

DaemonSet 非常适合运行那些提供节点级别基础设施服务的 Pod。

  • 集群存储守护进程:例如 glusterdceph。每个节点都需要运行一个存储守护进程,来提供分布式的存储服务。
  • 日志收集守护进程:例如 FluentdFilebeat。每个节点上都需要一个日志收集器,用于收集该节点上所有容器产生的日志,并统一发送到后端的日志存储(如 Elasticsearch)。
  • 监控采集守护进程:例如 Prometheus Node Exporter。每个节点上都需要一个 agent 来收集该节点的硬件和操作系统指标(如 CPU、内存、磁盘使用率),供 Prometheus 抓取。
  • 网络插件:例如 Calico、Weave Net 等 CNI 网络插件,通常以 DaemonSet 的形式运行,在每个节点上提供网络策略和路由功能。
  • 安全与合规:在每个节点上运行安全审计、漏洞扫描或合规性检查的代理。
4.3:工作原理解析

DaemonSet 控制器的工作流程非常直接:

  • 监听节点变化:DaemonSet 控制器持续监听(watch)Kubernetes API Server,关注集群中节点的添加和删除事件。
  • 节点添加:当一个新的节点被加入到集群中时,DaemonSet 控制器会立即检测到这个事件。它会根据定义的 Pod 模板(spec.template),在这个新节点上创建一个 Pod。
  • 节点删除:当一个节点被从集群中移除(或被标记为不可调度 NoSchedule),DaemonSet 控制器会检测到这一变化,并删除运行在该节点上的 Pod。
  • 节点选择器:DaemonSet 可以通过 nodeSelectornodeAffinity 来限制 Pod 只运行在部分节点上,而不是全部节点。

对比

特性 Deployment StatefulSet DaemonSet
核心目标 维护一组无状态的 Pod 副本 维护一组有状态的、有序的 Pod 副本 确保每个节点上都运行一个 Pod 副本
Pod 身份 可互换,无唯一标识 唯一、稳定的标识(有序编号) 与节点强关联,身份由所在节点定义
副本数量 replicas 定义,与节点数无关 replicas 定义,与节点数无关 自动与集群中符合条件的节点数保持一致
存储 通常使用共享存储或临时存储 每个 Pod 拥有独立的持久化存储(PVC) 通常使用 hostPath 访问节点本地资源
典型场景 Web 服务器、API 后端 数据库(MySQL)、消息队列(Kafka) 日志收集(Fluentd)、监控代理(Node Exporter)
4.4:deamonSet实例

部署 Fluentd 日志收集器 - 假设我们要在每一个节点上部署fluentd日志收集器实例

yaml 复制代码
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-elasticsearch
  namespace: kube-system
  labels:
    k8s-app: fluentd-logging
spec:
  selector:
    matchLabels:
      name: fluentd-elasticsearch # 必须匹配 template 中的标签

  template:
    metadata:
      labels:
        name: fluentd-elasticsearch # 这个标签被 DaemonSet 的 selector 使用,用于识别和管理pod
    spec:
      # 容忍度 (Tolerations):允许 Pod 被调度到带有污点的节点上
      # 容忍了控制平面(Control-Plane)和主节点(Master)的污点,使得该pod可以在任意的节点上运行
      tolerations:
      - key: node-role.kubernetes.io/control-plane
        operator: Exists
        effect: NoSchedule 
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      
      # pod说明 - container说明
      containers:
      - name: fluentd-elasticsearch # 容器的名称
        image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2 # 使用的镜像
        resources: # 资源说明
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
       # 日志收集器需要访问节点上的文件系统。
		# 这里使用了 hostPath 卷,将节点上的目录
		# 如 /var/log 和 /var/lib/docker/containers挂载到 Pod 的容器内部。
		# 这样,Fluentd 容器就能读取这些目录下的日志文件了。
        volumeMounts: # 挂载说明
            - name: varlog
              mountPath: /var/log
            - name: varlibdockercontainers
              mountPath: /var/lib/docker/containers
              readOnly: true
            - name: config-volume
              mountPath: /etc/fluent/fluent.conf
              subPath: fluent.conf
      terminationGracePeriodSeconds: 30
      volumes: # 数据卷说明
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
      - name: config-volume
        configMap:
          name: fluentd-config
4.5:容忍度和污点

容忍度是deamonSet非常重要的概念,Kubernetes 集群的主节点(Master) 通常自带一个污点(Taint)

例如上面说明的:node-role.kubernetes.io/control-plane:NoSchedule

污点的存在可以:防止普通工作负载 Pod 被调度到主节点上。

如果我们希望 DaemonSet 的 Pod(如日志收集器)也能在主节点上运行,就必须在 Pod 模板中声明相应的 tolerations,表示"我容忍这个污点,请允许我调度上去"。

例如上面的例子中:Pod 容忍了控制平面(Control-Plane)和主节点(Master)的污点,因此它可以在所有节点(包括主节点) 上运行。

4.6:如何控制 DaemonSet 的运行范围

你并不总是希望 DaemonSet 在所有节点上运行。可以通过以下两种方式控制:

nodeSelector - 节点选择器

只在带有特定标签的节点上运行。

  1. 先给节点打上标签kubectl label nodes <node-name> disktype=ssd
  2. 然后在 DaemonSet 的 spec.template.spec 中配置 nodeSelector
yaml 复制代码
spec:
  template:
    spec:
      nodeSelector:
        disktype: ssd
      containers:
      - ...

样,DaemonSet 只会在标签为 disktype=ssd 的节点上创建 Pod。

使用 nodeAffinity(节点亲和性)

nodeAffinity 提供了比 nodeSelector 更强大、更灵活的表达式来控制 Pod 的调度。

yaml 复制代码
spec:
  template:
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions: # 这表示 Pod 只能被调度到带有 disktype 标签,且值为 ssd 或 nvme 的节点上。
              - key: disktype
                operator: In
                values:
                - ssd
                - nvme
      containers:
      - ...

5:HPA 自动扩/缩容

HPA 是一种控制器,它能根据实时监控到的应用负载(如 CPU 使用率、内存使用率或其他自定义指标),自动地调整 Pod 的数量,以确保应用始终有适量的资源来处理流量。

在传统运维中:

  • 流量高峰时,需要手动扩容 Pod 数量,响应慢,容易出错。
  • 流量低谷时,需要手动缩容,否则造成资源浪费。

HPA 解决了这个痛点,实现了 "按需分配、弹性伸缩" 的云原生核心理念。

5.1:关键组件

指标来源 - Metrics Source

  • Metrics Server:核心指标,如 CPU/内存使用率(必须安装)。
  • Custom Metrics API:自定义指标,如 QPS(每秒查询数)、请求延迟等(需额外组件如 Prometheus Adapter)。

HPA Controller

  • 定期(默认每 30 秒)检查目标资源(如 Deployment、StatefulSet)的指标。
  • 根据当前指标值与目标值的比例,计算期望的 Pod 副本数。
  • 更新目标资源(如 Deployment)的 replicas 字段。

工作负载控制器

  • Deployment、ReplicaSet 或 StatefulSet 控制器检测到 replicas 字段变化,然后创建或删除 Pod
5.2:核心算法

HPA的核心算法是,当检测到目标资源的指标后,如何计算出对应的副本数

复制代码
期望副本数 = ceil[当前副本数 * (当前指标值 / 目标指标值)]

假设一个 Deployment 当前有 3 个 Pod,HPA 配置的目标 CPU 使用率为 50%。

场景:需要扩容

  • 当前平均 CPU 使用率:60%
  • 计算:期望副本数 = ceil[3 * (60 / 50)] = ceil[3 * 1.2] = ceil[3.6] = 4
  • 结果:HPA 会将 Pod 数扩容到 4 个

场景:需要缩容

  • 当前平均 CPU 使用率:30%
  • 计算:期望副本数 = ceil[3 * (30 / 50)] = ceil[3 * 0.6] = ceil[1.8] = 2
  • 结果:HPA 会将 Pod 数缩容到 2 个
5.3:一个完整的示例

第一步:准备Metrics Server

bash 复制代码
# 使用 kubectl 安装(以最新版本为例)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

# 验证安装
kubectl top nodes
kubectl top pods

第二步:创建一个deployment(这里测试)

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-apache
spec:
  selector:
    matchLabels:
      run: php-apache
  replicas: 1
  template:
    metadata:
      labels:
        run: php-apache
    spec:
      containers:
      - name: php-apache
        image: k8s.gcr.io/hpa-example
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: 200m   # 非常重要!HPA 依赖 requests 进行计算
            memory: 256Mi
          limits:
            cpu: 500m
            memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
  name: php-apache
  labels:
    run: php-apache
spec:
  ports:
  - port: 80
  selector:
    run: php-apache

🔴 必须设置 resources.requests,否则 HPA 无法计算 CPU 使用率百分比。

第三步:创建HPA

yaml 复制代码
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler # 指定kind = HPA
metadata:
  name: php-apache-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: php-apache  # 指向要自动扩缩容的 Deployment
  minReplicas: 1      # 最小副本数
  maxReplicas: 10     # 最大副本数
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization  # 使用率模式
        averageUtilization: 50  # 目标 CPU 使用率:50%
  behavior:  # 扩缩容行为配置(v2beta2+ 版本特性)
    scaleDown:
      stabilizationWindowSeconds: 300  # 缩容稳定窗口:5分钟
      policies:
      - type: Percent
        value: 10
        periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 0  # 扩容立即执行
      policies:
      - type: Percent
        value: 100
        periodSeconds: 15

扩展 - 扩缩容行为配置

这是 HPA v2 的强大特性,可以防止 Pod 数量剧烈波动:

yaml 复制代码
behavior:
  scaleDown:  # 缩容策略
    stabilizationWindowSeconds: 300  # 稳定窗口:指标下降后等待5分钟再缩容
    policies:
    - type: Percent    # 百分比策略
      value: 10        # 每次最多减少当前副本数的10%
      periodSeconds: 60 # 每60秒评估一次
    - type: Pods       # 绝对值策略
      value: 2         # 每次最多减少2个Pod
      periodSeconds: 60
    selectPolicy: Min  # 取最保守的策略(取最小值)
  
  scaleUp:  # 扩容策略
    stabilizationWindowSeconds: 0  # 0表示立即扩容
    policies:
    - type: Percent
      value: 100       # 每次最多增加当前副本数的100%(即翻倍)
      periodSeconds: 15 # 每15秒评估一次
5.4:观察HPA工作

最常见的就是检查指定的HPA的工作状态

bash 复制代码
# 查看 HPA 详细信息
kubectl describe hpa php-apache-hpa

# 输出示例:
Name:                           php-apache-hpa
Namespace:                      default
Labels:                         <none>
Annotations:                    <none>
CreationTimestamp:              Mon, 01 Jan 2023 12:00:00 +0800
Reference:                      Deployment/php-apache
Metrics:                        ( current / target )
  resource cpu on pods  (as a percentage of request):  0% (0) / 50%
Min replicas:                                          1
Max replicas:                                          10
Deployment pods:                                       1 current / 1 desired
Conditions:
  Type            Status  Reason              Message
  ----            ------  ------              -------
  AbleToScale     True    ReadyForNewScale    recommended size matches current size
  ScalingActive   True    ValidMetricFound    the HPA was able to successfully calculate...
  ScalingLimited  False   DesiredWithinRange  the desired count is within the acceptable range
Events:           <none>

我们上面已经部署了一个 php-apache 应用(Deployment + Service)

同时创建了一个 HPA,目标是保持 CPU 使用率在 50%

现在我们要模拟线上流量,观察 HPA 如何自动响应

bash 复制代码
kubectl run \  # 创建一个 Pod
  -i --tty \  # 交互式终端,允许我们与容器交互(但这里实际是自动运行命令)
  load-generator \ # Pod 名称
  --rm \ # Pod 退出后自动删除
  --image=busybox \ # 使用轻量级 busybox 镜像
  --restart=Never -- \ # Pod 退出后不重启, -- 是分隔 kubectl 参数和容器命令
  /bin/sh -c "while sleep 0.01; do wget -q -O- http://php-apache; done" # 每 10 毫秒就向我们的 php-apache 服务发送一次 HTTP 请求,持续不断。

使用无限循环的内部指令,不断的提升php-apache CPU占用率,观察HPA的行动

bash 复制代码
# 持续观察 HPA 的变化
watch -n 1 kubectl get hpa php-apache-hpa

# ------ 初始状态
NAME             REFERENCE               TARGET   MINPODS   MAXPODS   REPLICAS   AGE
php-apache-hpa   Deployment/php-apache   0%/50%   1         10        1          5m

# 启动创建load-generator Pod脚本后,等待大约 30-60 秒(HPA 默认检查间隔),你会看到:
NAME             REFERENCE               TARGET   MINPODS   MAXPODS   REPLICAS   AGE
php-apache-hpa   Deployment/php-apache   45%/50%  1         10        1          6m

# 触发扩容
NAME             REFERENCE               TARGET   MINPODS   MAXPODS   REPLICAS   AGE
php-apache-hpa   Deployment/php-apache   68%/50%  1         10        1          6m30s
# 这里显示 68% 但 REPLICAS 还是 1,因为 HPA 刚检测到,还没执行扩容
NAME             REFERENCE               TARGET   MINPODS   MAXPODS   REPLICAS   AGE
php-apache-hpa   Deployment/php-apache   68%/50%  1         10        2          7m

# 新 Pod 就绪,负载均衡
NAME             REFERENCE               TARGET   MINPODS   MAXPODS   REPLICAS   AGE
php-apache-hpa   Deployment/php-apache   35%/50%  1         10        2          8m

# 停止负载脚本,观察缩容
NAME             REFERENCE               TARGET   MINPODS   MAXPODS   REPLICAS   AGE
php-apache-hpa   Deployment/php-apache   5%/50%   1         10        2          15m

NAME             REFERENCE               TARGET   MINPODS   MAXPODS   REPLICAS   AGE
php-apache-hpa   Deployment/php-apache   5%/50%   1         10        1          16m
5.5:HPA 的最佳实践与注意事项

最佳实践

  1. 设置合理的 requests:这是 HPA 计算的基础
  2. 使用 readinessProbe:确保新 Pod 完全就绪后再纳入负载计算
  3. 配置 behavior:防止抖动,尤其是 scaleDown.stabilizationWindowSeconds
  4. 渐进式扩缩容:设置 policies 避免副本数突变
  5. 监控 HPA 事件:kubectl describe hpa 查看 Events
  6. 预热期设置:对于 JVM 等需要预热的应用,适当延长扩容等待时间

使用HPA常见的问题

HPA 不工作:

  • 检查 Metrics Server 是否安装
  • 检查 Pod 是否设置了 resources.requests
  • 检查 kubectl top pods 能否获取指标

扩容太激进或缩容太快:

  • 调整 behavior 配置
  • 增加 stabilizationWindowSeconds

使用自定义指标:

  • 需要安装 Prometheus + Prometheus Adapter
  • 确保指标名称和格式正确
5.6:和其他的扩缩容对比
特性 HPA(水平扩缩容) VPA(垂直扩缩容) Cluster Autoscaler(集群扩缩容)
扩缩对象 Pod 数量 Pod 资源 requests/limits 节点数量
调整方式 增减 Pod 调整单个 Pod 的 CPU/内存 增减工作节点
适用场景 无状态应用 有状态/无状态应用 节点资源不足时
重启 Pod 不需要(新创建) 需要(更新资源) 不需要

四:服务发布

1:service(重点)

在学习service前,先说说为什么需要service,核心原因是pod的问题

  1. 动态 IP:Pod 的 IP 地址不是固定的。当 Pod 重启、迁移或扩缩容时,IP 会变化。
  2. 多个副本:一个 Deployment 可能有多个 Pod 副本,每个都有不同的 IP。
  3. 健康检查:如何知道哪个 Pod 是健康的,可以接收流量?

如果没有service,每次 Pod 变化,你都需要手动更新所有依赖它的应用配置,这在动态的 Kubernetes 环境中完全不可行。

1.1:service的本质

service的本质就是在pod之上创建了一个稳定的抽象层:

service的核心价值

稳定的虚拟 IP(ClusterIP):客户端只需要记住这个 IP

负载均衡:将请求均匀分发到后端的健康 Pod

服务发现:通过 DNS 名称访问服务

会话保持:可选地将同一客户端的请求转发到同一 Pod

1.2:service工作原理

整体结构

Service 本身不转发流量,它只是规则的制定者,真正干活的是节点上的网络组件

核心组件

1️⃣ kube-proxy:流量的实际指挥者

kube-proxy 不是传统意义上的代理(proxy),它不直接转发数据包。它的核心工作是:

Kube-proxy主要有iptables模式、ipvs模式和userspace模式(已被废弃)

2️⃣ endpoints控制器:Pod的追踪器

Endpoints 控制器是 kube-controller-manager 的一部分,它的工作流:

bash 复制代码
# 查看 Service 的 Endpoints
kubectl get endpoints my-service -o yaml
yaml 复制代码
# 输出示例:
apiVersion: v1
kind: Endpoints
metadata:
  name: my-service
  namespace: default
subsets:
- addresses:
  - ip: 10.244.1.10
    nodeName: node1
    targetRef:
      kind: Pod
      name: web-pod-1
  - ip: 10.244.1.11
    nodeName: node1
    targetRef:
      kind: Pod
      name: web-pod-2
  ports:
  - port: 8080
    protocol: TCP

如果 Endpoints 为空,说明没有 Pod 匹配 Service 的 selector!

3️⃣ coreDNS:服务的电话本

coreDNS为service提供DNS解析

bash 复制代码
# 在 Pod 中测试 DNS
kubectl run -it --rm --image=busybox dns-test -- /bin/sh

# 查询完整域名
nslookup my-service.default.svc.cluster.local

# 查询同一命名空间内的服务(简写)
nslookup my-service

# 查询不同命名空间
nslookup my-service.other-namespace

# 对于 Headless Service(clusterIP: None)
nslookup headless-service
# 返回所有 Pod IP,而不是 Service IP

把 Service 想象成一个餐厅的订餐热线

组件 餐厅类比 说明
Service 订餐热线号码 固定不变,客户只需记住这个号
ClusterIP 热线总机号码 虚拟号码,不直接对应厨师
Endpoints 值班厨师名单 动态更新,谁今天上班就列谁
kube-proxy 接线员系统 接到电话,转接给合适的厨师
Pod 厨师 实际做菜的人,可能换班、请假
客户端 顾客 只打电话,不关心哪个厨师做菜
  1. 配置阶段:餐厅设置热线电话,登记今天上班的厨师
  2. 接线阶段:接线员系统学习:热线→厨师1、热线→厨师2...
  3. 服务阶段:顾客打电话→接线员接听→转给空闲厨师→厨师做菜→送回给顾客

所以:Service = 稳定的虚拟IP + 动态的Pod发现 + 智能的负载均衡

1.3:service类型

clusterIP -> 集群内部服务 -> 默认类型

clusterIP分配一个集群内部的虚拟 IP 地址(在 Service CIDR 范围内,如 10.96.0.0/12),只能从集群内部访问

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: backend-api
spec:
  type: ClusterIP  # 可省略,默认就是 ClusterIP
  selector:
    app: backend
  ports:
  - port: 80        # Service 对外的端口
    targetPort: 8080 # Pod 监听的端口
    protocol: TCP	

上面是指定service类型为clusterIP的定义,可以通过如下的方式进行访问

bash 复制代码
# 方式1:使用 DNS 名称(推荐)
# 在同一命名空间内
curl http://backend-api
# 在不同命名空间
curl http://backend-api.dev.svc.cluster.local

# 方式2:使用 ClusterIP
# 先获取 ClusterIP
kubectl get svc backend-api
# NAME          TYPE        CLUSTER-IP      PORT(S)
# backend-api   ClusterIP   10.96.123.456   80/TCP

curl http://10.96.123.456
场景 说明 示例
微服务间通信 服务之间内部调用 前端调用用户服务
数据库访问 应用访问数据库 Web应用访问 MySQL
内部API 不对外暴露的API 监控API、管理API
缓存访问 访问Redis/Memcached 应用访问Redis缓存

nodeport -> 节点端口服务

在每个节点上打开一个固定端口(30000-32767),外部用户可以通过 任何节点的IP:NodePort 访问服务。

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: web-app
spec:
  type: NodePort
  selector:
    app: web
  ports:
  - port: 80          # Service 端口(集群内部用)
    targetPort: 8080  # Pod 端口
    nodePort: 31080   # 节点端口(可选,不指定则随机分配)
bash 复制代码
# 1. 查看分配的 NodePort
kubectl get svc web-app
# NAME      TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)
# web-app   NodePort   10.96.123.456   <none>        80:31080/TCP
#                               ↑节点端口

# 2. 通过节点IP访问(假设节点IP是 192.168.1.100)
curl http://192.168.1.100:31080
curl http://192.168.1.101:31080  # 另一个节点也可以
场景 说明 优点 缺点
开发测试 快速验证服务 简单,无需额外配置 端口范围有限
演示环境 给客户演示 直接访问,无需域名 需要知道节点IP
没有云负载均衡器 本地/裸机集群 不依赖云服务 需要外部负载均衡
临时外部访问 临时暴露服务 快速启用 生产环境不推荐

在nodeport可以指定流量路径:

yaml 复制代码
spec:
	type: NodePort
	externalTrafficPolicy: Local # 或者Cluster(默认的流量路径是cluster)
  • Cluster:流量可能跨节点,丢失真实客户端IP
  • Local:保留客户端IP,但要求节点上有 Pod(否则连接失败)

LoadBalance -> 云负载均衡器

LoadBalancer 是 NodePort 的升级版,它会自动创建云厂商的负载均衡器(如 AWS ELB、GCP Load Balancer、Azure LB),并提供外部 IP 或 DNS 名称。

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: production-web
spec:
  type: LoadBalancer
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 8080
  # 云厂商特定注解
  # metadata:
  #   annotations:
  #     service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
bash 复制代码
# 查看 LoadBalancer 分配的外部 IP/DNS
kubectl get svc production-web
# NAME             TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)
# production-web   LoadBalancer   10.96.123.456   203.0.113.100    80:31080/TCP
#                                          ↑云厂商分配的外部IP

# 外部访问
curl http://203.0.113.100
# 或者使用云厂商分配的 DNS
curl http://xxx.elb.amazonaws.com
场景 说明 云厂商配置示例
生产环境 Web 服务 对外提供 Web 服务 AWS: ELB/NLB
API 网关 对外 API 入口 GCP: HTTP(S) Load Balancer
需要 SSL 终止 HTTPS 服务 Azure: Application Gateway
高可用需求 多区域负载均衡 跨可用区部署

下面是阿里云配置示例:

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: aliyun-lb-service 
  annotations: # 注解说明
    service.beta.kubernetes.io/alibaba-cloud-loadbalancer-address-type: "intranet"
    service.beta.kubernetes.io/alibaba-cloud-loadbalancer-charge-type: "paybybandwidth"
spec:
  type: LoadBalancer

ExternalName -> 外部服务代理

ExternalName 是一种特殊的 Service,它没有 selector,也不创建 Endpoints,而是返回一个 DNS CNAME 记录,将 Service 名称重定向到外部域名。

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: external-database
spec:
  type: ExternalName
  externalName: database.company.com  # 外部服务地址
bash 复制代码
集群内部访问: http://external-database.default.svc
        ↓
DNS 解析时返回 CNAME 记录
        ↓
实际解析到: database.company.com
        ↓
访问外部服务
场景 说明 示例
访问外部数据库 迁移过程中访问云数据库 AWS RDS, Cloud SQL
第三方API 访问外部 SaaS 服务 Stripe, SendGrid API
混合云架构 访问本地数据中心服务 本地 Oracle 数据库
逐步迁移 从外部服务迁移到内部 临时重定向

选择树

一个完整的电商服务配置

yaml 复制代码
# 1. 前端 Web(对外访问)
apiVersion: v1
kind: Service
metadata:
  name: frontend-web
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "arn:aws:acm:..."
spec:
  type: LoadBalancer  # 生产环境对外
  ports:
  - name: https
    port: 443
    targetPort: 3000
  selector:
    app: frontend

# 2. 后端 API(集群内部)
apiVersion: v1
kind: Service
metadata:
  name: backend-api
spec:
  type: ClusterIP  # 内部服务
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: backend

# 3. 数据库(有状态,直接访问每个实例)
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  clusterIP: None  # Headless Service
  ports:
  - port: 3306
  selector:
    app: mysql

# 4. 外部支付网关
apiVersion: v1
kind: Service
metadata:
  name: payment-gateway
spec:
  type: ExternalName
  externalName: api.stripe.com

# 5. Redis缓存(集群内部)
apiVersion: v1
kind: Service
metadata:
  name: redis
spec:
  type: ClusterIP
  ports:
  - port: 6379
  selector:
    app: redis
特性 ClusterIP NodePort LoadBalancer ExternalName
访问范围 集群内部 节点IP:端口 外部IP/DNS 外部服务
IP分配 虚拟ClusterIP ClusterIP+节点端口 云厂商外部IP
负载均衡 有(云LB)
成本 免费 免费 云厂商收费 免费
生产适用 内部服务 不推荐 推荐 特殊场景
配置复杂度 简单 简单 中等(云特定) 简单
DNS 集群内部DNS 需知道节点IP 外部DNS CNAME重定向

🎉 黄金法则:

  • 集群内部通信 → ClusterIP
  • 生产环境对外 → LoadBalancer + Ingress
  • 开发测试 → NodePort
  • 有状态应用 → Headless Service
  • 访问外部服务 → ExternalName

2:Ingress

2.1:Ingress介绍

Ingress 是 Kubernetes 的 API 对象,它管理外部访问集群内部服务的 HTTP 和 HTTPS 路由规则。

所以,简单来说:Ingress = HTTP/HTTPS 的路由规则管理器

Ingress充当了k8s的网关,负责接收外部的请求,并将请求根据既定的规则转发到正确的目标上(对外暴露service的一种方式)

为什么还有ingress呢,因为上面介绍的暴露service的两种方式都存在弊端

方式 优点 缺点
nodePort 简单直接,暴露节点的端口就可以了 占用节点端口,依赖节点(节点故障无法访问等等)
LoadBalance 不占用节点端口,高可用(VIP漂移) 需要单独的配置LB插件
2.2:Ingress工作原理

1.Ingress通过http/https对外暴露服务

  1. Ingress负责反代规则(哪个域名对应哪个service)
  2. Ingress Controller负责读取Ingress规则将请求转发至合适的Pod,并可动态感知Ingress规则变化
2.3:Ingress controller

Ingress Controller 是负责处理 Ingress 请求的组件。

Kubernetes 提供了多种 Ingress Controller,可以根据需要选择。以下是常用的 Ingress Controller:

  • Nginx Ingress Controller:基于 Nginx 的 Ingress Controller,功能强大,易于配置。
  • HAProxy Ingress Controller:基于 HAProxy 的 Ingress Controller,性能优异,适用于高并发场景。
  • Contour Ingress Controller:基于 Envoy 的 Ingress Controller,功能丰富,适用于多云场景。

常规来说,我们一般使用nginx ingress, 先下载对应yaml文件 - [https://gitee.com/mirrors/ingress-nginx\]

下载之后,在这个deploy/static/provider文件夹下发现有下面这么多文件夹:

对于大多数国内用户,如果你的Kubernetes集群是搭建在:

  • 阿里云、腾讯云、华为云等公有云上 → 应选择 cloud 目录下的 deploy.yaml
  • 公司内部机房或家用服务器上 → 应选择 baremetal 目录下的 deploy.yaml
bash 复制代码
# 替换镜像
# 将文件中的 registry.k8s.io 镜像替换为国内源
# 使用sed命令一键替换(推荐)
sed -i 's|registry.k8s.io/ingress-nginx/|registry.aliyuncs.com/google_containers/|g' deploy.yaml

# 部署
kubectl apply -f deploy.yaml
2.4:编写 ingress 规则

在部署好ingress controller之后,后续最为关键的就是要编写ingress规则

先部署一个测试的后端业务服务,这里使用最为经典的nginx

yaml 复制代码
# nginx-app.yaml
apiVersion: apps/v1
kind: Deployment # 声明资源类型是deployment -> 无状态资源
metadata:
  name: nginx-deployment # 声明名称
spec: # 设置规约值
  replicas: 2 # 声明副本数为2
  selector:
    matchLabels:
      app: nginx # 声明选择器是Pod中标签为nginx的
  template: # 声明模板 -> pod
    metadata:
      labels:
        app: nginx # 声明模板标签为nginx
    spec: # 模板规约,容器的名称,使用的镜像,暴露的端口
      containers:
      - name: nginx 
        image: nginx:alpine
        ports:
        - containerPort: 80
---
apiVersion: v1 
kind: Service # 声明资源的类型是Service, 东西流量,为了微服务之间的互相调用
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx # 声明选择器是nginx的App
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: ClusterIP # 使用clusterIP的service类型
bash 复制代码
kubectl apply -f nginx-app.yaml

创建 Ingress 规则(核心步骤)

这是最关键的一步,它定义了外部访问的路由规则。

以下是一个将特定域名流量转发到上面创建的 nginx-service 的示例

将外部 HTTP/HTTPS 流量路由到内部服务的核心配置。

yaml 复制代码
apiVersion: networking.k8s.io/v1  # Kubernetes API版本,v1是当前稳定版本
kind: Ingress                     # 资源类型:Ingress
metadata:
  name: example-ingress          # Ingress资源的名称,在命名空间中必须唯一
spec:
  rules:
  - host: "demo.yourdomain.com"  # 规则1:基于域名的虚拟主机
  # - host: "api.yourdomain.com" # 规则2:可以添加多个规则
  http:
  paths:
  - pathType: Prefix # 前缀匹配
    path: "/"
    backend:
      service:
        name: nginx-service # # Service资源名称,必须在同一命名空间
        port:
          number: 80 # # Service端口(或使用 name: "http" 引用端口名称)
bash 复制代码
kubectl apply -f example-ingress.yaml

pathType - 路径匹配模式(v1 API 关键字段)

类型 匹配规则 示例 path: "/api" 优先级
Exact 精确匹配,区分大小写 匹配 /api,不匹配 /api//api/v1
Prefix 前缀匹配,按路径元素分隔 匹配 /api/api/v1,不匹配 /apis
ImplementationSpecific 由 Ingress Controller 决定 取决于实现,通常同 Prefix

匹配优先级:精确匹配 > 前缀匹配(更长的路径前缀优先)。

  1. 用户访问 http://demo.yourdomain.com/
  2. DNS解析到 Ingress Controller 的 Service IP(云负载均衡器或节点IP)
  3. Ingress Controller(Nginx) 接收请求,查看 Host: demo.yourdomain.com
  4. 匹配规则:找到对应 host 的规则,然后按 pathTypepath 匹配路径
  5. 转发请求:将请求代理到 nginx-service:80 这个 Service
  6. Service 负载均衡到后端 Pod(你的应用容器)

五:配置和存储

1:配置管理

Kubernetes(K8s)的配置管理是其核心能力之一,它旨在将应用配置(如配置文件、环境变量、命令行参数等)从容器镜像中解耦,从而实现 "一次构建,到处部署" 的云原生理念

在传统模式下,配置通常被"硬编码"到应用代码或Docker镜像中,导致不同环境(开发、测试、生产)需要不同的镜像,管理复杂且易出错。K8s的配置管理通过一系列原生API对象解决了这个问题。

K8s的配置管理的核心是:将配置数据作为独立的K8s资源进行管理,并通过卷(Volume)或环境变量等方式动态注入到Pod容器中,实现配置与应用的分离。

1.1:configMap

管理非敏感的配置数据,例如配置文件、命令行参数、环境变量、端口号等。

  • 数据以键值对(key-value)形式存储。
  • Value可以是简单的字符串,也可以是完整的配置文件内容(如JSON、XML、Properties、YAML等)。
  • 存储大小有限制(默认约1MB in etcd)。

创建configMap的资源清单

yaml 复制代码
apiVersion: v1
kind: ConfigMap
metadata:
  name: game-config
  namespace: default
  labels:
    app: game
    tier: backend
data:
  # 简单键值对
  game.type: "mmorpg"
  game.difficulty: "hard"
  
  # 配置文件内容(整个文件作为值)
  server.properties: |
    enemy.types=aliens,monsters
    player.maximum-lives=5
    level.complexity=high
    
  # 另一个配置文件
  ui.properties: |
    color.good=purple
    color.bad=yellow
    allow.textmode=true
    
  # JSON 配置
  config.json: |
    {
      "logging": {
        "level": "DEBUG",
        "file": "/var/log/app.log"
      },
      "features": {
        "enableCache": true,
        "maxItems": 100
      }
    }

在 Pod 中使用 ConfigMap

  • 作为环境变量: 将整个ConfigMap或特定键注入为容器环境变量。
  • 作为文件(Volume挂载): 最常用、最灵活的方式。将ConfigMap中的每个键作为一个文件挂载到容器内的指定目录,文件内容即为键对应的值。支持热更新(Pod内文件会自动更新,但应用是否需要重载取决于自身)。
  • 作为命令行参数: 通过环境变量间接传递。
yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: configmap-env-pod
spec:
  containers:
  - name: test-container
    image: busybox
    command: [ "/bin/sh", "-c", "env" ]
    env:
    # 定义环境变量 LOG_LEVEL,值来自 ConfigMap
    - name: LOG_LEVEL
      valueFrom:
        configMapKeyRef:
          name: app-config        # ConfigMap 名称
          key: log-level          # ConfigMap 中的键
          optional: false         # 可选,如果为 true,ConfigMap 不存在时 Pod 也能启动
yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: configmap-env-all-pod
spec:
  containers:
  - name: test-container
    image: busybox
    command: [ "/bin/sh", "-c", "env" ]
    envFrom:
    - configMapRef:
        name: app-config          # 将 ConfigMap 中的所有键值对作为环境变量
yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: configmap-volume-pod
spec:
  containers:
  - name: test-container
    image: busybox
    command: [ "/bin/sh", "-c", "ls -la /etc/config && cat /etc/config/*" ]
    volumeMounts:
    - name: config-volume
      mountPath: /etc/config       # ConfigMap 中的每个键都会成为一个文件
      readOnly: true
  volumes:
  - name: config-volume
    configMap:
      name: game-config           # 引用的 ConfigMap

# 挂载效果
/etc/config/
├── game.type         # 文件内容: "mmorpg"
├── game.difficulty   # 文件内容: "hard"
├── server.properties # 文件内容: "enemy.types=aliens..."
└── ui.properties     # 文件内容: "color.good=purple..."

🎉 对于一些敏感服务的配置文件,在线上有时是不允许修改的,此时在配置 configmap 时可以设置 immutable: true 来禁止修改

1.2:Secret

专门用于管理和存储敏感信息,如密码、OAuth令牌、SSH密钥、TLS证书等。

使用方式与ConfigMap几乎完全相同(也支持环境变量和Volume挂载)。

K8s会对Secret数据进行Base64编码(仅是一种简单的混淆,并非加密)。在Master节点上,etcd中存储的数据可以启用静态加密。

Secret有三种主要类型:

  • Opaque(通用,默认)
  • kubernetes.io/tls(TLS证书)
  • kubernetes.io/dockerconfigjson(镜像仓库认证)。
场景 使用 ConfigMap 使用 Secret
数据库连接字符串 ❌ 敏感 ✅ 使用 Secret
日志级别 ✅ 非敏感
API 端点 URL ✅ 非敏感
TLS 证书 ❌ 敏感 ✅ 使用 Secret
特性开关 ✅ 非敏感
1.3:subPath

使用 ConfigMap 或 Secret 挂载到目录的时候,会将容器中源目录给覆盖掉,此时我们可能只想覆盖目录中的某一个文件,但是这样的操作会覆盖整个文件,因此需要使用到 SubPath

复制代码
假设你有一个容器镜像,其目录结构如下:
/opt/app/
├── app.jar
├── config/
│   ├── application.yaml    # 需要被替换的配置
│   └── security.yaml       # 需要保留的默认配置
└── logs/

如果你直接将 ConfigMap 挂载到 /opt/app/config

yaml 复制代码
volumeMounts:
- name: config-volume
  mountPath: /opt/app/config  # 这会覆盖整个 config 目录!

security.yaml 文件会消失!因为整个目录被 ConfigMap 的内容覆盖了。

这时候subPath就派出用场了:

yaml 复制代码
volumeMounts:
- name: config-volume
  mountPath: /opt/app/config/application.yaml  # 挂载到特定文件路径
  subPath: application.yaml                     # 只挂载这个文件

挂载ConfigMap中的单个文件

yaml 复制代码
# 指定configmap的资源文件,这里声明application.yaml和logging.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  application.yaml: |
    server:
      port: 8080
    database:
      url: jdbc:mysql://localhost:3306/mydb
  logging.yaml: |
    logging:
      level: INFO
      file: /var/log/app.log

# pod文件资源
apiVersion: v1
kind: Pod
metadata:
  name: subpath-example # pod的名称
spec:
  containers:
  - name: app # 容器名称
    image: myapp:latest # 使用的镜像
    volumeMounts: # 指定文件挂载
    - name: config-volume
      mountPath: /etc/app/application.yaml  # 挂载为单个文件
      subPath: application.yaml              # 只挂载这个键
    - name: config-volume
      mountPath: /etc/app/logging.yaml      # 挂载另一个文件
      subPath: logging.yaml
  volumes: # 指定卷
  - name: config-volume
    configMap:
      name: app-config

挂载 Secret 中的特定文件(TLS 证书)

yaml 复制代码
apiVersion: v1
kind: Secret
metadata:
  name: tls-secret
type: kubernetes.io/tls
data:
  tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0t...
  tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0t...
---
apiVersion: v1
kind: Pod
metadata:
  name: web-server
spec:
  containers:
  - name: nginx
    image: nginx:latest
    volumeMounts:
    - name: tls-volume
      mountPath: /etc/nginx/ssl/server.crt  # 挂载证书文件
      subPath: tls.crt
    - name: tls-volume
      mountPath: /etc/nginx/ssl/server.key  # 挂载私钥文件
      subPath: tls.key
  volumes:
  - name: tls-volume
    secret:
      secretName: tls-secret
1.4:配置热更新

我们通常会将项目的配置文件作为 configmap 然后挂载到 pod,那么如果更新 configmap 中的配置,会不会更新到 pod 中呢?这得分成几种情况:

  • 默认方式:会更新,更新周期是更新时间 + 缓存时间
  • subPath:不会更新
  • 变量形式:如果 pod 中的一个变量是从 configmap 或 secret 中得到,同样也是不会更新的

对于 subPath 的方式,我们可以取消 subPath 的使用,将配置文件挂载到一个不存在的目录,避免目录的覆盖,然后再利用软连接的形式,将该文件链接到目标位置,但是如果目标位置原本就有文件,可能无法创建软链接,此时可以基于 postStart 操作执行删除命令,将默认的吻技安删除即可

2:持久化存储(挂载)

为什么需要持久化存储呢?

  • 普通容器存储:容器内部的文件是临时的,容器重启→数据丢失(就像电脑重启后,内存中的数据消失)
  • 持久化存储:数据独立于容器生命周期存在(就像把文件保存到硬盘,电脑重启后文件还在)

典型的应用场景:

  • 数据库(MySQL、PostgreSQL)
  • 文件共享服务
  • 日志集中存储
  • 应用配置文件
2.1:volumes卷

卷就像是给你的容器挂载了一个外部的U盘,与Pod生命周期绑定:Pod删除,Volume通常也会被清理

主要有两种类型:

2.1.1:emptyDir

临时目录,Pod删除数据就没了

yaml 复制代码
# 定义一个使用 EmptyDir 临时存储卷的 Pod 资源
apiVersion: v1          # API版本:Pod 属于核心组资源,v1 是稳定版本
kind: Pod               # 资源类型:声明该YAML文件定义的是 Pod 资源
metadata:               # 元数据段:用于描述 Pod 的基础信息(名称、标签、注解等)
  name: test-pd         # Pod名称:在所属命名空间内唯一标识该Pod
spec:                   # 规格段:定义 Pod 的核心配置(容器、存储、网络、调度等)
  containers:           # 容器列表:一个Pod可包含多个容器,此处是数组形式(- 开头表示元素)
  - image: nginx        # 容器镜像:使用官方 nginx 镜像(默认拉取最新版)
    name: nginx-emptydir # 容器名称:在Pod内唯一标识该容器
    volumeMounts:       # 容器挂载配置:定义该容器需要挂载的 Volume 列表
    - mountPath: /cache # 挂载路径:将 Volume 挂载到容器内的 /cache 目录
      name: cache-volume # 挂载的Volume名称:关联下方 spec.volumes 中定义的 cache-volume(名称必须一致)
  volumes:              # Pod级Volume列表:定义该Pod可用的存储卷(所有容器可共享)
  - name: cache-volume  # Volume名称:与容器 volumeMounts.name 对应,作为挂载关联标识
    emptyDir: {}        # Volume类型:emptyDir(空目录卷),{} 表示使用默认配置
                        # emptyDir特性:Pod调度到节点后自动创建,Pod删除/迁移时数据永久丢失
                        # 默认存储在节点磁盘,可配置 medium: Memory 改为内存存储(tmpfs)
2.1.2:hostPath

使用节点本地目录(⚠️危险:节点故障数据可能丢失)

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: nginx
    name: nginx-volume
    volumeMounts:
    - mountPath: /test-pd # 挂载到容器的哪个目录
      name: test-volume # 挂载哪个 volume
  volumes:
  - name: test-volume
    hostPath:
      path: /data # 节点中的目录
      type: Directory # 检查类型,在挂载前对挂载目录做什么检查操作,有多种选项,默认为空字符串,不做任何检查


类型:
空字符串:默认类型,不做任何检查
DirectoryOrCreate:如果给定的 path 不存在,就创建一个 755 的空目录
Directory:这个目录必须存在
FileOrCreate:如果给定的文件不存在,则创建一个空文件,权限为 644
File:这个文件必须存在
Socket:UNIX 套接字,必须存在
CharDevice:字符设备,必须存在
BlockDevice:块设备,必须存在

Kubernetes 节点(Node) 主机文件系统 Pod: test-pd Container: nginx-volume 关联映射(双向同步) 数据同步 Volume: test-volume
(HostPath 类型) /test-pd
(容器内挂载点) /data
(主机目录 / HostPath 源) 容器内读写 /test-pd 等同于读写主机 /data type: Directory
挂载前检查主机 /data 是已存在目录

2.2:NFS挂载

网络共享存储 - 就像公司里的网络共享盘(NAS),所有电脑都能访问

  • 网络共享:多节点可以同时访问同一存储
  • 数据集中管理:数据存储在单独的NFS服务器
  • 需要额外部署:需要先搭建NFS服务器
2.2.1:部署NFS过程
2.2.1.1:部署NFS Server

方案一:独立服务器部署

bash 复制代码
# 在专用服务器上(假设IP:192.168.1.100)
# 1. 安装NFS服务
sudo apt-get update
sudo apt-get install nfs-kernel-server -y  # Ubuntu/Debian
# 或
sudo yum install nfs-utils -y  # CentOS/RHEL

# 2. 创建共享目录
sudo mkdir -p /data/nfs-share
sudo chmod 777 /data/nfs-share  # 简化权限,生产环境需细化

# 3. 配置共享目录
sudo vim /etc/exports

方案二:在K8s集群内部署NFS Server

yaml 复制代码
# nfs-server.yaml
apiVersion: apps/v1          # 使用Deployment资源的API版本
kind: Deployment             # 定义资源类型为部署控制器
metadata:                    # 资源的元数据部分
  name: nfs-server           # 部署的名称,用于标识和引用
spec:                        # 部署的具体规格配置
  replicas: 1                # Pod副本数量,设为1表示只运行一个NFS服务器实例
  selector:                  # 标签选择器,用于选择要管理的Pod
    matchLabels:             # 匹配标签的条件
      app: nfs-server        # 必须匹配标签app=nfs-server的Pod
  template:                  # Pod模板定义,描述如何创建Pod
    metadata:                # Pod的元数据
      labels:                # 给Pod设置的标签
        app: nfs-server      # 标签键值对,与上面的选择器匹配
    spec:                    # Pod的具体规格
      containers:            # 容器定义列表
      - name: nfs-server     # 容器名称
        image: gcr.io/google_containers/volume-nfs:0.8  # NFS服务器容器镜像
        ports:               # 容器暴露的端口列表
        - name: nfs          # 端口名称,便于识别
          containerPort: 2049  # NFS协议主端口,用于文件传输
        - name: mountd       # 端口名称
          containerPort: 20048 # NFS挂载守护进程端口,处理挂载请求
        - name: rpcbind      # 端口名称
          containerPort: 111   # RPC绑定端口,NFS依赖的服务端口
        securityContext:     # 容器的安全上下文配置
          privileged: true   # 授予容器特权模式,NFS服务需要访问内核功能
        volumeMounts:        # 容器内的卷挂载配置
        - mountPath: /exports  # 容器内的挂载点路径,NFS将共享此目录
          name: nfs-data     # 引用的卷名称,必须与下面的volumes名称匹配
      volumes:               # Pod级别的存储卷定义
      - name: nfs-data       # 卷名称,供容器引用
        hostPath:            # 卷类型:使用宿主机路径
          path: /data/nfs    # 宿主机上的实际目录路径
---
apiVersion: v1              # 使用Service资源的API版本
kind: Service               # 定义资源类型为服务
metadata:                   # 服务的元数据
  name: nfs-server          # 服务的名称
spec:                       # 服务的规格配置
  ports:                    # 服务暴露的端口列表
  - name: nfs               # 端口名称
    port: 2049              # 服务端口,集群内部访问时使用的端口
    targetPort: 2049        # 容器端口,流量转发到容器的这个端口
  - name: mountd            # 端口名称
    port: 20048             # 服务端口
    targetPort: 20048       # 容器端口
  - name: rpcbind           # 端口名称
    port: 111               # 服务端口
    targetPort: 111         # 容器端口
  selector:                 # 标签选择器,指定后端Pod
    app: nfs-server         # 选择标签为app=nfs-server的Pod作为后端
  clusterIP: None           # 设置为Headless Service,不分配集群IP
                           # 这样Pod可以直接通过DNS记录访问,适合有状态服务

宿主机存储 Kubernetes集群 DNS查询 返回Pod IP 读写数据 宿主机 /data/nfs nfs-server Service NFS Server Pod NFS客户端Pod

2.2.1.2:准备k8s节点
bash 复制代码
# 在每个K8s Worker节点上安装NFS客户端
# Ubuntu/Debian
sudo apt-get install nfs-common -y

# CentOS/RHEL
sudo yum install nfs-utils -y

# 测试连接
showmount -e 192.168.1.100
# 应显示:/data/nfs-share *
2.2.1.3:k8s中使用nfs

方式一:直接在pod中使用

yaml 复制代码
# nfs-pod-direct.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nfs-web
spec:
  containers:
  - name: web-server
    image: nginx
    volumeMounts:
    - name: nfs-storage 
      mountPath: /usr/share/nginx/html  # Nginx默认网页目录
      readOnly: false
  volumes:
  - name: nfs-storage 
    nfs:
      server: 192.168.1.100  # NFS服务器IP
      path: /data/nfs-share  # 共享目录
      readOnly: false  # 读写模式

方式二:生产环境中推荐使用PVC/PV方式

yaml 复制代码
# 1. 创建PV(持久卷)
# nfs-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
  labels:
    type: nfs
spec:
  capacity:
    storage: 10Gi  # 逻辑容量
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany  # NFS支持多节点读写
  persistentVolumeReclaimPolicy: Retain  # 删除策略
  storageClassName: nfs
  nfs:
    path: /data/nfs-share  # NFS共享路径
    server: 192.168.1.100  # NFS服务器IP
    readOnly: false
yaml 复制代码
# 2. 创建PVC(持久卷声明)
# nfs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: nfs
  resources:
    requests:
      storage: 5Gi  # 申请5GB,不能超过PV的10GB
yaml 复制代码
# 3. Pod使用PVC
# nfs-pod-pvc.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nfs-pod
spec:
  containers:
  - name: app
    image: busybox
    command: ["sh", "-c", "echo 'Hello from NFS' > /data/hello.txt && tail -f /dev/null"]
    volumeMounts:
    - name: nfs-volume
      mountPath: /data
  volumes:
  - name: nfs-volume
    persistentVolumeClaim:
      claimName: nfs-pvc
2.2.2:高级配置与优化
2.2.2.1:NFS挂载选项
yaml 复制代码
volumes:
- name: nfs-storage
  nfs:
    server: 192.168.1.100
    path: /data/nfs-share
    readOnly: false
    # 高级挂载选项
    mountOptions:
    - hard  # 硬挂载:NFS不可用时进程等待
    - intr  # 允许中断NFS操作
    - nfsvers=4.1  # 指定NFS版本
    - timeo=600    # 超时时间(0.1秒单位)
    - retrans=2    # 重试次数
参数 说明 推荐值
hard 硬挂载,服务端无响应时客户端重试 ✅生产环境
soft 软挂载,超时后返回错误 ❌不推荐
intr 允许中断挂起的IO操作 ✅推荐
noatime 不更新访问时间,提升性能 ✅推荐
nodiratime 不更新目录访问时间 ✅推荐
nfsvers=4.1 使用NFSv4.1协议 ✅推荐
rsize=1048576 读数据块大小 根据网络调整
wsize=1048576 写数据块大小 根据网络调整
yaml 复制代码
# 高性能NFS配置示例
volumes:
- name: nfs-optimized
  nfs:
    server: 192.168.1.100
    path: /data/nfs
    readOnly: false
    mountOptions:
    - hard
    - intr
    - noatime
    - nodiratime
    - nfsvers=4.1
    - rsize=1048576  # 1MB读缓冲
    - wsize=1048576  # 1MB写缓冲
    - tcp           # 使用TCP协议
    - timeo=600     # 60秒超时
    - retrans=2     # 重试2次
2.2.2.2:生产环境最佳架构
2.2.3:NFS和其他存储方案的对比
特性 NFS HostPath 云存储(EBS) Ceph/GlusterFS
共享性 ✅多节点读写 ❌单节点 ❌通常单节点 ✅多节点
持久性 ✅好 ❌节点故障丢失 ✅好 ✅好
性能 ⚠️依赖网络 ✅本地磁盘快 ✅云盘性能 ✅分布式快
成本 ✅低 ✅免费 ❌高 ⚠️中等
复杂度 ⚠️中等 ✅简单 ✅简单 ❌复杂
适用场景 共享存储、中小规模 开发测试 云环境、数据库 大规模生产
2.3:pv和pvc

先举一个简单的例子快速理解下二者 -> 食堂吃饭

PV(PersistentVolume) = 食堂准备好的饭菜

  • 厨师(管理员)已经做好了:10份红烧肉、20份青菜、30碗米饭
  • 这些饭菜已经存在,放在保温桶里

PVC(PersistentVolumeClaim) = 你的打饭请求

  • 你说:"我要一份红烧肉 + 一碗米饭"
  • 你不需要知道饭菜是怎么做的、在哪做的

K8s系统 = 食堂阿姨

  • 听到你的请求

  • 从现有的饭菜里找到匹配的给你

  • 如果没找到匹配的,就告诉你"没有啦!"

    -------- 没有PV/PVC时 ----------

    开发人员需要关心存储细节

    开发者:我的MySQL需要存储!
    运维:你要多大的?什么类型的?放哪里?
    开发者:emm...20G?SSD?放哪我不知道啊!
    运维:那你自己去服务器上挂载个磁盘吧!
    开发者:我不会啊!

    --------- 有PV/PVC时 ----------

    开发人员只需要说"我要存储"

    开发者:我的MySQL需要20G存储!
    K8s系统:好的,已分配!
    开发者:这么简单?存储在哪?什么类型?
    K8s系统:你不用管,用就行了!

2.3.1:核心概念

PV - PersistentVolume - 资源存储池 - 运维人员关注

  • 管理员创建:运维人员提前准备好

  • 集群级别:整个K8s集群都能用

  • 独立存在:不依赖任何Pod

  • 多种类型:NFS、云硬盘、本地磁盘等

    存储资源池(PV们):
    ├── PV1:100G SSD云盘(快速)
    ├── PV2:500G HDD本地盘(慢速)
    ├── PV3:1TB NFS共享存储(共享)
    └── PV4:50G 高速SSD(超快)

PVC - PersistentVolumeClaim --- 存储申请单 - 开发人员关注

  • 开发人员创建:应用需要存储时申请

  • 命名空间级别:只在当前项目/命名空间有效

  • 声明需求:只要说"我要多大的、什么性能的"

  • 自动匹配:系统自动找合适的PV给你

    存储申请(PVC们):
    ├── 申请1:我要30G,快点就行(给MySQL)
    ├── 申请2:我要100G,能共享的(给文件服务器)
    └── 申请3:我要10G,最便宜的(给测试环境)

2.3.2:部署一个WordPress博客

步骤1:管理员准备存储(创建PV)

yaml 复制代码
# 管理员准备存储(创建PV)
# 运维人员创建两个"饭菜"(PV)
apiVersion: v1
kind: PersistentVolume
metadata:
  name: fast-ssd-pv        # PV名字:高速SSD
spec:
  capacity:
    storage: 100Gi         # 容量:100GB
  accessModes:
    - ReadWriteOnce        # 访问模式:一次只能一个Pod读写
  storageClassName: fast   # 存储类型标签:快速型
  hostPath:
    path: "/data/ssd"      # 实际存储位置
  
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: slow-hdd-pv        # PV名字:慢速HDD
spec:
  capacity:
    storage: 500Gi         # 容量:500GB
  accessModes:
    - ReadWriteMany        # 访问模式:多个Pod可以同时读写
  storageClassName: slow   # 存储类型标签:慢速型
  nfs:
    server: 192.168.1.100
    path: "/data/nfs"

步骤2:开发人员申请存储(创建PVC)

yaml 复制代码
# WordPress开发人员申请:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wp-storage-pvc     # PVC名字:WordPress存储申请
spec:
  accessModes:
    - ReadWriteOnce        # 需求:单Pod读写
  resources:
    requests:
      storage: 20Gi        # 需求:20GB空间
  storageClassName: fast   # 需求:快速存储(匹配fast标签)

步骤3:系统自动匹配

复制代码
K8s系统自动匹配过程:
1. 收到PVC申请:"要20G,快速,单Pod读写"
2. 查找PV池:
   - fast-ssd-pv:100G,fast类型,单Pod读写 ✅ 匹配!
   - slow-hdd-pv:500G,slow类型,多Pod读写 ❌ 类型不匹配
3. 绑定:把fast-ssd-pv分配给wp-storage-pvc
4. 状态变更:
   - fast-ssd-pv状态:Available → Bound(已绑定)
   - wp-storage-pvc状态:Pending → Bound(已满足)

步骤4:应用使用存储(Pod挂载)

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: wordpress-pod
spec:
  containers:
  - name: wordpress
    image: wordpress:latest
    volumeMounts:
    - name: data-volume      # 给卷起个名字
      mountPath: /var/www/html  # 挂载到容器这个路径
  volumes:
  - name: data-volume        # 卷的名字
    persistentVolumeClaim:    # 卷类型:使用PVC
      claimName: wp-storage-pvc  # 使用哪个PVC(重点是这一行!)
2.3.3:重要概念详解

访问模式(Access Modes)

就像文件的打开方式:

模式 说明 比喻 适用场景
ReadWriteOnce (RWO) 单节点读写 单人间,一次只能一个人住 数据库(MySQL)
ReadOnlyMany (ROX) 多节点只读 图书馆,很多人可以同时看 配置文件、静态资源
ReadWriteMany (RWX) 多节点读写 会议室白板,多人可写可读 文件共享、用户上传目录

回收策略(Reclaim policy)

PV用完后怎么处理:

策略 说明 比喻 何时使用
Retain 保留数据 退房后房间保持原样 重要数据(数据库)
Delete 删除数据 退房后清空房间 临时数据(测试环境)
Recycle 清理数据(已废弃) 退房后简单打扫 基本不用了

自动售货机 - StorageClass

每次都要管理员提前创建PV,太麻烦了!StorageClass应运而生,他相当于一个自动创建PV的模板

yaml 复制代码
# StorageClass定义:一个"自动创建PV的模板"
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd-class      # 存储类名字
provisioner: kubernetes.io/aws-ebs  # 提供者:AWS云硬盘
parameters:
  type: gp3                 # 硬盘类型:通用型SSD
  fsType: ext4              # 文件系统:ext4

在实际的应用中,通常云厂商的StorageClass已经内置好了,完全不用我们自己创建

yaml 复制代码
# 前提:管理员已经创建了StorageClass
#(通常云厂商的K8s已经内置了)

# 开发人员一步到位:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-auto-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: standard  # 使用标准的StorageClass
  resources:
    requests:
      storage: 20Gi

# Pod配置不变,还是引用PVC
apiVersion: v1
kind: Pod
metadata:
  name: mysql
spec:
  containers:
  - name: mysql
    image: mysql:8.0
    env:
    - name: MYSQL_ROOT_PASSWORD
      value: "password"
    volumeMounts:
    - name: mysql-storage
      mountPath: /var/lib/mysql  # MySQL数据目录
  volumes:
  - name: mysql-storage
    persistentVolumeClaim:
      claimName: mysql-auto-pvc  # 使用PVC
2.3.4:几个常见的问题

Q1:PV和PVC一定要一一对应吗?

A: 是的!一个PVC只能绑定一个PV,一个PV也只能绑定一个PVC。

就像:一个停车位(PV)只能停一辆车(PVC),一辆车也只能占一个停车位。

Q2:PVC申请的容量可以比PV大吗?

A: 不可以!必须小于等于PV的容量。

就像:你不能申请20G的存储,却想用10G的硬盘。

Q3:删除Pod后,数据会丢失吗?

A: 不会!因为数据在PV里,不在Pod里。

就像:你退房后(删除Pod),行李还放在储物柜(PV)里。

Q4:删除PVC后,PV会怎样?

A: 取决于PV的回收策略(Reclaim Policy):

  • Retain:PV保留,数据还在,但状态变为Released(需手动清理)
  • Delete:PV和数据一起删除

Q5:StorageClass是必须的吗?

A: 不是必须的,但生产环境强烈推荐!

  • 小规模/测试:可以不用
  • 生产环境:一定要用,自动化管理
2.3.5:手动PV/PVC的命令一览
bash 复制代码
# 1. 创建PV
kubectl apply -f mysql-pv.yaml

# 2. 查看PV状态
kubectl get pv
# 应该看到:STATUS = Available(可用)

# 3. 创建PVC
kubectl apply -f mysql-pvc.yaml

# 4. 查看绑定情况
kubectl get pv
kubectl get pvc
# 应该看到:STATUS = Bound(已绑定)

# 5. 创建MySQL Pod
kubectl apply -f mysql-pod.yaml 

# 6. 测试数据持久化
kubectl exec -it mysql -- mysql -ppassword -e "CREATE DATABASE test;"
kubectl delete pod mysql
kubectl apply -f mysql-pod.yaml
kubectl exec -it mysql -- mysql -ppassword -e "SHOW DATABASES;"
# 应该还能看到test数据库

六:高级调度

1:CronJob

CronJob = 定时规则(什么时候做) + 任务模板(做什么) + 执行策略(怎么控制)

1.1:cron表达式
bash 复制代码
┌───────────── 分钟 (0 - 59)
│ ┌───────────── 小时 (0 - 23)
│ │ ┌───────────── 日 (1 - 31)
│ │ │ ┌───────────── 月 (1 - 12 或 JAN-DEC)
│ │ │ │ ┌───────────── 星期几 (0 - 6 或 SUN-SAT)
│ │ │ │ │
│ │ │ │ │
* * * * *

# 每分钟执行一次
"* * * * *"     # 每分钟的每一秒都检查

# 每小时的第30分钟执行
"30 * * * *"    # 1:30, 2:30, 3:30...

# 每天凌晨2点执行
"0 2 * * *"     # 每天2:00 AM

# 每周一上午8点执行  
"0 8 * * 1"     # 每周一8:00 AM

# 每月1号凌晨3点执行
"0 3 1 * *"     # 每月1号3:00 AM

# 每5分钟执行一次
"*/5 * * * *"   # 0分, 5分, 10分, 15分...

# 工作日上午10点执行
"0 10 * * 1-5"  # 周一到周五10:00 AM
1.2:核心配置点

调度配置-schedule

yaml 复制代码
schedule: "0 2 * * *"  # 每天凌晨2点

# 更复杂的例子:
schedule: "0 9-17 * * 1-5"  
# 解释:工作日(周一到周五)的9点到17点,每小时执行

并发策略

控制同时能运行多少个Job实例:

策略 说明 比喻 适用场景
Allow(默认) 允许并发 多个闹钟可以同时响 独立任务,互不影响
Forbid 禁止并发 前一个闹钟没关,后一个不响 数据备份(避免重复备份)
Replace 替换执行 新闹钟替换旧的 数据同步(要最新结果)
yaml 复制代码
# 数据库备份 - 用Forbid
schedule: "0 3 * * *"
concurrencyPolicy: Forbid  # 如果凌晨3点的备份还在运行,4点的就不启动

# 健康检查 - 用Allow  
schedule: "*/5 * * * *"
concurrencyPolicy: Allow   # 每5分钟检查一次,可以同时运行多个

历史记录限制

yaml 复制代码
successfulJobsHistoryLimit: 3  # 保留最近3个成功的Job记录
failedJobsHistoryLimit: 1      # 保留最近1个失败的Job记录

# 查看历史Job:
kubectl get jobs --watch

启动截止时间

yaml 复制代码
startingDeadlineSeconds: 300  # 5分钟

# 作用:
# 1. 如果计划执行时间过了,但在5分钟内,还是会执行
# 2. 如果超过5分钟,就跳过这次执行
# 3. 避免积累太多延迟任务

暂停Cronjob

yaml 复制代码
suspend: true  # 暂停CronJob,不再触发新任务

# 动态暂停/恢复:
kubectl patch cronjob my-cronjob -p '{"spec":{"suspend":true}}'  # 暂停
kubectl patch cronjob my-cronjob -p '{"spec":{"suspend":false}}' # 恢复
1.3:常用操作

数据库每日备份

yaml 复制代码
apiVersion: batch/v1
kind: CronJob
metadata:
  name: mysql-daily-backup
spec:
  schedule: "0 3 * * *"  # 每天凌晨3点
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: backup
            image: mysql:8.0
            command:
            - /bin/sh
            - -c
            - |
              # 备份命令
              mysqldump -h $DB_HOST -u $DB_USER -p$DB_PASSWORD $DB_NAME > /backup/$DB_NAME-$(date +%Y%m%d).sql
              # 压缩备份
              gzip /backup/$DB_NAME-$(date +%Y%m%d).sql
              # 只保留最近7天备份
              find /backup -name "*.gz" -mtime +7 -delete
            env:
            - name: DB_HOST
              value: "mysql-service"
            - name: DB_USER
              value: "root"
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysql-secret
                  key: password
            - name: DB_NAME
              value: "mydb"
            volumeMounts:
            - name: backup-storage
              mountPath: /backup
          restartPolicy: OnFailure
          volumes:
          - name: backup-storage
            persistentVolumeClaim:
              claimName: backup-pvc

网站健康检查

yaml 复制代码
apiVersion: batch/v1
kind: CronJob
metadata:
  name: website-health-check
spec:
  schedule: "*/5 * * * *"  # 每5分钟
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: checker
            image: curlimages/curl
            command:
            - /bin/sh
            - -c
            - |
              # 检查网站状态
              if curl -s -o /dev/null -w "%{http_code}" https://mywebsite.com | grep -q "200"; then
                echo "$(date): Website is UP"
              else
                echo "$(date): Website is DOWN"
                # 可以在这里添加告警逻辑
              fi
          restartPolicy: Never  # 不重启,记录失败即可

清理临时文件

yaml 复制代码
apiVersion: batch/v1
kind: CronJob
metadata:
  name: cleanup-temp-files
spec:
  schedule: "0 * * * *"  # 每小时执行
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: cleaner
            image: alpine:latest
            command:
            - /bin/sh
            - -c
            - |
              # 清理/tmp下超过1天的文件
              find /shared/tmp -type f -mtime +1 -delete
              # 清理空目录
              find /shared/tmp -type d -empty -delete
              echo "Cleanup completed at $(date)"
            volumeMounts:
            - name: shared-storage
              mountPath: /shared
          restartPolicy: OnFailure
          volumes:
          - name: shared-storage
            persistentVolumeClaim:
              claimName: shared-pvc
1.4:相关命令

基础操作命令

bash 复制代码
# 创建CronJob
kubectl apply -f my-cronjob.yaml

# 查看所有CronJob
kubectl get cronjobs
kubectl get cj  # 简写

# 查看详细信息
kubectl describe cronjob my-cronjob

# 删除CronJob
kubectl delete cronjob my-cronjob

# 编辑CronJob
kubectl edit cronjob my-cronjob

查看Job执行情况

bash 复制代码
# 查看CronJob创建的Job
kubectl get jobs --watch

# 查看特定Job的Pod
kubectl get pods --selector=job-name=my-cronjob-123456

# 查看Pod日志
kubectl logs <pod-name>

# 查看CronJob事件
kubectl describe cronjob my-cronjob | grep -A 10 Events

手动触发执行

bash 复制代码
# 手动运行一次CronJob(不等待计划时间)
kubectl create job --from=cronjob/my-cronjob manual-run-$(date +%s)

# 例如:
kubectl create job --from=cronjob/daily-backup manual-backup-001

2:initContainer

类似于餐馆正式营业前的准备

场景 InitContainer做的事 主Container做的事
Web应用 下载配置文件、初始化数据库 运行Web服务器
游戏服务器 下载地图资源、检查插件 启动游戏服务
数据处理 下载数据集、解密文件 运行分析程序
复制代码
# 没有InitContainer时:
Pod启动 → 主容器立即运行 → 需要配置文件 → 文件不存在 → ❌ 崩溃!

# 有InitContainer时:
Pod启动 → InitContainer运行 → 下载配置文件 → ✅ 完成 → 主容器启动 → ✅ 成功!
  • 在主容器启动前运行的容器
  • 可以有一个或多个,按顺序执行
  • 所有InitContainer必须成功完成,主容器才能启动
  • 如果InitContainer失败,Pod会重启(重新执行InitContainer)
yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: webapp-with-db-check
spec:
  initContainers:
  # InitContainer 1: 等待MySQL数据库就绪
  - name: wait-for-db
    image: busybox:1.28
    command: ['sh', '-c']
    args:
    - |
      echo "Waiting for MySQL to be ready..."
      # 不断尝试连接MySQL,直到成功
      until nc -z mysql-service 3306; do
        echo "MySQL is not ready yet - sleeping"
        sleep 2
      done
      echo "MySQL is ready! Proceeding..."
  
  # InitContainer 2: 初始化数据库表
  - name: init-database
    image: mysql:8.0
    command: ['sh', '-c']
    args:
    - |
      echo "Initializing database tables..."
      mysql -h mysql-service -u root -p$MYSQL_ROOT_PASSWORD mydb < /sql/init.sql
      echo "Database initialized!"
    env:
    - name: MYSQL_ROOT_PASSWORD
      valueFrom:
        secretKeyRef:
          name: mysql-secret
          key: password
    volumeMounts:
    - name: init-sql
      mountPath: /sql
  
  # 主容器:运行Web应用
  containers:
  - name: webapp
    image: my-webapp:latest
    ports:
    - containerPort: 8080
    env:
    - name: DB_HOST
      value: "mysql-service"
  
  volumes:
  - name: init-sql
    configMap:
      name: db-init-scripts

3:污点、容忍

污点(Taint):贴在节点(Node)上的"标签",说:我有这些特性/限制

容忍(Toleration):Pod的"能力",说:我能忍受这些特性/限制

🚀 举一个例子:宿舍管理

复制代码
学校有4栋宿舍楼(节点Node):
🏠 1号楼:研究生专用(有"污点":只允许研究生住)
🏠 2号楼:留学生专用(有"污点":只允许留学生住)  
🏠 3号楼:普通宿舍(没污点,谁都能住)
🏠 4号楼:新装修有甲醛(有"污点":不允许任何人住)

学生(Pod)有不同的"容忍"能力:
🎓 研究生:能容忍"研究生专用"污点
🌍 留学生:能容忍"留学生专用"污点
👥 普通生:什么污点都不能容忍
概念 作用对象 目的 类比
污点(Taint) 节点(Node) 排斥某些Pod 宿舍的"准入条件"
容忍(Toleration) Pod 忍受某些污点 学生的"资格证明"
节点亲和性 Pod 吸引到某些节点 学生"想住"某宿舍
节点选择器 Pod 选择某些节点 学生"要求"住某宿舍
3.1:污点的三大组成成分
3.1.1:effect的三种效果

NoSchedule - "不准新来的入住"

复制代码
效果:
- 新Pod:❌ 不能调度到这个节点
- 已有Pod:✅ 不受影响,继续运行

使用场景:
- 节点有特殊硬件(GPU)
- 节点是专有环境(测试环境)
- 节点有特殊网络配置

PreferNoSchedule - "最好别来,实在没地方再来"

复制代码
效果:
- 新Pod:⚠️ 尽量不调度,但如果其他节点都满了,也可以调度
- 已有Pod:✅ 不受影响

使用场景:
- 节点性能较差(内存小)
- 节点网络延迟较高
- 软性限制,不是硬性要求

NoExecute - "不准来,已经住的也要搬走"

复制代码
效果:
- 新Pod:❌ 不能调度
- 已有Pod:❌ 如果没有容忍这个污点,会被驱逐(删除)

使用场景:
- 节点维护(要关机)
- 节点故障(要维修)
- 节点资源严重不足
3.1.2:污点示例
bash 复制代码
# 查看节点的污点
kubectl describe node <node-name> | grep Taint

# 常见污点示例:
Taints:             gpu=true:NoSchedule           # GPU节点
                    disk=ssd:NoSchedule          # SSD存储节点
                    dedicated=db:NoSchedule      # 数据库专用节点
                    maintenance:NoExecute        # 正在维护的节点
                    node.kubernetes.io/not-ready:NoExecute  # 节点未就绪
3.2:容忍的配置方式
3.2.1:容忍基本使用
yaml 复制代码
tolerations:
- key: "污点的键"
  operator: "Equal 或 Exists"
  value: "污点的值"  # operator=Equal时需要
  effect: "污点的效果"  # 可选,不写表示容忍所有效果
  tolerationSeconds: 3600  # 仅对NoExecute有效,容忍时间(秒)

operator有两个类型,Equals表示精确匹配,Exists表示模糊匹配

yaml 复制代码
# 节点污点:gpu=true:NoSchedule
tolerations:
- key: "gpu"          # 键必须相同
  operator: "Equal"   # 操作符:等于
  value: "true"       # 值必须相同
  effect: "NoSchedule" # 效果必须相同
  
# 容忍所有gpu相关的污点,不管值是什么
tolerations:
- key: "gpu"          # 键必须存在
  operator: "Exists"  # 操作符:存在
  # 不需要value字段
  effect: "NoSchedule" # 效果必须匹配
3.2.2:特殊容忍

容忍所有的污点

yaml 复制代码
tolerations:
- operator: "Exists"  # 不指定key,表示容忍所有key

容忍特定效果的所有污点

yaml 复制代码
tolerations:
- operator: "Exists"  # 不指定key
  effect: "NoExecute" # 但指定效果,只容忍NoExecute的污点

带容忍时间的NoExecute

yaml 复制代码
tolerations:
- key: "maintenance"
  operator: "Equal"
  value: "true"
  effect: "NoExecute"
  tolerationSeconds: 3600  # 容忍3600秒(1小时),然后被驱逐
3.3:污点和容忍的流程

设置GPU专用节点

yaml 复制代码
# 步骤1:给GPU节点打上污点
kubectl taint nodes node-gpu-1 gpu=true:NoSchedule
kubectl taint nodes node-gpu-2 gpu=true:NoSchedule

# 步骤2:创建需要GPU的Pod(带容忍)
apiVersion: v1
kind: Pod
metadata:
  name: ai-training-pod
spec:
  containers:
  - name: ai-trainer
    image: tensorflow/tensorflow:latest-gpu
    command: ["python", "train.py"]
  tolerations:  # 容忍配置
  - key: "gpu"
    operator: "Equal"
    value: "true"
    effect: "NoSchedule"

# 步骤3:创建普通Pod(不带容忍)
apiVersion: v1
kind: Pod  
metadata:
  name: web-pod
spec:
  containers:
  - name: nginx
    image: nginx:alpine

# 调度结果:
# ai-training-pod ✅ 可以调度到node-gpu-1或node-gpu-2
# web-pod ❌ 不能调度到GPU节点,只能调度到普通节点
3.4:污点和容忍的常用命令
bash 复制代码
# 1. 查看节点污点
kubectl describe node <node-name> | grep -i taint
kubectl get node <node-name> -o jsonpath='{.spec.taints}'

# 2. 添加污点
kubectl taint node <node-name> <key>=<value>:<effect>
# 示例:
kubectl taint node worker1 gpu=true:NoSchedule
kubectl taint node worker2 dedicated=db:NoSchedule

# 3. 删除污点
kubectl taint node <node-name> <key>[:<effect>]-
# 示例:
kubectl taint node worker1 gpu:NoSchedule-  # 删除特定effect
kubectl taint node worker1 gpu-             # 删除所有gpu污点

# 4. 修改污点(先删后加)
kubectl taint node worker1 gpu:NoSchedule-
kubectl taint node worker1 gpu=true:NoExecute


# 查看Pod调度到哪个节点
kubectl get pod <pod-name> -o wide

# 查看节点上的Pod
kubectl get pods -o wide --all-namespaces | grep <node-name>

# 查看Pod的容忍配置
kubectl get pod <pod-name> -o yaml | grep -A 10 tolerations

4:亲和性

亲和性的理解可以想象相亲大会

复制代码
参与者:
💼 男生们 = Pod(要找地方住的)
🏢 女生们 = Node(可以提供住的地方的)

匹配条件:
❤️  "我喜欢程序员女生"       = 节点亲和性
💑  "我想跟小红坐一起"       = Pod亲和性  
🚫  "我不想跟小明坐一起"     = Pod反亲和性

匹配结果:
- 程序员男生 → 喜欢程序员的女生 ✅ 匹配!
- 男生想跟小红坐 → 小红旁边有空位 ✅ 匹配!
- 男生不想跟小明坐 → 离小明远远的 ✅ 匹配!
相亲场景 K8s概念 作用
"我喜欢住在学校附近" 节点亲和性 Pod选择特定Node
"我想跟闺蜜住同一栋楼" Pod亲和性 Pod跟其他Pod放一起
"我不想跟前男友住太近" Pod反亲和性 Pod远离其他Pod

由上面可以了解到有三种类型的亲和性

4.1:三种亲和性
4.1.1:节点亲和性

让Pod亲近某些节点

  • 基于节点标签进行选择
  • 比如:选择有SSD、大内存、特定区域等的节点

1️⃣ 首先,给节点打上标签

bash 复制代码
# 查看节点标签
kubectl get nodes --show-labels

# 给节点打标签
kubectl label nodes <node-name> disktype=ssd
kubectl label nodes <node-name> zone=us-east
kubectl label nodes <node-name> gpu=true

# 删除标签
kubectl label nodes <node-name> disktype-

2️⃣ 两种匹配规则

requiredDuringSchedulingIgnoredDuringExecution

必须满足,否则不调度

yaml 复制代码
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: disktype
          operator: In
          values:
          - ssd
          - nvme
# 翻译:必须调度到 disktype=ssd 或 disktype=nvme 的节点

preferredDuringSchedulingIgnoredDuringExecution

尽量满足,实在不行也能调度

yaml 复制代码
affinity:
  nodeAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
    - weight: 80  # 权重:1-100,越大越优先
      preference:
        matchExpressions:
        - key: zone
          operator: In
          values:
          - us-east-1a
操作符 含义 示例
In 标签值在列表中 zone In [us-east, us-west]
NotIn 标签值不在列表中 env NotIn [prod]
Exists 标签存在 gpu Exists
DoesNotExist 标签不存在 test DoesNotExist
Gt 值大于(数值比较) memory Gt 16
Lt 值小于(数值比较) cpu Lt 8
4.1.2:Pod亲和性

让Pod亲近其他Pod

  • 基于其他Pod的标签进行选择
  • 比如:Web服务想跟缓存服务部署在同一个节点

什么是"同一个位置"呢,这就涉及到了K8s的另一个重要的概念 - 拓扑域

  • topologyKey: kubernetes.io/hostname = 同一个节点
  • topologyKey: failure-domain.beta.kubernetes.io/zone = 同一个可用区
  • topologyKey: failure-domain.beta.kubernetes.io/region = 同一个区域
yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
        tier: frontend
    spec:
      affinity:
        podAffinity:
          # 硬性要求:必须跟缓存服务在同一个节点
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - redis-cache
            topologyKey: kubernetes.io/hostname  # 同一个节点
          
          # 软性偏好:尽量跟数据库在同一个可用区
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - mysql-db
              topologyKey: failure-domain.beta.kubernetes.io/zone
      containers:
      - name: webapp
        image: nginx:alpine
4.1.3:Pod反亲和性

让Pod远离其他Pod

  • 基于其他Pod的标签进行回避
  • 比如:数据库副本不要部署在同一个节点(提高可用性)
yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql-ha
spec:
  replicas: 3
  selector:
    matchLabels:
      app: mysql
      component: database
  template:
    metadata:
      labels:
        app: mysql
        component: database
    spec:
      affinity:
        podAntiAffinity:
          # 硬性要求:同一个应用的Pod不能在同一节点
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - mysql
            topologyKey: kubernetes.io/hostname  # 不能在同一节点
          
          # 软性偏好:尽量不在同一个可用区
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - mysql
              topologyKey: failure-domain.beta.kubernetes.io/zone
      containers:
      - name: mysql
        image: mysql:8.0
4.2:实际应用

一般都会搭配污点容忍配合使用

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: premium-app
spec:
  # 1. 容忍:能忍受GPU节点的污点
  tolerations:
  - key: "gpu"
    operator: "Equal"
    value: "reserved"
    effect: "NoSchedule"
  
  # 2. 亲和性:优先选择GPU节点
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        preference:
          matchExpressions:
          - key: gpu-model
            operator: In
            values:
            - v100
            - a100
    
    # 3. 反亲和性:不要跟其他计算密集型应用在一起
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 80
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: resource-intensive
              operator: In
              values:
              - "true"
          topologyKey: kubernetes.io/hostname
  
  containers:
  - name: app
    image: premium/app:latest

七:身份认证和权限

1:用户账户和服务账户

复制代码
用户账户(User Accounts):
- 👤 给"人"用的:管理员、开发人员
- 🔐 认证方式:客户端证书、令牌、密码等
- 🌍 范围:整个集群
- 📍 不存储在K8s中:由外部系统管理
- 🏢 典型场景:kubectl操作、管理集群

服务账户(Service Accounts):
- 🤖 给"Pod"用的:应用程序、自动化工具
- 🔑 认证方式:自动生成的JWT令牌
- 📦 范围:特定命名空间内
- 💾 存储在K8s中:作为K8s资源管理
- ⚙️ 典型场景:Pod访问API Server、CI/CD流水线
1.1:Service Account 自动化机制

Service Account Admission Controller

自动为Pod挂载ServiceAccount令牌

yaml 复制代码
# 当创建Pod时,自动发生:
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  # 如果没有指定,自动使用"default" ServiceAccount
  serviceAccountName: default
  
  # 自动挂载令牌到:
  # /var/run/secrets/kubernetes.io/serviceaccount/
  automountServiceAccountToken: true  # 默认值
复制代码
/var/run/secrets/kubernetes.io/serviceaccount/
├── ca.crt           # 集群CA证书
├── namespace        # Pod所在命名空间(文本文件)
└── token            # JWT令牌(用于认证)

token controller

自动管理ServiceAccount的令牌Secret

复制代码
工作流程:
1. 创建ServiceAccount时
2. Token Controller自动:
   - 生成对应的Secret
   - 在Secret中保存JWT令牌
   - 将Secret关联到ServiceAccount
3. 令牌过期时自动轮换
bash 复制代码
# 创建ServiceAccount
kubectl create serviceaccount myapp

# 查看自动创建的Secret
kubectl describe serviceaccount myapp
# 会看到:Tokens: myapp-token-xxxxx

# 查看Secret内容
kubectl describe secret myapp-token-xxxxx

Service Account Controller

管理命名空间与默认ServiceAccount的关系

复制代码
主要职责:
1. 确保每个命名空间都有"default" ServiceAccount
2. 确保default ServiceAccount有对应的Secret
3. 管理ServiceAccount的生命周期

2:认证和授权

2.1:RBAC四大件

Role/ClusterRole 定义了"能做什么"(权限模板),

RoleBinding/ClusterRoleBinding 定义了"谁能在哪里做"(权限分配)。

  • Role + RoleBinding = 某人在某命名空间的权限
  • ClusterRole + RoleBinding = 某人在某命名空间的集群级别权限
  • ClusterRole + ClusterRoleBinding = 某人在整个集群的权限
2.1.1:Role
yaml 复制代码
# Role就是"权限配置文件"
# 定义:在某个命名空间内,能对哪些资源做什么

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default          # 作用范围:default命名空间
  name: pod-reader            # 角色名称
rules:
- apiGroups: [""]             # 核心API组(空字符串)
  resources: ["pods"]         # 资源类型:Pod
  verbs: ["get", "list", "watch"]  # 允许的操作:查看
  • namespace:指定这个Role在哪个命名空间生效
  • resources:指定能操作哪些K8s资源(pods, services, configmaps等)
  • verbs:指定允许的操作(get, list, create, update, delete等)
2.1.2:ClusterRole
yaml 复制代码
# ClusterRole也是"权限配置文件"
# 定义:在整个集群内,能对哪些资源做什么

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  # 注意:没有namespace字段!
  name: node-viewer            # 角色名称
rules:
- apiGroups: [""]
  resources: ["nodes"]         # 集群级别资源:Node
  verbs: ["get", "list", "watch"]
  
- apiGroups: [""]
  resources: ["persistentvolumes"]  # 另一个集群资源
  verbs: ["get", "list"]
特性 Role ClusterRole
作用范围 单个命名空间 整个集群
资源类型 命名空间资源 集群资源+命名空间资源
定义位置 metadata.namespace指定 没有namespace字段
2.1.3:RoleBinding
yaml 复制代码
# RoleBinding就是"权限分配文件"
# 作用:把Role分配给某个用户/ServiceAccount/组

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: default          # 在default命名空间生效
  name: zhangsan-pod-reader   # 绑定名称
subjects:                     # 分配给谁?
- kind: User                  # 类型:用户
  name: zhangsan              # 用户名
  apiGroup: rbac.authorization.k8s.io
roleRef:                      # 分配什么角色?
  kind: Role                  # 引用Role类型
  name: pod-reader            # 引用的Role名称
  apiGroup: rbac.authorization.k8s.io
yaml 复制代码
# ---------- 可以绑定的主体如下 -------------
subjects:
# 1. 绑定给用户
- kind: User
  name: "zhangsan"

# 2. 绑定给ServiceAccount
- kind: ServiceAccount
  name: "myapp"
  namespace: "default"  # 必须指定命名空间

# 3. 绑定给用户组
- kind: Group
  name: "developers"
2.1.4:ClusterRoleBinding
yaml 复制代码
# ClusterRoleBinding:把ClusterRole分配给主体

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: view-all-binding      # 没有namespace字段!
subjects:
- kind: User
  name: view-user
roleRef:
  kind: ClusterRole           # 引用ClusterRole
  name: view                  # 内置的只读角色
yaml 复制代码
# 让某个ServiceAccount在所有命名空间都有view权限
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-all-namespaces
  namespace: default          # 在default命名空间
subjects:
- kind: ServiceAccount
  name: myapp
roleRef:
  kind: ClusterRole           # 引用ClusterRole!
  name: view                  # 但只在当前命名空间生效
2.2:实际工作常用
2.2.1:Pod需要读取ConfigMap

1️⃣ 创建ServiceAccount

bash 复制代码
kubectl create serviceaccount config-reader

2️⃣ 创建role

yaml 复制代码
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: configmap-reader
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["get", "list"]

3️⃣ 创建RoleBinding(分配权限)

yaml 复制代码
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: default
  name: config-reader-binding
subjects:
- kind: ServiceAccount
  name: config-reader
  namespace: default
roleRef:
  kind: Role
  name: configmap-reader

4️⃣ Pod使用这个ServiceAccount

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: myapp
spec:
  serviceAccountName: config-reader  # 使用专用ServiceAccount
  containers:
  - name: app
    image: myapp:latest

自动发生的过程:

  1. Pod创建时,Service Account Admission Controller自动挂载令牌
  2. Token Controller确保令牌Secret存在且有效
  3. Pod内的应用使用令牌访问API
  4. API Server检查:这个ServiceAccount是否有读取ConfigMap的权限
  5. 因为有RoleBinding,所以允许访问
2.3:内置角色
bash 复制代码
# 查看所有内置角色
kubectl get clusterroles

# 常用内置角色:
# cluster-admin    # 超级管理员(所有权限)
# admin            # 命名空间管理员
# edit             # 命名空间编辑者
# view             # 命名空间只读查看者
bash 复制代码
# 快速给用户只读权限
kubectl create rolebinding user-view \
  --clusterrole=view \      # 使用内置view角色
  --user=zhangsan \
  --namespace=default

# 给ServiceAccount编辑权限
kubectl create rolebinding sa-edit \
  --clusterrole=edit \      # 使用内置edit角色
  --serviceaccount=default:myapp \
  --namespace=default

八:云餐厅-组件总结

上面所有的K8s组件部分介绍完成,下面通过一个云餐厅的例子再次总结下各个组件的关系

1:基础设施:餐厅的物理空间

1.1:node - 厨房的工作台

你的餐厅后厨有若干个工作台:

  • 主厨工作台:配置高档,有专业灶具(相当于高性能服务器)
  • 普通工作台:标准配置(中等性能服务器)
  • 准备工作台:基本配置,用于预处理(低性能服务器)

每个工作台都有

  1. 灶具、水池、刀具(计算资源:CPU、内存)
  2. 工作台标签:标明用途(节点标签)
  3. 状态指示灯:显示是否可用(节点状态)

这些工作台就是K8s的 Node。它们构成了餐厅的"物理基础设施"。

1.2:Master Node - 餐厅经理办公室

在餐厅二楼有一个经理办公室,里面坐着几位关键人物:

  1. 餐厅经理(API Server):所有指令都必须通过他
  2. 调度主管(Scheduler):决定哪个任务去哪个工作台
  3. 各部门总监(Controller Manager):管理不同业务部门
  4. 总账本(etcd):记录餐厅的一切状态

他们不直接做菜,但管理整个餐厅的运营。

2:基本生产单元:菜品制作

2.1:pod - 一份完整的菜品订单

当客人点了一份"牛排套餐",这就创建了一个 Pod

这份套餐包括:

  1. 主菜容器:煎牛排(一个容器)
  2. 配菜容器:烤土豆(另一个容器)
  3. 酱料容器:黑椒汁(又一个容器)
  4. 共享信息:桌号、特殊要求(共享存储和网络)

关键特性

  • 这三个容器必须一起制作、一起上桌、一起撤下
  • 它们共享同一个"托盘"(网络空间)和"调料区"(存储卷)
  • 如果牛排煎糊了,整个套餐需要重做(Pod重启)

3:自动化管理:智能生产线

3.1:deployment - 标准化菜品生产线

你的餐厅招牌菜是"意大利面",每天点的人很多。你不可能每次都手动安排厨师。

于是你建立了一条 意大利面生产线:

  1. 目标状态:"随时保持有3份意大利面待命"
  2. 自动化系统:
    • 检查当前有几份意大利面
    • 如果只有2份,自动制作第3份
    • 如果某份坏了(容器崩溃),自动替换
    • 如果要换新配方(版本升级),逐步替换,确保一直有面可卖

Deployment 就是这样一个智能生产线管理员,确保你想要的副本数量始终存在。

3.2:statefulSet - 需要顺序的套餐

有些菜品需要按特定顺序准备,比如"三层下午茶套餐":

  1. 第一层:三明治(必须先做)
  2. 第二层:司康饼(等第一层完成后做)
  3. 第三层:甜点(等第二层完成后做)

StatefulSet 的特点:

  • 每层有固定的名字:套餐-0、套餐-1、套餐-2
  • 按顺序制作,按逆序撤下
  • 每层有自己的专用托盘(持久化存储)
  • 即使撤掉重做,还是同样的名字和托盘
3.3:daemonSet - 每个工作台的标配

为了保证厨房安全,每个工作台都需要:

  1. 灭火器(安全监控)
  2. 温度计(资源监控)
  3. 清洁工具(日志收集)

DaemonSet 确保:

  • 每新增一个工作台,自动配齐这些工具
  • 每个工作台都有且只有一套
  • 工具会一直运行,监控工作台状态
3.4:job - 一次性特别任务

餐厅打烊后,有些一次性任务:

  1. 清点库存:运行一次,完成就结束
  2. 生成日报:统计当天营业数据
  3. 备份数据:把重要数据备份起来

Job 就是执行这些"一次性任务"的机制。任务完成,工作人员就下班。

3.5:cronJob - 定期重复任务

有些任务需要定期执行:

  1. 每天6点:进货
  2. 每小时:检查食材新鲜度
  3. 每周一:设备维护

CronJob 就像一个智能闹钟:

  • 设定好时间表
  • 到点自动创建 Job
  • Job 执行具体任务
  • 任务完成,记录结果

4:服务接入:前厅服务

4.1:service - 餐厅服务员

后厨有10个厨师在做意大利面,但客人不知道找哪个厨师,而且厨师可能会轮换(Pod重启后IP变化)。

这时就需要 服务员(Service):

  1. 客人:只需说"我要意大利面"
  2. 服务员:
    • 查看哪些厨师有空
    • 选择一个厨师接单
    • 把订单递给厨师
    • 把做好的菜端给客人

Service 的四种工作模式:

  1. 内部服务员(ClusterIP):只为餐厅内部服务(其他菜品需要意大利面时使用)
  2. 窗口服务员(NodePort):在餐厅外墙开个窗口,外卖顾客可以直接点单
  3. 外卖平台(LoadBalancer):接入美团、饿了么,自动分配订单
  4. 直联服务员(Headless):VIP顾客可以直接指定厨师
4.2:Ingress - 餐厅领班/前台

餐厅有多个入口需求:

  1. 正门 :普通客人(www.restaurant.com
  2. 员工通道 :员工系统(staff.restaurant.com
  3. 外卖入口 :外卖平台(takeout.restaurant.com

如果每个入口都配一个外卖平台(LoadBalancer),成本太高。

Ingress 就像一个聪明的领班:

  • 站在餐厅唯一的主入口
  • 根据客人需求指引方向:
    • "普通用餐?请走正门到大厅"
    • "员工上班?请走侧门到办公区"
    • "取外卖?请到专门的取餐口"

领班手里拿着"路由规则表",根据客人的目的地分配合适的服务员。

5:配置管理:菜谱与秘方

5.1:ConfigMap - 标准菜谱

为了保证菜品质量一致,所有厨师使用统一的标准菜谱

  1. 配料表:每种食材的用量
  2. 烹饪步骤:详细的制作流程
  3. 时间温度:精确的烹饪参数

ConfigMap 的特点:

  • 公开透明,所有厨师都能看
  • 可以随时更新(比如改进配方)
  • 与厨师技能(容器镜像)分离,更新菜谱不需要换厨师
5.2:Secret - 保险柜里的秘方

有些关键信息不能公开:

  1. 秘制酱料配方:餐厅的独门秘籍
  2. 供应商API密钥:订货系统的密码
  3. 支付接口密钥:收款的秘密信息

Secret 就像餐厅的保险柜:

  • 信息加密存放
  • 只有授权厨师可以查看
  • 使用时临时取出,用完放回

6:存储管理:食材仓库

6.1:Volume - 临时储物架

厨师做菜时,需要临时存放:

  • 切好的配菜
  • 调好的酱料
  • 半成品食材

Volume 就像工作台旁边的临时储物架:

  • 做菜时方便取用
  • 菜品完成就清理
  • 如果换工作台(Pod迁移),架子上的东西可能就没了
6.2:PersistentVolume - 餐厅的仓库

餐厅需要长期存储:

  1. 冷库:存放肉类海鲜
  2. 干货库:存放米面调料
  3. 酒水库:存放酒水饮料

PersistentVolume(PV) 是房东提供的仓库空间。

PersistentVolumeClaim(PVC) 是租户的租赁合同:

  1. 租户申请:"我要租50平米的冷库"
  2. 系统匹配:找到合适的冷库PV
  3. 签订合同:PVC绑定到PV
  4. 厨师使用:凭合同(PVC)使用冷库
6.3:StorageClass - 仓库中介

传统方式:租户要自己找仓库、谈价格、签合同,很麻烦。

现代方式:找仓库中介(StorageClass)

  1. 租户:"我要20平米的快速冷藏库"
  2. 中介:"好的,马上安排"
  3. 自动完成:中介自动找仓库、签合同、配钥匙

完全自动化,租户完全不用操心仓库在哪、怎么管理。

7:控制平面:餐厅管理层

7.1:kube-apiserver - 前台接待处

所有指令都必须通过前台接待处

  • 客人点单
  • 经理下达指令
  • 厨师报告状态
  • 清洁工请求工具

前台接待员

  1. 验证每张单据的合法性(身份认证)
  2. 记录所有进出单据(审计日志)
  3. 分发给相关部门处理
7.2:etcd - 餐厅总账本

餐厅有一个总账本,记录一切:

  • 所有工作台状态
  • 所有菜品订单情况
  • 所有库存数量
  • 所有员工信息

特点

  • 高度可靠:有多本副本,一本坏了不影响
  • 实时同步:任何修改立即同步到所有副本
  • 完整历史:记录所有变更,可追溯
7.3:kube-scheduler -- 任务分配员

当有新订单(Pod)需要制作时,任务分配员决定:

  1. 哪个工作台有空位?(节点资源)
  2. 工作台是否有特殊要求?(节点亲和性/污点)
  3. 菜品有什么特殊需求?(Pod需求)
  4. 尽量让相关工作靠近(Pod亲和性)

就像餐厅的"排班经理",科学分配任务。

7.4:kube-controller-manager - 各部门主管

餐厅有多个部门,每个部门有主管:

  1. 节点主管(Node Controller) :管理所有工作台
    • 新工作台安装?登记入库
    • 工作台故障?标记不可用
    • 工作台修复?重新启用
  2. 生产线主管(Deployment Controller) :管理所有生产线
    • 检查意大利面生产线是否保持3份
    • 检查牛排生产线是否运行正常
  3. 服务员主管(Service Controller) :管理所有服务员
    • 确保每个菜品都有服务员
    • 服务员离职?自动招聘新服务员

还有很多其他主管,各司其职。

8:节点组件:工作台内部

8.1:kubelet:工作台监工

每个工作台都有一个监工

  1. 接收指令:从经理办公室接收任务
  2. 监督执行:确保厨师按要求做菜
  3. 报告状态:定时向经理汇报工作台状况
  4. 健康检查:检查厨师是否健康工作

监工不亲自做菜,但确保工作台一切正常。

8.2:kube-proxy:内部接线员

餐厅内部有复杂的通讯需求:

  • 意大利面厨师需要向番茄酱厨师要酱料
  • 甜点厨师需要向冰淇淋库要原料

内部接线员负责:

  1. 建立通讯录:记录每个厨师的分机号(IP)
  2. 转接电话:当A厨师找B厨师时,帮忙转接
  3. 负载均衡:多个牛排厨师时,平均分配订单
8.3:Container Runtime:厨师技能

最后,实际做菜需要厨师技能

  • Docker:一种流行的烹饪技法
  • containerd:另一种高效的烹饪技法
  • CRI-O:专门为K8s优化的烹饪技法

这些是实际的"做菜能力",没有它们,再好的管理系统也没用。

9:完整工作流:一份订单的旅程

让我们跟随一份"海鲜大餐"订单,看看整个系统如何协作:

第1步:客人下单

客人访问 restaurant.com/seafood(通过浏览器)

第2步:领班接待

Ingress 领班看到请求:

  • 域名:restaurant.com
  • 路径:/seafood
  • 查路由表:"海鲜请求 → 海鲜服务员"
第3步:服务员接单

Service 海鲜服务员收到订单:

  • 查看哪些海鲜厨师空闲
  • 使用轮询算法选择"海鲜厨师-B"
  • 把订单递给这个厨师
第4步:生产线响应

Deployment 海鲜生产线检查:

  • 应该保持3个海鲜厨师
  • 目前3个都健康,不需要新厨师
  • "海鲜厨师-B"开始工作
第5步:厨师准备

Pod "海鲜厨师-B"开始制作:

  1. 查看 ConfigMap(海鲜菜谱)
  2. 打开 Secret(秘制调料配方)
  3. 前往 PVC(海鲜冷库取食材)
  4. 使用 Volume(临时存放处理中的食材)
第6步:监控运行

DaemonSet 温度监控器一直工作:

  • 监控工作台温度
  • 温度过高?发出警报
  • 确保烹饪环境安全
第7步:定时任务

CronJob 每天5点的进货闹钟响了:

  • 创建 Job "今日进货任务"
  • Job创建"进货工"Pod
  • 进货工去市场采购新鲜海鲜
  • 放入 PV(海鲜冷库)
  • 任务完成,Pod结束
第8步:管理层监督

整个过程中,管理层持续工作:

  • API Server:记录所有操作
  • etcd:更新库存状态
  • Scheduler:当初决定"海鲜厨师-B"在这个工作台
  • Controller Manager:确保生产线、服务员都正常运行
第9步:完成上菜

海鲜大餐制作完成:

  • 通过服务员递给客人
  • 客人满意用餐
  • 订单状态标记为"已完成"
相关推荐
羊羊羊i16 分钟前
使用Informer监听K8s资源
云原生·容器·kubernetes
VermiliEiz1 小时前
二进制文件部署k8s方式(5)
云原生·容器·kubernetes
java_logo1 小时前
QWEN3 企业级 Docker 容器化部署指南
运维·docker·容器·qwen3部署·qwen3部署文档·qwen3部署教程·qwen3部署方案
taihexuelang2 小时前
大模型部署
人工智能·docker·容器
2301_810746312 小时前
CKA冲刺40天笔记 - day24 Kubernetes Clusterrole 和 Clusterrole Binding
笔记·容器·kubernetes
释怀不想释怀3 小时前
3.3 DockerCompose(快速部署)
云原生·eureka
释怀不想释怀3 小时前
Docker(项目部署)
运维·docker·容器
ICT董老师4 小时前
通过kubernetes部署nginx + php网站环境
运维·nginx·云原生·容器·kubernetes·php
原神启动14 小时前
K8S(八)—— Kubernetes Pod 资源限制 + 探针(Probe)解析
云原生·容器·kubernetes
zxnbmk4 小时前
【7】Kubernetes存储(本章知识密度较高,仅浅浅了解后续详解)
linux·云原生·容器·kubernetes