月度归档:2019年11月

Kubernetes StatefulSet


2019年11月27日 15:54:41   1,187 次浏览

前面介绍的Pod管理对象,如RC/RS、Deployment、DaemonSet等都是面向无状态服务的,而对于有状态的应用,比如MySQL集群,MongoDB集群等,则可以使用StatefulSet来完成。有状态的应用集群通常有以下这些特点:

  1. 每个节点都有固定的身份ID,通过这个ID,集群中的成员可以相互发现并通信;
  2. 集群的规模是比较固定的,集群规模不能随意变动;
  3. 集群中的每个节点都是有状态的,通常会持久化数据到永久存储中。

StatefulSet特性

通过StatefulSet搭建的集群通常有以下这些特性:

  1. StatefulSet里的每个Pod都有稳定、唯一的网络标识,可以用来发现集群内的其他成员。假设StatefulSet的名称为nginx,那么第1个Pod叫nginx-0,第2个叫nginx-1,以此类推;
  2. StatefulSet控制的Pod副本的启停顺序是受控的,操作第n个Pod时,前n-1个Pod已经是运行且准备好的状态。
  3. StatefulSet里的Pod采用稳定的持久化存储卷,通过PV或PVC来实现,删除Pod时默认不会删除与StatefulSet相关的存储卷(为了保证数据的安全);
  4. 配合Headless Service使用,用于发现和控制Pod实例数量。

StatefulSet实践

下面使用StatefulSet搭建个Nginx集群,持久化存储使用上一节搭建的名称为managed-nfs-storage的StorageClass。

创建nginx-headless-service.yml配置文件:

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    name: nginx
spec:
  ports:
    - port: 80
      targetPort: 80
  clusterIP: None # 表明为Headleass Service
  selector:
    role: web-app

创建该Headless Service:

kubectl create -f nginx-headless-service.yml

接着创建nginx-statefulset.yml配置文件:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx-sc
spec:
  serviceName: "nginx" # 对应刚刚创建的Headless Service名称
  replicas: 3
  selector:
    matchLabels:
      role: web-app
  template:
    metadata:
      labels:
        role: web-app
    spec:
      terminationGracePeriodSeconds: 10
      containers:
        - name: nginx
          image: nginx
          ports:
            - containerPort: 80
          volumeMounts:
            - mountPath: /usr/share/nginx/html
              name: nginx-persistent-storage # 和下面的pvc名称对应
  volumeClaimTemplates: # pvc模板
    - metadata:
        name: nginx-persistent-storage # pvc名称
      spec:
        storageClassName: managed-nfs-storage # 指定StorageClass名称
        accessModes: ["ReadWriteMany"]
        resources:
          requests:
            storage: 100Mi

创建该StatefulSet,观察pod的创建过程:

可以看到,pod的创建是严格one by one的,因为我们定义的StatefulSet的名称位nginx-sc,所以Pod的名称分别位nginx-sc-0、nginx-sc-1和nginx-sc-2。

查看对应的PVC和PV:

状态为Bound。

删除Pod,再次观察Pod的创建过程:

可以看到顺序性是严格保证的。

进入到Pod内部,查看其hostname:

hostname和pod名称一致。

到192.168.33.13的/nfs目录下可以看到挂载了三个nginx目录:

Kubernetes StorageClass实践


2019年11月26日 15:45:38   1,651 次浏览

手动创建PV不仅繁琐,还可能造成资源浪费。比如某个PV定义的存储空间为10Gi,该PV被某个声明需要8Gi内存的PVC绑定上了,这时候该PV处于Bound状态,无法再和别的PVC进行绑定,PV上剩下的2Gi内存实际上浪费的。StorageClass可以根据PVC的声明,动态创建对应的PV,这样不仅省去了创建PV的过程,还实现了存储资源的动态供应。

StorageClass构成

StorageClass的定义主要包括名称、后端存储的提供者(provisioner)和后端存储的相关参数配置。StorageClass一旦被创建出来,则将无法修改。如需更改,则只能删除原StorageClass的定义重建。

举个简单的StorageClass配置:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage # StorageClass名称
provisioner: fuseim.pri/ifs  # 指定具体存储的提供者
parameters: # 后端存储相关参数配置
  archiveOnDelete: "false"

不同的StorageClass主要区别在于:不同的存储提供者需要填写不同的参数配置,下面实现个NFS作为动态StorageClass存储的例子。

基于NFS存储类型的实践

NFS环境在上一节已经搭建好了,IP为192.168.33.13,路径为/nfs。

在master节点上克隆相关代码:

git clone https://github.com/kubernetes-incubator/external-storage.git

切换到external-storage/nfs-client/deploy目录

cd external-storage/nfs-client/deploy

创建RBAC:

kubectl create -f rbac.yml

接着部署NFS Client Provisioner,部署前修改deployment.yml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nfs-client-provisioner
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-provisioner
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: quay.io/external_storage/nfs-client-provisioner:latest
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: mrbird.cc/nfs # 名称随你定义
            - name: NFS_SERVER
              value: 192.168.33.13 # NFS 服务IP
            - name: NFS_PATH
              value: /nfs # NFS 目录
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.33.13 # NFS 服务IP
            path: /nfs # NFS 目录

然后创建该deployment:

kubectl create -f deployment.yml

修改class.yaml配置:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage # StorageClass名称,随你定义
provisioner: mrbird.cc/nfs # 和上面deployment里定义的一致
parameters:
  archiveOnDelete: "false"

创建这个StorageClass:

kubectl create -f class.yml

创建PVC,PVC的定义对应test-claim.yml:

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-claim
spec:
  storageClassName: managed-nfs-storage # 指定StorageClass名称,即我们上面定义的StorageClass
  accessModes:
    - ReadWriteMany
  resources: 
    requests:
      storage: 1Mi

创建这个PVC:

kubectl create -f test-claim.yml

状态为Bound,说明已经成功绑定上了存储。

查看PV,会看到系统自动创建了PV:

自动创建的PV名称为pvc-3b8c5364-aadb-4b07-a3b5-fddad280fc98。

最后,修改test-pod.yml配置:

kind: Pod
apiVersion: v1
metadata:
  name: test-pod
spec:
  containers:
  - name: test-pod
    image: busybox # 修改为这个,原先的镜像地址需要科学上网
    command:
      - "/bin/sh"
    args:
      - "-c"
      - "touch /mnt/SUCCESS && exit 0 || exit 1"
    volumeMounts:
      - name: nfs-pvc
        mountPath: "/mnt"
  restartPolicy: "Never"
  volumes:
    - name: nfs-pvc
      persistentVolumeClaim:
        claimName: test-claim # 指定PVC名称,对应上面创建的PVC

该Pod主要就是通过busybox在/mnt目录下创建了个SUCCESS文件。

创建该Pod:

kubectl create -f test-pod.yml

状态为Completed,说明busybox成功执行完了命令并结束了。如果一切顺利的话,在192.168.33.13的/nfs目录下会看到busybox pod创建的SUCCESS文件:

可以看到/nfs目录下新增了一个目录,目录名称格式为:[namespace]-[pvc名称]-[pv名称]。

我们还可以玩一下另一个测试,新建一个test-pod-rc.yml文件:

apiVersion: v1
kind: ReplicationController
metadata:
  name: busybox
spec:
  replicas: 2 # 副本数为2,为了测试共享存储
  selector:
    name: busybox
  template:
    metadata:
      labels:
        name: busybox
    spec:
      containers:
        - image: busybox
          command:
            - sh
            - -c
            - 'while true; do sleep $(($RANDOM % 5 + 5)); done'
          imagePullPolicy: IfNotPresent
          name: busybox
          volumeMounts:
            - name: nfs
              mountPath: "/mnt"
      volumes:
        - name: nfs
          persistentVolumeClaim:
            claimName: test-claim # 指定PVC名称,对应上面创建的PVC

上面busybox做的事情很简单,就是无限期休眠。创建该rc:

可以看到,pod分别部署到了node1和node2上。到node1节点上,进入busybox容器内部的/mnt目录,在该目录下创建一个hello文件:

然后到node2节点上,进入busybox容器内部/mnt目录,观察刚刚在node1节点busybox容器内部创建的hello文件是否已经同步过来了:

可以看到,文件同步成功。并且前面例子创建SUCCESS也同步过来了,这是因为它们指定了同一个PVC。

回到NFS服务器的/nfs目录下,可以看到刚刚创建的hello文件:

定时自动重启Pod服务


2019年11月09日 03:25:44   6,830 次浏览

方法1:滚动重启

从1.15版开始,Kubernetes允许您滚动重启部署。作为Kubernetes的新增功能,这是最快的重启方法。

kubectl rollout restart deployment [deployment_name]

上面提到的命令将逐步执行关闭操作,并重新启动deployment中的每个pod容器。在重启过程中应用仍然可用,因为大多数容器仍在运行。

方法2:使用环境变量

另一种方法是设置或更改环境变量,以强制Pod重新启动并与您所做的更改同步。

例如,可以更改容器部署日期:

kubectl set env deployment [deployment_name] DEPLOY_DATE="$(date)"

在上面的示例中,该命令set env设置环境变量的更改,deployment [deployment_name]选择您的部署,并DEPLOY_DATE="$(date)"更改部署日期。

方法3:缩放副本数

我们可以使用该scale命令来更改deployment的副本数。将此数量设置为0实际上会关闭容器:

kubectl scale deployment [deployment_name] --replicas=0

要重新启动Pod,请使用相同的命令将副本数设置为大于零的任何值:

kubectl scale deployment [deployment_name] --replicas=1

将副本数设置为0时,Kubernetes会销毁副本。

将数字设置为大于0后,Kubernetes将创建新副本。新副本的名称将与旧副本的名称不同。您可以使用该命令kubectl get pods检查pod的状态,并查看新名称。

上面我们是通过(kubectl rollout重新启动) ,部署创建新的副本,并等待它们启动,然后删除旧的pods,并重新路由流量。服务将不间断地继续。接下来使用cronjob命令配合rollout restart每天定时重启动pod。

在开始之前必须先设置RBAC,以便从集群内部运行的Kubernetes client具有对Kubernetes API执行所需调用的权限。

---
# Service account the client will use to reset the deployment,
# by default the pods running inside the cluster can do no such things.
kind: ServiceAccount
apiVersion: v1
metadata:
  name: deployment-restart
  namespace: <YOUR NAMESPACE>
---
# allow getting status and patching only the one deployment you want
# to restart
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: deployment-restart
  namespace: <YOUR NAMESPACE>
rules:
  - apiGroups: ["apps", "extensions"]
    resources: ["deployments"]
    resourceNames: ["<YOUR DEPLOYMENT NAME>"]
    verbs: ["get", "patch", "list", "watch"] # "list" and "watch" are only needed
                                             # if you want to use `rollout status`
---
# bind the role to the service account
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: deployment-restart
  namespace: <YOUR NAMESPACE>
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: deployment-restart
subjects:
  - kind: ServiceAccount
    name: deployment-restart
    namespace: <YOUR NAMESPACE>

cronjob配置:

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: deployment-restart
  namespace: <YOUR NAMESPACE>
spec:
  concurrencyPolicy: Forbid
  schedule: '0 8 * * *' # cron spec of time, here, 8 o'clock
  jobTemplate:
    spec:
      backoffLimit: 2 # this has very low chance of failing, as all this does
                      # is prompt kubernetes to schedule new replica set for
                      # the deployment
      activeDeadlineSeconds: 600 # timeout, makes most sense with 
                                 # "waiting for rollout" variant specified below
      template:
        spec:
          serviceAccountName: deployment-restart # name of the service
                                                 # account configured above
          restartPolicy: Never
          containers:
            - name: kubectl
              image: bitnami/kubectl # probably any kubectl image will do,
                                     # optionaly specify version, but this
                                     # should not be necessary, as long the
                                     # version of kubectl is new enough to
                                     # have `rollout restart`
              command:
                - 'kubectl'
                - 'rollout'
                - 'restart'
                - 'deployment/<YOUR DEPLOYMENT NAME>'

如果希望cronjob等待部署完成,可以将cronjob命令更改为:

command:
 - bash
 - -c
 - >-
   kubectl rollout restart deployment/<YOUR DEPLOYMENT NAME> &&
   kubectl rollout status deployment/<YOUR DEPLOYMENT NAME>

Kubernetes Pod扩容与缩容


2019年11月07日 14:06:42   1,670 次浏览

针对不同时期流量的大小我们可以给Pod扩缩容,Kubernetes支持通过kubectl命令手动扩缩容,也支持通过HPA自动横向扩缩容。

手动扩缩容

现有如下deployment配置(nginx-deployment.yml):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      name: nginx
  replicas: 3
  template:
    metadata:
      labels:
        name: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.7.9
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80

创建该Deployment:

有3个nginx实例,现在用下面这条命令将nginx实例扩充到5个:

kubectl scale deployment nginx-deployment --replicas 5

缩容:

kubectl scale deployment nginx-deployment --replicas 1

HPA

HPA能够根据特定指标完成目标Pod的自动扩缩容。创建一个与上面nginx-deployment相对应的HPA(nginx-deployment-hpa.yml):

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: nginx-deployment-hpa-v1
spec:
  maxReplicas: 6
  minReplicas: 2
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: nginx-deployment
  targetCPUUtilizationPercentage: 50

主要参数如下:

  1. scaleTargetRef:目标作用对象,可以是Deployment、ReplicationController或ReplicaSet。
  2. targetCPUUtilizationPercentage:期望每个Pod的CPU使用率都为50%,该使用率基于Pod设置的CPU Request值进行计算,例如该值为200m,那么系统将维持Pod的实际CPU使用值为100m。
  3. minReplicas和maxReplicas:Pod副本数量的最小值和最大值,系统将在这个范围内进行自动扩缩容操作,并维持每个Pod的CPU使用率为50%。

使用HPA,需要预先安装Metrics Server,用于采集Pod的CPU使用率。

Kubernetes Service基础


2019年11月07日 13:29:07   1,484 次浏览

Kubernetes可以为一组具有相同功能的Pod提供一个统一的入口地址,并且将请求均衡的转发到各个对应的Pod上。本节主要记录Service的一些基本用法。

基本用法

创建一个Tomcat RC(tomcat-rc.yml):

apiVersion: v1
kind: ReplicationController
metadata:
  name: tomcat-rc
spec:
  replicas: 2
  selector:
    name: tomcat
  template:
    metadata:
      name: tomcat
      labels:
        name: tomcat
    spec:
      containers:
        - name: tomcat
          image: tomcat
          ports:
            - containerPort: 8080

创建该RC:

我们可以通过Node IP + Container Port在Kubernetes集群中访问Tomcat:

由于Pod是Kubernetes集群范围内的虚拟概念,集群外的客户端无法通过Pod的IP和端口访问,我们可以将Pod的端口号映射到宿主机,以使客户端应用能够通过物理机访问容器应用,修改刚刚的tomcat-rc.yml,添加hostPort:

apiVersion: v1
kind: ReplicationController
metadata:
  name: tomcat-rc
spec:
  replicas: 2
  selector:
    name: tomcat
  template:
    metadata:
      name: tomcat
      labels:
        name: tomcat
    spec:
      containers:
        - name: tomcat
          image: tomcat
          ports:
            - containerPort: 8080
              hostPort: 8081 # 新增

更新该RC:

可以看到tomcat pod被分配到了node1和node2上,所以我们可以在宿主机外使用或者访问tomcat:

但是我们知道,Pod的IP地址是不可靠的,例如当Pod所在的Node发生故障时,Pod将被Kubernetes重新调度到另一个Node,Pod的IP地址将发生变化。所以我们可以定义一个tomcat pod的统一访问入口,这就是Service的作用。

创建Service有两种方式:

1.kubectl expose命令来创建Service

kubectl expose rc tomcat-rc

现在我们就可以通过Service的clusterIP + Port来访问了:

Service地址10.1.187.222:8080均衡的负载到了两个tomcat pod上(10.244.1.19:8080和10.244.4.13:8080)

2.通过配置文件创建

定义一个tomcat-service.yml:

apiVersion: v1
kind: Service
metadata:
  name: tomcat-service
spec:
  ports:
    - port: 8081 # service端口
      targetPort: 8080 # 目标端口
  selector:
    name: tomcat # 选择器,选择name=tomcat的pod

创建该Service:

同样,Service默认是不能外部访问的,如果想让外部能够访问到tomcat service,我们也需要将Service的端口映射到物理机,修改上面的tomcat-service.yml:

apiVersion: v1
kind: Service
metadata:
  name: tomcat-service
spec:
  type: NodePort # 新增
  ports:
    - port: 8081
      targetPort: 8080
      nodePort: 30000 # 新增
  selector:
    name: tomcat

上面配置通过设置nodePort(范围30000-32767)映射到物理机,同时设置Service的类型为NodePort,创建该Service:

现在我们就可以通过宿主机的IP+30000访问tomcat了:

负载均衡策略

Kubernetes Service提供了两种负载分发策略:RoundRobin和SessionAffinity:

  1. RoundRobin:轮询模式(默认),即轮询将请求转发到后端的各个Pod上。
  2. SessionAffinity:基于客户端IP地址进行会话保持的模式,即第1次将某个客户端发起的请求转发到后端的某个Pod上,之后从相同的客户端发起的请求都将被转发到后端相同的Pod上。可以通过设置service.spec.sessionAffinity=ClientIP来启用SessionAffinity策略:
apiVersion: v1
kind: Service
metadata:
  name: tomcat-service
spec:
  type: NodePort
  sessionAffinity: ClientIP # 采用SessionAffinity策略
  ports:
    - port: 8081
      targetPort: 8080
      nodePort: 30000
  selector:
    name: tomcat

Headless Service

Headless Service不提供ClusterIP,仅通过Label Selector将后端的Pod列表返回给调用的客户端。

创建tomcat-headless-service.yml:

apiVersion: v1
kind: Service
metadata:
  name: tomcat-headless-service
spec:
  ports:
    - port: 8080
  clusterIP: None # 设置clusterIP为None,表示headless service
  selector:
    name: tomcat

创建该headless service:

Kubernetes Pod升级与回滚


2019年11月05日 14:00:55   1,488 次浏览

当集群中的某个服务需要升级时,我们需要停止目前与该服务相关的所有Pod,然后下载新版本镜像并创建新的Pod。如果集群规模比较大,则这个工作变成了一个挑战,而且先全部停止然后逐步升级的方式会导致较长时间的服务不可用。Kubernetes提供了滚动升级功能来解决上述问题。如果在更新过程中发生了错误,则还可以通过回滚操作恢复Pod的版本。

Deployment升级

现有如下deployment定义(nginx-deployment.yml):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      name: nginx
  replicas: 3
  template:
    metadata:
      labels:
        name: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.7.9
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80

创建该deployment:

通过kubectl命令将nginx的版本更新到1.9.1:

kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1

查看滚动升级的过程:

查看Pod,会发现名称已经改变了:

上述滚动升级的过程可以用下图表示:

查看Deployment nginx-deployment的详细事件信息可以证明这一点:

kubectl describe deployments/nginx-deployment

查看rs:

默认的升级策略为RollingUpdate(滚动更新),Kubernetes还支持Recreate(重建)策略:

  1. Recreate:先杀掉所有正在运行的Pod,然后创建新的Pod。
  2. RollingUpdate:以滚动更新的方式来逐个更新Pod。同时,可以通过设置spec.strategy.rollingUpdate下的两个参数(maxUnavailable和maxSurge)来控制滚动更新的过程。

RollingUpdate的两个参数含义如下:

  1. maxUnavailable:用于指定Deployment在更新过程中不可用状态的Pod数量的上限。该maxUnavailable的数值可以是绝对值(例如1)或Pod期望的副本数的百分比(例如10%)。
  2. maxSurge:用于指定在Deployment更新Pod的过程中Pod总数超过Pod期望副本数部分的最大值。该maxSurge的数值可以是绝对值(例如5)或Pod期望副本数的百分比(例如10%)。

此外,除了使用kubectl命令升级外,我们也可以直接通过修改deployment.yml的方式来完成。

Deployment回滚

如果升级后效果不满意的话,我们也可以将Deployment回滚到升级之前,使用下面这条命令查看滚动升级的历史:

kubectl rollout history deployment/nginx-deployment

REVISION为升级的历史版本,我们只完成了nginx从1.7.9升级到1.9.1的过程,所以REVISION 1表示nginx为1.7.9的版本,REVISION 2表示nginx为1.9.1的版本。因为我们在创建deployment的时候没有加上--record=true参数,所以CHANGE-CACSE列是空的。

需要查看特定版本的详细信息,则可以加上–revision=参数:

要将nginx回退到1.7.9版本,我们可以将REVISION指定为1:

kubectl rollout undo deployment/nginx-deployment --to-revision=1

暂停与恢复

因为Deployment配置一旦修改就会触发升级操作,所以当修改的地方较多的时候就会频繁触发升级。我们可以先暂停升级操作,当所有配置都修改好后再恢复升级。

暂停:

kubectl rollout pause deployment/nginx-deployment

暂停后,对nginx-deployment的修改不会触发升级。修改好后,恢复升级:

kubectl rollout resume deployment/nginx-deployment

其他Pod管理对象升级策略

DaemonSet

DaemonSet的升级策略包括两种:OnDelete和RollingUpdate:

  1. OnDelete(默认):在创建好新的DaemonSet配置之后,新的Pod并不会被自动创建,直到用户手动删除旧版本的Pod,才触发新建操作。
  2. RollingUpdate:和前面介绍的一致,不过不支持rollout,只能通过将配置改回去来实现。

StatefulSet

支持Recreate、OnDelete和RollingUpdate。

Kubernetes Pod管理对象与调度策略


2019年11月04日 13:04:40   1,530 次浏览

在前面的学习中我们了解到,在Kubernetes中,Pod管理对象主要有RC(RS)、Deployment、StatefulSet、DaemonSet和Job(CronJob)等。其中RC(RS)和Deployment的用法已经大致了解,这里主要记录下StatefulSet、DaemonSet和Job(CronJob)的用法。默认情况下,Pod管理对象在创建Pod的时候是根据系统自动调度算法来完成部署的,我们可以设置调度策略来实现Pod的精准调度。

Pod调度策略

NodeSelector

我们可以该某个节点Node设置标签,然后通过NodeSelector让Pod调度到该节点上。

前面搭建的Kubernetes集群有两个worker节点node1和node2,我们给node2打个标签:

kubectl label nodes node2 tier=frontend

接着定义一个RC配置(node-select-pod.yml):

apiVersion: v1
kind: ReplicationController
metadata:
  name: test-rc
spec:
  replicas: 1
  selector:
    name: nginx
  template:
    metadata:
      labels:
        name: nginx
    spec:
      containers:
        - name: nginx
          image: nginx
          ports:
            - containerPort: 80
      nodeSelector:
        tier: frontend

创建该RC,观察Pod最终调度的节点:

可以看到nginx pod已经成功调度到了node2节点上。

Kubernetes会给每个node贴上一些默认的标签,通过kubectl get node node1 -o yaml可以看到这些默认的标签:

beta.kubernetes.io/arch: amd64
beta.kubernetes.io/os: linux
kubernetes.io/arch: amd64
kubernetes.io/hostname: node1
kubernetes.io/os: linux

这些默认的标签在下面这些调度策略中也是蛮常用的。

NodeAffinity

Affinity[əˈfɪnəti]:亲和力,喜好。NodeAffinity为Node亲和力调度,主要有两个规则(名称有点长,快18cm了吧):

  1. RequiredDuringSchedulingIgnoredDuringExecution:必须满足指定的规则才可以调度Pod,硬规则。
  2. PreferredDuringSchedulingIgnoredDuringExecution:软规则,最好满足所列出的条件才调度Pod。

IgnoredDuringExecution的意思是,如果一个Pod已经调度到某个节点上了,然后这个节点的标签发生了改变,那么不影响已经调度好的Pod,ignore。

定义一个配置文件(node-affinity.yml),用于演示NodeAffinity:

apiVersion: v1
kind: ReplicationController
metadata:
  name: test-rc
spec:
  replicas: 1
  selector:
    name: nginx
  template:
    metadata:
      labels:
        name: nginx
    spec:
      containers:
        - name: nginx
          image: nginx
          ports:
            - containerPort: 80
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: beta.kubernetes.io/arch
                    operator: In
                    values:
                      - amd64
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 1
              preference:
                matchExpressions:
                  - key: disk-type
                    operator: In
                    values:
                      - ssd

上面配置使得RC在调度nginx pod的时候需要满足:节点具有beta.kubernetes.io/arch=amd64标签,如果具有disk-type=ssd标签就更好了,换句话说就是希望nginx pod调度在cpu架构为amd,磁盘为ssd的节点上。

上面配置中,操作符operator除了可以使用In外,还可以使用NotIn、Exists、DoesNotExist、Gt、Lt。

NodeAffinity规则设置需要注意的几个点:

  1. 如果nodeAffinity指定了多个nodeSelectorTerms,那么其中一个能够匹配成功即可;
  2. 如果在nodeSelectorTerms中有多个matchExpressions,则一个节点必须满足所有matchExpressions才能运行该Pod。

运行上面这个配置:

可以看到,因为node1和node2都具有beta.kubernetes.io/arch=amd64标签,而没有disk-type=ssd标签,所以nginx pod有可能被调度到node1也有可能被调度到node2。

我们给node1添加disk-type=ssd标签,重新运行上面的配置文件:

可以看到,nginx pod被调度到了node1上。

PodAffinity

PodAffinity指的是Pod亲和力调度股则,比如某些节点已经存在一些Pod了,新的Pod在调度的时候希望和指定Pod在一台节点上,亦或有意避开和指定Pod在一台节点上,这时候就可以用PodAffinity实现。

NodeAffinity规则设置也是通过requiredDuringSchedulingIgnoredDuringExecutionPreferredDuringSchedulingIgnoredDuringExecution实现的。此外,NodeAffinity还需要设置topology(拓扑规则),意为表达节点所属的topology范围:

  1. kubernetes.io/hostname(节点所处的服务器)
  2. failure-domain.beta.kubernetes.io/zone(节点所处的服务器云盘分区)
  3. failure-domain.beta.kubernetes.io/region(节点所处的服务器云盘所在的地区)

要演示PodAffinity的使用,需要先创建一个参照Pod,创建一个参照Pod的配置文件(flag-pod.yml):

apiVersion: v1
kind: Pod
metadata:
  name: flag-pod
  labels:
    tier: frontend
    app: flag-nginx
spec:
  containers:
    - name: flag-nginx
      image: nginx
      ports:
        - containerPort: 8081

这个Pod具有tier=frontendapp=flag-nginx标签。

创建这个参照Pod:

参照pod运行在了node1节点上。

创建好后,接着定义一个新的配置类(pod-affinity.yml):

apiVersion: v1
kind: Pod
metadata:
  name: pod-affinity
spec:
  containers:
    - name: nginx
      image: nginx
      ports:
        - containerPort: 80
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
              - key: tier
                operator: In
                values:
                  - frontend
          topologyKey: kubernetes.io/hostname

上述配置要求,nginx pod需要和标签包含tier=frontend的Pod分配在同一个Node上(topologyKey: kubernetes.io/hostname),创建该Pod,观察它被调度到哪个节点上了:

podAntiAffinity和podAffinity刚好相反,举个例子,创建一个新的配置类(pod-affinity-two.yml):

apiVersion: v1
kind: Pod
metadata:
  name: pod-affinity-two
spec:
  containers:
    - name: nginx
      image: nginx
      ports:
        - containerPort: 80
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
              - key: app
                operator: In
                values:
                  - flag-nginx
          topologyKey: kubernetes.io/hostname

上面配置希望nginx pod不和app=flag-nginx的Pod在同一个节点。

创建该Pod,观察它被调度到哪个节点上了:

可以看到,它和flag-nginx处于不同的节点,位于node2。

Taints & Tolerations

Taints用于给节点添加污点,而Tolerations用于定义Pod对节点污点的容忍度。在Node上设置一个或多个Taint之后,除非Pod明确声明能够容忍这些污点,否则无法在这些Node上运行。Toleration是Pod的属性,让Pod能够(注意,只是能够,而非必须)运行在标注了Taint的Node上。

给节点设置污点的语法为:

kubectl taint nodes [nodeName] key=value:rule

其中rule的取值有:

  1. NoScheudle:不调度;
  2. PreferNoSchedule:最好不要调度;
  3. NoExecute:不运行;
  4. PreferNoExecute:最好不运行。
  • NoSchedule不调度,如果是在调度后设置的污点,并且Pod没有容忍该污点,也能继续执行
  • NoExecute不执行,如果是在调度后设置的污点,并且Pod没有容忍该污点,则会被驱逐。可以设置驱逐时间tolerationSeconds: xx

NoSchedule和NoExecute区别:

我们给node1节点设置一个污点:

kubectl taint nodes node1 aa=bb:NoSchedule

然后新建一个Pod配置类(pod-taint-test.yml):

apiVersion: v1
kind: Pod
metadata:
  name: pod-taint
spec:
  containers:
    - name: nginx
      image: nginx
      ports:
        - containerPort: 80
  tolerations:
    - key: "aa"
      operator: "Equal"
      value: "bb"
      effect: "NoSchedule"

Pod的Toleration声明中的key和effect需要与Taint的设置保持一致,并且满足以下条件之一:

  1. operator的值是Exists(无须指定value),如:
......
  tolerations:
    - key: "aa"
      operator: "Exists"
      effect: "NoSchedule"
  1. operator的值是Equal并且value相等。如果不指定operator,则默认值为Equal。

另外,有如下两个特例:

  1. 空的key配合Exists操作符能够匹配所有的键和值;
  2. 空的effect匹配所有的effect。

回到pod-taint-test.yml,该配置文件定义nginx pod可以容忍aa=bb:NoSchedule这个污点,所以它有可能会被调度到node1上。创建该Pod,观察:

这时候在nginx pod所运行的节点node1上新增一个污点:

kubectl taint nodes node1 cc=dd:NoExecute

观察nginx pod情况:

可以看到它被驱逐了,已经没有正在运行的pod了。

Pod Priority Preemption

我们可以给Pod指定优先级,优先级可以通过PriorityClass对象创建,比如创建一个优先级为10000的PriorityClass:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: hign-priority
value: 10000
globalDefault: false
description: "10000优先级"

上述文件定义了一个名为high-priority的优先级类别,优先级为10000,数字越大,优先越高。超过一亿的数字被系统保留,用于指派给系统组件。

优先级创建后,可以在Pod定义中引用该优先级:

...
spec:
  containers:
    - name: nginx
      image: nginx
      ports:
        - containerPort: 80
  priorityClassName: hign-priority

Pod管理对象

Job

Job是一种特殊的Pod管理对象,是一种一次性Pod运行任务,任务结束后,Pod的生命周期也就结束了。

定义一个Job配置类(job.yml):

apiVersion: batch/v1
kind: Job
metadata:
  name: job-ex
spec:
  template:
    spec:
      containers:
        - name: job
          image: busybox
          command: ["echo", "hello job"]
      restartPolicy: Never

Job的重启策略restartPolicy只支持Never和OnFailure。

创建这个Job:

查看Job状态:

COMPLETIONS 1/1表示总共需要执行1个任务,共执行完1个任务。

查看对应Pod的状态:

状态为Completed。

查看Job日志:

Job还可以设置并发数量和总Job数,修改上面的job.yml:

apiVersion: batch/v1
kind: Job
metadata:
  name: job-ex
spec:
  parallelism: 2 # 并发数2
  completions: 6 # 总的Job数量
  template:
    spec:
      containers:
        - name: job
          image: busybox
          command: ["echo", "hello job"]
      restartPolicy: Never

运行该Job:

查看Pod:

CronJob

CronJob顾名思义就是支持Cron表达式的Job,不过Kubernetes的Cron表达式和传统的Cron表达式不太一样,不支持到秒级。具体规则如下:

Minutes Hours DayofMonth Month DayofWeek Year
  1. Minutes:可出现, - * / 这4个字符,有效范围为0~59的整数。
  2. Hours:可出现, - * /这4个字符, 有效范围为0~23的整数。
  3. DayofMonth:可出现, - * / ? L W C 这8个字符,有效范围为0~31的整数。
  4. Month:可出现, - * /这4个字符,有效范围为1~12的整数或JAN~DEC。
  5. DayofWeek:可出现, - * / ? L C 这8个字符,有效范围为1~7的整数或SUN~SAT。1表示星期天,2表示星期一,以此类推。

上面特殊字符的含义如下:

  1. *:表示匹配该域的任意值,假如在Minutes域使用*,则表示每分钟都会触发事件。
  2. /:表示从起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域设置为5/20,则意味着第1次触发在第5min时,接下来每20min触发一次,将在第25min、第45min等时刻分别触发。

定义一个CronJob的配置类(cron-job.yml):

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: cron-job-ex
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: hello
              image: busybox
              command: ["sh", "-c", "date;echo hello cronJob"]
          restartPolicy: Never

上面的定时任务每分钟执行一次。

创建该CronJob:

过个两三分钟查看运行情况:

DaemonSet

DaemonSet适用于在每个Node都需要运行一个Pod的时候使用,比如:每一个Node上运行一个日志采集、性能监控的Pod。

比如我们现在需要在每个节点上都部署一个node-exporter来采集节点信息,可以通过DaemonSet来实现。

定义一个DaemonSet配置文件(daemonset.yml):

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-exporter-d
spec:
  selector:
    matchLabels:
      name: node-exporter
  template:
    metadata:
      labels:
        name: node-exporter
    spec:
      containers:
        - name: node-exporter
          image: prom/node-exporter
          ports:
            - containerPort: 9100

创建该DaemonSet之前先删除上面在node1节点上创建的污点:

kubectl taint nodes node1 aa:NoSchedule-
kubectl taint nodes node1 cc:NoExecute-

创建该DaemonSet,然后观察Pod信息,看是否每个节点都部署了一个实例:

Kubernetes Pod基础


2019年11月03日 18:09:07   1,520 次浏览

Pod是Kubernetes的最小调度单位,包含一个或者多个容器(比如Docker容器),容器间共享网络和存储。这节主要记录什么是静态Pod,Pod容器如何共享存储,如何使用ConfigMap管理Pod配置,如何使用Downward API获取Pod信息等。

静态Pod

静态Pod是由kubelet创建并管理的特殊的Pod,无法和Pod管理对象关联,并且不能通过API Server关联。创建静态Pod有配置文件方式和HTTP方式:

配置文件方式

在搭建Kubernetes集群的时候,从启动Master节点的日志可以看出,静态Pod的目录位于/etc/kubernetes/manifests

在该目录下创建静态Pod文件:

cd /etc/kubernetes/manifests
vim static-pod.yaml

内容如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: static-pod
  labels:
    name: static-pod
spec:
  containers:
    - name: static-nginx
      image: nginx
      imagePullPolicy: IfNotPresent
      ports:
        - containerPort: 80

过了一会查看Pod:

于静态Pod无法通过API Server直接管理,所以在Master上尝试删除这个Pod时,会使其变成Pending状态,且不会被删除。

删除该Pod的操作只能是到其所在Node上将其定义文件static-pod.yaml从/etc/kubernetes/manifests目录下删除:

HTTP方式

过设置kubelet的启动参数--manifest-url,kubelet将会定期从该URL地址下载Pod的定义文件,并以.yaml或.json文件的格式进行解析,然后创建Pod。

Pod容器共享Volume

同一个Pod的多个容器间可以共享Pod级别的Volume,举个例子:

vim pod-volume.yml

内容如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: pod-volume
spec:
  containers:
    - name: tomcat
      image: tomcat
      imagePullPolicy: IfNotPresent
      ports:
        - containerPort: 8080
      volumeMounts:
        - mountPath: /usr/local/tomcat/logs
          name: logs
    - name: busybox
      image: busybox
      imagePullPolicy: IfNotPresent
      command: ["sh", "-c", "tail -f /logs/catalina*.log"]
      volumeMounts:
        - mountPath: /logs
          name: logs
  volumes:
    - name: logs
      emptyDir: {}

上面Pod定义中,创建了一个Pod级别的Volume,名称为logs,类型为emptyDir。这个Volume同时挂载到了tomcat的/usr/local/tomcat/logs目录下,也挂载到了busybox的/logs目录下。

创建该Pod:

kubectl create -f pod-volume.yml

这里的tomcat镜像比较大,大概有500MB左右,所以在创建之前,最好在Kubernetes集群的每个节点中配置Docker镜像加速地址。

当pod-volume状态为ready后,查看busybox的日志:

该日志为tomcat的启动日志,说明上面挂载的Volume生效了,可以通过查看tomcat/usr/local/tomcat/logs目录下和busybox/logs目录下的内容来证明这一点:

ConfigMap

ConfigMap以一个或多个key:value的形式保存在Kubernetes系统中供应用使用,既可以用于表示一个变量的值(例如version=v1),也可以用于表示一个完整配置文件的内容(例如server.xml=<?xml...>...)。

创建ConfigMap

创建ConfigMap主要有两种方式:

1.通过yml文件创建

创建simple-cm.yml文件,内容如下所示:

apiVersion: v1
kind: ConfigMap
metadata:
  name: simple-cm
data:
  version: v1
  release: stable

该ConfigMap仅包含两个简单的值version和releases。

创建该ConfigMap:

kubectl create -f simple-cm.yml

查看该ConfigMap:

在定义ConfigMap的时候,value除了可以使用简单的值外,还可以是整个配置文件的内容。

创建file-cm.yml,内容如下所示:

apiVersion: v1
kind: ConfigMap
metadata:
  name: file-cm
data:
  serverXml: |
    <?xml version="1.0" encoding="UTF-8"?>
    <Server port="8005" shutdown="SHUTDOWN">
      <Listener className="org.apache.catalina.startup.VersionLoggerListener"/>
      <Listener SSLEngine="on" className="org.apache.catalina.core.AprLifecycleListener"/>
      <!-- Prevent memory leaks due to use of particular java/javax APIs-->
      <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
      <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
      <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>
      <GlobalNamingResources>
        <Resource auth="Container" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" name="UserDatabase" pathname="conf/tomcat-users.xml" type="org.apache.catalina.UserDatabase"/>
      </GlobalNamingResources>
      <Service name="Catalina">
        <Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
        <Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/>
        <Engine defaultHost="localhost" name="Catalina">
          <Realm className="org.apache.catalina.realm.LockOutRealm">
            <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
          </Realm>

          <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">
            <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t &quot;%r&quot; %s %b" prefix="localhost_access_log" suffix=".txt"/>
          <Context docBase="D:\Code\apache-tomcat-8.5.32\webapps\tj_certification" path="/tj_certification" reloadable="true" source="org.eclipse.jst.jee.server:tj_certification"/></Host>
        </Engine>
      </Service>
    </Server>
  serverProperties: "1catalina.org.apache.juli.AsyncFileHandler.level = FINE
                     1catalina.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
                     1catalina.org.apache.juli.AsyncFileHandler.prefix = catalina.

                     2localhost.org.apache.juli.AsyncFileHandler.level = FINE
                     2localhost.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
                     2localhost.org.apache.juli.AsyncFileHandler.prefix = localhost.

                     3manager.org.apache.juli.AsyncFileHandler.level = FINE
                     3manager.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
                     3manager.org.apache.juli.AsyncFileHandler.prefix = manager.

                     4host-manager.org.apache.juli.AsyncFileHandler.level = FINE
                     4host-manager.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
                     4host-manager.org.apache.juli.AsyncFileHandler.prefix = host-manager.

                     java.util.logging.ConsoleHandler.level = FINE
                     java.util.logging.ConsoleHandler.formatter = org.apache.juli.OneLineFormatter

                     org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level = INFO
                     org.apache.catalina.core.ContainerBase.[Catalina].[localhost].handlers = 2localhost.org.apache.juli.AsyncFileHandler

                     org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].level = INFO
                     org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].handlers = 3manager.org.apache.juli.AsyncFileHandler

                     org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].level = INFO
                     org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].handlers = 4host-manager.org.apache.juli.AsyncFileHandler"

创建该ConfigMap:

2.直接通过Kubectl命令创建

通过kubectl命令创建ConfigMap主要有以下三种用法:

  1. 通过–from-file参数从文件中进行创建,可以指定key的名称,也可以在一个命令行中创建包含多个key的ConfigMap,语法为:
kubectl cerate configmap [NAME] --from-file=[key=]source --from-file=[key=]source

通过–from-file参数从目录中进行创建,该目录下的每个配置文件名都被设置为key,文件的内容被设置为value,语法为:

kubectl cerate configmap [NAME] --from-file=config-file-dir

使用–from-literal时会从文本中进行创建,直接将指定的key#=value#创建为ConfigMap的内容,语法为:

kubectl cerate configmap [NAME] --from-literal=key1=value1 --from-literal=key2=value2

比如使用kubectl命令创建一个和simple-cm效果一样的ConfigMap:

使用kubectl命令创建一个和file-cm效果一样的ConfigMap:

首先在当前目录下准备好两个配置文件server.xml和server.properties:

然后使用kubectl命令创建:

Pod容器使用ConfigMap

Pod的容器要使用ConfigMap主要有两种方式:

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

通过环境变量的方式

创建一个Pod配置(simple-cm-pod.yml):

apiVersion: v1
kind: Pod
metadata:
  name: simple-cm-pod
spec:
  containers:
    - name: busybox
      image: busybox
      command: ["sh", "-c", "env | grep ENV"] # 打印名称包含ENV的环境变量
      env:
        - name: ENVVERSION     # 定义环境变量名称
          valueFrom:           # 值来自...
            configMapKeyRef:   # ConfigMap键的引用
              key: version     # ConfigMap的key
              name: simple-cm  # ConfigMap的名称
        - name: ENVRELEASE
          valueFrom:
            configMapKeyRef:
              key: release
              name: simple-cm

创建该Pod,并查看日志:

可以看到,值已经成功从ConfigMap里去到了。

如果要引用某个ConfigMap的所有内容,可以使用下面这种方式。定义一个Pod配置(simple-cm-pod-all.uyml):

apiVersion: v1
kind: Pod
metadata:
  name: simple-cm-pod-all
spec:
  containers:
    - name: busybox
      image: busybox
      command: ["sh", "-c", "env"]
      envFrom:
        - configMapRef:
            name: simple-cm

创建该Pod,并查看日志:

通过Volume挂载的方式

创建一个Pod配置(file-cm-pod.yml):

apiVersion: v1
kind: Pod
metadata:
  name: file-cm-pod
spec:
  containers:
    - name: tomcat
      image: tomcat
      ports:
        - containerPort: 8080
      volumeMounts:
        - mountPath: /configs # 容器挂载目录为/configs
          name: config-vm # 引用下面定义的这个Volume
  volumes:
    - name: config-vm   # volume名称为config-vm
      configMap:        # 通过ConfigMap获取
        name: file-cm   # 引用名称为file-cm的ConfigMap
        items:
          - key: serverXml # ConfigMap里配置的key
            path: server.xml # 值使用server.xml文件进行挂载
          - key: serverProperties
            path: server.properties

创建该Pod,并进入到容器内部观察/configs目录下文件内容:

可以看到名称为file-cm的ConfigMap内容已经成功挂载到了tomcat容器内部。

如果在引用ConfigMap时不指定items,则使用volumeMount方式在容器内的目录下为每个item都生成一个文件名为key的文件。

在Pod对ConfigMap进行挂载(volumeMount)操作时,在容器内部只能挂载为“目录”,无法挂载为“文件”。在挂载到容器内部后,在目录下将包含ConfigMap定义的每个item,如果在该目录下原来还有其他文件,则容器内的该目录将被挂载的ConfigMap覆盖

Downward API

Downward API用于将Pod相关信息注入到容器内部,主要有环境变量Volume挂载两种方式。

环境变量

创建一个Pod配置(dapi-pod.yml):

apiVersion: v1
kind: Pod
metadata:
  name: dapi-pod
spec:
  containers:
    - name: busybox
      image: busybox
      command: ["sh", "-c", "env | grep MY_POD"]
      env:
        - name: MY_POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name # 通过downward api获取当前pod名称
        - name: MY_POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace  # 通过downward api获取当前pod namespace
        - name: MY_POD_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP  # 通过downward api获取当前pod IP

创建该Pod,并查看日志:

通过环境变量的方式还可以将容器的requests和limits信息注入到容器的环境变量中,创建一个Pod配置(dapi-pod-container-vars.yml):

apiVersion: v1
kind: Pod
metadata:
  name: dapi-pod-container-vars
spec:
  containers:
    - name: busybox
      image: busybox
      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 3600;
          done;
      resources:
        requests:
          memory: "32Mi"
          cpu: "125m"
        limits:
          memory: "64Mi"
          cpu: "250m"
      env:
        - name: MY_CPU_REQUEST
          valueFrom:
            resourceFieldRef:
              resource: requests.cpu
              containerName: busybox
        - name: MY_CPU_LIMIT
          valueFrom:
            resourceFieldRef:
              resource: limits.cpu
              containerName: busybox
        - name: MY_MEM_REQUEST
          valueFrom:
            resourceFieldRef:
              resource: request.memory
              containerName: busybox
        - name: MY_MEM_LIMIT
          valueFrom:
            resourceFieldRef:
              resource: limits.memory
              containerName: busybox

创建该Pod并观察日志:

通过Volume挂载

我们可以通过Downward API将Pod的Label,Annotation等信息挂载到容器内部文件中,新建一个Pod配置(dapi-pod-volumes.yml):

apiVersion: v1
kind: Pod
metadata:
  name: dapi-pod-volume
  labels:
    tier: frontend
    release: canary
    environment: dev
  annotations:
    builder: mrbird
    blog: https://mrbird.cc
spec:
  containers:
    - name: busybox
      image: busybox
      command: ["sh", "-c", "sleep 36000"]
      volumeMounts:
        - mountPath: /podinfo # 挂载路径
          name: pod-info
  volumes:
    - name: pod-info
      downwardAPI: # 通过downward api获取pod labels和annations信息
        items:
          - path: "labels" # 挂载文件名称
            fieldRef:
              fieldPath: metadata.labels # 挂载内容
          - path: "annotation"
            fieldRef:
              fieldPath: metadata.annotations

创建该Pod,并进入到容器/podinfo目录观察结果:

Pod生命周期

阶段描述
PendingPod 已被 Kubernetes 接受,但尚未创建一个或多个容器镜像。这包括被调度之前的时间以及通过网络下载镜像所花费的时间,执行需要一段时间。
RunningPod 已经被绑定到了一个节点,所有容器已被创建。至少一个容器正在运行,或者正在启动或重新启动。
Succeeded所有容器成功终止,也不会重启。
Failed所有容器终止,至少有一个容器以失败方式终止。也就是说,这个容器要么已非 0 状态退出,要么被系统终止。
Unknown由于一些原因,Pod 的状态无法获取,通常是与 Pod 通信时出错导致的。

三种重启策略:

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

结合Pod的状态和重启策略,以下为一些常见的状态转换场景:

Pod包含的容器数Pod当前的状态发生事件Pod的结果状态
RestarPolicy=AlwaysRestartPolicy=OnFailureRestartPolicy=Never
包含1个容器Running容器成功退出RunningSucceededSucceeded
包含1个容器Running容器失败退出RunningRunningFailed
包含两个容器Running1个容器失败退出RunningRunningRunning
包含两个容器Running容器被OOM杀掉RunningRunningFailed