发布于 

Docker使用入门

# 引言

Docker 是一个开源的应用容器引擎,可以让开发者打包他们的应用及依赖到一个可移植的容器中,并且可以在任何流行的 Linux 机器上运行。

# 简单理解

docker 容器与虚拟机的最大区别是轻量级和快速启动。容器共享宿主机的内核,而虚拟机则需要完整的操作系统,因此容器启动更快,占用资源更少。

# 概念介绍

  • 镜像(Image): Docker 镜像是一个只读的模板,用于创建 Docker 容器。可以将其视为应用程序的快照。
  • 容器(Container): 容器是镜像的运行实例。它包含了应用程序及其所有依赖项,并且可以在任何支持 Docker 的环境中运行。
  • 仓库(Repository): 仓库是存储 Docker 镜像的地方。可以是公共的 Docker Hub,也可以是私有的仓库,docker 的官方仓库是 Docker Hub。
  • Dockerfile: Dockerfile 是一个文本文件,包含了构建 Docker 镜像的所有命令。它定义了镜像的内容和配置。
  • Docker Compose: Docker Compose 是一个工具,用于定义和运行多容器 Docker 应用。通过一个 YAML 文件,可以配置应用的服务、网络和卷等。

# 技术原理

Docker 使用 Linux 内核的 cgroups 和 namespaces 技术来实现容器化。cgroups 用于限制和隔离资源(如 CPU、内存、磁盘等),而 namespaces 用于隔离进程、网络、文件系统等。这样,Docker 可以在同一操作系统上运行多个独立的容器,而不会相互干扰。

# 安装 Docker

在 Debian 系统中安装 Docker 非常简单。
首先打开 https://get.docker.com/,选择 Debian 系统的安装脚本。可以使用以下命令直接安装 Docker:

1
2
3
# 下载并执行Docker安装脚本
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

也可以通过以下命令进行安装:

1
2
sudo apt update
sudo apt install docker.io

安装完成后,可以通过以下命令启动 Docker 服务:

1
2
sudo systemctl start docker
sudo systemctl enable docker

检查 Docker 是否安装成功,可以运行以下命令:

1
docker --version

当然你也可以通过 wsl 安装 Docker,具体步骤可以参考 Docker 官方文档.

# Docker 镜像

我们首先看一下基础的拉取镜像命令:

1
docker pull --platform=linux/amd64 docker.io/library/ubuntu:latest
  • docker.io 是 Docker Hub 的域名,当这一部分省略时,默认使用 Docker Hub 官方镜像。
  • library 是官方镜像的命名空间,
  • ubuntu 是镜像的名称,
  • :latest 是标签(tag),表示最新版本。当然你也可以自行指定版本,例如 ubuntu:20.04
  • --platform 参数可以指定平台,例如 --platform=linux/amd64 。大多数情况下,Docker 会自动选择合适的架构,但在某些情况下(例如在 raspberry pi 上运行),你可能需要手动指定。或者检查是否有 linux/arm64 版本。

# registry 与 repository 的区别

在 Docker 中,registry 是一个存储和分发 Docker 镜像的服务,而 repository 是一个特定的镜像集合。每个 repository 可以包含多个版本的镜像(通过标签区分)。简单来说,registry 是镜像的仓库,而 repository 是仓库中的一个具体项目。

# 镜像站点

在国内访问 Docker Hub 可能会比较慢,可以使用国内的镜像站点来加速下载。
配置 Docker 使用国内镜像站点非常简单。只需在 Docker 的配置文件中添加镜像站点地址即可。

1
2
3
4
5
6
7
8
9
10
11
12
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://docker.1panel.live",
"https://hub.rat.dev"
]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

# 镜像命令

# 列出所有镜像

1
docker images

# 删除镜像

1
docker rmi <image_id>

# 运行容器

运行容器是 Docker 的核心功能之一。可以使用以下命令来运行一个容器:

1
docker run -it -d --rm --name my_container ubuntu:latest
  • -it 选项表示以交互模式运行容器,并分配一个伪终端。
  • --name 选项用于指定容器的名称,并且这个名称在同一时间内必须是唯一的。
  • ubuntu:latest 是要运行的镜像名称。
  • -d 选项可以让容器在后台运行。
  • --rm 选项表示容器停止后自动删除容器。

如果需要查看正在运行的容器,可以使用以下命令:

1
docker ps

执行后会显示当前正在运行的容器列表。其中 CONTAINER ID 是容器的唯一标识符, IMAGE 是容器使用的镜像, COMMAND 是容器启动时执行的命令, STATUS 显示容器的运行状态。 NAMES 是容器的名称,如果没有手动设置,系统会分配一个随机的名称。 PORTS 显示容器的端口映射。

# 端口映射

容器中的端口和宿主机的端口是隔离的,默认情况下并不能直接从宿主机访问到 docker 的内部网络。如果容器需要与外部通信,可以使用端口映射。可以在运行容器时指定端口映射,例如:

1
docker run -d -p 8080:80 --name my_web_container nginx:latest

其中:

  • -p 选项用于指定端口映射。格式为 <host_port>:<container_port> ,表示将宿主机的 host_port 端口映射到容器的 container_port 端口。
  • 在这个例子中,宿主机的 8080 端口映射到容器的 80 端口。
    这个时候访问 http://localhost:8080 就可以访问到容器中的 Nginx 服务。

# 挂载卷

容器中的数据是临时的,当容器停止或删除时,数据也会丢失。为了持久化数据,可以使用卷(Volume)来挂载宿主机的目录到容器中。

1
docker run -d -v /path/on/host:/path/in/container --name my_data_container ubuntu:latest
  • -v 选项用于挂载卷。格式为 <host_path>:<container_path> ,表示将宿主机的 host_path 目录挂载到容器的 container_path 目录。

# 命名卷

如果需要使用命名卷,可以使用以下命令创建一个命名卷:

1
docker volume create my_volume

然后在运行容器时使用命名卷:

1
docker run -d -v my_volume:/path/in/container --name my_named_volume_container ubuntu:latest

查看命名卷目录

1
docker volume inspect my_volume

删除命名卷

1
docker volume rm my_volume

清理所有未使用的卷

1
docker volume prune -a

# 环境变量

环境变量是容器中运行的应用程序可以使用的配置参数。
在运行容器时,可以通过 -e 选项设置环境变量,例如:

1
docker run -d -e MY_ENV_VAR=value --name my_env_container ubuntu:latest

我们可以打开镜像的介绍页面查看环境变量的配置方法。

# 重启策略

在 Docker 中,可以设置容器的重启策略,以便在容器停止或崩溃时自动重启。可以使用 --restart 选项来设置重启策略,例如:

1
docker run -d --restart unless-stopped --name my_restart_container ubuntu:latest

--restart 选项可以设置以下几种重启策略:

  • no : 不自动重启容器(默认)。
  • always : 无论容器退出状态如何,都会自动重启。
  • unless-stopped : 除非容器被手动停止,否则会自动重启。
  • on-failure : 当容器以非零状态退出时自动重启,可以指定最大重启次数。

# 调试容器

# 执行命令

如果需要进入正在运行的容器,可以使用以下命令:

1
docker exec -it my_container /bin/sh

这将打开一个交互式的 bash shell,允许你在容器内执行命令。
如果你不需要进入容器只需要在容器内运行命令,可以使用以下命令:

1
docker exec my_container <command>

容器的运行和停止命令

1
2
docker start my_container
docker stop my_container

如果我们忘记了容器启动时的命令,可以使用以下命令查看容器的详细信息:

1
docker inspect my_container

把所有的内容扔给 ai 就可以查看了。

# 创建容器

这里的创建是指创建一个新的容器实例,而不是运行它。可以使用以下命令创建一个容器:

1
docker create --name my_container ubuntu:latest

# 查看日志

要查看容器的日志,可以使用以下命令:

1
docker logs my_container -f

其中 -f 选项表示实时跟踪日志输出。

# 制作镜像

# dockerfile

Dockerfile 是一个文本文件,包含了构建 Docker 镜像的所有命令。里面详细的描述了如何从基础镜像开始,安装软件包,复制文件,设置环境变量等。编写 dockerfile 的流程和正常运行项目时的流程类似。首先需要选择一个基础镜像,然后安装所需的软件包,复制项目文件,设置环境变量等。

# 编写 Dockerfile

在项目根目录下创建一个名为 Dockerfile 的文件,然后开始编写。

所有的 dockerfile 的命令都是大写的,其中第一行通常是 FROM 命令,用于指定基础镜像。也就是我们的镜像是从哪个镜像开始构建的。

接下来是 WORKDIR 命令,用于设置工作目录。这个有点类似于 cd 命令,表示进入到指定的目录下。所有后续的命令都会在这个目录下执行。

然后是 COPY 命令,用于将本地文件复制到容器中。可以指定源路径和目标路径。命令后面写两个路径,第一个是本地路径,第二个是容器内的路径。一般用两个点: COPY . .

然后是 RUN 命令,用于在容器内执行命令。可以用来安装软件包,运行脚本等。例如我们可以用以下命令安装 Python 依赖: RUN pip install -r requirements.txt .

EXPOSE 命令用于指定容器的端口。这个命令只是告诉 Docker 容器会监听哪个端口,并不会实际映射到宿主机的端口。

最后是 CMD 命令,用于指定容器启动时执行的命令。这个命令会在容器启动时自动执行。命令最好写成数组的形式,例如: CMD ["python", "app.py"]

最终写好的 Dockerfile 可能如下所示:

1
2
3
4
5
6
FROM python:3.9-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
EXPOSE 5000
CMD ["python", "app.py"]

# 构建镜像

在项目根目录下,使用以下命令构建镜像:

1
docker build -t my_image:latest .
  • -t 选项用于指定镜像的名称和标签。
  • my_image:latest 是镜像的名称和版本号。
  • . 表示当前目录是 Dockerfile 所在的目录。

# 运行镜像

构建完成后,可以使用以下命令运行镜像:

1
docker run -d -p 5000:5000 --name my_app_container my_image:latest
  • -p 选项用于端口映射,将宿主机的 5000 端口映射到容器的 5000 端口。
  • --name 选项用于指定容器的名称。

# 推送镜像到仓库

如果需要将镜像推送到 Docker Hub 或其他仓库,可以使用以下命令:

1
docker push my_image:latest

在执行这个命令之前,需要先登录到 Docker Hub:

1
docker login

然后使用以下命令推送镜像:

1
2
docker tag my_image:latest <your_dockerhub_username>/my_image:latest
docker push <your_dockerhub_username>/my_image:latest

# docker 网络

Docker 容器之间可以通过网络进行通信。Docker 提供了多种网络模式,包括桥接网络、主机网络和无网络等。

# 桥接模式

在默认情况下 docker 的网络模式都是桥接模式,所有容器都默认连接到这个网络上。每个容器都分配了一个内部的 IP 地址,通常是以 172.17.x.x 开头的。容器之间可以通过这个 IP 地址进行通信。但容器内部的服务默认是无法被外部访问的。

# 创建自定义网络

创建自定义网络的目的是为了让容器之间可以通过名称进行通信,而不需要使用 IP 地址。

我们可以使用以下命令创建一个自定义网络:

1
docker network create my_network

执行完这个命令后,创建容器时可以使用 --network my_network 选项将容器连接到自定义网络。

# 主机网络

这种模式下容器直接使用宿主机的网络栈,容器内的服务可以直接访问宿主机的网络接口,就像你直接在宿主机上运行应用程序一样。这种模式下容器的 IP 地址和宿主机的 IP 地址是相同的。可以使用 --network host 选项来指定主机网络模式。

# 无网络模式

最后一种网络模式是无网络模式,这种模式下容器没有网络连接。可以使用 --network none 选项来指定无网络模式。

# Docker Compose

有些时候一个完整的应用可能是由很多个应用程序组成的,比如一个 Web 应用可能需要一个数据库,一个缓存服务等。最容易想到的方法是将所有的服务都部署到一个容器里,但这样会导致容器变得非常庞大,而且只要有一个模块出现了故障整个容器都有可能崩溃。而且不易于管理和维护。Docker Compose 可以帮助我们解决这个问题。

Docker Compose 是一个工具,用于定义和运行多容器 Docker 应用。通过一个 YAML 文件,可以配置应用的服务、网络和卷等。使用 Docker Compose 可以更方便地管理多个容器,简化部署流程。

# 使用 docker run 命令部署

这是我在前几天创建的一个项目,按照往常的方法那我们就需要手动创建容器和网络了。以下是使用 docker run 命令部署的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 创建自定义网络
docker network create easyblog_network

### 启动 MySQL 容器
docker run -d \
--name easyblog_db \
-e MYSQL_ROOT_PASSWORD=password \
-e MYSQL_DATABASE=easyblog \
-v mysql_data:/var/lib/mysql \
-p 3386:3306 \
--network easyblog_network \
--restart always \
mysql:8.0

# 启动 Web 容器
docker run -d \
--name easyblog_web \
--network easyblog_network \
-e DB_HOST=easyblog_db \
-e DB_USER=root \
-e DB_PASSWORD=password \
-e DB_NAME=easyblog \
-e DB_PORT=3306 \
-e DB_TYPE=mysql \
-p 5000:5000 \
--restart always \
easyblog:latest

但是这样往往需要手动管理容器和网络,配置起来比较繁琐。

# 使用 docker-compose 部署

使用 Docker Compose 可以将上述配置简化为一个 docker-compose.yml 文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
version: '3.8'

services:
web:
build: .
# docker save -o easyblog.tar easyblog:latest
# docker load -i easyblog.tar
image: easyblog:latest
container_name: easyblog_web
ports:
# 将容器的 5000 端口映射到主机的 5000 端口
- "5000:5000"
# volumes: # 挂载 SQLite 数据库文件到容器
# - ./easyblog.db:/app/easyblog.db # 如果使用 MySQL,请注释掉此行
environment:
# 数据库连接环境变量说明:
# DB_TYPE: 数据库类型,支持 mysql 或 sqlite
# DB_HOST: 数据库主机地址。若使用容器中的 MySQL,请填写服务名(如 db);如自建 MySQL,请填写主机地址;使用 SQLite 可忽略
# DB_USER: 数据库用户名(MySQL 时必填)
# DB_PASSWORD: 数据库密码(MySQL 时必填)
# DB_NAME: 数据库名称(MySQL 时必填)
# DB_PORT: 数据库端口(MySQL 时必填,默认 3306)
# SQLITE_PATH: SQLite 数据库文件路径(仅使用 SQLite 时需要)
- DB_HOST=db
- DB_USER=root
- DB_PASSWORD=password
- DB_NAME=easyblog
- DB_PORT=3306
- DB_TYPE=mysql
depends_on:
- db
restart: always

db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: easyblog
volumes:
# 挂载 MySQL 数据目录到容器
- mysql_data:/var/lib/mysql
# - ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
# 将容器的 3306 端口映射到主机的 3386 端口
- "3386:3306"
restart: always

volumes:
mysql_data:

编写完 docker-compose.yml 文件后,可以使用以下命令启动所有服务:

1
docker-compose up -d

这将根据 docker-compose.yml 文件中的配置自动创建和启动所有服务。
要停止并删除所有服务,可以使用以下命令:

1
docker-compose down

# 使用 Docker Compose 的优势

  • 用一个 YAML 文件统一管理多个服务,配置更清晰,支持服务依赖( depends_on )、网络、卷等自动创建。
  • 一条命令即可启动 / 停止所有服务( docker-compose up/down ),适合多容器应用和开发环境。
  • 支持环境变量文件、扩展性强。

# 总结

Docker 是一个强大的容器化平台,可以帮助我们更高效地部署和管理应用程序。通过使用 Docker Compose,我们可以轻松地管理多容器应用,简化部署流程。本文介绍了 Docker 的基本概念、安装方法、镜像和容器的使用,以及如何使用 Docker Compose 来管理多容器应用。

# 参考资料