深入理解与相关配置
# 深入理解与相关配置
要想更好的理解docker是什么,能做什么,首先需要了解它的三大核心概念。镜像、容器、仓库。
# 镜像
镜像,可以说就是我们使用的app,不同的镜像有不同的功能。用计算机的方式来说,它就是一些只读
文件和文件夹的组合。它包含了容器运行时所需要的所有基础文件和配置信息,是容器启动的基础。所以你想启动一个容器,那首先必须要有一个镜像。镜像是 Docker 容器启动的先决条件。
如果你想要使用一个镜像,你可以用这两种方式:
- 自己创建镜像。通常情况下,一个镜像是基于一个基础镜像构建的,你可以在基础镜像上添加一些用户自定义的内容。例如你可以基于
centos
镜像制作你自己的业务镜像,首先安装nginx
服务,然后部署你的应用程序,最后做一些自定义配置,这样一个业务镜像就做好了。 - 从功能镜像仓库拉取别人制作好的镜像。一些常用的软件或者系统都会有官方已经制作好的镜像,例如
nginx
、ubuntu
、centos
、mysql
等,你可以到 Docker Hub (opens new window) 搜索并下载它们。
它提供了容器运行时所需的程序、软件库、资源、配置等静态数据。值得注意的是,镜像内容在使用其创建容器后也不会被改变。你可能会问它是如何做到不被改变的呢?
Docker 镜像是只读的,它包含了一系列层,每一层都是不可变的。当你基于某个镜像启动一个容器时,Docker 会在该镜像的顶部添加一个可写层,这个可写层用于保存容器运行时产生的所有修改和新增数据。
这意味着,容器在运行时对文件系统的任何修改,比如写入文件、修改配置等,都是在这个可写层上进行的,而不会影响到原始镜像。如果容器停止或删除,这个可写层也会随之消失,除非您使用 docker commit
命令将容器的当前状态保存为一个新的镜像,或者使用数据卷
(volumes)或绑定挂载(bind mounts)来持久化数据。
# 容器
刚刚我们提到了容器
。那容器是什么呢?容器是 Docker 的另一个核心概念。
通俗地讲,容器是镜像的运行实体。镜像是静态的只读文件,而容器带有运行时需要的可写文件层,并且容器中的进程属于运行状态。即容器运行着真正的应用进程。容器有初建、运行、停止、暂停和删除五种状态。
所以,容器=内存中运行的部分程序+能记录程序运行时产生数据的文件(动态数据文件),你可能会这样想对吧?
对,但不全对。上面说容器运行着真正应用的进程,由于docker的特性,那每个容器之间肯定是互相隔离的,要想实现这一目标。docker中的容器还需要宿主系统给予的一些功能支持——用以实现资源隔离和进程隔离。如:命名空间(Namespaces)、控制组(Cgroups)、联合文件系统(UnionFS)等。
所以说,Docker容器的启动依赖于镜像,它本身包括,动态文件(能记录运行时产生的数据,存放临时数据)和运行时的程序,还要加上于底层操作系统提供的资源隔离功能。
容器类似于面向对象编程中的类实例与类的关系——镜像是静态的模板,而容器则是镜像在运行时的具体表现。也就是说,在使用docker的过程中,你可以通过一个镜像,启动多个容器。
容器的本质是主机上运行的一个进程,但是容器有自己独立的命名空间隔离和资源限制。也就是说,在容器内部,无法看到主机上的进程、环境变量、网络等信息,这是容器与直接运行在主机上进程的本质区别。
# 仓库
Docker 的镜像仓库类似于代码仓库,用来存储和分发 Docker 镜像。镜像仓库分为公共镜像仓库和私有镜像仓库。
目前,Docker Hub (opens new window) 是 Docker 官方的公开镜像仓库,它不仅有很多应用或者操作系统的官方镜像,还有很多组织或者个人开发的镜像供我们免费存放、下载、研究和使用。除了公开镜像仓库,你也可以构建自己的私有镜像仓库。
# Docker的架构
# 容器发展简史
容器技术随着 Docker 的出现变得炙手可热,所有公司都在积极拥抱容器技术。此时市场上除了有 Docker 容器,还有很多其他的容器技术,比如 CoreOS 的 rkt、lxc 等。容器技术百花齐放是好事,但也出现了很多问题。比如容器技术的标准到底是什么?容器标准应该由谁来制定?
也许你可能会说, Docker 已经成为了事实标准,把 Docker 作为容器技术的标准不就好了?事实并没有想象的那么简单。因为那时候不仅有容器标准之争,编排技术之争也十分激烈。当时的编排技术有三大主力,分别是 Docker Swarm、Kubernetes 和 Mesos 。Swarm 毋庸置疑,肯定愿意把 Docker 作为唯一的容器运行时,但是 Kubernetes 和 Mesos 就不同意了,因为它们不希望调度的形式过度单一。
在这样的背景下,最终爆发了容器大战,OCI
也正是在这样的背景下应运而生。
OCI
全称为开放容器标准(Open Container Initiative),它是一个轻量级,开放的治理结构。OCI
组织在 Linux 基金会的大力支持下,于 2015 年 6 月份正式注册成立。基金会旨在为用户围绕工业化容器的格式和镜像运行时,制定一个开放的容器标准。目前主要有两个标准文档:容器运行时标准 (runtime spec)和容器镜像标准(image spec)。
正是由于容器的战争,才导致 Docker 不得不在战争中改变一些技术架构。最终形成了下图所示的技术架构。
我们可以看到,Docker 整体架构采用 C/S(客户端 / 服务器)模式,主要由客户端和服务端两大部分组成。客户端负责发送操作指令,服务端负责接收和处理指令。客户端和服务端通信有多种方式,即可以在同一台机器上通过UNIX
套接字通信,也可以通过网络连接远程通信。
# 客户端与服务端
# Docker 客户端
Docker 客户端其实是一种泛称。其中 docker 命令是 Docker 用户与 Docker 服务端交互的主要方式。除了使用 docker 命令的方式,还可以使用直接请求 REST API 的方式与 Docker 服务端交互,甚至还可以使用各种语言的 SDK 与 Docker 服务端交互。目前社区维护着 Go、Java、Python、PHP 等数十种语言的 SDK,足以满足你的日常需求。
# Docker 服务端
Docker 服务端是 Docker 所有后台服务的统称。其中 dockerd 是一个非常重要的后台管理进程,它负责响应和处理来自 Docker 客户端的请求,然后将客户端的请求转化为 Docker 的具体操作。例如镜像、容器、网络和挂载卷等具体对象的操作和管理。
Docker 从诞生到现在,服务端经历了多次架构重构。起初,服务端的组件是全部集成在 docker 二进制里。但是从 1.11 版本开始, dockerd 已经成了独立的二进制,此时的容器也不是直接由 dockerd 来启动了,而是集成了 containerd、runC 等多个组件。
虽然 Docker 的架构在不停重构,但是各个模块的基本功能和定位并没有变化。它和一般的 C/S 架构系统一样,Docker 服务端模块负责和 Docker 客户端交互,并管理 Docker 的容器、镜像、网络等资源。
# docker组件简介
以 Docker 的 18.09.2 版本为例,看下 Docker 都有哪些工具和组件。在 Docker 安装路径下执行 ls 命令可以看到以下与 docker 有关的二进制文件。
-rwxr-xr-x 1 root root 27941976 Dec 12 2019 containerd
-rwxr-xr-x 1 root root 4964704 Dec 12 2019 containerd-shim
-rwxr-xr-x 1 root root 15678392 Dec 12 2019 ctr
-rwxr-xr-x 1 root root 50683148 Dec 12 2019 docker
-rwxr-xr-x 1 root root 764144 Dec 12 2019 docker-init
-rwxr-xr-x 1 root root 2837280 Dec 12 2019 docker-proxy
-rwxr-xr-x 1 root root 54320560 Dec 12 2019 dockerd
-rwxr-xr-x 1 root root 7522464 Dec 12 2019 runc
2
3
4
5
6
7
8
可以看到,Docker 目前已经有了非常多的组件和工具。这里我不对它们逐一介绍,因为在第 11 课时,我会带你深入剖析每一个组件和工具。 这里我先介绍一下 Docker 的两个至关重要的组件:runC
和containerd
。
runC
是 Docker 官方按照 OCI 容器运行时标准的一个实现。通俗地讲,runC 是一个用来运行容器的轻量级工具,是真正用来运行容器的。containerd
是 Docker 服务端的一个核心组件,它是从dockerd
中剥离出来的 ,它的诞生完全遵循 OCI 标准,是容器标准化后的产物。containerd
通过 containerd-shim 启动并管理 runC,可以说containerd
真正管理了容器的生命周期。
通过上图,可以看到,dockerd
通过 gRPC 与containerd
通信,由于dockerd
与真正的容器运行时,runC
中间有了containerd
这一 OCI 标准层,使得dockerd
可以确保接口向下兼容。
gRPC (opens new window) 是一种远程服务调用。想了解更多信息可以参考https://grpc.io (opens new window) containerd-shim 的意思是垫片,类似于拧螺丝时夹在螺丝和螺母之间的垫片。containerd-shim 的主要作用是将 containerd 和真正的容器进程解耦,使用 containerd-shim 作为容器进程的父进程,从而实现重启 containerd 不影响已经启动的容器进程。
下面再说下runC
的大致运行运行过程:
当使用 Docker 或其他支持 OCI 规范的容器管理系统来启动容器时,每个容器的运行确实都会经过 runC 的处理。runC 作为容器运行时组件,负责根据 OCI 规范来创建和管理容器的生命周期。无论是在 Docker 中通过 docker run
命令启动容器,还是在 containerd 等其他容器管理工具中创建容器,最终都会调用到 runC 来执行容器的底层操作。
Docker 引擎在接收到启动容器的请求后,会准备容器的运行环境,包括设置命名空间、控制组以及其他必要的资源限制,然后调用 runC 来实际创建和启动容器。即使在 Docker 或 containerd 等较高层级的管理工具中,runC 仍然是执行容器的核心组件,确保了容器的标准化和跨平台一致性。
因此,可以认为每个容器在运行时,其底层的生命周期管理至少在某个阶段会经过 runC 的处理,来实现资源隔离、配置应用运行环境及管理容器的整个生命周期。