介绍
Docker 通过让开发者在称为容器的隔离环境中创建、部署和运行应用程序,彻底改变了应用程序开发。Mac 用户有时会遇到“docker command not found”(找不到 docker 命令)的错误,这在开始使用容器化时可能会令人沮丧。本实验(Lab)将指导你理解 Docker 概念,验证你的 Docker 安装,解决常见问题,并为你的开发工作设置一个合适的 Docker 环境。
理解 Docker 基础知识并验证安装
Docker 提供了一种标准化的方式,将应用程序及其依赖项打包到容器中,使其在不同的环境中具有可移植性。在解决任何 Docker 问题之前,让我们确保我们理解了基础知识并验证了我们的安装。
什么是 Docker?
Docker 是一个使用容器化技术来简化创建、部署和运行应用程序的平台。与虚拟机不同,Docker 容器共享宿主系统的内核,但在隔离的环境中运行,这使得它们轻量且高效。
Docker 的关键组件包括:
- Docker Engine:构建和运行容器的运行时
- Docker Images:用于创建容器的只读模板
- Docker Containers:Docker 镜像的运行实例
- Docker Registry:用于存储和共享 Docker 镜像的仓库
- Dockerfile:包含构建 Docker 镜像指令的文本文件
验证 Docker 安装
我们的实验(Lab)环境已经安装了 Docker。让我们通过检查 Docker 版本来验证这一点:
docker --version
你应该看到类似如下的输出:
Docker version 20.10.21, build 20.10.21-0ubuntu1~22.04.3
现在,让我们检查 Docker 守护进程是否正在运行:
sudo systemctl status docker
你应该看到输出表明 Docker 处于活动状态(正在运行)。输出将类似于:
● docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
Active: active (running) since ...
按 q 退出状态视图。
如果由于任何原因 Docker 没有运行,你可以使用以下命令启动它:
sudo systemctl start docker
运行你的第一个容器
让我们通过运行一个简单的“hello-world”容器来验证 Docker 是否正常工作:
docker run hello-world
如果 hello-world 镜像尚未在本地可用,此命令会下载它并在容器中运行它。你应该看到类似如下的输出:
Hello from Docker!
This message shows that your installation appears to be working correctly.
...
输出解释了 Docker 为运行此容器所做的事情,这很好地介绍了 Docker 的工作原理。
检查正在运行的容器
要查看所有当前正在运行的容器,请使用:
docker ps
由于 hello-world 容器在显示其消息后立即退出,你可能不会在此列表中看到它。要查看所有容器,包括已停止的容器,请使用:
docker ps -a
这将显示所有容器、它们的 ID、它们创建自的镜像、创建时间以及它们当前的状态。
现在,你已经验证了 Docker 已安装并在你的环境中正常工作,并且你已经运行了你的第一个容器!
使用 Docker 镜像和容器
现在你已经验证了 Docker 正常工作,让我们更详细地学习如何使用 Docker 镜像和容器。
理解 Docker 镜像
Docker 镜像(Docker Images)是容器的蓝图。它们包含应用程序代码、库、依赖项、工具以及应用程序运行所需的其他文件。
让我们使用一些基本命令来探索 Docker 镜像:
列出可用的镜像
要查看系统上所有可用的 Docker 镜像:
docker images
你应该看到类似如下的输出:
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest feb5d9fea6a5 X months ago 13.3kB
从 Docker Hub 拉取镜像
Docker Hub 是一个基于云的注册表服务,你可以在其中查找和共享 Docker 镜像。让我们拉取一个流行的镜像:
docker pull nginx
此命令下载最新的 nginx Web 服务器镜像。你将看到各种镜像层下载的进度输出:
Using default tag: latest
latest: Pulling from library/nginx
...
Digest: sha256:...
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest
再次运行 docker images 以在你的列表中查看新下载的 nginx 镜像。
使用容器
现在我们有了一些镜像,让我们学习如何创建和管理容器。
运行容器
让我们运行一个 nginx 容器,它将提供一个网页:
docker run --name my-nginx -p 8080:80 -d nginx
此命令执行以下几项操作:
--name my-nginx:将容器命名为“my-nginx”-p 8080:80:将你的宿主机上的端口 8080 映射到容器中的端口 80-d:在分离模式(后台)运行容器nginx:指定要使用的镜像
验证容器是否正在运行
检查你的容器是否正在运行:
docker ps
你应该在正在运行的容器列表中看到你的 nginx 容器:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1b2c3d4e5f6 nginx "/docker-entrypoint.…" X seconds ago Up X seconds 0.0.0.0:8080->80/tcp my-nginx
访问 Web 服务器
你可以在你的 LabEx 虚拟机环境中打开一个 Web 浏览器并导航到以下地址来访问 nginx Web 服务器:
http://localhost:8080
或者,你可以从终端使用 curl:
curl http://localhost:8080
你应该看到默认的 nginx 欢迎页面 HTML。
查看容器日志
要查看你的容器的日志:
docker logs my-nginx
这将显示 nginx 服务器的访问日志。
停止和删除容器
要停止正在运行的容器:
docker stop my-nginx
要删除容器(必须先停止):
docker rm my-nginx
验证容器是否已被删除:
docker ps -a
名为“my-nginx”的容器应该不再出现在列表中。
现在你了解了使用 Docker 镜像和容器的基础知识。你已经从 Docker Hub 拉取了镜像,运行了容器,映射了端口,查看了日志,并管理了容器的生命周期。
使用 Dockerfile 创建你自己的 Docker 镜像
到目前为止,我们已经使用了来自 Docker Hub 的预构建 Docker 镜像。现在,让我们学习如何使用 Dockerfile 创建我们自己的自定义 Docker 镜像。
什么是 Dockerfile?
Dockerfile 是一个文本文件,其中包含构建 Docker 镜像的指令。它指定了基础镜像,添加文件,安装软件,设置环境变量,并配置将从镜像创建的容器。
创建你的第一个 Dockerfile
让我们使用 Node.js 创建一个简单的 Web 应用程序,并将其打包为 Docker 镜像。
首先,为你的项目创建一个新目录:
mkdir -p ~/project/node-app
cd ~/project/node-app
现在,创建一个简单的 Node.js 应用程序。首先,创建一个名为 app.js 的文件:
nano app.js
将以下代码添加到文件中:
const http = require("http");
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader("Content-Type", "text/plain");
res.end("Hello World from Docker!\n");
});
const port = 3000;
server.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
按 Ctrl+O,然后按 Enter 保存文件,并使用 Ctrl+X 退出 nano。
接下来,创建一个 package.json 文件来定义你的 Node.js 应用程序:
nano package.json
添加以下内容:
{
"name": "docker-node-app",
"version": "1.0.0",
"description": "A simple Node.js app for Docker",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"author": "",
"license": "ISC"
}
保存并退出 nano。
现在,创建一个 Dockerfile:
nano Dockerfile
添加以下内容:
## Use an official Node.js runtime as the base image
FROM node:14-alpine
## Set the working directory in the container
WORKDIR /usr/src/app
## Copy package.json and package-lock.json
COPY package.json ./
## Install dependencies
RUN npm install
## Copy the application code
COPY app.js ./
## Expose the port the app runs on
EXPOSE 3000
## Command to run the application
CMD ["npm", "start"]
保存并退出 nano。
构建你的 Docker 镜像
现在你有了 Dockerfile,你可以构建你的 Docker 镜像:
docker build -t my-node-app .
此命令从你的 Dockerfile 构建一个镜像:
-t my-node-app:使用名称“my-node-app”标记镜像.:指定 Dockerfile 在当前目录中
你将看到显示构建进度的输出:
Sending build context to Docker daemon X.XXkB
Step 1/7 : FROM node:14-alpine
---> XXXXXXXXXX
Step 2/7 : WORKDIR /usr/src/app
---> XXXXXXXXXX
...
Successfully built XXXXXXXXXX
Successfully tagged my-node-app:latest
运行你的自定义 Docker 镜像
现在,使用你新构建的镜像运行一个容器:
docker run --name node-app-container -p 3000:3000 -d my-node-app
验证容器是否正在运行:
docker ps
你应该在列表中看到你的容器:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
XXXXXXXXXX my-node-app "npm start" X seconds ago Up X seconds 0.0.0.0:3000->3000/tcp node-app-container
测试你的应用程序
通过向其发出 HTTP 请求来测试应用程序:
curl http://localhost:3000
你应该看到:
Hello World from Docker!
理解 Dockerfile
让我们回顾一下 Dockerfile 的关键组件:
FROM node:14-alpine:指定要使用的基础镜像WORKDIR /usr/src/app:设置容器内的工作目录COPY package.json ./:将文件从宿主机复制到容器RUN npm install:在构建过程中在容器内运行命令EXPOSE 3000:记录容器在端口 3000 上监听CMD ["npm", "start"]:指定容器启动时要运行的命令
清理
让我们通过停止和删除容器来进行清理:
docker stop node-app-container
docker rm node-app-container
你现在已经学会了如何使用 Dockerfile 创建你自己的 Docker 镜像,构建这些镜像,并基于它们运行容器。这是 Docker 开发的一项基本技能。
使用 Docker 卷管理数据
在使用 Docker 容器时,一个挑战是数据的持久性。容器是短暂的,这意味着在容器内创建的任何数据都会在容器被移除时丢失。Docker 卷通过提供一种在容器外部持久化数据的方式来解决这个问题。
理解 Docker 卷
Docker 卷是持久化由 Docker 容器生成和使用的数据的首选机制。它们完全由 Docker 管理,并与宿主文件系统的目录结构隔离。
使用卷的好处包括:
- 卷比绑定挂载更容易备份或迁移
- 你可以使用 Docker CLI 命令管理卷
- 卷在 Linux 和 Windows 容器上都有效
- 卷可以在多个容器之间更安全地共享
- 卷驱动程序允许你将卷存储在远程主机、云提供商上,或加密卷的内容
创建和使用 Docker 卷
让我们创建一个简单的 MySQL 数据库容器,该容器使用卷来持久化其数据。
创建一个卷
首先,创建一个 Docker 卷:
docker volume create mysql-data
你可以使用以下命令列出所有卷:
docker volume ls
你应该在列表中看到你的新卷:
DRIVER VOLUME NAME
local mysql-data
运行一个带有卷的容器
现在,让我们运行一个使用此卷的 MySQL 容器:
docker run --name mysql-db -e MYSQL_ROOT_PASSWORD=mysecretpassword -v mysql-data:/var/lib/mysql -p 3306:3306 -d mysql:5.7
此命令:
--name mysql-db:将容器命名为“mysql-db”-e MYSQL_ROOT_PASSWORD=mysecretpassword:设置一个环境变量来配置 MySQL-v mysql-data:/var/lib/mysql:将“mysql-data”卷挂载到 MySQL 存储其数据的目录-p 3306:3306:将宿主机上的端口 3306 映射到容器中的端口 3306-d:以分离模式运行容器mysql:5.7:指定要使用的镜像
稍等片刻,让容器启动,然后检查它是否正在运行:
docker ps
与数据库交互
让我们创建一个数据库和一个表来演示数据持久性。首先,连接到 MySQL 容器:
docker exec -it mysql-db bash
在容器内部,连接到 MySQL 服务器:
mysql -u root -pmysecretpassword
创建一个新数据库和表:
CREATE DATABASE testdb;
USE testdb;
CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255));
INSERT INTO users (name) VALUES ('John'), ('Jane'), ('Bob');
SELECT * FROM users;
你应该看到插入的数据:
+----+------+
| id | name |
+----+------+
| 1 | John |
| 2 | Jane |
| 3 | Bob |
+----+------+
退出 MySQL 提示符和容器:
exit
exit
测试卷持久性
现在,让我们停止并移除容器,然后使用相同的卷创建一个新容器:
docker stop mysql-db
docker rm mysql-db
使用相同的卷创建一个新容器:
docker run --name mysql-db-new -e MYSQL_ROOT_PASSWORD=mysecretpassword -v mysql-data:/var/lib/mysql -p 3306:3306 -d mysql:5.7
现在连接到新容器并检查我们的数据是否已持久化:
docker exec -it mysql-db-new bash
mysql -u root -pmysecretpassword
USE testdb
SELECT * FROM users
你应该看到我们之前插入的相同数据:
+----+------+
| id | name |
+----+------+
| 1 | John |
| 2 | Jane |
| 3 | Bob |
+----+------+
退出 MySQL 提示符和容器:
exit
exit
这表明数据即使在原始容器被移除后也得以保留,因为它存储在 Docker 卷中。
检查和管理卷
你可以检查一个卷以获取有关它的更多信息:
docker volume inspect mysql-data
这将显示详细信息,例如挂载点和使用的驱动程序:
[
{
"CreatedAt": "YYYY-MM-DDTHH:MM:SS+00:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/mysql-data/_data",
"Name": "mysql-data",
"Options": {},
"Scope": "local"
}
]
要进行清理,让我们停止并移除容器:
docker stop mysql-db-new
docker rm mysql-db-new
如果你也想移除卷:
docker volume rm mysql-data
你现在已经学会了如何使用 Docker 卷在容器生命周期之间持久化数据,这对于有状态应用程序(如数据库)至关重要。
探索 Docker 网络
Docker 网络允许容器相互之间以及与外部世界进行通信。理解 Docker 的网络功能对于构建多容器应用程序至关重要。
Docker 网络类型
Docker 提供了几个开箱即用的网络驱动程序:
- bridge:默认的网络驱动程序。在同一桥接网络上的容器可以通信。
- host:移除容器和宿主机之间的网络隔离。
- none:禁用容器的所有网络。
- overlay:连接多个 Docker 守护程序并使 Swarm 服务能够通信。
- macvlan:为容器分配一个 MAC 地址,使其在网络上显示为物理设备。
探索默认桥接网络
当你安装 Docker 时,它会自动创建一个默认的桥接网络。让我们来探索它:
docker network ls
你应该看到类似如下的输出:
NETWORK ID NAME DRIVER SCOPE
XXXXXXXXXXXX bridge bridge local
XXXXXXXXXXXX host host local
XXXXXXXXXXXX none null local
你可以检查默认的桥接网络:
docker network inspect bridge
此命令提供有关网络的详细信息,包括连接到它的容器、IP 地址范围和网关。
创建和使用自定义桥接网络
让我们创建一个自定义桥接网络以实现更好的容器隔离:
docker network create my-network
验证网络是否已创建:
docker network ls
你应该在列表中看到你的新网络:
NETWORK ID NAME DRIVER SCOPE
XXXXXXXXXXXX bridge bridge local
XXXXXXXXXXXX host host local
XXXXXXXXXXXX my-network bridge local
XXXXXXXXXXXX none null local
现在,让我们在这个网络上运行两个容器,并演示它们之间的通信。
首先,在自定义网络上启动一个 NGINX 容器:
docker run --name web-server --network my-network -d nginx
接下来,让我们运行一个 Alpine Linux 容器,并使用它来测试与 NGINX 容器的连接:
docker run --name alpine --network my-network -it alpine sh
在 Alpine 容器内部,安装 curl 并测试与 NGINX 容器的连接:
apk add --update curl
curl web-server
输出应该是 NGINX 欢迎页面的 HTML。这有效是因为 Docker 为自定义网络中的容器提供了嵌入式 DNS,允许它们将容器名称解析为 IP 地址。
键入 exit 退出 Alpine 容器:
exit
将容器连接到多个网络
容器可以连接到多个网络。让我们创建另一个网络:
docker network create another-network
将现有的 web-server 容器连接到这个新网络:
docker network connect another-network web-server
验证容器现在是否已连接到两个网络:
docker inspect web-server -f '{{json .NetworkSettings.Networks}}' | json_pp
你应该看到容器已连接到 my-network 和 another-network。
使用端口发布运行容器
当你希望从 Docker 宿主机外部访问容器的服务时,你需要发布其端口:
docker run --name public-web -p 8080:80 -d nginx
此命令将宿主机上的端口 8080 映射到容器中的端口 80。你可以使用以下命令访问 NGINX Web 服务器:
curl http://localhost:8080
你应该看到 NGINX 欢迎页面。
清理
让我们清理我们创建的容器和网络:
docker stop web-server alpine public-web
docker rm web-server alpine public-web
docker network rm my-network another-network
验证容器和网络是否已被移除:
docker ps -a
docker network ls
理解容器通信
此步骤演示了 Docker 网络如何实现:
- 使用自定义网络的容器到容器通信
- 使用容器名称的 DNS 解析
- 将容器连接到多个网络
- 使用端口发布将容器服务暴露给外部世界
这些网络功能对于构建复杂的、多容器应用程序至关重要,在这些应用程序中,组件需要相互通信以及与外部服务通信。
总结
恭喜你完成了这个 Docker 实验!你已经学习了基本的 Docker 概念和技能,这些构成了基于容器的开发和部署的基础。
在这个实验中,你已经:
- 验证了 Docker 的安装并运行了你的第一个容器
- 使用了 Docker 镜像和容器,包括从 Docker Hub 拉取镜像和管理容器生命周期
- 使用 Dockerfile 创建了你自己的自定义 Docker 镜像
- 使用 Docker 卷在容器生命周期之间持久化数据
- 探索了 Docker 网络,以实现容器到容器的通信
这些技能将使你能够:
- 在可移植的容器中打包应用程序及其依赖项
- 为开发、测试和生产创建标准化的环境
- 实现微服务架构,其中每个组件都在其自己的容器中运行
- 确保有状态应用程序的数据持久性
- 构建具有适当隔离和通信的复杂多容器应用程序
Docker 已经成为现代软件开发中必不可少的工具,你所获得的知识将在广泛的开发和运营场景中发挥作用。



