docker平台

docker提供了一种拥有打包和运行在一个隔离环境能力的容器。隔离性和安全性允许你去在一台机器上允许多个容器。容器是轻量级的,并且包含运行应用程序所需的一切环境,因此无需依赖主机上当前安装的内容。

docker架构

docker使用了一个客户端-服务端的架构。docker客户端与docker守护进程(daemon)通信,后者负责构建、运行和分发docker容器的繁重工作。docker客户端和守护进程可以在同一个系统上运行,或者可以将docker客户端连接到远程docker守护进程。docker客户端和守护进程使用REST API,通过 UNIX 套接字或网络接口进行通信。另一个 Docker 客户端是 Docker Compose,它允许您使用由一组容器组成的应用程序。

docker架构

docker守护进程

Docker 守护程序 ( dockerd) 侦听 Docker API 请求并管理 Docker 对象,例如图像、容器、网络和卷。守护进程还可以与其他守护进程通信以管理 Docker 服务。

docker hub(registry)

Docker hub存储 Docker 镜像。Docker Hub 是一个任何人都可以使用的公共存储空间,并且 Docker 默认配置为在 Docker Hub 上查找镜像。甚至可以运行自己的私有hub。

当您使用docker pullordocker run命令时,将从你配置的hub中提取所需的镜像。当您使用该docker push命令时,镜像会被推送到你配置的hub中。

docker对象

当使用 Docker 时,正在创建和使用镜像、容器、网络、卷、插件和其他对象。本节简要概述了其中一些对象。

镜像(image)

镜像是一个只读模板,其中包含创建 Docker 容器的说明。通常,一个镜像基于另一个镜像,并带有一些额外的自定义,当镜像被命令创建时就会在镜像的最上层添加一个可写的层,也就是容器层,所有对于运行时容器的修改其实都是对这个容器读写层的修改。容器和镜像的区别就在于,所有的镜像都是只读的,而每一个容器其实等于镜像加上一个可读写的层,也就是同一个镜像可以对应多个容器。

docker镜像层结构

要构建自己的镜像,需要使用简单的语法创建一个Dockerfile ,用于定义创建和运行镜像所需的步骤。Dockerfile 中的每条指令都会在镜像中创建一个层(ENV、EXPOSE、CMD、ENTRY-POINT等命令不会创建镜像层,它们会在镜像中添加元数据),每层都有一个镜像层ID来标识它,可以通过docker image inspect命令来查看细节。当您更改 Dockerfile 并重建镜像时,仅重建那些已更改的层。与其他虚拟化技术相比,这是使镜像如此轻量、小巧和快速的部分原因。

如下图所示:P89

docker采取分层结构的最大好处就是共享资源,比如:有多个镜像都从相同的 base 镜像构建而来,那么宿主机只需在磁盘上保存一份 base 镜像,同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享。

容器

容器是镜像的可运行实例。您可以使用 Docker API 或 客户端 创建、启动、停止、移动或删除容器。你可以将容器连接到一个或多个网络,并将它与存储连接在一起,甚至可以根据其当前状态创建新镜像。

默认情况下,一个容器与其他容器及其主机的隔离相对较好。您可以控制容器的网络、存储或其他底层子系统与其他容器或主机的隔离程度。

容器由其镜像以及你在创建或启动它时提供给它的任何配置选项定义。当容器被移除时,任何未存储在持久存储中的状态更改都会消失。(如mysql存储的数据)

示例docker run命令

以下命令运行一个ubuntu容器,以交互方式附加到本地命令行会话,然后运行/bin/bash.

1
$ docker run -i -t ubuntu /bin/bash

当运行此命令时,会发生以下情况(假设使用的是默认docker hub配置):

  1. 如果在本地没有ubuntu镜像,Docker 会从配置的docker hub中下载它,就像docker pull ubuntu手动运行一样。
  2. Docker 会创建一个新容器,就像您docker container create 手动运行命令一样。
  3. Docker客户端会向Docker daemon发出命令,daemon收到命令后会向containerd发出调用。(daemon已经不再包含任何创建容器的代码了)
  4. 接着containerd将Docker镜像转换为OCI bundle,并让runc基于此创建一个新的容器。
  5. 然后,runc与操作系统内核接口进行通信,基于所有必要的工具(Namespace、CGroup等)来创建容器。容器进程作为runc的子进程启动,启动完毕后,runc将会退出。
  6. Docker 为容器分配一个读写文件系统(联合文件系统),作为它的最后一层。这允许正在运行的容器在其本地文件系统中创建或修改文件和目录。
  7. Docker 创建了一个网络接口来将容器连接到默认网络,因为没有指定任何网络选项。这包括为容器分配 IP 地址。默认情况下,容器可以使用主机的网络连接连接到外部网络。
  8. Docker 启动容器并执行/bin/bash. 因为容器以交互方式运行并附加到本地的终端(由于-iand-t 标志),所以可以在输出记录到终端时使用键盘提供输入。
  9. 当键入exit终止/bin/bash命令时,容器会停止但不会被删除。可以重新启动或删除它。

整个过程如下:P45

docker底层原理

Docker 是用Go编程语言编写的,并利用 Linux 内核的几个特性来提供其功能。

命名空间(Namespaces)

docker使用了一种称为命名空间的技术来提供称为容器的隔离工作区。当您运行容器时,Docker 会为该容器创建一组命名空间。这些命名空间提供了一层隔离。容器的每个方面都在单独的命名空间中运行,并且它的访问权限仅限于该命名空间。

这些命名空间提供了一层隔离。容器的每个方面都在单独的命名空间中运行,并且它的访问权限仅限于该命名空间。

docker引擎在linux下使用了以下这些命名空间:

  • pid命名空间:进程隔离
  • net命名空间:管理网络接口
  • ipc命名空间:管理进程间通信的资源(共享内存)
  • mnt命名空间:管理文件系统挂载
  • uts命名空间:隔离内核和版本定义

eg:

cd /proc,里面以进程id作为文件名,保存了每一个进程的信息。

cd 1和cd 2:进入进程id为1和2的文件里,发现两个文件夹里以上命名空间对应的id是相等的,说明宿主机里的两个进程是在一个命名空间里。

docker run -d redis:在宿主机上运行一个redis的容器,再进入到该容器对应的进程文件里,发现容器的命名空间和宿主机进程的命名空间是隔离的。

控制群组(control groups)

Linux 的命名空间为新创建的进程隔离了文件系统、网络并与宿主机器之间的进程相互隔离,但是命名空间并不能够为我们提供物理资源上的隔离,比如 CPU 或者内存,如果在同一台机器上运行了多个对彼此以及宿主机器一无所知的容器,这些容器却共同占用了宿主机器的物理资源。

docker引擎在linux上也依赖另一个名叫control groups(cgroups)的技术。一个cgroup能限制一个应用程序的一些特定的资源,如cpu、内存、网卡、块设备的读写,也就是限制一个容器使用的资源。

联合文件系统(Union file systems)

联合文件系统也叫文件系统,它是通过创建一种层来使它们非常的轻量和快。 docker引擎使用联合文件系统来提供容器的块。docker引擎可以使用多种联合文件系统,包含AUFS,btrfs,vfs和DeviceMapper。

总的来说,文件系统就是把磁盘上存储空间的内容通过一定的形式来展现出来的一个系统。

容器格式(Container format)

docker引擎把命名空间、控制群组、文件系统组合成一个包装器,叫做容器格式。它默认的容器格式叫libcontainer。在未来,docker会支持其他一些容器格式。

容器和虚拟机的区别

与虚拟机模型相同,os也占用了全部硬件资源。在OS层之上,需要安装容器引擎(docker)。容器引擎可以获取系统资源,比如进程树、文件系统以及网络栈,接着将资源分割为安全的互相隔离的资源结构,称之为容器,每一个容器看起来就像一个真实的操作系统,在其内部可以运行应用。
P70