Docker 教程

Docker 架构与核心概念

Docker 的架构和核心概念是理解 Docker 工作原理以及如何高效使用它进行容器化的基础。掌握这些底层原理,你就能从容地构建、管理和部署容器化应用。本章将涵盖 Docker 的核心组件、它们之间的交互方式,以及驱动容器化技术的关键概念。

1. Docker 架构

Docker 采用了客户端-服务器(Client-Server)架构。Docker 客户端 (Client) 负责与 Docker 守护进程 (Daemon) 进行通信,而守护进程则承担构建、运行和分发 Docker 容器的繁重工作。客户端和守护进程可以运行在同一个系统上,也可以连接到远程的守护进程。它们之间通常通过 REST API、UNIX 套接字或网络接口进行通信。

1.1 Docker 客户端 (Docker Client)

Docker 客户端是用户与 Docker 交互的主要方式。它提供了一个命令行界面(CLI),允许你向 Docker 守护进程发送指令。这些指令包括构建镜像、运行容器、从仓库拉取/推送镜像,以及管理数据卷和网络。Docker 客户端会将你的命令翻译成 Docker 守护进程能够理解的 API 请求。

示例:
当你运行 docker run hello-world 命令时,Docker 客户端会向 Docker 守护进程发送一个请求,要求它基于 hello-world 镜像运行一个容器。

1.2 Docker 守护进程 (Docker Daemon)

Docker 守护进程(dockerd)是一个持续在后台运行的进程,负责管理 Docker 镜像、容器、网络和数据卷。它监听 Docker API 请求并执行相应的操作。守护进程处理着容器化的核心任务,包括:

  • 镜像管理:从仓库拉取镜像、通过 Dockerfile 构建镜像,以及在本地存储镜像。
  • 容器管理:创建、启动、停止和删除容器。
  • 网络管理:创建和管理虚拟网络,以便容器之间、以及容器与外部世界进行通信。
  • 数据卷管理:为容器创建和管理持久化的存储卷。

Docker 守护进程利用操作系统内核的特性(如 namespacescgroups)来实现容器之间以及容器与宿主系统之间的隔离。

1.3 Docker 仓库 (Docker Registry)

Docker 仓库是用于存储和分发 Docker 镜像的系统。它是一个集中的存储库,你可以在这里保存和获取镜像。Docker Hub 是默认的公共仓库,但你也可以使用托管的或自建的私有仓库。

  • Docker Hub:由 Docker 官方提供的公共仓库,拥有海量的预构建镜像。它是 Docker 默认使用的仓库。
  • 私有仓库:由你控制和管理的仓库,允许你安全地存储私有或专有镜像。例如 Docker Trusted Registry (DTR)、Harbor,以及云服务商提供的仓库,如 Amazon Elastic Container Registry (ECR) 和 Google Container Registry (GCR)。

当你运行 docker pull <image> 时,Docker 守护进程会从配置好的仓库(默认为 Docker Hub)拉取镜像。当你运行 docker push <image> 时,守护进程则会将镜像推送到配置好的仓库。我们将在后续章节中深入探讨 Docker Hub 和镜像仓库。

2. Docker 核心概念

理解以下几个核心概念,对于掌握 Docker 的工作原理至关重要:

2.1 Docker 镜像 (Docker Images)

Docker 镜像是一个用于创建 Docker 容器的只读模板。它包含了运行应用程序所需的代码、运行库、依赖项、工具和其他文件。镜像是通过 Dockerfile 构建的,Dockerfile 是一个包含创建镜像指令的文本文件。

  • 分层结构 (Layers):Docker 镜像由多个层组成。Dockerfile 中的每一条指令都会创建一个新层。这些层堆叠在一起形成完整的镜像。这种分层架构实现了镜像的高效存储和分发,因为不同的镜像可以共享相同的基础层。
  • 镜像仓库 (Image Registry):镜像通常存储在 Docker 仓库中,例如 Docker Hub 或私有仓库。
  • 不可变性 (Immutability):Docker 镜像是不可变的,这意味着它们一旦构建就无法更改。当你从镜像运行一个容器时,Docker 会在镜像之上创建一个可写层,所有的更改都会保存在这个可写层中。这确保了原始镜像始终保持原样。

示例:
一个用于 Python Web 应用的 Docker 镜像可能包含:Python 运行环境、应用程序代码、所需的 Python 依赖包(如 Flask、Django),以及 Web 服务器(如 Gunicorn)。

2.2 Docker 容器 (Docker Containers)

Docker 容器是 Docker 镜像的可运行实例。它是一个轻量级、隔离的环境,你可以在其中运行你的应用程序。容器与宿主系统共享操作系统内核,但通过 namespacescgroups 技术实现了相互隔离。

  • 隔离性 (Isolation):容器之间以及容器与宿主系统之间是相互隔离的,为运行应用程序提供了一个安全且可预测的环境。
  • 可移植性 (Portability):容器具有高度的便携性,无论底层基础设施如何,只要系统安装了 Docker,容器就可以在上面运行。
  • 生命周期 (Lifecycle):容器拥有自己的生命周期:它们可以被创建、启动、停止、重启和删除。

示例:
你可以基于同一个 Docker 镜像运行多个容器,每个容器运行应用程序的一个独立实例。例如,你可以运行多个 Web 服务器容器来处理高并发的外部请求。

2.3 Dockerfile

Dockerfile 是一个纯文本文件,包含了一系列用于构建 Docker 镜像的指令。它指定了基础镜像、安装依赖所需的命令、要复制进镜像的应用程序代码,以及运行应用程序所需的任何其他配置。

  • 指令 (Instructions):Dockerfile 使用特定的语法指令,如 FROM、RUN、COPY、CMD 和 ENTRYPOINT。
  • 基础镜像 (Base Image):FROM 指令指定了作为起点的基础镜像。它可以是 Docker Hub 上的官方镜像(如 ubuntu、python、node),也可以是你自己创建的自定义镜像。
  • 自动化 (Automation):Dockerfile 实现了创建 Docker 镜像的自动化,确保镜像能够被一致且可重复地构建出来。我们将在后续章节中更深入地学习 Dockerfile。

示例:
一个针对 Node.js 应用的简单 Dockerfile 可能长这样:

# 使用官方 Node.js 镜像作为基础镜像
FROM node:14

# 设置工作目录
WORKDIR /app

# 复制 package.json 和 package-lock.json 文件
COPY package*.json ./

# 安装依赖
RUN npm install

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

# 暴露 3000 端口
EXPOSE 3000

# 启动应用程序
CMD ["npm", "start"]

2.4 Docker 数据卷 (Docker Volumes)

Docker 数据卷是一种用于持久化保存 Docker 容器产生和使用的数据的机制。数据卷独立于容器的生命周期,这意味着即使容器被删除了,存储在数据卷中的数据依然存在。

  • 数据持久化 (Data Persistence):数据卷提供了一种在容器重启和删除后依然能保留数据的方法。
  • 共享存储 (Shared Storage):数据卷可以在多个容器之间共享,允许它们访问同一份数据。
  • 类型 (Types):Docker 支持多种类型的数据卷,包括命名卷(named volumes)、绑定挂载(bind mounts)和 tmpfs 挂载。我们将在后面的模块中专门讲解数据卷管理。

示例:
你可以使用数据卷来存储数据库的数据文件,从而确保当数据库容器被停止或删除时,数据不会丢失。

2.5 Docker 网络 (Docker Networks)

Docker 网络为容器之间、以及容器与外部世界之间的通信提供了途径。Docker 支持多种网络类型,包括:

  • 桥接网络 (Bridge Network):默认的网络类型,它为容器创建一个私有网络,以便容器之间进行通信。
  • 主机网络 (Host Network):允许容器共享宿主机的网络命名空间,使容器能够直接访问宿主机的网络接口。
  • 覆盖网络 (Overlay Network):允许运行在不同 Docker 宿主机上的容器相互通信。我们将在后面的模块中介绍网络配置。

示例:
你可以为 Web 应用程序和数据库创建一个专属网络,允许它们相互通信,同时不将它们暴露给外部网络。

3. 实战示例与演示

让我们通过几个具体的例子来说明这些概念:

3.1 运行一个简单的 "Hello, World!" 容器

docker run hello-world

这个命令在后台执行了以下操作:

  1. Docker 客户端向 Docker 守护进程发送请求,要求基于 hello-world 镜像运行一个容器。
  2. 如果宿主机上还没有 hello-world 镜像,Docker 守护进程会去 Docker Hub 下载(拉取)它。
  3. Docker 守护进程基于该镜像创建一个新的容器。
  4. 容器运行一段简单的程序,在控制台打印出 "Hello from Docker!"。
  5. 程序执行完毕,容器退出运行,随后 Docker 守护进程会清理该容器。

3.2 构建一个简单的 Docker 镜像

创建一个名为 my-app 的文件夹,并在里面创建两个文件:app.pyDockerfile

app.py:

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello, Docker!"

if __name__ == "__main__":
    app.run(debug=True, host='0.0.0.0')

Dockerfile:

# 使用轻量级的 Python 3.9 基础镜像
FROM python:3.9-slim-buster

# 设置工作目录
WORKDIR /app

# 复制 app.py 到容器中
COPY app.py .

# 安装 flask 框架
RUN pip install flask

# 暴露 5000 端口
EXPOSE 5000

# 运行 Python 脚本
CMD ["python", "app.py"]

构建镜像:

docker build -t my-app .

运行容器:

docker run -d -p 5000:5000 my-app

这个例子展示了如何通过 Dockerfile 构建镜像并运行容器。Dockerfile 指定了基础镜像、复制了应用代码、安装了依赖、暴露了 5000 端口并启动了应用程序。