k8s核心知识点
kubelet对接容器引擎
v1.24 之前, kubelet 内置了一个叫 dockershim 的组件,用来把CRI请求转换成 Docker API 的调用,然后通过 dockerd 再转发给 containerd 去运行容器。这条链路是 kubelet → dockershim(在kubelet进程内) → dockerd → containerd,层级多、调用链长,相当于多了一层适配和转发,效率相对较低。
v1.24 及之后, 社区正式移除了 dockershim,kubelet 不再支持直接对接 Docker Engine。官方推荐的运行时是 containerd,因为 containerd 本身就内置了 CRI 插件(CRI plugin)。kubelet 可以直接通过 CRI 调用 containerd,链路简化为 kubelet → containerd(CRI插件) → 容器。
POD的三种调度策略
策略
- 1.根据资源
Pod声明的requests和limits,前者就是Pod需要多少资源,后者表示Pod最多用多少资源,资源比如CPU内存等
2.节点标签选择器,会选择符合标签的节点进行调度
3.节点亲和性,分为硬亲和和软亲和
硬亲和 必须符合标签的节点进行调度 软亲和 尝试符合节点进行调度不强制
钩子函数
发起调用的是kubelet 每个容器都可以定制钩子
PostStart
-
运行时机:与业务容器是同时起来的,是异步运行,没有先后
-
作用 在容器运行之初完成一些操作 锁文件
-
运行失败
容器器会被杀死,并根据RestartPolicy决定是否重启。
即PostStart如果执行失败,其所在的业务容器休想处于running状态 -
主要用途
配置动态环境:向外部服务注册当前容器的地址(服务发现注册)。
预热缓存:在应用处理流量前,预先加载一些数据到缓存中。
下载配置文件或资产:从云存储(如 S3)中拉取运行时所需的配置文件。
运行初始化脚本:执行一些数据库迁移或环境检查脚本。
通知系统:通知监控系统或上下游服务该容器已启动
- 注意
如果一些初始化工作一定要在业务容器运行前运行,那就用initcontainers来定制
如果这些初始化工作没有要求必须在业务容器运行前运行,仅仅
只是在容器启动之初做一下就可以
PreStop
-
运行时间:一定是容器终止之前执行
-
作用:
主要用于资源清理、优雅关闭应用程序、通知其他系统等, 从 Service 的 Endpoints 移除 -
注意:
当 Pod 开始终止时,Kubernetes 会执行preStop钩子。钩子有一个超时时间(默认为 30 秒)。如果钩子在超时时间内没有成功完成,Kubernetes 会终止 Pod,而不再继续等待钩子完成。
init容器
sepc.initcontaines 用来做初始化工作为后续的业务容器(主容器)
做一些初始化工作
1.比如业务容器是数据库 init就是给数据库做初始数据的生成 可以 基于pod特性共享存储卷 挂载给init容器 往init容器写一些数据 业务容器在挂载给业务容器,业务容器就会有数据
2.业务容器需要依赖上下游的微服务需要等到服务都就绪才能启动
可以在inti容器进行初始化的检测
init容器可以指定或者多个串行运行
list-watch机制
k8s中组件协同都采用list-watch机制进行通信
1. list:操作是一次性的用于获取集群中某种资源的当前状态列表
2. watch:用于订阅资源的更新时间
流程
1. 客户端先执行一次list操作来初始化资源状态
2. 然后通过watch操作来持续接受后续的更新
与轮训相比,这种机制可以更有效的获取资源状态的更新,并减少了网络流量和api的负载
实现方式
List:短连接(一次通信完成之后tcp链接就断开,适用于访问频率不高)
watch:长连接(一次通信完成之后tcp链接依然保留,适用于短时间内频繁访问)
pod重启机制
控制器管理的pod
- ReplicaSet、Deployment、DaemonSet重启策略只能是"Always"。
裸Pod
- Always、OnFailure、Never
- Always:当容器终止退出后,总是重启容器,默认策略
-OnFailure:当容器异常退出(退出状态码非0)时,才重启
-Never:当容器终止退出,从不重启容器
Pod的健康检查-探针
什么是探针
- 探针就是用来检测容器或容器的服务是否存活的一种机制
为何用探针
- 探针可以帮我们周期性检测服务状态,自动完成上下线或者重启
三种探针
-
startupProbe:启动探针 用于启动场景的检测
-
检测通过/成功之后重复执行livenessProbe探针和,readinessProbe探针
-
检测失败会根据重启策略重启POD
-
-
livenessProbe存活探针 用于启动之后的检测
- 周期性检测,检查失败就根据重启策略重启pod
-
readinessProbe 就绪探针 用于启动之后的检测
- 周期性检测,检测失败 reday 会显示为0 如果pod关联了svc svc 会把pod 的 endpoint 剔除
三种方式
-
exec:执行一段命令返回的的状态码为0就视为成功 -
TCP Socket 与容器指定的端口建立 TCP 连接。如果连接建立成功,则视为成功。
-
HTTP GET 向容器的 IP 地址、指定的端口和路径发起 HTTP GET 请求。如果响应状态码在 200 到 399 之间,则视为成功。
startupprobe 启动时 周期就要长一些 启动之后 livenessProbe readnessProbe 要检测时间要相应的短一点
资源申请与限制
requests申请与limits限制的区别
-
request
- 与调度有关系 预选阶段的参考指标 没有实际占用到相对于的资源,也有可能用超有可能不足
cpu_shares
- 与调度有关系 预选阶段的参考指标 没有实际占用到相对于的资源,也有可能用超有可能不足
-
limits
- 底层会修改目标节点的cgroup参数cpu.cfs_quota ,做实际的限制
initContainers与Containers包含多个容器的情况下,k8s如何调度
考虑initContainers与Containers包含多个容器的情况下,k8s如何调度?步骤如下
(1)找出initContainers多个容器中对资源需求requests最大的那一个--》得到一个值假设为x
(2)求出Containers下多个容器中对资源需求requests的总和--》得到一个值假设为y
(3)k8s会取x与y中较大的那一个作为预选阶段的筛选依据,确保pod被调度某个节点上之后
无论是initContainer还是业务Containers容器都有足够的剩余资源来确保拉起
服务的质量等级(Qos)
当某个节点的资源不足时,k8s会把该节点的pod进行驱逐,(pod的状态evicted 驱逐)
驱逐时会按照pod的Qos质量等级进行驱逐,质量等级越低的会成为被驱逐的目标
- Guranteed(完全可靠的)
pod中的所有容器对所有资源类型(cpu与内存)都定义了Limits与requests,且二者值相等、且大于0 - Burstable(不稳定的)
- 设置了requests或limits,但是存在不一致的情况
- BestEffort(尽最大努力)
- pod中的所有容器都未定义资源配置(Requests与Limits都未定义),那么该pod的Qos级别就是BestEffort
pod使用nvdia GPU VGPU
containerd容器引擎配置runtime
如果你使用的是 containerd 作为容器引擎,并且希望为其安装 NVIDIA Container Runtime,同时配置 runc 为默认的运行时,你可以按照以下步骤进行配置。
- 安装 NVIDIA Container Runtime
首先,你需要在每个 GPU 节点上安装 NVIDIA Container Toolkit,以便容器能够使用 GPU。以下是安装步骤:
# 设置容器运行时的安装源
distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
&& curl -s -L https://nvidia.github.io/nvidia-container-toolkit/gpgkey | sudo apt-key add - \
&& curl -s -L https://nvidia.github.io/nvidia-container-toolkit/$distribution/nvidia-container-toolkit.list | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
# 更新包列表并安装 NVIDIA Container Toolkit
sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
# 重启 containerd 服务以应用更改
sudo systemctl restart containerd
- 配置
containerd以使用 NVIDIA Container Runtime
你需要在 containerd 的配置文件中指定 NVIDIA Container Runtime,并将 runc 设置为默认运行时。
编辑 containerd 的配置文件,通常位于 /etc/containerd/config.toml。如果这个文件不存在,你可以通过运行以下命令生成默认配置文件:
sudo containerd config default | sudo tee /etc/containerd/config.toml
接着,编辑 config.toml 文件,添加 NVIDIA 运行时配置。
修改 config.toml:
- 打开配置文件进行编辑:
sudo nano /etc/containerd/config.toml
- 添加或修改以下内容:
[plugins]
[plugins."io.containerd.grpc.v1.cri"]
[plugins."io.containerd.grpc.v1.cri".containerd]
default_runtime_name = "runc"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia.options]
BinaryName = "/usr/bin/nvidia-container-runtime"
在这个配置中:
default_runtime_name = "runc": 设置runc为默认的运行时。runtime_type = "io.containerd.runc.v2": 指定使用runc作为容器的运行时。-
BinaryName = "/usr/bin/nvidia-container-runtime": 指定 NVIDIA Container Runtime 的路径。 -
重启
containerd
在完成配置修改后,重启 containerd 使其生效:
sudo systemctl restart containerd
- 验证配置
你可以通过以下命令验证配置是否正确:
sudo ctr run --rm --gpus 1 docker.io/nvidia/cuda:11.0-base nvidia-smi
这个命令应该输出 GPU 的信息,表明容器可以正确访问 GPU。
总结
通过以上步骤,你将成功配置 containerd 使用 NVIDIA Container Runtime,并将 runc 设置为默认运行时。这样做之后,默认情况下,Pod 会使用 runc,但如果你在 Pod 中请求 GPU 资源,containerd 会自动使用 NVIDIA Container Runtime 来运行这些 Pod。
containerd容器引擎配置Nvidia container runtime
容器获取外部数据的方式
Downward API
-
基于Downward API机制可以把容器所在pod的一些状态信息(pod名字、pod的ip、调度的节点,pod的注释、Label
、Annotation)注入到容器中(在容器体现的形式可以是环境变量、也可以某个文件中的文件) -
Downward API可以通过两种方式将Pod和容器的元数据注入容器内部
-
环境变量:将Pod或Container信息设置为容器内的环境变量
-
Volume挂载:将Pod或Container的信息以文件形式挂载到容器内部
-
configmap
-
configmap是什么
- configmap是一个配置资源
把配置文件内容都放到configmap里存着,然后可以用volume的方式挂载给多个pod
- configmap是一个配置资源
-
configmap资源 VS secret资源
- ConfigMap && Secret 是K8S中的针对应用的配置中心,CongiMap与Secrets类似,但通常
ConfifgMap用来存放不敏感的字符串
- ConfigMap && Secret 是K8S中的针对应用的配置中心,CongiMap与Secrets类似,但通常
Secret
- pass 暂未学习需要补上
控制器
daemonset
什么是控制器
- 控制器就是自动化管理与维护资源状态的工具
控制器分为两类
-
内置控制器 replicaset、deployment、daemonset、statefulset、job、cronjob
每一种控制器都具备自己特定的管理逻辑/功能 -
自定义控制器
为何要用控制器去管理资源
- 创建出资源本质仅仅只是往etcd数据库中写入了一条数据
控制器的原理
- 控制器的代码会watch 某种特定的资源类型
有新增的记录,控制器会调用api来根据资源的spec的固定来创建出资源
如果发现status与spec状态不符,则会进行调谐
常用控制器
-
replicaset控制器资源
- replicaset 只能感知副本的变化维持副本数量 感知不到pod的变化不支持 滚动升级 回滚 一般还是使用 Deployment 来自动管理ReplicaSet
-
deployment
-
deployment 关联replicaset 创建 多个副本
支持 水平扩缩 滚动更新 版本回退 -
滚动更新策略
-
strategy: # 滚动更新策略
type: RollingUpdate # 指定更新策略:RollingUpdate(默认值)-
可以指定
maxUnavailable和maxSurge来控制 rolling update 进程-
maxUnavailable 表示更新过程中,允许多少个 Pod 处于不可用状态。
可以是整数(数量),也可以是百分比。 maxUnavailable: 1 表示最多 1 个 Pod 不可用。 -
maxSurge 表示更新时,最多能额外启动多少个新 Pod。
可以是整数或百分比。
maxSurge: 2 表示在原有 5 个副本的基础上,最多可以临时增加 2 个 Pod -
滚动更新过程 先起 1 个新版本 Pod(因为 maxSurge=2,允许超额
-
再删 1 个旧版本 Pod(保证 maxUnavailable=1)
-
循环进行:起一个新 Pod → 删一个旧 Pod 最终所有 Pod 替换为新版本
-
-
-
Recreate:先删除所有旧 Pod,再启动新 Pod(会有短时间服务中断)。
-
RollingUpdate(默认):逐步替换,保证服务不中断。
-
总结
- 滚动更新策略就是通过控制 maxUnavailable 和 maxSurge,在不中断服务的前提下,逐步替换 Pod 实际生产中,一般配置:
maxUnavailable: 25%
maxSurge: 25%
- 滚动更新策略就是通过控制 maxUnavailable 和 maxSurge,在不中断服务的前提下,逐步替换 Pod 实际生产中,一般配置:
-
-
-
DaemonSet 控制器
-
默认会在每个节点都启动一个pod副本(可以用使用NodeSelector或者NodeAffinity来控制
哪些节点上启动) -
应用场景
- 1、每个节点上都要启动一个flanneld进程
2、kube-proxy组件负载pod的负载均衡流量转发,每个节点要有
3、采集每个节点上的日志
4、对每个节点进行监控
- 1、每个节点上都要启动一个flanneld进程
-
-
statefulset控制器
专门用于管理有状态的资源-
有状态指的是:应用的运行要依赖之前的数据---》限制调度,pod只能调度到之前状态存在的节点上才可以
-
无状态指的是:应用的运行不需要依赖之前的数据--》对调度没有限制,pod可以随意漂移到最合适的节点
-
特性
-
1)稳定的次序:对于N个副本的StatefulSet,每个Pod名字后缀从0-N,按次序启停
-
)稳定的网络标识(网络标识即dns域名):搭配无头服务每个pod都有一个固定的域名(FQDN),pod的ip地址随意变化,但这个域名不变
-
)稳定的存储:通过VolumeClaimTemplate为每个Pod创建一个PV。删除、减少副本,不会删除相关的卷。
-
-
statefulset控制器应用场景
- 能把应用做成无状态的就尽量做成无状态的
如果就是有状态的服务,
简单场景可以用statefulset
复杂场景往往需要自定义控制器来实现-
statefulset 与其他控制器的区别
-
deployment、daemonset都是面向无状态的服务 他们管理的pod ip 名字 启停顺序都是随机的
statefulset 是面向有状态的服务 他所管理的pod拥有固定的pod名称,启停顺序。 -
对应服务不同 在deployment 中对应的服务是service 在statefulset 中对应的无头服务 与 service的区别就是他没有cluster ip 解析他的名称
返回 headless service 对应的全部pod的endpoint列表 -
statefulset 在 headless service 的基础上有位 statefulset控制的每个pod副本创建了一个域名
-
-
两个重要组成部分
-
Headless Service
可以使得每个pod有一个固定FQDN名字,上游的访问可以直接访问该域名来访问pod
不需要svc做流量转发、没有负载均衡的效果 -
volumeClaimTemplates
创建pvc的模版,每个pod副本都会创建出一个自己独有的pvc,不与其他pod共享
-
-
无头服务
pod名字固定+创建一个无头服务,就可以让每个pod拥有一个固定的fqdn域名(coredns负责解析)- 基于普通svc的访问流程
普通svc的域名----coredns解析-----》svc的cluster ip----ipvs转发-》pod的endpoint
- 基于普通svc的访问流程
基于无头svc的访问流程
无头svc的域名----coredns解析-----》所关联pod的ip地址作为dns解析记录来返回 -
Job控制器
-
启动pod来执行一次性的任务,pod正常执行完毕后的状态为completed
Completed状态的含义:pod正常执行完毕,且返回的状态码为0 -
CrashLoopBackOff :该状态表示 Pod 内的容器在启动后反复崩溃,并且 Kubernetes 已经多次尝试重新启动但仍然失败。
-
job的pod中的容器要求是一个可以执行完毕、可以退出的任务,而不是一个常驻前台的进程
-
Job的RestartPolicy仅支持设置为Never或OnFailure,不能是Always,因为job是一次性的
-
关键参数
- activeDeadlineSeconds超时设置 超时后pod状态为failed
apiVersion: batch/v1
kind: Job
metadata:
name: job-demo
spec:
activeDeadlineSeconds: 15
template:
spec:
restartPolicy: Never
containers:
- name: counter
image: busybox
command:
- "bin/sh"
- "-c"
- "i=0;while true; do echo $i; sleep 1;let i++;done"-
ttlSecondsAfterFinished: 异常或成功后的清理时间
-
并行控制器
apiVersion: batch/v1
kind: Job
metadata:
name: job-para-test
spec:
ttlSecondsAfterFinished: 60
parallelism: 2
completions: 4
template:
spec:
containers:
- name: test-job
image: busybox
command: ["/bin/sh","-c","echo test;sleep 10"]
restartPolicy: Never- parallelism 默认为1 同一时间只有1个pod运行 并行的 - completions 默认1 只要完成N次就人为是成功的 没成功会重试 backoffLimit -
重启策略
- restartPolicy: OnFailure: 当pod运行失败后,会重启该pod本身(不会创建新pod),最多重启backoffLimit次
apiVersion: batch/v1
kind: Job
metadata:
name: job-demo
spec:
# backoffLimit: 6 # 默认就为6
template:
spec:
#restartPolicy: OnFailure # 修改重启策略
restartPolicy: Never
containers:
- name: counter
image: busybox
command:
- "bin/sh"
- "-c"
- "for i in 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1; do echo $i; sleep 1;if [ $i -eq 2 ];then exit -1;fi;done"- restartPolicy: Never:当pod运行失败后,不会重启pod本身了,但是job控制器会创建新的pod,最多新建backoffLimit个新pod
cronjob控制器
-
就是在job一次性任务的基础上加了一个时间调度
周期性管理k8s中的资源 -
关键参数
-
spec.concurrencyPolicy 字段并行控制 job
-
这三个参数是用来控制job允不允许在下一个时间节点创建出来
-
Allow
apiVersion: batch/v1
kind: CronJob
metadata:
name: my-cronjob
spec:
schedule: "/1 * * * " # 每1分钟执行一次,但每次pod的执行任务故意超过1分钟来验证允许并发
concurrencyPolicy: Allow
jobTemplate:
spec:
template:
spec:
containers:
- name: my-container
image: busybox
command: ["sh","-c","for i inseq 1 120;do echo $i;sleep 1;done"]
restartPolicy: OnFailure- CronJob CronJob 允许并发 Job 执行。,如果一个任务还没有结束,而下一次调度时间到了,新的任务会直接启动- Forbid
apiVersion: batch/v1
kind: CronJob
metadata:
name: my-cronjob
spec:
schedule: "/1 * * * " # 每1分钟执行一次,但每次pod的执行任务故意超过1分钟来验证是否允许并发
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
containers:
- name: my-container
image: busybox
command: ["sh","-c","for i inseq 1 120;do echo $i;sleep 1;done"]
restartPolicy: OnFailure- CronJob 不允许并发执行;如果新 Job 的执行时间到了而老 Job 没有执行完,CronJob 会忽略新 Job 的执行即新job不会启动- Replace
apiVersion: batch/v1
kind: CronJob
metadata:
name: my-cronjob
spec:
schedule: "/1 * * * " # 每1分钟执行一次,但每次pod的执行任务故意超过1分钟来验证是否允许并发
concurrencyPolicy: Replace
jobTemplate:
spec:
template:
spec:
containers:
- name: my-container
image: busybox
command: ["sh","-c","for i inseq 1 120;do echo $i;sleep 1;done"]
restartPolicy: OnFailure- 如果新 Job 的执行时间到了而老 Job 没有执行完,那么老的会被终止Terminating掉,CronJob 会用新 Job 替换当前正在运行的 Job -
-
cronjob保留job的历史记录限制
apiVersion: batch/v1
kind: CronJob
metadata:
name: my-cronjob
spec:
schedule: "/1 * * * " # 每1分钟执行一次,但每次pod的执行任务故意超过1分钟来验证允许并发
concurrencyPolicy: Allow
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
containers:
- name: my-container
image: busybox
command: ["sh","-c","for i inseq 1 120;do echo $i;sleep 1;done"]
restartPolicy: OnFailure- successfulJobsHistoryLimit: 3 - failedJobsHistoryLimit: 1 -
HPA资源水平 Pod 自动扩缩器
-
HPA的基本原理
- HPA控制器资源可以从一系列的API中获取pod的运行指标 然后通过自己内部计算算法来计算出应该扩容/缩容的副本数
-
metrucs server 部署
metrics server部署
# 添加这行
# --enable-aggregator-routing=true
### 修改每个 API Server 的 kube-apiserver.yaml 配置开启 Aggregator Routing:修改 manifests 配置后 API Server 会自动重启生效。
cat /etc/kubernetes/manifests/kube-apiserver.yaml
wget https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.7.1/components.yaml
在部署之前,修改 components.yaml
- 之前部署集群用的自签名证书,metrics-server直接请求kubelet接口会证书校验失败,因此deployment中增加
- --kubelet-insecure-tls参数。 - 另外镜像原先在registry.k8s.io,国内下载不方便,下面的配置中修改成了国内镜像仓库地址。内网环境中可以先下载,然后再推到内网镜像仓库,镜像也改成内网镜像地址。
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
spec:
# ...
template:
spec:
containers:
- args:
- --cert-dir=/tmp
- --secure-port=10250
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --kubelet-use-node-status-port
- --metric-resolution=15s
- --kubelet-insecure-tls # 增加
image: k8s.mirror.nju.edu.cn/metrics-server/metrics-server:v0.7.1 # 修改
或者用下面的镜像地址
ctr image pull registry.cn-hangzhou.aliyuncs.com/google_containers/metrics-server:v0.7.1
kubectl top nodes # 查看的是使用情况
# 如下:(cpu的使用了多少、使用的百分比是多少),(内存使用了多少、使用的百分比是多少)
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
k8s-master-01 97m 2% 1133Mi 61%
k8s-node-01 37m 0% 653Mi 35%
k8s-node-02 28m 2% 764Mi 41%
# 查看 top 命令的帮助
kubectl top --help
# 查看node节点的资源使用情况
kubectl top node
# 查看pod的资源使用情况
kubectl top pod
# 查看所有命名空间的pod资源使用情况
kubectl top pod -A
例如:
[root@k8s-master-01 ~]# kubectl -n kube-system top pod coredns-7c445c467-4f9dx
NAME CPU(cores) MEMORY(bytes)
coredns-7c445c467-4f9dx 1m 17Mi
3.4 创建基于cpu的HPA对象
# hpa-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hpa-demo
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
resources:
requests:
memory: 50Mi # 内部也一并加上吧,虽然此时用不到
cpu: 50m # 添加cpu即可
kubectl autoscale deployment hpa-demo --cpu-percent=10 --min=1 --max=10
# nginx-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: default
spec:
ports:
- name: web
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: ClusterIP
kubectl run -it --image busybox test-hpa --restart=Never --rm /bin/sh
/ # while true; do wget -q -O- http://nginx.default.svc.cluster.local; done
=======================练习
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: hpa-nginx
spec:
maxReplicas: 10 # 最大扩容到10个节点(pod)
minReplicas: 1 # 最小扩容1个节点(pod)
metrics:
- resource:
name: cpu
target:
averageUtilization: 40 # CPU 平局资源使用率达到40%就开始扩容,低于40%就是缩容
# 设置内存
# AverageValue:40
type: Utilization
type: Resource
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: hpa-nginx
---
apiVersion: v1
kind: Service
metadata:
name: hpa-nginx
spec:
type: NodePort
ports:
- name: "http"
port: 80
targetPort: 80
nodePort: 30080
selector:
service: hpa-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hpa-nginx
spec:
replicas: 1
selector:
matchLabels:
service: hpa-nginx
template:
metadata:
labels:
service: hpa-nginx
spec:
containers:
- name: hpa-nginx
image: nginx:latest
resources:
requests:
cpu: 100m
memory: 100Mi
limits:
cpu: 200m
memory: 200Mi
3.5 创建基于内存的HPA对象
# increase-mem-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: increase-mem-config
data:
increase-mem.sh: |
#!/bin/bash
mkdir /tmp/memory
mount -t tmpfs -o size=40M tmpfs /tmp/memory
dd if=/dev/zero of=/tmp/memory/block
sleep 60
rm /tmp/memory/block
umount /tmp/memory
rmdir /tmp/memory
# hpa-mem-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hpa-mem-demo
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
volumes:
- name: increase-mem-script
configMap:
name: increase-mem-config
containers:
- name: nginx
image: nginx:1.18
ports:
- containerPort: 80
volumeMounts:
- name: increase-mem-script
mountPath: /etc/script
resources:
requests:
memory: 50Mi
cpu: 50m
securityContext:
privileged: true
# hpa-mem.yaml
apiVersion: autoscaling/v2 #注意这里使用的 apiVersion 是 autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: hpa-mem-demo
namespace: default
spec:
maxReplicas: 5
minReplicas: 1
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: hpa-mem-demo
metrics: # metrics 属性里面指定的是内存的配置
- resource:
name: memory
target:
averageUtilization: 30
type: Utilization
type: Resource
强调:
hpa可能有多个,以算出来最大的那一个为准
-
HPA的工作原理
-
metrucs server采集所有的副本
HPA周期性通过metrucs server 的api 获取指标默认15秒 根据自定义的规则获得pod副本与当前pod副本数量不同时 发起scale操作调整当前的pod副本数量 -
扩缩容算法
-
hap定期从kubernets的监控系统 metrucs server中获取当前系统中运行的pod资源使用情况比如cpu 内存使用率 或者自定义的指标
-
hap对获取到所有pod的指标数据进行汇总 计算出所有pod的平均值
-
当前的pod 的cpu使用率使用了 pod的 request和pod的实际cpu或者内存来计算pod的资源使用率
-
hpa可能有多个,以算出来最大的那一个为准
-
-
-
指标
-
外部指标
-
外部指标允许 HPA 根据 Kubernetes 集群外部的服务指标来进行扩展
-
数据来源:外部指标来自于 Kubernetes 集群外部的资源或服务,通常是由外部的监控系统或服务提供的。
这些指标不依赖于 Kubernetes 内部的资源,可能包括外部服务的响应时间、消息队列的长度、数据库的请求数等。
-
-
自定义指标Custom metrics
- 数据来源:自定义指标通常是从 Kubernetes 集群内部的应用程序中收集的。这些指标可以通过 Prometheus、Metrics Server 或其他监控工具采集,通常与特定应用程序或 Pod 的性能相关
-
常规指标
- 一般由 metrics-server 提供例如cpu、内存指标
-
-
补充
-
HPA v1版,需要借助Heapster获取CPU和内存指标
-
HPA v2版,需要借助Metrics Server获取需要的指标
-
k8s的定制化开发
储备知识点
-
学k8s主要是学习怎么用k8s,而用k8s就是在用k8s提供各种资源类型
每种资源类背后都有控制器负责管理维护该资源类型具体关系如下:
资源类型resource type------》Pod资源类型----》相当于一张表 每种资源类型都会对应自己的控制器来负责自动化的管理维护 资源resource -------------》一个具体的pod----》相当于表中的一条记录
yaml清单仅仅只是用来往etcd数据库插入数据的,至于说要根据数据的规定
来做具体的管理维护工作的是躲在背后你看不见的那些控制器代码
CRD机制
-
CRD是什么
-
CRD全称
Custom Resource Define
是用来自定义新的资源类型的机制 -
自定义一种资源类型 相当于在etcd中定义了一张表
CR 声明yaml文件,kind指定为你上面创建的crd----》得到一种自定义的资源---》相当于在表中插入了记录
自定义控制器---》每种资源类型背后都会有控制器来负责维护其状态
-
-
如何用CRD
-
声明一个关于CRD的yaml清单---》就可以得到一个新的resource type---(CRD机制)
-
为资源类型开发专门的控制器逻辑------》自定义控制器--------------(client-go框架)
-
自定义控制器的开发框架
-
Client-Go 是最底层的库,提供了对Kubernetes API的原生支持,适合需要高度自定义控制器的开发者
-
client-go框架的核心组成
-
Reflector:基于list-watch机制与apiserver打交道
-
DeltaFIFO:队列
-
Informer:承上启下的作用
对上会从DeltaFIFO队列中取出数据,然后调用Indexer来对数据做好索引与缓存
对上会从DeltaFIFO队列中取出数据,然后调用Indexer来对数据做好索引与缓存
Informer 机制不仅仅是client-go 重要的组成部分,而且可以说基本上 Kubernetes 自定义组件都离不开 Informer,Kubernetes 之所以设计Informer这样一个结构,核心需求是为了减少 Kubernetes API Server 和 ETCD 的压力,增强整个集群的稳定性。
- Indexer :制作索引与缓存
-
-
-
Controller-Runtime** 在 Client-Go 之上提供了更高层次的抽象,简化了控制器的开发过程,适合大多数控制器开发需求
-
Kubebuilder** 是最高层次的框架,基于 Controller-Runtime,提供了完整的控制器开发工具链,非常适合快速开发和部署 Kubernetes 控制器和操作器。
Operator
-
Operator是什么
-
Operator是一种开发模型= 自定义资源(CRD) + 自定义的控制逻辑(controller)
-
Operator框架 Operator框架指的是实现开发模型的具体框架
-
-
为何要用Operator框架
- 基于该框架可以非常方便的去自定义资源(CRD) + 自定义的控制逻辑(controller)
例如:
1、自定义CRD,你不用去记忆CRD每个字段的含义
直接按照框架规定的标准去定义好一些字段即可,框架可以帮你生产CRD.yaml文件2、控制的实现逻辑,只需要去固定函数中填充代码即可 -
poerator 开发
- pass
Admission Webhooks
-
webhook指的是中途拦击请求做定制化处理
-
mutate:截胡请求之后做修改,改完之后扔回原链路
-
validate:对请求的数据做校验,不符合规定的直接扔掉
-
-
webhook的执行时机
-
Admission Webhook 本质是 api-server 的一个 webhook 调用
-
webhook的触发时机
-
总提来说,Admission Webhooks 机制是在 API Server 接收到请求、执行授权检查后和将请求持久化到 etcd 之前触发
-
详细的看,Mutating Webhook 在 Validating Webhook 之前触发,因此可以先对资源进行修改,然后再进行验证
-
-
Admission Webhook的运行流程
-
-
应用场景
-
mutating webhook应用场景
-
自动注入sidecar容器
-
自动注入sidecar容器
-
注入配置或者标签
-
-
Validating Webhook应用场景
-
Validating Webhook应用场景
-
必须用私有仓库的镜像
-
安全策略审计:例如不允许使用特权模式
-
-
-
mutating webhook实战案例
储备知识:基于flask框架开发一个web程序,对外提供api接口
# 安装python3解释器
# 为解释器环境安装flask代码包: pip3 install flask
from flask import Flask
app = Flask(__name__) # 创建一个 Flask 应用实例
@app.route('/xxx', methods=['GET']) # http://192.168.71.2:8888/xxx
def mutate():
print("run....................")
return "hello"
@app.route('/yyy', methods=['GET']) # http://192.168.71.2:8888/xxx
def test():
print("run22222222222222222....................")
return "hello2222222222222222222222222"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8888,)
上面是一个 py程序的小例子
下面是 实战的案例
需求:为所有新创建的pod打上标签:environment: production
1、先准备webhook程序
cat > webhook.py << 'EOF'
from flask import Flask, request, jsonify # 导入 Flask 框架、请求处理和 JSON 响应模块
import json
import ssl
import base64
app = Flask(__name__) # 创建一个 Flask 应用实例
def create_patch(metadata):
"""
创建 JSON Patch 以添加 'mutate' 注释。
如果 metadata.annotations 不存在,则首先创建该路径。
"""
if 'labels' in metadata:
dic = metadata['labels']
else:
dic = {}
patch = [
# 添加 'labels' 键,如果不存在
{'op': 'add', 'path': '/metadata/labels', 'value': dic},
# 添加 'environment' 标签
{'op': 'add', 'path': '/metadata/labels/environment', 'value': 'production'}
]
patch_json = json.dumps(patch)
patch_base64 = base64.b64encode(patch_json.encode('utf-8')).decode('utf-8')
return patch_base64
@app.route('/mutate', methods=['POST']) # https://webhook-service.default.svc:443/mutate
def mutate():
"""
处理 Mutating Webhook 的请求,对 Pod 对象应用 JSON Patch。
"""
admission_review = request.get_json() # 从请求中提取 AdmissionReview 对象
# 验证 AdmissionReview 格式是否正确
# admission_review['request']['object']
if 'request' not in admission_review or 'object' not in admission_review['request']:
return jsonify({
'kind': 'AdmissionReview',
'apiVersion': 'admission.k8s.io/v1',
'response': {
'allowed': False, # 如果格式无效,则禁止当前提交过来的资源请求
'status': {'message': 'Invalid AdmissionReview format'}
}
})
req = admission_review['request'] # 提取请求对象
print('--->',req)
# 生成 JSON Patch
metata = req['object']['metadata']
patch_json = create_patch(metata)
# 准备 AdmissionResponse 响应
admission_response = {
'kind': 'AdmissionReview',
'apiVersion': 'admission.k8s.io/v1',
'response': {
'uid': req['uid'],
'allowed': True,
'patchType': 'JSONPatch',
'patch': patch_json # 直接包含 Patch 数据作为 JSON 字符串
}
}
print(admission_response)
return jsonify(admission_response)
if __name__ == '__main__':
# 加载 SSL 证书和私钥
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain('/certs/tls.crt', '/certs/tls.key')
# Run the Flask application with SSL
app.run(host='0.0.0.0', port=443, ssl_context=context)
EOF
2、制作镜像(包含webhook.py的运行环境,依赖python3解释器、依赖flask框架)
文件dockerfile内容如下
# 使用官方 Python 镜像作为基础镜像
FROM python:3.9-slim
# 设置工作目录
WORKDIR /app
# 将当前目录的所有文件复制到容器的 /app 目录
COPY webhook.py .
# 安装 Flask 及其依赖
RUN pip install Flask
# 启动 Flask 应用
CMD ["python", "webhook.py"]
然后构建镜像:
docker build -t egon-mute-webhook:v1.0 .
打标签上传
docker tag egon-mute-webhook:v1.0 registry.cn-shanghai.aliyuncs.com/egon-k8s-test/egon-mute-webhook:v1.0
docker push registry.cn-shanghai.aliyuncs.com/egon-k8s-test/egon-mute-webhook:v1.0
3、配置 Webhook 的 Secret
# 生成 CA 私钥
openssl genrsa -out ca.key 2048
# 生成自签名 CA 证书,有效期为 100 年
openssl req -x509 -new -nodes -key ca.key -subj "/CN=webhook-service.default.svc" -days 36500 -out ca.crt
创建证书请求的配置文件
cat > webhook-openssl.cnf << 'EOF'
[req]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn
[ dn ]
C = CN
ST = Shanghai
L = Shanghai
O = egonlin
OU = egonlin
CN = webhook-service.default.svc
[ req_ext ]
subjectAltName = @alt_names
[alt_names]
DNS.1 = webhook-service
DNS.2 = webhook-service.default
DNS.3 = webhook-service.default.svc
DNS.4 = webhook-service.default.svc.cluster.local
[req_distinguished_name]
CN = webhook-service.default.svc
[v3_req]
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[ v3_ext ]
authorityKeyIdentifier=keyid,issuer:always
basicConstraints=CA:FALSE
keyUsage=keyEncipherment,dataEncipherment
extendedKeyUsage=serverAuth,clientAuth
subjectAltName=@alt_names
EOF
使用webhook-openssl.cnf这个配置文件生成 CSR:
# 生成 Webhook 服务的私钥
openssl genrsa -out webhook.key 2048
# 使用 OpenSSL 配置文件生成 CSR
openssl req -new -key webhook.key -out webhook.csr -config webhook-openssl.cnf
最终得到webhook.crt、webhook.key
将生成的证书和私钥存储在 Kubernetes Secret 中
kubectl delete secrets webhook-certs
kubectl create secret tls webhook-certs \
--cert=webhook.crt \
--key=webhook.key \
--namespace=default --dry-run=client -o yaml | kubectl apply -f -
3、创建deployment来部署webhook服务
apiVersion: apps/v1
kind: Deployment
metadata:
name: webhook-deployment
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: webhook
template:
metadata:
labels:
app: webhook
spec:
containers:
- name: webhook
image: registry.cn-shanghai.aliyuncs.com/egon-k8s-test/egon-mute-webhook:v1.0 # 替换为你的镜像
volumeMounts:
- name: webhook-certs
mountPath: /certs
readOnly: true
volumes:
- name: webhook-certs
secret:
secretName: webhook-certs
---
apiVersion: v1
kind: Service
metadata:
name: webhook-service
namespace: default
spec:
ports:
- port: 443
targetPort: 443
selector:
app: webhook
4、基于kind: mutatingwebhookconfiguration 该资源类型创建出一个资源,相当于于一道关卡
在该资源中声明把请求转给的目标webhook程序的api地址
基于脚本来生成yaml:
#!/bin/bash
base64 -w 0 ca.crt > ca.crt.base64
# 定义文件路径
ca_base64_file="ca.crt.base64"
yaml_file="m-w-c.yaml"
# 读取 ca.crt.base64 的内容
ca_base64_content=$(cat "$ca_base64_file" | tr -d '\n')
# 生成替换后的 YAML 文件内容
# 将 base64 内容插入到 YAML 文件中
cat <<EOF > "$yaml_file"
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: example-mutating-webhook
webhooks:
- name: example.webhook.com
clientConfig:
service:
name: webhook-service
namespace: default
path: "/mutate"
# 替换为 cat ca.crt.base64的内容
caBundle: "$ca_base64_content"
rules:
- operations: ["CREATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
admissionReviewVersions: ["v1"]
sideEffects: None
EOF
echo "YAML 文件已更新。"
5、创建pod进行测试
[root@k8s-master-01 /work]# cat test.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- name: nginx
image: nginx:1.18
[root@k8s-master-01 /wor
- validate webhook实战案例
cat > validating-webhook.py<< "EOF"
from flask import Flask, request, jsonify
import ssl
import logging
app = Flask(name)
logging.basicConfig(level=logging.INFO)
@app.route('', methods=['POST'])
def validate():
admission_review = request.get_json()
if 'request' not in admission_review or 'object' not in admission_review['request']:
return jsonify({
'kind': 'AdmissionReview',
'apiVersion': 'admission.k8s.io/v1',
'response': {
'allowed': False,
'status': {'message': 'Invalid AdmissionReview format'}
}
})
req = admission_review['request']
# 只处理 Pod
if req['kind']['kind'] == 'Pod':
pod = req['object']
labels = pod.get('metadata', {}).get('labels', {})
# 检查是否有 'environment' 标签
if 'environment' not in labels:
return jsonify({
'kind': 'AdmissionReview',
'apiVersion': 'admission.k8s.io/v1',
'response': {
'uid': req['uid'],
'allowed': False,
'status': {
'metadata': {},
'code': 400,
'message': 'Pod must have an "environment" label'
}
}
})
return jsonify({
'kind': 'AdmissionReview',
'apiVersion': 'admission.k8s.io/v1',
'response': {
'uid': req['uid'],
'allowed': True,
'status': {
'metadata': {},
'code': 200
}
}
})
return jsonify({
'kind': 'AdmissionReview',
'apiVersion': 'admission.k8s.io/v1',
'response': {
'allowed': True,
'status': {
'metadata': {},
'code': 200
}
}
})
if name == 'main':
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain('/certs/tls.crt', '/certs/tls.key')
app.run(host='0.0.0.0', port=443, ssl_context=context)
EOF
------------》总结
下面是请求在 Kubernetes 中从提交到最终转发到 Webhook Pod 的调用流程,使用箭头标注各个步骤:
-
用户提交请求
kubectl apply -f test.yaml↓
-
API Server 接收请求
- Kubernetes API Server 解析并检查请求是否匹配 Mutating Webhook 的配置。 ↓
-
API Server 触发 Mutating Webhook
- 如果请求匹配 Webhook 规则,API Server 会中断正常流程,准备将请求发送给 Webhook 服务。 ↓
-
转换请求为 AdmissionReview
- API Server 将原始请求转换为
AdmissionReview格式。 ↓
- API Server 将原始请求转换为
-
通过 HTTPS 发送请求
- API Server 使用
clientConfig中指定的 URL,通过 HTTPS 将AdmissionReview请求发送到 Webhook 服务。- 如果使用 Service
- 通过
https://webhook-service.default.svc:443/mutate将请求发送到 Webhook 服务的 Cluster IP。 ↓
- 通过
-
Webhook Service 接收请求
- Kubernetes Service 监听端口(如 443),并将请求路由到 Webhook Pod。 ↓
-
Webhook Pod 处理请求
- Webhook Pod 内的应用(例如 Flask 服务器)接收到
AdmissionReview请求,执行相应的逻辑(如修改、验证)。 ↓
- Webhook Pod 内的应用(例如 Flask 服务器)接收到
-
Webhook 返回 AdmissionResponse
- Webhook 处理完成后,生成
AdmissionResponse,并通过 HTTPS 返回给 API Server。 ↓
- Webhook 处理完成后,生成
-
API Server 处理 AdmissionResponse
- API Server 接收到 Webhook 的响应,继续处理原始请求,根据响应内容决定是否允许操作或进行其他变更。
通过这个流程,用户提交的请求最终会经过 Webhook 服务的验证或修改,然后再返回到 API Server 进行后续的处理。
API组织形式
apiserver的三大核心功能
-
对下封装了对etcd数据库的操作、对上提供了list-watch机制
-
对客户端提供了api接口
-
客户端访问的时候要经过一系列权限相关处理 检查凭证 检验授权 准入控制
组织方式
│ ├── batch (细分组:批处理任务)
│ │ ├── v1
│ │ │ ├── jobs
│ │ │ └── cronjobs
│ │ └── v2alpha1 (实验性版本)
│ │ └── ... (实验性功能)
│ │
│ ├── networking.k8s.io (细分组:网络)
│ │ └── v1
│ │ ├── ingresses
│ │ └── networkpolicies
│ │
│ ├── storage.k8s.io (细分组:存储)
│ │ └── v1
│ │ └── storageclasses
│ │
│ ├── rbac.authorization.k8s.io (细分组:权限控制)
│ │ └── v1
│ │ ├── roles
│ │ ├── rolebindings
│ │ ├── clusterroles
│ │ └── clusterrolebindings
│ │
│ └── ... (其他扩展组)
│
├── /metrics (性能指标)
│ └── 提供API Server内部性能监控数据
│
└── /healthz (健康检查)
└── 返回API Server健康状态(200 OK/错误码)
📋 层级结构说明
第一级:API Server根路径 (/)
访问入口:所有API请求的起点
第二级:主要API分组
/api - 核心组
包含Kubernetes最基础、最核心的资源
只有v1版本,保持向后兼容
/apis - 扩展组
包含所有非核心的扩展API
采用分组机制,每个细分组名全局唯一
系统端点
/metrics:监控数据
/healthz:健康检查
第三级:细分组 (/apis下的层级)
分组名:如apps、batch等,在YAML的apiVersion字段中指定
版本控制:每个分组可以有多个API版本(v1, v1beta1, v2alpha1等)
第四级:具体资源
每个API版本下包含具体的资源类型
如deployments、jobs、pods等
🎯 实际应用示例
YAML文件中的体现:
yaml
apiVersion: apps/v1 # 细分组: apps, 版本: v1
kind: Deployment # 资源类型
metadata:
name: my-app
REST API端点对应:
text
POST /apis/apps/v1/namespaces/default/deployments
这种层级结构确保了Kubernetes API的良好组织性和可扩展性!
api的版本细分
-
alpha版:尝鲜版本,新特性一定最先出现在这里
-
Beta版:稳定性提高版本,相对alpha版更稳定一些
-
稳定级别:比如v1版本表示是最稳定版了
-
查看api组织 kubectl get --raw / raw原生/
查看细分组 kubectl get --raw /apis |python -m json.tool
查看上下文 kubectl config get-contexts
查看当前上下文 kubectl config view
认证、授权、准入控制
限制用户对某个api下的某种类型资源拥有某种权限
-
apiGroups
-
verbs
-
resources
用户分类
-
普通用户
- 普通用户是假定被外部或独立服务管理的。管理员分配私钥。平时常用的kubectl命令都是普通用户执行的。
如果是用户需求权限,则将Role与User(或Group)绑定(这需要创建User/Group),是给用户使用的。
- 普通用户是假定被外部或独立服务管理的。管理员分配私钥。平时常用的kubectl命令都是普通用户执行的。
-
ServiceAccount(服务账户)
-
ServiceAccount(服务帐户)是由Kubernetes API管理的用户。它们绑定到特定的命名空间,并由API服务器自动创建或通过API调用手动创建。服务帐户与存储为Secrets的一组证书相关联,这些凭据被挂载到pod中,以便集群进程与Kubernetes API通信。(登录dashboard时我们使用的就是ServiceAccount)
-
如果是程序需求权限,将Role与ServiceAccount指定(这需要创建ServiceAccount并且在deployment中指定ServiceAccount),是给程序使用的。
-
授权介绍
-
定义角色:在定义角色时会指定此角色对于资源的访问控制的规则
-
绑定角色:将主体与角色进行绑定,对用户进行访问授权。
-
角色
-
Role:授权特定命名空间的访问权限
-
ClusterRole:授权所有命名空间的访问权限
-
-
角色绑定
-
RoleBinding:将角色绑定到主体(即subject)
-
ClusterRoleBinding:将集群角色绑定到主体
-
-
主体(subject)
-
User:用户
-
Group:用户组
-
ServiceAccount:服务账号
-
security context安全上下文
- pass 记得学习
service
svc是什么
- svc是一种智能负载均衡器,用来代理pod的服务的
为何要用service
-
解决了两个关键问题
-
pod的ip是动态变化的
-
pod是多副本的,会扩缩副本,即副本数变化
-
传统的做法:使用了nginx来代理多个应用服务 应用的增加和减少需要手动更新nginx负载均衡的upsteam 无法动态上下限
解决的办法: nginx负载均衡从consul zookeeper或etcd服务发现组件 检测服务的存活来实现服务动态的增减
-
svc作用
-
k8s中的svc使用标签来管理pod 感知副本的变化
-
动态感知pod的ip地址变化、pod内服务的变化
-
svc 每管理一个pod都会生成 endpoints是(ip+port)
-
pod的ip地址变化:endpoint会发生变化 利用了list-watch机制 能whatch到pod的ip地址变化 svc会自动更新关联的endpoint
-
pod没有通过readnessProbe就绪检查 会自动摘除被代理的endpoint
-
-
svc对外提供统一的访问地址
-
在集群内访问
-
cluster ip: 只能在k8s集群内访问,只要svc不被删除重建该地址不会变
-
fqdn域名:(被coredns组件解析) 只能在k8s集群内部使用
svc名字.名称空间.svc.cluster.local
-
-
在集群外访问
- NodePort 在所有宿主机监听一个端口号 搭配宿主机的ip和endpoint就会把请求打到svc svc计算出要访问哪一台机器
endpoint就是 pod的ip+端口 创建出的资源叫endpoint
- LoadBalance 公有云支持
-
一个svc资源就对应一个微服务 同一个服务多副本
-
svc的原理
-
svc负载均衡有两种模式
-
基于iptables
- kube-proxy 使用 iptables 的 DNAT(目标网络地址转换)规则来拦截到达 Service ClusterIP 的流量,并将其随机重写(负载均衡)到后端 Pod 的 IP。它本质上是“借用了”一个防火墙工具来实现负载均衡。
-
基于ipvs(效率更高)
-
它工作在内核态,基于 Netfilter(和 iptables 相同的基础框架),但实现了直接的路由和数据包转发。
-
它支持多种负载均衡算法:轮询、最少连接、目标哈希、源哈希等
-
当kube-proxy以ipvs代理模式启动时,kube-proxy将验证节点上是否安装了IPVS模块,如果未安装,则kube-proxy将回退到iptables代理模式
-
-
相同点
-
在所有安装有kube-proxy组件的k8s节点上都会有
一模一样的转发规则,并且是动态感知 -
在任何一个安装有kube-proxy组件的k8s节点上都可以发起
对svc的请求(svc必须有clusterip),请求的链路如下
请求----svc--基于转发模式计算出应该访问的podip--》用网络插件例如vxlan模式来封包
发送
-
-
不同点
-
iptables效率低(不是专门用来应对负载均衡)
-
随着iptables越来越多,整个内核的转发效率会变得很慢
-
ipvs专为负载均衡而生
-
-
svc的四种类型
-
ClusterIP:只能用于集群内部访问(默认)
-
NodePort:用于集群外部访问
该类型下的servie,会在集群主机上(所有安装有kubelet组件的主机)监听一个端口与service的端口映射,该端口称之为NodePort
因为NodePort是在所有集群主机监听的物理端口,所以搭配任意一台集群主机的ip地址就可以访问了,转发链路如下
集群外部通过请求 <NodeIP>:<NodePort> ----》ipvs规则计算出要转发的目标pod ip(ipvsadm -Ln)-----》然后转发到<Podip>:<TargetPort>
-
LoadBalancer:向云提供商申请一个独立于k8s的负载均衡器,该负载均衡器会将流量转发到每个物理节点,形式为:<NodeIP>:NodePort
只要把svc的type=NodePort改为type=LoadBalancer即可,k8s会自动帮我们创建一个对应的负载均衡器实例
并返回它的ip地址供外部客户端使用 -
ExternalName:**將svc映射為一個外部域名地址,通過externalName字段进行设置。
如果外部服务有可以解析的域名,直接指定即可 像mysql redis 想访问他可以借用这个字段-
如果外部服务没有域名,而只有ip+port,那我们无法指定ExternalName,此时只能通过自建endpoint来实现
-
我们的svc的clusterIP必须设置为None
-
k8s中总共有3种ip
-
Node IP:物理节点的静态ip
-
Pod IP:pod的ip地址,重启便会变化
-
Cluster IP:Service的ip地址,是新建的时候k8s分配的
获取客户端真实ip地址
-
设置externalTrafficPolicy: Local
- 意味着当你用某个物理节点的ip+nodeport来访问时,优先从该物理节点找pod
如果一直用某个物理节点的ip来访问,意味着没有负载均衡的效果,一直会访问固定pod
因为该参数的本意是尽量减少网络跳数
- 意味着当你用某个物理节点的ip+nodeport来访问时,优先从该物理节点找pod
ingress
-
储备
- 单体服务与微服务
单体服务:整个软件就只有一个应用构成
微服务:整个软件由很多个应用构成,每个应用都单独部署、单独监听端口,对外提供api接口(接口是restful风格的)
- 单体服务与微服务
-
在k8s中如何暴漏单体服务
单体服务跑在一个或多个pod中(为了实现pod的故障自愈会控制器来管理)
- NodePort+ 在k8s集群外自建负载均衡
- 应用场景:集群规模不大,自己管理负载均衡能应付过来
- LoadBalaner+ 在k8s集群外云平台会提供现成的负载均衡
ingress
ingress是什么
- ingress是一种七层流量转发机制,即七层负载均衡
请求的链路是请求到了svc之后,svc依据自己标签选中的微服务pod以及负载均衡
策略(例如ipvs)来将请求转发给指定pod
注意:svc拥有负载均衡能力的前提是它有clusterIP
为何要用ingress
-
为了用引入七层负载均衡把微服务重新汇聚成一个整体
然后对外暴漏 -
引入ingress之后完整的访问流程
ingress对象资源详解
构建TLS站点
- pass 回头记录
发布策略
什么是发布
- 部署启用新代码
发布过程中会遇到的问题
-
发布过程中服务会暂时中断
-
新版本遇到问题,如何快速回滚
-
确保服务中断断时间不要过长
滚动发布
-
就一套环境,在升级过程中,先启动一个新的,再停一个老版本先启动一个新的,再停一个老版本只需要新增1台机器,就可以滚动起来了
-
优点
- 节省机器、最小化停机时间、平滑过渡
-
缺点
- 新老版本共存,在整个升级过程中极不稳定
一旦访问出问题,无法确定是新版本的问题还是老版本的问题
关键是缺少对流量的控制
- 新老版本共存,在整个升级过程中极不稳定
蓝绿发布(蓝老绿新)
-
以整套环境基本单位来进行流量分发
-
是部署两套环境,两套环境的机器配置都一样
老环境部署老版本,新环境部署新版本 -
在两套环境的基础上做流量的分配,旧80%,新20%,然后逐步增加新环境的流量
-
优点:随时切换,快速切回老环境,也能抗住压力
-
缺点:耗费资源
金丝雀发布
- 先部署少数几台新版本,然后采用流量的控制的方式,逐步验证
让一点一点扩大范围,适用于迭代版本,而不是大版本更新
灰度发布
-
用户群体特征,从所有用户中选出一些充当小白鼠
-
先灰度验证,再发正式环境
-
金丝雀发布可以被视为灰度发布的一种实现方式,但灰度发布的范围更广,涵盖了更多逐步发布的策略。
金丝雀发布可以被视为灰度发布的一种实现方式,但灰度发布的范围更广,涵盖了更多逐步发布的策略。 金丝雀发布的流量分配不区分用户
ingress-金丝雀发布
-
控制流量的两大类方式
-
基于权重 canary-weight
-
基于用户请求 canary-by-header canary-by-header-value canary-by-header-value
-
CoreDNS
CoreDNS的解析流程
- 请求发送:Pod 的 /etc/resolv.conf 指向 10.96.0.10(kube-dns 的 Service IP),请求被发送到 CoreDNS。
缓存检查:cache 插件首先检查是否有缓存。假设没有,继续。
集群域名匹配:kubernetes 插件识别出该域名属于 cluster.local 域。
查询 Kubernetes API:它在内存中查找 default 命名空间下名为 nginx 的 Service,并获取其 ClusterIP(例如 10.103.232.145)。
返回结果:将 IP 地址 10.103.232.145 返回给客户端 Pod,同时 cache 插件会缓存这个结果。
处理外部域名:如果请求是 www.google.com,kubernetes 插件不处理,请求会传递给 forward 插件,由其转发到上游DNS服务器进行解析。
具体见pdf文件
NodeLocalDNScache
具体见pdf文件
调度策略
一、pod的调度
什么是调度?
调度指的就是创建新pod的时候,应该分配给哪个节点来创建该pod
ps:具体负责创建的是该节点上的kubelet
什么时候会创建新pod:
(1)使用者提交一个全新的创建pod的请求
(2)
某个节点挂掉、资源不足了,导致该节点上pod被驱逐到其他节点
(3)
pod是被控制器管理者,当副本数与预期不符,引发调谐过程也
会发起创建新pod的请求
结论:
1、静态pod不参与pod的调度流程,静态固定在某个节点上
2、非静态pod的创建,都会有调度流程
pod的创建完整流程:
预选:根据资源申请,选择器、亲和与反亲和、污点与容忍等规则,先筛选出一些机器---》符合基本条件的
优选:从筛选出的节点中进行打分,选择资源最优-------------------------》谁最优
- 节点选定:从优先级排序结果中挑选出优先级最高的节点运行Pod,当这类节点多于1个时,则进行随机选择
(3)
pod是被控制器管理者,当副本数与预期不符,引发调谐过程也
会发起创建新pod的请求
结论:
1、静态pod不参与pod的调度流程,静态固定在某个节点上
2、非静态pod的创建,都会有调度流程
节点选择NodeSelector
-
把调度到某个节点,选择依据是节点的标签必须事先给目标节点打上标签(依据该节点的特性)
-
场景举例
把运行有人工智能应用的pod调度到包含GPU的物理节点
把mysql调度到一个具有SSD磁盘的目标节点,就可以用NodeSelector -
注意
如果我们给多个Node都定义了相同的标签(disk=ssd),则kube-scheduler会根据调度算法从这组Node中挑选一个可用的Node
NodeSelector一定会调度到包含执行标签的节点,没有这种节点则调度失败,pod无法创建
物理节点上有一些预定义标签,你也可以直接选中这些标签
亲和性与反亲和性
-
一些彼此依赖的服务需要调度到同一个“地方”亲和
-
考虑到冗余性,需要将一些服务调到不同的“地方反亲和
-
维度
-
节点维度:标签选择器定位的是节点的标签
-
pod维度:标签选择器定位的是pod的标签,而不是节点
-
-
策略
-
软策略名为:preferredDuringSchedulingIgnoredDuringExecution
软策略:在Pod调度时可以尽量满足其规则,在无法满足规则时,可以调度到一个不匹配规则的节点之上,并且可以设置权重
-
硬策略名为:requiredDuringSchedulingIgnoredDuringExecution
- 硬策略`:是Pod调度时必须满足的规则,否则Pod对象的状态会一直是Pending,但不能设置权重
-
Pod的均匀调度
什么是均匀调度
- 均匀分布的效果就是让
同一标签的pod副本均匀分散到不同的拓扑域中
同一标签的pod副本代表同一服务
如何实现
- 引入一种判定机制,可以用标签将每个域中包含的带有此标签的pod给筛选出来,这便得到了每个域中
带有指定标签的pod的分布情况
然后接下来就是依次作为判定依据,来决定新pod应该创建到哪个域中,实现均匀分布
为何要做到均匀调度
- 均匀分布是实现容灾和高可用的核心,将业务 Pod 尽可能均匀的分布在不同可用拓扑域中是非常重要的
如何用均匀调度
-
前提:Even Pod Spreading默认已经启用
-
创建出不同域-- 打标签
-
为新pod声明均匀调度相关字段
-
topologyKey拓扑域
- 以何种拓扑域为单位来统计pod的分布情况,指的就是每个域中带有
-
labelSelector标签选择器:用该选择器来筛选pod
-
maxSkew
-
-
计算出新pod应该调度到哪个域中才均匀
-
先基于:topologyKey拓扑域+labelSelector标签选择器,来统计出每个域中包含的带有指定标签的pod数
-
然后计算出每个域的skew值当前拓扑域的skew = 当前拓扑域中被标签选出的 Pod 个数 - min{所有拓扑域中被标签选中的 Pod 个数的最小值}。
-
最后要让每个域的skew值<=maxSkew
补充:maxSkew值越小,越均匀
-
-
如果有多条均匀调度策略,相冲突,解决冲突的方法,是设置软硬策略
-
whenUnsatisfiable: 描述了如果 Pod 不满足分布约束条件该采取何种策略:
-
- DoNotSchedule (默认) 告诉调度器不要调度该 Pod,因此也可以叫作硬策略
-
- ScheduleAnyway 告诉调度器根据每个 Node 的 skew 值打分排序后仍然调度,因此也可以叫作软策略
-
Descheduler
-
Descheduler是什么
- Descheduler是一个均衡器,用来解决k8s运行过程中
节点pod分布的不均匀问题
- Descheduler是一个均衡器,用来解决k8s运行过程中
之前学习的均匀分布机制,只负责创建时的均匀,不负责运行时
-
为何要用Descheduler
- k8s集群是非常动态的,例如为了停机维护某一个物理节点,先执行驱逐/排空操作,使得该节点上的pod都被驱逐到了其他物理节点上。但是,当我们停机维护完成后,之前的pod并不会自动回到该节上,因为Pod一旦被绑定了节点是不会自动触发重新调度的,这就会导致集群在一段时间内出现不均衡的状态。
为了解决这个问题,你可能会想到手动去删掉之前驱逐走的那些pod,从而触发其重新调度,没错,这确实可以让当中的某些pod重新被调度回来,但这种做法并不是解决问题的根本。
真正的解决这个问题的办法,是引入一个均衡器来重相平衡集群,
-
Descheduler的核心原理
-
Descheduler本身并不会参与调度,它只是计算出该驱逐的pod进行驱逐,
-
驱逐后重建新pod的调度的任何还是会交给默认的调度器kube-scheduler来完成
-
-
添加PDB策略防止单点
-
引入descheduler之后,它会根据策略对pod进行驱逐以便进行重新调度,使k8s的资源达到一个平衡状态。
-
但是会有问题
-
1、如果一个服务只有一个副本,你为了资源平衡,把它驱逐走了,虽然会在新节点上拉起来,但过程中会导致业务中断
针对上述pod副本被全部驱逐导致业务中断的问题,我们可以通过配置PDB(PodDisruptionBudget) 对象来避免所有副本同时被删除,比如我们可以设置在驱逐的时候某应用最多只有一个副本不可用,
-
需要注意的点
- 当使用 descheduler 驱除 Pods 的时候,需要注意以下几点:
-
关键性 Pod 不会被驱逐,比如
priorityClassName设置为system-cluster-critical或system-node-critical的 Pod
[root@k8s-master-01 ~]# kubectl -n kube-system get cronjobs.batch descheduler -o yaml |grep -i priority
priorityClassName: system-cluster-critical
-
不属于 RS、Deployment 或 Job 管理的 Pods 不会被驱逐
-
DaemonSet 创建的 Pods 不会被驱逐
-
使用
LocalStorage的 Pod 不会被驱逐,除非设置evictLocalStoragePods: true -
具有 PVC 的 Pods 不会被驱逐,除非设置
ignorePvcPods: true -
在
LowNodeUtilization和RemovePodsViolatingInterPodAntiAffinity策略下,Pods 按优先级从低到高进行驱逐,如果优先级相同,Besteffort类型的 Pod 要先于Burstable和Guaranteed类型被驱逐
PDB策略
-
maxUnavailable: 1 设置最多不可用的副本数量,或者使用 minAvailable,可以使用整数或百分比
-
maxUnavailable - 最大不可用 Pod 数量 可以是整数也可以是百分比
-
子主题 4
调度、抢占、驱逐的区别?
调度(scheduling)指的是确保Pod匹配到合适的节点,然后由kubelet创建
负责调度的组件是:kube-scheduler
什么时候会触发调度:创建新pod
调度的依据:调度算法 (预选与优选)
抢占(Preemption)指的是终止低优先级的pod以便高优先级的 Pod 可以调度运行的过程。
负责按优先级抢占的组件是:kube-scheduler
负责按优先级抢占的组件是:kube-scheduler
什么时候回触发优先级抢占:创建一个高优先级的pod,但是因为资源不足而迟迟无法被调度时
抢占的依据:是pod的priorityClassName优先级类里规定的优先级值来作为判定优先级高低的用依据,而不是Qos
负责驱逐的组件是:kubelet
什么时候会触发驱逐:某个节点遇到不可压缩资源剩余量达预设的阈值时则会触发
驱逐哪些pod的依据是:Pod的Qos等级