docker 学习路线
参考资料
- 知乎 docker 的学习路线
- Docker — 从入门到实践
- Docker 核心技术与实现原理
- Docker 入门
- 《Kubernetes in Action》,想要学习 Kubernetes 的读者一定不要错过。
- 编程能力影响最大的两本书就是:
- 《计算机程序的构造和解释》(Structure and Interpertation of Computer Programming,SICP)
- 《计算机程序的概念、技术和模型》(Concepts, Techniques, and Models of Computer Programming,CTMCP)
理解 docker 的核心技术(core tech)
- docker 的核心技术有 namespace, control groups, union filesystem
Namespaces
- 命名空间 (namespaces) 是 Linux 为我们提供的用于分离进程树、网络接口、挂载点以及进程间通信等资源的方法。
- Docker 其实就通过 Linux 的 Namespaces 对不同的容器实现了隔离。
- Linux 的命名空间机制提供了以下七种不同的命名空间,包括 CLONE_NEWCGROUP、CLONE_NEWIPC、CLONE_NEWNET、CLONE_NEWNS、CLONE_NEWPID、CLONE_NEWUSER 和 CLONE_NEWUTS,通过这七个选项我们能在创建新的进程时设置新进程应该在哪些资源上与宿主机器进行隔离。
- Docker 通过命名空间成功完成了与宿主机进程和网络的隔离。
进程
- 一个是 pid 为 1 的 /sbin/init 进程
- 负责执行内核的一部分初始化工作和系统配置,也会创建一些类似 getty 的注册进程。
- 另一个是 pid 为 2 的 kthreadd 进程
- 这两个进程都是被 Linux 中的上帝进程 idle 创建出来的。
- 负责管理和调度其他的内核进程。
- Docker 容器内部的任意进程都对宿主机器的进程一无所知。
- 在 setNamespaces 方法中不仅会设置进程相关的命名空间,还会设置与用户、网络、IPC 以及 UTS 相关的命名空间。
网络
- Docker 为我们提供了四种不同的网络模式,Host、Container、None 和 Bridge 模式。
- Docker 默认的网络设置模式:网桥模式。
- 除了分配隔离的网络命名空间之外,Docker 还会为所有的容器设置 IP 地址。
- 当 Docker 服务器在主机上启动之后会创建新的虚拟网桥 docker0,随后在该主机上启动的全部服务在默认情况下都与该网桥相连。
- 在默认情况下,每一个容器在创建时都会创建一对虚拟网卡,两个虚拟网卡组成了数据的通道,其中一个会放在创建的容器中,会加入到名为 docker0 网桥中。
- libnetwork:
- 提供了一个连接不同容器的实现,同时也能够为应用给出一个能够提供一致的编程接口和网络层抽象的容器网络模型。
挂载点
- 新的进程中创建隔离的挂载点命名空间需要在 clone 函数中传入 CLONE_NEWNS,这样子进程就能得到父进程挂载点的拷贝,如果不传入这个参数子进程对文件系统的读写都会同步回父进程以及整个主机的文件系统。
- 如果一个容器需要启动,那么它一定需要提供一个根文件系统(rootfs),容器需要使用这个文件系统来创建一个新的进程,所有二进制的执行都必须在这个根文件系统中。
- 为了保证当前的容器进程没有办法访问宿主机器上其他目录,我们在这里还需要通过 libcontainer 提供的 pivot_root 或者 chroot 函数改变进程能够访问文件目录的根节点。
- chroot 的使用能够改变当前的系统根目录结构,通过改变当前系统的根目录,我们能够限制用户的权利,在新的根目录下并不能够访问旧系统根目录的结构个文件,也就建立了一个与原系统完全隔离的目录结构。
CGroups
- Control Groups(简称 CGroups)就是能够隔离宿主机器上的物理资源,例如 CPU、内存、磁盘 I/O 和网络带宽。
- 每一个 CGroup 都是一组被相同的标准和参数限制的进程,不同的 CGroup 之间是有层级关系的,也就是说它们之间可以从父类继承一些用于限制资源使用的标准和参数。
- 当我们使用 Docker 关闭掉正在运行的容器时,Docker 的子控制组对应的文件夹也会被 Docker 进程移除,Docker 在使用 CGroup 时其实也只是做了一些创建文件夹改变文件内容的文件操作,不过 CGroup 的使用也确实解决了我们限制子容器资源占用的问题,系统管理员能够为多个容器合理的分配资源并且不会出现多个容器互相抢占资源的问题。
- 每一个 CGroup 下面都有一个 tasks 文件,其中存储着属于当前控制组的所有进程的 pid,作为负责 cpu 的子系统,cpu.cfs_quota_us 文件中的内容能够对 CPU 的使用作出限制。
UnionFS
- Linux 的命名空间和控制组分别解决了不同资源隔离的问题,前者解决了进程、网络以及文件系统的隔离,后者实现了 CPU、内存等资源的隔离,但是在 Docker 中还有另一个非常重要的问题需要解决 - 也就是镜像。
- 使用 docker run 非常轻松地从远程下载 Docker 的镜像并在本地运行。
- Docker 镜像其实本质就是一个压缩包,我们可以使用下面的命令将一个 Docker 镜像中的文件导出, docker export $(docker create busybox) | tar -C rootfs -xvf -
- Docker 中的每一个镜像都是由一系列只读的层组成的,Dockerfile 中的每一个命令都会在已有的只读层上创建一个新的层, 当镜像被 docker run 命令创建时就会在镜像的最上层添加一个可写的层,也就是容器层,所有对于运行时容器的修改其实都是对这个容器读写层的修改。
- 容器和镜像的区别就在于,所有的镜像都是只读的,而每一个容器其实等于镜像加上一个可读写的层,也就是同一个镜像可以对应多个容器。
- AUFS 作为联合文件系统,它能够将不同文件夹中的层联合(Union)到了同一个文件夹中,这些文件夹在 AUFS 中称作分支,整个『联合』的过程被称为联合挂载(Union Mount)。
- 每一个镜像层或者容器层都是 /var/lib/docker/ 目录下的一个子文件夹;在 Docker 中,所有镜像层和容器层的内容都存储在 /var/lib/docker/aufs/diff/ 目录中。
- 而 /var/lib/docker/aufs/layers/ 中存储着镜像层的元数据,每一个文件都保存着镜像层的元数据,最后的 /var/lib/docker/aufs/mnt/ 包含镜像或者容器层的挂载点,最终会被 Docker 通过联合的方式进行组装。
- Docker 还支持了不同的存储驱动,包括 aufs、devicemapper、overlay2、zfs 和 vfs 等等,在最新的 Docker 中,overlay2 取代了 aufs 成为了推荐的存储驱动,但是在没有 overlay2 驱动的机器上仍然会使用 aufs 作为 Docker 的默认驱动。
- docker info | grep Storage 查看存储驱动
- Linux 命名空间、控制组和 UnionFS 三大技术支撑了目前 Docker 的实现,也是 Docker 能够出现的最重要原因。
docker 基本组件
基本组件(Docker daemon, Docker client, registry镜像库, image镜像,container容器)
- 容器 = 镜像 + 读写层
- 一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。
- Docker daemon 是服务器组件,以 Linux 后台服务的方式运行,是 Docker 最核心的后台进程,我们也把它称为守护进程。它负责响应来自 Docker Client 的请求,然后将这些请求翻译成系统调用完成容器管理操作。该进程会在后台启动一个 API Server ,负责接收由 Docker Client 发送的请求,接收到的请求将通过Docker daemon 内部的一个路由分发调度,由具体的函数来执行请求。
- Docker Client ,也称 Docker 客户端。它其实就是 Docker 提供命令行界面 (CLI) 工具,是许多 Docker 用户与 Docker 进行交互的主要方式。
docker 实践
- 编写 Dockerfile 文件,设定 docker 编译规则
- docker build -t jianglong/nginx_web:v1 . 编译时设置仓库名,镜像名和版本号
- 构建完成之后,使用 docker images 命令查看所有镜像
- docker run --name nginx_web -d -p 8080:80 jianglong/nginx_web:v1 启动容器