2、容器化基础:Docker核心概念、Dockerfile编写、镜像构建与优化
聊到云原生中间件的高可用,容器化是绕不开的第一步。说白了,Docker 就是那个让应用“打包即运行”的神器。我刚开始接触时也觉得它就是个轻量级虚拟机,后来踩了坑才明白——它跟虚拟机完全是两码事。
2.1 Docker 核心概念:镜像、容器、仓库
这三个概念,你搞懂了,Docker 就入门了一半。
- 镜像(Image):一个只读的模板。里面装了操作系统、依赖库、应用代码。你可以把它理解成“安装包”。
- 容器(Container):镜像跑起来后的实例。有读写层,可以启动、停止、删除。说白了,就是“运行中的程序”。
- 仓库(Registry):存镜像的地方。Docker Hub 是公共的,企业内部一般用 Harbor 或 Nexus。
关键区别:镜像是一个静态文件,容器是一个动态进程。我见过不少新手把容器当虚拟机用,进去装软件、改配置,结果一重启全丢了。记住,容器是“用完即走”的,数据要挂载卷。
2.2 Dockerfile 编写:从零到一
Dockerfile 就是构建镜像的“配方”。我习惯把它写得像一份清晰的说明书,每一行都有明确目的。
2.2.1 基础指令
| 指令 | 作用 | 示例 |
|---|---|---|
| FROM | 指定基础镜像 | FROM openjdk:11-jre-slim |
| WORKDIR | 设置工作目录 | WORKDIR /app |
| COPY | 复制本地文件到镜像 | COPY target/app.jar . |
| RUN | 构建时执行命令 | RUN apt-get update && apt-get install -y curl |
| CMD | 容器启动时的默认命令 | CMD ["java", "-jar", "app.jar"] |
| EXPOSE | 声明容器监听端口 | EXPOSE 8080 |
2.2.2 实战:一个 Java 应用的 Dockerfile
# 第一阶段:构建
FROM maven:3.8.4-openjdk-11 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests
# 第二阶段:运行
FROM openjdk:11-jre-slim
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
我的习惯:用多阶段构建。第一阶段装 Maven、编译代码,第二阶段只留 JRE 和 jar 包。这样最终镜像能小一半以上。我在项目中遇到过,一个 800MB 的镜像优化后只有 200MB,部署速度快了不止一点。
2.3 镜像构建与优化:让镜像“瘦”下来
镜像太大,拉取慢、启动慢、磁盘占用高。你想想看,生产环境几百个 Pod,每个都 1GB,那得多浪费资源。
2.3.1 优化原则
- 选择最小基础镜像:alpine、slim 版本优先。比如
openjdk:11-jre-slim比openjdk:11小 300MB。 - 减少层数:每个 RUN、COPY 都会产生一层。用
&&合并命令。 - 清理缓存:安装完依赖后,顺手删掉
/var/cache/apt、/tmp等临时文件。 - 利用 .dockerignore:排除本地无关文件,比如
.git、node_modules、target。
2.3.2 构建命令与技巧
# 基础构建
docker build -t myapp:1.0 .
# 指定 Dockerfile 路径
docker build -f docker/Dockerfile.prod -t myapp:prod .
# 利用构建缓存(关键)
# 把变化少的指令放前面,比如 COPY pom.xml 放 COPY src 前面
我曾经踩过的坑:构建时没有利用缓存,每次改一行代码都要重新下载所有 Maven 依赖。后来我把 pom.xml 单独 COPY 并执行 mvn dependency:go-offline,这样只要 pom.xml 不变,依赖层就能复用。构建时间从 5 分钟降到了 30 秒。
2.3.3 镜像大小对比
| 优化阶段 | 镜像大小 | 说明 |
|---|---|---|
| 未优化 | ~800MB | 使用完整 JDK,单阶段构建 |
| 使用 slim 基础镜像 | ~400MB | 换成 jre-slim |
| 多阶段构建 | ~200MB | 分离构建环境和运行环境 |
| 进一步清理 | ~150MB | 删除临时文件,使用 alpine |
2.4 容器化最佳实践
嗯,这里要注意几个点,都是我在生产环境里摸爬滚打总结出来的。
- 一个容器一个进程:别在容器里跑 systemd 或 supervisord。需要多个进程就用多个容器,配合 docker-compose 或 Kubernetes。
- 日志输出到 stdout/stderr:Docker 会自动收集标准输出。别写日志文件,否则日志轮转、磁盘爆满都是麻烦事。
- 健康检查:加上
HEALTHCHECK指令,让 Docker 知道你的应用是否活着。
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
核心总结:容器化的本质不是“把虚拟机变小”,而是“把应用及其依赖打包成一个不可变单元”。镜像要小、构建要快、运行要稳。做到这三点,你的中间件容器化就成功了一大半。
下一章我们会聊 Docker Compose 和网络存储,到时候再深入讲讲多容器编排的那些事儿。