Docker 彻底改变了我们开发、部署和运行应用程序的方式。它使开发者能够将应用程序打包进容器 — 这些是标准化的可执行组件,它们将应用程序的源代码与运行代码所需的操作系统(OS)库和依赖项结合在一起,从而能够在任何环境中运行。本文将指导您学习 Docker 的关键概念、安装步骤、常用命令、构建镜像、管理容器、网络配置、数据持久化以及一些高级主题。
那么,让我们立刻开始学习吧。但在深入了解 Docker 之前,您需要先了解容器及容器化技术。
容器与容器化技术
容器是什么?
容器是一种轻量级、独立且可运行的软件包,它集成了运行软件所需的所有元素,包括代码、运行环境、系统工具、库和配置。容器能将软件与其运行环境隔离开来,确保软件在不同环境(如开发、测试或生产环境)中表现一致。这意味着,您可以将应用程序及其所有依赖和库打包成一个整体,这个整体可以在任何安装了容器Runtime的机器上运行,比如 Docker。
什么是容器化?
容器化是将软件代码及其所有依赖打包成一个整体的过程,以确保该软件在任何基础设施上都能一致且可靠地运行。这种方法确保了应用程序在从一个计算环境迁移到另一个计算环境时,能够轻松部署并稳定运行。
容器化的优势
- 一致性:无论在哪种环境下运行,应用程序的行为都是一致的。
- 高效性:容器轻量,且与宿主机的操作系统内核共享,因此比虚拟机更节省系统资源。
- 可扩展性:随着应用程序规模的逐渐扩大,可以轻松地进行伸缩。
- 隔离性:每个容器都在独立的环境中运行,这增强了安全性和稳定性。
Docker 是什么?
Docker 是一个开源平台,它让开发者能够轻松高效地构建、部署、运行、更新以及管理容器化的应用程序。以下是 Docker 受到广泛欢迎的几个关键原因:
Docker 简化了开发流程,减少了“在我的机器上可以运行”的问题,并且可以高效地进行应用程序的部署和扩展。
Docker 的主要优点:
- 可移植性:无论应用程序部署在何处,都能保持相同的运行状态。
- 可扩展性:根据需求轻松调整应用程序的规模。
- 隔离性:容器将应用程序及其依赖项封装在一起,确保它们可以独立运行。
Docker 的历史
Docker 最初是在 2013 年 3 月由 DotCloud(现在的 Docker, Inc.)发布的。Docker 的设计理念是为了创建一种轻量级、可移植且高效的打包和运行应用程序的方式,这种方式可以在不同的环境中保持一致性。这个概念受到了货运集装箱的启发,即应用程序及其依赖项被封装进标准化的容器中,这样可以轻松地进行移动和部署。
如今,Docker 在多个行业中得到了广泛应用,并得到了大量积极开发者和贡献者的支持。它已经成为容器化技术的基础,并推动了其他相关技术的发展,如 Kubernetes 容器编排技术。
开始使用 Docker
在深入探索 Docker 之前,您需要在自己的系统上安装 Docker。Docker 支持多种平台,包括 Windows、macOS 和 Linux。
安装 Docker
请根据您的操作系统在官方网站上按照指南进行 Docker 安装。
安装完成后,检查 Docker 是否已启动运行:
docker — version
Docker 架构了解
Docker 组件介绍
Docker 采用客户机-服务器架构,包含以下几个核心组件:
- Docker 客户端 :这是一种命令行界面(CLI)工具,用户通过它与 Docker 进行交互。客户端与 Docker 守护进程通信,执行各种命令。
- Docker 守护进程 (或 Docker 引擎):Docker 引擎是一款开源的容器化技术,它允许开发者将应用程序打包进容器。这些容器是标准化的可执行组件,将应用程序的源代码与运行代码所需的操作系统(OS)库和依赖项结合起来,从而能在任何环境中运行。守护进程负责监听 Docker API 请求并处理这些请求。
- containerd :这是一个关键组件,负责管理容器的生命周期,包括容器的启动、停止以及容器进程的管理。
- runc :这是一种轻量级的命令行工具,用于根据开放容器倡议(OCI)规范创建和运行容器。
- Docker 注册表 :这是一个用于存储和分发 Docker 镜像的服务。Docker Hub 是默认的公共注册表,但也可以使用私有注册表。它类似于 GitHub,但用于推送镜像而非源代码。
- Docker 网络 :提供容器网络功能,使容器能够相互通信以及与外部世界通信。
- Docker 卷和绑定挂载 :这些功能使得容器与宿主系统之间的数据持久性和数据共享成为可能。
- Docker Compose :这是一个工具,用于通过 YAML 文件定义和运行多容器应用程序。
Docker 运行应用程序的方式
- 构建:Docker 客户端向 Docker 守护进程发送构建请求,守护进程根据
Dockerfile
中的指示创建一个镜像。 - 发送:镜像被存储在 Docker 注册表(可以是公共的或私有的)中,之后可以从这里下载和共享。
- 运行:Docker 客户端请求 Docker 守护进程基于某个镜像创建并运行一个容器。
Docker 镜像介绍
Docker 镜像是一个轻量级、独立且可执行的软件包,它包含了运行软件所需的一切,比如代码、运行环境、库、环境变量和配置文件。
拉取 Docker 镜像
您可以从 Docker Hub 拉取镜像:
docker pull hello-world
拉取镜像后,您可以使用 docker run 命令轻松地运行这些镜像。
镜像和容器之间的区别可以用这样一个比喻来理解:容器就像您在本地机器上运行从 GitHub 获取的源代码中的 node app.js,而镜像则是您在 GitHub 上的代码仓库。
Docker 基础命令
1.) 查看 Docker 版本
docker version
2.) 查看系统级 Docker 信息
docker info
3.) 列出所有 Docker 镜像
docker images
4.) 列出运行中的容器
docker ps
docker ps -a // 列出所有容器(包括运行和停止的)
5.) 从仓库拉取镜像
docker pull node:20 // 20 表示我们想要拉取的 node 的具体版本
6.) 从镜像创建并启动新容器
在下面的例子中,我们以分离模式(-d)部署一个 NGINX 服务器,并将宿主机的 8080 端口映射到容器的 80 端口。
docker run -d -p 8080:80 nginx
7.) 停止并移除运行中的容器
docker stop <container_id>
docker rm <container_id> // 移除已停止的容器
8.) 移除镜像
docker rmi <image_id>
9.) 构建镜像
docker build -t <你的镜像名称> .
10.) 推送镜像到仓库
docker push <镜像名称> <仓库名称>
Docker 端口映射详解
Docker 中的端口映射指的是将宿主机的端口映射到容器中的端口。这对于从 Docker 主机外部访问容器内运行的应用程序至关重要。
端口映射的工作原理
假设您在 Docker 容器中运行了一个端口为 3000 的 Web 服务器。默认情况下,这个端口只能在 Docker 网络内部访问,无法从宿主机或外部网络访问。
为了让这个服务器能够从容器外部访问,您需要将宿主机的端口转发到容器。
示例:
docker run -p [宿主机端口]:[容器端口] [镜像名称]
<i>-p</i>
是用来指定端口映射的标志。
Dockerfile 文件
Dockerfile
是一个文本文件,其中包含了一系列构建 Docker 镜像的指令。每条指令都会在镜像中创建一个层,而这些层会被缓存,以便加速后续的构建过程。
Dockerfile 中的主要指令
- FROM:设置后续指令所使用的基础镜像。
- WORKDIR:设置容器内部的工作目录。
- COPY:将宿主系统中的文件复制到容器。
- RUN:在容器中执行命令。
- CMD:指定容器启动时执行的命令。
- EXPOSE:声明容器监听的端口。
.dockerignore 文件
.dockerignore
文件的作用类似于 .gitignore
文件。它指定在构建 Docker 镜像时应该忽略的文件和目录。这样做有助于保持镜像的轻量级,避免包含不必要的文件。这可以减少构建上下文的大小,提高构建速度。例如,可以添加 node_modules、dist 等文件夹。
如何制作 Docker 镜像?
我们以将一个基础的 mongo-express typescript 应用程序 Docker 化为例。
1.) 创建项目目录
mkdir ts-express-app
cd ts-express-app
npm init -y
npm install express mongoose dotenv
npm install --save-dev typescript @types/node @types/express @types/mongoose ts-node
tsc --init
2.) 创建 tsconfig.json 文件
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true
}
}
3.) 创建 src/index.ts 文件
import express from 'express';
import mongoose from 'mongoose';
import dotenv from 'dotenv';
dotenv.config();
const app = express();
const PORT = 3000;
const DB_URL = process.env.DATABASE_URL || '';
mongoose.connect(DB_URL, { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => console.log('Connected to MongoDB'))
.catch(err => console.error('Could not connect to MongoDB', err));
app.get('/', (req, res) => {
res.send('Ram Ram bhai Sareya Ne');
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
4.) 创建 package.json 文件和 .env 文件
"scripts": {
"start": "node dist/index.js",
"build": "tsc"
}
DATABASE_URL=mongodb://localhost:27017/ts-express-app
现在让我们将其容器化
5.) 在项目根目录下创建 Dockerfile
文件:
# 以 Node.js 20 镜像为基础
FROM node:20
# 设置容器的工作目录
WORKDIR /usr/src/app
# 拷贝 package.json 和 package-lock.json 文件
COPY package*.json ./
# 安装项目依赖
RUN npm install
# 拷贝剩余的应用代码
COPY . .
# 编译 TypeScript 代码
RUN npm run build
# 公开应用运行的端口
EXPOSE 3000
# 启动应用的命令
CMD ["npm", "start"]
6.) 在项目根目录下创建 .dockerignore
文件:
node_modules
dist
npm-debug.log
7.) 构建Docker镜像
docker build -t ts-express-app .
上述命令应该顺利执行,并且会显示类似以下的输出:
8.) 运行 Docker 容器
在镜像构建好之后,您可以用下面的命令从镜像启动一个容器:
docker run -p 3000:3000 ts-express-app
就像我之前提到的,这个命令会启动您的容器,并将您电脑上的端口 3000 映射到容器的端口 3000。您可以访问 localhost:3000,看到容器正在成功运行。
在上述图片中,您可以看到我的镜像已经列在 Docker 镜像列表中,并且容器正在端口 3000 上运行,这个端口已经与您电脑的端口 3000 映射。请花点时间在这里进行操作和分析正在发生的事情。此外,在之前的命令中,我加入了一些之前没有解释的内容。这是您的作业,找出它并学习相关知识。如果您已经找到了,请在评论区留言,并给出适当的解释:)
接下来,您可以用 3 个简单的命令将镜像推送到 Docker Hub 注册中心 —
9.) 将镜像推送到 Docker 注册中心
docker login
docker tag ts-express-app your-dockerhub-username/ts-express-app:latest
docker push your-dockerhub-username/ts-express-app:latest
在上面的命令中,将 “your-dockerhub-username” 替换为您的 Docker Hub 用户名。我使用了一个稍后我会解释的标签表达式,现在请您先按照这个方法操作!
恭喜您成功将第一个镜像推送到 Docker Hub。现在我相信您已经对构建镜像和运行容器的基础知识有所了解,让我们更深入地了解它。
在生产模式下,您会隐藏 .env 文件以保护您的秘密。那么,如何在不用 .env 文件的情况下,将这些秘密告诉 Docker 中的 .env 文件。
只需使用 “-e” 标志,它允许您将环境变量传递到您的应用程序。
docker run -p 3000:3000 -e DATABASE_URL=mongodb://localhost:27017/ts-express-app ts-express-a
Docker 标签
Docker 标签提供了关于特定镜像版本/变体的有用信息。标签允许您从 Docker 注册中心识别和拉取不同版本的镜像。它们是您镜像 ID 的别名,通常看起来像这样:f1477ec11d12
。这只是引用您镜像的一种方式。例如,Git 标签就是用来引用您历史记录中的特定提交的。
标记 Docker 镜像的基本语法是:
docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
如果没有指定标签,Docker 会默认使用 latest
标签。
标签最常见的两种使用情况
- 当构建镜像时,我们使用以下命令:
docker build -t username/image_name:tag_name .
- 通过
tag
命令显式标记镜像。
docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
- 版本管理 :标签通常用于表示不同版本的镜像。例如,您可能有像
1.0
、1.1
、2.0
等标签,代表主要或次要版本更新。 - 环境或阶段识别 :标签可以帮助区分开发、测试和生产环境。例如,您可能有像
dev
、staging
和prod
这样的标签。 - 架构或平台识别 :标签可以识别为不同架构或平台构建的镜像,例如
amd64
、arm64
或windows
。
让我们通过在命令中使用标签来进一步了解 Docker 标签的用法—
- 使用标签构建 Docker 镜像:
docker build -t express-mongo-app:1.0 .
- 拉取具有特定标签的镜像:
docker pull node:14.18.0
- 为现有镜像添加新标签:
docker tag express-mongo-app:1.0 express-mongo-app:latest
- 利用标签进行版本管理:
采用语义化版本 (MAJOR.MINOR.PATCH
) —
docker build -t express-mongo-app:1.0.0 .
docker tag express-mongo-app:1.0.0 express-mongo-app:1.0
docker tag express-mongo-app:1.0.0 express-mongo-app:latest
Docker 执行命令
docker exec
命令能让你在正在运行的 Docker 容器内执行命令。这对于调试、执行管理任务、创建或删除文件夹和卷,以及查看容器状态都很有帮助。
Docker exec 基本用法
docker exec [OPTIONS] 容器ID|容器名称 命令 [参数…]
- OPTIONS:各种选项,比如
-it
用于进入交互模式。 - 容器:容器的名称或 ID。
- 命令:你希望在容器内执行的命令。
- [参数…]:命令所需的参数。
这会启动一个在运行容器内的交互式 Bash shell。你可以在其中执行命令、检查文件、创建或删除卷,或者在容器环境下运行脚本。
docker exec
中的 -it
选项代表 “-interactive -tty”,它允许你与容器的 shell 进行交互。
你可能想知道我上面提到的卷是什么。接下来,我们就来详细了解 —
Docker 卷
Docker 卷是在 Docker 容器上挂载的文件系统,用于保存容器产生的数据。
Docker 文件系统是什么?
Docker 容器执行 Docker 镜像中指定的软件堆栈。这些镜像由在联合文件系统上操作的只读层构成。启动新容器时,Docker 会在镜像层之上添加一个可读写层,使容器能像标准的 Linux 文件系统那样运作。因此,容器内对文件的任何修改都会在这个可读写层产生一个可运行的副本。但是,当容器停止或被移除时,这个可读写层也会消失。
Docker 负责管理存储在宿主文件系统特定区域(在 Linux 系统中为 /var/lib/docker/volumes
)的卷。这部分文件系统不应被 Docker 以外的进程修改。在 Docker 中,卷是存储数据的最理想方式。我们可以通过 docker volume create
命令手动创建卷,或者在 Docker 创建容器或服务时自动生成卷。
卷主要有三种类型:
- 命名卷 :由 Docker 管理,并存储在宿主文件系统的某个位置。
- 绑定挂载 :将宿主机的目录或文件挂载到容器中。在宿主系统上,绑定挂载可以存放在任何地方。这些可能是重要的系统文件夹或文件。它们总是可以被运行在 Docker 宿主或 Docker 容器上的非 Docker 进程修改。相比之下,绑定挂载不如卷实用。
- tmpfs 挂载 :将一个临时文件系统挂载到容器中,存储在宿主的内存里。这些挂载不会写入宿主系统的文件系统;它们仅保存在宿主系统的内存中。在 Docker 宿主或容器中都不会存储在磁盘上。敏感的或非持久性状态数据可以存储在 tmpfs 挂载上,直到容器运行结束。
命名卷:
使用下面的命令来创建一个命名卷:
docker volume create my-ts-app-data
接下来,我们将这个卷挂载到容器内的一个目录。我们将 my-ts-app-data
卷挂载到容器内的 /app/data
目录。容器内对 /app/data
的任何写入操作都会存储在宿主上的命名卷中。
docker run -d -p 3000:3000 -e DATABASE_URL=mongodb://mongo:27017/ts-express-app -v my-ts-app-data:/data/db ts-express-app
使用 docker volume inspect “my-ts-app-data”
命令可以查看卷的详细信息,包括它在宿主文件系统中的位置。
当不再需要某个卷时,只需使用 “rm” 命令来删除它。
docker volume rm my-ts-app-data
Docker 中卷的工作方式
现在,让我们尝试一些新的操作。假设我想在 mongoDB Compass 应用中查看我的数据。但我如何将它连接到容器中的卷呢?
通过运行以下命令在本地启动 mongo 容器 —
docker run -p 27017:27017 -d mongo
打开你的 mongoDB Compass 并连接到 27017 端口。创建一个数据库和集合,插入一些数据并保存。
添加了一个新的数据库和一些随机数据。
现在关闭容器然后重新启动。打开 mongoDB Compass 并检查我们之前创建的数据库和数据。你发现了什么?数据不见了,对吧。
那么我们怎样才能保持数据不丢失呢?你猜对了。使用卷!我们之前已经创建了一个名为 “my-ts-app-data” 的卷,现在就用它。将卷挂载到 mongo 容器的 /data/db
目录,并使用以下命令运行它。
docker run -d -v my-ts-app-data:/data/db -p 27017:27017 mongo
现在重复上面的步骤,创建一个数据库并在其中添加数据。关闭容器,重新启动它,并再次检查你之前输入的数据。你会看到你的数据被保存下来了!
现在,我们已经学习了命名卷,接下来让我们深入了解一些 Docker 的核心概念。正如你在上面注意到的,我在解释 Docker 卷时提到了“层”。但什么是层?让我们来看看…
我暂时没有讲解绑定挂载和 tmpfs 挂载,我们将在本文的后面学习它们。
Docker 中的层概念
到目前为止,我们已经了解到 Docker 构建由一系列有序的构建指令组成。Dockerfile 中的每一条指令大致对应一个层,也称为 镜像层 。当你从镜像创建一个新容器时,会在镜像层之上添加一个新的可写层,这样容器就可以进行更改,而不会影响到底层的镜像层。
Docker 层是什么?
- 基础层 :这是 Docker 镜像的起始点。它包含了一个操作系统,比如 Ubuntu、Alpine 等(取决于你的 Dockerfile 中指定的内容)。这个层是固定不变的,为后续的层提供了基础。
- 中间层 :这些层代表了 Dockerfile 中的指令,例如
RUN
、COPY
和ADD
。每一条指令都会在前一个层的基础上创建一个新的层。中间层是只读的,并且会被缓存起来。 - 顶层可读/写层 :当你从镜像运行一个容器时,Docker 会在只读的镜像层之上添加一个可写层。这样,容器就可以在不修改底层镜像的情况下进行更改。
- 可重用和可共享 :层被缓存并在不同的镜像之间重用,这使得构建和共享镜像变得更加高效。如果多个镜像是从同一个基础镜像构建的,或者共享相同的指令,它们可以重用相同的层,这样可以节省存储空间,并加快镜像的下载和构建速度。
Docker 层是如何生成的
Docker 层是根据 Dockerfile 中指定的指令生成的。Dockerfile 中的每一条指令都会在之前的层之上创建一个新的层。下面是一个 Dockerfile 的示例 —
FROM ubuntu: 18.04 // 这条指令通过从 Docker 注册表中拉取 ubuntu: 18.04 镜像来创建一个基础层。
COPY . /app // 这条指令在基础层之上创建一个新层。它将构建上下文(包含 Dockerfile 的目录)的全部内容复制到容器内的 /app 目录。
RUN make /app // 这条指令通过在容器内运行 make /app 命令来创建另一个层。这个命令构建位于 /app 目录中的应用程序。
CMD python /app/app.py // 这条指令创建一个新层,并指定容器启动时运行的默认命令,即 python /app/app.py。
在上述示例中,我们可以看到每一条指令都在前一个层之上创建了一个新层,这些层堆叠起来构成了最终的 Docker 镜像。为了更直观地了解这个过程,你可以参考下面的图片来了解实际发生了什么。
Docker 层缓存
当我们使用 Dockerfile 构建一个 Docker 镜像时,Docker 会按顺序处理每条指令,并为每一条指令创建一个新层。如果一个镜像的层自上次构建以来没有变化,那么 Docker 构建器会从构建缓存中获取它。如果一个层自上次构建以来发生了变化,那么这个层以及所有跟在它后面的层都必须重新构建。让我通过一个例子来解释 —
正如你在上面附带的屏幕截图中看到的,在第一个镜像中,我将我的 express 应用构建成了一个镜像,我们可以看到每一层都是从零开始构建的。现在我进行了一个微小的更改(只是在 app.js 文件中添加了 <i>console.log('hi')</i>
),现在我要重新构建镜像。所以在第二个镜像中,你可以看到由于 layer 2、3、4 没有变化,它们被缓存了,但是由于文件发生了变化,Docker 注意到了这个变化,因此 layer 5 没有被缓存,而是从头开始构建。由于 layer 5 发生了变化,所有基于它(在它之后)构建的层也将从头开始重新构建。
注意:在第二个镜像中的 layer 1 仍然被缓存,尽管屏幕截图中没有显示它被缓存。实际上我不确定为什么,如果你在评论中找到了答案,请告诉我。但是别担心,它确实被缓存了。
现在你已经对容器化应用程序以及 Docker 的内部工作原理和 Docker 中的层有了相当好的了解。在继续前进之前,先暂停一下,尝试使用卷,创建它们并在你的软件中访问它们。还要尝试使用基于多种技术栈的应用程序来构建和重新构建你的 Dockerfile。尝试优化 Dockerfile 以减少步骤/层数,并最大化使用缓存层以提高效率。
Docker 网络基础
在默认情况下,Docker 容器之间是不能相互交流的。为了解决这个问题,Docker 网络允许容器之间以及与外部世界进行通信。它为 Docker 容器之间的通信提供了隔离、安全性和控制手段。
Docker 网络的种类
Docker 提供了几种不同类型的网络:
1. 桥接网络:
这是独立容器的默认网络类型。桥接网络通过软件在主机和容器之间创建一个桥接。连接到这个网络的容器可以相互通信,但它们与网络外的容器是隔离开的。网络中的每个容器都有自己的 IP 地址。由于网络与主机进行了桥接,容器也能在局域网和互联网上进行通信。不过,它们在局域网上并不会显示为物理设备。
2. 主机网络模式:
采用主机网络模式的容器会直接共享主机的网络栈,不进行任何网络隔离。这些容器不会拥有自己的 IP 地址,其端口绑定会直接映射到主机的网络接口上。也就是说,如果一个容器进程在端口 80 上进行监听,那么它实际上会绑定到 <your_host_ip>:80 。
3. 覆盖网络:
覆盖网络是一种跨多个 Docker 主机分布的网络。它允许在这些主机上运行的所有容器之间相互通信,而不需要依赖操作系统级别的路由支持。覆盖网络主要用于实现 Docker Swarm 集群中的网络功能,但也可以在运行两个独立 Docker Engine 实例且容器需要直接相互通信的场景中使用。这样,你就可以构建类似 Swarm 的网络环境。
4. Macvlan 网络:
Macvlan 是一种高级网络选项,它能够让容器在网络上显示为物理设备。这是通过为网络中的每个容器分配一个唯一的 MAC 地址来实现的。使用这种网络类型时,你需要将主机的一个物理接口专用于虚拟网络。同时,更广泛的网络也必须进行适当配置,以支持由一个活跃的 Docker 主机运行多个容器可能产生的众多 MAC 地址。
5. ipvlan:
IPvLAN 是一个提供对容器分配的 IPv4 和 IPv6 地址精细控制,以及层 2 和层 3 VLAN 标记和路由功能的驱动程序。当你需要将容器化服务与现有物理网络集成时,这个驱动程序会非常有用。IPvLAN 网络会分配自己的接口,这比基于桥接的网络在性能上更有优势。
6. 无网络模式:
在这种模式下,容器会被完全隔离,且没有网络接口。
让我们来尝试让容器之间能够相互通信。
使用下面的命令来创建一个网络(默认情况下是一个桥接网络):
当你运行一个容器但没有指定网络时,它会被自动连接到一个桥接网络上。
docker network create my-first-network
docker network ls
在桥接网络上运行容器 —
docker run -d --name c1 --network my-first-network nginx
docker run -d --name c2 --network my-first-network nginx
现在,让我们尝试将容器 1 连接到容器 2。我们将进入 c1,然后 ping c2
docker exec -it c1 /bin/bash
ping c2
你可以看到它正常工作了。现在,让我们尝试一些新的操作。我将在一个容器中运行 mongo,在另一个容器中运行我的 express 应用,然后尝试通过我的 express 应用来访问 mongo 容器。使用我们之前创建的相同的 express 应用代码,只需在 .env 文件中添加以下内容 —
DATABASE_URL=[container_name]://mongodb:27017/ts-express-app-db
DATABASE_URL=mongodb://mongodb:27017/ts-express-app-db
docker run -d --name mongodb --network my-first-network -v my-ts-app-data:/data/db -p 27017:27017 mongo
docker run -d -p 3000:3000 --network my-first-network -e DATABASE_URL='mongodb://mongodb:27017/ts-express-app-db' ts-express-app
你可以看到两个容器都已经运行起来了,现在尝试使用 Postman 或者在你的终端里用 curl 命令添加一些数据。你会收到一个成功的响应。为了确认数据已经存储在 mongodb 中,你可以进入容器并执行以下 mongo 命令。
docker exec -it mongodb mongo
use mydatabase
db.users.find().pretty()
你也可以检查 mongoDB 容器的日志,以确认数据是否已经成功存储。
删除网络
docker network rm my-first-network
# 删除所有未使用的网络(即那些没有连接到任何容器的网络)
docker network prune
将容器从网络中断开 —
docker network disconnect my-first-network mongodb
Docker Compose
Docker Compose 是一个工具,用于定义和运行多容器 Docker 应用程序。它通过一个 YAML 文件(docker-compose.yml
)来配置和编排应用程序中构成服务的各个方面,包括它们的依赖关系、网络设置、卷以及其他配置参数。
我们现在一直在使用的 express 应用程序的 docker-compose.yml 文件示例 —
version: '3.9'
services:
ts-express-app:
build: .
image: "express-mongo-ts-docker"
container_name: ts-express-app
ports:
- "3000:3000"
environment:
- MONGO_URL=mongodb://mongodb:27017/ts-express-app-db
depends_on:
- mongodb
mongodb:
image: mongo
container_name: mongodb
volumes:
- my-ts-app-data:/data/db
ports:
- "27017:27017"
volumes:
my-ts-app-data:
让我们逐行解释上述文件的意思 —
1.) 版本声明:
version: '3.9'
:指定了 Docker Compose 文件格式的版本。
2.) 服务:
服务是部署的单位,定义了要使用的容器镜像。我在上面的文件中定义了两个服务 —
a.) ts-express-app
这个服务定义了我们的 Express 应用程序。
build: .
:从当前目录的 Dockerfile 构建 Docker 镜像。image: "express-mongo-ts-docker"
:将镜像命名为express-mongo-ts-docker
。添加这个名称是可选的。container_name: ts-express-app
:将容器名称设置为ts-express-app
。ports: - "3000:3000"
:常规端口映射,无需解释。environment: - MONGO_URL=mongodb://mongodb:27017/ts-express-app-db
:设置环境变量MONGO_URL
,用于我们的应用程序连接到 MongoDB。这个 URL 指向下面定义的mongodb
服务。depends_on: - mongodb
:确保ts-express-app
服务在mongodb
服务启动后启动。
b.) mongodb:
这个服务定义了 MongoDB 数据库。
image: mongo
:使用 Docker Hub 上的官方 MongoDB 镜像。container_name: mongodb
:将容器名称设置为mongodb
。volumes: - my-ts-app-data:/data/db
:将 Docker 卷my-ts-app-data
挂载到容器中的/data/db
,这是 MongoDB 存储其数据的地方。(我们之前已经学过,所以不需要深入解释)ports: - "27017:27017"
:常规端口映射(同样不需要解释……)
3.) 卷:
- my-ts-app-data :我们在文件末尾定义了在这里使用的卷。
好的,既然我们已经学习了 Compose 文件,请在应用程序的根目录下复制上述代码,并创建一个 docker-compose.yml
文件。然后进入终端,运行命令 <b>docker-compose up --build</b>
并等待它构建你的镜像并启动容器。你会看到成功消息,现在你的 MongoDB 和 Express 应用程序容器已经在同一网络中正确连接,并且可以开始使用我们定义的卷。
要停止应用程序,请使用命令 — docker-compose down
下面是我们在 Docker 仪表板上的图片,你可以看到我们的 MongoDB 和 Express 应用程序容器正在运行。在第三张图片中,我使用 POSTMAN 添加了一些数据,然后从我们的数据库中使用 GET 请求检索了数据。
这很简单,不是吗!:)
结语
本文详细介绍了 Docker 从入门到中级的大部分核心概念,并通过实例进行了解释。我尽力为初学者解答了所有关键问题,相信阅读完本文后,你已经具备了使用 Docker 的基本能力!
希望你能理解并吸收文章的精华。尽管文章篇幅较长,但我确实努力概括了初学者启动 Docker 所需的所有要点。遇到问题时,或者想要独立探索 Docker 的新功能,记得查阅官方文档。
如果你对这类主题或技术感兴趣,希望看到更多相关文章,请给予点赞和评论。同时,也欢迎你将本文分享给那些想要学习 Docker 的朋友!