86、Docker容器化:Dockerfile编写、多阶段构建、Docker Compose、Kubernetes基础
说实话,容器化这件事,我入行前几年一直觉得是运维的活。直到有一次,我在本地跑得好好的Kotlin服务,部署到测试环境就崩了——环境不一致,JDK版本差一个小版本,依赖库路径不对,折腾了两天。从那以后,我老老实实学起了Docker。
这一章,咱们就聊聊Kotlin项目怎么容器化。我会把Dockerfile编写、多阶段构建、Docker Compose编排,还有Kubernetes入门,串起来讲。你跟着走一遍,基本就能上手了。
一、Dockerfile 编写:从零到镜像
Dockerfile 说白了就是一张「菜谱」。你告诉 Docker 要用什么基础镜像、装什么依赖、复制哪些文件、最后执行什么命令。我刚开始写的时候,总喜欢把所有东西塞到一个层里,结果镜像体积大得吓人。
1.1 基础结构
一个典型的 Kotlin 项目 Dockerfile 长这样:
# 使用官方 OpenJDK 17 作为基础镜像
FROM openjdk:17-jdk-slim AS builder
# 设置工作目录
WORKDIR /app
# 先复制构建文件,利用 Docker 缓存
COPY gradlew .
COPY gradle gradle
COPY build.gradle.kts .
COPY settings.gradle.kts .
# 下载依赖(这一步会缓存,除非 build.gradle 变了)
RUN ./gradlew dependencies
# 复制源码并构建
COPY src src
RUN ./gradlew build -x test
# 第二阶段:运行阶段
FROM openjdk:17-jre-slim
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
COPY gradlew 和 COPY src 分开写。这样只要不改构建脚本,依赖层就不会重新下载,构建速度快很多。
1.2 指令详解
| 指令 | 作用 | 注意事项 |
|---|---|---|
| FROM | 指定基础镜像 | 尽量用 slim 或 alpine 版本 |
| WORKDIR | 设置工作目录 | 相当于 cd,不存在会自动创建 |
| COPY | 复制文件到镜像 | 支持 --from 跨阶段复制 |
| RUN | 执行命令(构建时) | 每个 RUN 会新增一层,尽量合并 |
| EXPOSE | 声明端口 | 只是文档作用,实际映射靠 -p |
| ENTRYPOINT | 容器启动命令 | 推荐用 exec 格式,避免 shell 干扰 |
CMD ["java", "-jar", "app.jar"] 但没指定 ENTRYPOINT,结果想传 JVM 参数时发现 CMD 会被覆盖。后来统一用 ENTRYPOINT + CMD 组合,前者固定,后者可覆盖。
二、多阶段构建:瘦身大法
你想想看,一个 Kotlin 项目编译需要 JDK、Gradle、各种依赖,但运行时只需要 JRE 和一个 jar 包。如果只用单阶段构建,镜像里会塞满编译工具,体积轻松上 500MB。
多阶段构建就是解决这个问题的。它允许你在一个 Dockerfile 里写多个 FROM,前面的阶段负责编译,后面的阶段只拿产物。
2.1 为什么需要多阶段?
- 镜像体积小:从 500MB 降到 150MB 左右
- 安全:运行镜像里没有源码、构建工具,攻击面小
- 构建缓存友好:依赖层和源码层分离,改代码不重下依赖
2.2 实战对比
单阶段构建(不推荐):
FROM openjdk:17-jdk
WORKDIR /app
COPY . .
RUN ./gradlew build
EXPOSE 8080
CMD ["java", "-jar", "build/libs/app.jar"]
# 镜像大小:约 650MB
多阶段构建(推荐):
# 构建阶段
FROM gradle:7.6-jdk17 AS build
WORKDIR /app
COPY build.gradle.kts settings.gradle.kts ./
COPY src src
RUN gradle build -x test
# 运行阶段
FROM openjdk:17-jre-slim
WORKDIR /app
COPY --from=build /app/build/libs/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
# 镜像大小:约 180MB
核心思想: 构建环境用全功能镜像,运行环境用精简镜像。中间通过 COPY --from= 传递产物。
三、Docker Compose:多容器编排
实际项目中,你的 Kotlin 服务往往需要数据库、缓存、消息队列。一个个手动 docker run 太累了。Docker Compose 就是用来定义和运行多容器应用的。
3.1 docker-compose.yml 示例
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/mydb
- SPRING_REDIS_HOST=redis
depends_on:
- db
- redis
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: mydb
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
pgdata:
3.2 常用命令
| 命令 | 作用 |
|---|---|
docker compose up -d |
启动所有服务(后台) |
docker compose down |
停止并删除容器、网络 |
docker compose logs -f |
实时查看日志 |
docker compose build |
重新构建镜像 |
docker compose up,生产环境用 Kubernetes。Compose 适合本地调试,K8s 适合集群管理。
四、Kubernetes 基础:从容器到集群
Docker Compose 解决了单机多容器的问题。但如果你有几十台服务器,需要自动扩缩容、滚动更新、服务发现……这时候就该 Kubernetes 上场了。
说实话,K8s 内容很多,咱们这里只讲最核心的三个概念:Pod、Deployment、Service。
4.1 Pod:最小部署单元
Pod 是 K8s 里最小的调度单位。一个 Pod 可以包含一个或多个容器,它们共享网络和存储。通常一个 Pod 只跑一个容器。
apiVersion: v1
kind: Pod
metadata:
name: kotlin-app-pod
spec:
containers:
- name: app
image: my-kotlin-app:latest
ports:
- containerPort: 8080
4.2 Deployment:声明式更新
直接创建 Pod 不太灵活——如果 Pod 挂了,没人帮你重启。Deployment 就是管这个的。你告诉它「我要跑 3 个副本」,它会自动维持这个状态。
apiVersion: apps/v1
kind: Deployment
metadata:
name: kotlin-app
spec:
replicas: 3
selector:
matchLabels:
app: kotlin-app
template:
metadata:
labels:
app: kotlin-app
spec:
containers:
- name: app
image: my-kotlin-app:latest
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
注意: 加上 livenessProbe 和 readinessProbe,K8s 才能知道你的应用是否健康。我曾经没加探针,结果 Pod 启动失败但 K8s 以为它活着,流量全打过去了……
4.3 Service:稳定的访问入口
Pod 的 IP 是会变的。Deployment 扩容或重启后,IP 就变了。Service 提供一个固定的虚拟 IP 和 DNS 名称,把流量转发到后端的 Pod。
apiVersion: v1
kind: Service
metadata:
name: kotlin-app-service
spec:
selector:
app: kotlin-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
4.4 常用 kubectl 命令
| 命令 | 作用 |
|---|---|
kubectl get pods |
查看所有 Pod |
kubectl logs pod-name |
查看 Pod 日志 |
kubectl apply -f deploy.yaml |
应用配置 |
kubectl delete pod pod-name |
删除 Pod(Deployment 会自动重建) |
resources.requests 和 resources.limits。结果某个 Pod 内存泄漏,把整个节点的资源吃光了,其他 Pod 全被 OOM Kill。后来我学乖了,每个容器都加上资源限制。
五、总结
这一章咱们从 Dockerfile 写起,到多阶段构建瘦身,再到 Docker Compose 编排多服务,最后聊了 K8s 的核心概念。说白了,容器化就是让应用「一次构建,到处运行」。
我个人觉得,对于中小团队,Docker Compose 已经够用。如果业务量上来了,再上 K8s 也不迟。别一开始就追求大而全,先把基础打牢。
嗯,今天就到这儿。你动手写一个 Dockerfile,把之前的 Kotlin 项目容器化试试?