Docker 初学者指南:容器化变得简单

· 12分钟阅读

目录

什么是 Docker?

Docker 是一个平台,可以让你将应用程序及其依赖项打包到轻量级、可移植的容器中。可以把容器想象成一个小型的、自包含的盒子,里面装有你的应用运行所需的一切:代码、运行时、库和系统工具。再也不会出现"在我的机器上可以运行"的问题了。

在 Docker 出现之前,部署软件意味着手动安装依赖项、配置服务器,并祈祷不会出现冲突。你需要花费数小时设置 Python 版本、Node.js 包、数据库驱动程序和系统库。然后你还要在预发布和生产服务器上重复这一切,祈祷配置能够匹配。

Docker 通过确保你的应用在任何地方都以相同方式运行来消除这种混乱——无论是你的笔记本电脑、队友的机器、预发布服务器还是生产环境。容器成为部署的单元,而不仅仅是应用程序代码。

Docker 已成为现代软件开发的标准工具。无论你是在构建微服务、设置 CI/CD 流水线,还是只是想要一个一致的开发环境,Docker 都能以最小的开销实现这一切。像 Netflix、Spotify 和 PayPal 这样的公司每天在生产环境中运行数百万个容器。

快速提示:Docker 不仅仅用于生产部署。许多开发人员使用它来避免在本地机器上混杂不同的语言版本、数据库和工具。一个项目需要 PostgreSQL,另一个需要 MySQL?在容器中同时运行它们,不会产生冲突。

容器与虚拟机

容器和虚拟机都提供隔离,但它们的底层工作方式非常不同。理解这种差异对于理解为什么容器变得如此流行至关重要。

虚拟机在虚拟机监控程序之上运行一个完整的操作系统及其自己的内核。每个虚拟机都需要自己的操作系统安装,消耗数 GB 的磁盘空间和大量内存。启动时间以分钟计。如果你运行三个虚拟机,就是在同时运行三个完整的操作系统。

容器共享主机操作系统内核,只打包应用程序层。它们的大小是 MB 级别(而不是 GB),启动时间以秒计(而不是分钟),你可以在一台机器上轻松运行数十个容器。

# 虚拟机方式:每个应用获得一个完整的操作系统
应用 A → 客户操作系统 → 虚拟机监控程序 → 主机操作系统 → 硬件
应用 B → 客户操作系统 → 虚拟机监控程序 → 主机操作系统 → 硬件

# 容器方式:应用共享内核
应用 A → 容器运行时 → 主机操作系统 → 硬件
应用 B → 容器运行时 → 主机操作系统 → 硬件

这种轻量级架构使容器成为微服务的理想选择,在微服务中你可能运行数百个小型服务而不是一个单体应用程序。资源效率令人惊叹——一台运行 10 个虚拟机的服务器可能可以轻松运行 100 个容器。

特性 虚拟机 容器
启动时间 分钟
磁盘空间 GB 级别(完整操作系统) MB 级别(仅应用层)
性能 接近原生 原生(无虚拟机监控程序开销)
隔离 完全隔离(独立内核) 进程级别(共享内核)
可移植性 有限(依赖虚拟机监控程序) 高(可在任何运行 Docker 的地方运行)
资源使用 重量级 轻量级

话虽如此,虚拟机并未过时。它们提供更强的隔离,因为每个虚拟机都有自己的内核。对于安全关键型工作负载或需要运行不同操作系统时,虚拟机仍然是更好的选择。许多组织同时使用两者:虚拟机用于基础设施隔离,容器用于应用程序部署。

Docker 核心概念

在深入了解命令和 Dockerfile 之前,你需要理解 Docker 的几个关键概念。这些构建块构成了 Docker 工作原理的基础。

镜像

Docker 镜像是一个只读模板,包含你的应用程序代码、运行时、库和依赖项。可以把它想象成快照或蓝图。镜像是根据 Dockerfile 中的指令构建的,并存储在像 Docker Hub 这样的注册表中。

镜像由层组成。Dockerfile 中的每条指令都会创建一个新层。Docker 会缓存这些层,所以如果你重新构建镜像并且只有最后一层发生了变化,Docker 会重用缓存的层。这使得构建速度非常快。

容器

容器是镜像的运行实例。你可以从同一个镜像创建多个容器,每个容器都独立运行。当你停止一个容器时,在其中所做的任何更改都会丢失,除非你明确保存它们或使用卷。

容器在设计上是短暂的。这种不可变性是一个特性,而不是缺陷——它确保了一致性并使扩展变得简单。

Dockerfile

Dockerfile 是一个包含构建 Docker 镜像指令的文本文件。它指定基础镜像、复制你的代码、安装依赖项,并定义如何运行你的应用程序。我们将在后面的章节中深入探讨 Dockerfile。

Docker 注册表

注册表是 Docker 镜像的存储和分发系统。Docker Hub 是默认的公共注册表,托管着数百万个镜像。你也可以为专有应用程序运行私有注册表。当你运行 docker pull nginx 时,Docker 会从 Docker Hub 下载 nginx 镜像。

卷是 Docker 持久化数据的机制。由于容器是短暂的,容器内写入的任何数据在容器停止时都会消失。卷让你可以在容器文件系统之外存储数据,在容器重启和删除后仍然保留。

专业提示:使用我们的 Docker 命令生成器快速创建复杂的 Docker 命令,无需记住所有标志和选项。

基本 Docker 命令

让我们了解一下你每天会使用的 Docker 命令。这些命令涵盖了从拉取镜像到清理资源的整个容器生命周期。

使用镜像

# 从 Docker Hub 拉取镜像
docker pull ubuntu:22.04

# 列出所有本地镜像
docker images

# 删除镜像
docker rmi ubuntu:22.04

# 从 Dockerfile 构建镜像
docker build -t myapp:1.0 .

# 为推送到注册表标记镜像
docker tag myapp:1.0 username/myapp:1.0

# 将镜像推送到注册表
docker push username/myapp:1.0

运行容器

# 在前台运行容器
docker run ubuntu:22.04 echo "Hello Docker"

# 在分离模式(后台)运行容器
docker run -d nginx

# 使用自定义名称运行
docker run -d --name my-nginx nginx

# 使用端口映射运行(主机:容器)
docker run -d -p 8080:80 nginx

# 使用环境变量运行
docker run -d -e POSTGRES_PASSWORD=secret postgres

# 使用卷挂载运行
docker run -d -v /host/path:/container/path nginx

# 以交互方式运行并打开 shell
docker run -it ubuntu:22.04 /bin/bash

管理容器

# 列出正在运行的容器
docker ps

# 列出所有容器(包括已停止的)
docker ps -a

# 停止容器
docker stop my-nginx

# 启动已停止的容器
docker start my-nginx

# 重启容器
docker restart my-nginx

# 删除容器
docker rm my-nginx

# 删除正在运行的容器(强制)
docker rm -f my-nginx

# 查看容器日志
docker logs my-nginx

# 实时跟踪日志
docker logs -f my-nginx

# 在运行的容器中执行命令
docker exec my-nginx ls /usr/share/nginx/html

# 在运行的容器中打开 shell
docker exec -it my-nginx /bin/bash

# 查看容器资源使用情况
docker stats

# 检查容器详细信息
docker inspect my-nginx

清理命令

# 删除所有已停止的容器
docker container prune

# 删除未使用的镜像
docker image prune

# 删除未使用的卷
docker volume prune

# 删除所有未使用的内容(容器、镜像、网络、卷)
docker system prune -a

-it 标志值得特别说明。-i 保持 STDIN 打开(交互式),-t 分配一个伪 TTY(终端)。它们一起使用可以让你像使用普通 shell 会话一样与容器交互。

快速提示:使用 docker ps -q 只获取容器 ID,这对脚本编写很有用。例如,docker stop $(docker ps -q) 会停止所有正在运行的容器。

编写 Dockerfile

Dockerfile 是魔法发生的地方。它是构建 Docker 镜像的配方,精确指定容器中包含什么。让我们分解最重要的指令和最佳实践。

基本 Dockerfile 结构

# 从基础镜像开始
FROM node:18-alpine

# 设置工作目录
WORKDIR /app

# 复制包文件
COPY package*.json ./

# 安装依赖项
RUN npm install

# 复制应用程序代码
COPY . .

# 暴露应用运行的端口
EXPOSE 3000

# 定义运行应用的命令
CMD ["node", "server.js"]

关键 Dockerfile 指令

FROM 指定基础镜像。始终使用特定的版本标签(如 node:18-alpine)而不是 latest,以确保可重现的构建。Alpine 变体更小且更安全。

WORKDIR 为后续指令设置工作目录。如果目录不存在,它会创建该目录。这比使用 RUN cd /app 更清晰。

COPY 将文件从主机复制到镜像中。语法是 COPY 源 目标。使用 COPY . . 复制所有内容,但要注意包含的内容(使用 .dockerignore)。

RUN 在构建过程中执行命令。每条 RUN 指令都会创建一个新层。使用 && 链接命令以减少层数:

# 不好:创建 3 层
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean

# 好:创建 1 层
RUN apt-get update && \
    apt-get install -y curl && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

EXPOSE 记录你的应用程序监听的端口。它实际上不会发布端口——这是通过 docker run -p 完成的。

CMD 指定容器启动时运行的默认命令。使用 JSON 数组格式(["可执行文件", "参数1"])以避免 shell 处理问题。

ENTRYPOINT 与 CMD 类似,但更难覆盖。当你希望容器像可执行文件一样运行时使用它。你可以结合 ENTRYPOINT 和 CMD 来实现灵活的默认值。

多阶段构建

多阶段构建允许你在一个 Dockerfile 中使用多个 FROM 语句。这对于创建小型生产镜像同时保持构建工具分离非常强大。

# 构建阶段
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# 生产阶段
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/server.js"]

生产镜像只包含编译后的代码和运行时依赖项,不包含构建工具。这可以将镜像大小减少 70% 或更多。

.dockerignore 文件

创建一个 .dockerignore 文件来从构建上下文中排除文件。这可以加快构建速度并减小镜像大小。

node_modules
npm-debug.log
.git
.env
*.md
.DS_Store
coverage
.vscode