在我们现在的服务器中,Docker容器技术已经是绕不开的重要部分。但是作为开发人员,往往对其理解并没有那么深刻。
1. 容器技术是做什么的
我们都知道,软件是运行在操作系统上的进程。对于现在的服务器而言,我们往往需要依赖操作系统的各种库与组件,这就需要大量的环境配置。而且,不同类型或者版本的操作系统,其配置往往还会有一些差异。
Docker是一个开源的应用容器引擎,可以轻松的为应用创建一个轻量级、可移植、可灵活堆叠扩缩容的容器,使任何应用可以在任何地方开发、部署和运行。
容器是一个轻量级的“操作系统模拟器”,其打包了程序运行所必需的最小粒度的环境配置,并且像JVM一样提供了跨操作系统的移植性,让我们程序的配置与部署变得简单高效。
2. Docker镜像、容器与仓库
当涉及到Docker的概念时,Docker镜像、容器和仓库是非常重要的。
- Docker镜像是一个只读的模板,它包含了应用程序运行所需的所有内容,包括代码、运行时、库、环境变量和配置文件。通过镜像,用户可以构建并部署容器。镜像是在Docker容器中的基础核心组件,可以让开发者快速且可靠地构建程序并确保跨不同环境的运行一致性。
- Docker容器是从镜像创建出来的运行实例,可以理解为镜像的一个具体实例,它可以被启动、停止、移动以及删除。容器是可运行的组件,其实例通过Docker镜像定义。通过容器,开发人员可以在其中运行应用程序并保持环境的一致性。
- Docker仓库是用来存放Docker镜像的地方。它可以是本地私有的,也可以是公共的。公共的Docker仓库最著名的就是Docker Hub,用来共享和分发Docker镜像。用户可以将自己的Docker镜像推送到仓库,也可以从仓库中拉取Docker镜像。
总之,Docker镜像是一个静态的只读文件,Docker容器是镜像的可运行实例,而Docker仓库则是用来存放和共享Docker镜像的地方。这三者在Docker中扮演了非常重要的角色。
3. Docker核心技术点
3.1. namespace
在Docker中,namespaces是Linux内核提供的一种机制,用于隔离系统资源,确保进程在其所属的namespace中可以独立地运行,而不受其他namespace的影响。Docker使用namespaces来提供容器的隔离环境,确保每个容器都拥有自己的一组namespace,可以隔离文件系统、网络、进程等资源。
以下是一些常见的Docker namespaces:
- PID namespaces: 允许容器内的进程在一个独立的进程空间中运行,与宿主机的进程空间隔离开来。
- Network namespaces: 为每个容器创建一个独立的网络命名空间,使得每个容器都拥有自己的网络接口、IP地址和路由表。
- Mount namespaces: 确保每个容器都有自己独立的文件系统挂载点,使得容器的文件系统与宿主机或其他容器的文件系统隔离开来。
- UTS namespaces: 允许每个容器拥有自己独立的主机名和域名设置,使得每个容器看起来都像是在一个独立的主机上运行。
通过使用namespaces,Docker能够实现容器之间的资源隔离,确保它们可以在独立的环境中运行,从而增强了安全性和可靠性。这种隔离机制使得Docker容器能够更加轻松地部署和运行,而不会相互影响。
3.2. cgroups控制组
在Docker中,cgroups(control groups)是一种Linux内核特性,用于限制、记录和隔离一个或多个进程对系统资源的使用。cgroups允许管理员按照需求分配系统资源,比如CPU、内存、磁盘I/O和网络带宽等,以及隔离进程组以防止资源饥饿和提高系统的可靠性。
在Docker中,cgroups用于确保容器中运行的进程不会耗尽系统资源,同时也可以限制每个容器可以使用的资源量。通过cgroups,Docker可以限制容器使用的CPU数量、内存大小、磁盘I/O速度等,并且可以在运行时动态调整这些限制。
总之,cgroups是Linux内核提供的一种机制,用于限制和隔离进程对系统资源的使用,Docker利用cgroups来实现容器的资源控制和隔离,确保容器可以在共享的系统上安全可靠地运行。
3.3. UnionFS文件系统
UnionFS (联合文件系统)是一种分层的文件系统,允许将多个不同的文件系统安装到同一个目录下,从而使这些文件系统的内容合并展现出来。在Docker中,UnionFS被用来实现镜像层的堆叠和管理。
Docker使用UnionFS来构建镜像,将多个只读层叠加到一起,形成一个可写的容器层。这种分层的设计使得镜像可以轻松地构建和共享,同时也提供了更高的资源利用率。
假设我们有一个应用程序,它的镜像包含以下几个层次的文件系统:
- 底层是操作系统的基础镜像,包含了操作系统的核心文件和依赖项。
- 第二层是你自己定义的一个镜像,可能包含了应用程序的代码和一些配置文件。
- 第三层是另一个镜像,可能包含了需要的库文件和其他依赖项。
这三个镜像可以通过UnionFS层叠在一起,对外呈现为一个统一的文件系统。这样,你的Docker容器就可以使用这些层次的文件系统,而不必担心它们之间的相互影响。
当Docker容器运行时,UnionFS会以只读方式挂载底层的镜像层,然后在其之上创建一个可写层,该层包含了容器运行时修改的文件和数据。因此,即使多个容器使用相同的底层镜像,它们也可以拥有自己独立的可写层,互不影响。
总之,UnionFS在Docker中实际上就是将多个文件系统层叠在一起,实现了高效地镜像构建与管理。
4. DockerFile
Dockerfile 是自动构建 docker 镜像的配置文件,也是我们在工程中直接打交道的文件,用户可以使用 Dockerfile 快速创建自定义的镜像。Dockerfile 中的命令非常类似于 linux 下的 shell 命令。
4.1. FROM
镜像定制指的是以某个镜像为基础,在其上进行定制。Dockerfile第一条指令必须是FROM。基础镜像可分为:服务类镜像:nginx, redis, mysql,开发、构建、应用各类语言的镜像:python,操作系统镜像:ubuntu,centos等。
ROM ubuntu:18.04
4.2. RUN
RUN用来执行命令行命令,就像在操作系统中直接执行命令。在UnionFS中,我们提到docker是分层结构,在每层执行结束RUN命令后,都会commit这层修改:
- Dockerfile支持行尾添加\进行换行,以及行首添加#进行注释
- 多个shell指令可以放在一个RUN中,使用&&进行连接,避免镜像过于臃肿
RUN buildDeps='gcc libc6-dev make' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps
4.3. COPY
主要作用是复制文件:
#shell格式,就像直接在命令行中输入的命令一样
COPY [源路径] [目的路径]
4.4. ADD
在COPY基础上增加了一些功能,例如:源文件可以是一个URL,这样Docker Engine会试图去下载这个文件,然后默认给予600的权限,但是如果需要修改权限,则要单独执行一个RUN命令;如果下载的文件是压缩包,则也要单独增加一个RUN命令去解压缩。一般不推荐使用ADD,尽量使用COPY。 如果压缩文件格式为gzip, bzip2, xz时,ADD可以自动解压缩,这时很实用。
4.5. CMD
Docker容器的本质是进程,因此,需要制定进程的启动程序和参数,CMD就是容器启动命令,一般有两种形式:
#exec格式,像是函数调用中的格式
CMD echo $HOME
#等价于
CMD ["sh" "-c" "echo $HOME"]
4.6. ENTRYPOINT
入口点,其目的和CMD一样都在于指定容器启动程序及参数,ENTRYPOINT可以使得CMD命令参数传递给ENTRYPOINT,主要用于以下几种场景:
- 让镜像像命令一样使用;
- 应用运行前的准备工作
4.7. VOLUME
VOLUME是Docker中的重要概念,是容器文件的挂载点,容器运行时,容器运行时应该尽量保持容器存储层不发生写操作,容器的存储层需要保持无状态变化,对于数据库类的文件,其数据应该保存在卷中。为了防止用户运行时忘记将动态目录挂载为卷,在Dockerfile中我们可以提前指定。
VOLUME /data1