资讯详情

资讯详情

Java Web项目容器化部署全流程:Docker镜像构建、MySQL编排与排错实战

项目写完离上线还差最后一公里。这最后一公里就是把Java Web项目从开发机搬到服务器上让它在另一台机器上也能稳定跑起来。我跟着黑马程序员的Java Web课程做项目时第一次手动部署就把自己折腾得够呛本地跑得好好的换台机器就报JDK版本不一致、Tomcat配置丢了、MySQL连不上……后来系统学了一遍Docker之后才明白容器化部署解决的就是这种“环境搬家”的痛。这篇笔记是我在学习过程中整理的部署全流程从Docker环境准备、镜像构建、容器启动到MySQL容器化与编排再到部署现场的排错记录按步骤写给你适合刚学完Java Web、准备把第一个后台项目部署到服务器上的同学。看完这套流程你再看别人的部署文档会发现骨架其实都一样。1. 项目部署到底在部署什么1.1 传统部署方式为什么让人头疼先想一个问题一个Java Web项目从开发到上线到底搬过去的是什么早期我理解的部署就是“把war包丢进Tomcat的webapps目录”再改一下数据库连接配置。真上手时才发现这只是冰山一角。一台干净的服务器上你要先装JDK版本还得和本地一样再装Tomcat配JVM内存参数、字符集还要装MySQL初始化库表、导入数据、设置账号权限如果项目用了Redis、Nginx那就更多了。每一步都靠人肉记住“我是怎么装的”稍有偏差运行时的报错五花八门。我印象最深的一次本地JDK是8u202服务器上是8u291项目里用了一个反射相关的工具库在高版本JDK下行为变了接口数据顺序错乱了几条排查了两个小时才定位到是JDK版本差异。这种事就是典型的“环境不一致问题”。如果你手动搭过两三次环境一定会有一个体会真正难的不是代码是让代码在别人的环境里跑起来时所有隐藏约定全部一致。1.2 Docker把“环境”打包成了镜像Docker解决这个问题的思路很直接把“应用程序 它运行所需的完整环境”一起打包成一个只读模板这个模板叫镜像。镜像里包含着操作系统层、JDK、Tomcat、依赖库、配置文件你的Jar包在中间某个层里。用镜像去创建运行实例这个运行实例就是容器同一台机器上可以同时跑多个互不干扰的容器。你可以把它想象成集装箱运输以前你要把一车货物运到国外每个国家对接标准都不一样到了港口还得重新装箱。而Docker像把货物装进统一标准的集装箱不管船运、卡车还是吊机用的都是同一套接口到了哪里都能无缝装卸。对应到技术上不论在Windows开发机上、Linux服务器上还是云主机上只要Docker引擎在镜像构建出来的容器行为就是一致的。Java后端开发对Docker尤其友好。我们平时用Spring Boot打成Jar包再用java -jar启动本质上运行时依赖的是一个完整JRE。Docker镜像可以把JRE和Jar包放在一起启动容器就等于执行启动命令部署过程从“装环境放包改配置”简化成“构建镜像运行容器”。1.3 学习部署时先确立的三个目标给自己定个清晰的学习边界能少走很多弯路。我在学这块时没有一上来就啃Docker源码或者研究容器底层原理而是先定了三个可执行的目标所有知识都围绕它们展开第一能把本地的Spring Boot项目打成一个镜像并在Docker容器里成功跑起来第二能把MySQL也容器化让Web应用和数据库分别运行在独立容器中并且它们之间能正常通信第三能用一套配置把数据库和应用一起启动也就是后面要讲的docker-compose编排。这三个目标完成之后再回头看docker run参数、网络模式、数据卷才觉得不抽象。2. 环境准备与Docker基础操作2.1 Docker Desktop还是Linux版怎么选部署环境通常分两类一类是你自己练习用的开发机Windows或macOS都有这类适合装Docker Desktop另一类是真正的Linux服务器那就直接安装Docker Engine并通过命令行操作。如果你是Windows用户装Docker Desktop前有个需要注意的点新版本Docker Desktop默认基于WSL2后端需要系统开启“适用于Linux的Windows子系统”和“虚拟机平台”这两项Windows功能。安装完成后Docker Desktop会帮你管理和配置好WSL2环境不需要手动装一个完整的Linux虚拟机。我建议安装之前先把Windows功能打开并且在BIOS里确认虚拟化技术已启用否则就会出现网上常搜到的“virtualization support not detected”报错。这个问题的排查细节我在后面的常见问题章节里会展开讲。如果你手头只有一台云服务器环境是CentOS 7或Ubuntu 22.04那就安装Docker Engine。推荐直接使用官方安装脚本不要用旧的yum install docker方式去装因为版本太老。安装完成后立刻执行两步操作systemctl start docker systemctl enable dockerenable是为了让Docker在开机时自动启动这一步在服务器上很关键很多人重启服务器后发现容器没起来就是因为漏了这个。2.2 配置镜像加速地址Docker默认从Docker Hub拉取镜像国内网络访问这个仓库经常很慢拉一个几百兆的镜像能卡死人。解决办法是给Docker配置镜像加速地址。在Docker Desktop的界面上路径是Settings - Docker Engine在json配置里加入registry-mirrors在Linux上则修改/etc/docker/daemon.json。配置完重启Docker生效。我的配置文件长这样{ registry-mirrors: [ https://docker.m.daocloud.io, https://hub-mirror.c.163.com, https://registry.docker-cn.com ] }镜像加速地址的具体可用性会变化不用死记某个地址你可以在自己使用的云厂商控制台里找到分配给个人的加速地址填入后重启。配置完成后用docker pull mysql:8.0测试拉取速度几秒钟能拉完说明加速生效。2.3 验证Docker环境是否可用环境装完别急着开始先跑一遍冒烟命令确认整个链路是通的。docker version docker ps docker run hello-worlddocker version能看到客户端和服务端版本如果Server段没输出说明Docker引擎没起来。docker ps列出所有运行中的容器初次执行结果为空是正常现象。docker run hello-world会拉取一个极小的测试镜像并运行如果控制台能输出Hello from Docker!那说明引擎、镜像仓库、容器运行链路都没问题。这一步花不了两分钟但它能帮你把“Docker没装好”和“项目部署命令写错”两类问题隔离开。我见过不少人在后面部署时出问题排查半天发现是Docker Desktop服务根本没启动。3. 让Java Web项目容器化3.1 先本地打包再写Dockerfile部署第一步永远是先在本地把项目打包。我用的是Spring Boot项目项目里用的构建工具是Maven所以先在项目根目录执行打包的命令mvn clean package -DskipTests打包成功后target目录下会生成一个xxx.jar文件。建议先用java -jar xxx.jar在本地跑一遍确认项目能正常启动。这一步非常关键——它把问题切分成了“应用本身的问题”和“容器化的问题”。如果本地都启动不了那就没必要继续往下走先回去改代码。确认没问题后在项目根目录新建一个文件命名为Dockerfile。所谓Dockerfile就是用来构建镜像的脚本文件里面写了构建镜像时需要执行的一系列指令。我的第一个Dockerfile长这样FROM eclipse-temurin:8-jre WORKDIR /app COPY target/demo-0.0.1-SNAPSHOT.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]逐行解释一下FROM指定基础镜像相当于你在一台已经装好JRE的机器上开始干活WORKDIR设置工作目录容器里后面的命令默认在这个目录执行COPY把刚才打好的Jar包复制到镜像的/app目录并改名为app.jarEXPOSE向外界声明容器内应用运行在8080端口ENTRYPOINT是容器启动时执行的命令也就是我们平时启动Spring Boot的命令。这里有个经验之谈基础镜像优先选择带完整JRE的镜像而不建议用openjdk:8-jdk-alpine这种老镜像。因为新版Spring Boot在一些发行版上没有兼容的字体库和加密组件会出现一些莫名其妙的底层异常。选eclipse-temurin或bellsoft这类社区维护活跃的镜像踩坑少很多。3.2 构建镜像并启动容器Dockerfile写好后在项目根目录执行构建命令docker build -t demo-web:1.0 .这条命令会读取当前目录下的Dockerfile按照里面的步骤构建镜像。-t给镜像命名为demo-web版本号是1.0。构建过程中你会看到一层一层的输出最后出现Successfully built和Successfully tagged就说明构建完成。用docker images可以查看镜像列表。接下来运行容器docker run -d --name demo-web-app -p 8080:8080 demo-web:1.0参数拆开看-d表示后台运行--name给容器命名方便后续管理-p 8080:8080做了端口映射将宿主机8080端口映射到容器内8080端口。这个映射很重要因为在浏览器里访问的是宿主机的IP和端口如果没有映射外部访问不到容器内部的服务。启动后执行docker ps可以看到容器状态是Up。再验证一下应用有没有起来docker logs demo-web-app curl http://localhost:8080/api/hello如果日志里有Started Application in X seconds这样的输出而且curl返回了正常结果说明整个容器化流程已经跑通了。到这一步你已经完成了应用本身的容器化部署。3.3 容器投产后怎么改代码刚上手时最容易犯的一个错误是改完代码、本地重新打包后以为刷新一下页面就能看到新效果。容器是无状态的运行实例镜像才是模板。改代码的正确流程是重新打包Jar包重新执行docker build构建新镜像再基于新镜像创建新容器。旧容器最好先停下来再替换docker stop demo-web-app docker rm demo-web-app docker build -t demo-web:1.1 . docker run -d --name demo-web-app -p 8080:8080 demo-web:1.1这里补充一个实际部署中经常用的技巧把宿主机的一个目录挂载到容器里让容器日志直接写到宿主机。运行容器时加上-v /home/ubuntu/logs:/app/logs即使容器被删除日志文件依然留在宿主机上。我在部署时都会顺手加这个参数排查问题的时候能少跑几趟重启流程。4. MySQL容器化与容器间通信4.1 先跑一个MySQL容器单机部署Spring Boot应用只是第一步项目要访问数据库所以MySQL也得容器化。运行MySQL容器的常用命令如下docker run -d --name demo-mysql \ -e MYSQL_ROOT_PASSWORDroot123 \ -e MYSQL_DATABASEdemo_db \ -p 3306:3306 \ -v mysql-data:/var/lib/mysql \ mysql:8.0这里有几个参数需要重点理解。MYSQL_ROOT_PASSWORD指定root密码MYSQL_DATABASE会在容器首次初始化时自动创建同名数据库。-v mysql-data:/var/lib/mysql是一个数据卷挂载目的是把MySQL的数据存在宿主机上否则一旦容器被删数据也就没了。数据卷是Docker管理的一块独立存储区域容器删了它还在下个容器还能复用。数据库容器起来之后还需要初始化表结构和数据。可以把SQL文件导入进去docker exec -i demo-mysql sh -c exec mysql -uroot -proot123 demo_db init.sqldocker exec是在运行中的容器里执行命令这里把宿主机上的init.sql文件内容喂给了MySQL客户端从而完成建表和初始数据导入。导入完成后可以通过docker exec -it demo-mysql mysql -uroot -p进入容器内的MySQL客户端验证数据。4.2 为什么不能用localhost连MySQL这时候问题来了应用容器怎么连数据库容器很多第一次做容器部署的人application.yml里写的还是开发时用的localhost结果应用容器一启动就连不上数据库。原因在于每个容器有自己独立的网络命名空间。容器里的localhost指向的是容器自己不是宿主机上的MySQL更不是另一个容器。实际上不同容器之间通信正确的做法是让它们加入同一个自定义网络。在自定义网络里Docker会为每个容器提供DNS解析你可以直接用容器名当主机名。操作步骤如下docker network create demo-net docker network connect demo-net demo-mysql docker network connect demo-net demo-web-app然后在Spring Boot项目的配置文件里把数据库地址改成容器名spring: datasource: url: jdbc:mysql://demo-mysql:3306/demo_db?useSSLfalseserverTimezoneAsia/Shanghai username: root password: root123这里demo-mysql是容器名也是网络中的主机名。重新构建镜像并启动容器后数据库连接就通了。如果你不想重新构建镜像也可以在启动时用环境变量覆盖配置比如-e SPRING_DATASOURCE_URLjdbc:mysql://demo-mysql:3306/demo_db...效果相同。4.3 用docker-compose整合Web应用与数据库手动启动两个容器还要连接网络、指定顺序步骤多了容易乱。这时候Docker Compose就派上用场了。它的作用是用一个YAML文件把多个容器的配置都写好执行一条命令全部启动。我项目里的docker-compose.yml长这样services: mysql: image: mysql:8.0 container_name: demo-mysql restart: always environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: demo_db TZ: Asia/Shanghai ports: - 3306:3306 volumes: - mysql-data:/var/lib/mysql healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -uroot, -proot123] interval: 10s timeout: 5s retries: 5 app: build: . container_name: demo-web-app restart: always depends_on: mysql: condition: service_healthy ports: - 8080:8080 environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/demo_db?useSSLfalseserverTimezoneAsia/Shanghai SPRING_DATASOURCE_USERNAME: root SPRING_DATASOURCE_PASSWORD: root123 volumes: mysql-data:这里我特别说明两点。第一depends_on只是让app容器在mysql容器之后启动但MySQL容器起来了不代表数据库初始化完成真正能用的做法是加上healthcheck让Compose等MySQL的健康检查通过后再启动应用。第二build: .表示app服务直接使用当前目录的Dockerfile构建镜像而mysql服务直接拉取官方镜像。执行启动命令docker compose up -d docker compose logs -f-d后台运行logs -f实时打印所有服务的日志。以后要停掉整个项目用docker compose down。这套方式就是线上部署的标准骨架很多生产级部署方案本质上就是它的加强版。5. 部署现场常见问题排查5.1 Docker Desktop启动失败提示虚拟化未检测到Windows上安装Docker Desktop时如果启动后弹窗提示类似“Virtualization support not detected”或“Docker Desktop failed to start”说明系统虚拟化没有正常开启。排查顺序是先打开任务管理器切到“性能”标签页查看下方“虚拟化”这一项是“已启用”还是“已禁用”。如果显示禁用需要进BIOS将Intel VT-x或AMD-V对应的开关打开。不同品牌电脑的BIOS菜单位置不一样常见路径是Advanced - CPU Configuration - Intel Virtualization Technology设为Enabled。如果BIOS已经开启但Docker Desktop仍报警还有一个常见区域是Windows功能面板。在“启用或关闭Windows功能”中勾选“虚拟机平台”和“适用于Linux的Windows子系统”然后重启电脑。重启后打开PowerShell执行wsl --status能看到WSL内核版本就说明基础环境没问题。5.2 镜像拉取失败或慢得离谱拉镜像报错failed to resolve reference或者一直卡着不动基本是网络问题。先确认配置的镜像加速地址是否生效在Docker Engine配置里确认registry-mirrors确实存在。然后手动拉一个小镜像测试比如docker pull busybox如果速度正常再拉MySQL这类大镜像。如果用了加速地址还是慢可以尝试重启Docker服务并重新拉取。另外基础镜像的标签建议明确到具体版本不要省略因为不同大版本的镜像体积差异很大。比如mysql:8.0比mysql:latest体积小也更稳定。对于Java项目JRE镜像比完整JDK镜像体积更小生产环境尽量用JRE基础镜像。5.3 容器启动后应用报错或访问不通遇到容器起来但页面打不开先看日志。docker logs demo-web-app能帮你定位大部分问题。最常见的几类错误我在部署时都遇到过。第一类是数据库连接报错比如UnknownHostException这一般是容器名解析失败检查两个容器是否在同一个自定义网络里数据库地址是否使用的是容器名。第二类是端口冲突启动时报Bind for 0.0.0.0:8080 failed: port is already allocated说明宿主机端口被占用换个映射端口比如-p 8081:8080即可。第三类是防火墙和云安全组问题服务器上端口已经映射了但外部还是访问不了需要检查云服务商的安全组规则是否放行了对应端口比如8080。容器内部环境无法直接调试这也是刚开始不习惯的地方。我的做法是先确保本地能启动、能访问然后再打包进镜像进入容器内部侦查环境用docker exec -it demo-web-app /bin/sh查看容器里的文件、环境变量和进程状态。5.4 时区与字符集问题容器默认时区是UTC日志时间和本地时间差8个小时。排查问题时看着日志总觉得不对劲后来才发现是时区问题。解决方式有两种一是在Dockerfile里加ENV TZAsia/Shanghai二是运行容器时加环境变量-e TZAsia/Shanghai。如果基础镜像没有自带时区数据还要执行apt-get install -y tzdata或apk add tzdata。Alpine系列镜像默认没有timezone数据这一点要注意。MySQL连接串里的serverTimezoneAsia/Shanghai也要保持一致否则从数据库读出来的时间可能和JVM时区校准不一致。字符集方面数据库连接串中建议加上characterEncodingutf8数据库初始化脚本里的表结构也要显式指定DEFAULT CHARSETutf8mb4否则中文数据存进去后容易乱码。5.5 访问容器内的MySQL失败最后再单独说一个高频问题容器里的MySQL在外面连不上。现象是本机用Navicat等数据库客户端连localhost:3306报Access denied或者Connection refused。先确认端口映射是否生效docker ps里看PORTS列确认是0.0.0.0:3306-3306/tcp而不是127.0.0.1:3306。如果是云服务器还需要在安全组放行3306端口。Access denied则多数是密码认证方式和驱动版本不匹配MySQL 8.0默认的认证插件是caching_sha2_password如果客户端驱动版本太旧就会报错。解决方法是让MySQL创建用户时使用mysql_native_password或者在Spring Boot项目里升级mysql-connector-java到8.x版本。6. 一点额外的心得部署这件事第一次总会手忙脚乱但它的套路其实非常固定。我现在部署一个新的Java Web项目基本就是三步本地打包并验证成功写Dockerfile用docker-compose编排依赖服务。掌握这套流程后你会发现它不仅能部署Java项目部署Python的Flask应用、Node的Express服务、前端的Nginx静态站点思路完全一致——都是把一个应用及其运行环境装进镜像再通过网络让多个容器协作。最后分享一个我踩过几次坑之后养成的习惯每个项目根目录放一份deploy.sh把打包、构建、启动、查看日志的命令按顺序写好。部署的时候不靠记忆敲命令直接执行脚本。这样做的好处是每次部署的流程都是可复现的而且一旦某条命令有问题改脚本比回忆命令靠谱得多。如果你也在学Java Web的路上卡点正好在部署这一步希望这篇笔记能帮你少走几个弯路。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →