## Docker

### docker基础知识

#### 镜像和容器

Docker中有几个重要的概念：

**镜像（Image）**：Docker将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起，称为镜像，是只读的。

**容器（Container）**：镜像中的应用程序运行后形成的进程就是容器，只是Docker会给容器进程做隔离，对外不可见。因此一个镜像可以启动多次，形成多个容器进程。

一切应用最终都是代码组成，都是硬盘中的一个个的字节形成的**文件**。只有运行时，才会加载到内存，形成进程。



Docker为了解决依赖的兼容问题的，采用了两个手段：

- 将应用的Libs（函数库）、Deps（依赖）、配置与应用一起打包

- 将每个应用放到一个隔离**容器**去运行，避免互相干扰

<img src="https://pic.bitday.top/i/2025/03/19/u64xsq-2.png" alt="image-20210731142219735" style="zoom: 67%;" />

这样打包好的应用包中，既包含应用本身，也保护应用所需要的Libs、Deps，无需在操作系统上安装这些，自然就不存在不同应用之间的兼容问题了。



#### DockerHub

http://dockerhub.com/

开源应用程序非常多，打包这些应用往往是重复的劳动。为了避免这些重复劳动，人们就会将自己打包的应用镜像，例如Redis、MySQL镜像放到网络上，共享使用，就像GitHub的代码共享一样。

- DockerHub：DockerHub是一个官方的Docker镜像的托管平台。这样的平台称为Docker Registry。

- 国内也有类似于DockerHub 的公开服务，比如 [网易云镜像服务](https://c.163yun.com/hub)、[阿里云镜像库](https://cr.console.aliyun.com/)等。

注意：很多国内的镜像现在也不能用了！需要换源！



#### Docker架构

我们要使用Docker来操作镜像、容器，就必须要安装Docker。

Docker是一个CS架构的程序，由两部分组成：

- 服务端(server)：Docker守护进程，负责处理Docker指令，管理镜像、容器等

- 客户端(client)：通过命令或RestAPI向Docker服务端发送指令。可以在本地或远程向服务端发送指令。

如图：

![image-20210731154257653](https://pic.bitday.top/i/2025/03/19/u66964-2.png)



#### 镜像操作

![image-20210731155649535](https://pic.bitday.top/i/2025/03/19/u65nlc-2.png)

1. docker push，将本地镜像上传到远程仓库（例如 Docker Hub）

```shell
docker login  	#docker hub登录
# 假设已有本地镜像 myimage，需要先打上标签：
docker tag myimage yourusername/myimage:latest
# 上传镜像到远程仓库：
docker push yourusername/myimage:latest
```

2. docker pull ，从远程仓库拉取镜像到本地。

```shell
docker pull yourusername/myimage:latest
```

3. docker save，将本地镜像保存为 tar 文件，方便备份或传输

```shell
docker save -o myimage.tar yourusername/myimage:latest
```

4. docker load，从 tar 文件中加载镜像到本地 Docker。

```shell
docker load -i myimage.tar
```

5. docker images ，查看本地镜像

```shell
docker images
```

6. docker build ，构建镜像 -t后面跟镜像名

```shell
docker build -t yourusername/myimage:latest .
```

7. 清理悬空、无名镜像

```shell
docker image prune
```



#### 容器操作

![image-20210731161950495](https://pic.bitday.top/i/2025/03/19/u66wrs-2.png)

1.docker run 创建并运行一个新容器

​		-d:以后台模式运行容器，不会占用当前终端。

​		--name <容器名> :为容器指定一个自定义名称，便于后续管理。

​		-p <宿主机端口>:<容器端口>  : 将容器内部的端口映射到宿主机，**使外部可以访问容器提供的服务，如果不写的话，只有容器内部网络能访问它**比如mysql，如果写''-p 3307:3306',那么可以用navicat连接localhost:3307访问这个数据库。

​		--restart <策略>  :设置容器的重启策略，如 `no`（默认不重启）、`on-failure`（失败时重启）、`always`（总是重启）或 `unless-stopped`(最重要！docker服务重启时，它也跟着重启，但它不会读取最新的compose文件，只会恢复中断前容器内元数据)。


​		-v <宿主机目录>:<容器目录>` 或 `--volume ：  如 -v /host/data:/app/data

```text
docker run --name test-container -d test:latest
```

2.docker exec 在正在运行的 test-container 内执行命令

​		-it : 给当前进入的容器创建一个标准输入、输出终端

```text
docker exec -it test-container sh
```

3.docker logs ，查看 test-container 的日志输出：

```text
docker logs --since 1h test-container   #查看最近1h
docker logs --since 5m test-container #查看最近5分钟
```

4.docker stop 停止正在运行的 test-container：

```text
docker stop test-container
```

5.docker start 启动一个已停止的 test-container：

```text
docker start test-container
```

6.docker cp 复制文件（或目录）到容器内部，先cd到文件所在目录

```text
docker cp localfile.txt test-container:/target_dir/
```

7.docker stats ，查看docker中运行的所有容器的运行状态（CPU 内存占用）

```text
docker stats
```

8.docker container ls，查看运行容器的创建时间、端口映射等

```text
 docker container ls
```

9.docker ps 查看 Docker 容器的状态,默认情况下，它只显示正在运行的容器

```text
docker ps -a  #查看所有容器，包括已经停止或启动失败的容器
```



#### 数据卷操作

**数据卷（volume）**是一个虚拟目录，指向宿主机文件系统中的某个目录。

![image-20210731173541846](https://pic.bitday.top/i/2025/03/19/u68796-2.png)

一旦完成数据卷挂载，对容器的一切操作都会作用在数据卷对应的宿主机目录了。

这样，我们操作宿主机的/var/lib/docker/volumes/html目录，就等于操作容器内的/usr/share/nginx/html目录了

有两种挂载方式：

**绑定挂载（Bind Mounts）更加推荐！**

- **原理**：直接将宿主机上的一个目录或文件挂载到容器内。

- **特点**

  - 数据存储在宿主机上，容器可以直接访问宿主机的文件系统。
  - 适合需要在开发过程中频繁修改代码或数据共享的场景。
  - 依赖宿主机的目录结构，移植性较低。

- **示例**：将宿主机的 `/path/on/host` 挂载到容器内的 `/app/data`：

  ```text
  docker run -v /path/on/host:/app/data your_image
  ```

**命名卷（Docker Volumes）**

- **原理**：由 Docker 管理的数据卷，存储在 Docker 的默认目录（通常在 `/var/lib/docker/volumes/`），或通过 Docker 卷插件存储到其他位置。

- **特点**

  - Docker 负责管理这些卷，提供更好的隔离和数据持久性。
  - 与宿主机的具体目录结构无关，便于迁移和备份。
  - 常用于生产环境中数据的持久化。

- **示例**：创建并挂载名为 `my_volume` 的卷到容器内的 `/app/data`：

  ```text
  docker run -v my_volume:/app/data your_image
  ```

创建命名卷

```text
docker volume create my_volume
```

查看命名卷，这将列出所有 Docker 管理的卷。

```text
docker volume ls
```

显示该命名卷的详细信息

```text
zy123@hcss-ecs-588d:~/zbparse$ sudo docker volume inspect html
[
    {
        "CreatedAt": "2025-02-25T18:46:10+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/html/_data",
        "Name": "html",
        "Options": null,
        "Scope": "local"
    }
]
```

Mountpoint是宿主机上的路径，也就是 Docker 存储该数据卷数据的**实际位置**



#### docker网络

Docker 网络的主要作用是实现容器之间的**通信和隔离**，同时也能控制容器与外部主机或网络的连接。通过创建自定义网络，你可以**让属于同一网络的容器通过名称互相访问**，而不必暴露所有服务到外部网络，这既提升了安全性又简化了容器间的交互。

**举例说明**

假设你有两个容器，一个运行 MySQL 数据库，另一个运行 Web 应用程序。你希望 Web 应用能够通过数据库容器的别名来访问 MySQL，而不需要硬编码 IP 地址。

1.创建自定义网络 ，名为 `app-net`

```text
docker network create app-net
```

2.启动 MySQL 容器，并加入 `app-net` 网络，同时为其指定别名 `db`

```text
docker run -d --name mysql \
  --network app-net \
  --network-alias db \
  -e MYSQL_ROOT_PASSWORD=my-secret-pw \
  mysql:latest
```

**Docker CLI 命令**（如 `docker ps`、`docker exec`）**需要使用 `container_name` 来操作容器**。

但如果 **另一个容器需要访问它，不能直接用 `mysql`容器名，而要用 IP 地址或 `network-alias`**。

`--network-alias db`（网络别名），只在特定网络中生效

注意：不使用docker-compose就没有服务名，可以通过容器名，**网络别名**实现同网络的容器间通信



3.启动 Web 应用容器，加入同一个 `app-net` 网络

```text
docker run -d --name webapp \
  --network app-net \
  your_webapp_image:latest
```

4.验证容器间通信，进入 Web 应用容器，尝试通过别名 `db` 连接 MySQL：

```text
docker exec -it webapp bash
# 在 webapp 容器内执行，比如使用 ping 测试网络连通性：
ping db
```

举个例子，如果你的 Java 应用运行在容器 B 中，而数据库容器 A 已经通过 `--network-alias db` 起了别名，那么在 Java 应用中，你只需要写：

```java
String dbUrl = "jdbc:mysql://db:3306/your_database";
```

而不必关心数据库容器的实际 IP 地址。

否则：

```java
String dbUrl = "jdbc:mysql://<宿主机IP或localhost>:3306/your_database";
```

因为会通过**宿主机IP映射到容器内**的IP

5.连接一个正在运行或已创建的容器到网络

接时可以使用 `--alias` 参数为容器在该网络中设置别名

```text
docker network connect app-net mysql --alias db
```

6.断开连接

```text
docker network disconnect app-net mysql
```

7.删除网络

需要注意的是，只有当网络中没有容器连接时才能删除。

```text
docker network rm app-net
docker network prune #删除所有未使用的网络
```



### docker安装：

**1.卸载旧版**

首先如果系统中已经存在旧的Docker，则先卸载：

```Shell
sudo apt-get remove docker docker-engine docker.io containerd runc
```

**说明**：该命令会删除系统中现有的 Docker 相关包，但不会删除 Docker 镜像、容器、卷等数据。如果需要彻底清理，可以手动删除相关目录。



**2.安装 Docker 依赖**

在安装新版 Docker 前，需要更新 apt 源并安装一些依赖包，以便能够通过 HTTPS 协议访问 Docker 官方仓库。

```text
sudo apt-get update
sudo apt-get install apt-transport-https ca-certificates curl gnupg lsb-release
```

**说明**：这些软件包包括用于 HTTPS 传输的支持库、CA 证书、curl 工具、GPG 密钥管理工具以及 Debian 版本识别工具。



**3.添加 Docker 官方 GPG 密钥**

```text
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
```

**说明**：此命令从 Docker 官方获取 GPG 密钥并保存为二进制格式，供后续验证软件包使用。



**4.设置 Docker 仓库**

使用以下命令将 Docker 稳定版仓库添加到 apt 源列表中：

这是最关键的一步，配置docker官方地址，往往**很难下载**！！！

```text
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
```

推荐使用以下阿里云的镜像加速源

```text
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
```

cat /etc/apt/sources.list.d/docker.list 可以查看是否配置成功



**5.安装 Docker Engine**

更新 apt 缓存后，安装最新版的 Docker Engine、CLI 工具和 containerd：

```text
sudo apt update   #如果是docker官方，这一步可能失败！
sudo apt install docker-ce docker-ce-cli containerd.io
```



**6.启动和校验**

```Bash
# 启动Docker
systemctl start docker
# 停止Docker
systemctl stop docker
# 重启
systemctl restart docker
# 设置开机自启
systemctl enable docker
# 执行docker ps命令，如果不报错，说明安装启动成功
docker ps
```

以上docker安装，最关键的就是**第4步**和**第5步**



linux vim：

**finalshell中粘贴会出现粘贴的文本一行比一行靠右，看起来乱成一团。比较快的解决办法是，在粘贴文档前，在命令行模式下，输入**
**：set paste**

删除全文：  控制模式下   %d



### docker配置代理

```text
sudo vim /etc/systemd/system/docker.service.d/http-proxy.conf
```

```text
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:7890"
Environment="HTTPS_PROXY=http://127.0.0.1:7890"
Environment="NO_PROXY=localhost,127.0.0.1"
```



```text
sudo systemctl daemon-reload   //加载配置文件，更新环境变量
sudo systemctl restart docker    
```

```text
systemctl show --property=Environment docker   //验证是否配置成功
```

经验总结：貌似配置代理+启动VPN没卵用，如果拉取不到镜像，还是老老实实配置国内镜像吧，另外注意GPT给出的镜像:tag是否真的存在！！！有时候可能是虚假的，根本拉不到。





### docker配置镜像：

1.编辑 Docker 配置文件,如果该文件不存在，可以创建一个新的文件。

```text
sudo vim /etc/docker/daemon.json
```

2.添加多个镜像仓库配置

```text
{
  "registry-mirrors": [
    "http://hub-mirror.c.163.com",
    "https://mirrors.tuna.tsinghua.edu.cn",
    "https://ustc-edu-cn.mirror.aliyuncs.com",
    "https://rnsxnws9.mirror.aliyuncs.com",
    "https://registry.docker-cn.com",
    "https://reg-mirror.qiniu.com"
  ]
}

```

3.重启 Docker 服务以应用

```text
sudo systemctl restart docker
```

4.验证配置

```text
docker info
```



### Dockerfile语法

我们只需要告诉Docker，我们的镜像的组成，需要哪些BaseImage、需要拷贝什么文件、需要安装什么依赖、启动脚本是什么，将来Docker会帮助我们构建镜像。

而描述上述信息的文件就是Dockerfile文件                               。![image-20210731180321133](https://pic.bitday.top/i/2025/03/19/u67pvv-2.png)       

`EXPOSE 8090` 是一个声明性的指令，`EXPOSE` 本身不会进行端口映射

在 Dockerfile 中，RUN 指令用于在构建镜像的过程中执行命令，这些命令会在镜像的一个临时容器中执行，然后将执行结果作为新的镜像层保存下来。常见的用途包括安装软件包、修改系统配置、编译代码等。

```text
RUN cd $JAVA_DIR \
 && tar -xf ./jdk8.tar.gz \
 && mv ./jdk1.8.0_144 ./java8
```



**减少重复构建镜像**

当你修改原镜像时，只需使用相同的镜像名执行：

```text
docker build -t zbparse .
```

Docker 会根据 **Dockerfile 和上下文的变化**来判断哪些层需要重建，只重建受影响的部分，而未变的层会使用缓存。

**优化建议**：

- 把变化较少的步骤（如 `FROM` 和设置工作目录和requirements.txt）放在前面。
- 将容易变化的步骤（比如 `COPY . .`）放在后面。

这样，即使修改了 `项目的代码`，其他层仍可复用缓存，从而减少重复构建的开销。



这样会有一个问题，如果新镜像与旧镜像名字一致，那么旧的镜像名会变成none！

以下方法可以删除none镜像。

```text
# 查找无标签镜像
docker images -f "dangling=true"
# 删除无标签镜像
docker rmi $(docker images -f "dangling=true" -q)
```



在构建命令中使用 `--no-cache` 选项可以强制 Docker 重新执行所有步骤，这在某些情况下是必要的，但通常应避免使用它以利用缓存。



### docker启动服务全流程

1. 编写dockerfile文件

   ```text
   # 使用官方 Python 运行时作为父镜像
   FROM python:3.8-slim
   
   # 设置工作目录
   WORKDIR /flask_project
   
   # 复制requirements文件到容器中
   COPY requirements.txt .
   
   # 关闭pip的进度条以减少日志输出量
   RUN pip config set global.progress_bar off
   
   # 安装依赖
   RUN pip install --upgrade pip --default-timeout=200 \
       && pip install -i https://mirrors.aliyun.com/pypi/simple/ -r requirements.txt
   
   # 将当前目录的内容复制到容器的 /flask_project 中
   COPY . .
   
   # 将 flask_project 添加到 PYTHONPATH
   ENV PYTHONPATH=/flask_project:$PYTHONPATH
   
   
   # 暴露端口
   EXPOSE 5000
   
   # 在容器启动时运行你的应用
   CMD ["python", "flask_app/run_serve.py"]
   ```

2. pip freeze > requirements.txt 导出requirements文件
   这里有个问题，这里生成的requirements可能包含诸多不需要的库，有另一种方式。做加法，先pip freeze生成，然后打开项目中的每个文件，查看import的包，手动写requirements，具体的包的版本从pip freeze生成的内容摘取。
   
3. requirements和dockerfile都放在项目的根目录下

5. 构造镜像     -t后面是镜像名 ，最后的点号 (`.`) 代表当前目录

```text
docker build -t zbparse .
```

5. 运行容器  -p后面，第一个5000代表宿主机端口，第二个5000代表容器内端口 ，zbparse-container为创建的容器名，zbparse是使用的镜像名字

```text
docker run -d -p 5000:5000 --name zbparse-container zbparse
```

6. 查看日志  ，若无报错贼容器正常启动

```text
docker logs zbparse-container
docker logs --tail 10 [容器ID或名称]  查看最近10条日志
docker logs --since 1h [容器ID或名称] 查看最近1小时的日志
```

7. 停止和删除容器，先停止后删除

```text
docker stop zbparse-container
docker rm zbparse-container
```

8. 删除镜像

```text
docker rmi zbparse
```

9. 进入容器，可以查看容器内的数据

```text
docker exec -it zbparse-container /bin/bash
```

10. 保存镜像为tar文件  （最简单的应该是上传docker hub镜像库，但是现在貌似被墙了）

```text
docker save -o zbparse.tar zbparse
```

11. 使用scp传输文件

```text
scp zbparse.tar root@118.178.236.139:/home/zy
```

这条命令使用 `scp`（安全复制）将本地生成的 `zbparse.tar` 文件传输到远程服务器 `118.178.236.139` 上，目标路径为 `/home/zy`，使用的登录用户名是 `root`。

12. 加载镜像

```text
sudo docker load -i zbparse.tar
```

13. 上传镜像

```text
docker login   #输入邮箱 密码
docker tag zbparse yourusername/zbparse  #标记你的 Docker 镜像
#其中yourusername/zbparse是标记名
docker push yourusername/zbparse  #推送镜像到 Docker Hub

```

**注意！denied: requested access to the resource is denied**

原因：docker hub上只能使用一个命名空间，也就是说

docker tag zbparse 646228430smile/zbparse:latest   这里的646228430smile是用户名，保持不变

![微信截图_20250225163059](https://pic.bitday.top/i/2025/03/19/u661a8-2.png)

14. 查看镜像

```text
docker images
```



## Docker部署可能遇到的问题

- **镜像拉取失败**

排除镜像站的问题，如果拉取不到镜像，很有可能是你使用的TAG不正确！！

直接去[maven Tags | Docker Hub](https://hub.docker.com/)上看看你输入的TAG是否存在，比如：
[Docker Hub](https://hub.docker.com/_/maven/tags)

如果你输入的都是个错误的TAG，那肯定拉不下来。



- **端口占用问题**

Error response from daemon: Ports are not available: exposing port TCP 0.0.0.0:5672 -> 0.0.0.0:0: listen tcp 0.0.0.0:5672: bind: An attempt was made to access a socket in a way forbidden by its access permissions.

先查看是否端口被占用：

```shell
netstat -aon | findstr 5672
```

如果没有被占用，那么就是windows的bug，在CMD使用管理员权限重启NAT网络服务即可

```shell
net stop winnat
net start winnat
```



- **构建镜像失败**

```text
RuntimeError: can‘t start new thread。
```

解释原因：线程资源限制： Docker 容器可能有默认或显式设置的资源限制，如 CPU 限制、内存限制和可用线程数限制。在这种情况下，pip 的进度条尝试创建新线程来处理进度条更新，这可能会超出容器允许的线程数上限。

解决方法：在dockerfile文件中增加一行关闭pip进度条展示
RUN pip config set global.progress_bar off

```text
 => [flask_app internal] load build definition from Dockerfile                                                                         0.0s
 => => transferring dockerfile: 982B                                                                                                   0.0s
 => ERROR [flask_app internal] load metadata for docker.io/library/python:3.8-slim                                                    60.4s
------
 > [flask_app internal] load metadata for docker.io/library/python:3.8-slim:
------
failed to solve: python:3.8-slim: failed to resolve source metadata for docker.io/library/python:3.8-slim: unexpected status from HEAD request to https://ustc-edu-cn.mirror.aliyuncs.com/v2/library/python/manifests/3.8-slim?ns=docker.io: 403 Forbidden
exit status 1

```

原因：在构建镜像时，Docker 在尝试从 ustc-edu-cn 镜像站获取 `python:3.8-slim` 镜像的元数据时被拒绝了（403 Forbidden）

解决方法：1.单独拉取该镜像 2.添加别的镜像源（可能清华源镜像有问题！！）



- **docker运行权限问题**

```text
OpenBLAS blas_thread_init: pthread_create failed for thread 1 of 4: Operation not permitted
OpenBLAS blas_thread_init: RLIMIT_NPROC -1 current, -1 max
```

解决方法：

```text
docker run --name zbparse-container --security-opt seccomp=unconfined zbparse
```



## Docker-Compose

### docker-compose安装：

**方式1：**从 Docker 20.10 开始，Docker 官方就将 Docker Compose 作为插件集成在 Docker Engine 中，所以在安装 Docker Engine 时，它也会一并安装 Docker Compose 插件。**无需额外安装**

验证安装

```text
docker compose version
```

**方式2：**安装独立版 Docker Compose 二进制文件

下载二进制文件（或者下载别人的镜像复制到服务器中的/usr/local/bin下）

```text
sudo curl -L "https://mirrors.aliyun.com/docker-ce/linux/static/stable/x86_64/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
```

赋予执行权限

```text
sudo chmod +x /usr/local/bin/docker-compose   
```

验证安装

```text
docker-compose --version
```



这两者功能基本一致，大部分命令和参数都是相同的，**只是命令前缀不同**。



### docker-compose.yml语法

**1.services**

**常见子配置**：

- **image**：指定服务使用的镜像。
- **build**：指定构建上下文或 Dockerfile 路径。
- **ports**：映射容器端口到主机端口。
- **environment**：设置环境变量。
- **volumes**：挂载主机目录或数据卷到容器。
- **depends_on**：定义服务启动顺序的依赖关系。

**2.networks**

定义和管理自定义网络，方便容器间通信。

**3.volumes**

定义数据卷，用于数据持久化或者在多个容器间共享数据。



```text
version: '3'

services:
  web_app:
    build:
      context: ./web_app          # 指定web_app的上下文
      dockerfile: Dockerfile      # 在./web_app下寻找Dockerfile
    container_name: web_app
    ports:
      - "8080:80"                # 将主机8080端口映射到容器的80端口
    environment:
      - DATABASE_HOST=db
      - DATABASE_USER=root
      - DATABASE_PASSWORD=root
      - DATABASE_NAME=my_database
    depends_on:
      - db
    networks:
      - my_network

  db:
    image: mysql:8
    container_name: mysql_db
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: my_database
    ports:
      - "3306:3306"
    volumes:
      - db_data:/var/lib/mysql    # 将命名数据卷挂载到MySQL数据目录
    networks:
      - my_network
    restart: always

networks:
  my_network:
    driver: bridge                # 使用桥接网络驱动

volumes:
  db_data:

```



```text
build:
  context: ./web_app
  dockerfile: Dockerfile

build: ./web_app    #两种写法是等效的
```



****

### docker-compose常用命令

**构建镜像：**这个命令根据 docker-compose.yml 中各服务的配置构建镜像。如果你修改了 Dockerfile 或者项目代码需要打包进镜像时，就需要运行该命令来构建新的镜像。

```shell
docker-compose build
```

**启动容器：**

```shell
docker-compose up -d
```

- 如果之前没有容器，就新建一个。如果有，若对比现有容器中的配置和compose文件中的配置，若不一样则删除旧容器，用新的配置重新启动一个新容器。

- `-d` 参数让所有容器在后台运行

- 一般镜像不会重建，除非你修改了构建上下文（`build:` 下的 Dockerfile ），或者 加了`--build`参数



**进入容器：**

```shell
docker compose exec -it filebrowser sh
```

注意！一般进入数据卷挂载，直接在宿主机上操作容器内部就可以了！！！！！



**启动或更新指定服务**

镜像不存在或需重建时才构建，配置变动（或镜像变动）则重建容器，都没变则一动不动

```shell
docker compose up -d pyapp
```



***！！注意在使用docker-compose命令时，需要指定服务名，不能使用容器名！！！*** 



**查看所有服务状态**

```shell
docker-compose ps
```

**查看服务的日志输出**(flask_app为服务名)

```shell
docker-compose logs flask_app --since 1h  #只显示最近 1 小时
```

**停止运行的容器**

只停止容器，容器还在磁盘上，`docker compose start`或`docker compose up`时容器会带着之前的状态继续运行

```shell
docker-compose stop 
docker-compose stop flask_app  #指定某个服务
```

**停止并删除所有由 docker-compose 启动的容器、网络等（默认不影响挂载卷）。**

下次再 `up`，就得重新创建容器、网络，但镜像不受影响（除非你显式用 `--rmi` 删除镜像）。

```shell
docker-compose down   #不能单独指定
```

**删除停止的容器**

默认不会删除网络、卷

```shell
docker-compose rm   # 删除所有已停止的服务容器（会交互式询问要不要删除）
docker-compose rm flask_app # 只删除指定服务的容器	
```

**启动服务**

```shell
docker-compose start #启动所有停止的..
docker-compose start flask_app
```

 **重启服务（停止+启动）**

```shell
docker-compose restart
docker-compose restart flask_app #指定某个服务
```



### docker-compose容器名

**1：默认**

当使用docker-compose build 构建镜像时，镜像的标签格式通常是 `项目名_服务名`

docker-compose up生成的容器名默认是 `项目名_服务名_索引号`

**索引号（Index Number）**：这是一个用于区分多个相同服务实例的数字索引。第一次启动时为 `1`，后续实例依次递增。

**服务名**是指你在docker-compose中设置的服务名称（services:下的名称）。

**项目名**默认是**当前目录名**，如果你使用了 `-p` 选项指定项目名，则使用指定的项目名,如

```shell
docker-compose -p my_custom_project up -d
```



**2.在docker-compose.yml中指定容器名**

```text
version: '3'
services:
  web:
    build: .
    container_name: my_custom_web_container
    restart: always   设置自动启动
    ports:
      - "5005:5005"
  db:
    image: mysql:5.7
    container_name: my_custom_db_container
    environment:
      MYSQL_ROOT_PASSWORD: example

```

启动docker-compose中的单独一个服务：docker-compose up -d web   web为里面的一个服务名



### 关于Docker-compose的重要说明

1.**服务名和网络别名**可用于**同一网络**内容器之间的通信（如Java→MySQL、前端→后端，因为同一个网络会自动注册一个DNS名）  尽管容器名也可以作为通信的名字、但是极不推荐！！！

2.服务名可用于docker compose系列命令

3.容器名主要用于docker命令：`docker ps`、`docker logs`、`docker exec` 等命令时指定目标容器。



**4.一个Docker-compose文件定义的各个服务默认使用同一个网络**。

5.不同的Docker-compose文件可以使用相同的镜像，如mysql8.0，此时docker会自动进行镜像复用，而不会重复下载。

```text
db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: db_project1
    ports:
      - "3306:3306"
    volumes:
      - db_data_project1:/var/lib/mysql
```



| **问题类型**   | **可能的冲突**                             | **解决方案**                                                 |
| -------------- | ------------------------------------------ | ------------------------------------------------------------ |
| **端口冲突**   | 容器监听相同的宿主机端口（如 `3306:3306`） | 在不同 `docker-compose.yml` 中映射不同端口（如 `3307:3306`） |
| **数据卷冲突** | 多个 MySQL 实例共享相同的 `/var/lib/mysql` | 使用不同的 `volume` 名称，或只运行一个 MySQL 实例            |
| **网络冲突**   | 默认网络可能导致 DNS 解析失败              | 在 `docker-compose.yml` 里创建独立的 `network`               |



**最佳实践1**

1. 部署单一 MySQL 服务，不用为每个应用都起一个数据库容器；  
2. 将需要访问该 MySQL 的服务挂到一个专用网络，且**各自用不同数据库用户和库名**；  
3. 若要更严格隔离（应用之间不可相互访问），可以为每个应用拆分出独立网络，只在网络交集里放 MySQL（MYSQL可以加入不同的网络）；  
4. 数据卷 `mysql-data` 仍然只对应一个实例，数据完全由该实例管控。



**最佳实践2**

JAVA应用和MYSQL一起定义在自己的Compose中（天生处于同一个网络，且应用之间隔离）

1.**独立数据卷**：给每个实例用不同的 named volume（如 `mysql-data-app1`、`mysql-data-app2`），保证数据互不干扰；

2.**不同端口映射**：在各自的 Compose 里把宿主机端口映射到 `3306`，如 `3306:3306`、`3307:3306`；





**最佳实践1：**

**让 MySQL 作为一个单独的 Compose 服务**

**在其他 `docker-compose.yml` 里连接到这个 MySQL 容器的网络**

1.创建一个 Docker 网络

```text
docker network create my_shared_network
```

2.创建 MySQL 的 `docker-compose-mysql.yml`

```yml
version: '3'
services:
  mysql:
    image: mysql:8
    volumes:
      - mysql-data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: my_database
    ports:
      - "3306:3306"
    networks:
      - my_shared_network
networks:
  my_shared_network:
    external: true

```

3.在 `docker-compose-app.yml` 里连接这个 MySQL

```yml
version: '3'
services:
  web_app:
    image: my_web_app
    environment:
      - DATABASE_HOST=mysql
      - DATABASE_USER=root
      - DATABASE_PASSWORD=root
      - DATABASE_NAME=my_database
    ports:
      - "8080:8080"
    networks:
      - my_shared_network
networks:
  my_shared_network:
    external: true
```



### 实践：部署微服务集群

**需求**：将之前学习的cloud-demo微服务集群利用DockerCompose部署



**实现思路**：

① 查看课前资料提供的cloud-demo文件夹，里面已经编写好了docker-compose文件

② 修改自己的cloud-demo项目，将数据库、nacos地址都命名为docker-compose中的服务名

③ 使用maven打包工具，将项目中的每个微服务都打包为app.jar

④ 将打包好的app.jar拷贝到cloud-demo中的每一个对应的子目录中

⑤ 将cloud-demo上传至虚拟机，利用 docker-compose up -d 来部署

#### compose文件

查看课前资料提供的cloud-demo文件夹，里面已经编写好了docker-compose文件，而且每个微服务都准备了一个独立的目录：

![image-20210731181341330](https://pic.bitday.top/i/2025/03/19/u659u8-2.png)

内容如下：

```yaml
version: "3.2"

services:
  nacos:
    image: nacos/nacos-server
    environment:
      MODE: standalone
    ports:
      - "8848:8848"
  mysql:
    image: mysql:5.7.25
    environment:
      MYSQL_ROOT_PASSWORD: 123
    volumes:
      - "$PWD/mysql/data:/var/lib/mysql"
      - "$PWD/mysql/conf:/etc/mysql/conf.d/"
  userservice:
    build: ./user-service
  orderservice:
    build: ./order-service
  gateway:
    build: ./gateway
    ports:
      - "10010:10010"
```

可以看到，其中包含5个service服务：

- `nacos`：作为注册中心和配置中心
  - `image: nacos/nacos-server`： 基于nacos/nacos-server镜像构建
  - `environment`：环境变量
    - `MODE: standalone`：单点模式启动
  - `ports`：端口映射，这里暴露了8848端口
- `mysql`：数据库
  - `image: mysql:5.7.25`：镜像版本是mysql:5.7.25
  - `environment`：环境变量
    - `MYSQL_ROOT_PASSWORD: 123`：设置数据库root账户的密码为123
  - `volumes`：数据卷挂载，这里挂载了mysql的data、conf目录，其中有我提前准备好的数据
- `userservice`、`orderservice`、`gateway`：都是基于Dockerfile临时构建的



查看mysql目录，可以看到其中已经准备好了cloud_order、cloud_user表：

![image-20210801095205034](https://pic.bitday.top/i/2025/03/19/u65i0j-2.png)

查看微服务目录，可以看到都包含Dockerfile文件：

![image-20210801095320586](https://pic.bitday.top/i/2025/03/19/u65rdk-2.png)

内容如下：

```dockerfile
FROM java:8-alpine
COPY ./app.jar /tmp/app.jar
ENTRYPOINT java -jar /tmp/app.jar
```



#### 修改微服务配置

因为微服务将来要部署为docker容器，而容器之间互联不是通过IP地址，而是通过容器名。这里我们将order-service、user-service、gateway服务的mysql、nacos地址都修改为基于服务名的访问。

如下所示：

```yaml
spring:
  datasource:
    url: jdbc:mysql://mysql:3306/cloud_order?useSSL=false
    username: root
    password: 123
    driver-class-name: com.mysql.jdbc.Driver
  application:
    name: orderservice
  cloud:
    nacos:
      server-addr: nacos:8848 # nacos服务地址
```



#### 打包

接下来需要将我们的每个微服务都打包。因为之前查看到Dockerfile中的jar包名称都是app.jar，因此我们的每个微服务都需要用这个名称。

可以通过修改pom.xml中的打包名称来实现，每个微服务都需要修改：

```xml
<build>
  <!-- 服务打包的最终名称 -->
  <finalName>app</finalName>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
  </plugins>
</build>
```

打包后：

![image-20210801095951030](https://pic.bitday.top/i/2025/03/19/u66mhd-2.png)

#### 拷贝jar包到部署目录

编译打包好的app.jar文件，需要放到Dockerfile的同级目录中。注意：每个微服务的app.jar放到与服务名称对应的目录，别搞错了。

user-service：

![image-20210801100201253](https://pic.bitday.top/i/2025/03/19/u66jrt-2.png)

order-service：

![image-20210801100231495](https://pic.bitday.top/i/2025/03/19/u675a6-2.png)

gateway：

![image-20210801100308102](https://pic.bitday.top/i/2025/03/19/u67av0-2.png)

#### 部署

最后，我们需要将文件整个cloud-demo文件夹上传到虚拟机中，理由DockerCompose部署。

上传到任意目录：

![image-20210801100955653](https://pic.bitday.top/i/2025/03/19/u67ku9-2.png)

部署：

进入cloud-demo目录，然后运行下面的命令：

```sh
docker-compose up -d
```



