Kubernetes Pod基础
Kubernetes Pod
简介
Pod
是Kubernetes
集群运行的最小单元,每个Pod
都有一个特殊的被称为"根容器"的Pause
容器。Kubernetes
为每个Pod
都分配了一个Pod IP
,一个Pod
里的多个容器共享Pod IP
地址。Kubernetes
要求底层网络支持集群内任意两个Pod
之间的TCP/IP
直接通信,因此,一个Pod
里的容器与另一台主机的Pod
容器能够直接互通。
基本用法
一个Pod
中可以存放一个容器也可以存放多个容器,多数场景是一个容器,当存放多个容器时,两个容器之间共享网络空间、存储空间等,例如:
# frontend-localredis-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: redis-php
labels:
name: redis-php
spec:
containers:
- name: frontend
image: kubeguide/guestbook-php-frontend
ports:
- containerPort: 80
hostPort: 1800
- name: redis
image: kubeguide/redis-master
ports:
- containerPort: 6379
属于同一个Pod
的多个容器应用之间相互访问时仅需要通过localhost
就可以通信。
静态Pod
静态Pod
是由kubelet
进行管理的仅存在于特定Node
上的Pod
。它们不能通过Api Server
进行管理,无法与ReplicationController、Deployment或DaemonSet进行关联,并且kuberlet
无法对其进行健康检查。静态Pod
由kubelet
创建,并且总在Kubelet
所在Node
运行。
创建静态Pod
有两种方式:配置文件方式、HTTP方式
- 配置文件方式
需要在kubelet启动参数中指定--config
,即指定kubelet监控的配置文件的目录,kubelet
会定期扫描该目录,根据该目录下的.yaml
或.json
文件进行创建。由于无法使用API Server管理静态Pod,所以只能删除对应目录下的对应.yaml或.json文件才能删除Pod。
- HTTP方式
通过设置kubelet的启动参数--manifest-url
,kubelet将会定期从该URL地址下载Pod的定义文件,并以.yaml或.json文件的格式进行解析, 然后创建Pod。
Pod容器共享Volume
在一个Pod
中,多个容器可以共享Pod
级别的存储卷Volume
。Volume
可以被定义为各种类型,多个容器各自进行挂载操作,将一个Volume
挂载为容器内部需要的目录。
# pod-volume-applogs.yaml
apiVersion: v1
kind: Pod
metadata:
name: volume-pod
spec:
containers:
- name: tomcat
image: h-reg.dasouche-inc.net/devops/tomcat@sha256:8b42182fb220374cc8be73fbf4d9dba5946c9e6cb024c19f8470a27a6557e364
ports:
- containerPort: 8080
volumeMounts:
- name: app-logs
mountPath: /usr/local/tomcat/logs
- name: busybox
image: h-reg.dasouche-inc.net/devops/busybox@sha256:0415f56ccc05526f2af5a7ae8654baec97d4a614f24736e8eef41a4591f08019
command: ["sh", "-c", "tail -f /logs/catalina*.log"]
volumeMounts:
- name: app-logs
mountPath: /logs
volumes:
- name: app-logs
emptyDir: {}
Pod配置管理
应用部署的最佳实践是将应用所需的配置信息与程序进行分离,这样可以使应用程序被更好的复用。将应用打包为容器镜像后,可以通过环境变量或者外挂文件的方式在创建容器时进行配置注入,在大规模容器集群环境中,对多个容器进行不同的配置将变得非常复杂。因此,Kubernetes1.2版本开始提供了一种统一的应用配置方案ConfigMap
。
ConfigMap概述
ConfigMap
的使用场景如下:
- 生成为容器内的环境变量
- 设置容器启动命令的启动参数(需要设置为环境变量)
- 以Volume的形式挂载为容器内部文件或目录
ConfigMap
以一个或多个key:value
的形式保存在Kubernetes系统中供应用使用,既可以表示一个变量的值,也可以表示一个完整配置文件的内容。可以直接使用Yaml配置文件或者直接使用命令kubectl create configmap
的方式创建ConfigMap。
创建ConfigMap资源对象
- 通过Yaml配置文件的方式创建
# configmap-appvars.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: configmap-appvars
data:
applogslevel: info
applogsdir: /var/log/app
执行kubectl create命令创建ConfigMap
kubectl create -f configmap-appvars.yaml
查看创建好的ConfigMap
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl get configmaps
NAME DATA AGE
configmap-appvars 2 2m57s
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl describe configmaps configmap-appvars
Name: configmap-appvars
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
applogsdir:
----
/var/log/app
applogslevel:
----
info
Events: <none>
下面的例子描述了将配置文件server.yaml
和logging.properties
定义为ConfigMap
的用法,设置key
为配置文件的别名,value
为配置文件的全部文本内容:
# configmap-appconfigfiles.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: config-appconfigfiles
data:
key-serverxml: |
<?xml version='1.0' encoding='utf-8'?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
key-loggingproperties: "tomcat.util.buf.StringCache.byte.enabled=true\r\n\r\ntomcat.util.scan.StandardJarScanFilter.jarsToScan=log4j-core*.jar,log4j-taglib*.jar,log4javascript*.jar\r\n\r\n"
执行命令创建ConfigMap
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl create -f configmap-appconfigfiles.yaml
configmap/config-appconfigfiles created
查看创建的ConfigMap
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl get configmaps
NAME DATA AGE
config-appconfigfiles 2 2m18s
configmap-appvars 2 42h
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl describe configmaps config-appconfigfiles
Name: config-appconfigfiles
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
key-loggingproperties:
----
tomcat.util.buf.StringCache.byte.enabled=true
tomcat.util.scan.StandardJarScanFilter.jarsToScan=log4j-core*.jar,log4j-taglib*.jar,log4javascript*.jar
key-serverxml:
----
<?xml version='1.0' encoding='utf-8'?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
Events: <none>
- 通过
kubectl
命令行方式创建
不使用Yaml
文件,可以直接使用kubectl create configmap
创建,也可以使用参数--from-file
或者--from-literal
指定内容:
- 通过
--from-file
参数从文件中进行创建,可以指定key的名称,也可以在一个命令行中创建包含多个key
的ConfigMap
,语法为:
kubectl create configmap NAME --from-file=[key=]sorce --from-file=[key=]source
- 通过
--from-file
参数从目录中进行创建,目录下的每个配置文件名被设置为key,文件内容被设置为value,语法:
kubectl create configmap NAME --from-file=config-files-dir
- 指定
--from-literal
参数,将从文本中进行创建,直接指定为key=value创建ConfigMap,语法:
kubectl create configmap NAME --from-literal=key1=value1 --from-litaral=key2=value2
使用案例:
- 当前目录下存放配置文件
server.xml
,创建一个包含该文件的ConfigMap:
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl create configmap configmap-server.xml --from-file=server.xml
configmap/configmap-server.xml created
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl get configmaps
NAME DATA AGE
config-appconfigfiles 2 19m
configmap-appvars 2 43h
configmap-server.xml 1 8s
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl describe configmaps configmap-server.xml
Name: configmap-server.xml
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
server.xml:
----
<?xml version='1.0' encoding='utf-8'?>
...
- 根据配置文件目录
configs
下的两个配置文件server.xml
和catalina.properties
创建ConfigMap
:
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl create configmap configmap-appconf --from-file=configs
configmap/configmap-appconf created
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl get configmaps
NAME DATA AGE
config-appconfigfiles 2 23m
configmap-appconf 2 9s
configmap-appvars 2 43h
configmap-server.xml 1 4m49s
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl describe configmaps configmap-appconf
Name: configmap-appconf
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
catalina.properties:
----
tomcat.util.scan.StandardJarScanFilter.jarsToSkip=\
bootstrap.jar,commons-daemon.jar,tomcat-juli.jar,\
annotations-api.jar,el-api.jar,jsp-api.jar,servlet-api.jar,websocket-api.jar,\
...
server.xml:
----
<?xml version='1.0' encoding='utf-8'?>
...
Events: <none>
- 使用
--from-literal
参数创建ConfigMap
:
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl create configmap cm-appenv --from-literal=loglevel=info --from-literal=appname=apollo
configmap/cm-appenv created
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl describe configmaps cm-appenv
Name: cm-appenv
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
loglevel:
----
info
appname:
----
apollo
Events: <none>
容器应用对ConfigMap
的使用方式有两种:
- 通过环境变量获取
ConfigMap
的内容 - 通过
Volume
挂载方式将ConfigMap
中的内容挂载为容器内的文件或目录
在Pod中使用ConfigMap
- 通过环境变量方式
查询configmap-appvars
的ConfigMap
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl describe cm configmap-appvars
Name: configmap-appvars
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
applogsdir:
----
/var/log/app
applogslevel:
----
info
Events: <none>
定义Pod并将configmap-appvars中的内容以环境变量的方式设置为容器内部的环境变量,容器的启动命令将显示这两个环境变量的值:
# cm-appvars-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: cm-appvars-pod
spec:
containers:
- name: cm-appvars
image: h-reg.dasouche-inc.net/devops/busybox@sha256:0415f56ccc05526f2af5a7ae8654baec97d4a614f24736e8eef41a4591f08019
command: ["/bin/sh", "-c", "env | grep APP"]
env:
- name: APPLOGLEVEL # 定义环境变量的名字
valueFrom: # 定义Key的来源
configMapKeyRef:
name: configmap-appvars # 指定Key的来源
key: applogslevel # 指定Key的名字
- name: APPLOGDIR
valueFrom:
configMapKeyRef:
name: configmap-appvars
key: applogsdir
restartPolicy: Never
创建Pod,并查看结果:
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl create -f cm-appvars-pod.yaml
pod/cm-appvars-pod created
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
default cm-appvars-pod 0/1 Completed 0 41s
default redis-php 2/2 Running 0 45h
default volume-pod 2/2 Running 0 44h
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl logs cm-appvars-pod
APPLOGLEVEL=info
APPLOGDIR=/var/log/app
Kubernetes 1.6
版本开始,引入了envFrom
字段,可以将ConfigMap(也可以用于Secret资源对象)中定义的key=value自动生成为环境变量:
# cm-envfrom-pod-test.yaml
apiVersion: v1
kind: Pod
metadata:
name: cm-envfrom-pod
spec:
containers:
- name: cm-envfrom
image: h-reg.dasouche-inc.net/devops/busybox@sha256:0415f56ccc05526f2af5a7ae8654baec97d4a614f24736e8eef41a4591f08019
command:
- /bin/sh
- -c
- env
envFrom:
- configMapRef:
name: cm-appenv # 根据cm-appenv中的key=value自动生产环境变量
restartPolicy: Never
创建Pod并查看结果:
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl create -f cm-envfrom-pod-test.yaml
pod/cm-envfrom-pod created
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
default cm-appvars-pod 0/1 Completed 0 16m
default cm-envfrom-pod 0/1 Completed 0 8s
default redis-php 2/2 Running 0 46h
default volume-pod 2/2 Running 0 44h
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl logs cm-envfrom-pod
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.0.0.1:443
HOSTNAME=cm-envfrom-pod
SHLVL=1
HOME=/root
loglevel=info
KUBERNETES_PORT_443_TCP_ADDR=10.0.0.1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
appname=apollo
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.0.0.1:443
KUBERNETES_SERVICE_HOST=10.0.0.1
PWD=/
注意
:环境变量的名称受POSIX
命名规范([a-zA-Z_][a-zA-Z0-9_]*)
约束,不能以数字开头。如果包含非法字符,则系统将跳过该条环境变量的创建,并记录一个Event
来提示环境变量无法生成,但并不阻止Pod
的启动。
- 通过
volumeMount
使用ConfigMap
将configmap的内容以文件的形式mount到容器内部的/configfiles目录下:
# cm-appconfigfiles-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: cm-appconfigfiles-pod
spec:
containers:
- name: cm-appconfigfiles-pod
image: h-reg.dasouche-inc.net/devops/tomcat-app:v1
ports:
- containerPort: 8080
volumeMounts:
- name: server-xml # 引用的volume的名字
mountPath: /configfiles # 在容器中挂载的目录
volumes:
- name: server-xml # 定义volume的名称
configMap:
name: config-appconfigfiles # 指定ConfigMap名称
items:
- key: key-serverxml # 指定key的名称
path: server.xml # value 将以 server.xml 文件名进行挂载
- key: key-loggingproperties # 指定key的名称
path: logging.properties # value 将以 logging.properties 文件名进行挂载
创建该Pod并查看结果:
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl create -f cm-appconfigfiles-pod.yaml
pod/cm-appconfigfiles-pod created
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl get Pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
default cm-appconfigfiles-pod 1/1 Running 0 99s
default cm-appvars-pod 0/1 Completed 0 45m
default cm-envfrom-pod 0/1 Completed 0 29m
default redis-php 2/2 Running 0 46h
default volume-pod 2/2 Running 0 45h
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl exec -it cm-appconfigfiles-pod -- /bin/bash
root@cm-appconfigfiles-pod:/usr/local/tomcat# cd /configfiles/
root@cm-appconfigfiles-pod:/configfiles# ls
logging.properties server.xml
root@cm-appconfigfiles-pod:/configfiles# cat server.xml
<?xml version='1.0' encoding='utf-8'?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
注意:如果没有指定items
字段,则挂载时,生成每个key
作为文件名的文件
使用ConfigMap的限制条件
- 在创建Pod前,ConfigMap必须先创建
- ConfigMap受Namespace限制,只有处于相同的Namespace中的Pod才可以引用
- ConfigMap中的配额管理还未能实现
- 静态Pod无法使用ConfigMap
- 进行volumeMounts挂载时只能挂载为目录,无法挂载为文件
容器内获取Pod信息
Pod被创建之后,我们如何在容器内部知道Pod的名称、IP地址、Namespace等信息呢,答案就是Downward API
。
Downward API
可以通过以下两种方式将Pod信息注入容器内部:
- 环境变量
- Volume挂载
环境变量方式:将Pod信息注入为环境变量
下面的例子通过Downward API
将Pod
的IP、名称和所在Namespace注入容器的环境变量中,容器应用使用env命令将全部环境变量打印到标准输出中:
# downwardapi-pod-test.yaml
apiVersion: v1
kind: Pod
metadata:
name: downwardapi-pod
spec:
containers:
- name: demo-container
image: h-reg.dasouche-inc.net/devops/busybox
command:
- bin/sh
- -c
- env
env:
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: MY_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: MY_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
restartPolicy: Never
注意到上面valueFrom
这种特殊的语法是Downward API
的写法。目前Downward API
提供了以下变量:
- metadata.name:Pod的名称,当Pod通过RC生成时,其名称是RC随机产生的唯一名称。
- status.podIP:Pod的IP地址,之所以叫作status.podIP而非metadata.IP,是因为Pod的IP属于状态数据,而非元数据。
- metadata.namespace:Pod所在的Namespace。
创建Pod并查看结果:
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl create -f downwardapi-pod-test.yaml
pod/downwardapi-pod created
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl logs downwardapi-pod
MY_POD_NAMESPACE=default
MY_POD_IP=172.31.57.3
MY_POD_NAME=downwardapi-pod
环境变量方式:将容器资源注入为环境变量
通过Downward API
将Container
的资源请求和限制信息注入容器的环境变量中,容器应用使用printenv
命令将设置的资源请求和资源限制环境变量打印到标准输出中:
# downwardapi-pod-container-vars.yaml
apiVersion: v1
kind: Pod
metadata:
name: downwardapi-pod-container-vars
spec:
containers:
- name: demo-container
image: h-reg.dasouche-inc.net/devops/busybox
imagePullPolicy: Never
command: ["/bin/sh", "-c"]
args:
- while true; do
printenv MY_CPU_REQUEST MY_CPU_LIMIT;
printenv MY_MEM_REQUEST MY_MEM_LIMIT;
sleep 3600;
done;
resources:
requests:
memory: "32M"
cpu: "125m"
limits:
memory: "64M"
cpu: "250m"
env:
- name: MY_CPU_REQUEST
valueFrom:
resourceFieldRef:
containerName: demo-container
resource: requests.cpu
- name: MY_CPU_LIMIT
valueFrom:
resourceFieldRef:
containerName: demo-container
resource: limits.cpu
- name: MY_MEM_REQUEST
valueFrom:
resourceFieldRef:
containerName: demo-container
resource: requests.memory
- name: MY_MEM_LIMIT
valueFrom:
resourceFieldRef:
containerName: demo-container
resource: limits.memory
restartPolicy: Never
注意valueFrom
这种特殊的Downward API
语法,目前resourceFieldRef
可以将容器的资源请求和资源限制等配置设置为容器内部的环境变量:
- requests.cpu:容器的CPU请求值。
- limits.cpu:容器的CPU限制值。
- requests.memory:容器的内存请求值。
- limits.memory:容器的内存限制值。
运行kubectl create
命令来创建Pod:
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl create -f downwardapi-pod-container-vars.yaml
pod/downwardapi-pod-container-vars created
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl get pods -A |grep downwardapi-pod-container-vars
default downwardapi-pod-container-vars 1/1 Running 0 36s
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl logs downwardapi-pod-container-vars
1
1
32000000
64000000
Volume挂载方式
通过Downward API
将Pod的Label
、Annotation
列表通过Volume
挂载为容器中的一个文件,容器应用使用echo命令将文件的内容打印到标准输出中:
# downwardapi-pod-volume.yaml
apiVersion: v1
kind: Pod
metadata:
name: downwardapi-pod-volume
labels:
zone: souche
cluster: stable
rack: rack-10
annotations:
build: two
builder: cc
spec:
containers:
- name: demo-container
image: h-reg.dasouche-inc.net/devops/busybox
command: ["/bin/sh", "-c"]
args:
# 需要注意每个参数列表项都会放在一行,注意结尾写分号
- while true; do
if [[ -e /etc/podinfo/labels ]]; then
cat /etc/podinfo/labels; fi;
if [[ -e /etc/podinfo/annotations ]]; then
cat /etc/podinfo/annotations; fi;
sleep 300;
done;
volumeMounts:
- name: podinfo
mountPath: /etc/podinfo
readOnly: false
volumes:
- name: podinfo
downwardAPI:
items:
- path: "labels"
fieldRef:
fieldPath: metadata.labels
- path: "annotations"
fieldRef:
fieldPath: metadata.annotations
restartPolicy: Never
这里要注意volumes
字段中downwardAPI
的特殊语法,通过items
的设置,系统会根据path
的名称生成文件。根据上例的设置,系统将在容器内生成/etc/labels
和/etc/annotations
两个文件。在/etc/labels
文件中将包含metadata.labels
的全部Label
列表,在/etc/annotations
文件中将包含 metadata.annotations
的全部Label
列表。
运行kubectl create
命令创建Pod:
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl create -f downwardapi-pod-volume.yaml
pod/downwardapi-pod-volume created
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl get pods -A |grep downwardapi-pod-volume
default downwardapi-pod-volume 0/1 ErrImageNeverPull 0 14s
查看日志
root@sc-cc-k8s-master-001:~/yaml/Pods# kubectl logs downwardapi-pod-volume
cluster="stable"
rack="rack-10"
zone="souche"
build="two"
builder="cc"
kubernetes.io/config.seen="2021-01-31T18:23:28.730360031+08:00"
从结果我们可以看到Pod
的信息被保存在/etc/podinfo/labels
和/etc/podinfo/annotations
文件中。
DownwardAPI
的具体价值是什么呢?
在某些集群中,集群中的每个节点都需要将自身的标识(ID)及进程绑定的IP地址等信息事先写入配置文件中,进程在启动时会读取这些信息,然后将这些信息发布到某个类似服务注册中心的地方,以实现集群节点的自动发现功能。此时Downward API
就可以派上用场了,具体做法是先编写一个预启动脚本或Init Container,通过环境变量或文件方式获取Pod自身的名称、IP地址等信息,然后将这些信息写入主程序的配置文件中,最后启动主程序。
Pod的声明周期和重启策略
Pod
在整个生命周期中被系统定义为各种状态,熟悉Pod
的各种状态对于理解如何设置Pod
的调度策略、重启策略是很有必要的。
状态值 | 描述 |
---|---|
Pending | API Server已经创建该Pod,但是Pod内还有一个或多个容器镜像未创建,包括下载镜像的过程。 |
Running | Pod内的所有容器均已创建,且至少一个容器处在运行状态、启动状态或重启状态 |
Successed | Pod内所有容器均成功执行后退出,且不会重启 |
Failed | Pod内所有容器均已退出,但是至少有一个容器退出为失败状态 |
Unknown | 由于某种原因无法获取该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监控检查和服务可用性检查
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,则表明容器健康。
案例:通过执行cat /tmp/health
判断容器是否运行正常,在该Pod
运行后,将在创建/tmp/health
文件10s后删除该文件,而LivenessProbe
健康检查的初始探测时间(initialDelaySeconds
)为 15s,探测结果是Fail
,将导致kubelet
杀掉该容器并重启它:
# liveness-pod-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: liveness-exec
labels:
app: liveness
spec:
containers:
- name: liveness
image: h-reg.dasouche-inc.net/devops/busybox
args:
- /bin/sh
- -c
- echo ok > /tmp/health; sleep 10; rm -rf /tmp/health; sleep 300;
livenessProbe:
exec:
command:
- cat
- /tmp/health
initialDelaySeconds: 15
timeoutSeconds: 1
TCPSocketAction
:通过容器的IP地址和端口号执行TCP检查,如果能够建立TCP连接,则表明容器健康。
案例:通过与容器内的localhost:80建立TCP连接进行健康检查。
# liveness-tcp-health-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: liveness-tcp-health-pod
spec:
containers:
- name: nginx
image: h-reg.dasouche-inc.net/devops/nginx:1.7.9
ports:
- containerPort: 80
livenessProbe:
tcpSocket:
port: 80
initialDelaySeconds: 10
timeoutSeconds: 1
HTTPGetAction
:通过容器的IP地址、端口号及路径调用HTTP Get方法,如果响应的状态码大于等于200且小于400,则认为容器健康。
案例:kubelet定时发送HTTP请求到localhost:80/_status/healthz来进行容器应用的健康检查。
# httpget-liveness-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: httpget-liveness-pod
spec:
containers:
- name: nginx
image: h-reg.dasouche-inc.net/devops/nginx:1.7.9
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
timeoutSeconds: 1
对于每种探测方式,都需要设置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
状态。
Pod调度:Deployment或RC:全自动调度
在Kubernetes
平台上,我们很少会直接创建一个Pod
,在大多数情况下会通过RC、Deployment、DaemonSet、Job等控制器完成对一组Pod副本的创建、调度及全生命周期的自动控制任务。
早期Kubernetes
版本只有一个RC
(Replication Controller)副本控制器,随着版本发展,出现了新的继承者ReplicaSet
,ReplicaSet
进一步增强了RC
标签选择器的灵活性。之前RC
的标签 选择器只能选择一个标签,而ReplicaSet
拥有集合式的标签选择器,可以选择多个Pod
标签。与RC不同,ReplicaSet被设计成能控制多个不同标签的Pod副本。一个ReplicaSet对象,其实就是由副本数目的定义和一个Pod模板组成的。说到ReplicaSet
,我们更要说下Deployment
,它是ReplicaSet
之前的一个管理层控制器,可以用于更加自动地完成Pod
副本的部署、版本更新、回滚等功能。
Kubernetes
的滚动升级就是巧妙运用ReplicaSet
的这个特性来实现的,同时,Deployment也是通过ReplicaSet
来实现Pod
副本自动控制功能的。我们不应该直接使用底层的ReplicaSet
来控制Pod
副本,而应该使用管理ReplicaSet
的Deployment
对象来控制副本,这是来自官方的建议。
在大多数情况下,我们希望Deployment
创建的Pod
副本被成功调度到集群中的任何一个可用节点,而不关心具体会调度到哪个节点。但是,在真实的生产环境中的确也存在一种需求:希望某种Pod
的副本全部在指定的一个或者一些节点上运行,比如希望将MySQL
数据库调度到一个具有SSD
磁盘的目标节点上,此时Pod
模板中的NodeSelector
属性就开始发挥作用了,上述MySQL
定向调度案例的实现方式可分为以下两步:
(1)把具有SSD磁盘的Node都打上自定义标签disk=ssd
。
(2)在Pod模板中设定NodeSelector的值为disk: ssd
。
如此一来,Kubernetes
在调度Pod
副本的时候,就会先按照Node
的标签过滤出合适的目标节点,然后选择一个最佳节点进行调度。
上述逻辑看起来既简单又完美,但在真实的生产环境中可能面临以下令人尴尬的问题:
(1)如果NodeSelector
选择的Label
不存在或者不符合条件,比如这些目标节点此时宕机或者资源不足,该怎么办?
(2)如果要选择多种合适的目标节点,比如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
。
在Kubernetes 1.9
版本之前,通过副本控制器创建的Pod
,在副本控制器被删除后,其下的Pod
并不会被删除,Kubernetes 1.9
版本以后,这些Pod
会被一并删除,如果不希望删除,可以执行如下命令:
kubectl delete replicaset my-repset --cascade=false
Deployment
或RC
的主要功能之一就是自动部署一个容器应用的多份副本,以及持续监控副本的数量,在集群内始终维持用户指定的副本数量。
案例:使用Deployment
副本控制器创建一个有3个副本的Nginx服务。
# nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: h-reg.dasouche-inc.net/devops/nginx:1.7.9
ports:
- containerPort: 80
在具体的实现上,这个 Deployment,与 ReplicaSet,以及 Pod 的关系是怎样的呢?我们可以用一张图把它描述出来:
![img](https://i-blog.csdnimg.cn/blog_migrate/cfc77a2f28204a37ed49eae6ec51fb7b.jpeg)
通过这张图,我们就很清楚地看到,一个定义了 replicas=3 的 Deployment,与它的 ReplicaSet,以及 Pod 的关系,实际上是一种“层层控制”的关系。
其中,ReplicaSet
负责通过“控制器模式”,保证系统中 Pod 的个数永远等于指定的个数(比如,3 个)。这也正是 Deployment
只允许容器的 restartPolicy=Always
的主要原因:只有在容器能保证自己始终是 Running 状态的前提下,ReplicaSet 调整 Pod 的个数才有意义。
运行kubectl create命令创建这个Deployment:
root@sc-cc-k8s-master-001:~/yaml/Deployments# kubectl create -f nginx-deployment.yaml
deployment.apps/nginx-deployment created
查看Deployment
状态:
root@sc-cc-k8s-master-001:~/yaml/Deployments# kubectl get deployments.apps -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
nginx-deployment 3/3 3 3 45s nginx h-reg.dasouche-inc.net/devops/nginx:1.7.9 app=nginx
上面的状态表示成功创建了三个副本。
需要注意:对Deployment
进行的每一次更新操作,都会生成一个新的ReplicaSet
对象,随着应用版本的不断增加,Kubernetes 中还是会为同一个 Deployment 保存很多很多不同的 ReplicaSet。那么,我们又该如何控制这些“历史”ReplicaSet 的数量呢?
很简单,Deployment
对象有一个字段,叫作 spec.revisionHistoryLimit
,就是 Kubernetes
为 Deployment
保留的“历史版本”个数。所以,如果把它设置为 0,你就再也不能做回滚操作了。
通过运行kubectl get rs
和kubectl get pods
可以查看已创建的ReplicaSet
(RS)和Pod
的信息:
root@sc-cc-k8s-master-001:~/yaml/Deployments# kubectl get rs
NAME DESIRED CURRENT READY AGE
nginx-deployment-779f54f8c 3 3 3 2m56s
root@sc-cc-k8s-master-001:~/yaml/Deployments# kubectl get pods |grep nginx-deployment
nginx-deployment-779f54f8c-9g88t 1/1 Running 0 3m30s
nginx-deployment-779f54f8c-mc9d4 1/1 Running 0 3m30s
nginx-deployment-779f54f8c-tcbmz 1/1 Running 0 3m30s
从调度策略上来说,这3个Nginx Pod
由系统全自动完成调度。它们各自最终运行在哪个节点上,完全由控制节点的Scheduler经过一系列算法计算得出,用户无法干预调度过程和结果。
除了使用系统自动调度算法完成一组Pod
的部署,Kubernetes
也提供了多种丰富的调度策略,用户只需在Pod
的定义中使用NodeSelector、NodeAffinity、PodAffinity、Pod驱逐等更加细粒度的调度策略设置,就能完成对Pod的精准调度。