前言
学习 Docker 时,我们学会了把应用程序封装成镜像,然后基于这个镜像启动容器来部署我们的服务,接着通过端口映射就可以访问我们的服务了。既然单个容器可以部署我们的应用,为什么 Kubernetes 还要额外引入 Pod 概念?直接调度容器不是更简单吗?
容器的单进程模型
容器的本质是进程,是一个被 namespace、cgroup、rootfs 隔离和限制的普通用户进程。同时,容器的设计理念建议每个容器只运行一个主进程,这就是容器的“单进程模型”。
容器为什么受限于单进程模型?
- 相较于虚拟机,容器的开销极小,单进程模型让容器专注于单一任务,简化了监控和管理
- 每个进程在单独的容器中运行,确保了资源使用的隔离性,一个进程失控不会影响其它容器进程
- 因为容器功能单一,开发调试也更方便
除了以上原因,还有一点。通常,容器中的1号进程就是我们的应用进程,它不像 Linux 的 systemd 根进程那样具备监控和管理多个进程的能力。举个例子,你用容器部署了一个 Tomcat,容器的1号进程是 Tomcat,随后你又在容器里擅自启动了一个 Nginx,当这个 Nginx 挂了,你的 Tomcat 如何能感知到呢?如何释放回收进程对应的资源呢?Nginx 挂了以后,容器的状态是正常还是异常呢?这些都是问题。相反,单进程模型的容器维护就简单很多,容器的状态只跟1号进程的状态相关。
容器和容器组
因为容器受限于“单进程模型”,所以不建议在单个容器中启动多个应用进程来工作,而是考虑启动多个容器。这些容器彼此协同工作,一起来完成某项任务,就像 Linux 中的进程和进程组一样。
举个例子,你有三个容器:
- Tomcat容器:部署 Web 服务,运行中产生日志
- 日志容器:收集 Web 服务产生的日志并上报
- 监控容器:收集 Web 服务状态指标,并上报监控中心
要想正常工作,日志容器就必须能读取 Tomcat 容器的日志文件,监控容器必须能基于 Socket 通信采集 Tomcat 容器的状态指标。我们发现,这三个容器具备某种“超亲密”关系,它们之间会发生直接或间接的基于磁盘或 Socket 频繁的数据交换行为,这三个容器必须“成组”调度,否则这些“数据交换”处理起来会很麻烦。
“成组”调度,意味着这三个容器必须部署在同一台机器上,这样就方便容器间共享磁盘挂载和网络通信。
如果没有容器组的概念,每个容器单独调度,想实现容器成组调度就非常困难。可能一组容器调度到一半,节点的资源不够了,kubernetes 不得不回退操作,把已经部署好的容器退出,再迁移到其它节点。
相反,有了容器组,成组调度就非常简单了,kubernetes 在部署这一组容器时,压根就不会考虑资源不够的节点,也就不存在中途回退的情况了。
Pod 其实就对应了“容器组”的概念。
Pod 概念
在 Kubernetes 中,Pod 是最小的可部署计算单元。它可以包含一个或多个容器,这些容器共享存储和网络。共享存储方便基于存储卷交互数据,共享网络意味着容器之间均可以通过 localhost 互相通信。
Pod 是一个逻辑概念,kubernetes 底层操作的还是容器本身。
Pod 具备以下特点:
- 共享存储:Pod 可以定义存储卷,Pod 内所有容器都可使用
- 共享网络:Pod 内所有容器均可以通过 localhost 互相通信
- 最小调度单元:Pod 是 kubernetes 最小调度单元,调度器会根据所有容器请求的资源分配到合适的节点
- 多容器模式:容器虽然受限于单进程模型,但是 Pod 可以包含多容器,这些容器基于共享存储和网络可以紧密协作一起完成某项任务
- 独立 IP:每个 Pod 会被分配一个独立的 IP 地址,kubernetes 集群内的 Pod 彼此之间可以跨节点相互通信
尾巴
kubernetes 为啥要在容器的基础上又引入 Pod 概念?因为容器受限于单进程模型,一般一个容器只运行一个主应用进程,而一个复杂任务往往又需要多个进程协同工作,也就意味着要部署多个容器,这些容器会发生频繁的基于存储卷或 Socket 的数据交换行为,所以最好把这些容器调度到同一个节点上。理论上,这些容器形成了具备“超亲密”关系的容器组概念,为了方便这些容器成组调度,kubernetes 引入了 Pod 概念,Pod 是 kubernetes 调度的最小单位,Pod 里面包含一组容器,这些容器共享存储和网络,方便彼此通信。