devops系列(三) Docker 容器化入门:告别“在我电脑上能跑“
Docker 容器化入门告别在我电脑上能跑一、那个让全公司程序员血压飙升的瞬间你有没有经历过这种场景周五下午五点你正美滋滋地收拾东西准备下班测试小哥突然在群里你“哥你昨天提测的功能又挂了。”你一脸懵“不可能啊我本地跑得好好的”十分钟后你蹲在测试机前面排查发现是 JDK 版本不对——你本地用的是 JDK 17测试环境还是 JDK 8。改完 JDK又发现某个系统环境变量没配。好不容易环境变量搞定了依赖库的版本又对不上……等全部修完窗外天都黑了晚饭变夜宵。说白了这就是经典的在我电脑上能跑综合症。环境不一致就像你搬家时只带了家具没带螺丝到了新家发现怎么装都装不上。那有没有一种办法能把我家原封不动地搬到测试家、生产家呢有这就是 Docker。二、Docker 到底是啥用搬家给你讲明白我第一次接触 Docker 的时候也被一堆概念绕晕了镜像、容器、仓库……听着就头大。后来我想了个办法用搬家来类比一下子就通透了。镜像Image 搬家前的打包清单你要搬家得先把家里的东西一样一样打包好列个清单。这个清单加上打包好的箱子就是镜像。它包含了你的应用代码、运行环境、依赖库、系统配置一应俱全。容器Container 搬进去住的房子镜像本身不会跑就像打包好的箱子不会自己打开。你得找个房子把箱子拆开家具摆好才能真正住进去。这个住进去的状态就是容器。你可以用同一个镜像在不同的服务器上启动无数个容器。仓库Registry 搬家公司/仓库你打包好的东西可以存到仓库里。下次要搬到别的地方直接从仓库拉过来就行。Docker Hub 就是最大的公共仓库你们公司也可以搭自己的私有仓库。还有一个更形象的比喻是俄罗斯套娃镜像是最外面那个完整的套娃容器是你打开后里面那一层正在运行的状态。每个容器都是独立的互不干扰。三、手写 Dockerfile从能跑到跑得优雅光理解概念不够咱们来动手写一个 Dockerfile。假设你有一个 Spring Boot 项目用 Maven 构建打包后是个 JAR 文件。最朴素的写法可能是这样的# 阶段一直接基于一个带 JDK 的完整系统镜像 FROM ubuntu:22.04 # 安装 JDK 和 Maven RUN apt-get update apt-get install -y openjdk-17-jdk maven # 把代码拷进去 COPY . /app WORKDIR /app # 编译打包 RUN mvn clean package -DskipTests # 运行 CMD [java, -jar, target/myapp.jar]这段 Dockerfile 能跑吗能。但问题大了去了镜像体积爆炸基于 Ubuntu还装了 JDK 和 Maven动辄 1GB 起步。构建效率低每次改一行代码Maven 依赖都要重新下载。安全风险高带着完整的编译工具跑到生产环境没必要。优化思路分层构建 多阶段构建Docker 的构建是分层缓存的。咱们要做的就是让变化少的层在前面变化多的层在后面这样改动代码时就能复用缓存。另外多阶段构建的意思是编译用一个镜像运行用另一个更小的镜像最后只把 JAR 文件带过去。优化后的 Dockerfile 长这样# 阶段一编译 FROM maven:3.9-eclipse-temurin-17-alpine AS builder WORKDIR /app # 关键先把 pom.xml 拷进去单独下载依赖 # 这样只要 pom.xml 不变这层缓存就一直复用 COPY pom.xml . RUN mvn dependency:go-offline # 再把源码拷进去编译 COPY src ./src RUN mvn clean package -DskipTests # 阶段二运行 FROM eclipse-temurin:17-jre-alpine WORKDIR /app # 只把编译好的 JAR 从 builder 阶段拷过来 COPY --frombuilder /app/target/*.jar app.jar # 暴露端口 EXPOSE 8080 # 运行 ENTRYPOINT [java, -jar, app.jar]关键点在哪maven:3.9-eclipse-temurin-17-alpine和eclipse-temurin:17-jre-alpine都是 Alpine 版本体积比 Ubuntu 小得多。先拷pom.xml单独下载依赖利用了 Docker 分层缓存后续改源码不用重新下依赖。--frombuilder实现了多阶段构建最终镜像只包含 JRE 和 JAR没有 Maven、没有源码干净清爽。效果对比我实际测的数据方案镜像体积二次构建时间单阶段 Ubuntu 方案~1.2GB3-5 分钟多阶段 Alpine 方案~180MB20-30 秒这差距相当于从绿皮火车升级到了高铁。四、Docker Compose一键启动整个小区单个容器搞定了但真实项目很少只有一个服务。你的 Web 应用可能要连 Redis、MySQL、Nginx手动一个个docker run太麻烦了。Docker Compose 就是来解决这个问题的。你可以把它理解成小区物业管家一份配置文件一键启动整个小区的所有住户。下面这份docker-compose.yml可以直接拿去用version:3.8services:# Web 应用 app:build:.container_name:my-appports:-8080:8080environment:-SPRING_DATASOURCE_URLjdbc:mysql://mysql:3306/mydb-SPRING_REDIS_HOSTredisdepends_on:-mysql-redisnetworks:-my-network# MySQL mysql:image:mysql:8.0container_name:my-mysqlenvironment:MYSQL_ROOT_PASSWORD:root123MYSQL_DATABASE:mydbvolumes:-mysql_data:/var/lib/mysqlports:-3306:3306networks:-my-network# Redis redis:image:redis:7-alpinecontainer_name:my-redisvolumes:-redis_data:/dataports:-6379:6379networks:-my-network# 数据卷容器删了数据还在volumes:mysql_data:redis_data:# 自定义网络容器之间可以通过服务名互相访问networks:my-network:driver:bridge几个重点解释一下build: .表示在当前目录找 Dockerfile 构建应用镜像。depends_on只是控制启动顺序不保证服务已就绪。生产环境建议加健康检查。volumes挂载数据卷这是血泪教训——不挂 volume容器一删MySQL 数据全没了。networks让所有容器在同一个自定义网络里它们可以通过服务名如mysql、redis直接互相访问不用记 IP。启动命令就一行docker-composeup-d停止也是一行docker-composedown要连数据一起清掉docker-composedown-v五、几个不得不说的踩坑记录Docker 用起来爽但坑也不少。我挑两个印象最深的分享给你。坑一容器里时区不对日志时间差了 8 小时有一次排查线上问题看日志发现时间全是 UTC跟北京时间差了 8 小时。找半天才发现是 Alpine 镜像默认没有时区数据。解决办法在 Dockerfile 里加上# Alpine 镜像需要手动安装时区数据 RUN apk add --no-cache tzdata ENV TZAsia/Shanghai如果是非 Alpine 镜像可以直接挂载宿主机的时区文件ENV TZAsia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime echo $TZ /etc/timezone坑二数据没挂 volume容器删了数据没了这个坑我踩过两次刻骨铭心。第一次是本地搭测试环境MySQL 容器跑得好好的我手贱执行了docker system prune -a把所有没挂 volume 的容器数据一起清了。测试数据全没哭都没地方哭。第二次是生产环境迁移同事直接docker run起了个新容器没挂 volume。结果服务器重启后容器自动重建数据库直接归零。核心原则有状态服务必须挂 volume。MySQL、Redis、Elasticsearch但凡存数据的volume 就是你的救命稻草。六、容器调试几个常用的小技巧容器跑起来了但出问题怎么排查分享几个我常用的命令# 看容器日志dockerlogs-f容器名# 进容器内部看看dockerexec-it容器名 /bin/sh# 如果是基于 Ubuntu/Debian 的镜像可以用 bashdockerexec-it容器名 /bin/bash# 查看容器资源占用dockerstats# 查看容器网络信息dockerinspect 容器名|grepIPAddress进容器后你可以像操作普通 Linux 一样排查看进程、看文件、ping 其他容器。不过要注意很多精简镜像比如 Alpine、distroless连curl、ping都没有需要提前安装或者换镜像调试。七、总结Docker 到底改变了什么写到这里咱们来回顾一下问题环境不一致导致在我电脑上能跑成了程序员噩梦。方案Docker 用镜像把应用和环境一起打包做到一次构建到处运行。实现通过合理的 Dockerfile 分层和多阶段构建把镜像体积从 GB 级压到 MB 级用 Docker Compose 一键管理多容器环境。验证镜像体积和构建效率大幅提升环境一致性得到保障。Docker 不是什么高深莫测的黑科技它本质上就是一个更优雅的打包和搬家工具。理解了这一点你会发现它其实很好上手。当然Docker 只是容器化的起点。再往后还有 Kubernetes、Service Mesh、CI/CD 流水线……路还长着呢。八、聊聊你的经历你在工作中遇到过在我电脑上能跑的崩溃时刻吗是用 Docker 解决的还是踩过别的坑关于 Dockerfile 优化、Docker Compose 使用或者容器化迁移你有什么独门技巧欢迎在评论区交流咱们一起进步