Docker体系结构和Docker镜像

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.


版权声明:本文为w1206507055原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。