Docker 从入门到部署微服务:八年 Java 开发的实战笔记
作为一名写了八年 Java 的开发者,我对 “部署” 的恐惧曾持续了很久 —— 本地跑通的项目,到测试环境就报 “类找不到”;生产环境 JDK 版本比开发环境低两个版本;微服务集群部署时,每个服务器的配置文件改得手抽筋…… 直到五年前开始用 Docker,这些 “环境玄学” 问题才彻底成为历史。
这篇文章从 Java 开发的实际场景出发,带你从 Docker 基础一路走到微服务部署,全是实战干货 —— 没有多余的理论,只有 “照着做就能成” 的步骤和八年踩坑总结。
一、先聊为什么 Java 开发者必须学 Docker?
别觉得 Docker 是运维的事,对 Java 开发来说,它解决的全是咱们日常头疼的问题:
1. 最痛的 “环境一致性” 问题
“我这能跑啊”—— 这句话在团队协作中出现的频率,曾和NullPointerException一样高。原因很简单:开发、测试、生产的环境(JDK 版本、依赖库、配置文件、端口占用)千差万别。
Docker 用 “容器” 把应用和环境打包成一个整体,不管在哪台机器上跑,里面的 JDK、依赖、配置全一样。八年经验:用了 Docker 后,“环境问题” 排查时间减少 80% 。
2. 微服务部署的 “简化神器”
现在谁还写单体应用?微服务少则三五几个,多则几十上百,每个服务要部署到不同服务器,还得配 Nginx、MySQL、Redis…… 手动部署一次能累瘫。
Docker+Docker Compose 能一键启动所有服务,端口冲突、依赖缺失这些问题,用配置文件就能搞定。我曾带团队把一个 12 个服务的微服务集群部署时间从 “半天” 压到 “5 分钟”。
3. 开发效率的 “隐形加速器”
新同事入职,搭环境要一天?用 Docker 的话,拉个镜像、启动容器,10 分钟就能跑通项目。测试同学要验证不同 JDK 版本的兼容性?启动两个不同基础镜像的容器就行,不用在服务器上装一堆 JDK。
二、Docker 核心概念:用 Java 开发者能懂的话讲透
别被 “镜像”“容器” 这些词唬住,用 Java 类比一下就懂了:
| Docker 概念 | 通俗理解 | Java 类比 |
|---|---|---|
| 镜像(Image) | 打包好的 “安装包”,包含应用和运行环境 | 编译好的jar包(但比 jar 多了运行环境) |
| 容器(Container) | 镜像运行起来的实例,可启动、停止、删除 | 用java -jar启动的进程(但更隔离) |
| 仓库(Repository) | 存放镜像的地方 | Maven 仓库(存放 jar 包的地方) |
| Docker Compose | 多容器编排工具 | 类似 Spring Context,管理多个服务的依赖和启动顺序 |
核心逻辑就一句话:用镜像定义环境和应用,用容器运行实例,用 Compose 管理多个容器(微服务) 。
三、实战第一步:Docker 基础操作(半小时上手)
1. 安装 Docker(以 CentOS 为例)
# 安装Docker引擎
yum install -y docker-ce docker-ce-cli containerd.io
# 启动Docker服务
systemctl start docker
# 设置开机自启(关键,不然服务器重启后容器全没了)
systemctl enable docker
# 验证是否安装成功(出现版本号就ok)
docker --version
Windows/mac 用户直接装Docker Desktop,图形化界面,更简单。
2. 第一个 Docker 命令:跑个 Hello World
# 从仓库拉取hello-world镜像(类似从Maven仓库下载jar)
docker pull hello-world
# 运行镜像,生成容器(类似java -jar执行jar)
docker run hello-world
执行后看到 “Hello from Docker!”,说明成功了。这一步的本质:从 Docker Hub(官方仓库)拉了个最小镜像,启动容器后输出一句话就退出。
3. 核心命令(记这几个够日常用)
# 查看本地所有镜像(类似查看本地Maven仓库的jar)
docker images
# 查看正在运行的容器(类似jps命令看进程)
docker ps
# 查看所有容器(包括停止的)
docker ps -a
# 停止容器(类似kill进程)
docker stop 容器ID/容器名
# 删除容器(停止后才能删)
docker rm 容器ID/容器名
# 删除镜像(先删依赖它的容器)
docker rmi 镜像ID/镜像名
四、Java 开发者必会:把 Spring Boot 项目打包成 Docker 镜像
这是日常开发最常用的场景 —— 把自己写的 Spring Boot 应用做成镜像,到处跑。
1. 准备一个 Spring Boot 项目
假设项目已经能打成 jar 包(demo-0.0.1-SNAPSHOT.jar),放在/opt/project目录下。
2. 编写 Dockerfile(核心!定义镜像规则)
在 jar 包同级目录新建Dockerfile文件(无后缀),内容:
# 基础镜像:用官方的Java 8镜像(相当于服务器上装了JDK8)
FROM openjdk:8-jdk-alpine
# 作者信息(可选,规范而已)
MAINTAINER oldjava@example.com
# 把本地的jar包复制到容器里,并重命名为app.jar(简化名字)
COPY demo-0.0.1-SNAPSHOT.jar /app.jar
# 容器启动时执行的命令(类似java -jar)
ENTRYPOINT ["java", "-jar", "/app.jar"]
# 暴露端口(项目的端口,比如Spring Boot默认8080)
EXPOSE 8080
八年经验注解:
- 基础镜像选
alpine版本(体积小,比如openjdk:8-jdk-alpine只有 100 多 MB,比完整版小 70%); COPY命令路径别写错,本地路径是相对于 Dockerfile 的位置;EXPOSE只是声明端口,实际启动时还要用-p映射(后面说)。
3. 构建镜像
在 Dockerfile 所在目录执行:
# 构建镜像,-t指定镜像名:版本号(规范:项目名:版本)
docker build -t demo:1.0 .
看到 “Successfully built xxxxxxxx” 说明构建成功,用docker images能看到demo:1.0。
4. 启动容器运行项目
# 启动容器,-d后台运行,-p映射端口(宿主端口:容器端口),--name指定容器名
docker run -d -p 8080:8080 --name demo-app demo:1.0
此时访问服务器的8080端口,就能看到 Spring Boot 的接口了。
五、微服务部署:用 Docker Compose 管理多服务
真实的微服务项目不止一个应用,还得有 MySQL、Redis、Nginx 这些依赖。手动启动每个容器太麻烦,用Docker Compose一键搞定。
1. 安装 Docker Compose
# 下载compose(Linux)
curl -L "https://github.com/docker/compose/releases/download/v2.20.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# 加执行权限
chmod +x /usr/local/bin/docker-compose
# 验证
docker-compose --version
2. 编写 docker-compose.yml(核心配置)
假设微服务有三个部分:
-
自己的 Spring Boot 应用(
app-service) -
MySQL 数据库(
mysql) -
Redis 缓存(
redis)
在项目根目录新建docker-compose.yml:
version: '3.8' # compose版本,选3.x以上
services:
# 1. MySQL服务
mysql:
image: mysql:5.7 # 用MySQL 5.7镜像
container_name: ms-mysql # 容器名
ports:
- "3306:3306" # 映射端口
environment:
- MYSQL_ROOT_PASSWORD=123456 # root密码
- MYSQL_DATABASE=ms_db # 自动创建的数据库名
volumes:
- mysql-data:/var/lib/mysql # 数据持久化(关键!不然容器删了数据没了)
restart: always # 容器挂了自动重启
# 2. Redis服务
redis:
image: redis:5.0 # Redis 5.0镜像
container_name: ms-redis
ports:
- "6379:6379"
volumes:
- redis-data:/data # 持久化Redis数据
restart: always
# 3. 自己的微服务应用
app-service:
build: ./app # Dockerfile所在目录(相对于当前yml文件)
image: app-service:1.0 # 构建的镜像名
container_name: ms-app
ports:
- "8080:8080"
depends_on:
- mysql # 依赖mysql,启动顺序:先启动mysql再启动app
- redis # 依赖redis
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/ms_db # 注意:这里的host是mysql容器名
- SPRING_REDIS_HOST=redis # Redis的host是redis容器名
restart: always
# 声明数据卷(用于持久化数据)
volumes:
mysql-data:
redis-data:
关键说明:
- 服务间通过 “容器名” 通信(比如 app 连接 MySQL 的 host 是
mysql,不是localhost); depends_on只保证启动顺序,不保证服务就绪(比如 MySQL 启动了但还没初始化好,app 可能连不上,后面说解决办法);volumes是核心,避免容器删除后数据丢失(八年踩坑:第一次用 Docker 没配这个,删容器时把数据库数据全清了,被测试骂了半天)。
3. 一键启动所有服务
在docker-compose.yml所在目录执行:
# 构建并启动所有服务(-d后台运行)
docker-compose up -d
执行后,MySQL、Redis、app-service 会按顺序启动。用docker-compose ps查看所有服务状态,用docker-compose logs -f app-service查看应用日志。
4. 常用 Compose 命令
# 停止并删除所有服务(容器、网络)
docker-compose down
# 重启某个服务(比如app-service)
docker-compose restart app-service
# 重新构建镜像并启动(当Dockerfile或代码改了)
docker-compose up -d --build
六、八年踩坑总结:这些坑 90% 的 Java 开发者都会掉
1. 容器内的时间和宿主机不一致
Java 程序打印的日志时间比实际晚 8 小时?因为 Docker 容器默认时区是 UTC,和北京时间差 8 小时。
解决:在 Dockerfile 里加时区配置:
# 解决时区问题(alpine镜像)
RUN apk --update add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
2. 微服务启动时依赖服务还没就绪
depends_on只能保证启动顺序,但 MySQL 启动后需要 30 秒初始化,app 这时候连接会报 “连接拒绝”。
解决:在 app 里加重试机制(比如 Spring 的@Retryable),或用专门的工具(如wait-for-it)。
3. 镜像体积太大,拉取慢
Java 镜像很容易几百 MB,部署时拉半天。
解决:用多阶段构建减小体积:
# 第一阶段:编译项目(用带Maven的镜像)
FROM maven:3.6-openjdk-8 AS builder
WORKDIR /app
COPY pom.xml .
COPY src ./src
# 打包(跳过测试,加快构建)
RUN mvn package -Dmaven.test.skip=true
# 第二阶段:运行(用轻量的alpine镜像)
FROM openjdk:8-jdk-alpine
WORKDIR /app
# 只复制第一阶段打包好的jar,其他编译文件全丢弃
COPY --from=builder /app/target/*.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
这样构建的镜像,比直接打包小 50% 以上。
4. 容器日志占满磁盘
容器默认会把日志输出到/var/lib/docker/containers,时间长了能占满磁盘。
解决:在 docker-compose.yml 里限制日志大小:
services:
app-service:
# ...其他配置
logging:
driver: "json-file"
options:
max-size: "10m" # 单个日志文件最大10MB
max-file: "3" # 最多保留3个文件
5. 生产环境别用latest标签
总有人图省事,镜像标签用latest(默认标签),但latest会自动指向最新版本,可能导致不同环境跑的镜像不一致。
解决:用固定版本号(如app-service:1.2.3),每次发布更新版本号。
七、最后:Docker 不是银弹,但能解决 Java 开发 80% 的部署问题
八年开发下来,Docker 给我的最大感受是 “解放”—— 不用再为环境配置扯皮,不用手动登录十几台服务器部署,不用怕删容器删到数据。它不是什么高深技术,核心就是 “用代码定义环境”,这恰恰是程序员最擅长的事。
对于 Java 开发者来说,学 Docker 不用追求精通底层原理,先会用Dockerfile打包应用,能用docker-compose部署微服务,就已经能解决日常工作中大部分问题了。剩下的,边用边学就行。
如果这篇笔记能让你少掉几个坑,省出几小时摸鱼时间,那它就值了。(完)