Kubernetes-Pod详解

Pod

为什么需要Pod

Pod,是Kubernetes项目中的原子调度单位容器就是未来云计算系统中的进程;容器镜像就是系统里的.exe安装包,Kubernetes就是操作系统。在一个真正的操作系统里,进程并不是“孤苦伶仃”地独自运行,而是以进程组的方式,“有原则”的组织在一起。在kubernetes中,“进程组”所映射的概念就是Pod。Google的工程师发现,他们部署的应用,往往都存在着类似于“进程和进程组”的关系,也就是说这些应用之间有着亲密的协作关系,使得他们必须部署在同一台机器上。

Pod的实现原理

Pod只是一个逻辑概念。

Pod其实是一组共享了某些资源的容器,具体来说,Pod里的所有容器,共享的是同一个Network Namespace,并且可以声明共享同一个Volume。

在Kubernetes中,Pod的实现需要使用一个中间容器,这个容器叫做Infra容器。在Pod中,Infra容器永远是第一个被创建的容器,而其他用户定义的容器,则通过Join Network Namespace的方式与Infra容器关联在一起。

Infra容器使用的是一个非常特殊的镜像,叫做:k8s.gcr.io/pause。这个镜像是一个用汇编语言编写的、永远处于暂停状态的容器。在Infra容器“Hold住”NetworkNamespace之后,用户容器就可以加入到Infra容器的NetworkNamespace中了。这也就意味着,对于Pod中的容器A和容器B:

  • 它们可以直接使用localhost进行通信
  • 它们看到的网络设备和Infra容器看到的完全一样
  • 一个Pod只有一个IP地址,也就是这个Pod的Network Namespace对应的IP地址
  • 所有的网络资源,都是一个Pod一份,并且被该Pod内的所有容器共享
  • Pod的生命周期只和Infra容器一致,与容器A和容器B无关

对于Pod共享Volume来说,Kubernetes只要把所有Volume定义设计在Pod层即可。例如:

apiVersion: v1
kind: Pod
metadata:
 name: two-containers
spec:
 restartPolicy: Never #Pod的重启策略。1.Always:容器失效时,kubelet自动重启该容器。2.OnFailure:容器终止运行且退出码不为0时重启。3.Never:无论状态如何,kubelet都不重启该容器。
 volumes:
 - name: shared-data
   hostPath:
    path : /data
 containers:
 - name: nginx-container
   image: nginx
   volumeMounts:
   - name: shared-data
     mountPath: /usr/share/nginx/html
 - name: debian-container
   image: debian
   volumeMounts:
   - name: shared-data
     mountPath: /pod-data
   command: ["/bin/sh"]
   args: ["-c","echo Hello from the debian container > /pod/data/index.html"]

Pod对象的生命周期

Pod生命周期变化主要体现在Pod API对象的Status部分。

  • Pending,这个状态意味着Pod的YAML文件已经提交给了Kubernetes,API对象已经被创建并被保存在etcd中。但是,这个Pod里有些容器因为某种原因不能被成功创建,比如,调度不成功
  • Running,这个状态下,Pod已经调度成功,和一个具体的节点绑定。它包含的容器都已经创建成功,并且至少有一个正在运行中。
  • Succeed,这个状态意味着,Pod中所有容器都正常运行完毕,并且已经退出了。
  • Failed,这个状态下,Pod里至少有一个容器以不正常的状态(非0状态码)退出。这个状态的出现意味着你要想办法Debug这个容器的应用,比如查看Pod的Events和日志
  • Unknown,这是一个异常状态,意味着Pod的状态不能持续被kubelet汇报给kube-apiserver,这很可能是Master和kubelet间的通信出了问题。

Pod核心字段

可以把Pod堪称传统环境中的“机器”,把容器看作运行在这个“机器”里的“用户程序”。

凡是调度、网络、存储以及安全相关的属性,都是Pod级别的。

NodeSelector

这是一个供用户将Pod与Node进行绑定的字段,用法如下:

apiVersion: v1
kind: Pod
...
sepc:
 nodeSelector:
 disktype: ssd

这样一个配置后,这个Pod只能被调度到携带了“disktype:ssd”标签的节点上。

NodeName

一旦Pod这个字段被辅助,Kubernetes就会认为这个Pod已经经过了调度,调度的结果就是赋值的节点名字。这个字段一般由调度器负责设置,用户也可以设置这个字段“骗过”调度器

HostAliases

定义了Pod的hosts文件(/etc/hosts)里的内容。

apiVersion: v1
kind: Pod
...
spec:
 hostAliases:
 - ip: "10.1.2.3"
   hostnames:
   - "foo.remote"
   - "bar.remote"

这个Pod启动后,/etc/hosts文件内容将如下所示:

127.0.0.1 localhost
...
10.244.135.10 hostaliases-pod
10.1.2.3 foo.remote
10.1.2.3 bar.remote

在Kubernetes中如何过要设置hosts文件的内容,一定要通过这种方式,否则,如果直接修改hosts文件的话,在Pod删除重建之后,kubelet会自动覆盖掉被修改的内容。

Containers

Image、Command、workingDir、Ports以及VolumeMounts都是构成kubernetes项目中container的主要字段。还有其他几个属性值需要你关注

ImagePullPolicy

定义了镜像拉取的策略。

  • Always:默认值,即每次创建Pod都重新拉取一次镜像。另外当容器的镜像类似于nginx,nginx:latest这样的名字时,ImagePullPolicy也会被认为是Always。
  • Never:永远不主动拉取这个镜像
  • IfNotPresent:只有在宿主机上不存在这个镜像时才拉取

Lifecycle

定义的是Container Lifecycle Hooks。它的作用是在容器状态发生变化的时候触发一系列“钩子”。比如

apiVersion: v1
kind: Pod
metadata:
 name: lifecycle-demo
spec:
 containers:
 - name: lifecycle-demo-container
   image: nginx
   lifecycle:
    postStart:
     exec:
      command: ["/bin/sh","-c","echo Hello fomr the postStart handler > /usr/share/message"]
    preStop:
     exec:
      command: ["/usr/sbin/nginx","-s","quit"]

postStart,指的是在容器启动后,立刻执行一个指定的操作。postStart定义的操作,虽然是在Docker容器ENTRYPOINT执行之后,但不严格保证顺序,也就是说在postStart启动时,ENTRYPOINT有可能还没结束。

preStop,发生的时机,是容器被杀死之前。它会阻塞当前的容器杀死流程,直到这个Hook定义的操作完成。

Projected Volume

ProjectVolume可以翻译为投射数据卷,Kubernetes中,有几种特殊的Volume,它们存在的意义并不是为了存放容器中的数据,也不是用来进行容器和宿主机之间的数据交换,它们的作用是为容器提供预先定义好的数据。这些Volume中的信息好像是被Kubernetes“投射”进容器中的。

目前为止,Kubernetedes支持的Projected Volume一共有四种:

  • Secret
  • ConfigMap
  • Downward API
  • ServiceAccountToken

Secret

secret可以帮你把Pod想要访问的加密数据存放到Etcd中,然后,你就可以通过在Pod的容器里挂载Volume的方式,访问到这些Secret保存的信息

Secret使用实例:

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,是projected类型的。这个volume的数据来源则是名为user和pass的secret对象

secret创建命令:

kubectl create secret generic [NAME] [DATA] [TYPE] #TYPE默认为Opaque
kubectl create secret generic [NAME] --from-file=[KEY]=[VALUE的文件路径]
kubectl create secret generic [NAME] --from-literal=[KEY]=[VALUE] --from-literal=[KEY]=[VALUE] ....

然后,我们创建这两个secret对象

kubectl create secret generic user --from=literal=username=admin
kubectl create secret generic pass --from=literal=password=hanjiaxv123

也可以使用文件创建

$ cat ./username.txt
admin
$ cat ./password.txt
hanjiaxv123
$ kubectl create secret generic user --from-file=./username.txt
$ kubectl create secret generic pass --from-file=./password.txt

接下来,我们创建这个pod,然后进入pod。

[root@host1 ~]# kubectl create -f geektime/pod/test-projected-volume.yaml 
pod/test-projected-volume created
[root@host1 ~]# kubectl exec -it test-projected-volume -- /bin/sh
/ # ls /projected-volume
pass      username
/ # cd /projected-volume
/projected-volume # cat pass
hanjiaxv123/projected-volume # cat username

ConfigMap

ConfigMap与secret的区别在于,ConfigMap保存的是不需要加密的、应用所需的配置信息。

ConfigMap用法几乎和secret相同。

ConfigMap创建语法:

kubectl create configmap [NAME] [DATA]

指定文件:

kubectl create configmap kube-flannel-cfg --from-file=configure-pod-container/configmap/cni-conf.json -n kube-system

指定键值对:

kubectl create configmap special-config --from-literal=special.how=very --from-literal=special.type=charm

ConfigMap使用

apiVersion: v1
kind: ConfigMap
metadta:
 name: special-config
data:
 special.how: very
 special.type: charm

第一种,用ConfigMap配置环境变量

apiVersion: v1
kind: Pod
metadata:
 name: cm-env-test
spec:
 containers:
  - name: test-container
    image: k8s.gcr.io/busybox
    command: ["/bin/sh","-c","env"]
    env:
     #使用special-config中的special.how定义环境变量
     - name: SPECIAL_LEVEL_KEY
       valueFrom:
        configMapKeyRef:
         name: special-config
         key: special.how

第二种,使用ConfigMap管控命令行参数

apiVersion: v1
kind: Pod
metadata:
 name: cm-cmd-test
spec:
 containers:
 - name: test-container
   image: k8s.io.gcr/busybox
   command: ["/bin/sh","-c","echo $(SPECIAL_LEVEL_KEY)"]
   env:
   - name: SPECIAL_LEVEL_KEY
     valueFrom:
      configMapKeyRef:
       name: special-config
       key: special.how

第三种,使用ConfigMap挂载配置文件

apiVersion: v1
kind: Pod
metadata:
 name: cm-volume-test
spec:
 containers:
  - name: test-container
    image: k8s.io.gcr/busybox
    command: ["/bin/sh","-c","ls /etc/config/"]
    volumeMounts:
    - name: config-volume
      mountPath: /etc/config
 volumes:
  - name: config-volume
    configMap:
     name: special-config

Service Account

如果现在有一个Pod,我能否在这个Pod里安装一个Kubernetes的Client,这样就可以从容器里直接访问并操作这个Kubernetes的API了呢?

答案是可以。

首先,要解决API Server的授权问题

Service Account对象的作用,就是Kubernetes系统内置的一种“服务账号”,它是Kubernetes进行权限分配的对象。比如,Service Account A,可以只被允许对Kubernetes API进行GET操作,而Service Account B,则可以有Kubernetes API的所有操作权限。

这种Service Account的授权信息和文件,实际上保存在它所绑定的一个特殊的Secret对象中,这种特殊的Service Account对象,就叫做ServiceAccountToken。任何运行在Kubernetes集群上的应用,都必须使用这个ServiceAccountToken中保存的授权信息,才可以合法地访问API Server。ServiceAccountToken只是一种特殊地Secret。

Kubernetes已经为你提供了一个默认“服务账户”,并且,任何一个运行在Kubernetes里的Pod,都可以直接使用这个默认的Service Account,无需显示地声明挂载它。

查看一下集群中运行的Pod,就会发现,每一个Pod,都已经声明了一个类型是Secret、名为default-token-xxxx的Volume,然后自动挂载在每一个容器的固定目录上。

[root@host1 ~]# kubectl describe pods test-projected-volume
...
Volumes:
  mysql-cred:
    Type:                Projected (a volume that contains injected data from multiple sources)
    SecretName:          user
    SecretOptionalName:  
    SecretName:          pass
    SecretOptionalName:  
  default-token-j5k7m:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-j5k7m
    Optional:    false
....

Kubernetes在每个Pod创建的时候,自动在它的spec.volumes部分添加上了默认ServiceAccountToken的定义,然后自动给每个容器加上了对应的volumeMounts字段。这个容器内的路径在Kubernetes里是固定的,即:/var/run/secrets/kubernetes.io/serviceaccount,这个Secret类型的Volume内容如下所示:

/ # ls /var/run/secrets/kubernetes.io/serviceaccount/
ca.crt     namespace  token

你的应用程序只要加载这些授权文件,就可以访问并操作Kubernetes API了。如果使用的是Kubernetes官方的Client包,还可以自动加载这个目录下的文件。

Pod的健康检查机制

Liveness Probe是存活探针、Readiness是就绪探针。

一个程序在长时间运行后或者有bug的情况下,会进入不正常状态,Liveness会将容器内的进程杀死,重启整个容器/Pod,使得Pod恢复最初始的状态。readiness探测失败后,Pod和容器并不会被删除,而是被标记为特殊状态,进入这个状态后,会切断上层流量到达该Pod。

探测方式

  • httpGet 通过发送http Get请求返回200-399状态码则表示容器健康
  • Exec 通过执行命令检查服务是否正常,命令返回值为0表示容器健康
  • tcpSocket 通过容器的IP和PORT执行TCP检查,如果可以建立TCP连接表示容器健康

探测结果

  • Success Container通过了检查
  • Failure Container未能通过检查
  • Unknown 未能执行检查,不采取任何动作

重启策略

  • Always 总是重启
  • OnFailure 失败才重启
  • Never 永远不重启

实例参考

exec方式:

apiVersion: v1
kind: Pod
metadata:
 labels:
  test: liveness
 name: liveness-exec
spec:
 containers:
 - name: liveness
   image: k8s.gcr.io/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

httpGet

apiVersion: v1
kind: Pod
metadata:
 labels:
  test: liveness
 name: liveness-http
spec:
 containers:
 - name: liveness
   image: k8s.gcr.io/liveness
   args:
   - /server
   livenessProbe:
    httpGet:
     path: /healthz
     port: 8080
     httpHeaders:
     - name: Custom-Header
       value: Awesome
    initialDelaySeconds: 3
    periodSeconds: 3

tcpSocket

apiVersion: v1
kind: Pod
metadata:
 name: goproxy
 labels:
  app: goproxy
spec:
 containers:
 - name: goproxy
   image: k8s.gcr.io/goproxy:0.1
   ports:
   - containerPort: 8080
     readinessProbe:
      tcpSocket:
       port: 8080
      initialDelaySeconds: 5
      periodSeconds: 5
     livenessProbe:
      tcpSocket:
       port: 8080
      initialDelaySeconds: 15
      periodSeconds: 20

参数:

  • initialDelaySeconds:Pod启动后延迟多久进行检查
  • periodSeconds:检查间隔事件,默认10S
  • timeoutSeconds:探测的超时时间,默认1S
  • successThreshold:探测失败后再次判断成功的阈值,默认为1次
  • failureThreshold:探测失败的重试次数,默认3次

总结

Liveness(存活探针) Readiness(就绪探针)
介绍 用于判断容器是否存活,即容器的状态是否是Running,如果Liveness探针判断容器不健康,则会触发kubelet杀掉容器,并根据配置的策略判断是否重启容器,如果默认不配置Liveness探针,则认为返回值默认为成功 用于判断容器是否启动完成,即Pod得Condition是否为Ready,如果探测结果不成功,则会将Pod从Endpoint中移除,直至下次判断成功,再将Pod挂回到Endpoint上
检测失败 杀掉Pod 切断上层流量到Pod
使用场景 支持重新拉起的应用 启动后无法立即对外服务的应用

参考: 阿里云大学云原生技术公开课 极客时间深入剖析Kubernetes

你可能感兴趣的:(Kubernetes-Pod详解)