【公开课】理解 Pod 和容器设计模式
内容介绍:
一、为什么我们需要Pod
二、Pod的实现机制
三、详解容器设计模式
四、总结
一、为什么我们需要Pod
Pod是Kubernetes项目中一个重要的概念,是一个非常最要的原子
1.容器的基本概念
容器的本质:
一个视图被隔离、资源受限的进程
容器里PID =1的进程就是应用本身
管理虚拟机=管理基础设施;管理容器=直接管理应用本身
是不可变基础设施最佳的体现
那么Kubernetes呢?
Kubernetes 就是云时代的操作系统!
以此类推,容器镜像其实就是:这个操作系统的软件安装包
2.真实操作系统案例
举例: helloworld程序
helloworld程序实际上是由一组进程(Linux里的线程)组成
$ pstree -p
...
|helloworld,3062l
| |-{api},3063
| |-{main} ,3064l
| |-{log} ,3065
| 、-{ compute} ,3133
进程等同于Linux 里的线程
在Linux 里查看helloworld里面的pstree
有四个线程组成,api、main、log、compute
这4个进程共享helloworld程序的资源,相互协作,完成 helloworld程序的工作
思考
Kubernetes =操作系统(比如:Linux)
容器=进程(Linux线程)
Pod = ?
进程组(Linux线程组)
在真实的操作系统中,一个程序往往是由进程组进行管理
将Kubernetes 类比为操作系统,如Linux,也可以类比为进程,容器也可以类比为进程,也就是Linux线程,而Pod即为进程组,也就是Linux线程组这样一个类比关系
3.进程组
举例:
helloworld程序由4个进程组成,这些进程之间共享某些文件
问题: helloworld程序如何用容器跑起来呢?
解法一:启动一个Docker 容器中,启动这4个进程
疑问∶容器PID= 1的进程就是应用本身比如main进程,那么“谁"来负责管理剩余的3个进程?
核心问题:容器是“单进程"模型!
由于容器等于应用等于进程,只能管理PID=1的进程
除非:
应用进程本身具备"进程管理"能力〈这意味着: helloworld程序需要具备systemd的能力)
或者,容器的PID=1进程改成systemd,或者在容器运行systemd
这会导致:管理容器=管理systemd !=直接管理应用本身
由于容器实际是一个单进程模型,如果在容器里启动多个进程,只有其中一个可以作为PID=1的进程,如果PID=1的进程失败退出了,其他三个进程无法管理和回收资源
如果在容器里运行一个systemd,用systemd来管理其他进程,会出现没办法直接管理应用,应用被systemd接管,应用状态的生命周期不等于容器的生命周期
4.Pod=“进程组”
$ pstree -p
...
|helloworld,3062l
| |-{api},3063
| |-{main} ,3064l
| |-{log} ,3065
| 、-{ compute} ,3133
apiVersion: v1
kind: Pod
metadata:
name: helloworld
spec: .
containers:
- name: api
image: api
ports:
- containerPort: 80
- name: main
image: main
- name: log
image: log
volumeMounts:
- name: log-storage
name: compute
image: compute
volumeMounts:
- name: data-storage
在Kubernetes里面,Pod实际上是Kubernetes项目抽象出来的一个可以类比为进程组的概念。由四个进程共同组织的一个应用helloworld,在Kubernetes里面实际上会被定义为一个拥有四个容器的Pod。有四个进程,有四个职责不同、相互协作的进程要需要放在容器里运行。
在Kubernetes里,并不会将他们一起放入一个容器里,在Kubernetes中,把四个独立的进程分别用四个独立的容器启动起来,然后定义在一个Pod中。当Kubernetes把helloworle运行起来时,实际上会看到四个容器。他们共享某些资源,这些资源都属于Pod。Pod在Kubernetes中只是一个逻辑单位,没有一个真实的东西对应这个Pod。物理上存在的是四个容器,这四个容器的组合成为Pod,Pod是Kubernetes分配资源的一个单位。因为这些容器它要共享资源,所以Pod是多个容器的组合Kubernetes的原子调度单位
Pod:一个逻辑单位,多个容器的组合Kubernetes的原子调度单位
5.来自Google Borg的思考
Google的工程师们发现,在 Borg 项目部署的应用,往往都存在着类似于“进程和进程组”的关系。更具体地说,就是这些应用之间有着密切的协作关系,使得它们必须部署在同一台机器上并且共享某些信息
是进程组的概念,也是Pod的用法
- Large-scale cluster management at Google with Borg, EuroSys'15
6.为什么Pod必须是原子调度单位
举例:两个容器紧密协作
App:业务容器,写日志文件
LogCollector:转发日志文件到ElasticSearch 中
内存要求:
App: 1G
LogCollector: 0.5G
当前可用内存:
Node_A: 1.25G
Node_B: 2G
如果App 先被调度到了Node_A上,会怎么样?
LogCollector无法调度到了Node_A上,因为资源不够
整个应用本身出现问题
Task co-scheduling 问题
Mesos:资源囤积(resource hoarding) :
所有设置了Affinity约束的任务都达到时,才开始统一进行调度
调度效率损失和死锁
Google Omega:乐观调度处理冲突:
先不管这些冲突,而是通过精心设计回轨机制在出现了冲突之后解决问题
复杂
Kubernetes: Pod
7.再次理解Pod
亲密关系-调度解决
两个应用需要运行在同一台宿主机上
超亲密关系-Pod解决
会发生直接的文件交换
使用localhost或者Socket文件进行本地通信
会发生非常频繁的RPC调用
会共享某些Linux Namespace (比如,一个容器要加入另一个容器的Network Namespace)
二、Pod的实现机制
1.Pod 要解决的问题
如何让一个Pod里的多个容器之间最高效的共享某些资源和数据?
容器之间原本是被Linux Namespace和cgroups隔离开的
2.共享网络
容器A和B
通过Infra Container 的方式共享同一个Network Namespace:
镜像: k8s.gcr.io/pause;汇编语言编写的、永远处于“暂停";大小100~200 KB
直接使用localhost进行通信
看到的网络设备跟Infra容器看到的完全一样
一个Pod只有一个IP地址,也就是这个Pod的NetworkNamespace对应的IP地址
所有网络资源,都是一个Pod一份,并且被该Pod中的所有容器共享,整个Pod的生命周期跟Infra容器一致,而与容器A和B无关
Pod不会重建,也不会重启
3.共享存储
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-datalindex.html" ]
shared-data 对应在宿主机上的目录会被同时绑定挂载进了上述两个容器当中
三、详解容器设计模式
举例:WAR包+ Tomcat的容器化
方法一:把WAR包和Tomcat打包进一个镜像
无论是WAR包和Tomcat更新都需要重新制作镜像
方法二︰镜像里只打包Tomcat。使用数据卷(hostPath)从宿主机上将WAR包挂载进Tomcat容器
需要维护―套分布式存储系统,容器可迁移,不固定
容器本身要依赖一套持久化的存储
有没有更通用的方法?
1.InitContainer
Init Container会比spec.containers定义的用户容器先启动,并且严格按照定义顺序依次执行
/app是一个Volume
一个Pod里的多个容器可以共享Volume
Tomcat容器,同样声明了挂载该Volume 到自己的webapps目录下
故当Tomcat容器启动时,它的webapps目录下就一定会存在sample.war文件
apiversion : v1
kind: Pod
metadata :
name: javaweb-2
spec:
initcontainers:
- image: resouer/ sample:v2
name: war
command: [ "cp" , " l sample.war" , " l app" ]
volumeMounts :
- mountPath: l app
name: app-volume
containers:
- image: resouer/tomcat : 7 .0
name: tomcat
command:[ "sh","-c","l root/apache-tomcat-7.0.42-v2/bin/ start.sh" ]
volumeMounts:
- mountPath: lroot/ apache-tomcat-7.0.42-v2/webapps
name: app-volume
ports :
- containerPort: 8080
hostPort: 8001
volumes :
- name: app-volume
emptyDir: {}
2.容器设计模式: Sidecar
通过在Pod里定义专门容器,来执行主业务容器需要的辅助工作
比如︰
原本需要SSH进去执行的脚本
日志收集(本身是一个进程,可以打包到Pod中做数据工作)
Debug应用
应用监控
优势︰
将辅助功能同主业务容器解耦.实现独立发布和能力重用
3.Sidecar:应用与日志收集
业务容器将日志写在Volume 里
Volume 在Pod中共享
日志容器共享该Volume 从而将日志转发到远程存储当中
Fluentd等,都是这样的工作方式
4.Sidecar:代理容器
代理容器:有一个Pod,需要访问外部系统或外部服务,但是这些外部系统或服务是一个集群状态
代理容器对业务容器屏蔽被代理的服务集群,简化业务代码的实现逻辑
提示:
容器之间通过localhost直接通信
代理容器的代码可以被全公司重用
不会降低性能
5.Sidecar:适配器容器
适配器容器将业务容器暴露出来的接口转换为另一种格式
举例:
业务容器暴露出来的监控接口是/metrics
Monitoring Adapter将其转换为/healthz 以适配新的监控系统
提示:
容器之间通过 localhost直接通信
代理容器的代码可以被全公司重用
四、总结
Pod是Kubernetes项目里实现“容器设计模式"的核心机制
“容器设计模式"是Google Borg的大规模容器集群管理最佳实践之一
也是Kubernetes进行复杂应用编排的基础依赖之一
所有“设计模式"的本质都是:解耦和重用