Kubernetes 是一个容器编排工具,自动地执行容器化应用程序的管理,主要用于自动部署、扩展和管理容器。
Pod 是包含一个或多个容器的容器组,是 Kubernetes 中创建和管理的最小对象。
特点:
环境:kind 搭建的 1master 2worker 集群
kind集群的配置文件 config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: demo
nodes:
- role: control-plane
image: kindest/node:v1.23.4
extraPortMappings: # 将容器内的 Kubernetes API Server 端口(6443)映射到宿主机的 26443 端口
- containerPort: 6443 # 容器内部端口
hostPort: 26443 # 宿主机映射端口
listenAddress: "0.0.0.0" # 监听所有网络接口
- role: worker
image: kindest/node:v1.23.4
- role: worker
image: kindest/node:v1.23.4
搭建教程看 1.k8s-环境搭建 > 3.3.2. 1master 1worker 开放监听(v1.23) 修改一下配置文件即可
创建pod的配置文件 nginx.yaml
文件,内容如下
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
containers:
- name: nginx-container
image: docker.io/library/nginx:latest
imagePullPolicy: IfNotPresent
其中 kind 表示要创建的资源是 Pod 类型,metadata.name 表示要创建的 pod 的名字,这个名字需要是唯一的。spec.containers 表示要运行的容器的名称和镜像名称,镜像默认来源 DockerHub。
建议先把Nginx镜像pull到node当中,在imagePullPolicy: IfNotPresent
进行本地拉取,不然会直接访问dockerhub 导致拉取失败
在宿主机下载镜像
docker pull nginx:latest
导入到 Kind 集群
kind load docker-image nginx:latest --name demo
运行k8s命令 kubectl apply -f nginx.yaml
来创建 nginx-pod 这个Pod,通过 kubectl get pods
来查看 pod 是否正常启动。
刚刚创建名为 nginx-pod 这个 Pod 资源的底层如下图,最内层是我们的服务 nginx,运行在名为 nginx-container 的容器当中,容器(Container)的本质是进程,而 Pod 是管理这一组进程的资源。
故 Pod 可以管理多个容器
#查看Pod日志
kubectl logs --follow nginx-pod
#利用Pod执行命令
kubectl exec nginx-pod -- ls
#两种方法删除Pod
kubectl delete pod nginx-pod
kubectl delete -f nginx.yaml
创建后等待一会直到它显示运行即可
控制节点类似于域控机器;工作节点类似于域内主机,实际情况下是物理或虚拟机器,作为 Pod 的宿主机;Pod 类似于域内主机上多容器管理的 VM 软件,管理着一批容器化应用程序,实际情况下是Node上的一个进程组。
Master组件是集群的控制平台(control plane),Master组件负责集群中的全局决策(例如调度),Master组件可以运行于集群中的任何机器上,但为了方便简单,通常所有Master组件在同一台机器上运行,并且该机器上一般不运行用户的容器
此Master组件提供 Kubernetes API,是 Kubernetes 控制平台的前端,是所有 API 请求的入口点。Kubernetes管理工具如 kubectl 就是通过 kubernetes API 实现对 Kubernetes 集群的管理。
一致性分布式键值存储数据库,Kubernetes集群的所有配置信息都存储在 etcd 中。
此Master组件监控所有新创建但尚未分配到节点上的 Pod,并且自动选择为 Pod 选择一个合适的节点去运行。
影响选择的因素有:
此Master组件运行了所有的控制器,它像是集群的大脑,负责监控集群状态,并根据用户或系统的期望状态,自动调节和管理集群中的各种资源。控制器后面会重点讲。
此Master组件运行了与具体云基础设施供应商互动的控制器。
Node 组件运行在每一个节点上(包括 master 节点和 worker 节点),负责维护运行中的 Pod 并提供 Kubernetes 运行时环境。
kubelet 组件是运行在集群节点上的代理程序,负责与 API 服务器通信并管理该节点上的容器,它确保Pod 中的容器处于运行状态。不是通过 Kubernetes 创建的容器 Kubelet 不去管理。
每个node上都有此组件
kube-proxy 组件是一个网络代理程序,负责处理网络代理和负载均衡的功能。它运行在每个节点上(master和worker),维护节点上的网络规则,并支持服务的访问和负载均衡。
当我们需要在集群中区分不同的环境用于不同的需求,比如一个环境用于测试,一个环境用于开发,就需要用到 命名空间(Namespace),可以让不同环境的资源独立互相不影响,命名空间将同一集群中的资源划分为相互隔离的组,同一命名空间内的资源名称要唯一,但跨命名空间时没有这个要求。前文中创建的各种资源都没有指定命名空间,所以都处于默认的命名空间 default 中进行操作和获取资源。
#创建命名空间
kubectl create namespace test
kubectl get namespaces
#在 kube-system 命名空间中获取资源
kubectl get pods -n kube-system
kubectl get pods -A
kubectl get pods -A -o wide
假如我们现在有一个 Pod 正在提供线上的服务,我们来想想一下我们可能会遇到的一些场景:1. 明天举办一个活动,网站访问量会突然暴增;2. 运行当前 Pod 的节点发生故障了,Pod 不能正常提供服务了。
第一种情况,可能比较好应对,活动之前我们可以大概计算下会有多大的访问量,提前多启动几个 Pod 副本,活动结束后再把多余的 Pod 杀掉,虽然有点麻烦,但是还是能够应对这种情况的。
第二种情况,可能某天夜里收到大量报警说服务挂了,然后起来打开电脑在另外的节点上重新启动一个新的 Pod,问题可以解决。
但是如果我们都人工的去解决遇到的这些问题,就非常麻烦,非常不科学。如果有一种机制能够来帮助我们自动管理 Pod 就好了,Pod 挂了自动帮我在合适的节点上重新启动一个 Pod,这样是不是遇到上面的问题我们都不需要手动去解决了。ReplicaSet 这种资源对象就可以来帮助我们实现这个功能。
ReplicaSet(RS) 的主要作用就是维持一组 Pod 副本的运行,保证一定数量的 Pod 在集群中正常运行,ReplicaSet 控制器会持续监听它所控制的这些 Pod 的运行状态,在 Pod 发生故障数量减少或者增加时会触发调谐过程,始终保持副本数量一致。
环境:kind 搭建的 1master 2worker 集群
通过 YAML 文件来描述我们的 ReplicaSet 资源对象,如下 YAML 文件是一个常见的 ReplicaSet 定义:
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: nginx-rs
namespace: default
spec:
replicas: 3 # 期望的 Pod 副本数量
selector: # 必须匹配 template.metadata.labels
matchLabels:
app: nginx
template: # Pod 模板
metadata:
labels: # 必须与 selector.matchLabels 一致
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest # 建议显式指定版本
ports:
- containerPort: 80
ReplicaSet 控制器会通过定义的 Label Selector 标签去查找集群中的 Pod 对象
kubectl apply -f nginx-rs.yaml
kubectl get rs
kubectl get po -l app=nginx
kubectl describe pod nginx-rs-cmsdp
# 尝试删除其中一个 Pod
kubectl delete pod nginx-rs-cmsdp
# 再查看 Pod 列表
kubectl get po -l app=nginx
# 可以看到又重新出现了一个 Pod,这个就是上面我们所说的 ReplicaSet 控制器为我们做的工作
# 我们在 YAML 文件中声明了 3 个副本,然后现在我们删除了一个副本,就变成了两个,这个时候
ReplicaSet 控制器监控到控制的 Pod 数量和期望的 3 不一致,所以就需要启动一个新的 Pod 来保持 3 个副本,这个过程上面我们说了就是调谐的过程。
# 查看 RS 的描述信息来查看到相关的事件信息
kubectl describe rs nginx-rs
# 查看一个 Pod 的描述信息可以看到这个 Pod 的所属控制器信息
kubectl get po -l app=nginx
kubectl describe pod xxxxx
# 结果会存在 Controlled By: ReplicaSet/nginx-rs #从这里可以看到,这个pod是被
ReplicaSet/nginx-rs控制
# 如果要彻底删除 Pod, 我们就只能删除 RS 对象或者把rs里的副本数改为0
kubectl delete rs nginx-rs
kubectl get rs,pod
还有一点要注意:直接修改 nginx-rs.yaml
里的镜像版本后,是没办法改变pod里的镜像版本的,即:RS不支持应用的更新操作
前面我们学习了 ReplicaSet 控制器,了解到该控制器是用来维护集群中运行的 Pod 数量的,但是往往在实际操作的时候,我们反而不会去直接使用 RS,而是会使用更上层的控制器,比如我们今天要学习的主角 Deployment。Deployment 一个非常重要的功能就是实现了 Pod 的滚动更新,比如我们应用要更新,我们只需要更新我们的容器镜像,然后修改 Deployment 里面的 Pod 模板镜像,那么 Deployment 就会用滚动更新(Rolling Update)的方式来升级现在的 Pod,这个能力是非常重要的。因为对于线上的服务我们需要做到不中断服务,所以滚动更新就成了必须的一个功能。而 Deployment 这个能力的实现,依赖的就是上节课我们学习的 ReplicaSet 这个资源对象,实际上我们可以通俗的理解就是每个Deployment 就对应集群中的一次部署,这样就更好理解了。
Deployment 是最常用的K8s工作负载控制器(Workload Controllers),是K8s的一个抽象概念,用于更高级层次对象,部署和管理Pod。
Deployment 的主要功能:
Deployment 协调工作原理
环境:kind 搭建的 1master 2worker 集群
如下资源对象就是一个常见的 Deployment 资源类型,
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
namespace: default
labels: #这个标签仅仅用于标记deployment本身这个资源对象。注意:deployment里metadata里的label标签定不定义都无所谓,没有什么实际意义,因为deployment在k8s已经是一个很高级的概念了,没有什么人可以管它了。
role: deploy
spec:
replicas: 3 # 期望的 Pod 副本数量,默认值为1
selector: # Label Selector,必须匹配Pod模板中的标签。
matchLabels:
app: nginx
template: # Pod 模板
metadata:
labels:
app: nginx #一定要包含上面的 matchLabels里面的标签。
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
kubectl apply -f nginx-deploy.yaml
kubectl get deployments.apps
kubectl get po
# 到这里我们发现和之前的 RS 对象是否没有什么两样,都是根据spec.replicas 来维持的副本数量。
# 查看一个Pod 的描述信息
kubectl describe po xxxxx
# 仔细查看其中有这样一个信息 Controlled By: ReplicaSet/xxxxx
# 这个不是表示当前我们这个 Pod 的控制器是一个 ReplicaSet 对象啊,
# 我们不是创建的一个 Deployment 吗?为什么Pod 会被 RS 所控制呢?
# 再去看下这个对应的 RS 对象的详细信息
kubectl describe rs xxxxx
# 其中有这样的一个信息: Controlled By: Deployment/nginx-deploy
# 意思就是我们的 Pod 依赖的控制器 RS 实际上被我们的 Deployment 控制着
Pod、ReplicaSet、Deployment 三者的关系
Deployment 是通过管理 ReplicaSet 的数量和属性来实现 水平扩展/收缩 以及 滚动更新 两个功能的。
水平扩展/收缩:Deployment 控制器只需要去修改它所控制的 ReplicaSet 的 Pod 副本数量就可以了,它并不是一次升级,不会创建新的 ReplicaSet。这个功能是单靠 ReplicaSet 就能实现。而 Deployment 最突出的一个功能是支持滚动更新。
滚动更新:K8s 对 Pod 升级的默认策略,通过使用新版本 Pod 逐步更新旧版本 Pod,实现零停机发布,用户无感知。它是一个一个来的,不是一步到位的。
滚动更新过程
通过该控制器的名称我们可以看出它的用法:Daemon,就是用来部署守护进程的,DaemonSet 用于在每个 Kubernetes 节点中将守护进程的副本作为后台进程运行,说白了就是在每个节点部署一个 Pod 副本,当节点加入到 Kubernetes 集群中,Pod 会被调度到该节点上运行,当节点从集群够被移除后,该节点上的这个 Pod 也会被移除,当然,如果我们删除 DaemonSet,所有和这个对象相关的 Pods都会被删除。例如以下场景会用到 DaemonSet:
.spec.nodeName
),所以 DaemonSet 可以创建 Pod,即使调度器还没有启动。环境:kind 搭建的 1master 2worker 集群
DaemonSet 的 yam l写法和 Deployment 非常类似,只是改变了下 kind 名称,注意下 DaemonSet 是没有副本数这一参数选项的。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nginx-ds
namespace: default
spec:
selector:
matchLabels:
k8s-app: nginx
template:
metadata:
labels:
k8s-app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- name: http
containerPort: 80
kubectl apply -f nginx-ds.yaml
kubectl get node
kubectl get po -l k8s-app=nginx -owide
# 观察可以发现除了 master 节点之外的2个节点上都有一个相应的 Pod 运行,
# 因为 master 节点上默认被打上了污点(taints), DaemonSet是受污点的的影响的,
# 所以默认情况下不能调度普通的 Pod 上去,后面讲解调度器的时候会和大家学习如何调度上去
# 先来提前看看污点长什么样
kubectl describe node demo-control-plane |grep Taints
kubectl describe node demo-worker |grep Taints
kubectl describe node demo-worker2 |grep Taints
# 删除一个节点上的 pod, 观察下这个pod是否会被立马重建
kubectl get po -o wide
kubectl delete po xxxxx
集群中的 Pod 和 Node 是一一对应的,而 DaemonSet 会管理全部机器上的 Pod 副本,负责对它们进行更新和删除。
那么,DaemonSet 控制器是如何保证每个 Node 上有且只有一个被管理的 Pod ?
Job 资源用于执行一次性或批处理任务。比如你可以创建一个 Job 运行某 Pod 直到完成,当第一个 Pod 失败或者被删除时,Job 对象会启动一个新的 Pod。
Job 会创建一个或者多个 Pod,并将继续重试 Pod 的执行,直到指定数量的 Pod 成功终止。随着 Pod 成功结束,Job 跟踪记录成功完成的 Pod 个数。当数量达到指定的成功个数阈值时,任务(即 Job)结束。删除 Job 的操作会清除所创建的全部 Pod。
下面配置文件中 spec.parallelism 指的是并发执行最大数量,spec.completions 指的是会创建 Pod 的数量,每个 pod 都会完成下面的任务。两个字段组合起来就是先创建 3 个 pod 并发执行任务,一旦某个 pod 执行完成,就会再创建新的 pod 来执行,直到 5 个 pod 执行完成,Job 才会被标记为完成。
spec.template.spec.restartPolicy
为 OnFailure 的含义是当 Pod 中的容器意外退出或者被杀掉时,Pod 会继续留在当前节点,但容器会被重新运行。
apiVersion: batch/v1
kind: Job
metadata:
name: hello-job
spec:
parallelism: 3
completions: 5
template:
spec:
restartPolicy: OnFailure
containers:
- name: echo
image: busybox
command: ["/bin/sh"]
args: ["-c", "for i in 9 8 7 6 5 4 3 2 1 ; do echo $i ; done"]
kubectl apply -f hello-job.yaml
kubectl get jobs
kubectl get pods
kubectl logs -f hello-job-xxxx
CronJob 资源用于执行定时、周期性任务,本质上是创建基于 Cron 时间调度的 Jobs。
Cron 时间表语法
# ┌───────────── 分钟 (0 - 59)
# │ ┌───────────── 小时 (0 - 23)
# │ │ ┌───────────── 月的某天 (1 - 31)
# │ │ │ ┌───────────── 月份 (1 - 12)
# │ │ │ │ ┌───────────── 周的某天 (0 - 6)(周日到周一;在某些系统上,7 也是星期日)
# │ │ │ │ │ 或者是 sun,mon,tue,web,thu,fri,sat
# │ │ │ │ │
# │ │ │ │ │
# * * * * *
apiVersion: batch/v1
kind: CronJob
metadata:
name: hello-cronjob
spec:
schedule: "* * * * *" # 每分钟执行一次
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: echo
image: busybox
command: ["/bin/sh"]
args: ["-c", "for i in 9 8 7 6 5 4 3 2 1 ; do echo $i ; done"]
我们在实际使用的时候并不会直接使用 Pod,而是会使用各种控制器来满足我们的需求,在 Master 的各组件中
API Server 仅负责将资源存储于 etcd 中,并将其变动通知给各其他组件,如 kubelet、kube-scheduler、kube-proxy 和 kube-controller-manager 等。kube-scheduler监控到处于未绑定状态的 Pod 对象出现时就启动调度器为其挑选最合适的工作节点。另外 Kubernetes 的核心功能之一还在于要确保各资源对象的当前状态(status)已匹配用户期望的状态(spec),使当前状态不断地向期望状态 "调谐"来完成容器应用管理,这些就是 kube-controller-manager 的任务,kube-controller-manager是一个独立的组件,但是它却包含了很多功能不同的控制器。
Kubernetes 控制器会监听资源的 创建/更新/删除 事件,并触发 Reconcile 调谐函数作为响应,整个调整过程被称作 "Reconcile Loop"(调谐循环) 或者 "Sync Loop"(同步循环)。Reconcile 是一个使用资源对象的命名空间和资源对象名称来调用的函数,使得资源对象的实际状态与 资源清单中定义的状态保持一致。调用完成后,Reconcile 会将资源对象的状态更新为当前实际状态。我们可以用下面的一段伪代码
来表示这个过程:
for {
desired := getDesiredState() // 期望的状态
current := getCurrentState() // 当前实际状态
if current == desired { // 如果状态一致则什么都不做
} else { // 如果状态不一致则调整编排,到一致为止
// 调整编排
}
}
这个编排模型就是 Kubernetes 项目中的一个通用编排模式,即:控制循环(control loop)
每个控制器对象运行一个调谐循环负责状态同步,并将目标资源对象的当前状态写入到其 status 字段中。
实现调谐功能是依靠的 Kubernetes 实现的核心机制之一的 List-Watch,在资源对象的状态发生变动时,由 APServer 负责写入 etcd 并通过水平触发机制主动通知给相关的客户端程序以确保其不会错过任何一个事件。控制器通过API Server 的 Watch 接口实时监控目标资源对象的变动并执行调谐操作,但并不会与其他控制器进行任何交互
我们有一些后端的 Pod 为集群中的其他应用提供 API 服务,如果我们在前端应用中把所有的这些后端的 Pod 的地址都写死,然后以某种方式去(比如轮询方式)访问其中一个 Pod 的服务,这样看上去是可以工作的,但是如果这个 Pod 挂掉了,然后重新启动起来了,是不是 IP 地址非常有可能就变了,这个时候前端就极大可能访问不到后端的服务了。这时候就需要实现一个服务发现的工具,当我们 Pod 被销毁或者新建过后,我们可以把这个 Pod 的地址注册到这个服务发现中心去就可以,但是这样的话我们的前端应用就不能直接去连接后台的 Pod 集合了,应该连接到一个能够做服务发现的中间件上面。为解决这个问题 Kubernetes 就为我们提供了这样的一个对象 - Service,Service 是一种抽象的对象,它定义了一组 Pod 的逻辑集合和一个用于访问它们的策略,
例如
Service 的这种抽象就可以帮我们达到解耦的目的。
Service 存在的意义
Service 主要功能
在正常情况下,Pod 不重建,ip 是不会变化的,但更新镜像版本时,Pod 会被重建。
Pod 与 Service 的关系
Kubernetes 系统中的三种 IP
首先,Node IP 是 Kubernetes 集群中节点的物理网卡 IP 地址(一般为内网),所有属于这个网络的服务器之间都可以直接通信,所以 Kubernetes 集群外要想访问 Kubernetes 集群内部的某个节点或者服务,肯定得通过 Node IP 进行通信。然后 Pod IP 是每个 Pod 的 IP 地址,它是网络插件进行分配的。最后 Cluster IP 是一个虚拟的 IP,仅仅作用于 Kubernetes Service 这个对象,由 Kubernetes 自己来进行管理和分配地址。
Kind 部署 1master 2worker(v1.23) 集群。
目的是为了确认 Service 是否成功地将流量负载均衡到了后端的 Pod 上。
创建相关资源
kubectl apply -f nginx-svc-demo.yaml
# nginx-svc-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: nginx
ports:
- protocol: TCP
containerPort: 80 # 容器内暴露的端口,这里只是一个标识,真正起作用的还是容器里服务的端口号
name: nginx-http
---
# 如果有一组 Pod 服务,它们对外暴露了 8080 端口,同时都被打上了 app=myapp 这样的标签,
# 那么我们就可以像下面这样来定义一个 Service 对象,
# myservice 会将请求代理到使用 TCP 端口为 8080 并具有标签 app=myapp 的 Pod 上
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
selector:
app: web
type: ClusterIP # 服务类型,默认就是 ClusterIP
ports:
- protocol: TCP
port: 8080 # 如果没有配置 targetPort,则这里的 port 要和容器内暴露的端口保持一致
targetPort: 80 # 一定要和容器内应用暴露的端口保持一致
name: myapp-http
查看Service
kubectl get svc -owide
查看Endpoint 将显示每个服务背后的端点(Pod 的 IP 地址和端口),一旦服务中的 Pod 集合发生更改,Endpoints 就会被更新。
kubectl get ep -owide
分别访问下 PodIP:80 和 SvcIP:8080
#进入 master 节点
docker exec -it demo-control-plane /bin/bash
#访问 pod
curl 10.244.1.10:80 | grep "<p><em>"
#访问Service
curl 10.96.60.89:8080 | grep "<p><em>"
当我们访问svcIP这个Nginx时,就会负载均衡到这3个pod的nginx上面
集群内部使用,通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的服务类型。
对外暴露应用,通过每个 Node 节点上的 IP 和静态端口(NodePort)暴露服务。通过请求 NodeIp:NodePort 可以从集群的外部访问一个 NodePort 服务。
什么是调度?
Pod 是 Kubernetes 中最小的调度单元,而 Pod 又是运行在 Node 之上的。所谓调度,简单来说就是为一个新创建出来的 Pod,寻找一个最适合它运行的 Node。
kube-scheduler 是 kubernetes 的核心组件之一,主要负责整个集群资源的调度功能,根据特定的调度算法和策略,将 Pod 调度到最优的工作节点上面去,从而更加合理、更加充分的利用集群的资源。
kube-scheduler 是默认调度器,它负责分配调度 Pod 到集群内的节点上,它监听 kube-apiserver,查询还未分配 Node 的 Pod,然后根据调度策略为这些 Pod 分配节点。
环境:kind 搭建的 1master 2worker 集群
用于将 Pod 调度到匹配 Label 的 Node 上,如果没有匹配的标签会调度失败,可以约束 Pod 到特定的节点运行,需要完全匹配节点标签。
label 标签是 kubernetes 中一个非常重要的概念,用户可以非常灵活的利用 label 来管理集群中的资源,比如最常见的 Service 对象通过 label 去匹配 Pod 资源,而 Pod 的调度也可以根据节点的 label 来进行调度。
# 查看 node 的 label
kubectl get node --show-labels
# 给节点 demo-worker2 增加一个 app=test 的标签
kubectl label nodes demo-worker2 app=test
kubectl get node demo-worker2 --show-labels
# 删除标签
kubectl label nodes demo-worker2 app-
当节点被打上了相关标签后,在调度的时候就可以使用这些标签了,只需要在 Pod 的 spec 字段中添加 nodeSelector 字段,里面是我们需要被调度的节点的 label 标签,比如下面的 Pod 要强制调度到 worker2 这个节点上去,我们就可以使用 nodeSelector 来表示了,
apiVersion: v1
kind: Pod
metadata:
labels:
app: busybox-pod
name: test-busybox
spec:
containers:
- command:
- sleep
- "3600"
image: busybox
imagePullPolicy: Always
name: test-busybox
nodeSelector: # 注意:nodeSelector 和 containers 同级,必须放在 containers 定义之后
app: test
kubectl apply -f 01-node-selector-demo.yaml
kubectl get po -owide
# 查看 Pod 的创建情况, Events 下面的信息
kubectl describe po test-busybox
如果 pod 中 nodeSelector 里的标签未出现在所有节点上,但后续给 node 打好符合要求的标签,原来处于pending 状态的 pod 会自动迁移到该节点上。
nodeSelector 属于强制性的,如果我们的目标节点没有可用的资源,我们的 Pod 就会一直处于 Pending 状态。
通过上面的例子我们可以感受到 nodeSelector 的方式比较直观,但是还不够灵活,控制粒度偏大。
指定节点名称,用于将 Pod 调度到指定的 Node 上。如果在 pod 里指定了 nodeName 字段,那么即使该 pod没有配置污点容忍,也会被强制调度到指定的节点上的。
apiVersion: v1
kind: Pod
metadata:
name: pod-example
labels:
app: nginx
spec:
nodeName: node1 # 指定Pod调度到node1节点
containers:
- name: nginx
image: nginx:latest # 建议显式指定镜像版本
ports:
- containerPort: 80 # 建议声明容器端口
resources: # 建议添加资源限制(生产环境必需)
limits:
cpu: "500m"
memory: "512Mi"
requests:
cpu: "100m"
memory: "128Mi"
nodeAffinity(节点亲和性)、podAffinity(pod 亲和性)、podAntiAffinity(pod 反亲和性)
亲和性调度可以分成软策略和硬策略两种方式:
节点亲和性主要是用来控制 Pod 要部署在哪些节点上,以及不能部署在哪些节点上的,它可以进行一些简单的逻辑组合了,不只是简单的相等匹配。
kubectl label nodes demo-worker2 app=test
apiVersion: apps/v1
kind: Deployment
metadata:
name: node-affinity
labels:
app: node-affinity
spec:
replicas: 5
selector:
matchLabels:
app: node-affinity
template:
metadata:
labels:
app: node-affinity
spec:
containers:
- name: nginx
image: nginx:latest # 建议显式指定版本
ports:
- containerPort: 80
name: nginxweb
affinity:
node:
# 硬策略:必须满足的条件
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: NotIn
values:
- demo-control-plane # 禁止调度到控制平面节点
# 软策略:优先满足的条件
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1 # 权重值(1-100)
preference:
matchExpressions:
- key: app
operator: In
values:
- test # 优先调度到带有 app=test 标签的节点
kubectl apply -f node-affinty-demo.yaml
kubectl get pods -l app=node-affinity -o wide
kubectl delete Deployment node-affinity
apiVersion: v1
kind: Pod
metadata:
name: my-busybox-pod
labels:
app: busybox-pod
spec:
containers:
- command:
- sleep
- "3600"
image: busybox:latest # 建议显式指定镜像版本
imagePullPolicy: Always
name: test-busybox
resources: # 建议添加资源限制(生产环境必需)
limits:
cpu: "100m"
memory: "128Mi"
requests:
cpu: "50m"
memory: "64Mi"
下面三个 pod 会调度到 my-busybox-pod 所在的节点上。
apiVersion: apps/v1
kind: Deployment
metadata:
name: pod-affinity
labels:
app: pod-affinity
spec:
replicas: 3
selector:
matchLabels:
app: pod-affinity
template:
metadata:
labels:
app: pod-affinity
spec:
containers:
- name: nginx
image: nginx:latest # 建议显式指定版本
ports:
- containerPort: 80
name: nginxweb
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
- labelSelector:
matchExpressions:
- key: app
operator: In # 修正操作符缩进
values:
- busybox-pod # 必须与存在 app=busybox-pod Pod 的节点共同运行
topologyKey: kubernetes.io/hostname # 根据主机名进行拓扑约束
Pod 反亲和性 和 pod 亲和性 则是反着来的,比如一个节点上运行了某个 Pod,那么我们的模板 Pod 则不希望被调度到这个节点上面去了。
apiVersion: apps/v1
kind: Deployment
metadata:
name: pod-antiaffinity
labels:
app: pod-antiaffinity
spec:
replicas: 3
selector:
matchLabels:
app: pod-antiaffinity
template:
metadata:
labels:
app: pod-antiaffinity
spec:
containers:
- name: nginx
image: nginx:latest # 显式指定镜像版本
ports:
- containerPort: 80
name: nginxweb
resources: # 添加资源限制(推荐)
limits:
cpu: "500m"
memory: "512Mi"
requests:
cpu: "100m"
memory: "128Mi"
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬性反亲和规则
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- busybox-pod # 避免调度到已有 app=busybox-pod Pod 的节点
topologyKey: kubernetes.io/hostname # 按节点主机名判断拓扑域
Taints(污点):避免Pod调度到特定Node上
Tolerations(污点容忍):允许Pod调度到持有Taints的Node上
其中 Taint 应用于 Node 上,而 Toleration 则应用于 Pod 上。
如果一个节点标记为 Taints,除非 Pod 也被标识为可以容忍污点节点,否则该 Taints 节点不会被调度 Pod。
比如用户希望把 Master 节点保留给 Kubernetes 系统组件使用,或者把一组具有特殊资源预留给某些 Pod,则污点就很有用了,Pod 不会再被调度到 taint 标记过的节点。某些工具搭建的集群默认就给 master 节点添加了一个污点标记,所以我们看到我们平时的 Pod 都没有被调度到 master 上去。
污点其实是一个label标签,只不过它是一个特殊的label标签