zoukankan      html  css  js  c++  java
  • 13. 为什么我们会需要 Pod?

    13. 为什么我们会需要 Pod?

    13.1 docker容器的本质

    """
    docker容器的本质  是进程.
    主要通过
    Namespace 做隔离,Cgroups 做限制,rootfs 做文件系统
    """ 
    
    

    疑问: 既然有容器为什么 Kubernetes 项目又突然搞出一个 Pod 来呢?

    要回答这个问题,我们还是要一起回忆一下我曾经反复强调的一个问题:容器的本质到底是什么?

    你现在应该可以不假思索地回答出来:容器的本质是进程。

    没错。容器,就是未来云计算系统中的进程;容器镜像就是这个系统里的“.exe”安装包。那么 Kubernetes 呢?

    你应该也能立刻回答上来:Kubernetes 就是操作系统!

    非常正确。

    13.2 进程组

    现在,就让我们登录到一台 Linux 机器里,执行一条如下所示的命令

    pstree -g
    
    systemd(1)-+-accounts-daemon(1984)-+-{gdbus}(1984)
               | `-{gmain}(1984)
               |-acpid(2044)
              ...      
               |-lxcfs(1936)-+-{lxcfs}(1936)
               | `-{lxcfs}(1936)
               |-mdadm(2135)
               |-ntpd(2358)
               |-polkitd(2128)-+-{gdbus}(2128)
               | `-{gmain}(2128)
               |-rsyslogd(1632)-+-{in:imklog}(1632)
               |  |-{in:imuxsock) S 1(1632)
               | `-{rs:main Q:Reg}(1632)
               |-snapd(1942)-+-{snapd}(1942)
               |  |-{snapd}(1942)
               |  |-{snapd}(1942)
               |  |-{snapd}(1942)
               |  |-{snapd}(1942)
    
    
    # 小插曲: 当只知道一个命令,如果查询这个命令属于哪个rpm包中?
    1. 命令存在时,使用rpm -qf `which 命令`
    [root@node02 tools]# rpm -qf `which yum`
    yum-3.4.3-161.el7.centos.noarch
    
    2. 命令不存在时,使用yum whatprovides 命令
    [root@node02 tools]# yum whatprovides pstree
    Loaded plugins: fastestmirror
    Loading mirror speeds from cached hostfile
     * base: mirrors.aliyun.com
     * extras: mirrors.aliyun.com
     * updates: mirrors.aliyun.com
    docker-ce-stable/x86_64/filelists_db                             |  16 kB  00:00:00     
    kubernetes/filelists                                             |  18 kB  00:00:00     
    psmisc-22.20-15.el7.x86_64 : Utilities for managing processes on your system
    Repo        : base
    Matched from:
    Filename    : /usr/bin/pstree
    

    不难发现,在一个真正的操作系统里,进程并不是“孤苦伶仃”地独自运行的,而是以进程组的方式,“有原则的”组织在一起。在这个进程的树状图中,每一个进程后面括号里的数字,就是它的进程组 ID(Process Group ID, PGID

    对于操作系统来说,这样的进程组更方便管理。举个例子,Linux 操作系统只需要将信号,比如,SIGKILL 信号,发送给一个进程组,那么该进程组中的所有进程就都会收到这个信号而终止运行。

    Kubernetes 项目所做的,其实就是将“进程组”的概念映射到了容器技术中,并使其成为了这个云计算“操作系统”里的“一等公民”。

    13.3 容器设计模式

    Pod 在 Kubernetes 项目里还有更重要的意义,那就是:容器设计模式

    为了理解这一层含义,我就必须先给你介绍一下Pod 的实现原理。

    首先,关于 Pod 最重要的一个事实是:它只是一个逻辑概念。

    也就是说,Kubernetes 真正处理的,还是宿主机操作系统上 Linux 容器的 Namespace 和 Cgroups,而并不存在一个所谓的 Pod 的边界或者隔离环境。

    那么,Pod 又是怎么被“创建”出来的呢?

    答案是:Pod,其实是一组共享了某些资源的容器

    具体的说:Pod 里的所有容器,共享的是同一个 Network Namespace,并且可以声明共享同一个 Volume。

    那这么来看的话,一个有 A、B 两个容器的 Pod,不就是等同于一个容器(容器 A)共享另外一个容器(容器 B)的网络和 Volume 的玩儿法么?

    这好像通过 docker run --net --volumes-from 这样的命令就能实现嘛,比如:

    $ docker run --net=B --volumes-from=B --name=A image-A ...
    

    但是,你有没有考虑过,如果真这样做的话,容器 B 就必须比容器 A 先启动,这样一个 Pod 里的多个容器就不是对等关系,而是拓扑关系了。

    所以,在 Kubernetes 项目里,Pod 的实现需要使用一个中间容器,这个容器叫作 Infra 容器。在这个 Pod 中,Infra 容器永远都是第一个被创建的容器,而其他用户定义的容器,则通过 Join Network Namespace 的方式,与 Infra 容器关联在一起。这样的组织关系,可以用下面这样一个示意图来表达:

    如上图所示,这个 Pod 里有两个用户容器 A 和 B,还有一个 Infra 容器。很容易理解,在 Kubernetes 项目里,Infra 容器一定要占用极少的资源,所以它使用的是一个非常特殊的镜像,叫作:k8s.gcr.io/pause。这个镜像是一个用汇编语言编写的、永远处于“暂停”状态的容器,解压后的大小也只有 100~200 KB 左右。

    而在 Infra 容器“Hold 住”Network Namespace 后,用户容器就可以加入到 Infra 容器的 Network Namespace 当中了。所以,如果你查看这些容器在宿主机上的 Namespace 文件(这个 Namespace 文件的路径,我已经在前面的内容中介绍过),它们指向的值一定是完全一样的。

    这也就意味着,对于 Pod 里的容器 A 和容器 B 来说:

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

    而对于同一个 Pod 里面的所有用户容器来说,它们的进出流量,也可以认为都是通过 Infra 容器完成的。这一点很重要,因为将来如果你要为 Kubernetes 开发一个网络插件时,应该重点考虑的是如何配置这个 Pod 的 Network Namespace,而不是每一个用户容器如何使用你的网络配置,这是没有意义的。

    这就意味着,如果你的网络插件需要在容器里安装某些包或者配置才能完成的话,是不可取的:Infra 容器镜像的 rootfs 里几乎什么都没有,没有你随意发挥的空间。当然,这同时也意味着你的网络插件完全不必关心用户容器的启动与否,而只需要关注如何配置 Pod,也就是 Infra 容器的 Network Namespace 即可。

    有了这个设计之后,共享 Volume 就简单多了:Kubernetes 项目只要把所有 Volume 的定义都设计在 Pod 层级即可。

    这样,一个 Volume 对应的宿主机目录对于 Pod 来说就只有一个,Pod 里的容器只要声明挂载这个 Volume,就一定可以共享这个 Volume 对应的宿主机目录。比如下面这个例子:

    apiVersion: v1
    kind: Pod
    metadata:
      name: two-containers
    spec:
      restartPolicy: Never
      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"]
    

    在这个例子中,debian-container 和 nginx-container 都声明挂载了 shared-data 这个 Volume。而 shared-data 是 hostPath 类型。所以,它对应在宿主机上的目录就是:/data。而这个目录,其实就被同时绑定挂载进了上述两个容器当中。

    这就是为什么,nginx-container 可以从它的 /usr/share/nginx/html 目录中,读取到 debian-container 生成的 index.html 文件的原因。

    明白了 Pod 的实现原理后,我们再来讨论“容器设计模式”,就容易多了。

    Pod 这种“超亲密关系”容器的设计思想,实际上就是希望,当用户想在一个容器里跑多个功能并不相关的应用时,应该优先考虑它们是不是更应该被描述成一个 Pod 里的多个容器。

    为了能够掌握这种思考方式,你就应该尽量尝试使用它来描述一些用单个容器难以解决的问题。

    第一个最典型的例子是:WAR 包与 Web 服务器。

    我们现在有一个 Java Web 应用的 WAR 包,它需要被放在 Tomcat 的 webapps 目录下运行起来。

    假如,你现在只能用 Docker 来做这件事情,那该如何处理这个组合关系呢?

    • 一种方法是,把 WAR 包直接放在 Tomcat 镜像的 webapps 目录下,做成一个新的镜像运行起来。可是,这时候,如果你要更新 WAR 包的内容,或者要升级 Tomcat 镜像,就要重新制作一个新的发布镜像,非常麻烦。
    • 另一种方法是,你压根儿不管 WAR 包,永远只发布一个 Tomcat 容器。不过,这个容器的 webapps 目录,就必须声明一个 hostPath 类型的 Volume,从而把宿主机上的 WAR 包挂载进 Tomcat 容器当中运行起来。不过,这样你就必须要解决一个问题,即:如何让每一台宿主机,都预先准备好这个存储有 WAR 包的目录呢?这样来看,你只能独立维护一套分布式存储系统了。

    实际上,有了 Pod 之后,这样的问题就很容易解决了。我们可以把 WAR 包和 Tomcat 分别做成镜像,然后把它们作为一个 Pod 里的两个容器“组合”在一起。这个 Pod 的配置文件如下所示:

    apiVersion: v1
    kind: Pod
    metadata:
      name: javaweb-2
    spec:
      initContainers:
      - image: geektime/sample:v2
        name: war
        command: ["cp", "/sample.war", "/app"]
        volumeMounts:
        - mountPath: /app
          name: app-volume
      containers:
      - image: geektime/tomcat:7.0
        name: tomcat
        command: ["sh","-c","/root/apache-tomcat-7.0.42-v2/bin/start.sh"]
        volumeMounts:
        - mountPath: /root/apache-tomcat-7.0.42-v2/webapps
          name: app-volume
        ports:
        - containerPort: 8080
          hostPort: 8001 
      volumes:
      - name: app-volume
        emptyDir: {}
    

    在这个 Pod 中,我们定义了两个容器,第一个容器使用的镜像是 geektime/sample:v2,这个镜像里只有一个 WAR 包(sample.war)放在根目录下。而第二个容器则使用的是一个标准的 Tomcat 镜像。

    不过,你可能已经注意到,WAR 包容器的类型不再是一个普通容器,而是一个 Init Container 类型的容器。

    在 Pod 中,所有 Init Container 定义的容器,都会比 spec.containers 定义的用户容器先启动。并且,Init Container 容器会按顺序逐一启动,而直到它们都启动并且退出了,用户容器才会启动。

    所以,这个 Init Container 类型的 WAR 包容器启动后,我执行了一句 "cp /sample.war /app",把应用的 WAR 包拷贝到 /app 目录下,然后退出。

    而后这个 /app 目录,就挂载了一个名叫 app-volume 的 Volume。

    接下来就很关键了。Tomcat 容器,同样声明了挂载 app-volume 到自己的 webapps 目录下。

    所以,等 Tomcat 容器启动时,它的 webapps 目录下就一定会存在 sample.war 文件:这个文件正是 WAR 包容器启动时拷贝到这个 Volume 里面的,而这个 Volume 是被这两个容器共享的。

    像这样,我们就用一种“组合”方式,解决了 WAR 包与 Tomcat 容器之间耦合关系的问题。

    实际上,这个所谓的“组合”操作,正是容器设计模式里最常用的一种模式,它的名字叫:sidecar。

    顾名思义,sidecar 指的就是我们可以在一个 Pod 中,启动一个辅助容器,来完成一些独立于主进程(主容器)之外的工作。

    比如,在我们的这个应用 Pod 中,Tomcat 容器是我们要使用的主容器,而 WAR 包容器的存在,只是为了给它提供一个 WAR 包而已。所以,我们用 Init Container 的方式优先运行 WAR 包容器,扮演了一个 sidecar 的角色。

    第二个例子,则是容器的日志收集。

    比如,我现在有一个应用,需要不断地把日志文件输出到容器的 /var/log 目录中。

    这时,我就可以把一个 Pod 里的 Volume 挂载到应用容器的 /var/log 目录上。

    然后,我在这个 Pod 里同时运行一个 sidecar 容器,它也声明挂载同一个 Volume 到自己的 /var/log 目录上。

    这样,接下来 sidecar 容器就只需要做一件事儿,那就是不断地从自己的 /var/log 目录里读取日志文件,转发到 MongoDB 或者 Elasticsearch 中存储起来。这样,一个最基本的日志收集工作就完成了。

    跟第一个例子一样,这个例子中的 sidecar 的主要工作也是使用共享的 Volume 来完成对文件的操作。

    但不要忘记,Pod 的另一个重要特性是,它的所有容器都共享同一个 Network Namespace。这就使得很多与 Pod 网络相关的配置和管理,也都可以交给 sidecar 完成,而完全无须干涉用户容器。这里最典型的例子莫过于 Istio 这个微服务治理项目了。

    Istio 项目使用 sidecar 容器完成微服务治理的原理,我在后面很快会讲解到。

    备注:Kubernetes 社区曾经把“容器设计模式”这个理论,整理成了一篇小论文,你可以点击链接浏览。

  • 相关阅读:
    Python 爬虫js加密破解(一) 爬取今日头条as cp 算法 解密
    Python 爬虫实例(2)—— 爬取今日头条
    Python 爬虫实例(1)—— 爬取百度图片
    python 操作redis之——HyperLogLog (八)
    python 操作redis之——有序集合(sorted set) (七)
    Python操作redis系列之 列表(list) (五)
    Python操作redis系列以 哈希(Hash)命令详解(四)
    Python操作redis字符串(String)详解 (三)
    How to Install MySQL on CentOS 7
    Linux SSH远程文件/目录 传输
  • 原文地址:https://www.cnblogs.com/plf-Jack/p/11300009.html
Copyright © 2011-2022 走看看