跳转至

八:Docker

本文目前尚未完稿,存在诸多未尽章节且未经审阅,不是正式版本。

导言

「容器」,是近年来非常热门的一个概念。它通过操作系统内核提供的隔离技术,实现轻量级的虚拟化环境。目前,它在软件的开发、部署等方面有着非常广泛的应用。

而 Docker,是 Linux 容器技术中的代表性软件,它为用户提供了方便的接口来创建、使用 Linux 容器。下面,就让我们简单地入门一下 Docker。

为什么使用 Docker?

Docker 能够利用 Linux 内核的容器特性,隔离出一个轻便的环境来运行程序。这有什么意义呢?试想以下这些情况:

  • 你运行的 Linux 发行版很老,而你需要运行一个给新版本的 Linux 发行版,或者完全不同的 Linux 发行版设计的程序。
  • 你和朋友在设计一个大型的程序,而因为你们配置的环境不同,有时候在某个人的机器上正常运行的程序,在另一台机器上没法正常运行。
  • 你希望在多台服务器上部署一个项目,但是项目需要非常复杂的配置,一个一个配置服务器的成本非常大。
  • …………

Docker 就可以帮助解决这些问题。它可以快速配置不同的环境(比如说,通过 Docker,你可以在 Ubuntu 上使用 CentOS 的环境),部署应用。

安装 Docker

Docker 可以在 Windows, Linux 和 macOS 上安装。下面我们讨论内容都基于 Docker 免费的社区版本。

在 Windows 或 macOS 上安装

上面提到,Docker 使用了 Linux 内核的容器特性,它依赖于 Linux。所以在 Windows 和 macOS 上,Docker 不得不通过虚拟 Linux 内核的方式来完成任务。其提供了一套被称为 Docker Desktop 的软件来帮助在 Windows 和 macOS 上配置 Docker。直接从官网下载即可。

Docker Desktop on Windows 的环境要求

Docker Desktop on Windows 要求系统为 64 位的 Windows 10 专业版,硬件支持 Hyper-V 虚拟化且 Hyper-V 已经开启。如果使用 Windows 20H1 之前的版本且开启 Hyper-V 的情况下,如 VirtuaBox 和 VMware Workstation 等虚拟机软件无法正常使用;在 20H1 之后,虚拟机软件也需要升级到对应的版本才能与 Hyper-V 协同工作。如果环境要求无法达到,可以安装老版本的 Docker Toolbox on Windows

Windows 容器

你可能会搜索到,Docker 也支持「Windows 容器」。是的,在新版本(1607 之后)的 Windows 10 中,Windows 内核支持 Windows 容器。可以在这样的容器中运行 Windows 程序。如果你感兴趣,可以阅读微软的 Containers on Windows DocumentationDocker Windows Containers 的介绍。这样的容器无法运行 Linux 程序,下面也不会涉及到。

在 Linux 上安装

各大发行版的软件源包含 Docker,也可以跟从官方文档,安装其提供的 Docker 社区版本。

docker, docker.io 和 docker-ce

首先,在 Debian/Ubuntu 上,不要运行 sudo apt install docker。现在,这里安装的 docker 是一个系统托盘程序,和本章的 Docker 没有任何关系

docker.io 是由 Debian/Ubuntu 维护的 Docker 版本,和软件源里的其他程序一样,它比官方的最新版本要稍微老一些。它所依赖的程序库由 Debian/Ubuntu 软件源中的其他软件包提供。

docker-ce 是由 Docker 官方维护的。它依赖的程序库都被打包在了这个包中。本章中,可以安装这两个版本中的任意一个。

不要在 WSL1 上安装 Docker

WSL1 在 Windows 上提供了方便的 Linux 环境。但很遗憾,Docker 的核心服务无法在 WSL1 上运行,直接安装是无法使用的。虽然可以把 WSL 中的 Docker 的命令行工具连接到 Docker for Windows 的核心服务上,但是比较麻烦,这里不推荐这样做。

如果你正在使用 WSL2,可以安装 Docker,并且使用 Windows 下的 Docker Desktop 进行管理,详细信息可阅读 Using Docker in WSL 2

配置 Registry Mirror(可选,推荐)

此段落需要编辑,添加可用的 Registry Mirror。

Docker 默认从 Docker Hub 上拖取所需要的镜像。但由于网络原因,拖取的过程可能会比较慢。幸运的是,一些服务在中国提供了 Docker Hub 的镜像,微软 Azure 和网易云就是其中两个。

为了使用微软 Azure 和网易云的 Docker Hub 镜像,在 Debian/Ubuntu 上,可以编辑 /etc/docker/daemon.json 文件(如果文件不存在,请新建一个),写入以下内容。

{
  "registry-mirrors": [
    "https://reg-mirror.qiniu.com",
    "https://hub-mirror.c.163.com"
  ]
}

使用 sudo systemctl restart docker 命令重启 Docker 服务后,再次运行 docker info 命令,可以看到如下输出:

 Registry Mirrors:
  https://reg-mirror.qiniu.com/
  https://hub-mirror.c.163.com/

如果你看到了上面的输出,说明你的 Docker Registry Mirror 已经配置好了。

使用 Hello World 测试 Docker 安装

Docker 官方提供了最精简的 hello-world 镜像,可以用来验证 Docker 安装是否正确、是否正常工作。尝试运行 docker run --rm hello-world 看看吧。

$ docker run --rm hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

如果你看到了像上面这样的输出,说明你安装的 Docker 已经一切准备就绪,可以使用了。

使用 Docker 容器

接下来我们来尝试几个例子,体验 Docker 环境的独立性与易用性。

以下内容均为草稿,亟待扩充,不是正式内容

在 Ubuntu 容器中使用 shell

  • docker run -it --rm ubuntu

这里,--rm 代表容器停止运行(退出)之后,会被立刻删除。

-it 是为了获得可交互的 Shell 所必须的。-i 会将容器的 init(主进程,这里是 /bin/bash)的标准输入与 docker 这个程序的标准输入相连接;而 -t 会告知主进程输入为终端(TTY)设备。

在执行以上命令之后,你会获得一个 Ubuntu 20.04 的容器环境,退出 Shell 之后容器就会被销毁。

如果没有加上 --rm,退出后可以使用 docker ps -a 查看系统中所有的容器。

$ sudo docker ps -a
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                         PORTS               NAMES
39d8ef1d4acf        ubuntu                "/bin/bash"              6 seconds ago       Exited (0) 3 seconds ago                           gracious_brahmagupta

之后使用 docker start 启动容器。

$ sudo docker start -ai 39d
[email protected]:/#

-a 代表连接输出以及信号。这里不需要输入完整的 ID,只需要前几位即可。

如果忘记加上了参数直接启动,也可以使用 docker attach 将容器的主进程的输入输出接上。

$ sudo docker attach 39d
[email protected]:/#

docker exec 也可以完成相似的事情:它可以在容器中执行指定的命令(当然也包括 Shell 了)。

$ sudo docker exec -it 39d bash
[email protected]:/#

由于 docker exec 创建的进程不是主进程,退出后容器也不会退出,适合需要调试容器的场合。

docker start 相对应,docker stop 可以关闭一个容器,docker rm 可以删除一个容器。

$ sudo docker stop 39d
39d
$ sudo docker rm 39d
39d

在 Python 容器中使用 Python 命令行

  • docker run -it --name python3 python

与上面的例子类似,执行之后会获得一个 Python 3 最新版本的环境。这里我们通过 --name 将创建的容器命名为 python3。之后的容器操作中我们就不需要查询容器 ID,直接使用 python3 代表这个容器即可。

在 MkDocs 容器中构建本书

  • 从 GitHub 上获取本书源码:git clone https://github.com/ustclug/Linux101-docs.git
  • docker run --rm -v ${PWD}/Linux101-docs:/docs -p 8000:8000 squidfunk/mkdocs-material

在执行完成之后,可以使用浏览器访问本地的 8000 端口,以查看构建结果。

这里多出了两个参数:

  • -v: 代表将本地的文件(夹)「挂载」(实际是 bind mount)到容器的对应目录中(这里是 /docs)。注意这个参数只接受绝对路径,所以这里读取了 PWD 这个变量,通过拼接的方式拼出绝对路径。
  • -p 8000:8000: 代表将容器的 8000 端口暴露在主机的 8000 端口上,否则容器外部访问不了 8000 端口。
  • 另外,我们不需要在终端中与容器中的进程进行交互,所以没有设置 -it 参数。

构建自己的 Docker 镜像

手工构建镜像

  • docker exec 进去把东西准备好,然后 docker commit

使用 Dockerfile 自动化构建

Dockerfile 是构建 Docker 镜像的标准格式,下面会举一些例子。在举完例子之后,我们会基于这些例子简单介绍 Dockerfile 的语法。

构建简单的交叉编译1环境

这个例子尝试使用 Debian 仓库中的 RISC-V 交叉编译工具链与 QEMU 模拟器构建一个简单的用于交叉编译的环境。

FROM debian:sid-20201117

RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list && \
    apt update && apt install -y gcc-10-riscv64-linux-gnu g++-10-riscv64-linux-gnu libc6-dev-riscv64-cross \
                                 binutils-riscv64-linux-gnu libstdc++-10-dev-riscv64-cross \
                                 qemu-system-misc qemu-user-static qemu-user binfmt-support \
                                 fish vim --no-install-recommends
RUN mkdir /workspace/

WORKDIR /workspace/
ENV QEMU_LD_PREFIX=/usr/riscv64-linux-gnu/

CMD ["fish"]

通过使用 docker build,我们可以构建出镜像。

sudo docker build -t riscv-cross:example .

-t riscv-cross:example 代表为这个镜像打上 riscv-cross:example 的标签。构建完成后,使用 docker run 执行即可:

$ sudo docker run -v ${PWD}/workspace:/workspace -it riscv-cross:example
Welcome to fish, the friendly interactive shell
Type `help` for instructions on how to use fish
[email protected] /workspace# vim helloworld.c
[email protected] /workspace# riscv64-linux-gnu-gcc-10 helloworld.c
[email protected] /workspace# qemu-riscv64 ./a.out 
Hello, world!

在生产环境中运行使用 Flask 编写的简单网站

Flask 是一个知名的 Python web 框架。

FROM tiangolo/uwsgi-nginx-flask:python3.8

RUN pip3 config set global.index-url https://mirrors.bfsu.edu.cn/pypi/web/simple
RUN pip3 install pyopenssl

COPY ./app /app

尽量减少 Docker 镜像的层数

TBA

使用 Docker Compose 自动运行容器

子项目?


  1. 交叉编译:指在某个平台上编译出另一个平台的程序。例如在 Linux 上使用 MinGW 工具链编译 Windows 程序即为一个交叉编译的例子。