Kubernetes Pod基础

Kubernetes Pod基础

Kubernetes Pod

简介

PodKubernetes集群运行的最小单元,每个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

image-20210129171615178

属于同一个Pod的多个容器应用之间相互访问时仅需要通过localhost就可以通信。

静态Pod

静态Pod是由kubelet进行管理的仅存在于特定Node上的Pod。它们不能通过Api Server进行管理,无法与ReplicationController、Deployment或DaemonSet进行关联,并且kuberlet无法对其进行健康检查。静态Podkubelet创建,并且总在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级别的存储卷VolumeVolume可以被定义为各种类型,多个容器各自进行挂载操作,将一个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.yamllogging.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 &quot;%r&quot; %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 &quot;%r&quot; %s %b" />
      </Host>
    </Engine>
  </Service>
</Server>

Events:  <none>
  • 通过kubectl命令行方式创建

不使用Yaml文件,可以直接使用kubectl create configmap创建,也可以使用参数--from-file或者--from-literal指定内容:

  1. 通过--from-file参数从文件中进行创建,可以指定key的名称,也可以在一个命令行中创建包含多个keyConfigMap,语法为:
kubectl create configmap NAME --from-file=[key=]sorce --from-file=[key=]source
  1. 通过--from-file参数从目录中进行创建,目录下的每个配置文件名被设置为key,文件内容被设置为value,语法:
kubectl create configmap NAME --from-file=config-files-dir
  1. 指定--from-literal参数,将从文本中进行创建,直接指定为key=value创建ConfigMap,语法:
kubectl create configmap NAME --from-literal=key1=value1 --from-litaral=key2=value2

使用案例:

  1. 当前目录下存放配置文件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'?>
...
  1. 根据配置文件目录configs下的两个配置文件server.xmlcatalina.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>
  1. 使用--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的使用方式有两种:

  1. 通过环境变量获取ConfigMap的内容
  2. 通过Volume挂载方式将ConfigMap中的内容挂载为容器内的文件或目录

在Pod中使用ConfigMap

  1. 通过环境变量方式

查询configmap-appvarsConfigMap

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的启动。

  1. 通过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信息注入容器内部:

  1. 环境变量
  2. Volume挂载

环境变量方式:将Pod信息注入为环境变量

下面的例子通过Downward APIPod的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 APIContainer的资源请求和限制信息注入容器的环境变量中,容器应用使用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的LabelAnnotation列表通过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的调度策略、重启策略是很有必要的。

状态值描述
PendingAPI Server已经创建该Pod,但是Pod内还有一个或多个容器镜像未创建,包括下载镜像的过程。
RunningPod内的所有容器均已创建,且至少一个容器处在运行状态、启动状态或重启状态
SuccessedPod内所有容器均成功执行后退出,且不会重启
FailedPod内所有容器均已退出,但是至少有一个容器退出为失败状态
Unknown由于某种原因无法获取该Pod的状态,可能由于网络通信不畅导致

Pod的重启策略(restartPolicy)应用于Pod内所有容器,并且仅在Pod所处的Node上由kubelet进行判断和重启操作。当某个容器异常退出或者健康检查失败时,kubelet将根据RestartPolicy的设置来进行相应的操作。

Pod的重启策略包括Always、OnFailureNever,默认值为Always。

  • Always:当容器失效时,由kubelet自动重启该容器。
  • OnFailure:当容器终止运行且退出码不为0时,由kubelet自动重启该容器。
  • Never:不论容器运行状态如何,kubelet都不会重启该容器。

kubelet重启失效容器的时间间隔以sync-frequency乘以2n来计算,例如1、2、4、8倍等,最长延时5min,并且在成功重启后的10min后重置该时间。

Pod的重启策略与控制方式息息相关,当前可用于管理Pod的控制器包括ReplicationControllerJobDaemonSet及直接通过kubelet管理(静

态Pod)。每种控制器对Pod的重启策略要求如下:

  • RC和DaemonSet:必须设置为Always,需要保证该容器持续运行。
  • Job:OnFailure或Never,确保容器执行完成后不再重启。
  • kubelet:在Pod失效时自动重启它,不论将RestartPolicy设置为什么值,也不会对Pod进行健康检查。

Pod监控检查和服务可用性检查

KubernetesPod的健康状态可以通过两类探针来检查:LivenessProbeReadinessProbekubelet定期执行这两类探针来诊断容器的健康状况。

  1. LivenessProbe探针:用于判断容器是否存活(Running状态),如果LivenessProbe探针探测到容器不健康,则kubelet将杀掉该容器,并根据容器的重启策略做相应的处理。如果一个容器不包含LivenessProbe探针,那么kubelet认为该容器的LivenessProbe探针返回的值永远是Success
  2. ReadinessProbe探针:用于判断容器服务是否可用(Ready状态),达到Ready状态的Pod才可以接收请求。对于被Service管理的PodServicePod Endpoint的关联关系也将基于Pod是否Ready进行设置。如果在运行过程中Ready状态变为False,则系统自动将其从Service的后端Endpoint列表中隔离出去,后续再把恢复到Ready状态的Pod加回后端Endpoint列表。这样就能保证客户端在访问Service时不会被转发到服务不可用的Pod实例上。

LivenessProbeReadinessProbe均可配置以下三种实现方式:

  1. 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
  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
  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

对于每种探测方式,都需要设置initialDelaySecondstimeoutSeconds两个参数,它们的含义分别如下:

  • initialDelaySeconds:启动容器后进行首次健康检查的等待时间,单位为s。

  • timeoutSeconds:健康检查发送请求后等待响应的超时时间,单位为s。当超时发生时,kubelet会认为容器已经无法提供服务,将会重启该容器。

KubernetesReadinessProbe机制可能无法满足某些复杂应用对容器内服务可用状态的判断,所以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)副本控制器,随着版本发展,出现了新的继承者ReplicaSetReplicaSet进一步增强了RC标签选择器的灵活性。之前RC的标签 选择器只能选择一个标签,而ReplicaSet拥有集合式的标签选择器,可以选择多个Pod标签。与RC不同,ReplicaSet被设计成能控制多个不同标签的Pod副本。一个ReplicaSet对象,其实就是由副本数目的定义和一个Pod模板组成的。说到ReplicaSet,我们更要说下Deployment,它是ReplicaSet之前的一个管理层控制器,可以用于更加自动地完成Pod副本的部署、版本更新、回滚等功能。

Kubernetes的滚动升级就是巧妙运用ReplicaSet的这个特性来实现的,同时,Deployment也是通过ReplicaSet来实现Pod副本自动控制功能的。我们不应该直接使用底层的ReplicaSet来控制Pod副本,而应该使用管理ReplicaSetDeployment对象来控制副本,这是来自官方的建议。

在大多数情况下,我们希望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(节点亲和性设置)来解决该需求。在真实的生产环境中还存在如下所述的特殊需求:

  1. 不同Pod之间的亲和性(Affinity)。比如MySQL数据库与Redis中间件不能被调度到同一个目标节点上,或者两种不同的Pod必须被调度到同一个Node上,以实现本地文件共享或本地网络通信等特殊需求,这就是PodAffinity要解决的问题。
  2. 有状态集群的调度。对于ZooKeeperElasticsearchMongoDBKafka等有状态集群,虽然集群中的每个Worker节点看起来都是相同的,但每个Worker节点都必须有明确的、不变的唯一ID(主机名或IP地址),这些节点的启动和停止次序通常有严格的顺序。此外,由于集群需要持久化保存状态数据,所以集群中的Worker节点对应的Pod不管在哪个Node上恢复,都需要挂载原来的Volume,因此这些Pod还需要捆绑具体的PV。针对这种复杂的需求,Kubernetes提供了StatefulSet这种特殊的副本控制器来解决问题,在Kubernetes 1.9版本发布后,StatefulSet才用于正式生产环境中。
  3. 在每个Node上调度并且仅仅创建一个Pod副本。这种调度通常用于系统监控相关的Pod,比如主机上的日志采集、主机性能采集等进程需要被部署到集群中的每个节点,并且只能部署一个副本,这就是DaemonSet这种特殊Pod副本控制器所解决的问题。
  4. 对于批处理作业,需要创建多个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

DeploymentRC的主要功能之一就是自动部署一个容器应用的多份副本,以及持续监控副本的数量,在集群内始终维持用户指定的副本数量。

案例:使用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

通过这张图,我们就很清楚地看到,一个定义了 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,就是 KubernetesDeployment 保留的“历史版本”个数。所以,如果把它设置为 0,你就再也不能做回滚操作了。

通过运行kubectl get rskubectl 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的精准调度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值