Docker是基于容器的应用开发,部署和运行平台
- 高性能:相比传统虚拟机,不需要
hepervisor
的额外负载,而是直接在主机内核中运行,可以在同一硬件上运行更多工作负载 - 持续交付和部署:通过
container
,在一个隔离环境中打包和运行应用程序的功能,保证一致的运行环境,非常适合持续集成和持续交付(CI Continuous Integration,CD Continuous Delivery )工作流程,通过利用Docker的方法快速分发,测试和部署代码,可以显着减少编写代码和在生产中运行代码之间的延迟 - 易于扩展维护迁移,基于容器的特性允许高度可移植的工作负载,可以进行集群的自动化编排,容器集群管理方案有:Swarm,Kubernetes,Mesos等
docker_practice:github 上一个Docker的简介
架构
Docker Engine使用client-server架构,Docker client(docker
,是一个命令行接口,CLI)通过REST API和Docker daemon(dockerd
)交互,服务端绑定在一个Unix socket上
- Docker daemon:监听Docker API请求,管理Docker对象,例如镜像,容器,网络,数据卷,daemon也可以和其他主机的daemon通信来管理Docker服务
- Docker client:用户与Docker交互的主要方式,客户端使用Docker API将命令发送到
dockerd
,后者将其执行,Docker client可以与多个守护进程通信 - Docker registry:用于存储Docker镜像, Docker Hub和Docker Cloud是公共仓库,Docker默认在Docker Hub上查找镜像,也可以运行自己搭建的私有仓库
容器原理
docker使用go语言编写,它基于Linux内核的namespace与Cgroups功能
Containers and virtual machines
容器直接运行在Linux上,并与其他容器共享主机的内核。它运行一个独立的进程,不占用其他的内存,非常轻量级,是基于操作系统的虚拟化技术,早期的Docker就是基于LXC(Linux Container)项目封装了一套工具,后来重新设计了自己的虚拟化平台并发布了Libcontainer
项目,摆脱了对LXC的依赖
虚拟机(VM)运行一个完整的客户操作系统Cuest OS
,通过hypervisor
对主机资源进行虚拟访问,这样做能够比较好的实现虚拟机与宿主机操作系统的异构,例如Linux系统上跑Windows虚拟机,VM需要提供的环境比大多数应用程序需要的资源更多,例如KVM, 全称是基于内核的虚拟机(Kernel-based Virtual Machine),自Linux 2.6.20版本后就整合到内核中,是一个轻量级的Hypervisor,依托CPU虚拟化指令集(例如Intel-VT,Virtualization Technology、AMD-V)实现高性能的虚拟化支持
从隔离上来讲,虚拟机是用来对硬件资源进行划分,属于硬件虚拟化技术,通过Hypervisor层来实现对资源的彻底隔离,容器是操作系统级别的虚拟化,利用内核的特性实现,不需要外部辅助
Namespaces
Docker使用namespaces
技术来提供容器的隔离工作空间。当运行容器时,Docker会为该容器创建一组名称空间。这些命名空间提供了一层隔离。容器的每个方面都在一个单独的命名空间中运行,其访问权限仅限于该命名空间,名称空间种类如下:
- The cgroup namespace:CGroup isolation(CGoup: Control Group).让不同进程组看到的CGroup规则各不相同,为不同进程组采用各自的配额标准提供便利
- The pid namespace: Process isolation (PID: Process ID).基于进程的隔离能力,容器中的首个进程成为PID为1的进程,是所有进程的父进程,以及很多特权
- The net namespace: Managing network interfaces (NET: Networking).基于网络栈的隔离能力,容器中的进程和特定的网络关联起来
- The ipc namespace: Managing access to IPC resources (IPC: InterProcess Communication).基于System V进程信道的隔离能力
- The mnt namespace: Managing filesystem mount points (MNT: Mount).基于磁盘挂载点和文件系统的隔离能力
- The uts namespace: Isolating kernel and version identifiers. (UTS: Unix Timesharing System).基于主机名的隔离能力
- The user namespace:基于系统用户的隔离能力,同一用户在不同namespace中可以拥有不同的UID和GID
# 查看进程的namespace
$ ls -l /proc/$$/ns
总用量 0
lrwxrwxrwx 1 wdy wdy 0 9月 17 15:08 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 wdy wdy 0 9月 17 15:08 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 wdy wdy 0 9月 17 15:08 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 wdy wdy 0 9月 17 15:08 net -> net:[4026531957]
lrwxrwxrwx 1 wdy wdy 0 9月 17 15:08 pid -> pid:[4026531836]
lrwxrwxrwx 1 wdy wdy 0 9月 17 15:08 user -> user:[4026531837]
lrwxrwxrwx 1 wdy wdy 0 9月 17 15:08 uts -> uts:[4026531838]
Control groups
Linux上的Docker Engine还依赖于另一种称为控制组(cgroups)的技术。cgroup将应用程序限制到特定的一组资源。控制组允许Docker Engine将可用的硬件资源共享给容器,也可以强制执行限制和约束。例如限制特定容器的CPU,内存,磁盘IO等
$ cat /proc/$$/cgroup
11:cpuset:/
10:net_cls,net_prio:/
9:devices:/user.slice
8:perf_event:/
7:freezer:/
6:pids:/user.slice/user-1000.slice
5:cpu,cpuacct:/user.slice
4:memory:/user.slice
3:blkio:/user.slice
2:hugetlb:/
1:name=systemd:/user.slice/user-1000.slice/session-c2.scope
本质上对CGroup的所有操作都是对系统挂载的CGroup目录进行修改,在/sys/fs/cgroup/
目录下创建新的目录
Union file systems
联合文件系统或UnionFS是通过创建layer来操作的文件系统,使它们非常轻量和快速。Docker Engine使用UnionFS为容器提供构建块。Docker Engine可以使用多种UnionFS,包括AUFS,btrfs,vfs和DeviceMapper
Container format
Docker Engine将命名空间,控制组和UnionFS组合到一个称为容器格式的包装器中。默认的容器格式是libcontainer
。将来,Docker可能支持其他容器格式
Docker基本概念
Images
Docker镜像依照OCI(Open Container Initiative)规范,是一个只读模板(内容在构建之后不会改变),一个image由manifest
、image index
(可选)、filesystem layers
和configuration
四部分组成
镜像采用分层的结构,可以最大化利用磁盘存储,因为大多数的镜像都会基于一些标准的基础镜像来构建,分层存储方便重复利用
最底层bootfs(boot file system)
是一个引导文件系统,Linux在启动后之后,整个内核都会被加载进内,bootfs
会被卸载掉,之上是rootfs
,即经典的目录结构,对于base image
来说,底层直接用host
的kernel
,自己只需要提供rootfs
就行了,对应的所有容器都共用host
的kernel
使用Dockerfile创建image,Dockerfile中的每条指令都在镜像中创建一个层,每安装一个软件,就在现有镜像的基础上增加一层,镜像的每一层都可以被共享,例如多个镜像都从相同的base
镜像构建而来,那么主机只需在磁盘上保存一份base
镜像,更改Dockerfile并重建映像时,仅重建那些已更改的层,这使镜像轻量,小巧和快速
当容器启动时,一个新的可写层被加载到镜像的顶部。这一层通常被称作容器层 container layer
,“容器层”之下的都叫“镜像层”,对容器的改动 - 无论添加、删除、还是修改文件都只会发生在容器层中,只有容器层是可写的,容器层具有Copy-on-Write
特性,下面的所有镜像层都是只读的,当需要修改时才复制一份数据到容器层进行修改。可见,容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改
docker使用UnionFS文件系统,它能将多个文件系统的内容合并起来,形成一个单一的挂载点,挂到同一个目录下,底层使用copy on write技术,对文件的修改的会被向上拷贝到文件系统的一个临时分层里,不会影响文件系统的原始数据,Docker支持众多的联合文件系统,可以在启动Docker后台服务时通过--storage-driver
参数制定所使用的容器存储种类,例如aufx,overlay2,devicemapper等
Containers
容器是镜像的可运行实例,可以通过Docker API或CLI创建,启动,停止,移动或删除容器。可以将容器连接到一个或多个网络,并连接上存储,也可以根据其当前状态创建新的镜像。
默认情况下,容器与其他容器以及宿主机相对隔离,但是可以自由控制容器的网络,存储或其他底层子系统与其他容器或宿主机的隔离程度。容器由其镜像以及在创建或启动时为其提供的配置选项定义,容器内的文件是和宿主机隔离开的,如果不进行数据卷挂载,容器关闭以后数据就会丢失
Services
Service允许在多个Docker daemons中扩展容器,一起作为一个swarm集群工作,内部具有多个manager和worker节点。swarm的每个成员都是Docker daemon,并且都使用Docker API进行通信。 可以再服务中定义所需的状态,例如服务副本数。默认情况下,服务在所有工作节点之间进行负载平衡。 对于访问者来说,Docker服务似表现的就像一个单独的应用程序。Docker Engine在Docker 1.12及以上版本中支持swarm模式,进行集群服务管理
部署层次
Containers
Docker通过读取Dockerfile
中的指令自动构建镜像,Dockerfile是一个文本文档,定义了容器环境内进行的操作。在容器内对网络接口和磁盘等资源的访问是虚拟化的,与系统的其他部分隔离,因此需要在dockerfile中将端口映射到宿主机,具体说明要“复制”到哪些文件到容器,挂载数据卷等,然后就可以启动一个运行镜像的容器
Services
在分布式应用程序中,应用程序的不同部分称为“services”。例如,如果一个视频共享站点,它可能包括一个用于将应用程序数据存储在数据库中的服务,一个用户上传内容后进行视频转码的服务,前端web服务等
服务实际上只是“工作状态的容器”,一个服务只运行一个镜像,但它设定了镜像的运行方式,包括应该使用哪些端口,应该运行多少个容器副本,以及服务具有所需的容量,等等。扩展一个服务会改变运行该部分程序的容器的数量,为该服务分配更多计算资源,通过YAML文件即可定义,运行和扩展服务
Stacks
stack是一组关联的services,它们共享依赖关系,并且可以被一起编排缩放。堆栈能够定义和协调整个应用程序的功能,对外体现为多个容器组合而成的复杂系统
docker stack deploy -c docker-compose.yml stack_nameXXX
Swarm
Docker Swarm是docker的原生集群,用于多个机器上创建容器集群服务,一个swarm
是运行Docker并加入一个群集的一组计算机,集群中的机器被称为node。该模块叫做Swarm Mode
,早期版本中叫做Swarm standalone
内部使用Etcd中的Raft模块确定Master
节点,raft是分布式一致性协议,用于解决在网络集群中进行状态确认的问题
# Manage nodes in a swarm
docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
4zrcf6u6gfwvpgw7ygcj2rrt5 * gpu-server5 Ready Active Leader 18.06.1-ce
wyndarmh11o1qpt6yqy8717xs gpu-server7 Ready Active 18.06.1-ce
AVAILABILITY
列表示调度器是否可以向该节点分配任务
Active
正常
Pause
不能分配新的任务,但是已有task可以继续运行
Drain
不能分配新的任务,调度器关闭已有的Task,在其他节点上运行,通常用于对节点进行维护升级
MANAGER STATUS
列显示节点在Raft的角色:
- 没有值表示不参与群组管理的工作节点
-
Leader
表明节点是primary manager node
,对集群进行管理和编排 -
Reachable
该节点是参与Raft共识仲裁的manager。 如果leader节点变得不可用,则该节点有资格被选为新leader -
Unavailable
意味着该manager节点无法与其他manage通信
Swarm manager是集群中唯一可以执行命令的机器,或授权其他机器作为worker加入群集,worker只执行工作,没有权力调度集群
Docker Machine
Docker Machine是一个工具,用于在虚拟机上安装Docker Engine
,并使用docker-machine
命令管理这些主机
数据管理
在Docker的使用过程中往往需要对数据进行持久化,或者需要在多个容器之间进行数据共享,所以这就涉及到Docker容器的数据操作
- Volumes方式下:容器内的数据被存放到宿主机(linux)一个特定的目录下(/var/lib/docker/volumes/)。这个目录只有Docker可以管理,其他进程不能修改。如果想持久保存容器的应用数据,Volumes是Docker推荐的挂载方式
- Bind mounts方式下:容器内的数据被存放到宿主机文件系统的任意位置,甚至存放到一些重要的系统目录或文件中。除了Docker之外的进程也可以任意对他们进行修改
- tmpfs方式下:容器的数据只会存放到宿主机的内存中,不会被写到宿主机的文件系统中,因此不能持久保存容器的应用数据。
Volumes
由Docker创建和管理,可以通过命令docker volume create
来现式创建一个卷,也可以由Docker在创建容器或服务的过程中来创建
当创建一个volume时,它将会被存储到宿主机的一个目录下/lib/docker/volumes/
,当挂载这个volume到一个容器中时,就是挂载这个目录到容器中。这和bind mount的工作机制很相似,但是volume是被Docker所管理并与宿主机其他核心功能隔离
一个volume可以被同时挂载到多个容器中。当没有任何容器在使用这个volume的时候,这个volume也仍然可以被Docker获取,并不会被自动被删除。可以使用命令删除不使用的volume:docker volume prune
当挂载一个volume时,可以选择为他命名(named),也可以匿名(anonymous). 匿名volume首次被挂载到一个容器中时,Docker会给它一个随机的名字,保证在宿主机操作系统中这个volume的名字是唯一的。除了名字之外,命名和匿名的volume没有区别。
Volume也支持使用volume drivers,用来将数据保存到远程主机或云上
Bind mounts
在docker的早期版本中就存在该功能,与volumes相比,他的功能比较局限。当使用bind mounts时,宿主机的目录或文件被挂载到容器中。容器将按照挂载目录或文件的绝对路径来使用或修改宿主机的中的数据。宿主机中的目录或文件不需要预先存在,在需要的时候会自动创建。使用Bind mounts在性能上是非常好的,但这依赖于宿主机有一个目录结构妥善的文件系统。如果你要创建一个新的Docker应用,推荐使用named volume的方式,因为你无法通过Docker CLI来管理bind mounts
bind mounts是一把双刃剑,因为可以在通过容器内部的进程对主机文件系统进行修改,包括创建,修改和删除重要的系统文件和目录,这个功能虽然很强大,但显然也会造成安全方面的影响,包括影响到宿主机上Docker以外的进程
tmpfs mounts
仅Linux系统支持,容器内的应用数据将不会被持久的保存到硬盘上,其中的数据只能在某个容器的生存周期内被使用,用于保存一些不需要持久存储,或一些敏感的数据信息。比如,在docker内部,swarm服务使用tmpfs方式来将secrets挂载到服务容器中
Bind mounts和volumes都可以通过使用标志v
或--volume
来挂载到容器中,只是格式有些许不同。tmpfs可以使用标志-tmpfs
进行挂载。在Docker17.06及其以上版本中,推荐使用--mount
来对容器或服务进行这三种方式的挂载,因为这种格式更加清晰
网络
Docker网络模块主要依赖libnetwork
,依照Container Network Model (CNM) 实现
网络模型
CNM:Container Network Model,容器的标准网络模型,只要符合这个模型的网络接口就能被用于容器之间的通信,通信的细节和过程完全由网络接口实现,涉及三个术语:
- Sandbox:对应一个网络环境,包括网卡配置、路由表、DNS配置等
- Endpoint:容器中的虚拟网卡,显示为eth0、eth1
- Network:能够相互通信的容器网络,加入了同一个网络的容器可以直接通过对方的名字相互连接,实体是主机上的虚拟网卡或网桥
CNI标准:Container Networking Interface 另一个轻量级的应用容器网络的开放插件化标准,多数容器集群架构都使用该标准,例如Kubernetes、Mesos
- Container:拥有独立Network Namespace的运行单元,与CNM的Sandbox概念基本一致
- Network:可以互相连接的一组实体,实体拥有各自独立的唯一IP地址,例如容器,物理机或网关、路由器等网络设备,相当于CNM的Network加Endpoint概念
容器网络涉及的其他技术:
- Virtual Bridge:虚拟的网络交换单元,功能相当于交换机,为连在其中的设备转发数据帧,例如Docker默认创建的docker0网桥
- iptable:内置在内核中的基于规则的网络防火墙和包转发服务,为容器提供于NAT和网络安全相关的特性
- Veth(Virtual Ethernet Device) Pair,由两个虚拟网卡组成的数据通道,用于不同network namespace间进行通信,例如veth pair 一端连接到容器中作为eth0网卡,另一端连接到docker0网桥,将容器的数据发往另一个 network namespace
Docker的网络实现
Docker网络共有四种模式
- none
容器内只有回环网络,容器拥有自己独立的network namespace,但不进行配置,通常在容器不需要网络或者自定义网络时使用 - host
直接使用host的网络,容器共享宿主机的network namespace,使用的是宿主机的IP、端口等资源 - bridge
docker的默认网络模式,docker在安装时会创建一个名为docker0
的Linux bridge,默认网段是172.17.0.0/16,在不指定network
的情况下,新建的容器都会通过veth pair默认挂到docker0上,并在该网段内获取一个IP,在同一个网桥内的容器可以通过IP地址通信,外部以及不同网络之间的通信需要通过映射到主机的端口,底层依靠iptables实现端口转发功能
容器与宿主机通信:在桥接模式下,Docker Daemon 将 veth0 附加到 docker0 网桥上,保证宿主机的报文有能力发往 veth0。再将 veth1 添加到 Docker 容器所属的网络命名空间,保证宿主机的网络报文若发往 veth0 可以立即被 veth1 收到
容器与外界通信:容器如果需要联网,则需要采用 NAT方式。准确的说,是 NATP (网络地址端口转换) 方式。NATP 包含两种转换方式:SNAT 和 DNAT
-
目的 NAT (Destination Network Address Translation,DNAT): 修改数据包的目的地址。
-
源 NAT (Source Network Address Translation,SNAT): 修改数据包的源地址
- overlap
overlap
网络驱动程序在多个Docker daemon主机之间创建分布式的网络。该网络(overlap)位于主机网络之上,允许连接到它的容器进行通信。它基于VxLAN实现,原理就是进行额外的装包、拆包,因为现有网络只认识节点到节点的路由,容器间通信时将数据包加上额外的传输协议包头(ISO网络模型二层或以上协议),先将数据传输到目的节点,解包后传输到目的容器
初始化swarm或将Docker主机加入现有swarm时,会在该Docker主机上创建两个新网络:
- 一个名为
ingress
的overlay
网络,用于处理与swarm service相关的控制和数据流量。创建swarm service时,如果不将其连接到用户定义的overlay网络,则默认下会连接到ingress网络 - 一个名为
docker_gwbridge
的bridge网络,所有加入swarm的主机上都有该网桥,且网段是相同的
端口相关:
- TCP 端口 2377 用来进行集群管理通信
- TCP UDP 端口 7946 用来节点之间通信
- UDP 端口 4789 用来进行覆盖网络的通信
容器网络还有另外一种经典模式,Container模式:该模式下启动的容器将和指定的其他容器共享network namespace,该模式也是Kubernetes使用的网络模式
跨结点网络
基本条件
- 对IP分配进行规划,使得每个容器对应不同的IP地址
- 记录容器IP和宿主机IP的映射关系
解决方案:
集群IP统一分配,通常会给每个主机分配一个IP段,每个主机会知道当前节点所有容器的路由规则,以及集群所有IP段到对应主机的路由规则,路由过程中首先是源容器将数据发送到目标容器所在的主机节点,然后由目标主机的本地路由规则将数据包送到真正的目标容器
此外该中心地址分配器需要保证稳定性,例如Flannal和Calico通过Etcd服务存储网络分配信息,内部使用一致性协议Raft保证消息的可靠性
- 覆盖网络:主要是基于VxLAN实现夸节点网络
- Docker内置的overlap网络
- Flannel的UDP和VxLAN模式
- 扩展路由:让容器的IP和主机的IP地址一样,直接进行通信,不需要额外的封装,具体有macvlan,节点网关路由,节点BGP路由三种
- Docker的macvlan网络模式
- Caliao基于节点BGP协议的路由方案,可以直接利用数据中心的网络结构,不需要额外的NAT、隧道或者Overlay Network,没有额外的封包解包,提升网络效率
- Flannel的HostGW模式:基于内核路由表和Iptable规则的路由方案
macvlan技术在主机网卡上添加多个MAC地址(需要硬件设备的支持),对应上多个IP,每个网卡分配给容器的不同Network Namespace,就相当于把所有容器的网络都直接相连到了主机网卡
节点网关路由:每个节点上运行一个Agent服务,监听容器IP段的分配,一旦由变化就将新的路由规则刷新到内核路由表,每个节点上的Agent就是一个控制网络联通规则的网关程序,这种方法称为主机网关
节点BGP路由:如果节点直接隔着一个路由器,节点网关路由就会失效,路由器没有相应的路由规则,此时需要Agent把自己作为容器所在IP子网段的边界路由器,利用BGP协议让网络中的其他三层设备学习到容器网段的路由信息
Flannel容器网络方案
Flannel实现原理
- 每个主机上的Docker容器分配互相不冲突的IP地址
- 在这些IP地址之间建立一个覆盖网络
Flannel会启动flanneld
进程,连接etcd,管理IP地址段的分配,初始时获取一个未分配的地址段,然后在Docker的启动参数中指定docker0网桥的地址,flannel之间的底层通信协议有很多,例如UDP,VxLAN等,通过在应用层进行封装、解包进行通信
--bip=172.24.77.1/24
# ifconfig 中的部分数据
docker0: flags=4099 mtu 1500
inet 172.24.77.1 netmask 255.255.255.0 broadcast 172.24.77.255
flannel.1: flags=4163 mtu 1450
inet 172.24.77.0 netmask 255.255.255.255 broadcast 0.0.0.0