md_files/自学/Docker指南.md

1288 lines
34 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 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的代码共享一样。
- DockerHubDockerHub是一个官方的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`
-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可能遇到的问题**
- **linux中构建镜像问题**
```text
RuntimeError: cant 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. 若要更严格隔离(应用之间不可相互访问),可以为每个应用拆分出独立网络,只在网络交集里放 MySQLMYSQL可以加入不同的网络
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
```