第三章·深入掌握Pod
接下来,让我们深入探索Pod的应用、配置、调度、升级及扩缩容,开始Kubernetes容器编排之旅。
本章将对Kubernetes如何发布与管理容器应用进行详细说明和示例,主要包括Pod和容器的使用、应用配置管理、Pod的控制和调度管理、Pod的升级和回滚,以及Pod的扩缩容机制等内容。
一.Pod定义详解
YAML格式的Pod定义文件的完整内容如下(*为必选的):
# 版本号 *
apiVersion: v1
# 资源类型 *
kind: Pod
# 元数据 *
metadata:
# Pod的名称,命名规范需符合RFC 1035 *
name: string
# Pod命名空间,默认值为default *
namespace: string
# 自定义标签列表
labels:
- name: string
# 自定义注释列表
annotations:
- name: string
# Pod中容器的详细定义 *
spec:
# Pod中容器列表 *
containers:
# Pod的名称,命名规范需符合RFC 1035 *
- name: string
# 容器的镜像名称
image: string
# 镜像的拉取策略 (Alway: 每次都尝试重新拉取镜像 Never:仅使用本地镜像 IfNotPresent:本地存在则不拉取,不存在则拉取)
# PS: 以下三种系统将默认设置imagePullPolicy=Alway
1.不设置imagePullPolicy也未指定镜像的tag
2.不设置imagePullPolicy镜像tag为latest
3.启动了名为AlwayPullImages的准入控制器(Admission Contrioller)
imagePullPolicy: [Always | Never | IfNotPresent]
# 容器的启动命令列表,如果不指定则使用镜像打包时使用的启动命令
command: [string]
# 容器的启动命令参数列表
args: [string]
# 容器的工作目录
workingDir: String
# 挂载到容器内部的存储卷配置
volumMounts:
# 引用Pod定义的共享存储卷的名称,需使用volum部分定义的共享存储卷名称
- name: string
# 存储卷在容器内挂载的绝对路径,少于512个字符
mountPath: string
# 是否为只读模式,默认只读模式
readOnly: boolean
# 容器需要暴露的端口号列表
ports:
# 端口的名称
- name: string
# 容器需要监听的端口号
containerPort: int
# 容器所在主机需要监听的端口号,默认与containerPort相同。设置hostPort时,同一台宿主机将无法启动该容器的第2份副本
hostPort: int
# 端口协议,支持TCP和UDP,默认值为TCP
protocol: string
# 容器运行前需设置的环境变量列表
env:
# 环境变量名称
- name: string
# 环境变量的值
value: string
# 资源限制的资源请求的设置
resources:
# 资源限制的设置
limits:
# CPU限制,单位为core数,将用于docker run --cpu-shares参数
cpu: string
# 内存限制,单位可以为MiB、GiB等,将用于docker run --memory参数
memory: string
# 资源限制的设置
requests:
# CPU限制,单位为core数,容器启动的初始可用数量
cpu: string
# 内存请求,单位可以为MiB、GiB等,容器启动的初始可用数量
memory: string
# 对Pod内各容器健康检查的设置,当探测无响应几次之后,系统将自动重启该容器。方法包括:exec、httpGet、topSocket。对一个容器仅需设置一种健康检测方法
livenessProbe:
# 对Pod内各容器健康检查的设置,exec方式
exec:
# exec方式需要指定的命令或者脚本
command: [string]
# 对Pod内各容器健康检查的设置,HttpGet方式,需指定path、port
httpGet:
path: string
port: number
host: string
scheme: string
httpHeaders:
- name: string
value: string
# 对Pod内各容器健康检查的设置,tcpSocket方式
tcpSocket:
port: number
# 容器启动后首次探测的时间,单位为s
initialDelaySeconds: 0
# 对容器健康检查的探测等待响应的超时时间设置,单位为s,默认值为1s.若超过该超时时间的设置,则将认为该容器不健康,会重启该容器
timeoutSeconds: 0
# 对容器健康检查的定期探测时间设置,单位为s,默认10s探测一次
periodSeconds: 0
successThreshold: 0
failureThreshold: 0
securityContext:
privileged: false
# Pod的重启策略
1.Always:无论如何终止的kubelet都将重启它
2.Onfailure:只有Pod以非零退出码终止时,才会重启,正常结束不会被重启
3.Never:Pod终止后,kubelet将退出码报告给Master,不会重启该Pod
restartPolicy: [Always | Never | OnFailure]
# 设置Node的Label,以key:value格式指定,pod将被调度到具有这些Labels的node上
nodeSelector: object
# pull镜像时使用的Secret名称,以name:secrekey格式指定
imagePullSecrets:
- name: string
# 是否使用主机网络模式,默认false,设置为true不再使用Docker网桥,该Pod将无法在同一台宿主机上启动第2个副本
hostNetwork: false
# 在该Pod上定义的共享存储卷列表
volumes:
# 共享存储卷的名称,在一个Pod中每个存储卷定义一个名称,应符合RFC 1035规范.容器定义部分的containers.volumeMounts.name将引用该共享存储卷的名称
- name: string
# 类型为emptyDir的存储卷,表示与Pod同生命周期的一个临时目录,其值为一个空对象: emptyDir:{}
emptyDir: {}
# 类型为hostpath的存储卷,表示Pod容器挂载的宿主机目录.通过volumes.hostPath.path指定
hostPath:
path: string
# 类型为secret的存储卷,表示挂载集群预定义的secret对象到容器内部
secret:
secreName: string
items:
- key: string
path: string
# 类型为configMap的存储卷,表示挂载集群预定义的configMap对象到容器内部
configMap:
name: string
items:
- key: string
path: string
二.Pod的基本用法
在对Pod的用法进行说明之前,有必要先对Docker容器中应用的运行要求进行说明。
在使用Docker时,可以使用docker run命令创建并启动一个容器。而在Kubernetes系统中对长时间运行容器的要求是:其主程序需要一直在前台运行。如果我们创建的Docker镜像的启动命令是后台执行程序,例如Linux脚本:
nohup ./start.sh &
则在kubelet创建包含这个容器的Pod之后运行完该命令,即认为Pod执行结束,将立刻销毁该Pod。如果为该Pod定义了ReplicationController,则系统会监控到该Pod已经终止,之后根据RC定义中Pod的replicas副本数量生成一个新的Pod。一旦创建新的Pod,就在运行完启动命令后陷入无限循环的过程中。这就是Kubernetes需要我们自己创建Docker镜像并以一个前台命令作为启动命令的原因。
对于无法改造为前台执行的应用,也可以使用开源工具Supervisor辅助进行前台运行的功能。Supervisor提供了一种可以同时启动多个后台应用,并保持Supervisor自身在前台执行的机制,可以满足Kubernetes对容器的启动要求。关于Supervisor的安装和使用,请参考官网的文档说明。
接下来讲解Pod对容器的封装和应用。
Pod可以由1个或多个容器组合而成。在上一节Guestbook的例子中,名为frontend的Pod只由一个容器组成:
apiVersion: v1
kind: Pod
metadata:
name: frontend
labels:
name: frontend
spec:
containers:
- name: frontend
image: kubeguide/guestbook-php-frontend
env:
- name: GET_HOSTS_FROM
value: env
ports:
- containerPort: 80
这个frontend Pod在成功启动之后,将启动1个Docker容器。
另一种场景是,当frontend和redis两个容器应用为紧耦合的关系,并组合成一个整体对外提供服务时,应将这两个容器打包为一个Pod,如图3.1所示。
配置文件frontend-localredis-pod.yaml的内容如下:
apiVersion: v1
kind: Pod
metadata:
name: reids-php
labels:
name: redis-php
spec:
containers:
- name: frontend
image: kubeguide/guestbook-php-frontend
ports:
- containerPort: 80
- name: reids
image: kubeguide/redis-master
ports:
- containerPort: 6379
属于同一个Pod的多个容器应用之间相互访问时仅需通过localhost就可以通信,使得这一组容器被“绑定”在一个环境中。
在Docker容器kubeguide/guestbook-php-frontend:localredis的PHP网页中,直接通过URL地址“localhost:6379”对同属于一个Pod的redis-master进行访问。guestbook.php的内容如下
运行kubectl create命令创建该Pod:
[root@k8s-m01 ~]# kubectl create -f frontend-localredis-pod.yaml
查看已经创建的Pod:
[root@k8s-m01 ~]# kubectl get pods
看到READY信息为2/2,表示Pod中的两个容器都成功运行了。
查看这个Pod的详细信息,可以看到两个容器的定义及创建的过程(Event事件信息):
[root@k8s-m01 ~]# kubectl describe pod redis-php
三.静态Pod
静态Pod是由kubelet进行管理的仅存在于特定Node上的Pod。它们不能通过API Server进行管理,无法与ReplicationController、Deployment或者DaemonSet进行关联,并且kubelet无法对它们进行健康检查。静态Pod总是由kubelet创建的,并且总在kubelet所在的Node上运行。
创建静态Pod有两种方式:配置文件方式和HTTP方式。
1.配置文件方式
首先,需要设置kubelet的启动参数“--pod-manifest-path”(或者在kubelet配置文件中设置staticPodPath,这也是新版本推荐的设置方式,--pod-manifest-path参数将被逐渐弃用),指定kubelet需要监控的配置文件所在的目录,kubelet会定期扫描该目录,并根据该目录下的.yaml或.json文件进行创建操作。
假设配置目录为/etc/kubelet.d/,配置启动参数为--pod-manifest-path=/etc/kubelet.d/,然后重启kubelet服务。
在/etc/kubelet.d目录下放入static-web.yaml文件,内容如下:
apiVersion: v1
kind: Pod
metadata:
name: static-web
labels:
name: static-web
spec:
containers:
- name: static-web
image: nginx
ports:
- name: web
- containerPort: 80
等待一会儿,查看本机中已经启动的容器:
[root@k8s-m01 ~]# docker ps
可以看到一个Nginx容器已经被kubelet成功创建了出来。
到Master上查看Pod列表,可以看到这个static pod
[root@k8s-m01 ~]# kubectl get pods
由于静态Pod无法通过API Server直接管理,所以在Master上尝试删除这个Pod时,会使其变成Pending状态,且不会被删除。
删除该Pod的操作只能是到其所在Node上将其定义文件static-web.yaml从/etc/kubelet.d目录下删除。
[root@k8s-m01 ~]# rm /etc/kulet.d/static-web.yaml
2.HTTP方式
通过设置kubelet的启动参数“--manifest-url”,kubelet将会定期从该URL地址下载Pod的定义文件,并以.yaml或.json文件的格式进行解析,然后创建Pod。其实现方式与配置文件方式是一致的。
四.Pod容器共享Volume
同一个Pod中的多个容器能够共享Pod级别的存储卷Volume。Volume可以被定义为各种类型,多个容器各自进行挂载操作,将一个Volume挂载为容器内部需要的目录,如图3.2所示。
在下面的例子中,在Pod内包含两个容器:tomcat和busybox,在Pod级别设置Volume“app-logs”,用于tomcat容器向其中写日志文件,busybox容器从中读日志文件。
配置文件pod-volume-applogs.yaml的内容如下:
apiVersion: v1
kind: Pod
metadata:
name: volume-pod
spec:
containers:
- name: tomcat
image: tomcat
ports:
- containerPort: 8080
volumeMounts:
- name: app-logs
mountPath: /usr/local/tomcat/logs
- name: busybox
image: busybox
command: ["sh", "-c", "tail -f /log/catalina*.log"]
volumeMounts:
- name: app-logs
mountPath: /logs
volumes:
- name: app-logs
emptyDir: {}
这里设置的Volume名称为app-logs,类型为emptyDir(也可以设置为其他类型,详见第1章对Volume概念的说明),挂载到tomcat容器内的/usr/local/tomcat/logs目录下,同时挂载到logreader容器内的/logs目录下。tomcat容器在启动后会向/usr/local/tomcat/logs目录写文件,logreader容器就可以读取其中的文件了。
logreader容器的启动命令为tail-f/logs/catalina*.log,我们可以通过kubectl logs命令查看logreader容器的输出内容:
[root@k8s-m01 ~]# kubectl logs volume-pod -c busybox
这个文件为tomcat生成的日志文件/usr/local/tomcat/logs/catalina.
[root@k8s-m01 ~]# kubectl exec -it volume-pod -c tomcat -- ls /usr/local/tomcat/logs
[root@k8s-m01 ~]# kubectl exec -it volume-pod -c tomcat -- tail /usr/local/tomcat/logs/catalina.2022-11.17.log
五.Pod的配置管理
应用部署的一个最佳实践是将应用所需的配置信息与程序分离,这样可以使应用程序被更好地复用,通过不同的配置也能实现更灵活的功能。将应用打包为容器镜像后,可以通过环境变量或者外挂文件的方式在创建容器时进行配置注入,但在大规模容器集群的环境中,对多个容器进行不同的配置将变得非常复杂。Kubernetes从1.2版本开始提供了一种统一的应用配置管理方案—ConfigMap。本节对ConfigMap的概念和用法进行详细讲解。
1.ConfigMap概述
ConfigMap供容器使用的典型用法如下。
(1)生成容器内的环境变量。
(2)设置容器启动命令的启动参数(需设置为环境变量)。
(3)以Volume的形式挂载为容器内部的文件或目录。
ConfigMap以一个或多个key:value的形式保存在Kubernetes系统中供应用使用,既可以用于表示一个变量的值(例如apploglevel=info),也可以用于表示一个完整配置文件的内容(例如server.xml=<?xml...>...)。
我们可以通过YAML文件或者直接使用kubectl create configmap命令行的方式来创建ConfigMap。
2.创建ConfigMap资源对象
1.通过YAML文件方式创建
在下面的例子cm-appvars.yaml中展示了将几个应用所需的变量定义为ConfigMap的用法:
[root@k8s-m01 ~]# cat cm-appvars.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-appvars
data:
apploglevel: info
appdatadir: /var/data
运行kubectl create命令创建该ConfigMap:
[root@k8s-m01 ~]# create -f cm-appvars.yaml
configmap/cm-appvars created
查看创建好的ConfigMap:
[root@k8s-m01 java_web]# kubectl get configmap
NAME DATA AGE
cm-appvars 2 27s
在下面的例子中展示了将两个配置文件server.xml和logging.properties定义为ConfigMap的用法,设置key为配置文件的别名,value则是配置文件的全部文本内容:
[root@k8s-m01 ~]# cat cm-appconfigfiles.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name : cm-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" />
<Connector port="8009" protocol="AJP/1.3" 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 : "handlers
=1catalina.org.apache.juli.FileHandler,2localhost.org.apache.juli.FileHandler,
3manager.org.apache.juli.FileHandler,4host-manager.org.apache.juli.FileHandler,
java.util.logging.ConsoleHandler\r\n\r\n.handlers= 1catalina.org.apache.juli.FileHandler,
java.util.logging.ConsoleHandler\r\n\rln1catalina.org.apache.juli.FileHandler.level
= FINE\r\n1catalina.org.apache.juli.FileHandler.directory
= ${catalina.base}/logs\r\nicatalina.org.apache.juli.FileHandler.prefix
= catalina.\r\n\r\n2localhost.org.apache.juli.FileHandler.level
= FINE\r\n2localhost.org.apache.juli.FileHandler.directory
= ${catalina.base}/logs\r\n2localhost.org.apache.juli.FileHandler.prefix
= localhost.\r\n\r\n3manager.org.apache.juli.FileHandler.level
= FINE\r\n3manager.org.apache.juli.FileHandler.directory
= ${catalina.base} /logs\r\n3manager.org.apache.juli.FileHandler.prefix
= manager.\r\n\r\n4host-manager.org.apache.juli.FileHandler.level
= FINE\r\n4host-manager.org.apache.juli.FileHandler.directory
= ${catalina.base}/logs\r\n4host-manager.org.apache.juli.FileHandler.prefix
= host-manager. \r\n\r\njava.util.logging.consoleHandler.level
= FINE\rl njava.util.logging.ConsoleHandler.formatter
= java.util.logging.SimpleFormatter\r\n\r\n\r\norg.apache.catalina.core.
ContainerBase.[Catalina].[localhost].level
= INFO\r\norg.apache.catalina.core.ContainerBase.[Catalina].[localhost].handlers
= 2localhost.org.apache.juli.FileHandler\r\n\r\norg.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].level
= INFO\r\norg.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].handlers
= 3manager.org.apache.juli.FileHandler\r\n\r\norg.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].level
= INFO\r\norg.apache.catalina.core.ContainerBase.[Catalina].[localhost].[\host-manager].handlers
= 4host-manager.org.apache.juli.FileHandler\r\n\r\n"
运行kubectl create命令创建该ConfigMap
[root@k8s-m01 java_web]# kubectl create -f cm-appconfigfiles.yaml
configmap "cm-appconfigfiles" created
2.通过kubectl命令行方式创建
不使用YAML文件,直接通过kubectl create configmap也可以创建ConfigMap,可以使用参数--from-file或--from-literal指定内容,并且可以在一行命令中指定多个参数。
(1)通过--from-file参数从文件中进行创建,可以指定key的名称,也可以在一个命令行中创建包含多个key的ConfigMap,语法如下:
[root@k8s-m01 ~]# kubectl create configmap NAME --from-file=[key=]source --from-file=[key=]source
(2)通过--from-file参数在目录下进行创建,该目录下的每个配置文件名都被设置为key,文件的内容被设置为value,语法如下:
[root@k8s-m01 ~]# kubectl create configmap NAME --from-file=config-files-dir
(3)使用--from-literal时会从文本中进行创建,直接将指定的key#=value#创建为ConfigMap的内容,语法如下:
[root@k8s-m01 ~]# kubectl create configmap NAME --from-literal=key1=value1 --from-literal=key2=value2
容器应用对ConfigMap的使用有以下两种方法。
(1)通过环境变量获取ConfigMap中的内容。
(2)通过Volume挂载的方式将ConfigMap中的内容挂载为容器内部的文件或目录。
3.在Pod中使用ConfigMap
1.通过环境变量方式使用ConfigMap
以前面创建的ConfigMap“cm-appvars”为例:
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-appvars
data:
apploglevel: info
appdatadir: /var/data
在Pod“cm-test-pod”的定义中,将ConfigMap“cm-appvars”中的内容以环境变量(APPLOGLEVEL和APPDATADIR)方式设置为容器内部的环境变量,容器的启动命令将显示这两个环境变量的值("env|grep APP"):
apiVersion: v1
kind: Pod
metadata:
name: cm-test-pod
spec:
containers:
- name: cm-test
image: busybox
command: [ "/bin/sh", "-c", "env | grep APP" ]
env:
- name: APPLOGLEVEL # 定义环境变量的名称
valueFrom: # key"apploglevel"对应的值
configMapKeyRef:
name: cm-appvars # 环境变量的值取自cm-appvars
key: apploglevel # key为apploglevel
- name: APPDATADIR # 定义环境变量的名称
valueFrom: # key "appdatadir"对应的值
configMapKeyRef:
name: cm-appvars # 环境变量的值取自cm-appvars
key: appdatadir # key为appdatadir
restartPolicy: Never
运行kubectl create-f命令创建该Pod,由于是测试Pod,所以该Pod在运行完启动命令后将会退出,并且不会被系统自动重启(restartPolicy=Never):
[root@k8s-m01 java_web]# kubectl create -f cm-test-pod.yaml
pod/cm-test-pod created
运行kubectl get pods -A命令查看已经停止的Pod:
[root@k8s-m01 java_web]# kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
default cm-test-pod 0/1 Completed 0 86s
查看该Pod的日志,可以看到启动命令env|grep APP的运行结果如下:
[root@k8s-m01 java_web]# kubectl logs cm-test-pod
APPDATADIR=/var/data
APPLOGLEVEL=info
这说明容器内部的环境变量使用ConfigMap cm-appvars中的值进行了正确设置。
Kubernetes从1.6版本开始引入了一个新的字段envFrom,实现了在Pod环境中将ConfigMap(也可用于Secret资源对象)中所有定义的key=value自动生成为环境变量:
apiVersion: v1
kind: Pod
metadata:
name: cm-test-pod
spec:
containers:
- name: cm-test
image: busybox
command: [ "/bin/sh", "-c", "env" ]
envFrom:
- configMapRef
name: cm-appvars # 根据cm-appvars中的key=value自动生成环境变量
restartPolicy: Never
通过这个定义,在容器内部将会生成如下环境变量:
apploglevel=info
appdatadir=/var/data
需要说明的是,环境变量的名称受POSIX命名规范([a-zA-Z][a-zA-Z0-9]*)约束,不能以数字开头。如果包含非法字符,则系统将跳过该条环境变量的创建,并记录一个Event来提示环境变量无法生成,但并不阻止Pod的启动。
2.通过volumeMount使用ConfigMap
在如下所示的cm-appconfigfiles.yaml例子中包含两个配置文件的定义:server.xml和logging.properties。
cm-appconfigfiles.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-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" />
<Connector port="8009" protocol="AJP/1.3" 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 %1 %u %t "&r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
key-loggingproperties: "handlers
= 1catalina.org.apache.juli.FileHandler, 2localhost.org.apache.juli.FileHandler, 3manager.org.apache.juli.FileHandler, 4host-manager.org.apache.juli.FileHandler, java.util.logging.ConsoleHandler\r\n\r\n.handlers = 1catalina.org.apache.juli.Filehandler, java.util.logging.ConsoleHandler\r\n\r\n1catalina.org.apache.juli.FileHandler.level
= FINE\r\nlcatalina.org.apache.juli.FileHandler.directory = ${catalina.base}/logs\r\n1catalina.org.apache.juli.FileHandler.prefix
= catalina.\r\n\r\n2localhost.org.apache.juli.FileHandler.level = FINE\r\n2localhost.org.apache.juli.FileHandler.director
= ${catalina.base}/logs\r\n2localhost.org.apache.juli.FileHandler.prefix = localhost.\r\n\r\n3manager.org.apache.juli.FileHandler.level
= FINE\r\n3manager.org.apache.juli.FileHandler.directory = ${catalina .base}/logs\r\n3manager.org.apache.juli.FileHandler.prefix
= manager.\r\n\r\n4host-manager.org.apache.juli.FileHandler.level = FINE\r\n4host-manager.org.apache.juli.FileHandler.directory
= ${catalina.base}/logs\r\n4host-manager.org.apache.juli.FileHandler.
prefix
= host-manager.\r\n\r\njava.util.logging.ConsoleHandler.level = FINE\r\njava.util.logging.ConsoleHandler.formatter
= java.util.logging.SimpleFormatter\r\n\r\n\r\norg.apache.catalina.core.ContainerBase.[Catalina].[localhost].level
=I NFO\r\norg.apache.catalina.core.ContainerBase.[Catalina].[localhost].handlers
= 2localhost.org.apache.juli.FileHandler\r\n\r\norg.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].level
= INFO\r\norg.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].handlers
= 3manager.org.apache.juli.FileHandler\r\n\r\norg.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].level
= INFO\r\norg.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].handlers
= 4host-manager.org.apache.juli.FileHandler\r\n\r\n"
在Pod“cm-test-app”的定义中,将ConfigMap“cm-appconfigfiles”中的内容以文件的形式挂载到容器内部的/configfiles目录下。Pod配置文件cm-test-app.yaml的内容如下:
apiVersion: v1
kind: Pod
metadata:
name: cm-test-app
spec:
containers:
- name: cm-test-app
image: kubeguide/tomcat-app:v1
ports:
- containerPort: 8080
volumeMounts:
- name: serverxml # 引用Volume的名称
mountPath: /configfiles # 挂载到容器内的目录
volumes:
- name: cm-appconfigfiles # 定义Volume的名称
configMap
name: cm-appconfigfiles # 使用ConfigMap"cm-appconfigfiles"
items:
- key: key.serverxml # key=key-serverxml
path: server.xml # value将server.xml文件名进行挂载
- key: key-loggingproperties # key=key-loggingproperties
path: loggin.properties # value将logging.properties文件名进行挂载
创建该Pod:
[root@k8s-m01 java_web]# kubectl create -f cm-test-app.yaml
登录容器,查看到在/configfiles目录下存在server.xml和logging.properties文件,它们的内容就是ConfigMap“cm-appconfigfiles”中两个key定义的内容:
[root@k8s-m01 java_web]# kubectl exec -it cm-test-app -- bash
root@cm-test-app:/# cat /configfiles/server.xml
root@cm-test-app:/# cat /configfiles/logging.properties
如果在引用ConfigMap时不指定items,则使用volumeMount方式在容器内的目录下为每个item都生成一个文件名为key的文件。
Pod配置文件cm-test-app.yaml的内容如下:
apiVersion: v1
kind: Pod
metadata:
name: cm-test-app
spec:
containers:
- name: cm-test-app
image: kubeguide/tomcat-app:v1
imagePullPolicy: Never
ports:
- containerPort: 8080
volumeMounts:
- name: serverxml # 引用Volume的名称
mountPath: /configfiles # 挂载到容器内的目录
volumes:
- name: serverxml # 定义Volume的名称
configMap:
name: cm-appconfigfiles # 使用ConfigMap"cm-appconfigfiles"
创建该Pod:
[root@k8s-m01 ~]# kubectl create -f cm-test-app.yaml
登录容器,查看到在/configfiles目录下存在key-loggingproperties和key-serverxml文件,文件的名称来自在ConfigMap cm-appconfigfiles中定义的两个key的名称,文件的内容则为value的内容:
# ls /configfiles
key-logginproperties key-serverxml
4.使用ConfigMap的限制条件
使用ConfigMap的限制条件如下。
- ConfigMap必须在Pod之前创建,Pod才能引用它。
- 如果Pod使用envFrom基于ConfigMap定义环境变量,则无效的环境变量名称(例如名称以数字开头)将被忽略,并在事件中被记录为InvalidVariableNames。
- ConfigMap受命名空间限制,只有处于相同命名空间中的Pod才可以引用它。
- ConfigMap无法用于静态Pod。
六.在容器内获取Pod信息(Downward API)
我们知道,Pod的逻辑概念在容器之上,Kubernetes在成功创建Pod之后,会为Pod和容器设置一些额外的信息,例如Pod级别的Pod名称、Pod IP、Node IP、Label、Annotation、容器级别的资源限制等。在很多应用场景中,这些信息对容器内的应用来说都很有用,例如使用Pod名称作为日志记录的一个字段用于标识日志来源。为了在容器内获取Pod级别的这些信息,Kubernetes提供了Downward API机制来将Pod和容器的某些元数据信息注入容器环境内,供容器应用方便地使用。
Downward API可以通过以下两种方式将Pod和容器的元数据信息注入容器内部。
(1)环境变量:将Pod或Container信息设置为容器内的环境变量。
(2)Volume挂载:将Pod或Container信息以文件的形式挂载到容器内部。
下面通过几个例子对Downward API的用法进行说明。
1.环境变量方式
通过环境变量的方式可以将Pod信息或Container信息注入容器运行环境中,下面通过两个例子进行说明。
1)将Pod信息设置为容器内的环境变量
下面的例子通过Downward API将Pod的IP、名称和所在命名空间注入容器的环境变量中,Pod的YAML文件内容如下:
[root@k8s-m01 ~]# cat dapi-envars-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: dapi-envars-fieldref
spec:
containers:
- name: test-container
image: busybox
command: [ "sh", "-c"]
args:
- while true; do
echo -en '\n';
printenv MY_NODE_NAME MY_POD_NAME MY_POD_NAMESPACE;
printenv MY_POD_IP MY_POD_SERVICE_ACCOUNT;
sleep 10;
done;
env:
- name: MY_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- 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
- name: MY_POD_SERVICE_ACCOUNT
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName
restartPolicy: Never
注意,环境变量不直接设置value,而是设置valueFrom对Pod的元数据进行引用。
在本例中通过对Downward API的设置使用了以下Pod的元数据信息设置环境变量。
◎ spec.nodeName:Pod所在Node的名称。
◎ metadata.name:Pod名称。
◎ metadata.namespace:Pod所在命名空间的名称。
◎ status.podIP:Pod的IP地址。
◎ spec.serviceAccountName:Pod使用的ServiceAccount名称。
运行kubectl create命令创建这个Pod:
[root@k8s-m01 ~]# kubectl create -f dapi-envars-pod.yaml
查看Pod的日志,可以看到容器启动命令将环境变量的值打印出来:
[root@k8s-m01 ~]# kubectl logs dapi-envars-fieldref
[root@k8s-m01 java_web]# kubectl logs dapi-envars-fieldref
k8s-node02
dapi-envars-fieldref
default
10.244.58.196
default
......
我们从日志中可以看到Pod的Node IP、Pod名称、命名空间名称、Pod IP、ServiceAccount名称等信息都被正确设置到了容器的环境变量中。
也可以通过kubectl exec命令登录容器查看环境变量的设置:
[root@k8s-m01 java_web]# kubectl exec -it dapi-envars-fieldref -- sh
/ # printenv | grep MY
MY_POD_SERVICE_ACCOUNT=default
MY_POD_NAMESPACE=default
MY_POD_IP=10.244.58.196
MY_NODE_NAME=k8s-node02
MY_POD_NAME=dapi-envars-fieldref
2)将Container信息设置为容器内的环境变量
下面的例子通过Downward API将Container的资源请求和资源限制信息设置为容器内的环境变量,Pod的YAML文件内容如下:
[root@k8s-m01 ~]# cat dapi-envars-container.yaml
apiVersion: v1
kind: Pod
metadata:
name: dapi-envars-resourcefieldref
spec:
containers:
- name: test-container
image: busybox
imagePullPolicy: Never
command: [ "sh", "-c"]
args:
- while true; do
echo -en '\n';
printenv MY_CPU_REQUEST MY_CPU_LIMIT;
printenv MY_MEM_REQUEST MY_MEM_LIMIT;
sleep 10;
done;
args:
- while true; do
echo -en '\n';
printenv MY_CPU_REQUEST MY_CPU_LIMIT;
printenv MY_MEM_REQUEST MY_MEM_LIMIT;
sleep 3600;
done;
resources:
requests:
memory: "32Mi"
cpu: "125m"
limits:
memory: "64Mi"
cpu: "250m"
env:
- name: MY_CPU_REQUEST
valueFrom:
resourceFieldRef:
containerName: test-container
resource: requests.cpu
- name: MY_CPU_LIMIT
valueFrom:
resourceFieldRef:
containerName: test-container
resource: limits.cpu
- name: MY_MEM_REQUEST
valueFrom:
resourceFieldRef:
containerName: test-container
resource: requests.memory
- name: MY_MEM_LIMIT
valueFrom:
resourceFieldRef:
containerName: test-container
resource: limits.memory
restartPolicy: Never
在本例中通过Downward API将以下Container的资源限制信息设置为环境变量。
◎ requests.cpu:容器的CPU请求值。
◎ limits.cpu:容器的CPU限制值。
◎ requests.memory:容器的内存请求值。
◎ limits.memory:容器的内存限制值。
运行kubectl create命令创建Pod:
[root@k8s-m01 java_web]# kubectl create -f dapi-envars-container.yaml
pod/dapi-envars-resourcefieldref created
查看Pod的日志:
[root@k8s-m01 java_web]# kubectl logs dapi-envars-resourcefieldref
1
1
33554432
67108864
我们从日志中可以看到Container的requests.cpu、limits.cpu、requests.memory、limits.memory等信息都被正确保存到了容器内的环境变量中。
通过Volume挂载方式
通过Volume挂载方式可以将Pod信息或Container信息挂载为容器内的文件,下面通过两个例子进行说明。
1)将Pod信息挂载为容器内的文件
下面的例子通过Downward API将Pod的Label、Annotation信息通过Volume挂载为容器中的文件:
[root@k8s-m01 ~]# cat dapi-volume.yaml
apiVersion: v1
kind: Pod
metadata:
name: kubernetes-downwardapi-volume-example
labels:
zone: us-est-coast
cluster: test-cluster1
rack: rack-22
annotations:
bulid: two
bulider: john-doe
spec:
containers:
- name: client-container
image: busybox
command: ["sh", "-c"]
args:
- while true; do
if [[ -e /etc/podinfo/labels ]]; then
echo -en '\n\n'; cat /etc/podinfo/labels; fi;
if [[ -e /etc/podinfo/annotations ]]; then
echo -en '\n\n'; cat /etc/podinfo/annotations; fi;
sleep 5;
done;
volumeMounts:
- name: podinfo
mountPath: /etc/podinfo
volumes:
- name: podinfo
downwardAPI:
items:
- path: "labels"
fieldRef:
fieldPath: metadata.labels
- path: "annotations"
fieldRef:
fieldPath: metadata.annotations
在Pod的volumes字段中使用Downward API的方法:通过fieldRef字段设置需要引用Pod的元数据信息,将其设置到volume的items中。在本例中使用了以下Pod元数据信息。
◎ metadata.labels:Pod的Label列表。
◎ metadata.namannotations:Pod的Annotation列表。
然后,通过容器级别volumeMounts的设置,系统会基于volume中各item的path名称生成文件。根据上面的设置,系统将在容器内的/etc/podinfo目录下生成labels和annotations两个文件,在labels文件中将包含Pod的全部Label列表,在annotations文件中将包含Pod的全部Annotation列表。
运行kubectl create命令创建Pod:
[root@k8s-m01 java_web]# kubectl create -f dapi-volume.yaml
pod/kubernetes-downwardapi-volume-example created
查看Pod的日志,可以看到容器启动命令将挂载文件的内容打印出来:
[root@k8s-m01 java_web]# kubectl logs kubernetes-downwardapi-volume-example
cluster="test-cluster1"
rack="rack-22"
zone="us-est-coast"
bulid="two"
bulider="john-doe"
cni.projectcalico.org/containerID="2fac7806d596294e8aae29cbbcff227924514b495b48cb46ec3972ffca97e114"
cni.projectcalico.org/podIP="10.244.58.197/32"
cni.projectcalico.org/podIPs="10.244.58.197/32"
kubernetes.io/config.seen="2022-11-22T14:32:28.512114862+08:00"
kubernetes.io/config.source="api"
......
进入容器,查看挂载的文件:
[root@k8s-m01 java_web]# kubectl exec -it kubernetes-downwardapi-volume-example -- sh
/ # ls -l /etc/podinfo/
total 0
lrwxrwxrwx 1 root root 18 Nov 22 06:32 annotations -> ..data/annotations
lrwxrwxrwx 1 root root 13 Nov 22 06:32 labels -> ..data/labels
查看文件labels的内容:
# cat /etc/podinfo/labels
cluster="test-cluster1"
rack="rack-22"
zone="us-est-coast"
2)将Container信息挂载为容器内的文件
下面的例子通过Downward API将Container的资源限制信息通过Volume挂载为容器中的文件:
[root@k8s-m01 ~]# cat dapi-volume-resources.yaml
apiVersion: v1
kind: Pod
metadata:
name: kubernetes-downwardapi-volume-example-2
spec:
containers:
- name: client-container
image: k8s.gcr.io/busybox:1.24
command: ["sh", "-c"]
args:
- while true; do
echo -en '\n';
if [[ -e /etc/podinfo/cpu_limit ]]; then
echo -en '\n'; cat /etc/podinfo/cpu_limit; fi;
if [[ -e /etc/podinfo/cpu_limit ]]; then
echo -en '\n'; cat /etc/podinfo/cpu_request; fi;
if [[ -e /etc/podinfo/cpu_limit ]]; then
echo -en '\n'; cat /etc/podinfo/mem_limit; fi;
if [[ -e /etc/podinfo/cpu_limit ]]; then
echo -en '\n'; cat /etc/podinfo/cpu_request; fi;
sleep 5;
done;
resources:
requests:
memory: "32Mi"
cpu: "125m"
limits:
memory: "64Mi"
cpu: "250m"
volumeMounts:
- name: podinfo
mountPath: /etc/podinfo
volumes:
- name: podinfo
downwardAPI:
items:
- path: "cpu_limit"
resourceFieldRef:
containerName: client-container
resource: limits.cpu
divisor: 1m
- path: "cpu_request"
resourceFieldRef:
containerName: client-container
resource: requests.cpu
divisor: 1m
- path: "mem_limit"
resourceFieldRef:
containerName: client-container
resource: limit.memory
divisor: 1Mi
- path: "mem_request"
resourceFieldRef:
containerName: client-container
resource: requests.memory
divisor: 1Mi
在本例中通过Downward API设置将以下Container的资源限制信息设置到Volume中。
◎ requests.cpu:容器的CPU请求值。
◎ limits.cpu:容器的CPU限制值。
◎ requests.memory:容器的内存请求值。
◎ limits.memory:容器的内存限制值。
运行kubectl create命令创建Pod:
[root@k8s-m01 java_web]# kubectl create -f dapi-volume-resources.yaml
pod/kubernetes-downwardapi-volume-example-2 created
查看Pod的日志,可以看到容器启动命令将挂载文件的内容打印出来:
[root@k8s-m01 java_web]# kubectl logs kubernetes-downwardapi-volume-example-2
250
125
64
12
进入容器,查看挂载的文件:
[root@k8s-m01 java_web]# kubectl exec -it kubernetes-downwardapi-volume-example-2 -- sh
/ # ls -l /etc/podinfo/
total 0
lrwxrwxrwx 1 root root 16 Nov 22 08:44 cpu_limit -> ..data/cpu_limit
lrwxrwxrwx 1 root root 18 Nov 22 08:44 cpu_request -> ..data/cpu_request
lrwxrwxrwx 1 root root 16 Nov 22 08:44 mem_limit -> ..data/mem_limit
lrwxrwxrwx 1 root root 18 Nov 22 08:44 mem_request -> ..data/mem_request
查看文件cpu_limit的内容:
/ # cat /etc/podinfo/cpu_limit
250
3.Downward API支持设置的Pod和Container信息
Downward API支持设置的Pod和Container信息如下。
1)可以通过fieldRef设置的元数据如下。
- metadata.name:Pod名称。
- metadata.namespace:Pod所在的命名空间名称。
- metadata.uid:Pod的UID,从Kubernetes 1.8.0-alpha.2版本开始支持。
- metadata.labels['
']:Pod某个Label的值,通过 进行引用,从Kubernetes 1.9版本开始支持。 - metadata.annotations['
']:Pod某个Annotation的值,通过 进行引用,从Kubernetes 1.9版本开始支持。
2)可以通过resourceFieldRef设置的数据如下。
- Container级别的CPU Limit。
- Container级别的CPU Request。
- Container级别的Memory Limit。
- Container级别的Memory Request。
- Container级别的临时存储空间(ephemeral-storage)Limit,从Kubernetes 1.8.0-beta.0版本开始支持。
- Container级别的临时存储空间(ephemeral-storage)Request,从Kubernetes 1.8.0-beta.0版本开始支持。
3)对以下信息通过fieldRef字段进行设置。
- metadata.labels:Pod的Label列表,每个Label都以key为文件名,value为文件内容,每个Label各占一行。
- metadata.namannotations:Pod的Annotation列表,每个Annotation都以key为文件名,value为文件内容,每个Annotation各占一行。
4)以下Pod的元数据信息可以被设置为容器内的环境变量。
- status.podIP:Pod的IP地址。
- spec.serviceAccountName:Pod使用的ServiceAccount名称。
- spec.nodeName:Pod所在Node的名称,从Kubernetes 1.4.0-alpha.3版本开始支持。
- status.hostIP:Pod所在Node的IP地址,从Kubernetes 1.7.0-alpha.1版本开始支持。
Downward API在volume subPath中的应用
有时,容器内挂载目录的子路径(volumeMounts.subPath)也需要使用Pod或Container的元数据信息,Kubernetes从1.11版本开始支持通过Downward API对子路径的名称进行设置,引入了一个新的subPathExpr字段,到1.17版本达到Stable阶段。用户可以将Pod或Container信息先使用Downward API设置到环境变量上,再通过subPathExpr将其设置为subPath的名称。
通过Kubernetes提供的Downward API机制,只需经过一些简单配置,容器内的应用就可以直接使用Pod和容器的某些元数据信息了。
七.Pod生命周期和重启策略
Pod在整个生命周期中被系统定义为各种状态,熟悉Pod的各种状态对于理解如何设置Pod的调度策略、重启策略是很有必要的。
Pod的状态如下所示。
状态值 | 描述 |
---|---|
Pending | API Server已经创建该Pod, 但在Pod内还有一个或多个容器没有创建,包括正在下载镜像的过程 |
Running | Pod内所有容器均已创建,且至少有一个容器处于运行状态、正在启动状态或正在重启状态 |
Succeeded | 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的状态和重启策略,列出一些常见的状态转换场景。
Pod包含的容器数 | Pod当前的状态 | 发生事件 | RestartPolicy=Always Pod的结果状态 | RestartPolicy=OnFailure Pod的结果状态 | RestartPolicy=Never Pod的结果状态 |
---|---|---|---|---|---|
包含1个容器 | running | 容器退出成功 | Running | Succeeded | Succeeded |
包含1个容器 | running | 容器退出失败 | Running | Running | Failed |
包含1个容器 | running | 1个容器退出失败 | Running | Running | Running |
包含1个容器 | running | 容器被OOM"杀掉" | Running | Runing | Failed |
八.Pod健康检查和服务可用性检查
Kubernetes对Pod的健康状态可以通过三类探针来检查:LivenessProbe、ReadinessProbe及StartupProbe,其中最主要的探针为LivenessProbe与ReadinessProbe,kubelet会定期执行这两类探针来诊断容器的健康状况。
(1)LivenessProbe探针:用于判断容器是否存活(Running状态),如果LivenessProbe探针探测到容器不健康,则kubelet将“杀掉”该容器,并根据容器的重启策略做相应的处理。如果一个容器不包含LivenessProbe探针,那么kubelet认为该容器的LivenessProbe探针返回的值永远是Success。
(2)ReadinessProbe探针:用于判断容器服务是否可用(Ready状态),达到Ready状态的Pod才可以接收请求。对于被Service管理的Pod,Service与Pod Endpoint的关联关系也将基于Pod是否Ready进行设置。如果在运行过程中Ready状态变为False,则系统自动将其从Service的后端Endpoint列表中隔离出去,后续再把恢复到Ready状态的Pod加回后端Endpoint列表。这样就能保证客户端在访问Service时不会被转发到服务不可用的Pod实例上。需要注意的是,ReadinessProbe也是定期触发执行的,存在于Pod的整个生命周期中。
(3)StartupProbe探针:某些应用会遇到启动比较慢的情况,例如应用程序启动时需要与远程服务器建立网络连接,或者遇到网络访问较慢等情况时,会造成容器启动缓慢,此时ReadinessProbe就不适用了,因为这属于“有且仅有一次”的超长延时,可以通过StartupProbe探针解决该问题。