Docker的体系架构
Docker的核心组件包括:
Docker 客户端:Client
Docker 服务器:Docker daemon
Docker 镜像:Image
Registry:镜像仓库
Docker 容器:Container
Docker 架构如图所示
Docker采用的是Client/Server 架构。客户端向服务器发送请求,服务器负责构建、运行和分发容器。客户端和服务器可以运行在同一个Host 上。
1)Docker客户端
Docker客户端通过命令行或者其他工具使Docker API
(https://docs.docker.com/reference/api/docker_remote_api) 与 Docker 的守护进程通信。最常用的Docker客户端是docker命令。通过docker我们可以方便地在Host上构建和运行容器。
2)Docker服务器
Docker 主机(Host)一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。
Docker daemon是服务器组件,以Linux后台服务的方式运行
Docker daemon运行在Docker host上,负责创建、运行、监控容器,构建、存储镜像。默认配置下,Docker daemon只能响应来自本地Host的客户端请求。如果要允许远程客户端请求,需要在配置文件中打开TCP监昕,步骤如下:
(1)编辑配置文件/etc/systemd/system/multi-user.target.wants/docker.service,在环境变量ExecStart后面添加-H tcp://0.0.0.0
如果使用的是其他操作系统,配置文件的位置可能会不一样。
(2)重启Docker daemon
查看docker进程和监听
3)Docker镜像
Docker 镜像是用于创建Docker容器的模板。可将Docker镜像看成只读模板,类似于安装系统用到的那个iso文件,我们通过镜像来完成各种应用的部署。通过它可以创建Docker容器。例如某个镜像可能包含一个Apache HTTP Server以及用户开发的Web应用。
镜像有多种生成方法:(1)从无到有开始创建镜像 (2)下载并使用别人创建好的现成的镜像 (3)在现有镜像上创建新的镜像。
可以将镜像的内容和创建步骤描述在一个文本文件中,这个文件被称作Dockerfile,通过执行docker build <docker-file>命令可以构建出Docker镜像,后面我们会讨论。
4)Docker容器
Docker容器就是Docker镜像的运行实例,容器是独立运行的一个或一组应用。容器可以被启动、开始、停止、删除等操作,每个容器都是相互隔离的。可以把容器看做是一个简易版的linux环境(包括root用户权限、进程空间、用户空间和网络空间等)和运行在其中的应用程序。
用户可以通过CLI(Docker)或是API启动、停止、移动或删除容器。可以这么认为,对于应用软件,镜像是软件生命周期的构建和打包阶段,而容器则是启动和运行阶段。
5)Registry镜像仓库
Registry是存放Docker镜像的仓库,Registry分私有和公有两种。
Docker Hub (https://hub.docker.com/)是默认的Registry,由Docker公司维护,上面有数以万计的镜像,用户可以自由下载和使用。
出于对速度或安全的考虑,用户也可以创建自己的私有Registry,后面我们会学习如何搭建私有Registry。
docker pull命令可以从Registry下载镜像。
docke run命令则是先下载镜像(如果本地没有),然后再启动容器。
[root@docker ~]# docker run -d -p 80:80 httpd
(1)Docker客户端执行docker run命令
(2)Docker daemon发现本地没有httpd镜像
(3) docker daemon从Docker Hub下载镜像httpd
(4)下载完成,镜像httpd被保存到本地
(5)Docker daemon启动容器
docker images可以查看到httpd已经下载到本地
[root@docker ~]# docke images
docker ps或者docker container ls显示容器正在运行
[root@docker ~]# docke ps
[root@docker ~]# docke container ls
docker镜像
镜像是Docker容器的基石,容器是镜像的运行实例,有了镜像才能启动容器。本章内容安排如下:首先通过研究几个典型的镜像, 分析镜像的内部结构,然后学习如何构建自己的镜像,最后介绍怎样管理和分发镜像。
1、镜像的内部结构
为什么我们要讨论镜像的内部结构?
如果只是使用镜像,当然不需要了解,直接通过docker命令下载和运行就可以了。但如果我们想创建自己的镜像,或者想理解Docker为什么是轻量级的,就非常有必要学习这部分知识了。我们从一个最小的镜像开始吧。
用docker images命令查看镜像的信息
[root@docker ~]# docke images hello-world
通过docker run运行
[root@docker ~]# docke run hello-world
2、base镜象
base镜像有两层含义:(1)不依赖其他镜像,从scratch 构建; (2)其他镜像可以以之为基础进行扩展。
所以,能称作base 镜像的通常都是各种Linux 发行版的Docker 镜像,比如Ubuntu、Debian、CentOS 等。
我们以CentOS7.9.2009为例考察base镜像包含哪些内容。下载镜像:
#docker pull centos:centos7.9.2009
查看镜像信息
[root@docker ~]# docke images centos
镜像大小202MB。
一个CentOS才202MB
平时我们安装一个CentOS至少都有几个GB, 怎么可能才202MB !
相信这是几乎所有Docker初学者都会有的疑问。下面我们来解释这个问题。
我们都知道,Linux操作系统由内核空间和用户空间组成。对于Linux而言,内核启动后,会挂载 root(根) 文件系统为其提供用户空间支持。如图所示。
1) rootfs
内核空间是kernel,,Linux刚启动时会加载bootfs 文件系统。
用户空间的文件系统是rootfs,包含我们熟悉的 /dev, /proc, /bin 等目录。
对于 base 镜像来说,底层直接用 Host 的 kernel,自己只需要提供 rootfs 就行了。而base镜像,就相当于是一个 root 文件系统。比如 Docker官方镜像ubuntu:14.04 就包含了完整的一套 Ubuntu 14.04 最小系统的 root 文件系统。
而对于一个精简的 OS,rootfs 可以很小,只需要包括最基本的命令、工具和程序库就可以了。相比其他 Linux 发行版,CentOS 的 rootfs 已经算臃肿的了,alpine 还不到 10MB。
我们平时安装的 CentOS 除了 rootfs 还会选装很多软件、服务、图形桌面等,需要好几个 GB 就不足为奇了。
2) base镜像提供的是最小安装的 Linux 发行版,下面是 CentOS 镜像的 Dockerfile 的内容:
第二行 ADD 指令添加到镜像的 tar 包就是 CentOS 7 的 rootfs。在制作镜像时,这个 tar 包会自动解压到 / 目录下,生成 /dev, /proc, /bin 等目录。
注:可在Docker Hub(https://hub.docker.com/) 的镜像描述页面中查看Dockerfile。
3、支持运行多种Linux OS
不同Linux发行版的区别主要就是rootfs。
比如Ubuntu 14.04使用upstart管理服务,apt管理软件包;而CentOS 7使用systemd和yum。这些都是用户空间上的区别,Linux kernel 差别不大。
所以 Docker 可以同时支持多种 Linux 镜像,模拟出多种操作系统环境。
上图 Debian 和 BusyBox(一种嵌入式 Linux)上层提供各自的 rootfs,底层共用 Docker Host 的 kernel。
1)base 镜像只是在用户空间与发行版一致,kernel 版本与发行版是不同的。
例如 Ubuntu 18.04.1 使用 4.15.x 的 kernel; CentOS 7 使用 3.10.x 的 kernel。
如果 Docker Host 是 centos7.5(比如我们的实验环境),那么在Ubuntu容器中使用的实际是Host 3.x.x 的 kernel(容器的kernel以宿主机kernel一致)。
- Host 发行版本为centos7.5
- Host kernel 为3.10.0-862.14.4.el7.x86_64
- 容器的发行版本为Ubuntu 18.04.1
- 容器的 kernel 版本与 Host 一致
2)容器只能使用 Host 的 kernel,并且不能修改。
所有容器都共用 host的 kernel,在容器中没办法对 kernel 升级。如果容器对 kernel 版本有要求(比如应用只能在某个 kernel 版本下运行),则不建议用容器,这种场景虚拟机可能更合适。
4、镜像的分层结构(镜像层和容器层)
Docker 支持通过扩展现有镜像,创建新的镜像。
实际上,Docker Hub 中 99% 的镜像都是通过在 base 镜像中安装和配置需要的软件构建出来的。比如我们现在构建一个新的镜像,Dockerfile 如下:
① 新镜像不再是从 scratch 开始,而是直接在 Debian base 镜像上构建。
② 安装 emacs 编辑器。
③ 安装 apache2。
④ 容器启动时运行 bash。
构建过程如下图所示:
可以看到,新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层。
为什么 Docker 镜像要采用这种分层结构呢?
最大的一个好处就是:共享资源。
比如:有多个镜像都从相同的 base 镜像构建而来,那么 Docker Host 只需在磁盘上保存一份 base 镜像;同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享,我们将在后面更深入地讨论这个特性。
这时可能就有人会问了:如果多个容器共享一份基础镜像,当某个容器修改了基础镜像的内容,比如 /etc下的文件,这时其他容器的 /etc 是否也会被修改?答案是不会!修改会被限制在单个容器内。
这就是我们接下来要学习的容器 Copy-on-Write 特性。
可写的容器层(Copy-on-Write)
当容器启动时,一个新的可写层被加载到镜像的顶部。这一层通常被称作“容器层”,“容器层”之下的都叫“镜像层”。
所有对容器的改动--无论添加、删除、还是修改文件都只会发生在容器层中。只有容器层是可写的,容器层下面的所有镜像层都是只读的。
下面我们深入讨论容器层的细节。
镜像层数量可能会很多,所有镜像层会联合在一起组成一个统一的文件系统。如果不同层中有一个相同路径的文件,比如 /a,上层的 /a 会覆盖下层的 /a,也就是说用户只能访问到上层中的文件 /a。在容器层中,用户看到的是一个叠加之后的文件系统。
1)添加文件--在容器中创建文件时,新文件被添加到容器层中。
2)读取文件--在容器中读取某个文件时,并且容器层没有此文件,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后打开并读入内存。
3)修改文件--在容器中修改已存在的文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后修改之。
4)删除文件--在容器中删除文件时,Docker 也是从上往下依次在镜像层中查找此文件。找到后,会在容器层中记录下此删除操作。
只有当需要修改时才复制一份数据,这种特性被称作 Copy-on-Write。可见,容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改。这样就解释了我们前面提出的问题:容器层记录对镜像的修改,所有镜像层都是只读的,不会被容器修改,所以镜像可以被多个容器共享。
2、构建镜像
对于Docker用户来说,最好的情况是不需要自己创建镜像。几乎所有常用的数据库、中间件、应用软件等都有现成的Docker官方镜像或其他人和组织创建的镜像,我们只需要稍作配置就可以直接使用.
使用现成镜像的好处除了省去自己做镜像的工作量外,更重要的是可以利用前人的经验。特别是使用那些官方镜像,因为Docker的工程师知道如何更好地在容器中运行软件。
当然,某些情况下我们也不得不自己构建镜像, 比如
(1)找不到现成的镜像,比如自己开发的应用程序。
(2)需要在镜像中加入特定的功能,比如官方镜像几乎都不提供ssh。
所以本节我们将介绍构建镜像的方法。
同时分析构建的过程也能够加深我们对前面镜像分层结构的理解。
Docker 提供了两种构建镜像的方法:docker commit命令与Dockerfile构建文件。
1) docker commit
docker commit命令是创建新镜像最直观的方法,其过程包含三个步骤
• 运行容器。
• 修改容器。
• 将容器保存为新的镜像。
举个例子:在centos base镜像中安装vim并保存为新镜像。
第一步:运行容器,如图所示。
-it 参数的作用是以交互模式进入容器,并打开终端。9f937c7b4534 是容器的内部 ID。
第二步:修改容器,即安装vim
确认 vim 没有安装
安装vim
第三步:保存为新镜像
在新窗口中查看当前运行的容器
trusting_elgamal是Docker为我们的容器随机分配的名字。
执行docker commit命令将容器保存为镜像
[root@docker ~]# docker commit trusting_elgamal centos-with-vim
新镜像命名为centos-with-vim。查看新镜像的属性
从size上看到镜像因为安装了软件而变大了。从新镜像启动容器,验证vim已经可以使用
2) Dockerfile
Dockerfile是一个文本文件,记录了镜像构建的所有步骤。
官网:https://docs.docker.com/engine/reference/builder/
构建三步骤:
编写Dockerfile文件
docker bulid 命令构建镜像
docker run 依镜像运行容器示例
2.Dockerfile构建过程解析
docker执行docker的大致流程
1)docker从基础镜像运行一个容器
2)执行一条指令并对容器做出修改
3)执行类型 docker commit的操作提交一个新的镜像层
4)docker 在基于刚提交的镜像运行一个容器
5)执行dockerfile中按序执行直到所有指令都执行完成
3.Dockerfile常用指令
Dockerfile由一行行命令语句组成,并且支持#开头注释
1)构建指令用于构建image,其指定操作不会在运行image的容器执行
2)设置指令用于设置image的属性,其指定的操作将在运行image的容器中执行
一般dockerfile分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令
FROM(构建指令)
指定base镜像,必须指定且需要dockerfile其他指令的前面,后续的指令都依赖于该指定的镜像,
该命令格式有两种格式:
1)FROM <image>
2)FROM <image>:<tag>
MAINTAINER(构建指令)
设置镜像的作者,可以是任意字符串。用于将image的制作者相关的信息写入到image中 ,现在已经被“LABEL maintainer=”取代使用 docker inspect 命令输出相应的字段记录该信息
COPY(构建指令)
将文件从build context复制到镜像
COPY支持两种形式:
COPY src ... dest
COPY ["src",.."dest"]
ADD (构建指令)
与COPY类似,从build context 复制到镜像,如果 src 是归档文件(tar,zip,tgz等)文件会自动解压到test
ADD包含两种格式:
1.ADD <src> ...<test>
2.ADD ["src",... "test"]
ENV(构建指令)
设置环境变量,环境可被后面的指令使用,并在容器运行时保持
格式:ENV key value
例如:
ENV MY_VERSION 1.3
RUN apt-get install -y mypackage=$MY_VERSION
EXPOSE(设置指令)
格式:
EXPOSE port
EXPOSE [<port>/<protocol>...]
指定容器开放的端口,供互联网系统使用
可以指定TCP或UDP,默认时TCP
在启动容器的时候如果使用-P,Docker主机自动分配一个端口和容器端口映射
在启动容器的时候如果使用-p,则可以具体指定哪个宿主机端口和容器端口映射
VOLUME(设置指令)
格式:VOLUME ["<mountpoint>"]
将文件或目录声明为volume,使容器中的一个目录具有持久化存储数据的功能,该目录可以被容器本身使用,也可以共享给其他容器使用,
WORKDIR(设置指令)
格式:WORKDIR /etc/passwd
示例:
WORKDIR /a (这时工作目录为/a)
WORKDIR b (这时工作目录为/a/b)
WORKDIR c (这时工作目录为/a/b/c)
类似于cd命令切换到当前目录,为后面的 RUN, CMD, ENTRYPOINT, ADD 或 COPY 指令设置镜像中的当前工作目录。
RUN (构建指令)
在构建镜像过程中指定要执行的命令
CMD(设置指令)
容器启动时运行指令的命令
dockerfile 中可以有CMD指令,但只有最后一个生效
ENTRYPOINT(设置指令)
设置容器启动时运行的命令
dockerfile 中可以有多个ENTRYPOINT 指令,但只有最后一个生效
USER
USER指令用于指定容器执行程序的用户身份,默认时root用户
当服务不需要管理员权限时,通过该命令指定运行用户
格式:
USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group
示例:
USER www
HEALTHCHECK
容器健康状况检查命令
语法有两种:
1.HEALTHCHECK [OPTIONS] CMD command
2.HEALTHCHECK NONE
第一个功能是在容器内部运行一个命令来检查容器的健康状况
第二个功能是在基础镜像中取消健康检查命令
[OPTIONS]的选项支持以下三种选项:
--interval=duration 两次检查默认的时间间隔为30秒,间隔(s秒、M分钟、h小时),从容器运行起来开始计时interval秒(或者分钟小时)进行第一次健康检查
--timeout=DURATION
--retries=N 连续失败指定次数后,则容器被认为是不健康的,状态为unhealthy,默认次数是3
注意:HEALTHCHECK命令只能出现一次,如果出现了多次,只有最后一个生效
CMD关键字后面可以跟执行shell脚本的命令或者exec。CMD后面的命令执行完的返回值代表容器的运行状况,具体的返回值如下:
0: success - 表示容器是健康的
1: unhealthy - 表示容器已经不能工作
2: reserved - 保留值
ARG构建参数
格式:ARG <参数名>[=<默认值>]
ARG构建参数和ENV一样,都是设置环境变量,区别在于 ARG所设置的构建环境的变量,在容器中运行时不会存在这些变量,不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的。
LABEL
给镜像添加信息。使用docker inspect可查看镜像的相关信息
语法:
LABEL <key>=<value> <key>=<value> <key>=<value> ...
一个Dockerfile种可以有多个LABEL:
LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines.