前几天在 VPS(Virtual Private Servers) 上使用 Docker 部署一些自用服务。之前对 Docker 了解过一些,长时间不用容易遗忘,趁此再复习一下 Docker,记录在此备忘清单。

Docker

macOS 本地的话推荐 OrbStack,轻量、快速、简单易用。
Linux 服务器上安装照官方文档 Install Docker Engine 安装即可。

下面是在 Ubuntu 上安装 Docker Engine 的步骤(摘自官方文档,但改用了 apt 而非 apt-get)。

Install Docker Engine on Ubuntu

  1. Run the following command to uninstall all conflicting packages:

    1
    $ for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt remove $pkg; done
  2. 在新主机上首次安装 Docker Engine 前,需要配置下 Docker 的 apt 仓库:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # Add Docker's official GPG key:
    sudo apt update
    sudo apt install ca-certificates curl
    sudo install -m 0755 -d /etc/apt/keyrings
    sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
    sudo chmod a+r /etc/apt/keyrings/docker.asc

    # Add the repository to Apt sources:
    echo \
    "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
    $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
    sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
    sudo apt update
  3. Install the latest version of Docker packages:

    1
    $ sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  4. Verify that the Docker Engine installation is successful by running the hello-world image.

    1
    $ sudo docker run hello-world

Install Docker Compose Plugin

安装完 Docker Engine,继续安装 Docker Compose Plugin。

1
2
3
4
$ sudo apt install docker-compose-plugin

# Verify that Docker Compose is installed correctly by checking the version.
$docker compose version

Container

  • docker run ubuntu:latest ls -l: 执行一个最新版的 Ubuntu 容器,第一次执行这条命令的时候,由于本地还没有 Ubuntu 最新版的安装镜像,因此 Docker 会自动从 Docker Hub 上下载。ls -l 是在 Ubuntu 中执行的命令。
  • docker ps: 查看当前正在执行的容器
  • docker ps -a: 查看当前所有容器(包括正在执行的和已经结束的)
  • docker inspect [CONTAINER ID / NAMES]: 查看容器里都有哪些内容
    • 同时使用 jq 来格式化 JSON

  • docker rm [CONTAINER ID / NAMES]: 删除容器
  • docker rm $(docker ps -aq): 删除所有容器,-aq-a-q 的缩写,-q 是让 docker ps 命令只显示容器 ID 列表
  • docker run --rm ubuntu:latest ls -l: 每次执行完一个容器之后自动删除容器

  • docker run -it ubuntu:latest bash:“登录”到正在执行中的容器中,和容器里这个 bash 交互
    • -i 是 interactive,表示我们要和容器交互;
    • -t 是 tty,让 docker 创建一个虚拟终端,这样就能在屏幕上看到来自容器的控制台输出了
    • 执行 $ exit 可以离开 bash。Bash 执行结束了,容器也就结束了
  • docker start [CONTAINER ID]: 重新启动之前退出的容器,可以用 -i 参数以交互模式恢复容器的执行
  • docker stop [CONTAINER ID]: 停止一个在后台执行的容器

在容器中运行 Nginx

  1. 以交互模式启动一个执行 bash 的容器:docker run -it -p 8080:80 ubuntu:latest bash,这里,-p 参数可以让我们用 host_port:container_port 的格式指定容器内外的端口映射规则;
  2. 在 bash 中执行 apt update && apt install -y nginx
  3. 安装完成后,和普通的 Ubuntu 不同的是,Nginx 不会自动启动,需要手工执行一下 $ nginx 命令。 再执行 ps aux 命令,就能看到 Nginx 已经启动了;
  4. 然后就可以在浏览器中访问 http://localhost:8080 了。

通过 Volumn 共享文件

在启动容器的时候,使用 -v 参数,可以把 Host 上的某个目录映射到容器里。也可以使用多个 -v 参数,来给容器添加多个目录映射。

在 host 中,创建一个 /tmp/web 目录,并在其中添加一个 demo.html 文件:

1
2
$ mkdir /tmp/web
$ echo "This is a web page from host." > /tmp/web/demo.html

容器中的 Nginx 默认的 web 根目录是 /var/www/html。执行下面的命令启动 docker:

1
docker run -it -p 8080:80 -v /tmp/web:/var/www/html ubuntu:latest bash

重新在容器里运行 Nginx:

1
2
$ apt update && apt install nginx -y
$ nginx

然后在浏览器里就可以访问 http://localhost:8080/demo.html 了。

Image

  • docker images: 查看本地已经安装的 Docker 镜像
  • docker rmi ubuntu:latest: 使用 name:tag 的形式来删除指定镜像,也可以使用镜像 ID
  • docker rmi $(docker images -q): 使用镜像 ID 来删除镜像

构建你自己的 Docker 镜像

以上面在 Ubuntu 镜像中运行 Nginx 为例。

  1. docker diff a33bf65c9623: 查看容器中的每一个文件变化

  2. docker commit -a "Kilig" -m "Install Nginx" a33bf65c9623 kilig/nginx:0.1.0: 像 Git 中提交代码一样,去提交这些变化

    • -a 表示 Author,即提交者的姓名;
    • -m 表示 Message,即本次提交的注释;
    • a33bf65c9623,这是容器 ID,它表示了我们要制作的镜像最终的状态;
    • kilig/nginx:0.1.0,这是新镜像的名称,以及版本号。
  3. 重新执行 docker images,就能看到我们新创建的 nginx 镜像了

    但运行一下刚创建的容器 docker run -it -p 8080:80 kilig/nginx:0.1.0 nginx,容器执行一下就退出了。

    为什么会这样呢?这是因为当我们执行 nginx 命令的时候,会启动两类进程:首先启动的是作为管理调度的 master process,它继续生成实际处理 HTTP 请求的 worker process。默认情况下,master process 是一个守护进程,它启动之后,就会断掉和自己的父进程之间的关联,于是 Docker 就跟踪不到了,进而容器也就会退出了。因此,解决的办法,就是让 Nginx 的 master process 不要以守护进程的方式启动,而是以普通模式启动就好了。为此,我们得修改下 Nginx 的配置文件。

    修正:

  4. 用我们新创建的镜像,启动一个执行 Bash 的容器:

    1
    $ docker run -it kilig/nginx:0.1.0 bash
  5. 修改这个容器中 Nginx 的配置文件,关掉守护进程模式:

    1
    2
    $ echo "daemon off;" >> /etc/nginx/nginx.conf
    $ exit
  6. 重新提交一次:

    1
    $ docker commit -a "Kilig" -m "Turn off the daemon mode" 153a7b934628 kilig/nginx:0.1.1
  7. docker history kilig/nginx:0.1.1: 可以查看我们的提交记录

  8. 重新执行下面的命令启动 Nginx:docker run -it -p 8080:80 -d kilig/nginx:0.1.1 nginx
    这里我们使用了 -d 参数使容器在后台执行。

使用 Dockerfile 自动化镜像构建

使用 Dockfile 把打造镜像的过程放在一个脚本里,避免易错的手工打造,提高效率,也便于分享。
Dockerfile 是 docker 默认会使用的文件名,当然也可以在执行 docker run 的时候使用 -f [filename] 参数来指定其他文件名,不过一般都保持默认的。

  1. 新建一个 /tmp/nginx 目录,在其中创建一个叫做 Dockerfile 的文件。

  2. 在 Dockerfile 中,添加下面内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    FROM ubuntu:latest

    LABEL maintainer="kilig <kilig@kilig.studio>"

    RUN apt update && apt install nginx -y \
    && apt clean \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
    && echo "daemon off;" >> /etc/nginx/nginx.conf

    CMD ["nginx"]

    在上面的文件,所有大写字母,都是 Dockerfile 中的命令。其中:

    • FROM 指的是构建新镜像的基础,这里我们要基于 ubuntu:latest 这个镜像定制自己的镜像;
    • LABEL 用于定义一些容器的 metadata;
    • RUN 用于设置构建新镜像的各种动作。在此一共执行了 4 个动作,分别是:安装 Nginx、清理下载安装包、清除临时文件、关闭 Nginx 守护进程模式。这里我们使用了 && 把这 4 个动作写成了一个 RUN 命令,而没有使用不同的 RUN 命令分别执行这些动作。作为一个最佳实践,在构建一个新镜像时,我们应该尽可能减少 RUN 命令的使用次数,这样可以减少镜像的大小;
    • CMD 用于设置容器启动时默认执行的命令,显然,我们就是要启动 nginx。
  3. 执行下面的命令构建镜像:

    1
    $ docker build -t kilig/nginx:0.1.2 .

    这里:

    • 当我们执行 docker build 的时候,docker 就会默认在当前目录中,查找一个叫做 Dockerfile 的文件名作为构建脚本。或者我们也可以通过 -f filename 的形式指定成其他文件;
    • -t 用于设置新镜像的名称和 TAG
    • . 用于设置构建镜像时的上下文环境,这个环境不一定是当前目录。在 Dockerfile 中,所有的相对路径都会基于这个上下文环境指定的目录。
  4. 执行下面的命令启动镜像:

    1
    $ docker run -it -p 8080:80 kilig/nginx:0.1.2

    由于 Dockfile 中通过 CMD 命令设置了容器启动的默认命令,在启动的时候,就可以不用再设置了。

更多 Dockerfile 中的命令

  • ARG 用于定义在构建镜像时使用的变量;
  • ENV 用于定义在构建镜像和执行容器时使用的环境变量;
    • 执行容器的时候,可以使用 -e 参数,修改环境变量的值

提交镜像到 Docker Hub

1
2
3
4
5
# 提交镜像到 Docker Hub
$ docker push kilig/nginx:0.1.2

# 把镜像下载回来
$ docker pull kilig/nginx:0.1.2

Network

  • docker network -h 查看和网络相关的命令的用法。

Docker 主要支持两种形式的网络,分别是:

  • bridge mode:这就是我们在单个 host 上执行多个容器时使用的网络,同时,也是 Docker 默认的网络类型;
  • overlay mode:这是在多台 hosts 上部署复杂网络结构时使用的网络模式。

使用 Docker 中的网络:

  1. 执行下面的命令,创建一个 Docker 网络:

    1
    $ docker network create --driver=bridge kilig-net

    Docker 会给我们返回一个表示该网络的哈希值。

  2. 把所有相关的容器在启动的时候,通过 --network 选项,加入到 kilig-net 中:

    1
    $ docker run --network=kilig-net -p 80:80 -it --rm kilig/nginx:0.1.2

Volume

1
$ docker run --rm -it -v /data busybox

busybox 是一个极简的 Linux,非常适合用来试验一些功能。这里我们使用 -v 的时候,只指定了一个目录 /data,然后就可以把 Linux 中一些有写操作的目录,符号链接到 /data

那么使用 -v /data 的时候,实际的文件究竟存在了哪呢?

  1. 执行 docker volume ls,可以看到 Docker 给这个 data volume 分配了一个唯一 ID:VOLUME NAME
  2. 执行 docker volume inspect [VOLUME NAME],在输出的信息中 Mountpoint 就是 /data 容器实际保存的目录。

但在 Mac 或者 Windows 上,这个目录就并不是直接创建在 Host 的文件系统中的,而是在 Docker 创建的一个虚拟层上的。为了看到这个 volume 对应的物理文件夹:

  1. 我们把 Mac 的 / 映射到容器里的 /vm-data 目录:$ docker run --rm -it -v /:/vm-data busybox
  2. 在容器里执行 ls /vm-data/var/lib/docker/volumes/,就可以看到 /data volume 实际存储的位置了。

Docker Compose

使用 Docker Compose 一键部署开发环境。

  • docker-compose.yml 是编写的构建脚本,文件名是固定的;
  • 同级目录下 .env 文件用来定义环境变量;

docker-compose.yml 所在目录下,执行 docker compose build 构建镜像,再执行 docker compose up -d,就可以启动服务并在后台运行了。

需要注意的是:docker 并不一定会按照 docker-compose.ymlservices 的顺序启动,所以不能依赖这个关系。

另见