第三章·深入掌握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所示。

file

配置文件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的内容如下

file

file

运行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所示。
file

在下面的例子中,在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..log的内容。登录tomcat容器进行查看:

[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探针解决该问题。


山林不向四季起誓 荣枯随缘