在 https://segmentfault.com/a/11… 容器,隔离,云的概述。这篇对其中用途广泛的 docker,k8s 做详细介绍,并给出云搭建的生态环境体系。
docker
1. 与其他 VM 等对比
容器发展,详见上面提到的文章
chroot | 1979 |
Linux Vserver | 2001 |
process container | 2006 |
LXC | 2008 |
Docker | 2013 |
windows container | 2017 |
典型图:VM 与 container 对比,差异在于 OS
VM | container | |
隔离 | OS | kernel namespace |
可配额,可度量 | 硬件页映射 | cgroups |
移动性 | snapshot,image | AUFS |
安全 | gresc patch |
缺点
隔离性相比 KVM 之类的虚拟化方案还是有些欠缺,所有 container 公用一部分的运行库
网络管理相对简单,主要是基于 namespace 隔离
cgroup 的 cpu 和 cpuset 提供的 cpu 功能相比 KVM 的等虚拟化方案相比难以度量(所以 dotcloud 主要是安内存收费)
docker 对 disk 的管理比较有限(disk quota)container 随着用户进程的停止而销毁,container 中的 log 等用户数据不便收集
优点:
轻量级的特点,其启动快,而且 docker 能够只加载每个 container 变化的部分,这样资源占用小
docker 不只是容器,高级容器引擎,应用部署平台,应用镜像商店
2.docker 生态圈————不只是容器
- Image 一个包含运行环境的只读 模板
- Container 镜像的运行态,docker 利 用容器运行应用
- Registry 存储容器镜像的仓库
- Dockerfile 包含构建镜像一系列命令 和参数的脚本
3. 执行过程
- Docker Daemon 可以认为是通过 Docker Server 模块接受 Docker Client 的请求,并在 Engine 中处理请求,然后根据请求类型,创建出指定的 Job 并运行。Docker Daemon 运行在 Docker host 上,负责创建、运行、监控容器,构建、存储镜像。
- job 运行过程的作用有以下几种可能:
向 Docker Registry 获取镜像
通过 graphdriver 执行容器镜像的本地化操作
启动容器 - graph
Graph 在 Docker 架构中扮演已下载容器镜像的保管者,以及已下载容器镜像之间关系的记录者。
一方面,Graph 存储着本地具有版本信息的文件系统镜像,另一方面也通过 GraphDB 记录着所有文件系统镜像彼此之间的关系。
GraphDB 是一个构建在 SQLite 之上的小型图数据库,实现了节点的命名以及节点之间关联关系的记录。它仅仅实现了大多数图数据库所拥有的一个小的子集,但是提供了简单的接口表示节点之间的关系。
Graph 的本地目录中,关于每一个的容器镜像,具体存储的信息有:该容器镜像的元数据,容器镜像的大小信息,以及该容器镜像所代表的具体 rootfs。 - networkdriver 执行容器网络环境的配置
networkdriver 的用途是完成 Docker 容器网络环境的配置,其中包括 Docker 启动时为 Docker 环境创建网桥;Docker 容器创建时为其创建专属虚拟网卡设备;以及为 Docker 容器分配 IP、端口并与宿主机做端口映射,设置容器防火墙策略等。 - execdriver 执行容器内部运行的执行工作
execdriver 作为 Docker 容器的执行驱动,负责创建容器运行命名空间,负责容器资源使用的统计与限制,负责容器内部进程的真正运行等。在 execdriver 的实现过程中,原先可以使用 LXC 驱动调用 LXC 的接口,来操纵容器的配置以及生命周期,而现在 execdriver 默认使用 native 驱动,不依赖于 LXC。具体体现在 Daemon 启动过程中加载的 ExecDriverflag 参数,该参数在配置文件已经被设为 ”native”。这可以认为是 Docker 在 1.2 版本上一个很大的改变,或者说 Docker 实现跨平台的一个先兆 -
libcontainer
Docker 架构中一个使用 Go 语言设计实现的库,设计初衷是希望该库可以不依靠任何依赖,直接访问内核中与容器相关的 API。
正是由于 libcontainer 的存在,Docker 可以直接调用 libcontainer,而最终操纵容器的 namespace、cgroups、apparmor、网络设备以及防火墙规则等。这一系列操作的完成都不需要依赖 LXC 或者其他包。
libcontainer 提供了一整套标准的接口来满足上层对容器管理的需求。docker run 过程 (1) Docker Client 接受 docker run 命令,解析完请求以及收集完请求参数之后,发送一个 HTTP 请求给 Docker Server,HTTP 请求方法为 POST,请求 URL 为 /containers/create? +xxx; (2) Docker Server 接受以上 HTTP 请求,并交给 mux.Router,mux.Router 通过 URL 以及请求方法来确定执行该请求的具体 handler;(3) mux.Router 将请求路由分发至相应的 handler,具体为 PostContainersCreate;(4) 在 PostImageCreate 这个 handler 之中,一个名为 "create" 的 job 被创建,并开始让该 job 运行;(5) 名为 "create" 的 job 在运行过程中,执行 Container.Create 操作,该操作需要获取容器镜像来为 Docker 容器创建 rootfs,即调用 graphdriver;(6) graphdriver 从 Graph 中获取创建 Docker 容器 rootfs 所需要的所有的镜像;(7) graphdriver 将 rootfs 所有镜像,加载安装至 Docker 容器指定的文件目录下;(8) 若以上操作全部正常执行,没有返回错误或异常,则 Docker Client 收到 Docker Server 返回状态之后,发起第二次 HTTP 请求。请求方法为 "POST",请求 URL 为 "/containers/"+container_ID+"/start";(9) Docker Server 接受以上 HTTP 请求,并交给 mux.Router,mux.Router 通过 URL 以及请求方法来确定执行该请求的具体 handler;(10) mux.Router 将请求路由分发至相应的 handler,具体为 PostContainersStart;(11) 在 PostContainersStart 这个 handler 之中,名为 "start" 的 job 被创建,并开始执行;(12) 名为 "start" 的 job 执行完初步的配置工作后,开始配置与创建网络环境,调用 networkdriver;(13) networkdriver 需要为指定的 Docker 容器创建网络接口设备,并为其分配 IP,port,以及设置防火墙规则,相应的操作转交至 libcontainer 中的 netlink 包来完成;(14) netlink 完成 Docker 容器的网络环境配置与创建;(15) 返回至名为 "start" 的 job,执行完一些辅助性操作后,job 开始执行用户指令,调用 execdriver;(16) execdriver 被调用,初始化 Docker 容器内部的运行环境,如命名空间,资源控制与隔离,以及用户命令的执行,相应的操作转交至 libcontainer 来完成;(17) libcontainer 被调用,完成 Docker 容器内部的运行环境初始化,并最终执行用户要求启动的命令。
4.docker 技术
隔离 命名空间
1、进程命名空间:每个命名空间独立维护自己的进程号,父子关系结构,子空间的进程对父空间是可见的,新 fork 出的进程 在父命名空间和子命名空间都会生成一个进程号
2、网络命名空间:实现网络隔离 完全独立的网络协议视图,包括网络设备接口、IPV4 IPV6 协议栈,路由表,防火墙规则,sockers 等,采用虚拟网络设备,将容器中的虚拟网卡绑定在本地主机的 docker0 的网桥上
3、ipc 命名空间 进程间交互,信号量、消息队列,和共享内存,同一个 IPC 空间的可以交互,不同的不可以
4、挂载命名空间 类似 chroot, 将一个进程放到一个特定的目录执行。允许不同命名空间的进程看到的文件结构不同,文件目录被隔离 chroot(PATH) 这个 function 必须具有 root 的身份才能执行,执行后会将根目录切换到 PATH 所指定的地方。
5、UTS 命名空间 UNIX Time-sharing System 允许每个容器拥有独立的主机名和域名,默认主机名为容器 ID
6、用户命名空间 每个容器可以有不同的用户和组 id,可以在容器内使用特定的内部用户执行程序。每个容器都有 root 账号
可配额,可度量
1.cpu,io,mem 等等:cgroups
2. 网卡
docker 创建容器的过程:
1、创建一对虚拟接口 veth pair,分别放在本地主机和新容器的命名空间中
2、本地主机一端的虚拟接口 连接到默认的 docker0 网桥上,或指定网桥,并具有一个 veth 开头的唯一名字
3、容器一端的虚拟接口,放到新的容器中,修改名字为 eth0,只在容器中可见
4、从网桥可用地址中 分配一个空闲的给 eth0,(172.17.0.2/16) 并设置默认路由网卡为 docker0 的 ip(172.17.42.1/16)
Veth pair 是一对虚拟网卡,从一张 veth 网卡发出的数据包可以直接到达它的 peer veth, 两者之间存在着虚拟链路。不同网络命名空间之间交互
Veth 网卡和常规的以太网区别仅在于 xmit 接口:将数据发送到其 peer, 触发 peer 的 Rx 过程。
3.disk/network quota
虽然 cgroup 提供 IOPS 之类的限制机制,但是从限制用户能使用的磁盘大小和网络带宽上还是非常有限的。
Disk/network 的 quota 现在有两种思路:
1). 通过 docker run - v 命令将外部存储 mount 到 container 的目录下,quota 从 Host 方向限制,在 device mapper driver 中更采用实际的 device 因此更好控制。
2). 通过使用 disk quota 来限制 AUFS 的可操作文件大小。维护一个 UID 池,每次创建 container 都从中取一个 user name,在 container 里和 Host 上用这个 username 创建用户,在 Host 上用 set quota 限制该 username 的 UID 的 disk. 网络上由于 docker 采用 veth 的方式,可以采用 tc 来控制 host 上的 veth 的设备。
移植 portable
REDHAT 实现 AFUS
的 driver(RHEL DEVIDE MAPPER)
- 功能 采用 AUFS 作为 docker 的 container 的文件系统,能够提供如下好处:
1)节省存储空间 – 多个 container 可以共享 base image 存储
2)快速部署 – 如果要部署多个 container,base image 可以避免多次拷贝
3)内存更省 – 因为多个 container 共享 base image, 以及 OS 的 disk 缓存机制,多个 container 中的进程命中缓存内容的几率大大增加
4)升级更方便 – 相比于 copy-on-write 类型的 FS,base-image 也是可以挂载为可 writeable 的,可以通过更新 base image 而一次性更新其之上的 container
5)允许在不更改 base-image 的同时修改其目录中的文件 – 所有写操作都发生在最上层的 writeable 层中,这样可以大大增加 base image 能共享的文件内容。
以上 5 条 1-3 条可以通过 copy-on-write 的 FS 实现, 4 可以利用其他的 union mount 方式实现, 5 只有 AUFS 实现的很好。这也是为什么 Docker 一开始就建立在 AUFS 之上。 - AUFS 工作过程:
1. 启动:bootfs+rootfs
bootfs (boot file system) 主要包含 bootloader 和 kernel, bootloader 主要是引导加载 kernel, 当 boot 成功后 kernel 被加载到内存中后 bootfs 就被 umount 了.
rootfs (root file system) 包含的就是典型 Linux 系统中的 /dev, /proc,/bin, /etc 等标准目录和文件。
2. 典型的 Linux 在启动后,首先将 rootfs 设置为 readonly, 进行一系列检查, 然后将其切换为 “readwrite” 供用户使用。在 Docker 中,初始化时也是将 rootfs 以 readonly 方式加载并检查,然而接下来利用 union mount 的方式将一个 readwrite 文件系统挂载在 readonly 的 rootfs 之上,并且允许再次将下层的 FS(file system) 设定为 readonly 并且向上叠加, 这样一组 readonly 和一个 writeable 的结构构成一个 container 的运行时态, 每一个 FS 被称作一个 FS 层。
3. 得益于 AUFS 的特性, 每一个对 readonly 层文件 / 目录的修改都只会存在于上层的 writeable 层中。这样由于不存在竞争, 多个 container 可以共享 readonly 的 layer。所以 docker 将 readonly 的层称作 “image” – 对于 container 而言整个 rootfs 都是 read-write 的,但事实上所有的修改都写入最上层的 writeable 层中, image 不保存用户状态,可以用于模板、重建和复制。
5. 部署平台
用户每次向 Git 服务器的 push 提交都会通知给 Jenkins(基于 Java 开发的一种持续集成工具),Jenkins 触发 build。Maven(是一个采用 Java 编写的开源项目管理工具,我们最常用的就是该工具的构建功能)构建所有的相关代码,包括 Docker 镜像。
Maven 会把完成的镜像推送到私有的 Registry 保存
最后 Jenkins 会触发 Docker Registry pull 下载镜像到宿主机本地,并自动启动应用容器。
k8s
k8s 是一个容器集群管理系统,其提供应用部署、维护、扩展机制等功能,利用 Kubernetes 能方便地管理跨机器运行容器化的应用。其架构图如下:
- Labels 是用于区分 Pod、Service、Replication Controller 的 key/value 键值对
- master 包含 Replication Controller,API Server
- Replication Controller:
会确保 Kubernetes 集群中指定的 pod 副本 (replicas) 在运行,即使在节点出错时。通过修改 Replication Controller 的副本 (replicas) 数量来水平扩展或者缩小运行的 pods,一个一个地替换 pods 来 rolling updates 服务,labels 维度的 Multiple release tracks - API Server 的主要声明,包括 Pod Registry、Controller Registry、Service Registry、Endpoint Registry、Minion Registry、Binding Registry、RESTStorage 以及 Client
Minion Registry 负责跟踪 Kubernetes 集群中有多少 Minion(Host):可以对 Minion Registry Create、Get、List、Delete
Pod Registry 负责跟踪 Kubernetes 集群中有多少 Pod 在运行,以及这些 Pod 跟 Minion 是如何的映射关系,对 Pod 进行 Create、Get、List、Update、Delete 操作。
……
Endpoints Registry 负责收集 Service 的 endpoint,比如 Name:”mysql”,Endpoints: [“10.10.1.1:1909″,”10.10.2.2:8834”],Create、Get、List、Update、Delete 以及 watch
Binding Registry 绑定的 Pod 被绑定到一个 host
Scheduler 收集和分析当前 Kubernetes 集群中所有 Minion 节点的资源 (内存、CPU) 负载情况,然后依此分发新建的 Pod 到 Kubernetes 集群中可用的节点 - Kubelet
是 Kubernetes 集群中每个 Minion 和 Master API Server 的连接点,Kubelet 运行在每个 Minion 上,是 Master API Server 和 Minion 之间的桥梁,接收 Master API Server 分配给它的 commands 和 work,与持久性键值存储 etcd、file、server 和 http 进行交互,读取配置信息。Kubelet 的主要工作是管理 Pod 和容器的生命周期,其包括 Docker Client、Root Directory、Pod Workers、Etcd Client、Cadvisor Client 以及 Health Checker 组件 - Proxy
是为了解决外部网络能够访问跨机器集群中容器提供的应用服务而设计的,从上图 3-3 可知 Proxy 服务也运行在每个 Minion 上。Proxy 提供 TCP/UDP sockets 的 proxy,每创建一种 Service,Proxy 主要从 etcd 获取 Services 和 Endpoints 的配置信息,或者也可以从 file 获取,然后根据配置信息在 Minion 上启动一个 Proxy 的进程并监听相应的服务端口,当外部请求发生时,Proxy 会根据 Load Balancer 将请求分发到后端正确的容器处理。
交互:
http://tiewei.github.io/cloud…
云
公司云架构图:
容器
- 静态容器
固定可访问 IP, 不能动态漂移,容器中无代码,扩容较快,轻量虚拟机,支持共享卷 / 块设备,日志收集与物理机一样,IP 和容器名一一对应,容器名不变。(迁移换 IP) - 有状态容器
k8s 的 statefulstes 模型,可以较好弹性伸缩,可以动态漂移,有代码,扩容很快,支持共享卷 / 块设备(带云盘),日志远程收集,容器名固定,ip 与容器名一一对应,ip 池管理。适用于对程序数据有持久化需求的服务,zk - 无状态容器
k8s 的 deployment 模型,开箱即用,可以动态漂移,急速扩容,弹性伸缩极好,只支持共享卷(ceph 这种网络分布式磁盘),日志远程收集,容器名不固定,不固定但可直接访问的 ip,ip 池管理,适用于无状态微服务。只支持云平台变更(直接的上线系统不行),不挂载云盘
数据
小的放 ceph, 大的物理机映射 => 网络抖动全换成本地磁盘(无论日志还是数据,数据需要单独迁移,两种方案都可以)
网络
docker 的虚拟网络很多种实现,原生或者插件,一般原生的 overlay 用的较多
sdn overlay 网络 稳定性差(ip 不受限制,因此漂移也不受限制)=》物理网络(ip 受限于 tor[交换机] 的网络分配,ip 只能 tor 切换,扩容后资源不均衡)
流量到 LB 后,若采取 sdn,会到 sdn 中心节点,虚拟 ip 和物理 ip 的映射,找到物理 ip 的宿主机器,宿主机器通过 overlay 与 docker 通信
若物理网络,直接到 docker,docker 的 ip 就是分配的物理 ip。漂移受 tor 的影响指的是在 ip 不变的情况下漂移。ip 变化会导致日志等问题
- 物理网络
容器 ip2 直接到物理机(ip1,ip2=》mac1。物理机再转发给 ip - docker 虚拟网络原理
1. 同 host:bridge 的 ip 模式为例
创建虚拟网卡。同一网卡之间都可以互相通信
不同网卡之间即使配了 ip 路由也会隔离,用的 iptable 的 drop
与外网:先默认连 docker0,如果网桥 docker0 收到来自 172.17.0.0/16 网段的外出包,把它交给 MASQUERADE 处理(iptable NAT)。而 MASQUERADE 的处理方式是将包的源地址替换成 host 的地址发送出去,即做了一次网络地址转换(NAT)
外网反向:ip:port(不同虚拟 ip 绑定不同端口),docker-proxy 监听所有 port, 换 ip1 发给 docker0, 与 ip1 通信
2. 跨主机 overlay
Docerk overlay 网络需要一个 key-value 数据库用于保存网络状态信息,包括 Network、Endpoint、IP 等。Consul、Etcd 和 ZooKeeper 都是 Docker 支持的 key-vlaue 软件,我们这里使用 Consul。
不同 host: 基于 VxLAN。VxLAN 可将二层数据封装到 UDP 进行传输,VxLAN 提供与 VLAN 相同的以太网二层服务,但是拥有更强的扩展性和灵活性。
外网:为每个创建了 eth1 可以连接外网。其过程和 bridge 的 docker0 访问外网一样
host 内部 br0 还是直接 endpoint 连接着,另外加了一个 vxlan 设备,与其他 host 连接 - VXLAN 原理
vxlan 在二层包上封装 vxlan header 放在 UDP 中发到 VTEP 设备上解封(header 中包含 24 位的 ID,同一 ID 的可以互相通信)
Linux vxlan 创建一个 UDP Socket,默认在 8472 端口监听。
接收到 vxlan 包后,解包,然后根据其中的 vxlan ID 将它转给某个 vxlan interface,然后再通过它所连接的 linux bridge 转给虚机。
Linux vxlan 在收到虚机发来的数据包后,将其封装为多播 UDP 包,从网卡发出。