large-scale cluster mangement & kubernetes under the hood
TRANSCRIPT
容器集群编排与Kubernetes原理剖析LARGE-SCALE CONTAINER CLUSTER MANAGEMENT & DEEP DIVE INTO KUBERNETES
About Me• @resouer • Lei (Harry) Zhang• Phd & Researcher @ZJU• Contributor• Former: Baidu, VMware• Publish: – Docker & Kubernetes Under The Hood
Contents
• 深入理解容器(以 runc实现为例)• Borg的大规模集群管理经验• Kubernetes的原理与实现
深入理解容器第一部分
注:除非特殊说明,“容器”均指代 OpenContainer组织的 runc实现
进程 101
• 静态视角–程序:代码和数据的集合• 表现为:磁盘上的可执行镜像( Executable
Image)• 动态视角–进程:程序运行起来的执行环境的总和• 表现为:指令与数据、寄存器值、堆栈、被打开文件数、 I/O设备状态等信息的动态变化
容器 101• 静态视角
– 镜像:程序、数据以及它们所依赖的所有文件、目录和配置的集合– 表现为: rootfs
• 文件系统结构和 library• 目录: /dev /proc /bin /etc /lib /usr /tmp• 指令: sh ls cp mv etc...• 配置: rc inittab fstab etc...• 设备: /dev/hd* /dev/tty* /dev/fd0 etc...
– 为了避免每个进程都需要 build一个 rootfs:“层”和 unionfs• 动态视角
– 容器:程序运行起来的执行环境的视图和边界– 表现为:?
容器 201 : docker run
Docker Daemon
Container•镜像 ID•网络配置•Volumes•其他 docker run参数
Command•Resources : cgroup配置•InitPath : dockerinit的位置•Process:
• Entrypoint + Args • e.g. /bin/sh –c echo hello
•Rootfs•其他容器参数,比如网络,Mounts等
生成
Libcontainer(runc)
linuxContainer•dockerinit•Config
• Cgroups • Namespaces
• 默认 NS• 用户指定的 NS
• Mounts• 其他 Command中的容器相关参数
Process•Entrypoint + Args•Env•User•Work dir
$ docker run ubuntu “echo hello”
Docker Daemon
容器 201 : docker run
libcontainer
initProcess (父进程)•Pipes : childPipe, parentPipe•Cmd
• dockerinit• childPipe• Cloneflags
•Cgroups•Config
• Process (Entrypoint + Args)• LinuxContainer.Conifg
LinuxContainer.start(Process)
linuxContainer•dockerinit•Config
Process•Entrypoint •Args
initProcess
容器dockerinit
Cmd.start
1
2
将 dockerinit的 PID加入
Cgroups
创建容器内网络设备: lo ,veth
发送 Config给容器
准备 pipe 按照容器 config初始化内容和 rootfs
Pipes
EntrypointInit()
执行用户指令
Pipes
close pipe
execv
所以,容器的动态表现是• 一个自带“世界观”的进程
– namespaces:视图– pivot_root 和 cgroups:边界– 三层文件系统: RW layer , init-layer , read-only layers
FROM ubuntu:latest
VOLUME /data
Copy-On-Write & /data
“echo hello”namespace cgroups或者 hyper , novm
read-only layer
/bin /dev /etc /home /lib /lib64 /media /mnt /opt /proc /root /run /sbin /sys /tmp /usr /var /data
/etc/hosts /etc/hostname /etc/resolv.confinit-layer
read-write layer
/var/lib/docker/
volumes/<volume_id>/_data
container/<container_id>/hosts container/<container_id>/resolv.confcontainer/<container_id>/hostname
aufs/mnt
从 BORG 谈容器集群编排第二部分
Borg : http://research.google.com/pubs/pub43438.html
从单机到集群• 多任务单机操作系统• 进程隔离
– 每个进程有自己的虚拟地址空间– 进程间使用 IPC通信
• 单机资源共享– 资源对象
• CPU• 物理内存• 文件• 外部设备
– 并发:进程上下文切换• 兼顾公平与高效的调度
• 多容器集群数据中心• 进程 +环境隔离
– 环境隔离与资源边界: namespaces和 cgroups
– 容器间通信:网络方案• 集群资源共享
– 资源对象:机器(或资源包)– 在机器内部才考虑单机资源共享
• CPU,物理内存,文件,外部设备,并发,公平调度• 兼顾公平、高效、利用率、能耗、访问延时的调度• 机器规模是关键
容器集群管理中的问题1. 任务分类与优先级?2. 提升利用率?3. 允许抢占的调度?4. 如何评估调度算法?5. 调度策略优化?6. 是否混部的权衡?
问题一:容器的分类• LRS ( Long Running Service)–“死循环”,比如一个Web服务–需要立刻响应用户的请求,时延敏感–高优先级或生产环境级( prod)
• batch jobs– Map-Reduce,或者其它类似的计算任务• 它们的执行往往需要持续一段时间,但是最终都会停止
–对服务器瞬时的性能波动不敏感– 低优先级( non-prod )
容器分类举例• 监控级、生产级、批任务级、尽力级(也叫测试级)– 监控级和生产级: prod任务
• prod任务可以抢占非 prod任务的资源• prod任务不能互相抢占–避免触发连锁反应( A抢占 B , B抢占 C , C再抢占 D)
问题二:提升利用率by LeanCloud 陈伟, Docker Meetup
资源边界• cgroups 只是资源边界– 只有在资源申请试图超出限额时才发挥作用–所以一台机器理应运行更多的容器
• 但是,我们必须有合理的判断依据来决定“这台机器尚能运行多少个新容器”– 硬指标: 4G机器, 1G的任务,还剩 3G– 超卖: 4G的机器, 1G的任务,还剩 4G*2-
1G=7G–有没有更好的做法?
宿主机: 10G可用: 6G
动态资源边界4G
容器 A
资源回收事件
宿主机: 10G可用: 9G
回收资源: 3G
1G
容器 A
• 宿主机默认按照动态边界计算自己的可用资源• 如果容器资源使用超出 1G:迅速恢复资源边界到初始值(比如 4G)• prod级别的容器永远只使用现有容器的原始边界计算可用资源,而非动态边界• 效果:将近一半的集群减少了 30%的机器
资源回收事件• 容器启动成功并运行 300s 后– 减少该容器资源边界
• 机器某种资源的可用值不足(比如流量激增)–可压缩资源,比如 CPU 周期和磁盘 I/O带宽
• 热回收,调节 LRS容器的容器边界– 如若达不到目的,触发重调度事件,移除部分容器
– 不可压缩资源,比如内存和磁盘空间• 只能按优先级从低到高杀死容器,直到恢复合理的可用值
安全余量• 安全边界 = 实际资源使用量 + 可配置的安全余量• 方法:以集群为单位,通过实验获得该集群的合理余量
– 给定不同的安全余量值– 观察不同余量下资源 margin 和 OOM 次数在一段时间内的的变化– 选取平衡点的余量值 资源margin
问题三:允许抢占的调度• 第一,可行性检查
– Node是否满足容器资源需求• 可用资源 = 机器容量 – 所有容器的边界 + 可以抢占的容器的资源
– 是否满足自定义约束(比如指定的磁盘类型)– 得到候选机器列表
• 第二,打分– 尽量避免发生资源抢占;如果避免不了,则让被抢占的容器数量最少、优先级最低– 挑选容器依赖更完备的机器(比如 package 完备,已经 pull 过所需的镜像)– 使容器尽量分布在不同的高可用域当中– 尽量混合部署不同优先级的容器
• 流量峰值突然出现后便于回收资源
问题四:如何评估调度算法• 压缩实验– 不断减少集群中机器的数量(“压缩”),然后重调度某个指定的容器到该集群,直到它再也不能正常调度运行在这个集群上
• 结果: 最小工作集群• 调度算法越优秀,对于同一个容器来说它最后得到的最小集群单元中的机器数就越少
问题五:调度策略优化• 缓存机器的打分结果
– 只有当机器本身信息或者容器发生了变化(比如容器数目或者容器边界)时,才更新缓存的机器分数– 忽略那些不太明显的变化,减少缓存的更新次数
• 划分容器组(等价类)– 为一组需求和约束都一样的、亲密关系的容器执行调度过程,而不是单个容器
• 随机选择一组机器来做可行性检查,而不是整个集群– 随机挑选一个机器来检查可行性,判断是否通过,再挑选下一个,直到通过筛选的机器达到预设的数目,返回之作为候选机器列表– 参见 Sparrow调度器
• 效果:极端情况可能耗时几天的任务 -> 几百秒
问题六:是否混部的权衡• LRS 和 batch job分开部署:–需要额外增加 20%-30%的机器
• 不同用户的容器分开部署:–需要额外增加 20%-150%的机器
• 共享带来的 CPU 性能损失: 3%– CPI ( cycles per instruction)–每增加一个容器,其他任务的 CPI 增加 0.3%
• 权衡:减少的机器数量 VS CPU 性能损失
KUBERNETES 原理与实现第三部分
Kubernetes
1. k8s 是 Borg的开源实现吗?他俩什么关系?2. K8s的架构?3. 怎么部署 Kubernetes?4. Pod的作用?5. Pod的调度策略?6. 如何创建一个 Pod?7. Pod如何被访问到?8. k8s提供跨主机网络不?9. k8s 跟 XXX到底啥区别?
Kubernetes 核心结构
部署 1 : k8s in dockerMaster Worker
docker daemon docker daemon
docker-bootstrap daemon--iptables=false
docker-bootstrap daemon--iptables=false
flanneld etcd flanneld
kubelet kube-proxy kubelet kube-
proxy
master
2
1
• 优点:快速,无依赖, self-deploy,操作系统无关,适合开发环境或小型集群• 缺点:不方便与现有系统集成,需要登录到每台机器上执行脚本• 见: <k8s>/getting-started-guides/docker-multinode.md
mastermaster
部署 2 : ubuntu cluster1. 编译出 kubernetes , etcd, flanneld 二进制文件2. 规划集群:
3. 部署:
• 优点:一键部署,适合一定规模的集群,符合 k8s e2e test规范,便于同现有运维系统集成• 缺点:只支持 Ubuntu,暂不支持 systemd• 见: <k8s>/docs/getting-started-guides/ubuntu.md
IP Address Role
10.10.103.223 node
10.10.103.162 node
10.10.103.250 both master and node
export [email protected] [email protected] [email protected] role="ai i i“export NUM_MINIONS=${NUM_MINIONS:-3}export SERVICE_CLUSTER_IP_RANGE=192.168.3.0/24export FLANNEL_NET=172.16.0.0/16 KUBERNETES_PROVIDER=ubuntu ./kube-up.sh
Why Pod
• Pod– IP 、 port等网络资源的分配的基本单位– 共享存储卷 volume– 共享 namespace
• pid, network, ipc, uts
• 两个典型场景:– Kubernetes 的 Master组件
• ./apiserver --address=0.0.0.0• ./scheduler --master=127.0.0.1:8080• ./controller-manager --master=127.0.0.1:8080
– 一个应用容器和它的日志转发器• Web app• log aggregator
apiserver
scheduler controller-manager
But wait …• Pod并不是一组容器的简单集合或者集群• Pod 是 Kubernetes 世界观里的“容器”
– Lessons learned from Borg– 成规模场景下的任务组织形式
• Pod :进程组• 容器 :进程
– 超亲密关系– 地位对等– 共同决定 Pod状态
containers: - name: container A - name: container BrestartPolicy: - string: {}volumes: - name: string source: emptyDir | HostDir emptyDir: {} hostDir: path: string
如何创建 Pod?
V0.16.2
Scheduler默认策略• 完全插件化• Predicates:筛选出合格的 Node
– 容器申请的主机端口是否可用– 可用 CPU和内存是否满足 Pod 里的需求
• 不支持抢占机制,不关注利用率– host volume目录是否冲突– 是否匹配用户指定的 Label– 是不是指定的 hostname
• Priorities:对通过上述筛选的 Node打分– 选择资源空闲更多的机器– 选择调度完成后 CPU和内存利用率更平衡的机器– 属于同一个任务的副本 Pod 尽量分布在不同机器上
V0.16.2
Scheduler如何采集信息?• 向 API Server请求信息( 10s),有 client 缓存机制• 两种数据更新方法
– Reflector:监测( watch)对象变化– Poller:抓取不支持 etcd watch操作的 api对象数据描述 采集方法 数据对象 client 缓存类型
所有未调度的 pod Reflector podQueue queue所有已调度运行的pod
Reflector podLister cache
所有可用的minion Poller MinionLister cache所有 service对象 Reflector ServiceLister cache
V0.16.2
SyncPods
pods
pod nodeName
pod nodeName
新版 kubelet
scheduler APIServer
POST /binding
pod node list
RunKubeletPods with pod.host == nodeName
watch (三种源)
startKubelet
Pods
statusManager
Pod.statusPod
SyncPods
ContainerManager,Cadvisor
…
managePodLoop
managePodLoop
managePodLoopWorker
updatePod
Pods合法性检查
Pod
Pod
V1.0
SyncPod : Pod vs 现有 Pod
1.创建 Pod数据目录2.创建 API实体3.mount Volume4.更新 status
DockerManager1.遍历 Pod中的Containers2.pull镜像3.生成 Docker参数4.启动容器
期望状态RC: replica: 2 selector: ‘label’ : ‘test’ // pod definition …
如何控制 Pod?• Controller Manager– Replication Controller (RC)– 检测循环( 10s):
期望状态RC: replica: 2 selector: ‘label’ : ‘test’ // pod definition …
Diff
•期望的 replica值•查询到的 Pod数量APIserver
etcd1. label 匹配2. 选择状态不为
podFailed 的 Pods
create/delete Pod
fetch with Reflector cache
如何访问 Pod?• Service:– Portal,提供访问 Pod的固定入口( portal_ip : port)– 多个副本 Pod的动态 Load Balancer– 适合强依赖于 IP的业务 NAME SELECTOR IP PORT
…service-nginx name=nginx 11.1.1.88 800111.1.1.88:8001
Label: name=nginx
Pod A-1
Label: name=nginx
Pod A-2
Label: name=nginx
Pod A-3
Endpoint
Node
Service
Node
// 请求发起自容器: PREROUTINGtarget prot opt source destination …REDIRECT tcp -- 0.0.0.0/0 11.1.1.88 tcp dpt:8001 redir ports 43318// 请求发起在 HOST : OUTPUTtarget prot opt source destination …DNAT tcp -- 0.0.0.0/0 11.1.1.88 tcp dpt:8001 to:10.10.103.58:43318
11.1.1.88:8001
kube-proxy
Proxier43318
LoadBalancer
Pod地址 : 端口
根据 etcd 中 Service的增删创建并维护规则
Request
etcd
1. Service变化2. Endpoint变化
Pod地址 : 端口bi-directionally io.Copy
Round-RobinSession Affinity
Service• portalIP 仅在集群范围内的机器上有效
– publicIP(使用机器的 public IP 做 portalIP)– 外部 LoadBalancer
• 大规模 &高并发场景下的担忧– 可能的瓶颈:
• userspace 的 Proxier ( 已经 Fixed,需要 iptables 1.4.0)• iptables 更新耗时
– 可选替换: cloudfoundry/gorouter , flynn/flynn/router , HAproxy• 监视: <master:port>/api/v1beta3/watch/pods,更新 Server Pool• 缺点:
– 不能有效提供 portal_ip– 需要域名依赖或者端口管理 Host A
pod_1
Host B
pod_1pod_2
pod_1.mydomain.com
LB
如何创建 Service?
V0.16.2
如何控制 Service?• Controller Manager– Service Controller:使用真实状态更新 Service– 检测循环:• 遍历所有 Service
– 根据 label,查询出 Pod 列表(真实状态)– 该 Service 持有的 Endpoint 列表(期望状态)– Diff– 有差异:删除多余的 Endpoint或者检查实际状态列表的资源版本号
等于 0,真实 Pod并没有出现在 Endpoint 列表中,创建该Endpoint
不等于 0,更新该 Endpoint
Pod网络:单 Pod 单 IP模型
pause
Container A Container B
--net=container:pause
/proc/{pid}/ns/net -> net:[4026532483]
• 网络容器( infra容器)– 使用 docker0网桥作为默认网关,并且被分配该网桥上的一个 IP地址作为所有容器共享的 IP地址
Kubernetes的网络假设1. 容器之间可以跨主机通信,无需经过 NAT2. 所有主机与容器之间可以直接通信,无需经过
NAT3. 容器本身看到的自己的 IP地址与其他容器看它的 IP地址是一样的但是, Kubernetes 本身对上述假设不做任何实现e.g. Flannel SocketPlane Calico OVS macvlan ipvlan ...
典型的网络实现• 两条基本原则
– 绝大部分 Docker 跨主网络方案均可以满足上述“网络假设”– 可以随意更改 Daemon的配置、启动参数,但是 Kubernetes目前必须使用标准 Docker API
• docker run … √• my_tool run … ×
• API 不一致,使用 Powerstrip进行转义 : https://github.com/ClusterHQ/powerstrip
kubelet
Powerstrip
docker run
my_tool run
抛砖引玉• Flannel
– overlay网络, UDP 封装或者 VxLAN封装– 用 Docker 启动 flanneld 后配置 DOCKER_OPTS 的 --bip 即可– 简单,稳定,但没有划分多个隔离域的能力
• Weave– 通过 ambassador容器抓取链路层流量,使用 UDP 转发该报文到目的端
• SocketPlane– OVS的功能性封装,使用 GRE/VxLAN隧道– 使用 Powerstrip– Hack:修改 kubelet,在启动 infra容器时添加” SP_NETWORK=xxx”环境变量
Qperf test Flannel UDP
Flannel VxLAN Weave Socketplane Native
TCP bandwidth 28.7 MB/s 47.8 MB/s 3.75 MB/s 42.8 MB/s 62.5 MB/s
TCP latency 200 µs 127 µs 384.4 µs 110.9 µs 96.1µs
Kubernetes VS 小伙伴们• 祖师: Borg
– 专注于支撑成规模的企业服务并且最大程度地提高资源利用率• Kubernetes
– 典型的容器集群管理工具(类似 Swarm),但基本单位是 Pod
– 暂时不关注资源抢占和任务混部,以及资源利用率提升– K8S大量借鉴了 Borg的优秀思想、架构、甚至代码实现,但并不能说就是 Borg的开源实现– Goal :” run your container workload at scale”
Kubernetes VS 小伙伴们• Mesos
– 专注于 Scheduling和资源抽象,需要同上层框架协作– 良好的 framework 支持,适合作为基础依赖– 适合大数据场景,非原生针对容器集群管理设计– Goal: “I already have a Layer 1 (business or platform layer), but I need Layer 0
(infrastructure layer) to run it on cluster“
• Flynn Deis Marathon + Mesos Cloud Foundry OpenShift – 更类似 PaaS– “从代码,到可运行制品,再到运行实体”– 低用户自由度,高自动化程度
• Compose + Swarm– 最 Docker友好的容器集群管理工具– 最原生的 Docker 支持 = vendor lock in– 缺乏成规模集群管理的 vision
Further Contact
• www.sel.zju.edu.cn• harryzhang AT zju.edu.cn• @柳烟堆雪