Docker 镜像

我们都知道,操作系统分为内核和用户空间。对于 Linux 而言,内核启动后,会挂 载 root 文件系统为其提供用户空间支持。而 Docker 镜像(Image),就相当于 是一个 root 文件系统。比如官方镜像 ubuntu:18.04 就包含了完整的一套 Ubuntu 18.04 最小系统的 root 文件系统。

Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资 源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境 变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。

分层存储

因为镜像包含操作系统完整的 root 文件系统,其体积往往是庞大的,因此在 Docker 设计时,就充分利用 Union FS 的技术,将其设计为分层存储的架构。所以 严格来说,镜像并非是像一个 ISO 那样的打包文件,镜像只是一个虚拟的概念,其 实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系 统联合组成。

镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生 改变,后一层上的任何改变只发生在自己这一层。比如,删除前一层文件的操作, 实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容 器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。因 此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西, 任何额外的东西应该在该层构建结束前清理掉。

分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的 镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜 像。

docker 容器

镜像( Image )和容器( Container )的关系,就像是面向对象程序设计中的 类 和 实例 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被 创建、启动、停止、删除、暂停等。

容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的 独立的 命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、 自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环 境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容 器封装的应用比直接在宿主运行更加安全。也因为这种隔离的特性,很多人初学 Docker 时常常会混淆容器和虚拟机。

前面讲过镜像使用的是分层存储,容器也是如此。每一个容器运行时,是以镜像为 基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而 准备的存储层为 容器存储层。

容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此, 任何保存于容器存储层的信息都会随容器删除而丢失。

使用 docker 镜像

启动镜像

$ docker run -it --rm ubuntu:18.04 bash
  • -it :这是两个参数,一个是 -i :交互式操作,一个是 -t 终端。我们这里打算进入 bash 执行一些命令并查看返回结果,因此我们需要交互式终端。
  • --rm :这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动 docker rm 。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用 --rm 可以避免浪费空间。
  • ubuntu:18.04 :这是指用 ubuntu:18.04 镜像为基础来启动容器。
  • bash :放在镜像名后的是 命令,这里我们希望有个交互式 Shell,因此用的是 bash 。

列出镜像

 docker image ls

使用 Dockerfile 定制镜像

Dockerfile

$ mkdir mynginx
$ cd mynginx
$ touch Dockerfile

其内容为:

FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

FROM 就是指定 基础镜像,因此一个 Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令。

RUN 指令是用来执行命令行命令的

构建镜像

在 Dockerfile 文件所在目录执行:

docker build -t nginx:v3 .

镜像构建上下文(Context)

当构建的时候,用户会指定构建镜像上下文的路径, docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上 传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。

如果在 Dockerfile 中这么写:

COPY ./package.json /app/

这并不是要复制执行 docker build 命令所在的目录下的 package.json ,也不是复制 Dockerfile 所在目录下的 package.json ,而是复制上下文 (context) 目录下的 package.json 。

刚才的命令 docker build -t nginx:v3 . 中的这个 . ,实际上是在指定上下文的目录, docker build 命令会将该目录下的内容打包交给Docker 引擎以帮助构建镜像。

Dockerfile 指令详解

  • COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置
  • CMD 指令就是用于指定默认的容器主进程的启动命令的。(Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。)

    Docker 不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机 面那样,用 systemd 去启动后台服务,容器内没有后台服务的概念。 一些初学者将 CMD 写为: CMD service nginx start 然后发现容器执行后就立即退出了。甚至在容器内去使用 systemctl 命令结果 却发现根本执行不了。这就是因为没有搞明白前台、后台的概念,没有区分容器和 虚拟机的差异,依旧在以传统虚拟机的角度去理解容器。 对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主 进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。 而使用 service nginx start 命令,则是希望 upstart 来以后台守护进程形式启 动 nginx 服务。而刚才说了 CMD service nginx start 会被理解为 CMD [ "sh", "-c", "service nginx start"] ,因此主进程实际上是 sh 。那么当 service nginx start 命令结束后, sh 也就结束了, sh 作为主进程退出 了,自然就会令容器退出。 正确的做法是直接执行 nginx 可执行文件,并且要求以前台形式运行。比如: CMD ["nginx", "-g", "daemon off;"]

操作 Docker 容器

启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态( stopped )的容器重新启动。

新建并启动

所需要的命令主要为 docker run 。 例如,下面的命令输出一个 “Hello World”,之后终止容器。

$ docker run ubuntu:18.04 /bin/echo 'Hello world'
Hello world

这跟在本地直接执行 /bin/echo 'hello world' 几乎感觉不出任何区别。 下面的命令则启动一个 bash 终端,允许用户进行交互。

$ docker run -t -i ubuntu:18.04 /bin/bash
root@af8bae53bdd3:/#

其中, -t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上, -i 则让容器的标准输入保持打开。

启动已终止容器

可以利用 docker container start 命令,直接将一个已经终止的容器启动运行。

后台运行

更多的时候,需要让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时,可以通过添加 -d 参数来实现。

终止容器

可以使用 docker container stop 来终止一个运行中的容器。

进入容器

在使用 -d 参数时,容器启动后会进入后台。

某些时候需要进入容器进行操作,包括使用 docker attach 命令或 docker exec 命令,推荐大家使用 docker exec 命令

docker 数据管理

数据卷

数据卷 是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多

有用的特性: 数据卷 可以在容器之间共享和重用 对 数据卷 的修改会立马生效 * 对 数据卷 的更新,不会影响镜像

创建一个数据卷

$ docker volume create my-vol

在用 docker run 命令的时候,使用 --mount 标记来将 数据卷 挂载到容器里。在一次 docker run 中可以挂载多个 数据卷 。

下面创建一个名为 web 的容器,并加载一个 数据卷 到容器的 /webapp 目录。

$ docker run -d -P \
--name web \
# -v my-vol:/wepapp \
--mount source=my-vol,target=/webapp \
training/webapp \
python app.py

数据卷 默认会一直存在,即使容器被删除

挂载主机目录

挂载一个主机目录作为数据卷

使用 --mount 标记可以指定挂载一个本地主机的目录到容器中去。

$ docker run -d -P \
--name web \
# -v /src/webapp:/opt/webapp \
--mount type=bind,source=/src/webapp,target=/opt/webapp \
training/webapp \
python app.py

其他

docker run image [COMMAND]

  • image 是必选的
  • COMMAND 是可选的
  • 如果没有 COMMAND,dockerfile 里面有 CMD,会执行 CMD