学习笔记-剖析k8s之Pod使用-3月day15

前言

作为Kubernetes项目里最核心的编排对象,Pod携带的信息非常丰富。其中包括资源定义(比如CPU、内存等),以及调度相关的字段等。
在这里,我们先从一种特殊的Volume开始,更加深入地理解Pod对象各个重要字段的含义。

Pod字段

volume

先来认识一种特殊的Volume,Projected Volume,投射数据卷。Projected Volume是Kubernetes v1.11之后的新特性,它们存在的意义不是为了存放容器里的数据,也不是用来进行容器和宿主机之间的数据交换。这些特殊Volume的作用,是为容器提供预先定义好的数据。所以,从容器的角度来看,这些Volume里的信息就是仿佛是被Kubernetes“投射”(Project)进入容器当中的。这正是Projected Volume的含义。
目前,Kubernetes支持的Projected Volume一共有四种:

  1. Secret;
  2. ConfigMap;
  3. Downward API;
  4. ServiceAccountToken;

Secret

它的作用,是帮你把Pod想要访问的加密数据,存放到Etcd中。然后,你就可以通过在Pod的容器里挂载Volume的方式,访问到这些Secret里保存的信息了。
Secret最典型的使用场景,比如存放数据库的Credential信息,创建test-projected-volume.yaml文件:

apiVersion: v1
kind: Pod
metadata:
  name: test-projected-volume 
spec:
  containers:
  - name: test-secret-volume
    image: busybox
    args:
    - sleep
    - "86400"
    volumeMounts:
    - name: mysql-cred
      mountPath: "/projected-volume"
      readOnly: true
  volumes:
  - name: mysql-cred
    projected:
      sources:
      - secret:
          name: user
      - secret:
          name: pass

在这个Pod中,它声明挂载的Volume,并不是常见的emptyDir或者hostPath类型,而是projected类型。而这个 Volume的数据来源(sources),则是名为user和pass的Secret对象,分别对应的是数据库的用户名和密码。这里用到的数据库的用户名、密码,正是以Secret对象的方式交给Kubernetes保存的:

 kubectl create secret generic user --from-file=./username.txt
 kubectl create secret generic pass --from-file=./password.txt

除了使用kubectl create secret指令,也可以直接通过编写YAML文件的方式来创建这个Secret对象:

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  user: YWRtaW4=
  pass: MWYyZDFlMmU2N2Rm

说明,Secret对象要求data字段这些数据必须是经过Base64转码的,以免出现明文密码的安全隐患,操作如下:

echo -n 'src_data' | base64

(注意,这样创建的Secret对象,它里面的内容仅仅是经过了转码,而并没有被加密。在真正的生产环境中,你需要在Kubernetes中开启Secret的加密插件,增强数据的安全性。)

创建这个Pod:

kubectl create -f test-projected-volume.yaml

当Pod变成Running状态之后,就可以验证这些Secret对象是不是已经在容器里了:

$ kubectl exec -it test-projected-volume -- /bin/sh
$ cat /projected-volume/user
root
$ cat /projected-volume/pass
1f2d1e2e67df

可以看到,保存在Etcd里的用户名和密码信息,已经以文件的形式出现在了容器的Volume目录里。而这个文件的名字,就是kubectl create secret指定的Key,或者说是Secret对象的data字段指定的Key。
像这样通过挂载方式进入到容器里的Secret,一旦其对应的Etcd里的数据被更新,这些Volume里的文件内容,同样也会被更新。其实,这是kubelet组件在定时维护这些Volume。需要注意的是,这个更新可能会有一定的延时。所以在编写应用程序时,在发起数据库连接的代码处写好重试和超时的逻辑,绝对是个好习惯。

ConfigMap

用法几乎与Secret完全相同,区别在于,ConfigMap保存的是不需要加密的、应用所需的配置信息。
比如,一个Java应用所需的配置文件(.properties文件),可以通过下面这样的方式保存在ConfigMap里:

# .properties文件的内容
$ cat example/ui.properties
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice

# 从.properties文件创建ConfigMap
$ kubectl create configmap ui-config --from-file=example/ui.properties

# 查看这个ConfigMap里保存的信息(data)
$ kubectl get configmaps ui-config -o yaml
apiVersion: v1
data:
  ui.properties: |
    color.good=purple
    color.bad=yellow
    allow.textmode=true
    how.nice.to.look=fairlyNice
kind: ConfigMap
metadata:
  name: ui-config
  ...

Downward API

它的作用是,让Pod里的容器能够直接获取到这个Pod API对象本身的信息。
比如:

apiVersion: v1
kind: Pod
metadata:
  name: test-downwardapi-volume
  labels:
    zone: us-est-coast
    cluster: test-cluster1
    rack: rack-22
spec:
  containers:
    - name: client-container
      image: k8s.gcr.io/busybox
      command: ["sh", "-c"]
      args:
      - while true; do
          if [[ -e /etc/podinfo/labels ]]; then
            echo -en '\n\n'; cat /etc/podinfo/labels; fi;
          sleep 5;
        done;
      volumeMounts:
        - name: podinfo
          mountPath: /etc/podinfo
          readOnly: false
  volumes:
    - name: podinfo
      projected:
        sources:
        - downwardAPI:
            items:
              - path: "labels"
                fieldRef:
                  fieldPath: metadata.labels

在这个Pod的YAML文件中,声明了一个projected类型的Volume。只不过这次Volume的数据来源,变成了Downward API。而这个Downward API Volume,则声明了要暴露Pod的metadata.labels信息给容器。通过这样的声明方式,当前Pod的Labels字段的值,就会被Kubernetes自动挂载成为容器里的/etc/podinfo/labels文件。
而这个容器的启动命令,则是不断打印出/etc/podinfo/labels里的内容。所以,当我创建了这个Pod之后,就可以通过kubectl logs指令,查看到这些Labels字段被打印出来:

$ kubectl create -f dapi-volume.yaml
$ kubectl logs test-downwardapi-volume
cluster="test-cluster1"
rack="rack-22"
zone="us-est-coast"

Downward API能够获取到的信息,一定是Pod里的容器进程启动之前就能够确定下来的信息。而如果你想要获取Pod容器运行后才会出现的信息,比如,容器进程的PID,那就肯定不能使用Downward API了,而应该考虑在Pod里定义一个sidecar容器。

Service Account

考虑这样一个需求,是否可以在Pod里安装一个Kubernetes的Client,这样就可以从容器里直接访问并且操作这个Kubernetes的API了呢?
Service Account对象的作用,就是Kubernetes系统内置的一种“服务账户”,它是Kubernetes进行权限分配的对象。比如,Service Account A,可以只被允许对Kubernetes API进行GET操作,而Service Account B,则可以有Kubernetes API的所有操作权限。
像这样的Service Account的授权信息和文件,实际上保存在它所绑定的一个特殊的Secret对象里的。这个特殊的Secret对象,就叫作ServiceAccountToken。任何运行在Kubernetes集群上的应用,都必须使用这个ServiceAccountToken里保存的授权信息,也就是Token,才可以合法地访问API Server。可以看到,ServiceAccountToken,只是一种特殊的Secret而已。
为了方便使用,Kubernetes已经为你提供了一个默认“服务账户”(default Service Account)。并且,任何一个运行在Kubernetes里的Pod,都可以直接使用这个默认的Service Account,而无需显示地声明挂载它。
以上实现,还是靠Projected Volume机制。
查看任意一个运行在Kubernetes集群里的Pod,就会发现,每一个Pod,都已经自动声明一个类型是Secret、名为default-token-xxxx的Volume,然后 自动挂载在每个容器的一个固定目录上:

$ kubectl describe pod nginx-deployment-5c678cfb6d-lg9lw
Containers:
...
  Mounts:
    /var/run/secrets/kubernetes.io/serviceaccount from default-token-s8rbq (ro)
Volumes:
  default-token-s8rbq:
  Type:       Secret (a volume populated by a Secret)
  SecretName:  default-token-s8rbq
  Optional:    false

这个Secret类型的Volume,正是默认Service Account对应的ServiceAccountToken。所以说,Kubernetes其实在每个Pod创建的时候,自动在它的spec.volumes部分添加上了默认ServiceAccountToken的定义,然后自动给每个容器加上了对应的volumeMounts字段。这个过程对于用户来说是完全透明的。这样,一旦Pod创建完成,容器里的应用就可以直接从这个默认ServiceAccountToken的挂载目录里访问到授权信息和文件。
所以,你的应用程序只要直接加载这些授权文件,就可以访问并操作Kubernetes API了。而且,如果你使用的是Kubernetes官方的Client包(k8s.io/client-go)的话,它还可以自动加载这个目录下的文件,你不需要做任何配置或者编码操作。
以上把Kubernetes客户端以容器的方式运行在集群里,然后使用default Service Account自动授权的方式,被称作“InClusterConfig”。

容器健康检查和恢复机制

在Kubernetes中,你可以为Pod里的容器定义一个健康检查“探针”(Probe)。这样,kubelet就会根据这个Probe的返回值决定这个容器的状态,而不是直接以容器镜像是否运行(来自Docker返回的信息)作为依据。这种机制,是生产环境中保证应用健康存活的重要手段。
举例说明:

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: test-liveness-exec
spec:
  containers:
  - name: liveness
    image: busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5
      periodSeconds: 5

在这个Pod中,定义了一个有趣的容器。它在启动之后做的第一件事,就是在/tmp目录下创建了一个healthy文件,以此作为正常运行的标志。而30 s过后,它会把这个文件删除掉。
同时,定义了一个这样的livenessProbe(健康检查)。它的类型是exec,这意味着,它会在容器启动后,在容器里面执行一条我们指定的命令:“cat /tmp/healthy”。如果这个文件存在,这条命令的返回值就是0,Pod就会认为这个容器不仅已经启动,而且是健康的。
实践一下这个健康检查过程:

$ kubectl create -f test-liveness-exec.yaml # 创建Pod
$ kubectl get pod # 查看Pod状态
NAME                READY     STATUS    RESTARTS   AGE
test-liveness-exec   1/1       Running   0          10s

可以看到,由于已经通过了健康检查,这个Pod就进入了Running状态。而30 s之后,我们再查看一下Pod的Events:

$ kubectl describe pod test-liveness-exec
FirstSeen LastSeen    Count   From            SubobjectPath           Type        Reason      Message
--------- --------    -----   ----            -------------           --------    ------      -------
2s        2s      1   {kubelet worker0}   spec.containers{liveness}   Warning     Unhealthy   Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
$ kubectl get pod test-liveness-exec # 再次查看Pod状态
NAME           READY     STATUS    RESTARTS   AGE
liveness-exec   1/1       Running   1          1m

这里,Pod并没有进入Failed状态,而是保持了Running状态。注意到RESTARTS字段从0到1的变化,这个异常的容器已经被Kubernetes重启了。在这个过程中,Pod保持Running状态不变。
需要注意的是:Kubernetes中并没有Docker的Stop语义。所以虽然是Restart(重启),但实际却是重新创建了容器。

这个功能就是Kubernetes里的Pod恢复机制,也叫restartPolicy。它是Pod的Spec部分的一个标准字段(pod.spec.restartPolicy),默认值是Always,即:任何时候这个容器发生了异常,它一定会被重新创建。

但一定要强调的是,Pod的恢复过程,永远都是发生在当前节点上,而不会跑到别的节点上去。事实上,一旦一个Pod与一个节点(Node)绑定,除非这个绑定发生了变化(pod.spec.node字段被修改),否则它永远都不会离开这个节点。这也就意味着,如果这个宿主机宕机了,这个Pod也不会主动迁移到其他节点上去。如果你想让Pod出现在其他的可用节点上,就必须使用Deployment这样的“控制器”来管理Pod,哪怕你只需要一个Pod副本。

通过设置restartPolicy,可以改变Pod的恢复策略:

  1. Always:在任何情况下,只要容器不在运行状态,就自动重启容器;
  2. OnFailure: 只在容器 异常时才自动重启容器;
  3. Never: 从来不重启容器。

如何配置Pod的恢复策略呢?参考两个基本的设计原理:

  1. 只要Pod的restartPolicy指定的策略允许重启异常的容器(比如:Always),那么这个Pod就会保持Running状态,并进行容器重启。否则,Pod就会进入Failed状态 。
  2. 对于包含多个容器的Pod,只有它里面所有的容器都进入异常状态后,Pod才会进入Failed状态。在此之前,Pod都是Running状态。此时,Pod的READY字段会显示正常容器的个数。

PodPreset(Pod预设置)

Kubernetes v1.11版本发布的功能,可以自动给对应的基本的Pod对象加上其他必要的信息,比如labels,annotations,volumes等等。而这些信息,可以是运维人员事先定义好的。
(使开发所写的基本配置的Pod Yaml文件符合生产环境运行要求并可执行)

小结

从以上介绍中:应用是Pod对象,应用的配置是ConfigMap对象,应用要访问的密码则是Secret对象,理解Kubernetes“一切皆对象”的设计思想。

此文章为3月Day15学习笔记,内容来源于极客时间《深入剖析Kubernetes》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值