k8s之Pod基础


之前说过,出于Docker的进程管理机制,一个Container最好只运行一个进程。因此,k8s以Pod为最基本的调度单位,能够将一些关联性很强的进程分别运行在不同容器中,并将这些容器绑定在一起,从而实现不同进程的联系和管理。如果没有Pod的话,在多节点集群中,这些容器很有可能会被部署到不同的node上,这就导致了这些进程之间不易联系,所以需要把它们“绑“起来放在一个node中运行,这就是Pod设计的初衷。

Pod原理

其实 Pod 也只是一个逻辑概念,真正起作用的还是 Linux 中 容器的 NamespaceCgroup 这两个最基本的概念,Pod 被创建出来其实是一组共享了一些资源的容器而已。首先 Pod 里面的所有容器,都是共享的同一个 Network namespace,但是涉及到文件系统的时候,默认情况下 Pod 里面的容器之间的文件系统是完全隔离的,但是我们可以通过声明来共享同一个 Volume

对于共享同一个Network namespace这个概念是不是比较熟悉,在 Docker 网络模式中,我们可以指定新创建的容器和一个已经存在的容器共享一个 Network namespace,在运行容器的时候只需要指定 --net=container:目标容器名 这个参数就可以了,但是这种模式有一个明显的问题,那就是容器的启动有先后顺序问题,那么 Pod 是怎么来处理这个问题的呢?

那就是加入一个中间容器(没有什么架构是加一个中间件解决不了的?)这个容器叫做 Infra 容器,全称 Infrastructure Container,又叫Pause容器。这个容器在 Pod 中永远都是第一个被创建的容器,这样是不是其他容器都加入到这个 Infra 容器就可以了,这样就完全实现了 Pod 中的所有容器都和 Infra 容器共享同一个 Network namespace 了,如下图所示:

所以当我们部署完成 k8s 集群的时候,首先需要保证在所有节点上可以拉取到默认的 Infra 镜像,默认情况下 Infra 镜像地址为 k8s.gcr.io/pause:3.1,这个容器占用的资源非常少,但是这个镜像默认是需要科学上网的,所以很多时候我们在部署应用的时候一直处于 Pending 状态,因为所有 Pod 最先启动的容器镜像都拉不下来,启动不了,那其他容器肯定也就不能启动了。

[root@master k8s]$ kubelet --help |grep infra
      --pod-infra-container-image string                                                                          The image whose network/ipc namespaces containers in each pod will use. This docker-specific flag only works when container-runtime is set to docker. (default "k8s.gcr.io/pause:3.1")

从上面图中我们可以看出普通的容器加入到了 Infra 容器的 Network namespace 中,所以这个 Pod 下面的所有容器就是共享同一个 Network Namespace 了,普通容器不会创建自己的网卡,配置自己的 IP,而是和 Infra 容器共享 IP、端口范围等,而且容器之间的进程可以通过 lo 网卡设备进行通信,也就是说:

  • 在Pod内部,容器之间可以直接用localhost来通信;
  • 看到的网络设备信息和Infra容器完全一样;
  • 同一个Pod下的容器不能绑定相同的端口;
  • Pod的声明周期只跟Infra容器一致,而与容器A和B无关。

当然,Infra还提供其他的namespace,汇总如下:

  • PID namespace:Pod中的不同应用程序可以看到其他应用程序的进程ID。
  • Network namespace:Pod中的多个容器能够访问同一个IP和端口范围。
  • IPC namespace:Pod中的多个容器能够使用SystemV IPC或POSIX消息队列进行通信。
  • UTS namespace:Pod中的多个容器共享一个主机名。

对于文件系统 k8s 是怎么实现让一个 Pod 中的容器共享的呢?默认情况下容器的文件系统是互相隔离的,要实现共享只需要在 Pod 的顶层声明一个 Volume,然后在需要共享这个 Volume 的容器中声明挂载即可。

比如下面的示例:

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  volumes: 
  - name: varlog
    hostPath: 
      path: /var/log/counter
  containers:
  - name: count
    image: busybox
    args:
    - /bin/sh
    - -c
    - >
      i=0;
      while true;
      do
        echo "$i: $(date)" >> /var/log/1.log;
        i=$((i+1));
        sleep 1;
      done
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  - name: count-log
    image: busybox
    args: [/bin/sh, -c, 'tail -n+1 -f /var/log/1.log']
    volumeMounts:
    - name: varlog
      mountPath: /var/log

上述Pod创建了一个名为varlog的Volume,且这个Volume的类型是hostPath,意味着宿主机的/var/log/counter目录将被这个Pod挂载,即共享。那共享给谁呢?在Pod中需要挂载的容器上声明挂载即可。可以看到,第一个容器count将上述Volume挂载到了自己的/var/log中,挂载点名为carlog,第二个容器count-log也是一样。

这个方式也是 Kubernetes 中一个非常重要的设计模式:sidecar 模式的常用方式。典型的场景就是容器日志收集,比如上面我们的这个应用,其中应用的日志是被输出到容器的 /var/log 目录上的,这个时候我们可以把 Pod 声明的 Volume 挂载到容器的 /var/log 目录上,然后在这个 Pod 里面同时运行一个 sidecar 容器,他也声明挂载相同的 Volume 到自己容器的 /var/log (或其他)目录上,这样在第一个容器中的日志信息就会同步到sidecar容器中了。这个 sidecar 容器就只需要从 /var/log 目录下面不断消费日志发送到 Elasticsearch 中存储起来就完成了最基本的应用日志的基本收集工作了。

除了这个应用场景之外使用更多的还是利用 Pod 中的所有容器共享同一个 Network Namespace 这个特性,这样我们就可以把 Pod 网络相关的配置和管理也可以交给一个 sidecar 容器来完成,完全不需要去干涉用户容器,这个特性在现在非常火热的 Service Mesh(服务网格)中应用非常广泛,典型的应用就是 Istio。这个之后再说。


文章作者: SrcMiLe
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 SrcMiLe !
评论
  目录