Docker是PaaS 提供商 dotCloud 开源的一个基于 LXC 的高级容器引擎,源代码托管在 Github 上, 基于go语言
并遵从Apache2.0协议
开源。最初实现是基于LXC,从0.7
以后开始去除LXC,转而使用自行开发的libcontainer,从1.11
开始,则进一步演进为使用runC和containerd。
Docker 容器可以快速自动化地部署应用,并通过操作系统内核技术(namespaces、cgroups等)
为容器
提供资源隔离与安全保障。Docker 作为轻量级的虚拟化方式,实现了PaaS平台高效部署、运行和维护。
Docker是完整的一套容器管理系统。Docker提供了一组命令,让用户更加方便直接地使用容器技术,而不需要过多关心底层内核技术。
我们先区分两个概念:容器与虚拟机。
很多人都用过虚拟机,我们用的传统虚拟机如 VMware 都需要模拟整台机器包括硬件。每台虚拟机都需要有自己的操作系统,虚拟机一旦被开启,预分配给它的资源将全部被占用。每台虚拟机包括应用,必要的二进制和库,以及一个完整的用户操作系统。 传统虚拟机技术
是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程
容器技术是和我们的宿主机共享硬件资源及操作系统,可以实现资源的动态分配。容器包含应用和其所有的依赖包,但是与其他容器共享内核。
容器在宿主机操作系统中,在用户空间以分离的进程
运行。容器技术是实现操作系统虚拟化的一种途径
,可以让您在资源受到隔离的进程中运行应用程序及其依赖关系。
Docker 属于Linux容器的一种封装,提供简单易用的容器使用接口。它是目前最流行的 Linux 容器解决方案。
容器不是模拟一个完整的操作系统,而是对进程进行隔离,相当于是在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。
Docker 将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了 Docker ,就不用担心环境问题。
总体来说,Docker 的接口相当简单,用户可以方便地创建和使用容器,把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改,就像管理普通的代码一样。
Docker 启动快速属于秒级别。虚拟机通常需要几分钟去启动。
Docker 需要的资源更少。Docker 在操作系统级别
进行虚拟化,Docker 容器和内核交互,几乎没有性能损耗,性能优于通过 Hypervisor 层(一种运行在基础物理服务器和操作系统之间的中间软件层)与内核层的虚拟化。
Docker 更轻量,更高效的系统资源利用。Docker 的架构可共用一个内核与共享应用程序库,所占内存极小。同样的硬件环境,Docker 运行的镜像数远多于虚拟机数量,对系统的利用率非常高。
一致的运行环境。由于开发环境、测试环境、生产环境不一致,会导致有些bug并未在开发过程中被发现。应用运行环境一致,就不会再出现“这段代码在我机器上没问题啊”这类问题。
持续交付和部署。对开发和运维(DevOps)人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。
更轻松的迁移。由于Docker确保了执行环境的一致性,使得应用的迁移更加容易。
更轻松的维护和扩展。Docker使用的分层存储
以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。
Docker用途:简单配置、代码流水线管理、开发效率、应用隔离、服务器整合、调试能力、多租户、快速部署
docker由 docker-client
,docker engine
,containerd
,docker-shim
,runc
组成,现在来谈谈每个组件是用来干嘛的:
containerd:docker engine
实际真实调用的还是containerd的api接口(rpc方式实现),containerd是docker engine和runc之间的一个中间交流组件。
docker-shim:docker-shim是一个真实运行的容器的真实垫片载体,每启动一个容器都会起一个新的docker-shim的一个进程,他直接通过指定的三个参数:容器id,boundle目录(containerd的对应某个容器生成的目录,一般位于:/var/run/docker/libcontainerd/containerID),运行是二进制(默认为runc)来调用runc的api创建一个容器。
runc: runc是一个命令行工具端,他根据oci(开放容器组织)的标准来创建和运行容器。
Docker 是一个客户/服务器(Client/Server,CS)架构
。Docker Client
是远程控制器,可通过TCP REST
向Docker Host
发送请求(创建容器、运行容器、保存容器、删除容器等)。Docker服务端的Daemon
对客户端的请求进行相应的管理,随后通过 driver
转发至容器中的libcontainer执
行环境。libcontainer提供与不同Linux内核隔离的接口
,类似命名空间及控制组。这种架构允许多个容器在共享同一个Linux内核的情况下完全隔离的运行。
Docker 包括三个基本概念:
Docker 镜像类似于虚拟机镜像,可以将它理解为一个只读的模板,每一个镜像由一系列的层 (layers) 组成。例如,一个镜像可以包含一个基本的操作系统环境,里面仅安装了 Apache 应用程序(或用户需要的其他软件)。可以把它称为一个 Apache镜像。
镜像是创建Docker 容器的基础。通过版本管理和增量的文件系统,Docker提供了一套十分简单的机制来创建和更新现有的镜像,用户甚至可以从网上下载一个已经做好的应用镜像,并直接使用。
Docker 使用 UnionFS
来将这些层联合到单独的镜像中。UnionFS 允许独立文件系统中的文件和文件夹(称之为分支)被透明覆盖,形成一个单独连贯的文件系统。正因为有了这些层的存在,Docker 是如此的轻量。当你改变了一个 Docker 镜像,比如升级到某个程序到新的版本,一个新的层会被创建。因此,不用替换整个原先的镜像或者重新建立(在使用虚拟机的时候你可能会这么做),只是一个新 的层被添加或升级了。现在你不用重新发布整个镜像,只需要升级,层使得分发 Docker 镜像变得简单和快速。
Docker 容器类似于一个轻量级的沙箱,Docker 利用容器来运行和隔离应用。容器是从镜像创建的应用运行实例,可以将其启动、开始、停止、删除,而这些容器都是彼此相互隔离的、互不相见的。
可以把容器看做是一个简易版的Linux系统环境(包括root用户权限、进程空间、用户空间和网络空间等)以及运行在其中的应用程序打包而成的盒子。
每一个 Docker 容器都是独立和安全的应用平台,Docker 容器是 Docker 的运行部分。
Docker 仓库类似于代码仓库,它是Docker集中存放镜像文件的场所。
仓库注册服务器是存放仓库的地方,其上往往存放着多个地方。每个仓库集中存放某一类镜像,往往包括多个镜像文件,通过不同的标签(tag)来进行区分。一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应改软件的各个版本。我们可以通过 <仓库名>:<标签>
的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest
作为默认标签。
Docker 仓库也有公有和私有
的概念。
公有 Docker 仓库
名字是 Docker Hub。Docker Hub 提供了庞大的镜像集合供使用。这些镜像可以是自己创建,或者在别人的镜像基础上创建。 国内还有(时速云、阿里云)等公开仓库。
当用户不希望公开自己的镜像文件,Docker 也支持用户在本地网络内创建一个只能自己访问的私有仓库
。当用户创建了自己的镜像之后就可以使用push命令将其上传到指定的公有或者私有仓库。这样下次在另外一台机器上使用该镜像时,只需要将其从仓库上pull 下来就可以了。Docker 仓库是 Docker 的分发部分。
使用docker命令时,Docker客户端都告诉Docker守护进程运行一个容器。
docker run -i -t ubuntu /bin/bash
可以来分析这个命令,Docker客户端使用docker命令来运行,run参数表明客户端要运行一个新的容器。
Docker客户端要运行一个容器需要告诉Docker守护进程的最小参数信息是:
-> 这个容器从哪个镜像创建,这里是ubuntu,基础的Ubuntu镜像。
-> 在容器中要运行的命令,这里是/bin/bash,在容器中运行Bash shell。
那么运行这个命令之后在底层发生了什么呢?按照顺序,Docker做了这些事情:
-> 拉取ubuntu镜像:Docker检查ubuntu镜像是否存在,如果在本地没有该镜像,Docker会从Docker Hub下载。如果镜像已经存在,Docker会使用它来创建新的容器。
-> 创建新的容器:当Docker有了这个镜像之后,Docker会用它来创建一个新的容器。
-> 分配文件系统并且挂载一个可读写的层:容器会在这个文件系统中创建,并且一个可读写的层被添加到镜像中。
-> 分配网络/桥接接口:创建一个允许容器与本地主机通信的网络接口。
-> 设置一个IP地址:从池中寻找一个可用的IP地址并且服加到容器上。
-> 运行你指定的程序:运行指定的程序。
-> 捕获并且提供应用输出:连接并且记录标准输出、输入和错误让你可以看到你的程序是如何运行的。
由此就可以拥有一个运行着的Docker容器了!从这里开始你可以管理你的容器,与应用交互,应用完成之后,可以停止或者删除你的容器。
Docker容器的底层技术:Namesapce(资源隔离)
和 Cgroup(资源限制)
命名空间(namespaces)
是 Linux 为我们提供的用于分离进程树、网络接口、挂载点以及进程间通信等资源
的方法。
如果我们在服务器上启动了多个服务,每一个服务都能看到其他服务的进程,也可以访问宿主机器上的任意文件。一旦服务器上的某一个服务被入侵,那么入侵者就能够访问当前机器上的所有服务和文件,这也是我们不想看到的,而 Docker 其实就通过 Linux 的 Namespaces 对不同的容器实现了隔离。
(1)pid namespace:使用在进程隔离(Process ID):
不同用户的进程就是通过pid namespace隔离开的,且不同 namespace 中可以有相同 PID。
具有以下特征:
(2)mnt namespace:使用在管理挂载点(Mount)
类似 chroot,将一个进程放到一个特定的目录执行。mnt namespace 允许不同namespace的进程看到的文件结构不同,这样每个namespace 中的进程所看到的文件目录就被隔离开了。同 chroot 不同,每个 namespace 中的 container 在 /proc/mounts 的信息只包含所在namespace的mount point。
(3)net namespace:使用在进程网络接口(Networking)
网络隔离是通过 net namespace 实现的, 每个 net namespace 有独立的 network devices, IP addresses, IP routing tables, /proc/net 目录。这样每个 container 的网络就能隔离开来。 docker 默认采用 veth 的方式将 container 中的虚拟网卡同 host 上的一个 docker bridge 连接在一起。
(4)uts namespace:使用在隔离内核和版本标识 (Unix Timesharing System)
UTS (“UNIX Time-sharing System”) namespace 允许每个 container 拥有独立的 hostname 和 domain name, 使其在网络上可以被视作一个独立的节点而非 Host 上的一个进程。
(5)ipc namespace:使用在管理进程间通信资源 (InterProcess Communication)
container 中进程交互还是采用 Linux 常见的进程间交互方法 (interprocess communication - IPC), 包括常见的信号量、消息队列和共享内存。然而同 VM 不同,container 的进程间交互实际上还是 host 上具有相同 pid namespace 中的进程间交互,因此需要在IPC资源申请时加入 namespace 信息 - 每个 IPC 资源有一个唯一的 32bit ID。
(6)user namespace:使用在管理空户空间
每个 container 可以有不同的 user 和 group id, 也就是说可以以 container 内部的用户在 container 内部执行程序而非 Host 上的用户。
有了以上 6 种 NameSpaces 从进程、网络、IPC、文件系统、UTS和用户角度的隔离,一个 container 就可以对外展现出一个独立计算机的能力,并且不同 container 从 OS 层面实现了隔离。
然而不同 namespace 之间资源还是相互竞争的,仍然需要类似ulimit来管理每个 container 所能使用的资源。
Docker还使用到了cgroups技术来管理群组。使应用隔离运行的关键是让它们只使用你想要的资源。这样可以确保在机器上运行的容器都是良民(good multi-tenant citizens)。群组控制允许Docker分享或者限制容器使用硬件资源。例如,限制指定的容器的内容使用。
cgroups实现了对资源的配额和度量。 cgroups 的使用非常简单,提供类似文件的接口,在 /cgroup 目录下新建一个文件夹即可新建一个 group,在此文件夹中新建 task 文件,并将 pid 写入该文件,即可实现对该进程的资源控制。
联合文件系统(UnionFS)
是用来操作创建层的,使它们轻巧快速。Docker使用UnionFS提供容器的构造块。Docker可以使用很多种类的UnionFS包括AUFS, btrfs, vfs, and DeviceMapper
Docker连接这些组建到一个包装中,称为一个 container format(容器格式)。默认的容器格式是libcontainer。Docker同样支持传统的Linux容器使用LXC。在未来,Docker也许会支持其它的容器格式,例如与BSD Jails 或 Solaris Zone集成。