带着问题学习Kubernetes第二篇-POD如何管理?

第一篇我们带着问题学习了Kubernetes的总体架构,了解了K8S的各个组件以及它们的作用,这篇文章将继续带着问题学习K8S中最为重要的概念POD,K8S中为什么引入POD而不是直接管理Docker,POD又是如何管理容器的,K8S是如何管理POD的,POD的生命周期等一系列问题。

问题一:Kubernetes为什么将POD作为最小的可部署单元,而不是一个单一的容器?

虽然直接部署单个容器似乎比较简单,但是有充分的理由需要添加一个由POD表示的抽象层。容器是一个实体,它指一个特定的事物。这个具体的事物可能Docker容器,但它也可能是一个rkt的容器,或由virtlet管理的虚拟机。每一种都有不同的要求。

更重要的是,Kubernetes需要更多的信息来管理一个容器,如重新启动策略,它定义了当容器终止时,或活性的探针时,进行哪些动作;从应用程序的角度,它定义了检测动作,判断容器进程是否还活着,如Web服务器响应HTTP请求。

区别于在现有的“实体”上超载附加属性,Kubernetes决定使用一个新的实体,POD。POD可以被认为是逻辑容器,包含一个或多个容器(包装),作为一个单一的实体被管理。

问题二:Kubernetes为什么让一个POD里放置多个容器?

POD中的容器运行在“逻辑主机”上;它们使用相同的网络namespace(换句话说,相同的IP地址和端口空间),以及相同的IPC名称空间。它们还可以使用共享卷。这些属性使得这些容器能够有效地通信,确保数据本地化。此外,POD使您可以以一个单元的模式管理几个紧密耦合的应用程序容器。

如果应用程序需要在同一主机上运行多个容器,为什么不将单个容器与所需的所有内容组合起来呢?首先,这样做很可能违反“一个容器一个进程”的原则。这很重要,因为同一个容器中有多个过程,系统将很难排错。因为来自不同过程的日志将被混合在一起,很难管理进程的生命周期,例如当父进程死亡后处理“僵尸”的过程。其次,一个应用程序使用多个容器将使系统更简单、更透明,并能使软件依赖关系解耦。同时,更精细颗粒的容器可以在团队之间重复使用。

问题三:Kubernetes如何在一个POD里放置多个容器?

假设有两个容器,一个是 nginx 容器,做静态服务器,一个是 git-sync 容器,用于定时监测 git 仓库上代码的变化,拉取最新代码到本地。这是两个独立的容器,如果把它们放在一个 Pod 里面,Pod 的特点是内部网络共享、数据空间共享。这样就大大减少了原先跨容器访问的复杂度。这里通过配置文件来启动包含 nginx、git-sync 容器的 Pod,使用Deployment的方式创建多个Container,还有很多的其他方式,比如在POD中定义,通过ReplicationController等方式。

配置文件 nginx-git.yml 具体内容如下:(官方参考文档):

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nginx-git
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx # 启动 nginx 容器
        image: nginx
        volumeMounts: # 挂载数据卷
        - mountPath: /usr/share/nginx/html
          name: git-volume
      - name: git-sync # 启动 git-sync 容器
        image: openweb/git-sync
        env:
        - name: GIT_SYNC_REPO
          value: "https://github.com/jasonGeng88/test.git"
        - name: GIT_SYNC_DEST
          value: "/git"
        - name: GIT_SYNC_BRANCH
          value: "master"
        - name: GIT_SYNC_REV
          value: "FETCH_HEAD"
        - name: GIT_SYNC_WAIT
          value: "10"
        volumeMounts: # 挂载数据卷
        - mountPath: /git
          name: git-volume
      volumes: # 共享数据卷
      - name: git-volume
        emptyDir: {}

问题四:POD中多个容器间如何通讯?

方法一:使用共享卷方式

在Kubernetes,你可以使用一个共享的Kubernetes卷作为一种简单而有效的方式,在一个POD内容器间进行数据共享。在大多数情况下,在主机上使用与容器内所有容器共享的目录,是足够的。

Kubernetes卷数据支持容器重启,但这些卷与POD寿命一样。这意味着卷容量(所拥有的数据)和POD存在时间一样。如果那个POD被删除,即使同类更换创建,共享卷被销毁和创建。

一个使用共享卷的标准多容器POD的用例是,在一个容器写日志和其他文件到共享目录中,其他容器从这个共享目录中可以读取数据。例如,我们可以创建一个POD:

apiVersion: v1
kind: Pod
metadata:
  name: mc1
spec:
  volumes:
  - name: html
    emptyDir: {}
  containers:
    - name: 1st
      image: nginx
      volumeMounts:
        - name: html
          mountPath: /usr/share/nginx/html
    - name: 2nd
      image: debian
      volumeMounts:
        - name: html
          mountPath: /html    
      command: ["/bin/sh", "-c"]
      args:
        - while true; do
            date >> /html/index.html;
            sleep 1;
          done

在这个例子中,我们定义了一个名为HTML的卷。它的类型是emptyDir,这意味着当一个POD被分配到一个节点时,卷先被创建,并只要Pod在节点上运行时,这个卷仍存在。正如名字所说,它最初是空的。第一容器运行nginx的服务器并将共享卷挂载到目录/ usr /share/ Nginx /HTML。第二容器使用Debian的镜像,并将共享卷挂载到目录/HTML。每一秒,第二容器添加当前日期和时间到index.html文件中,它位于共享卷。当用户发出一个HTTP请求到POD,nginx的服务器读取该文件并将其传递给响应请求的用户。

你可以检查一下,POD可以通过暴露nginx端口并使用您的浏览器访问它,或通过直接检查容器中的共享目录:

$ kubectl exec mc1 -c 1st -- /bin/cat /usr/share/nginx/html/index.html
...
Fri Aug 25 18:38:00 UTC 2017

$kubectl exec mc1 -c 2nd -- /bin/cat /html/index.html
...
Fri Aug 25 18:38:00 UTC 2017
Fri Aug 25 18:38:08 UTC 2017

方法二:使用进程间IPC通讯

一个POD里的容器共享相同的IPC命名空间,这意味着他们也可以互相使用标准进程间通信,如SystemV信号系统或POSIX共享内存。

在下面的示例中,我们定义了一个带有两个容器的POD。我们使用相同的容器镜像。第一个容器,生产者,创建一个标准的Linux消息队列,写一些随机消息,然后写一个特殊的退出消息。第二个容器,消费者,打开相同的消息队列以便读取和读取消息,直到接收到退出消息。我们还将重启策略设置为“从不”,所以只有在两个容器都终止后POD停止。

apiVersion: v1
kind: Pod
metadata:
  name: mc2
spec:
  containers:
    - name: producer
      image: allingeek/ch6_ipc
      command: ["./ipc", "-producer"]
    - name: consumer
      image: allingeek/ch6_ipc
      command: ["./ipc", "-consumer"]
  restartPolicy:Never

为了验证这一点,使用kubectl 创建POD,并观察POD状态:

$kubectl get pods --show-all -w
NAME      READY      STATUS                RESTARTS     AGE
mc2       0/2        Pending               0            0s
mc2       0/2        ContainerCreating     0            0s
mc2       0/2        Completed             0            29s

现在您可以检查每个容器的日志,并验证第二个容器接收了来自第一个容器的所有消息,包括退出消息:

$ kubectl logs mc2 -c producer
...
Produced: f4
Produced: 1d
Produced: 9e
Produced: 27
$ kubectl logs mc2 -c producer
...
Produced: f4
Produced: 1d
Produced: 9e
Produced: 27
Consumed: done
问题五:POD如何共享Volume

Kubernetes的Volume概念,用途和目的与Docker的Volume比较类似,但两者不能等价。首先,Kubernetes中的Valume定义在Pod上,然后被一个POD的多个容器挂载到具体的目录下。其次,Kubernetes中的Volume与Pod的生命周期相同,但与容器的生命周期不相关,当容器重启时,Volume的数据不会丢失。最后,Kubernetes支持多种类型的Volume,包括NFS,GlusterFS,云存储等先进的分布式文件系统。
Kubernetes中除了这种挂在POD上的共享存储外,还可以使用PersistentVolume和PersistentVolumeClaim这种方式来实现网络存储,不同是挂在Pod上的共享存储属于一种“计算资源”,而网络存储属于一种“实体资源”,与Pod无关。

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: loganalysis
    image: loganalysis
    volumeMounts:
    - name: app-logs
      mountPath: /usr/local/tomcat/logs
  volumes:
  - name: app-logs
  emptyDir: {}

上面的配置文件中,volumes配置声明是用于定义volume的,volumeMounts是用于在Pod中引用定义的Volume。mountPath定义容器的挂在目录。volumes的配置声明有以下三种方式:

volumes:
  - name: app-logs
  emptyDir: {}


  volumes:
  - name: app-logs
  hostPth: 
    path: "/data"


  volumes:
  - name: app-logs
  gcePersistenDisk: 
    pdName: my-data-disk  //my-data-disk需要先创建好    fsType: ext4

emptyDir是Pod分配到Node后创建的,他的初始内容为空,pod在Node上移除以后也就被销毁了。
hostPath是挂载到宿主机上的目录,比较适用于需要永久保存的数据
gcePersistenDisk 表示使用谷歌公有云提供的磁盘
创建my-data-disk: gcloud compute disks create --size=500GB --zone=us-centrall-a my-data-disk

问题六:Pod的生命周期和重启策略

pod一共有四种状态,Pending,Running,Failed,Unknown。如下表:

状态值描述PendingAPIserver已经创建该server,但pod内有一个或多个容器的镜像还未创建,可能在下载中RunningPod内所有的容器已创建,且至少有一个容器处于运行状态,正在启动或重启状态FailedPod内所有容器都已退出,其中至少有一个容器退出失败Unknown由于某种原因无法获取Pod的状态比如网络不通

重启策略如下表:

重启策略描述Always容器失效时,即重启OnFailure容器终止运行,且退出码不为0 时重启Never不重启

问题七:Pod是如何调度

Pod调度依据不同的应用场景选择不同的调度方式,可以通过RC,Deployment,DaemonSet,Job,StatefulSet等方式。

方式一:RC,Deployment全自动调度

RC,Deployment的主要功能之一就是自动部署一个容器应用的多份副本,以及持续监控,保持集群内有一定数量的副本数量(配置文件指定了副本数量),如下实例:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

方式二:NodeSelect定向调度

kubernetes中的Schduler 负责实现pode的调度,他会根据一些复杂的算法,把pod调度到某一个Node上,如果你想指定某个Pod需要指定在某个Node上则可以通过NodeSelector定向调度。方式如下:第一步,给Node打上label,如下:

格式: kubectl label nodes <node-name> <label-key>=<label-value>

kubectl label nodes node1 zone=north

第二步,将pod调度到某个node上

apiVersion: v1
kind: Pod
metadata:
name: pod-with-healthcheck
spec:
containers:
- name: nginx
  image: nginx
  ports:
  - containerPort: 80
nodeSelector:  zone: north

方式三:DaemonSet特定场景调度

DaemonSet 确保全部(或者一些)Node 上运行一个 Pod 的副本。当有 Node 加入集群时,也会为他们新增一个 Pod 。当有 Node 从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。
使用 DaemonSet 的一些典型用法:
运行集群存储 daemon,例如在每个 Node 上运行 glusterd、ceph。
在每个 Node 上运行日志收集 daemon,例如fluentd、logstash。
在每个 Node 上运行监控 daemon,例如 Prometheus Node Exporter、collectd、Datadog 代理、New Relic 代理,或 Ganglia gmond。一个简单的用法是,在所有的 Node 上都存在一个 DaemonSet,将被作为每种类型的 daemon 使用。 一个稍微复杂的用法可能是,对单独的每种类型的 daemon 使用多个 DaemonSet,但具有不同的标志,和/或对不同硬件类型具有不同的内存、CPU要求。

示例:配置使得在每个节点上都有一个fluentd 容器

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
 name: fluentd-cloud-logging
 namespace: kube-system
 labels:
   k8s-app: fluentd-cloud-logging
spec:
  template:
    metadata:
       namespace: kube-system
       labels:
         k8s-app: fluentd-cloud-logging
    spec:
      containers:
      - name: fluentd-cloud-logging
        images: gcr.io/google_containers/fluentd-elasticsearch:1.17
        resources:
          limits:
            cpu: 100m
            memory: 200Mi
        env:
        - name: FLUENTD_ARGS
          value: -q
        volumeMounts:
        - name: varlog
          mountPath: /var/log
          readOnly: false
        - name: containers
          mountPath: /var/lib/docker/containers
      volumes:
      - name: containers
        hostPath:
          path: /var/lib/docker/containers
      - name: varlog
        hostPath:
          path: /var/log

方式四:StatefuleSet带有状态的Pod部署

StatefulSet是一种给Pod提供唯一标志的控制器,它可以保证部署和扩展的顺序。

Pod一致性:包含次序(启动、停止次序)、网络一致性。此一致性与Pod相关,与被调度到哪个node节点无关。

稳定的次序:对于N个副本的StatefulSet,每个Pod都在[0,N)的范围内分配一个数字序号,且是唯一的。

稳定的网络:Pod的hostname模式为名称(序号)。

稳定的存储:通过VolumeClaimTemplate为每个Pod创建一个PV。删除、减少副本,不会删除相关的卷。

参考下面的实例:

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1beta2
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx
  serviceName: "nginx"
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: disk-ssd
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: disk-ssd
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "alicloud-disk-ssd"
      resources:
        requests:
          storage: 20Gi

验证服务伸缩性

创建Statefulset服务:

# kubectl create -f statefulset.yaml

# kubectl get pod
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          21m
web-1     1/1       Running   0          20m

# kubectl get pvc
NAME             STATUS    VOLUME                   CAPACITY   ACCESS MODES   STORAGECLASS        AGE
disk-ssd-web-0   Bound     d-2ze9k2rrtcy92e97d3ie   20Gi       RWO            alicloud-disk-ssd   21m
disk-ssd-web-1   Bound     d-2ze5dwq6gyjnvdcrmtwg   20Gi       RWO            alicloud-disk-ssd   21m

扩容服务到3个Pod,显示会创建新的云盘卷:

# kubectl scale sts web --replicas=3
statefulset.apps "web" scaled

# kubectl get pod
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          24m
web-1     1/1       Running   0          23m
web-2     1/1       Running   0          2m

# kubectl get pvc
NAME             STATUS    VOLUME                   CAPACITY   ACCESS MODES   STORAGECLASS        AGE
disk-ssd-web-0   Bound     d-2ze9k2rrtcy92e97d3ie   20Gi       RWO            alicloud-disk-ssd   24m
disk-ssd-web-1   Bound     d-2ze5dwq6gyjnvdcrmtwg   20Gi       RWO            alicloud-disk-ssd   24m
disk-ssd-web-2   Bound     d-2zea5iul9f4vgt82hxjj   20Gi       RWO            alicloud-disk-ssd   2m

缩容服务到2个Pod,显示pvc/pv并不会一同删除:

# kubectl scale sts web --replicas=2
statefulset.apps "web" scaled

# kubectl get pod
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          25m
web-1     1/1       Running   0          25m

# kubectl get pvc
NAME             STATUS    VOLUME                   CAPACITY   ACCESS MODES   STORAGECLASS        AGE
disk-ssd-web-0   Bound     d-2ze9k2rrtcy92e97d3ie   20Gi       RWO            alicloud-disk-ssd   25m
disk-ssd-web-1   Bound     d-2ze5dwq6gyjnvdcrmtwg   20Gi       RWO            alicloud-disk-ssd   25m
disk-ssd-web-2   Bound     d-2zea5iul9f4vgt82hxjj   20Gi       RWO            alicloud-disk-ssd   3m

再次扩容到3个Pod,新的pod会复用原来的PVC/PV:

# kubectl scale sts web --replicas=3
statefulset.apps "web" scaled

# kubectl get pod
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          27m
web-1     1/1       Running   0          27m
web-2     1/1       Running   0          2m

# kubectl get pvc
NAME             STATUS    VOLUME                   CAPACITY   ACCESS MODES   STORAGECLASS        AGE
disk-ssd-web-0   Bound     d-2ze9k2rrtcy92e97d3ie   20Gi       RWO            alicloud-disk-ssd   27m
disk-ssd-web-1   Bound     d-2ze5dwq6gyjnvdcrmtwg   20Gi       RWO            alicloud-disk-ssd   27m
disk-ssd-web-2   Bound     d-2zea5iul9f4vgt82hxjj   20Gi       RWO            alicloud-disk-ssd   5m

删除StatefulSet服务,PVC、PV并不会随着删除;

验证服务稳定性

删除一个Pod前,Pod引用PVC:disk-ssd-web-1

# kubectl describe pod web-1 | grep ClaimName
ClaimName:  disk-ssd-web-1

# kubectl delete pod web-1
pod "web-1" deleted

删除Pod后,重新创建的Pod名字与删除的一致,且使用同一个PVC:

# kubectl get pod
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          29m
web-1     1/1       Running   0          6s
web-2     1/1       Running   0          4m

# kubectl describe pod web-1 | grep ClaimName
ClaimName:  disk-ssd-web-1

验证服务高可用性

云盘中创建临时文件:

# kubectl exec web-1 ls /data
lost+found
# kubectl exec web-1 touch /data/statefulset
# kubectl exec web-1 ls /data
lost+found
statefulset

删除Pod,验证数据持久性:

# kubectl delete pod web-1
pod "web-1" deleted

# kubectl exec web-1 ls /data
lost+foundstatefulset

更多请参考
https://yq.aliyun.com/articles/629007
https://jimmysong.io/kubernetes-handbook/concepts/statefulset.html

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。