Docker 从入门到部署微服务:八年 Java 开发的实战笔记

251 阅读9分钟

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部署微服务,就已经能解决日常工作中大部分问题了。剩下的,边用边学就行。

如果这篇笔记能让你少掉几个坑,省出几小时摸鱼时间,那它就值了。(完)