从零开始:Dockerfile 编写与 Spring Cloud 项目部署到 Docker Compose
大家好!今天我们来聊聊如何用 Docker 把一个 Spring Cloud 项目跑起来。如果你是个完全的小白,不用担心,我会一步步带你搞清楚 Dockerfile 里的每一行是什么意思,以及如何把你的服务塞进 Docker Compose 里,和中间件一起愉快地运行。我们还会讲讲 Docker 启动时的 -v
参数和"挂载",以及 context
路径的设置问题。
什么是 Dockerfile?
简单来说,Dockerfile 就是一个"说明书",告诉 Docker 如何打包你的程序。它就像一个食谱,列出了所有原料和步骤,最后烤出一个可以运行的"程序蛋糕"(Docker 镜像)。有了镜像,你就可以用 Docker Compose 像搭积木一样把服务和中间件组合起来。
假设你有一个 Spring Cloud 项目,可能包含多个微服务(比如用户服务、订单服务),每个服务都需要跑在自己的容器里。我们先从单个服务的 Dockerfile 开始讲起,然后再扩展到 Docker Compose。
Dockerfile 详解:以 Spring Cloud 服务为例
我们以一个简单的 Spring Cloud 服务(比如"用户服务")为例,假设它是用 Java 写的,跑在一个 Spring Boot 项目里,打包成了一个 user-service.jar
文件。下面是一个典型的 Dockerfile:
dockerfile
# 第一行:选择一个基础镜像
FROM openjdk:11
# 第二行:设置工作目录
WORKDIR /app
# 第三行:复制文件到容器里
COPY target/user-service.jar /app/user-service.jar
# 第四行:暴露端口
EXPOSE 8080
# 第五行:运行命令
CMD ["java", "-jar", "user-service.jar"]
每一行是什么意思?
-
FROM openjdk:11
- 作用:这是基础镜像,相当于给你的程序找一个"操作系统+运行环境"。因为 Spring Cloud 是 Java 项目,我们选了 OpenJDK 11(Java 11 的运行环境)。
- 小白理解 :就像你要做饭,得先有个厨房。
openjdk:11
就是这个厨房,里面已经装好了 Java 11 的工具。
-
WORKDIR /app
- 作用:设置容器里的工作目录,之后的操作都在这个文件夹里进行。
- 小白理解 :就像你在厨房里找了个桌子(
/app
),接下来都在这个桌子上切菜、炒菜。
-
COPY target/user-service.jar /app/user-service.jar
- 作用 :把你本地的
user-service.jar
文件复制到容器里的/app
文件夹下。 - 小白理解 :你把家里做好的蛋糕(
user-service.jar
)搬到厨房的桌子上(/app
),名字还是叫user-service.jar
。
- 作用 :把你本地的
-
EXPOSE 8080
- 作用:告诉 Docker 这个容器会用 8080 端口跑服务,但只是声明,不是真的开放(开放端口得靠 Docker Compose 或运行命令)。
- 小白理解:就像你在厨房门口贴了个告示:"我这里会用 8080 号窗口送菜",但门还没开。
-
CMD ["java", "-jar", "user-service.jar"]
- 作用:容器启动时执行的命令,这里是运行你的 Spring Boot 应用。
- 小白理解 :相当于你告诉厨房:"开火,用 Java 这个锅,把
user-service.jar
这个蛋糕热一下端出去"。
怎么用这个 Dockerfile?
-
把上面内容保存成一个文件,名字就叫
Dockerfile
(没后缀)。 -
在项目根目录下,确保
target/user-service.jar
存在(通常用mvn package
打包生成)。 -
打开终端,跑这个命令生成镜像:
bashdocker build -t user-service:latest .
-t user-service:latest
:给镜像取名叫user-service
,版本是latest
。.
:告诉 Docker 在当前目录找 Dockerfile。
-
跑容器试试:
bashdocker run -p 8080:8080 user-service:latest
-p 8080:8080
:把本地的 8080 端口映射到容器的 8080 端口,这样你就能通过localhost:8080
访问服务。
Docker 启动时的 -v
是什么意思?挂载又是啥?
你可能听说过 docker run
时可以用 -v
参数,比如:
bash
docker run -p 8080:8080 -v /my/local/logs:/app/logs user-service:latest
这里多了个 -v /my/local/logs:/app/logs
,它是什么意思呢?别急,我们慢慢拆开。
什么是挂载?
挂载(Volume Mount)是 Docker 的一种功能,让你把本地电脑上的文件夹或文件"连接"到容器里的某个文件夹。简单来说,就是让容器能用你电脑上的东西,或者把容器里的东西保存到你电脑上。
- 小白理解 :想象你的容器是一个独立的小房子,里面有自己的桌子(
/app/logs
)。挂载就像在房子外接了一根管子,把你家里的抽屉(/my/local/logs
)和房子的桌子连起来。房子可以用抽屉里的东西,桌子上的东西也能存到抽屉里。
-v
的格式
-v
后面跟的是两个路径,用冒号 :
分开:
- 左边 (
/my/local/logs
):你电脑上的路径。 - 右边 (
/app/logs
):容器里的路径。
比如 -v /my/local/logs:/app/logs
:
- 意思是把你电脑上的
/my/local/logs
文件夹挂载到容器里的/app/logs
文件夹。 - 如果你的服务在
/app/logs
里写日志,这些日志会直接出现在你电脑的/my/local/logs
里。
为什么要用挂载?
-
数据持久化:
- 容器就像一个临时小房子,关掉就没了(除非你特意保存)。如果你的服务生成了日志、配置文件之类的东西,不挂载的话,容器一停这些数据就丢了。
- 挂载后,数据会存到你电脑上,容器重启也不会丢。
-
调试方便:
- 比如你的 Spring Cloud 服务需要读取一个本地的配置文件(比如
application.yml
),可以用挂载把文件传进去。
- 比如你的 Spring Cloud 服务需要读取一个本地的配置文件(比如
举个例子
假设你的 user-service
会生成日志到 /app/logs
,你想把日志保存到本地:
-
本地创建一个文件夹,比如
/home/user/logs
。 -
启动容器时加
-v
:bashdocker run -p 8080:8080 -v /home/user/logs:/app/logs user-service:latest
-
服务跑起来后,日志会出现在
/home/user/logs
里,你可以用文本编辑器直接看。
Docker Compose 登场:服务和中间件一起跑
你提到 Docker Compose 文件里只有中间件(比如 MySQL、Redis)的部署,但没写你的服务。别急,我们来扩展一下,把你的 Spring Cloud 服务加进去,还可以用挂载。
假设你的 docker-compose.yml
长这样:
yaml
version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: user_db
ports:
- "3306:3306"
redis:
image: redis:latest
ports:
- "6379:6379"
这是中间件的部署,跑了个 MySQL 和 Redis。我们把 user-service
加进去:
yaml
version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: user_db
ports:
- "3306:3306"
redis:
image: redis:latest
ports:
- "6379:6379"
user-service:
build:
context: ./user-service
dockerfile: Dockerfile
ports:
- "8080:8080"
depends_on:
- mysql
- redis
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/user_db?useSSL=false
- SPRING_REDIS_HOST=redis
volumes:
- /home/user/logs:/app/logs
context: ./user-service
是什么意思?
在 build
部分,context: ./user-service
告诉 Docker Compose 去哪里找构建镜像需要的文件(包括 Dockerfile 和你的 user-service.jar
)。这里的路径是相对于 docker-compose.yml
文件所在目录的。
- 小白理解 :就像你告诉管家(Docker Compose):"去我家旁边的
user-service
小店拿材料做蛋糕。"docker-compose.yml
是管家的家,./user-service
是从管家家门口出发的路。
如果 Spring Cloud 项目不在 Docker Compose 下属文件夹怎么办?
你提到你的 Spring Cloud 项目可能不在 docker-compose.yml
旁边的文件夹里,而是比它更高一级。比如你的目录可能是这样:
sql
my-spring-cloud-project/
├── user-service/
│ ├── Dockerfile
│ ├── target/
│ │ └── user-service.jar
├── order-service/
│ ├── Dockerfile
│ ├── target/
│ │ └── order-service.jar
├── docker-compose/
│ └── docker-compose.yml
在这里,docker-compose.yml
在 docker-compose/
文件夹里,而 user-service/
在上一级(my-spring-cloud-project/
)。如果你直接写 context: ./user-service
,Docker Compose 会找不到,因为从 docker-compose/
出发,旁边没有 user-service/
。
- 问题 :那
./user-service
是指哪个地址?是指我的 Spring Cloud 某个具体服务的 Dockerfile 的地址吗? - 答案 :是的,
context
指向的是某个服务的构建上下文,也就是包含 Dockerfile 和相关文件(比如 JAR 文件)的文件夹。但路径要从docker-compose.yml
的位置算起。
解决方案:调整路径
你需要用相对路径或绝对路径告诉 Docker Compose 正确的地址。假设 docker-compose.yml
在 docker-compose/
里,user-service/
在 my-spring-cloud-project/
:
-
用相对路径:
-
从
docker-compose/
往上一级是../
,然后再进user-service/
。 -
所以改成:
yamlbuild: context: ../user-service dockerfile: Dockerfile
-
-
用绝对路径(更明确,但不灵活):
-
假设你的项目在
/home/user/my-spring-cloud-project/
:yamlbuild: context: /home/user/my-spring-cloud-project/user-service dockerfile: Dockerfile
-
更新后的 docker-compose.yml
可能是这样:
yaml
version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: user_db
ports:
- "3306:3306"
redis:
image: redis:latest
ports:
- "6379:6379"
user-service:
build:
context: ../user-service
dockerfile: Dockerfile
ports:
- "8080:8080"
depends_on:
- mysql
- redis
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/user_db?useSSL=false
- SPRING_REDIS_HOST=redis
volumes:
- /home/user/logs:/app/logs
小白理解
- 相对路径
../user-service
:就像管家站在docker-compose/
门口,抬头一看,旁边没有user-service
,得先退回大门口(../
),再走进旁边的user-service
小店拿材料。 user-service
是具体服务的地址:是的,它就是你 Spring Cloud 项目里某个微服务(比如用户服务)的文件夹,里面有 Dockerfile 和 JAR 文件。
其他部分的含义
-
ports: - "8080:8080"
:- 把本地的 8080 端口映射到容器的 8080 端口。
- 小白理解:在店门口开了个 8080 号窗口,顾客能直接点单。
-
depends_on
:- 表示
user-service
依赖mysql
和redis
,启动时会先跑中间件。 - 小白理解:你得先把水电(MySQL、Redis)接好,店才能开张。
- 表示
-
environment
:- 设置环境变量,让你的 Spring Cloud 服务知道去哪连数据库和 Redis。
- 小白理解 :就像给店员写了张纸条:"水管接在
mysql
上,电线接在redis
上"。
-
volumes: - /home/user/logs:/app/logs
:- 把本地的
/home/user/logs
挂载到容器里的/app/logs
,保存日志。 - 小白理解:给店里接了根管子,把日志抽屉连到桌子。
- 把本地的
文件夹结构
你的项目可能是这样:
sql
my-spring-cloud-project/
├── user-service/
│ ├── Dockerfile
│ ├── target/
│ │ └── user-service.jar
├── docker-compose/
│ └── docker-compose.yml
user-service/
是你的服务代码目录,里面有 Dockerfile 和打包好的 JAR。docker-compose.yml
在docker-compose/
里,管理所有服务。- 本地的
/home/user/logs
文件夹需要你手动创建,用来存日志。
怎么跑?
-
进入
docker-compose/
目录,跑:bashdocker-compose up --build
--build
:每次都重新构建镜像,确保最新代码生效。
-
访问
localhost:8080
,应该能看到你的服务跑起来了!日志会出现在/home/user/logs
里。
小结与扩展
- Dockerfile :负责把你的 Spring Cloud 服务打包成镜像,核心是
FROM
、COPY
和CMD
。 - Docker Compose :像个大管家,把你的服务和中间件(MySQL、Redis)组合起来跑,靠
build
和environment
连接一切。 - 挂载(
-v
或volumes
):让容器和本地共享数据,比如保存日志、传配置文件。 context
路径 :告诉 Docker Compose 去哪找 Dockerfile 和文件,路径要从docker-compose.yml
的位置算起。如果项目层级更高,用../
或绝对路径调整。- 如果你有多个微服务(比如
order-service
),就再写一个 Dockerfile,然后在docker-compose.yml
里加一个服务,重复上面的步骤。
希望这篇博客能帮你从零开始搞定 Docker 部署,尤其是弄明白路径问题!有问题欢迎留言,我会尽量解答。