Docker Guide
在现代软件开发和部署中,Docker 作为一种容器技术,极大地简化了运行环境的搭建和应用程序的部署。与传统的虚拟机(VM)不同,Docker 容器利用宿主机的操作系统内核,而不是虚拟出一整套硬件和操作系统,从而实现轻量级和高效的资源利用。
架构和原理
- 架构
镜像(Image)是包含应用程序和运行环境(依赖库、配置等)的只读模板。容器(Container)是镜像运行的示例,具备独立的文件系统、网络和进程空间。
Docker 是典型的 C/S 架构,Docker 引擎(Docker Engine)由 Docker Cli 和 Docker Daemon 构成。Docker Daemon(dockerd 守护进程)负责处理用户请求、管理容器生命周期、镜像存储、网络和存储等资源,是常驻后台的核心服务。Docker Cli 负责解析 command 命令,并通过 Docker Engine 提供的 Restful API 发送请求给 Docker Daemon,Docker Daemon 收到命令后会根据命令创建和管理各个容器。
- 原理
Docker 通过 Linux 内核的 Namespace(命名空间)Cgroup(控制组),实现进程隔离和资源限制。它们让容器能够像独立的系统一样运行,但仍然共享宿主机的内核。
Namespace 用于隔离不同进程的系统资源,让每个容器看起来像是运行在一个独立的系统环境中,而实际上它们仍然共享同一个宿主机的内核。常见的 Namespace 类型有,PID(进程)、NET(网络)、IPC(进程间通信)、UTS(主机名)、MNT(挂载点)、USER(用户)。
Cgroup 用于限制、分配和监控容器使用的系统资源,例如 CPU、内存、磁盘 I/O 和网络带宽,防止某个容器独占所有资源,影响宿主机或其他容器的运行。
安装
Windows 11
Windows 下的 Docker Desktop 运行于 WSL 上,会默认安装 WSL,并创建两个仅供 Docker 使用的 Linux 发行版(distro): docker-desktop 和 docker-desktop-data。其中,docker-desktop 用于运行 Docker engine,docker-desktop-data 用于存储 containers 和 images。
1 | wsl --list -v # 查看已安装 Linux 发行版 |
WSL 发行版使用 ext4.vhdx 虚拟硬盘文件进行存储,在 %LOCALAPPDATA%/Docker/wsl 目录下。Docker Desktop 创建的 Linux 发行版也存储在此虚拟存储硬盘中,其中,docker-desktop 存储在 distro/ext4.vhdx,docker-desktop-data 存储在 data/ext4.vhdx。
1 | # 注销卸载这两个发行版 |
- 迁移 docker-desktop-data
可将 docker-desktop-data 迁移至其它盘或其它机器。
1 | # 导出 |
1 | # 导入 |
注意,在导出或导入前,需使用 wsl --shutdown 关闭所有 WSL 发行版,以确保虚拟磁盘未被使用。
配置
Docker 的配置文件为 daemon.json,在 C:\Users\用户名\.docker 目录下,可以直接编辑此文件,也可以在 Docker Desktop 中修改。
1 | { |
基本命令
1 | # 镜像仓库 |
注:上面命令中 contain 可以是 contain-id 也可以是 contain-name。
docker run
1 | –-name:容器名,不指定则采用默认 |
执行 docker run 时,如果本地没有镜像,则会自动 pull 拉取。
注意:$(pwd)、`pwd`、$PWD 在 PowerShell 中返回的是 Windows 中反斜杠路径风格 D:\project\docker-data\wordpress,Git Bash 中虽然能返回左斜杆,但是路径不对,需要再加一个 /,即 //d/project/docker-data/wordpress。
1 | # Windows Git Bash 中添加 /,PowerShell 不用 |
实践中为了方便管理、复用以及自动化,常将 docker run 保存为 *.sh *.ps1 Bash 或 PowerShell 脚本。
1 | bash run-mysql.sh or powershell run-mysql.ps1 |
1 | run-nginx.sh |
docker build
docker build 命令用于根据 Dockerfile 创建 Docker 镜像。用来实现基础镜像的扩展和应用程序的发布。在实践中,通过 docker build 将应用程序代码(COPY)、依赖(RUN)和运行时环境(FROM)打包成一个镜像,方便应用程序的部署,还可以与 CI/CD 配合使用,实现自动化的镜像构建和部署。
1 | docker build -t image-name:tag dockerfile-path # -t 指定要创建的目标镜像名及 tag(tag 默认为 latest),dockerfile-path 为 Dockerfile 所在目录 |
Dockerfile
Dockerfile 是 docker build 的描述文件,文本内容包含了一条条构建镜像所需的指令。常用指令有:
1 | FROM: 指定基础镜像 |
注:COPY 和 WORKDIR 指令会自动创建目录,无需 RUN mkdir -p /test。
以打包部署前端应用程序镜像为例,其 Dockerfile 和 default.conf 配置如下所示:
1 | FROM nginx:alpine |
1 | server { |
1 | docker build -t geki:1.0.0 . # 使用当前目录的 Dockerfile 构建镜像 |
构建缓存 Build Cache
Docker 镜像是分层构建的,每个 Dockerfile 指令(如 FROM、COPY、RUN 等)都会创建一个新的镜像层(Layer),这些层是只读的,且会被缓存。首次构建时,所有层均会执行,生成缓存,后续构建,如果 Docker 发现某一层已经构建过,并且前面的层没有发生变化,就会直接复用缓存,避免重复执行。
1 | FROM node:18-alpine |
注意:设置 .dockerignore 文件,否则 COPY . . 会拷贝 node_modules,不但慢,而且不利于缓存。
- 优化缓存利用的原则
固定不变的内容放前面,容易变化的内容放后面,避免缓存层失效:
1 | # 先安装依赖,利用缓存 |
合并 RUN 命令,减少层数:
1 | RUN apt-get update && apt-get install -y curl git |
利用 .dockerignore 文件,避免不必要的文件影响 COPY 指令的缓存:
1 | node_modules |
多阶段构建
在 Dockerfile 中,多阶段构建(multi-stage build)是指使用多个 FROM 语句来构建镜像,每个 FROM 代表一个独立的构建阶段(Stage),主要意义是减少最终镜像的体积,并确保最终运行环境只包含必要的文件。AS <stage> 定义阶段名,,--from=<stage> 用来跨阶段复制文件。
1 | # 构建阶段 |
数据卷
Docker Volumes (数据卷) 用于解决容器的数据持久化和数据共享(宿主机到容器,容器间)问题。Docker Volumes 有三种类型:Volumes (卷)、Bind mounts (绑定挂载)、tmpfs mounts (临时挂载)
- Volumes
Volumes 分为命名卷(Named Volumes)和匿名卷(Anonymous Volumes)。Linux 下默认存储在 /var/lib/docker/volumes/<卷名>,Windows 下的 Docker Volumes 则存储在 WSL 2 虚拟机的文件系统中。
命名卷的生命周期独立于容器,即使容器被删除,卷仍然存在,直到手动删除 (docker volume rm),而匿名卷的生命周期与容器绑定,当容器删除时,匿名卷也会被删除,除非使用 docker run --rm 运行临时容器。
1 | # wsl |
匿名卷不指定卷名称,仅指定容器内的挂载路径,Docker 会自动分配随机卷名。-v 容器目录/文件
1 | # 匿名卷挂载(仅指定容器内路径) |
1 | # 创建命名卷 |
命名卷适合数据持久化(比如数据库存储、日志存储)和跨容器数据共享(比如 Nginx 直接托管 Nuxt 静态资源,做动静分离)。“卷”虽然初始化配置稍微复杂一点,灵活性也不如“绑定挂载”,但它不依赖宿主机的文件系统,直接由 Docker 管理,具有更高的可移植性、安全性和 I/O 性能。
- 绑定挂载
直接将宿主机的目录或文件挂载到容器中 -v 主机目录/文件:容器目录/文件。绑定挂载直接依赖宿主机的目录,Docker 不会管理它的生命周期,数据不会随容器删除而丢失。
1 | docker run -d -v /home/user/data:/app/data my-image |
绑定挂载灵活,简单,但依赖宿主机文件系统(ext4、xfs、NTFS…),可移植性差,如果主机文件发生变化,会影响容器运行,而且由于额外的抽象层,I/O 性能也可能不如 Docker 直接管理的卷。
绑定挂载通常用在本地开发环境中将本地代码目录挂载到容器以及共享配置(比如共享宿主机 Nginx 配置),实现热更新,方便开发调试。在生产环境中,应用程序代码(如 NodeJS、Nuxt 的业务代码)和配置(如 Nginx 配置)会通过自定义构建镜像,COPY 进镜像中,而不是通过数据卷挂载。数据卷通常用于存储动态数据,如数据库、日志等,而非静态的应用程序代码。
1 | # 开发环境(Windows Git Bash 加 /) |
1 | # 生产环境 |
注意:挂载主机目录,docker run -v 相对路径是相对于命令执行的目录,即当前 shell 终端所在的路径(pwd),docker-composer.yml 中相对路径则相对于 docker-compose.yml 文件所在的目录。
- 临时挂载
只适用于 Linux,数据存储在内存中,不会写入磁盘,容器停止后数据会丢失。适用于存储临时数据(如 session、缓存)。
1 | docker run -d --tmpfs /app/data:rw,size=64m my-image |
TODO:Docker Volumes 数据的迁移、备份、恢复
网络
Docker 安装完成后会自动创建三个网络,其中 name 为 bridge 的网络使用 bridge 驱动,是容器的默认网络。
1 | C:\Users\tracy>docker network ls |
- 创建自定义网络
1 | docker network create [--driver=<bridge | host | overlay | ipvlan| macvlan | none>] [--gateway=<gateway>] [--subnet=<subnet>] [network-name] |
Docker 的几种网络模式:
1 | bridge 桥接模式,是默认的网络驱动程序,用于多个容器在同一个宿主机上通信 |
默认情况下,Docker 容器会使用 bridge 网络,各个容器可以通过桥接网络(即 docker0 网桥)访问彼此,但只有通过明确的链接或自定义网络,容器才能知道彼此的存在。
1 | # 创建自定义网络 |
- 指定自定义网络
1 | # 创建一个桥接类型的网络 |
1 | # 为已存在的容器指定网络 |
1 | # 使用 docker-compose 给一组容器指定网络 |
Docker Compose
Docker Compose 是 Docker 提供的单机容器编排工具,用来定义和运行多个容器。Docker Compose 使用一个名为 docker-compose.yml 的 YAML 文件来配置应用程序的服务,这个文件中定义了一系列 docker volume、docker network、docker build 和 docker run 的组合,从而使得管理和编排多个容器变得更加方便。
docker-compose.yaml 配置说明:
1 | service-name: |
docker-compose 常用命令:
1 | docker compose version # docker-compose 版本 |
常用的 docker-compose,lnmp, wordpress,更多查看此项目 awesome-compose。
私有镜像仓库
1 | # 启动私有镜像仓库 |
1 | # 构建镜像 |
注:registry-domain 不能带 http:// 或 https://,另外,直接在 docker build 命令中指定镜像的完整名称和标签(比如,docker build -t localhost:5000/tracyblog:1.0.0 .),这样在构建镜像时就已经给镜像打上了标签,docker tag 步骤可省略。
1 | # 验证镜像 |
- 用户认证
1 | # 使用 htpasswd 工具生成认证文件 |
注意:Windows PowerShell 中使用 > 重定向符号时,默认会将输出保存为 UTF-16 LE (带 BOM) 编码。而 htpasswd 工具生成的密码文件要求是 ASCII 或 UTF-8 (无 BOM) 编码。PowerShell 中应使用以下方式生成。
1 | docker run --rm --entrypoint htpasswd httpd:2 -Bbn abc 123 | Set-Content -Encoding ASCII auth/htpasswd |
1 | # 运行带有身份验证的私有 Registry |
注意:上述命令在 Windows Git Bash 上执行时,需添加 /,即 /${PWD} 或 /$(pwd)。另外,Git Bash 会自动将 REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd 转为 REGISTRY_AUTH_HTPASSWD_PATH=C:/Program Files/Git/auth/htpasswd,改为 ./auth/htpasswd 则不转换。
1 | docker run -d \ |
1 | # 验证环境变量设置 |
访问 http://localhost:5000/v2/、curl -u abc:123 http://localhost:5000/v2/,或者 docker login 测试登录。
docker-compose.yml 是更好的选择,不用考虑 Windows 路径规范和自动路径转换的问题。
1 | version: '3.8' |