深入掌握POD
1. Pod 定义详解
YAML格式的Pod定义文件的完整内容如下:
apiVersion: v1 # 必选,版本号,例如v1
kind: Pod # 必选,Pod
metadata: # 必选,元数据
name: string # 必选,Pod名称
namespace: string # 必选,Pod所属的命名空间
labels: # 自定义标签
- name: string # 自定义标签名字
annotations: # 自定义注释列表
- name: string
spec: # 必选,Pod中容器的详细定义
containers: # 必选,Pod中容器列表
- name: string # 必选,容器名称
image: string # 必选,容器的镜像名称
imagePullPolicy: [Always | Never | IfNotPresent]# 镜像拉取的策略,默认为Always:Alawys 表示每次都尝试重新拉取镜像;IfNotPresent 表示如果本地有该镜像,则使用本地镜像,本地不存在时拉取镜像;Nerver表示仅使用本地镜像
command: [string] # 容器的启动命令列表,如不指定,使用打包时使用的启动命令
args: [string] # 容器的启动命令参数列表
workingDir: string # 容器的工作目录
volumeMounts: # 挂载到容器内部的存储卷配置
- name: string # 引用pod定义的共享存储卷的名称,需用volumes[]部分定义的的卷名
mountPath: string # 存储卷在容器内mount的绝对路径,应少于512字符
readOnly: boolean # 是否为只读模式,默认为读写模式
ports: # 需要暴露的端口库号列表
- name: string # 端口号名称
containerPort: int # 容器需要监听的端口号
hostPort: int # 容器所在主机需要监听的端口号,默认与Container相同。
# 当设置hostPort时,同一台宿主机将无法启动该容器的第二个副本
protocol: string # 端口协议,支持TCP和UDP,默认TCP
env: # 容器运行前需设置的环境变量列表
- name: string # 环境变量名称
value: string # 环境变量的值
resources: # 资源限制和请求的设置
limits: # 资源限制的设置
cpu: string # Cpu的限制,单位为core数,将用于docker run --cpu-shares参数
memory: string # 内存限制,单位可以为Mib/Gib,将用于docker run --memory参数
requests: # 资源请求的设置
cpu: string # Cpu请求,容器启动的初始可用数量
memory: string # 内存清楚,容器启动的初始可用数量
livenessProbe:# 对Pod内个容器健康检查的设置,当探测无响应几次后将自动重启该容器;检查方法有exec、httpGet和tcpSocket,对一个容器只需设置其中一种方法即可
exec: # 对Pod容器内检查方式设置为exec方式
command: [string] # exec方式需要制定的命令或脚本
httpGet: # 对Pod内个容器健康检查方法设置为HttpGet,需要制定Path、port
path: string
port: number
host: string
scheme: string
HttpHeaders:
- name: string
value: string
tcpSocket: # 对Pod内个容器健康检查方式设置为tcpSocket方式
port: number
initialDelaySeconds: 0 # 容器启动完成后首次探测的时间,单位为秒
timeoutSeconds: 0 # 对容器健康检查探测等待响应的超时时间,单位秒,默认1秒
periodSeconds: 0 # 对容器监控检查的定期探测时间设置,单位秒,默认10秒一次
successThreshold: 0
failureThreshold: 0
securityContext:
privileged: false
restartPolicy: [Always | Never | OnFailure]# Pod的重启策略: Always表示不管以何种方式终止运行,kubelet都将重启;OnFailure表示只有Pod以非0退出码退出才重启; Nerver表示不再重启该Pod
nodeSelector: obeject # 设置NodeSelector表示将该Pod调度到包含这个label的node上,以key:value的格式指定
imagePullSecrets: # Pull镜像时使用的secret名称,以key:secretkey格式指定
- name: string
hostNetwork: false # 是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络
volumes: # 在该pod上定义共享存储卷列表
- name: string # 共享存储卷名称 (volumes类型有很多种)
emptyDir: {} # 类型为emtyDir的存储卷,与Pod同生命周期的一个临时目录。为空值
hostPath: string # 类型为hostPath的存储卷,表示挂载Pod所在宿主机的目录
path: string # Pod所在宿主机的目录,将被用于同期中mount的目录
secret: # 类型为secret的存储卷,挂载集群与定义的secre对象到容器内部
scretname: string
items:
- key: string
path: string
configMap: # 类型为configMap的存储卷,挂载预定义的configMap对象到容器内部
name: string
items:
- key: string
path: string
Pod YAML定义文件详解
2. Pod 的基本用法
Pod可以由1个或多个容器组合而成。举例说明:
Pod由一个容器组成YAML文件:
以下文件启动后将生成一个名为“frontend”的Pod,该Pod中包含一个容器。
# cat frontend-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: frontend
labels:
name: frontend
spec:
containers:
- name: frontend
image: ygego/php-frontend
env:
- name: GET_HOSTS_FROM
value: env
ports:
- containerPort: 80
另一个场景是:假如frontend 和 Redis 两个容器应用为紧耦合的关系,并且组合成一个整体对外提供服务时,应该将这两个容器打包为一个Pod,属于同一个Pod的多个容器应用之间相互访问时仅需要通过localhost就可以通信。如图:
Pod由两个个容器组成YAML文件:
# cat frontend-redis-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: php-redis
labels:
name: php-redis
spec:
containers:
- name: frontend
image: ygego/php-frontend
env:
- name: GET_HOSTS_FROM
value: env
ports:
- containerPort: 80
- name: redis
image: ygego/redis-master
ports:
- containerPort: 6379
使用Kubectl 创建该Pod:
# kubectl apply -f frontend-redis-pod.yaml
pod/php-redis created
查看创建好的Pod:
# kubectl get pods
NAME READY STATUS RESTARTS AGE
php-redis 2/2 running 0 1h
可以看到READY信息为2/2,表示Pod中的两个容器都成功的运行起来了。
查看Pod的详细信息:
可以看到两个容器的定义以及创建的过程(Event事件信息)
# kubectl describe pod php-redis
Name: php-redis
Namespace: default
Priority: 0
Node: k8s-node03/10.4.148.228
Start Time: Tue, 07 Jan 2020 17:12:12 +0800
Labels: name=php-redis
Annotations: cni.projectcalico.org/podIP: 10.100.7.161/32
kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"labels":{"name":"php-redis"},"name":"php-redis","namespace":"default"},"spec...
Status: Running
IP: 10.100.7.161
Containers:
frontend:
Container ID: docker://7b40ddbaed450da56f4ae9aeb7ddc46a1da0e5630327b41973718a075b805cd5
Image: ygego/php-frontend
Image ID: docker://sha256:d014f67384a11186e135b95a7ed0d794674f7ce258f0dce47267c3052a0d0fa9
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Tue, 07 Jan 2020 17:12:15 +0800
Ready: True
Restart Count: 0
Environment:
GET_HOSTS_FROM: env
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-c6t97 (ro)
redis:
Container ID: docker://3483cbd386fa788302e6d6b1d6bace0233e8a972dfcc14b9a2bb131320d36737
Image: ygego/redis-master
Image ID: docker://sha256:405a0b586f7ebeb545ec65be0e914311159d1baedccd3a93e9d3e3b249ec5cbd
Port: 6379/TCP
Host Port: 0/TCP
State: Running
Started: Tue, 07 Jan 2020 17:12:16 +0800
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-c6t97 (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
default-token-c6t97:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-c6t97
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s
node.kubernetes.io/unreachable:NoExecute for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 17s default-scheduler Successfully assigned default/php-redis to k8s-node03
Normal Pulled 15s kubelet, k8s-node03 Container image "ygego/php-frontend" already present on machine
Normal Created 15s kubelet, k8s-node03 Created container frontend
Normal Started 14s kubelet, k8s-node03 Started container frontend
Normal Pulled 14s kubelet, k8s-node03 Container image "ygego/redis-master" already present on machine
Normal Created 14s kubelet, k8s-node03 Created container redis
Normal Started 13s kubelet, k8s-node03 Started container redis
3. Pod 容器共享Volume
同一个Pod中的多个容器能够共享Pod级别的存储卷Volume。Volume可以被定义为各种类型,多个容器各自进行挂载操作,将一个Volume挂载为容器内部需要的目录,如图所示:
如下图所示的例子中,在Pod中包含两个容器: Tomcat 和 busybox,在Pod级别设置Volume:“app-logs”,用于 Tomcat向其中写入日志文件,busybox 读取日志文件。
这里设置的Volume名为app-logs,类型为emptyDir,挂载在Tomcat容器内的/usr/local/tomcat/logs目录,同时挂载到busybox容器内的/logs目录。Tomcat容器启动后会向/usr/local/tomcat/logs目录写入文件,busybox容器就可以读取其中的文件了,busybox 容器的启动命令为“tail -f /logs/catalina*.log”。
# cat pod-volume-applogs.yaml
apiVersion: v1
kind: Pod
metadata:
name: volume-pod
spec:
containers:
- name: tomcat
image: tomcat
imagePullPolicy: Never
ports:
- containerPort: 8080
volumeMounts:
- name: app-logs
mountPath: /usr/local/tomcat/logs
- name: busybox
image: busybox
imagePullPolicy: Never
command: ["sh", "-c", "tail -f /logs/catalina*.log"]
volumeMounts:
- name: app-logs
mountPath: /logs
volumes:
- name: app-logs
emptyDir: {}
使用Kubectl 创建该Pod:
# kubectl apply -f pod-volume-applogs.yaml
pod/volume-pod created
查看创建好的Pod:
# kubectl get pods
NAME READY STATUS RESTARTS AGE
volume-pod 2/2 Running 0 2m13s
登录Tomcat容器查看生成的日志文件:
# kubectl exec -it volume-pod -c tomcat -- ls /usr/local/tomcat/logs
catalina.2020-01-07.log host-manager.2020-01-07.log localhost.2020-01-07.log localhost_access_log.2020-01-07.txt manager.2020-01-07.log
查看Tomcat日志中的内容:
# kubectl exec -it volume-pod -c tomcat -- tail /usr/local/tomcat/logs/catalina.2020-01-07.log
07-Jan-2020 09:30:43.227 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
07-Jan-2020 09:30:43.237 INFO [main] org.apache.tomcat.util.net.NioSelectorPool.getSharedSelector Using a shared selector for servlet write/read
07-Jan-2020 09:30:43.250 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["ajp-nio-8009"]
07-Jan-2020 09:30:43.251 INFO [main] org.apache.tomcat.util.net.NioSelectorPool.getSharedSelector Using a shared selector for servlet write/read
07-Jan-2020 09:30:43.251 INFO [main] org.apache.catalina.startup.Catalina.load Initialization processed in 751 ms
07-Jan-2020 09:30:43.274 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
07-Jan-2020 09:30:43.274 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet Engine: Apache Tomcat/8.5.50
07-Jan-2020 09:30:43.288 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
07-Jan-2020 09:30:43.297 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-nio-8009"]
07-Jan-2020 09:30:43.300 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 48 ms
使用 kubectl logs 命令查看busybox容器的输出内容:
# kubectl logs volume-pod -c busybox
07-Jan-2020 09:30:43.227 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
07-Jan-2020 09:30:43.237 INFO [main] org.apache.tomcat.util.net.NioSelectorPool.getSharedSelector Using a shared selector for servlet write/read
07-Jan-2020 09:30:43.250 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["ajp-nio-8009"]
07-Jan-2020 09:30:43.251 INFO [main] org.apache.tomcat.util.net.NioSelectorPool.getSharedSelector Using a shared selector for servlet write/read
07-Jan-2020 09:30:43.251 INFO [main] org.apache.catalina.startup.Catalina.load Initialization processed in 751 ms
07-Jan-2020 09:30:43.274 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
07-Jan-2020 09:30:43.274 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet Engine: Apache Tomcat/8.5.50
07-Jan-2020 09:30:43.288 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
07-Jan-2020 09:30:43.297 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-nio-8009"]
07-Jan-2020 09:30:43.300 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 48 ms
可以看到,busybox容器的日志内容就是Tomcat容器的日志内容。
4. Pod 的配置管理
应用部署的一个最佳实践是将应用所需的配置信息与程序进行分离,这样可以使应用程序被更好地复用,通过不同的配置也能实现更灵活的功能。将应用打包为容器镜像后,可以通过环境变量或者外挂文件的方式在创建容器时进行配置注入,但在大规模容器集群的环境中,对多个容器进行不同的配置将变得非常复杂。Kubernetes 从 1.2 版本开始提供了一种统一的应用配置管理方案---ConfigMap。
4.1 ConfigMap 概述
ConfigMap 供容器使用的典型用法如下:
- 生成为容器内的环境变量
- 设置容器启动命令的启动参数(需设置为环境变量)
- 以Volume的形式挂载为容器内部的文件或目录
ConfigMap以一个或多个"Key:Value"的形式保存在 Kubernetes 系统中给应用使用,既可以用于表示一个变量的值(例如:loglevel=info),也可以用于表示一个完整配置文件的内容(例如:server.xml=<?xml...>...)
可以通过YAML配置文件或者直接使用"kubectl create configmap"命令行的方式来创建ConfigMap。
4.2 创建ConfigMap资源对象
4.2.1. 通过YAML配置文件方式创建
下面的文件描述了将应用所需的变量定义为ConfigMap的配置:
# cat cm-appvars.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-appvars
data:
apploglevel: info
appdatadir: /var/data
使用Kubectl 创建该ConfigMap:
# kubectl apply -f cm-appvars.yaml
configmap/cm-appvars created
查看创建的ConfigMap:
# kubectl get configmap
NAME DATA AGE
cm-appvars 2 18s
# kubectl describe configmap cm-appvars
Name: cm-appvars
Namespace: default
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","data":{"appdatadir":"/var/data","apploglevel":"info"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"cm-appva...
Data
====
appdatadir:
----
/var/data
apploglevel:
----
info
Events: <none>
# kubectl get configmap -o yaml
apiVersion: v1
items:
- apiVersion: v1
data:
appdatadir: /var/data
apploglevel: info
kind: ConfigMap
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"appdatadir":"/var/data","apploglevel":"info"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"cm-appvars","namespace":"default"}}
creationTimestamp: "2020-01-07T11:28:13Z"
name: cm-appvars
namespace: default
resourceVersion: "1896678"
selfLink: /api/v1/namespaces/default/configmaps/cm-appvars
uid: b93e6852-4b51-49c7-ac12-5a0833d04025
kind: List
metadata:
resourceVersion: ""
selfLink: ""
4.2.2 通过Kubectl命令行方式创建
通过命令行“kubectl create configmap“创建ConfigMap,可以使用参数“--from-file“或“--from-literal“指定内容,并且可以在一行命令中指定多个参数。
准备配置文件
# cat /root/configmap/cm1.cfg
env=dev1
app.name=cm1
log.level=info
# cat /root/configmap/cm2.cfg
env=alpha
app.name=cm2
log.level=debug
1、从文件创建(--from-file)
通过--from-file参数从文件中进行创建,可以指定key的名称,也可以在一个命令行中创建包含多个key的ConfigMap。
语法:
# kubectl create configmap NAME --from-file=[key=]CONFIG_FILE
创建ConfigMap
# kubectl create configmap cm-test --from-file=/root/configmap/cm1.cfg --from-file=/root/configmap/cm2.cfg
configmap/cm-test created
查看创建的ConfigMap
# kubectl get configmap
NAME DATA AGE
cm-test 2 8s
# kubectl describe configmap cm-test
Name: cm-test
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
cm1.cfg:
----
env=dev1
app.name=cm1
log.level=info
cm2.cfg:
----
env=alpha
app.name=cm2
log.level=debug
Events: <none>
# kubectl get configmap cm-test -o yaml
apiVersion: v1
data:
cm1.cfg: |
env=dev1
app.name=cm1
log.level=info
cm2.cfg: |
env=alpha
app.name=cm2
log.level=debug
kind: ConfigMap
metadata:
creationTimestamp: "2020-01-07T12:37:25Z"
name: cm-test
namespace: default
resourceVersion: "1903226"
selfLink: /api/v1/namespaces/default/configmaps/cm-test
uid: a03188d3-f0d1-437c-b0ce-b95bf8d3520f
创建时指定Data中的Key
# kubectl create configmap cm-test1 --from-file=cm-test1=/root/kube/kube-book/configmap/cm1.cfg
configmap/cm-test1 created
# kubectl describe configmap cm-test1
Name: cm-test1
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
cm-test1:
----
env=dev1
app.name=cm1
log.level=info
Events: <none>
2、从目录创建(--from-file)
通过--from-file参数从目录中进行创建,该目录下的每个配置文件名都被设置为key,文件的内容被设置为value。
语法为:
# kubectl create configmap NAME --from-file=CONFIG_FILE_DIR
创建ConfigMap
# kubectl create configmap cm-test2 --from-file=/root/configmap/
configmap/cm-test2 created
查看创建的ConfigMap
# kubectl get configmap
NAME DATA AGE
cm-test2 2 6s
# kubectl describe configmap cm-test2
Name: cm-test2
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
cm1.cfg:
----
env=dev1
app.name=cm1
log.level=info
cm2.cfg:
----
env=alpha
app.name=cm2
log.level=debug
Events: <none>
# kubectl get configmap cm-test2 -o yaml
apiVersion: v1
data:
cm1.cfg: |
env=dev1
app.name=cm1
log.level=info
cm2.cfg: |
env=alpha
app.name=cm2
log.level=debug
kind: ConfigMap
metadata:
creationTimestamp: "2020-01-07T12:45:26Z"
name: cm-test2
namespace: default
resourceVersion: "1903985"
selfLink: /api/v1/namespaces/default/configmaps/cm-test2
uid: 72983c0c-df9a-42de-8d3a-7cabbb348fe2
3、指定键值创建(--from-literal)
通过 --from-literal 时会从文本中进行创建,直接将指定的 key=value 创建为ConfigMap的内容。
语法为:
# kubectl creata configmap NAME --from-literal=KEY1=VALUE1 --from-literal=KEY2=VALUE2
创建ConfigMap
# kubectl create configmap mysql-env --from-literal=mysql.user=admin --from-literal=mysql.passwd=admin@123
configmap/mysql-env created
查看创建的ConfigMap
# kubectl get configmap
NAME DATA AGE
cm-test 2 14h
cm-test1 1 14h
cm-test2 2 14h
mysql-env 2 54s
# kubectl describe configmap mysql-env
Name: mysql-env
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
mysql.passwd:
----
admin@123
mysql.user:
----
admin
Events: <none>
# kubectl get configmap mysql-env -o yaml
apiVersion: v1
data:
mysql.passwd: admin@123
mysql.user: admin
kind: ConfigMap
metadata:
creationTimestamp: "2020-01-08T03:03:45Z"
name: mysql-env
namespace: default
resourceVersion: "1985164"
selfLink: /api/v1/namespaces/default/configmaps/mysql-env
uid: 3268d53e-35b4-41ed-9820-b7afcaedb10f
4.3 在Pod 中使用ConfigMap
Pod中的容器应用使用ConfigMap有两种方法:
- 通过环境变量获取ConfigMap中内容
- 通过Volume挂载的方式将ConfigMap中的内容挂载为容器内部的文件或目录
4.3.1 通过环境变量方式使用ConfigMap
以上面创建的ConfigMap“mysql-env”为例,该ConfigMap中有两个键值对:mysql.user=admin 、mysql.passwd=admin@123
# kubectl get configmap mysql-env -o yaml
apiVersion: v1
data:
mysql.passwd: admin@123
mysql.user: admin
kind: ConfigMap
metadata:
creationTimestamp: "2020-01-08T03:03:45Z"
name: mysql-env
namespace: default
resourceVersion: "1985164"
selfLink: /api/v1/namespaces/default/configmaps/mysql-env
uid: 3268d53e-35b4-41ed-9820-b7afcaedb10f
通过定义文件“cm-test-pod.yaml”创建Pod,将ConfigMap“mysql-env”中的内容以环境变量方式设置为容器内部的环境变量。
# cat cm-test-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: cm-test-pod
spec:
containers:
- name: cm-pod
image: busybox
imagePullPolicy: Never
command: ["/bin/sh", "-c", "sleep 3600"]
env:
- name: MYSQL_USER # 定义容器内环境变量名称:MYSQL_USER
valueFrom:
configMapKeyRef:
name: mysql-env # “MYSQL_USER”的值取自“mysql-env”
key: mysql.user # MYSQL_USER对应的key为“mysql.user”
- name: MYSQL_PASSWD # 定义容器内环境变量名称:MYSQL_PASSWD
valueFrom:
configMapKeyRef:
name: mysql-env # “MYSQL_PASSWD”的值取自“mysql-env”
key: mysql.passwd # MYSQL_PASSWD对应的key为“mysql.passwd”
restartPolicy: Never
执行Kubectl创建POD
# kubectl apply -f cm-test-pod.yaml
pod/cm-test-pod created
# kubectl get pods
NAME READY STATUS RESTARTS AGE
cm-test-pod 1/1 Running 0 10s
进入POD内的容器查看环境变量
# 使用 kubectl exec 进入POD内部容器,执行命令“env |grep MYSQL”查看MYSQL相关环境变量
# kubectl exec -it cm-test-pod env |grep MYSQL
MYSQL_USER=admin
MYSQL_PASSWD=admin@123
可以看到POD内部容器的环境变量已经设置成ConfigMap“mysql-env”中的值。
Kubernetes从1.6版本开始,引入一个新的字段“envFrom”,实现了在POD环境中将ConfigMap(也可用于Secret资源)中所定义的 key=value自动生成为环境变量。
示例如下:
# cat cm-envfrom-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: cm-envfrom-pod
spec:
containers:
- name: cm-envfrom
image: busybox
imagePullPolicy: Never
command: ["/bin/sh", "-c", "sleep 3600"]
envFrom:
- configMapRef:
name: mysql-env # 根据mysql-env中的key=value自动生成环境变量
restartPolicy: Never
# kubectl apply -f cm-envfrom-pod.yaml
pod/cm-envfrom-pod created
# kubectl get pods
NAME READY STATUS RESTARTS AGE
cm-envfrom-pod 1/1 Running 0 25s
# kubectl exec -it cm-envfrom-pod env |grep mysql
mysql.passwd=admin@123
mysql.user=admin
需要注意的是,环境变量的名称需要遵循格式" [a-zA-Z_][a-zA-Z0-9_]*",不能以数字开头。如果包含非法字符,则系统将跳过该条环境变量的创建,并记录一个Event来提示环境变量无法生成,但并不会阻止Pod的启动。
4.3.2 通过volumeMount使用ConfigMap
准备变量配置文件
# cat /root/configmap/cm1.cfg
env=dev1
app.name=cm1
log.level=info
# cat /root/configmap/cm2.cfg
env=alpha
app.name=cm2
log.level=debug
创建ConfigMap
# kubectl create configmap cm-volume --from-file=/root/configmap/
configmap/cm-volume created
查看创建好的ConfigMap
# kubectl get cm
NAME DATA AGE
cm-volume 2 5s
# kubectl get cm cm-volume -o yaml
apiVersion: v1
data:
cm1.cfg: |
env=dev1
app.name=cm1
log.level=info
cm2.cfg: |
env=alpha
app.name=cm2
log.level=debug
kind: ConfigMap
metadata:
creationTimestamp: "2020-01-08T07:01:20Z"
name: cm-volume
namespace: default
resourceVersion: "2007689"
selfLink: /api/v1/namespaces/default/configmaps/cm-volume
uid: 4c9545b6-4060-4850-bedf-728e6af38295
创建POD定义文件
# cat cm-volume-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: cm-volume-pod
spec:
containers:
- name: cm-volume
image: busybox
imagePullPolicy: Never
command: ["/bin/sh", "-c", "sleep 3600"]
volumeMounts:
- name: config-volume # 引用Volume的名称
mountPath: /etc/config # 挂载到容器内的目录
volumes:
- name: config-volume # 定义Volume的名称
configMap:
name: cm-volume # 引用的ConfigMap为cm-volume
items:
- key: cm1.cfg # key=cm1.cfg
path: cm1-cfg # cm1.cfg的value将以文件名cm1-cfg进行挂载
- key: cm2.cfg # ey=cm2.cfg
path: cm2-cfg # cm2.cfg的value将以文件名cm2-cfg进行挂载
根据定义文件生产Pod
# kubectl apply -f cm-volume-pod.yaml
pod/cm-volume-pod created
进入POD容器内部查看挂载配置文件
# kubectl get pods
NAME READY STATUS RESTARTS AGE
cm-volume-pod 1/1 Running 0 17s
# kubectl exec -it cm-volume-pod /bin/sh
/ # cd /etc/config/
/etc/config # ls
cm1-cfg cm2-cfg
/etc/config # cat cm1-cfg
env=dev1
app.name=cm1
log.level=info
/etc/config # cat cm2-cfg
env=alpha
app.name=cm2
log.level=debug
/etc/config #
执行“kubectl exec”登录POD内部容器,切换到/etc/config目录下,可以看到存在cm1-cfg和cm2-cfg两个文件,它们的内容就是ConfigMap“cm-volume”中两个key定义的内容。
如果在引用configMap时不指定items,POD内部容器在使用volumeMount挂载ConfigMap时,会根据ConfigMap中数据(data)条目(item)的数量,在容器挂载目录下为每个条目生成一个以条目名称为文件名的文件。下面举例说明:
创建POD定义文件
# cat cm-volume-pod2.yaml
apiVersion: v1
kind: Pod
metadata:
name: cm-volume-pod2
spec:
containers:
- name: cm-volume2
image: busybox
imagePullPolicy: Never
command: ["/bin/sh", "-c", "sleep 3600"]
volumeMounts:
- name: config-volume2 # 引用Volume的名称
mountPath: /etc/config # 挂载到容器内的目录
volumes:
- name: config-volume2 # 定义Volume的名称
configMap:
name: cm-volume # 引用的ConfigMap为cm-volume
根据定义文件生产Pod
# kubectl apply -f cm-volume-pod2.yaml
pod/cm-volume-pod2 created
进入POD容器内部查看挂载配置文件
# kubectl get pods
NAME READY STATUS RESTARTS AGE
cm-volume-pod2 1/1 Running 0 113s
# kubectl exec -it cm-volume-pod2 /bin/sh
/ # cd /etc/config/
/etc/config # ls
cm1.cfg cm2.cfg
/etc/config # cat cm1.cfg
env=dev1
app.name=cm1
log.level=info
/etc/config # cat cm2.cfg
env=alpha
app.name=cm2
log.level=debug
/etc/config #
执行“kubectl exec”登录POD内部容器,切换到/etc/config目录下,可以看到存在cm1.cfg和cm2.cfg两个文件,它们的内容就是ConfigMap“cm-volume”中两个key定义的内容。
4.3.3 使用ConfigMap的限制条件
使用ConfigMap的限制条件如下:
- ConfigMap必须在Pod之前创建。
- ConfigMap受Namespace限制,只有处于相同Namespace中的Pod才可以引用它。
- ConfigMap中定义的key的值都是明文的,对于一些密码类型的值不安全。
- 在Pod对ConfigMap进行挂载(volumeMount)操作时,在容器内部只能挂载为“目录”,无法挂载为“文件”。挂载到容器内部后,在目录下将包含ConfigMap定义的每个item,如果在该目录下原来还有其他文件,则容器内的该目录将被挂载的ConfigMap覆盖。如果应用程序需要保留原来的其他文件,则需要进行额外的处理。可以将ConfigMap挂载到容器内部的临时目录,再通过启动脚本将配置文件复制或者链接到(cp或link命令)应用所用的实际配置目录下。
5. 容器内读取Pod的信息(Downward API)
待完善
6. Pod 生命周期和重启策略
Pod在整个生命周期中被系统定义为各种状态,熟悉Pod的各种状态对于理解如何设置Pod的调度策略、重启策略是很有必要的。
Pod的状态如表格所示:
状态值 | 描述 |
---|---|
Pending | API Server 已经创建该Pod,但是在Pod内还有一个或多个容器的镜像没有创建,包括正在下载镜像的过程 |
Running | Pod 内所有容器均已创建,且至少有一个容器处于运行状态、正在启动状态或正在重启状态 |
Succeeded | Pod 内所有容器均成功执行后退出,且不会在重启 |
Failed | Pod 内所有容器均已退出,但至少有一个容器退出为失败状态 |
Unknow | 由于某种原因无法获取该Pod的状态,可能由于网络通信不畅导致 |
Pod的重启策略(RestartPolicy)应用于Pod内的所有容器,并且仅在Pod所处的Node上由kubelet进行判断和重启操作。当某个容器异常退出或者健康检查失败时,kubelet将根据RestartPolicy的设置来进行相应的操作。
Pod的重启策略包括Always、OnFailure和Never,默认值为Always。
- Always:当容器失效时,由kubelet自动重启该容器。
- OnFailure:当容器终止运行且退出码不为0时,由kubelet自动重启该容器。
- Never:不论容器运行状态如何,kubelet都不会重启该容器。
kubelet重启失效容器的时间间隔以sync-frequency乘以2n来计算,例如1、2、4、8倍等,最长延时5min,并且在成功重启后的10min后重置该时间。
Pod的重启策略与控制方式息息相关,当前可用于管理Pod的控制器包括ReplicationController、Job、DaemonSet及直接通过kubelet管理(静态Pod)。
每种控制器对Pod的重启策略要求如下。
- RC和DaemonSet:必须设置为Always,需要保证该容器持续运行。
- Job:OnFailure或Never,确保容器执行完成后不再重启。
- kubelet:在Pod失效时自动重启它,不论将RestartPolicy设置为什么值,也不会对Pod进行健康检查。
结合Pod的状态和重启策略,下面表格列出一些常见的状态转换场景:
7. Pod健康检查和服务可用性检查
Kubernetes 对 Pod 的健康状态可以通过两类探针来检查:LivenessProbe 和ReadinessProbe,Kubelet定期执行这两类探针来诊断容器的健康状况。
- LivenessProbe探针:用于判断容器是否存活(Running状态),如果LivenessProbe探针探测到容器不健康,则kubelet将杀掉该容器,并根据容器的重启策略做相应的处理。如果一个容器不包含LivenessProbe探针,那么kubelet认为该容器的LivenessProbe探针返回的值永远是Success。
- ReadinessProbe探针:用于判断容器服务是否可用(Ready状态),达到Ready状态的Pod才可以接收请求。对于被Service管理的Pod,Service与Pod Endpoint的关联关系也将基于Pod是否Ready进行设置。如果在运行过程中Ready状态变为False,则系统自动将其从Service的后端Endpoint列表中隔离出去,后续再把恢复到Ready状态的Pod加回后端Endpoint列表。这样就能保证客户端在访问Service时不会被转发到服务不可用的Pod实例上。
LivenessProbe和ReadinessProbe均可配置以下三种实现方式。
- ExecAction:在容器内部执行一个命令,如果该命令的返回码为0,则表明容器健康。
- TCPSocketAction:通过容器的IP地址和端口号执行TCP检查,如果能够建立TCP连接,则表明容器健康。
- HTTPGetAction:通过容器的IP地址、端口号及路径调用HTTP Get方法,如果响应的状态码大于等于200且小于400,则认为容器健康。
对于每种探测方式,都需要设置initialDelaySeconds和timeoutSeconds两个参数,它们的含义分别如下:
- initialDelaySeconds:启动容器后进行首次健康检查的等待时间,单位为s。
- timeoutSeconds:健康检查发送请求后等待响应的超时时间,单位为s。当超时发生时,kubelet会认为容器已经无法提供服务,将会重启该容器。
Kubernetes的ReadinessProbe机制可能无法满足某些复杂应用对容器内服务可用状态的判断,所以Kubernetes从1.11版本开始,引入Pod Ready++特性对Readiness探测机制进行扩展,在1.14版本时达到GA稳定版,称其为Pod Readiness Gates。
通过Pod Readiness Gates机制,用户可以将自定义的ReadinessProbe探测方式设置在Pod上,辅助Kubernetes设置Pod何时达到服务可用状态(Ready)。为了使自定义的ReadinessProbe生效,用户需要提供一个外部的控制器(Controller)来设置相应的Condition状态。
8. 玩转Pod调度
在Kubernetes平台上,我们很少会直接创建一个Pod,在大多数情况下会通过RC、Deployment、DaemonSet、Job等控制器完成对一组Pod副本的创建、调度及全生命周期的自动控制任务。
在最早的Kubernetes版本里是没有这么多Pod副本控制器的,只有一个Pod副本控制器RC(Replication Controller),这个控制器是这样设计实现的:RC独立于所控制的Pod,并通过Label标签这个松耦合关联关系控制目标Pod实例的创建和销毁,随着Kubernetes的发展,RC也出现了新的继任者——Deployment,用于更加自动地完成Pod副本的部署、版本更新、回滚等功能。
严谨地说,RC的继任者其实并不是Deployment,而是ReplicaSet,因为 ReplicaSet进一步增强了RC标签选择器的灵活性。之前RC的标签选择器只能选择一个标签,而ReplicaSet拥有集合式的标签选择器,可以选择多个Pod标签,如下所示:
......
selector:
matchLabels:
tier: frontend
matchExpressions:
- {key: tier, operator: In, values: [frontend]}
与RC不同,ReplicaSet被设计成能控制多个不同标签的Pod副本。一种常见的应用场景是,应用MyApp目前发布了v1与v2两个版本,用户希望MyApp的Pod副本数保持为3个,可以同时包含v1和v2版本的Pod,就可以用ReplicaSet来实现这种控制,写法如下:
......
selector:
matchLabels:
version: v2
matchExpressions:
- {key: version, operator: In, values: [v1, v2]}
其实,Kubernetes的滚动升级就是巧妙运用ReplicaSet的这个特性来实现的,同时,Deployment也是通过ReplicaSet来实现Pod副本自动控制功能的。我们不应该直接使用底层的ReplicaSet来控制Pod副本,而应该使用管理ReplicaSet的Deployment对象来控制副本,这是来自官方的建议。
在大多数情况下,我们希望Deployment创建的Pod副本被成功调度到集群中的任何一个可用节点,而不关心具体会调度到哪个节点。但是,在真实的生产环境中的确也存在一种需求:希望某种Pod的副本全部在指定的一个或者一些节点上运行,比如希望将MySQL数据库调度到一个具有SSD磁盘的目标节点上,此时Pod模板中的NodeSelector属性就开始发挥作用了,上述MySQL定向调度案例的实现方式可分为以下两步:
- 把具有SSD磁盘的Node都打上自定义标签“disk=ssd”;
- 在Pod模板中设定NodeSelector的值为“disk: ssd”.
如此一来,Kubernetes在调度Pod副本的时候,就会先按照Node的标签过滤出合适的目标节点,然后选择一个最佳节点进行调度。
上述逻辑看起来既简单又完美,但在真实的生产环境中可能面临以下问题:
- 如果NodeSelector选择的Label不存在或者不符合条件,比如这些目标节点此时宕机或者资源不足,该怎么办?
- 如果要选择多种合适的目标节点,比如SSD磁盘的节点或者超高速硬盘的节点,该怎么办?
为此,Kubernates引入了NodeAffinity(节点亲和性设置)来解决该需求。
在真实的生产环境中还存在如下所述的特殊需求:
- 不同Pod之间的亲和性(Affinity)。比如MySQL数据库与Redis中间件不能被调度到同一个目标节点上,或者两种不同的Pod必须被调度到同一个Node上,以实现本地文件共享或本地网络通信等特殊需求,这就是PodAffinity要解决的问题。
- 有状态集群的调度。对于ZooKeeper、Elasticsearch、MongoDB、Kafka等有状态集群,虽然集群中的每个Worker节点看起来都是相同的,但每个Worker节点都必须有明确的、不变的唯一ID(主机名或IP地址),这些节点的启动和停止次序通常有严格的顺序。此外,由于集群需要持久化保存状态数据,所以集群中的Worker节点对应的Pod不管在哪个Node上恢复,都需要挂载原来的Volume,因此这些Pod还需要捆绑具体的PV。针对这种复杂的需求,Kubernetes提供了StatefulSet这种特殊的副本控制器来解决问题,在Kubernetes 1.9版本发布后,StatefulSet才可用于正式生产环境中。
- 在每个Node上调度并且仅仅创建一个Pod副本。这种调度通常用于系统监控相关的Pod,比如主机上的日志采集、主机性能采集等进程需要被部署到集群中的每个节点,并且只能部署一个副本,这就是DaemonSet这种特殊Pod副本控制器所解决的问题。
- 对于批处理作业,需要创建多个Pod副本来协同工作,当这些Pod副本都完成自己的任务时,整个批处理作业就结束了。这种Pod运行且仅运行一次的特殊调度,用常规的RC或者Deployment都无法解决,所以Kubernates引入了新的Pod调度控制器Job来解决问题,并继续延伸了定时作业的调度控制器CronJob。
与单独的Pod实例不同,由RC、ReplicaSet、Deployment、DaemonSet等控制器创建的Pod副本实例都是归属于这些控制器的,这就产生了一个问题:控制器被删除后,归属于控制器的Pod副本该何去何从?在Kubernates 1.9之前,在RC等对象被删除后,它们所创建的Pod副本都不会被删除;在Kubernates1.9以后,这些Pod副本会被一并删除。如果不希望这样做,则可以通过kubectl命令的--cascade=false参数来取消这一默认特性。
# kubectl delete replicaset my-repset --cascade=false
8.1 全自动调度(Deployment)
Deployment的主要功能之一就是自动部署一个容器应用的多份副本,以及持续监控副本的数量,在集群内始终维持用户指定的副本数量。
以下是一个Deployment的例子,该配置文件将创建一个RepicaSet,该ReplicaSet会创建3个Nginx应用的Pod:
# cat nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
运行kubectl apply 命令创建该Deployment:
# kubectl apply -f nginx-deployment.yaml
deployment/nginx-deployment created
查看Deployment的状态:
- -o wide:查看更详细的信息
# kubectl get deployments -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
nginx-deployment 3/3 3 3 2m22s nginx nginx:1.14 app=nginx
通过以上信息可以看到:Deployment已创建好所有3个副本,并且所有副本都是最新的可用的。
通过运行“kubectl get pods”和“kubectl get rs”查看已经创建的ReplicaSet和Pod的信息。
# kubectl get rs -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
nginx-deployment-6cfc444fb 3 3 3 2m30s nginx nginx:1.14 app=nginx,pod-template-hash=6cfc444fb
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-6cfc444fb-lqbjv 1/1 Running 0 2m36s 10.100.14.221 k8s-node02 <none> <none>
nginx-deployment-6cfc444fb-q72lc 1/1 Running 0 2m36s 10.100.0.228 k8s-node01 <none> <none>
nginx-deployment-6cfc444fb-zxf7d 1/1 Running 0 2m37s 10.100.7.164 k8s-node03 <none> <none>
以上信息可以看到:ReplicaSet和Pods的名称之间是有所关联的。从调度策略上看,这3个Nginx Pod由系统全自动完成调度。它们各自最终运行在哪个节点上,完全由Master的Scheduler经过一系列算法计算得出,用户无法干预调度过程和结果。
除了使用系统自动调度算法完成一组Pod的部署,Kubernetes也提供了多种丰富的调度策略,用户只需在Pod的定义中使用NodeSelector、NodeAffinity、PodAffinity、Pod驱逐等更加细粒度的调度策略设置,就能完成对Pod的精准调度。以下是这些策略的详细说明。
8.1.1 Node定向调度(NodeSelector)
Kubernetes Master上的Scheduler服务(kube-scheduler进程)负责实现Pod的调度,整个调度过程通过执行一系列复杂的算法,最终为每个Pod都计算出一个最佳的目标节点,这一过程是自动完成的,通常我们无法知道Pod最终会被调度到哪个节点上。在实际情况下,也可能需要将Pod调度到指定的一些Node上,可以通过Node的标签(Label)和Pod的nodeSelector属性相匹配,来达到上述目的。
首先,我们给目标Node打上标签,下面我们给节点k8s-node01打上一个“role=middleware”的标签。
# kubectl label nodes k8s-node01 role=middleware
# kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
k8s-master Ready master 17d v1.15.5 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-master,kubernetes.io/os=linux,node-role.kubernetes.io/master=
k8s-node01 Ready <none> 17d v1.15.5 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node01,kubernetes.io/os=linux,role=middleware
k8s-node02 Ready <none> 17d v1.15.5 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node02,kubernetes.io/os=linux
k8s-node03 Ready <none> 17d v1.15.5 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node03,kubernetes.io/os=linux
然后,在Pod的资源定义文件总加上nodeSelector的配置,下面以一个Redis的Pod为例:
apiVersion: v1
kind: Pod
metadata:
name: php-redis
labels:
name: php-redis
spec:
containers:
- name: frontend
image: ygego/php-frontend
env:
- name: GET_HOSTS_FROM
value: env
ports:
- containerPort: 80
- name: redis
image: ygego/redis-master
ports:
- containerPort: 6379
nodeSelector:
role: middleware
运行命令创建此Pod,Scheduler就会将给Pod调度到拥有标签“role=middleware”的Node上。
# kubectl apply -f frontend-redis-pod.yaml
pod/php-redis created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
php-redis 2/2 Running 0 8s 10.100.0.229 k8s-node01 <none> <none>
如果我们给多个Node都定义了相同的标签(例如zone=north),则scheduler会根据调度算法从这组Node中挑选一个可用的Node进行Pod调度。
通过基于Node标签的调度方式,我们可以把集群中具有不通特点的Node都加上不同的标签,例如“role=frontedn”、“role=backend”、“role=database”等标签,在部署应用是就可以根据应用的需求设置NodeSelector来进行指定Node范围的调度。
需要注意的是,如果我们指定了Pod的nodeSelector条件,且在集群中不存在包含相应标签的Node,则即使在集群中还有其他可供使用的Node,这个Pod也无法被成功调度。
除了用户可以给Node添加标签,Kubernetes集群也会给Node预定义一些标签,用户也可以使用这些系统标签进行Pod的定向调度,包括:
- kubernetes.io/hostname
- beta.kubernetes.io/os(从1.14版本开始更新为稳定版,到1.18版本删除)
- beta.kubernetes.io/arch(从1.14版本开始更新为稳定版,到1.18版本删除)
- kubernetes.io/os(从1.14版本开始启用)
- kubernetes.io/arch(从1.14版本开始启用)
NodeSelector通过标签的方式,简单实现了限制Pod所在节点的方法。亲和性调度机制则极大扩展了Pod的调度能力,主要的增强功能如下:
- 更具表达力(不仅仅是“符合全部”的简单情况)。
- 可以使用软限制、优先采用等限制方式,代替之前的硬限制,这样调度器在无法满足优先需求的情况下,会退而求其次,继续运行该Pod。
- 可以依据节点上正在运行的其他Pod的标签来进行限制,而非节点本身的标签。这样就可以定义一种规则来描述Pod之间的亲和或互斥关系。
亲和性调度功能包括节点亲和性(NodeAffinity)和Pod亲和性(PodAffinity)两个维度的设置。节点亲和性与NodeSelector类似,增强了上述前两点优势;Pod的亲和与互斥限制则通过Pod标签而不是节点标签来实现,也就是上面第3点内容所陈述的方式,同时具有前两点提到的优点。
NodeSelector将会继续使用,随着节点亲和性越来越能够表达nodeSelector的功能,最终NodeSelector会被废弃。
8.1.2 Node亲和性调度(NodeAffinity)
NodeAffinity意为Node亲和性的调度策略,是用于替换NodeSelector的全新调度策略。目前有两种节点亲和性表达:
- RequiredDuringSchedulingIgnoredDuringExecution:必须满足指定的规则才可以调度Pod到Node上(功能与nodeSelector很像,但是使用的是不同的语法),相当于硬限制。
- PreferredDuringSchedulingIgnoredDuringExecution:强调优先满足指定规则,调度器会尝试调度Pod到Node上,但并不强求,相当于软限制。多个优先级规则还可以设置权重(weight)值,以定义执行的先后顺序。
IgnoredDuringExecution的意思是:如果一个Pod所在的节点在Pod运行期间标签发生了变更,不再符合该Pod的节点亲和性需求,则系统将忽略Node上Label的变化,该Pod能继续在该节点运行。
NodeAffinity语法支持的操作符包括In、NotIn、Exists、DoesNotExist、Gt、Lt。虽然没有节点排斥功能,但是用NotIn和DoesNotExist就可以实现排斥的功能了。
NodeAffinity调度规则示例,具体需求如下:
- requiredDuringSchedulingIgnoredDuringExecution要求只运行在amd64的节点上(beta.kubernetes.io/arch In amd64)
- preferredDuringSchedulingIgnoredDuringExecution要求尽量运行在磁盘类型为ssd的节点上(disk-type In ssd)。
Pod资源定义文件:
apiVersion: v1
kind: Pod
metadata:
name: node-affinity
spec:
containers:
- name: node-affinity
image: nginx:1.14
imagePullPolicy: IfNotPresent
affinity:
nodeAffinity:
requireDuringSchedulingIgnoreDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: beta.kubernetes.io/arch
opertator: In
value:
- amd64
preferredDuringSchedulingIgnoreDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: disk-type
operator: In
values;
- ssd
NodeAffinity规则设置的注意事项如下:
- 如果同时定义了nodeSelector和nodeAffinity,那么必须两个条件都得到满足,Pod才能最终运行在指定的Node上。
- 如果nodeAffinity指定了多个nodeSelectorTerms,那么其中一个能够匹配成功即可。
- 如果nodeSelectorTerms中有多个matchExpressions,则一个节点必须满足所有matchExpressions才能运行该Pod。
8.1.3 Pod亲和与互斥调度(PodAffinity)
出于高效通信的需求,偶尔需要把一些 Pod对象组织在相近的位置(同一节点、机架、区域或地区等),如某业务的前端 Pod和后端Pod等,此时可以将这些Pod对象间的关系称为亲和性。偶尔,出于安全或分布式等原因也有可能需要将一些Pod对象在其运行的位置上隔离开来,如在每个区域运行一个应用代理 Pod对象等,此时可把这些 Pod对象间的关系称为反亲和性(anti-affinity)。
当然,也可以通过节点亲和性来定义Pod对象间的亲和或反亲和特性,但用户必须为此明确指定Pod可运行的节点标签,显然这并非较优的选择。较理想的实现方式是,允许调度器把第一个 Pod放置于任何位置,而后与其有亲和或反亲和关系的 Pod据此动态完成位置编排,这就是 Pod亲和性调度和反亲和性调度的功用。 Pod的亲和性定义也存在“硬”(required)亲和性和“软”(preferred)亲和性的区别,它们表示的约束意义同节点亲和性相似。
Kubemetes调度器通过内建的MatchlnterPodAffinity预选策略为这种调度方式完成节点预选,并基于InterPodAffinityPriority优选函数进行各节点的优选级评估。
8.1.3.1 位置拓扑
Pod亲和性调度需要各相关的 Pod对象运行于“同一位置”,而反亲和性调度则要求它们不能运行于“同一位置”。何谓同一位置?事实上,它们取决于节点的位置拓扑,拓扑的方式不同,对于如图 8-1所示的Pod-A和Pod-B是否在同一位置的判定结果也可能有所不同。
如果以基于各节点的 kubernetes.io/hostname标签作为评判标准,那么很显然,“同一位置”意味着同一个节点,不同节点即不同的位置,如图 8-2所示。
而如果是基于如图 8-3所划分的故障转移域来进行评判,那么 serverl和 server4属于同一位置,而 server2和 server3属于另一个意义上的同一位置
故此,在定义Pod对象的亲和性与反亲和性时,需要借助于标签选择器来选择被依赖的Pod对象,并根据选出的Pod对象所在节点的标签来判定“同一位置”的具体意义。
8.1.3.2 Pod硬亲和调度
Pod强制约束的亲和性调度也使用 requiredDuringSchedulinglgnoredDuringExecution属性进行定义。 Pod亲和性用于描述一个 Pod对象与具有某特征的现存 Pod对象运行位置的依赖关系,因此,测试使用 Pod亲和性约束,需要事先存在被依赖的 Pod对象,它们具有特别的识别标签。下面创建一个有着标签“ app=nginx”的 Deployment资源部署一个目标 Pod对象:
# kubectl run nginx -l app=nginx --image nginx:1.14
deployment.apps/nginx created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-699bd9c545-jhwhs 1/1 Running 0 47s 10.100.7.166 k8s-node03 <none> <none>
下面的资源配置清单中定义了一个 Pod对象,它通过labelSlector定义的标签选择器挑选感兴趣的现存 Pod对象,而后根据挑选出的 Pod对象所在节点的标签“kubernetes.io/hostname”来判断同一位置的具体含义,并将当前 Pod对象调度至这一位置的某节点之上:
# cat required-podAffinity-pod.yaml
apiVersion: vl
kind: Pod
metadata:
name: pod-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoreDuringExecution:
- labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["nginx"]}
topologyKey: kubernetes.io/hostname
containers:
- name: myapp
image: ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
事实上,kubernetes.io/hostname标签是Kubernetes集群节点的内建标签,它的值为当前节点的节点主机名称标识,对于各个节点来说,各有不同。因此,新建的 Pod对象将被部署至被依赖的 Pod对象的同一节点之上, requiredDuringSchedulinglgnoredDuringExecution表示这种亲和性为强制约束。
# kubectl apply -f required-podAffinity-pod.yaml
pod/pod-affinity created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-699bd9c545-jhwhs 1/1 Running 0 16m 10.100.7.166 k8s-node03 <none> <none>
pod-affinity 1/1 Running 0 12s 10.100.7.167 k8s-node03 <none> <none>
基于单一节点的 Pod亲和性只在极个别的情况下才有可能会用到,较为常用的通常是基于同一地区(region)、区域(zone)或机架( rack)的拓扑位置约束。例如部署应用程序服务(myapp)与数据库(db)服务相关的 Pod时,db Pod可能会部署于如图 8-4所示的 foo或 bar这两个区域中的某节点之上,依赖于数据服务的 myapp Pod对象可部署于 db Pod所在区域内的节点上。当然,如果 db Pod在两个区域 foo和 bar中各有副本运行,那么 myapp Pod将可以运行于这两个区域的任何节点之上。
例如,创建两个拥有标签为“app=db”的副本Pod作为被依赖的资源,它们可能运行于类似图8-5所示的三个节点中的任何一个或两个节点之上,本示例中它们凑巧运行于两个 zone标签值不同的节点之上:
# kubectl run db -l app=db --image=ygego/redis-master:latest --image-pull-policy=Never --replicas=2
deployment.apps/db created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
db-d47c9597c-n2pq8 1/1 Running 0 7s 10.100.0.232 k8s-node01 <none> <none>
db-d47c9597c-vjdt8 1/1 Running 0 7s 10.100.7.169 k8s-node03 <none> <none>
因此,依赖亲和于这两个 Pod的其他 Pod对象可运行于 zone标签值为 foo和 bar的区域内的所有节点之上。以下资源配置清单中定义了这样一些Pod资源,它们由 Deployment控制器所创建:
# cat deploy-required-podAffinity.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-required-podaffinity
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp
labels:
app: myapp
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["db"]}
topologyKey: zone
containers:
- name: myapp
image: ygego/myapp:v1
imagePullPolicy: IfNotPresent
# kubectl apply -f deployment-required-affinity.yaml
deployment.apps/myapp-required-podaffinity created
# kubectl get pods -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
myapp-required-podaffinity-57ff997984-4snvl 1/1 Running 0 11s 10.100.0.233 k8s-node01 <none> <none> app=myapp,pod-template-hash=57ff997984
myapp-required-podaffinity-57ff997984-mpgx4 1/1 Running 0 11s 10.100.14.222 k8s-node02 <none> <none> app=myapp,pod-template-hash=57ff997984
myapp-required-podaffinity-57ff997984-w9mwn 1/1 Running 0 11s 10.100.7.170 k8s-node03 <none> <none> app=myapp,pod-template-hash=57ff997984
在调度示例中的 Dep loym巳nt控制器创建的 Pod资源时,调度器首先会基于标签选择器查询拥有标签“ app=db”的所有Pod资源,接着获取到它们分别所属的节点的 zone标签值,接下来再查询拥有匹配这些标签值的所有节点,从而完成节点预选。而后根据优选函数计算这些节点的优先级,从而挑选出运行新建Pod对象的节点。
需要注意的是,如果节点上的标签在运行时发生了更改,以致它不再满足Pod上的亲和性规则,但该Pod还将继续在该节点上运行,因此它仅会影响新建的Pod资源;另外,labelSelector属性仅匹配与被调度器的 Pod在同一名称空间中的 Pod资源,不过也可以通过为其添加 namespace字段以指定其他名称空间。