恋上蓝花楹

Docker容器化部署实战:从入门到避坑指南

Docker容器化部署

Docker已经成为了现代应用部署的标准工具,但从”能用”到”用好”,中间隔着不少坑。这篇文章总结我在实际项目中踩过的那些坑,希望能帮你少走弯路。

一、镜像构建:小即是美

很多人写的Dockerfile,镜像动辄1GB起步。这不是Docker的问题,是构建方式的问题。

❌ 错误示范

FROM openjdk:17
COPY . /app
WORKDIR /app
RUN ./mvnw package
CMD ["java", "-jar", "target/app.jar"]

这样构建出来的镜像包含Maven、源码、编译产物,臃肿且不安全。

✅ 正确做法:多阶段构建

# 构建阶段
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests

# 运行阶段
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

这样最终镜像只有JRE和jar包,体积从1GB+降到150MB左右。

二、日志处理:别让磁盘爆满

Docker默认的日志驱动会把所有stdout输出写到/var/lib/docker/containers下,长期运行后磁盘会被日志撑爆。

解决方案

方法1:配置日志轮转

# /etc/docker/daemon.json
{
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  }
}

方法2:使用独立的日志驱动

docker run --log-driver=json-file 
  --log-opt max-size=10m 
  --log-opt max-file=5 
  your-image

或者直接使用syslog、fluentd等专业日志方案。

三、网络陷阱:localhost不是万能的

容器内访问宿主机服务,用localhost往往会失败。这是因为容器有独立的网络命名空间。

正确姿势

  • Linux:使用 host.docker.internal172.17.0.1(docker0网关)
  • macOS/Windows:Docker Desktop提供了 host.docker.internal 域名
  • 通用方案--network host 模式(但失去了网络隔离)
# 连接宿主机的MySQL
spring:
  datasource:
    url: jdbc:mysql://host.docker.internal:3306/mydb

四、时区问题:为什么时间不对?

容器默认使用UTC时区,这会导致日志时间、业务时间都与本地时间差8小时。

# 方式1:设置环境变量
docker run -e TZ=Asia/Shanghai your-image

# 方式2:挂载时区文件
docker run -v /etc/localtime:/etc/localtime:ro your-image

# Dockerfile中设置
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

五、健康检查:别让僵尸容器活着

容器”活着”不代表应用”正常”。Java应用可能因为OOM或者死锁,进程还在但无法响应请求。

HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 
  CMD curl -f http://localhost:8080/actuator/health || exit 1

K8s中对应配置:

livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 10
readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5

六、资源限制:别让一个容器拖垮整台机器

docker run 
  --memory="512m"         # 内存限制
  --memory-swap="1g"      # 内存+交换分区限制
  --cpus="1.5"            # CPU核心数
  --cpu-shares=512        # CPU权重
  --pids-limit=100        # 进程数限制
  your-image

Java应用特别注意:JVM感知不到容器内存限制,可能导致OOM。务必添加JVM参数:

java -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -jar app.jar

七、安全建议:最小权限原则

  • 不要用root运行应用:添加非root用户
  • 只读文件系统:--read-only
  • 限制capabilities:--cap-drop ALL --cap-add CHOWN
  • 使用安全扫描:docker scout quickview
FROM eclipse-temurin:17-jre-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --chown=appuser:appgroup target/*.jar app.jar
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

写在最后

Docker很强大,但”能跑”和”生产可用”之间,隔着很多细节。希望这些实战经验能帮你避开我踩过的坑。

容器化不是目的,稳定可靠的交付才是。工具是为业务服务的,别为了用而用。

wulilele

我是一名热爱科技与AI的软件工程师。