AI摘要

本文介绍了作者如何将Spring Boot应用容器化并发布到镜像仓库的全过程。文章分为五部分,包括思想转变、编写Dockerfile、本地实践、发布镜像和使用docker-compose编排全家桶。作者分享了在实践中遇到的问题和解决方案,强调了Docker带来的确定性和效率,使得项目环境配置更加标准化和易于管理。

还记得我第一次接手一个老项目时,为了在本地跑起来,折腾了整整两天:安装特定版本的MySQL、配置Redis、设置各种环境变量...那种“在我的机器上明明是好的”的挫败感,让我对一种叫“容器”的技术产生了强烈的好奇。Docker,对我而言,最初的需求并非为了高深的微服务部署,只是想让我和我的应用,都能拥有一份“说走就走”的自由

这篇文章,就是我学习并使用Docker,将最熟悉的Spring Boot应用一步步打包、运行、并最终发布到镜像仓库的全过程。我会重点分享那些在文档上看不到、只有亲手实践才会撞上的“墙壁”。

第一部分:思想转变——从“手动配环境”到“一键拥有环境”

在Docker之前,我的世界是这样的:

  1. 开发机:安装JDK 8, Maven, MySQL, Redis...
  2. 测试环境:可能需要JDK 11,不同版本的MySQL...
  3. 处理依赖冲突,处理端口占用,心力交瘁。

Docker带来的是一种“降维打击”式的思想转变:我们交付的不仅仅是一个Jar包,而是一个完整的、隔离的、包含了应用本身及其全部运行时的“集装箱”

这个集装箱里,操作系统、JDK、我们的应用,都被精密地打包在一起。无论是在本地、测试服务器还是云上,只要你能运行Docker,这个集装箱就能以完全相同的方式启动。

第二部分:第一步——编写Dockerfile,我们的“造船说明书”

一切始于一个名为Dockerfile的文件。它就像一份详细的造船说明书,告诉Docker如何一步步地把我们的应用构建成一个镜像(Image)。

我的第一个(也是糟糕的)Dockerfile:

一开始,我想得很简单:找个有JDK的基础镜像,把Jar包扔进去,运行就好了。

FROM openjdk:8-jdk
COPY target/my-app-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

看起来没问题,对吧?但这里隐藏着几个我后来才意识到的问题:

  1. 基础镜像过于庞大openjdk:8-jdk镜像基于完整的操作系统,包含了各种我们不需要的开发工具,体积高达500MB以上。
  2. 以root用户运行:默认情况下,容器内的进程以root用户身份运行,这有潜在的安全风险。
  3. 不够优化:每次代码改动,整个Jar包都要重新复制,无法利用Docker的构建缓存。

进化后的Dockerfile:使用多阶段构建

在踩了坑并学习了最佳实践后,我重写了一份更专业的Dockerfile:

# 第一阶段:构建阶段 (命名为 builder)
FROM maven:3.8.5-openjdk-11 AS builder
WORKDIR /app
COPY pom.xml .
# 利用Docker缓存层:如果pom.xml没变,则直接使用缓存的依赖
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package -DskipTests

# 第二阶段:运行阶段
FROM openjdk:11-jre-slim
WORKDIR /app

# 创建一个非root用户并切换
RUN groupadd -r springapp && useradd -r -g springapp springapp
USER springapp

# 从构建阶段复制打好的Jar包,注意改了个简单的名字
COPY --from=builder /app/target/my-app-0.0.1-SNAPSHOT.jar app.jar

# 暴露端口(只是一个声明,便于理解)
EXPOSE 8080

# 使用非阻塞式的启动命令,支持优雅关闭
ENTRYPOINT ["java", "-jar", "app.jar"]

这样写的好处:

  • 镜像极小:运行环境使用-jre-slim(仅包含运行环境),最终镜像体积从500MB+降到了200MB以内。
  • 安全:使用非root用户运行应用。
  • 构建速度快:分离了依赖安装和源码编译,如果pom.xml没变,mvn dependency:go-offline这一层会被缓存,无需重复下载依赖。
  • 职责分离:构建环境与运行环境完全隔离,更干净。

第三部分:本地实践——构建与运行,遭遇“墙壁”

在项目根目录下,执行构建命令:

docker build -t my-spring-app .

看着一层层指令执行,最后成功构建出镜像,成就感满满。但紧接着,运行它的时候,问题来了。

墙壁一:“端口拒接”的幽灵

我运行了:docker run my-spring-app。控制台日志显示Spring Boot启动成功。但当我用浏览器访问localhost:8080时,却得到了“连接被拒绝”。

排查与解决:
我忘了,容器拥有自己独立的网络命名空间。EXPOSE 8080只是声明,并没有将容器的8080端口映射到宿主机的端口上。正确的运行命令需要-p参数进行端口映射:

docker run -p 8080:8080 my-spring-app

-p <宿主机端口>:<容器端口> 这个语法让我记了很久。

墙壁二:如何连接本地数据库?

我的应用需要连我本地电脑(宿主机)上的MySQL。在应用的application.yml里,数据库地址我写的是localhost:3306。但在容器内部,“localhost”指的是容器自己,它里面可没有MySQL。

排查与解决:
在Docker for Mac/Windows桌面版上,可以从容器内部使用一个特殊的域名host.docker.internal来访问宿主机。所以,我需要将数据库地址改为jdbc:mysql://host.docker.internal:3306/my_db

并且,在运行容器时,通过-e参数传入环境变量来覆盖配置是更优雅的做法:

docker run -p 8080:8080 -e "SPRING_DATASOURCE_URL=jdbc:mysql://host.docker.internal:3306/my_db" my-spring-app

这让我理解了配置外部化的重要性,为后续的云原生部署打下了基础。

第四部分:发布镜像——拥有你的“集装箱编号”

本地测试没问题后,我想把它分享给同事。这就需要把镜像推送到一个镜像仓库,比如Docker Hub(公共的)或私有的Harbor。

  1. 打标签(Tagging):镜像的默认标签是latest,但这不利于版本管理。最佳实践是打上版本标签。

    docker tag my-spring-app my-dockerhub-username/my-spring-app:1.0.0
  2. 登录(Login)

    docker login
  3. 推送(Push)

    docker push my-dockerhub-username/my-spring-app:1.0.0

我遇到的“权限拒绝”问题:
第一次推送时,我直接用了docker push my-spring-app:1.0.0,结果失败。Docker Hub要求,除非是推送到官方库(如library/ubuntu),否则镜像名必须以你的用户名开头,格式为<用户名>/<镜像名>:<标签>。这个规则让我印象深刻。

第五部分:进阶一步:使用docker-compose编排“全家桶”

我的应用依赖MySQL和Redis。难道每次都要同事先手动启动这些中间件吗?不,Docker的精髓在于“编排”。docker-compose.yml文件可以定义和运行多个容器。

version: '3.8'
services:
  app:
    image: my-spring-app:1.0.0
    ports:
      - "8080:8080"
    environment:
      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/my_db
      - SPRING_REDIS_HOST=redis
    depends_on:
      - mysql
      - redis

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: my_db
    volumes:
      - mysql_data:/var/lib/mysql

  redis:
    image: redis:6-alpine

volumes:
  mysql_data:

现在,只需要在项目目录下执行一条命令,整个应用栈(应用、数据库、缓存)就全部启动并互连好了:

docker-compose up

这种体验,真正让我感受到了容器化带来的便捷和强大。

结语:从繁琐中解放

回顾这段旅程,Docker带给我的最大价值,是确定性效率。我再也不需要写冗长的环境配置文档。项目的Dockerfiledocker-compose.yml就是最好的文档,它们能精准地复现出所需的运行环境。

这个过程,就像从一个手工作坊的工匠,变成了现代化流水线上的工程师。我交付的不再是一堆散乱的零件,而是一个个标准化的、随时可以投入运行的“集装箱”。这种掌控感,是每个追求效率的开发者都无法抗拒的。

版权声明 ▶ 本网站名称:黄磊的博客
▶ 本文标题:Docker入门到实践:将Spring Boot应用容器化并发布
▶ 本文链接:https://www.huangleicole.com/backend-related/84.html
▶ 转载本站文章需要遵守:商业转载请联系站长,非商业转载请注明出处!!

如果觉得我的文章对你有用,请随意赞赏