找回密码
 立即注册

QQ登录

只需一步,快速开始

peUfSYR.png
查看: 49|回复: 0

Day 29 存量应用容器化

[复制链接]

876

主题

13

回帖

2808

积分

管理员

积分
2808
发表于 2026-3-29 16:17:56 | 显示全部楼层 |阅读模式
老项目跑了三年,稳如老狗。结果领导开完会回来甩下一句:"这个月把咱们的系统全部容器化。"你看着那堆 crontab、手动部署脚本和写死在代码里的本地路径,感觉头皮一阵发麻。别慌,存量应用容器化这事儿,说难不难,说简单也没那么简单——关键是要有章法。

本文你将学到

  • 如何评估一个存量应用是否适合容器化(以及优先级排序)
  • 12-Factor App 改造的核心要点,哪些必须改、哪些可以缓
  • 数据层迁移的几种实战方案
  • 灰度切换策略,让你不用半夜提心吊胆

    ━━━━━━━━━━━━━━━━━━━━

    阅读时间: 约 12 分钟
    实操时间: 约 40 分钟
    难度等级: 中高级(需要有基础的 Docker 和运维经验)

    ━━━━━━━━━━━━━━━━━━━━

    一、容器化评估:不是所有应用都要第一批上车

    很多人一上来就动手写 Dockerfile,这就好比装修房子不量尺寸直接买家具——大概率翻车。

    容器化评估清单

    在动手之前,先给每个应用做个"体检":
    1. +------------------+--------+--------+--------+--------+
    2. |     评估维度     |  应用A |  应用B |  应用C |  应用D |
    3. +------------------+--------+--------+--------+--------+
    4. | 无状态程度       |  高    |  低    |  中    |  高    |
    5. | 外部依赖复杂度   |  低    |  高    |  中    |  低    |
    6. | 配置硬编码程度   |  无    |  严重  |  少量  |  无    |
    7. | 本地文件依赖     |  无    |  大量  |  少量  |  无    |
    8. | 定时任务依赖     |  无    |  有    |  有    |  无    |
    9. | 业务重要性       |  核心  |  核心  |  边缘  |  边缘  |
    10. | 容器化优先级     |  P0    |  P2    |  P1    |  P0    |
    11. +------------------+--------+--------+--------+--------+
    复制代码

    优先级排序原则:先易后难,先边缘后核心。就像搬家,你肯定先搬不怕摔的东西,传家宝最后搬。

  • P0(立即容器化):无状态、少依赖、配置规范的应用
  • P1(改造后容器化):需要少量改造就能上容器的应用
  • P2(规划后容器化):需要较大重构才能容器化的应用
  • P3(暂不容器化):强依赖本地硬件、特殊驱动等,容器化收益不大

    快速评估脚本

    写一个脚本快速扫描应用的"容器化友好度":
    1. #!/bin/bash
    2. # check-containerize-readiness.sh
    3. # 容器化就绪度检查脚本
    4. APP_DIR=${1:-.}
    5. SCORE=100
    6. echo "=============================="
    7. echo " 容器化就绪度检查"
    8. echo " 目标目录: $APP_DIR"
    9. echo "=============================="
    10. # 检查硬编码路径
    11. echo ""
    12. echo "[检查] 硬编码路径..."
    13. HARDCODED=$(grep -rn '/home/\|/usr/local/\|/opt/\|C:\\' "$APP_DIR" \
    14.   --include="*.py" --include="*.java" --include="*.js" \
    15.   --include="*.go" --include="*.php" --include="*.rb" 2>/dev/null | wc -l)
    16. if [ "$HARDCODED" -gt 0 ]; then
    17.   echo "  发现 $HARDCODED 处硬编码路径(扣 10 分)"
    18.   SCORE=$((SCORE - 10))
    19. else
    20.   echo "  未发现硬编码路径"
    21. fi
    22. # 检查硬编码端口
    23. echo ""
    24. echo "[检查] 硬编码端口..."
    25. PORTS=$(grep -rn 'listen.*:[0-9]\{4,5\}\|bind.*:[0-9]\{4,5\}' "$APP_DIR" \
    26.   --include="*.py" --include="*.java" --include="*.js" \
    27.   --include="*.go" --include="*.conf" 2>/dev/null | wc -l)
    28. if [ "$PORTS" -gt 0 ]; then
    29.   echo "  发现 $PORTS 处硬编码端口(扣 5 分)"
    30.   SCORE=$((SCORE - 5))
    31. else
    32.   echo "  未发现硬编码端口"
    33. fi
    34. # 检查环境变量使用
    35. echo ""
    36. echo "[检查] 环境变量使用情况..."
    37. ENV_USAGE=$(grep -rn 'os.environ\|os.Getenv\|process.env\|getenv\|ENV\[' "$APP_DIR" \
    38.   --include="*.py" --include="*.java" --include="*.js" \
    39.   --include="*.go" --include="*.php" --include="*.rb" 2>/dev/null | wc -l)
    40. if [ "$ENV_USAGE" -gt 5 ]; then
    41.   echo "  环境变量使用良好($ENV_USAGE 处)"
    42. elif [ "$ENV_USAGE" -gt 0 ]; then
    43.   echo "  环境变量使用较少($ENV_USAGE 处,扣 5 分)"
    44.   SCORE=$((SCORE - 5))
    45. else
    46.   echo "  未使用环境变量(扣 15 分)"
    47.   SCORE=$((SCORE - 15))
    48. fi
    49. # 检查本地文件写入
    50. echo ""
    51. echo "[检查] 本地文件写入..."
    52. FILE_WRITES=$(grep -rn 'open.*w\|fwrite\|writeFile\|os.Create\|file_put_contents' "$APP_DIR" \
    53.   --include="*.py" --include="*.java" --include="*.js" \
    54.   --include="*.go" --include="*.php" 2>/dev/null | wc -l)
    55. if [ "$FILE_WRITES" -gt 10 ]; then
    56.   echo "  大量文件写入操作($FILE_WRITES 处,扣 15 分)"
    57.   SCORE=$((SCORE - 15))
    58. elif [ "$FILE_WRITES" -gt 0 ]; then
    59.   echo "  少量文件写入操作($FILE_WRITES 处,扣 5 分)"
    60.   SCORE=$((SCORE - 5))
    61. else
    62.   echo "  未发现本地文件写入"
    63. fi
    64. # 检查 crontab 依赖
    65. echo ""
    66. echo "[检查] 定时任务依赖..."
    67. if [ -f /etc/crontab ] && grep -q "$APP_DIR" /etc/crontab 2>/dev/null; then
    68.   echo "  发现 crontab 依赖(扣 10 分)"
    69.   SCORE=$((SCORE - 10))
    70. else
    71.   echo "  未发现 crontab 依赖"
    72. fi
    73. echo ""
    74. echo "=============================="
    75. echo " 容器化就绪度评分: $SCORE / 100"
    76. echo "=============================="
    77. if [ "$SCORE" -ge 80 ]; then
    78.   echo " 评级: P0 - 可直接容器化"
    79. elif [ "$SCORE" -ge 60 ]; then
    80.   echo " 评级: P1 - 少量改造后可容器化"
    81. elif [ "$SCORE" -ge 40 ]; then
    82.   echo " 评级: P2 - 需要较大改造"
    83. else
    84.   echo " 评级: P3 - 建议暂缓容器化"
    85. fi
    复制代码

    运行效果:
    1. ==============================
    2. 容器化就绪度检查
    3. 目标目录: ./my-legacy-app
    4. ==============================
    5. [检查] 硬编码路径...
    6.   发现 3 处硬编码路径(扣 10 分)
    7. [检查] 硬编码端口...
    8.   未发现硬编码端口
    9. [检查] 环境变量使用情况...
    10.   环境变量使用较少(2 处,扣 5 分)
    11. [检查] 本地文件写入...
    12.   少量文件写入操作(4 处,扣 5 分)
    13. [检查] 定时任务依赖...
    14.   未发现 crontab 依赖
    15. ==============================
    16. 容器化就绪度评分: 80 / 100
    17. ==============================
    18. 评级: P0 - 可直接容器化
    复制代码

    二、12-Factor 改造:给老应用做个"微整形"

    12-Factor App 是 Heroku 团队总结的云原生应用方法论。你不需要一步到位全部满足,但有几条是容器化的硬性前提。可以把它想象成体检报告——有些指标飘红必须治,有些偏高观察就行。

    必须改造项(容器化前置条件)
    1. 迁移前(裸机部署)                    迁移后(容器化部署)
    2. +---------------------------+        +---------------------------+
    3. |  /opt/myapp/              |        |  Docker Container         |
    4. |  ├── config.properties    |  ──>   |  ├── ENV 环境变量注入     |
    5. |  │   (硬编码数据库地址)    |        |  │   DATABASE_URL=...     |
    6. |  ├── logs/                |        |  ├── stdout/stderr 日志   |
    7. |  │   (本地日志文件)        |  ──>   |  │   (日志收集器采集)     |
    8. |  ├── upload/              |        |  ├── Volume 挂载          |
    9. |  │   (用户上传文件)        |  ──>   |  │   /data/upload -> NFS  |
    10. |  └── crontab              |        |  └── K8s CronJob          |
    11. |      (定时任务)            |  ──>   |      (独立调度)           |
    12. +---------------------------+        +---------------------------+
    复制代码

    第三条:配置存储在环境变量中

    这是最常见的问题。老项目喜欢把配置写在文件里、写在代码里,甚至写在数据库里。
    1. # 改造前:硬编码配置
    2. DB_HOST = "192.168.1.100"
    3. DB_PORT = 3306
    4. DB_NAME = "myapp"
    5. REDIS_URL = "redis://192.168.1.101:6379"
    6. # 改造后:环境变量注入
    7. import os
    8. DB_HOST = os.environ.get("DB_HOST", "localhost")
    9. DB_PORT = int(os.environ.get("DB_PORT", "3306"))
    10. DB_NAME = os.environ.get("DB_NAME", "myapp")
    11. REDIS_URL = os.environ.get("REDIS_URL", "redis://localhost:6379")
    复制代码

    第十一条:日志作为事件流输出到 stdout
    1. # 改造前:写本地文件
    2. import logging
    3. handler = logging.FileHandler('/var/log/myapp/app.log')
    4. logger.addHandler(handler)
    5. # 改造后:输出到 stdout
    6. import logging
    7. import sys
    8. handler = logging.StreamHandler(sys.stdout)
    9. handler.setFormatter(logging.Formatter(
    10.     '%(asctime)s %(levelname)s %(name)s %(message)s'
    11. ))
    12. logger.addHandler(handler)
    复制代码

    第六条:以无状态进程运行

    这条最"伤筋动骨"。如果你的应用把 Session 存在本地内存、把上传文件存在本地磁盘,就需要改造:
    1. # docker-compose.yml 中的状态外置方案
    2. services:
    3.   myapp:
    4.     image: myapp:latest
    5.     environment:
    6.       - SESSION_STORE=redis
    7.       - SESSION_REDIS_URL=redis://redis:6379
    8.       - UPLOAD_STORAGE=s3
    9.       - UPLOAD_S3_BUCKET=myapp-uploads
    10.     volumes:
    11.       # 临时方案:挂载共享存储
    12.       - nfs-uploads:/app/upload
    13.   redis:
    14.     image: redis:7-alpine
    15. volumes:
    16.   nfs-uploads:
    17.     driver: local
    18.     driver_opts:
    19.       type: nfs
    20.       o: addr=192.168.1.200,rw
    21.       device: ":/exports/myapp-uploads"
    复制代码

    可以后续改造的项(不阻塞容器化)

    | Factor | 说明 | 紧急程度 |
    |--------|------|---------|
    | 第一条:基准代码 | 一份代码多份部署 | 低 |
    | 第五条:构建、发布、运行 | 严格分离三个阶段 | 中 |
    | 第八条:并发 | 通过进程模型扩展 | 低 |
    | 第十条:开发环境与线上环境等价 | 环境一致性 | 中 |

    三、实战:一个 Spring Boot 老项目的容器化

    来看一个真实场景:一个跑了两年的 Spring Boot 项目,有本地配置文件、本地日志、本地上传目录。

    第一步:创建多阶段 Dockerfile
    1. # ============= 构建阶段 =============
    2. FROM maven:3.9-eclipse-temurin-17 AS builder
    3. WORKDIR /build
    4. # 先复制依赖文件,利用缓存
    5. COPY pom.xml .
    6. RUN mvn dependency:go-offline -B
    7. # 再复制源码并构建
    8. COPY src ./src
    9. RUN mvn package -DskipTests -B
    10. # ============= 运行阶段 =============
    11. FROM eclipse-temurin:17-jre-alpine
    12. WORKDIR /app
    13. # 安全:不用 root 运行
    14. RUN addgroup -S appgroup && adduser -S appuser -G appgroup
    15. # 从构建阶段复制产物
    16. COPY --from=builder /build/target/*.jar app.jar
    17. # 健康检查
    18. HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
    19.   CMD wget -qO- http://localhost:8080/actuator/health || exit 1
    20. # 切换用户
    21. USER appuser
    22. # JVM 参数通过环境变量控制
    23. ENV JAVA_OPTS="-Xms256m -Xmx512m"
    24. ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
    复制代码

    第二步:改造配置文件
    1. # application.yml - 改造后
    2. spring:
    3.   datasource:
    4.     url: ${DATABASE_URL:jdbc:mysql://localhost:3306/myapp}
    5.     username: ${DB_USERNAME:root}
    6.     password: ${DB_PASSWORD:}
    7.   redis:
    8.     host: ${REDIS_HOST:localhost}
    9.     port: ${REDIS_PORT:6379}
    10.   servlet:
    11.     multipart:
    12.       location: ${UPLOAD_DIR:/tmp/uploads}
    13. # 日志输出到 stdout
    14. logging:
    15.   pattern:
    16.     console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
    17.   file:
    18.     name: ""  # 禁用文件日志
    复制代码

    第三步:编写 docker-compose.yml
    1. services:
    2.   myapp:
    3.     build: .
    4.     ports:
    5.       - "8080:8080"
    6.     environment:
    7.       - DATABASE_URL=jdbc:mysql://db:3306/myapp?useSSL=false
    8.       - DB_USERNAME=myapp
    9.       - DB_PASSWORD=${DB_PASSWORD}
    10.       - REDIS_HOST=redis
    11.       - UPLOAD_DIR=/data/uploads
    12.       - JAVA_OPTS=-Xms512m -Xmx1024m
    13.     volumes:
    14.       - upload-data:/data/uploads
    15.     depends_on:
    16.       db:
    17.         condition: service_healthy
    18.       redis:
    19.         condition: service_started
    20.     restart: unless-stopped
    21.   db:
    22.     image: mysql:8.0
    23.     environment:
    24.       - MYSQL_DATABASE=myapp
    25.       - MYSQL_USER=myapp
    26.       - MYSQL_PASSWORD=${DB_PASSWORD}
    27.       - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
    28.     volumes:
    29.       - db-data:/var/lib/mysql
    30.     healthcheck:
    31.       test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
    32.       interval: 10s
    33.       timeout: 5s
    34.       retries: 5
    35.   redis:
    36.     image: redis:7-alpine
    37.     volumes:
    38.       - redis-data:/data
    39. volumes:
    40.   upload-data:
    41.   db-data:
    42.   redis-data:
    复制代码

    第四步:构建并验证
    1. # 构建镜像
    2. docker compose build
    3. # 预期输出:
    4. # [+] Building 45.2s (12/12) FINISHED
    5. # => [builder 1/4] FROM maven:3.9-eclipse-temurin-17
    6. # => [builder 2/4] COPY pom.xml .
    7. # => [builder 3/4] RUN mvn dependency:go-offline -B
    8. # => [builder 4/4] COPY src ./src
    9. # => [builder 5/4] RUN mvn package -DskipTests -B
    10. # => [stage-1 1/3] FROM eclipse-temurin:17-jre-alpine
    11. # => [stage-1 2/3] COPY --from=builder /build/target/*.jar app.jar
    12. # 启动服务
    13. docker compose up -d
    14. # 检查状态
    15. docker compose ps
    16. # 预期输出:
    17. # NAME       IMAGE        STATUS                   PORTS
    18. # myapp-1    myapp:latest Up 30s (healthy)         0.0.0.0:8080->8080/tcp
    19. # db-1       mysql:8.0    Up 32s (healthy)         3306/tcp
    20. # redis-1    redis:7      Up 32s                   6379/tcp
    21. # 查看应用日志
    22. docker compose logs myapp --tail=20
    23. # 测试健康检查
    24. curl http://localhost:8080/actuator/health
    25. # {"status":"UP"}
    复制代码

    四、数据迁移:最让人睡不着觉的部分

    容器化最棘手的不是应用本身,而是数据。就像搬家最难搬的不是家具,而是那些装满东西的柜子。

    数据库迁移方案对比
    1. 方案一:直连原库(最简单,过渡期推荐)
    2. +-------------+          +-----------------+
    3. |   容器应用   | -------> |  原有数据库实例  |
    4. +-------------+          +-----------------+
    5. 优点:零数据迁移  缺点:网络延迟、单点风险
    6. 方案二:主从同步(平滑迁移推荐)
    7. +----------+    同步    +----------+
    8. |  原主库   | --------> | 容器内从库 |
    9. +----------+           +----------+
    10.                             |
    11.                        应用切换到从库
    12.                             |
    13.                        从库提升为主库
    14. 优点:平滑切换  缺点:需要同步窗口
    15. 方案三:导出导入(小数据量推荐)
    16. +----------+  dump   +--------+  import  +----------+
    17. |  原数据库 | ------> | SQL文件 | -------> | 容器数据库 |
    18. +----------+         +--------+          +----------+
    19. 优点:简单直接  缺点:有停机窗口
    复制代码

    实操:MySQL 数据迁移
    1. # 方案三实操:适合数据量在 10GB 以内的场景
    2. # 1. 从原库导出
    3. mysqldump -h 192.168.1.100 -u root -p \
    4.   --single-transaction \
    5.   --routines \
    6.   --triggers \
    7.   --databases myapp > myapp_backup.sql
    8. echo "导出完成,文件大小:"
    9. ls -lh myapp_backup.sql
    10. # 2. 启动容器数据库
    11. docker compose up -d db
    12. sleep 10  # 等待 MySQL 初始化完成
    13. # 3. 导入到容器数据库
    14. docker compose exec -T db mysql -u root -p${DB_ROOT_PASSWORD} < myapp_backup.sql
    15. # 4. 验证数据完整性
    16. docker compose exec db mysql -u root -p${DB_ROOT_PASSWORD} -e "
    17.   SELECT table_name, table_rows
    18.   FROM information_schema.tables
    19.   WHERE table_schema = 'myapp'
    20.   ORDER BY table_rows DESC;"
    21. # 预期输出:
    22. # +------------------+------------+
    23. # | table_name       | table_rows |
    24. # +------------------+------------+
    25. # | orders           |     125000 |
    26. # | users            |      50000 |
    27. # | products         |       8000 |
    28. # | order_items      |     380000 |
    29. # +------------------+------------+
    复制代码

    文件存储迁移
    1. # 上传文件从本地目录迁移到 Docker Volume
    2. # 1. 查看原始文件
    3. echo "原始文件统计:"
    4. find /opt/myapp/upload -type f | wc -l
    5. du -sh /opt/myapp/upload
    6. # 2. 创建并挂载 Volume
    7. docker volume create myapp-uploads
    8. # 3. 用临时容器复制文件
    9. docker run --rm \
    10.   -v /opt/myapp/upload:/source:ro \
    11.   -v myapp-uploads:/dest \
    12.   alpine sh -c "cp -a /source/. /dest/ && echo '复制完成'"
    13. # 4. 验证
    14. docker run --rm -v myapp-uploads:/data alpine sh -c "
    15.   echo '文件数量:' && find /data -type f | wc -l
    16.   echo '总大小:' && du -sh /data
    17. "
    复制代码

    五、灰度切换:别一刀切,要像温水煮青蛙

    这是整个容器化过程中最关键的一步。一刀切全量切换就像高速公路上换轮胎——理论上可以但没人敢。

    灰度切换策略
    1. 阶段 1:并行运行(1-2 周)
    2. +--------+     +----------+     +---------+
    3. | Nginx  | --> | 原应用    |     | 容器应用 |  (仅内部测试)
    4. +--------+     +----------+     +---------+
    5. 阶段 2:金丝雀发布(1 周)
    6. +--------+     +----------+
    7. | Nginx  | --> | 原应用    |  95% 流量
    8. |        |     +----------+
    9. |        |     +---------+
    10. |        | --> | 容器应用 |   5% 流量
    11. +--------+     +---------+
    12. 阶段 3:逐步放量
    13. +--------+     +----------+
    14. | Nginx  | --> | 原应用    |  50% 流量
    15. |        |     +----------+
    16. |        |     +---------+
    17. |        | --> | 容器应用 |  50% 流量
    18. +--------+     +---------+
    19. 阶段 4:全量切换
    20. +--------+     +---------+
    21. | Nginx  | --> | 容器应用 |  100% 流量
    22. +--------+     +---------+
    23.                +----------+
    24.                | 原应用    |  保留 1 周可回滚
    25.                +----------+
    复制代码

    Nginx 灰度配置
    1. # /etc/nginx/conf.d/myapp.conf
    2. upstream legacy {
    3.     server 192.168.1.10:8080;
    4. }
    5. upstream container {
    6.     server 127.0.0.1:8080;
    7. }
    8. # 灰度分流(基于权重)
    9. split_clients "$request_id" $backend {
    10.     5%    container;
    11.     *     legacy;
    12. }
    13. server {
    14.     listen 80;
    15.     server_name myapp.example.com;
    16.     location / {
    17.         proxy_pass http://$backend;
    18.         proxy_set_header Host $host;
    19.         proxy_set_header X-Real-IP $remote_addr;
    20.         proxy_set_header X-Backend $backend;  # 方便排查
    21.     }
    22.     # 健康检查端点直接打到容器
    23.     location /health {
    24.         proxy_pass http://container/actuator/health;
    25.     }
    26. }
    复制代码

    监控对比脚本

    灰度期间,你需要对比两个环境的关键指标:
    1. #!/bin/bash
    2. # compare-metrics.sh - 灰度期间指标对比
    3. echo "========== 灰度监控对比 =========="
    4. echo "时间: $(date '+%Y-%m-%d %H:%M:%S')"
    5. echo ""
    6. # 响应时间对比
    7. echo "[响应时间]"
    8. LEGACY_RT=$(curl -s -o /dev/null -w "%{time_total}" http://192.168.1.10:8080/api/health)
    9. CONTAINER_RT=$(curl -s -o /dev/null -w "%{time_total}" http://127.0.0.1:8080/api/health)
    10. echo "  原应用:   ${LEGACY_RT}s"
    11. echo "  容器应用: ${CONTAINER_RT}s"
    12. # 错误率对比(从 Nginx access log 统计)
    13. echo ""
    14. echo "[最近 5 分钟错误率]"
    15. LEGACY_ERR=$(awk -v d="$(date -d '5 minutes ago' '+%d/%b/%Y:%H:%M')" \
    16.   '$4 > "["d && $9 ~ /^5/' /var/log/nginx/access.log | \
    17.   grep "legacy" | wc -l)
    18. CONTAINER_ERR=$(awk -v d="$(date -d '5 minutes ago' '+%d/%b/%Y:%H:%M')" \
    19.   '$4 > "["d && $9 ~ /^5/' /var/log/nginx/access.log | \
    20.   grep "container" | wc -l)
    21. echo "  原应用:   ${LEGACY_ERR} 个 5xx"
    22. echo "  容器应用: ${CONTAINER_ERR} 个 5xx"
    23. # 内存使用对比
    24. echo ""
    25. echo "[内存使用]"
    26. echo "  容器应用:"
    27. docker stats --no-stream --format "    {{.Name}}: {{.MemUsage}}" myapp-1
    28. echo ""
    29. echo "=================================="
    复制代码

    常见问题 Q&A

    Q1: 应用依赖 crontab 定时任务,容器化后怎么办?

    有三种方案,推荐程度递减:

  • 如果用 K8s,直接用 CronJob 资源,最优雅
  • 用独立的定时容器,通过 curl 触发主应用的 API 端点
  • 在容器内跑 crond(不推荐,违反单进程原则,但作为过渡方案可以接受)
    1. # 方案 2 示例:docker-compose 中的定时触发容器
    2. services:
    3.   scheduler:
    4.     image: alpine:3.19
    5.     entrypoint: ["/bin/sh", "-c"]
    6.     command:
    7.       - |
    8.         echo "0 2 * * * wget -qO- http://myapp:8080/api/tasks/cleanup" | crontab -
    9.         crond -f
    10.     depends_on:
    11.       - myapp
    复制代码

    Q2: 应用启动要 2 分钟,健康检查老是失败怎么调?

    Java 应用尤其常见这个问题。关键是调整健康检查参数:
    1. HEALTHCHECK \
    2.   --interval=30s \
    3.   --timeout=5s \
    4.   --start-period=120s \
    5.   --retries=3 \
    6.   CMD wget -qO- http://localhost:8080/actuator/health || exit 1
    复制代码

    核心参数是
    1. --start-period
    复制代码
    ,给应用一个"热身时间",这段时间内健康检查失败不计入重试次数。

    Q3: 容器化后性能下降了怎么排查?

    九成的性能问题出在这几个地方:

  • DNS 解析慢:容器内 DNS 走的是 Docker 内置 DNS,可以在 docker-compose 里配置外部 DNS
  • 磁盘 IO:overlay2 文件系统有额外开销,频繁读写的目录用 Volume 挂载
  • 内存限制:JVM 的
    1. -Xmx
    复制代码
    要配合容器的内存限制,否则容易被 OOM Kill
  • 网络模式:默认 bridge 模式有 NAT 开销,性能敏感场景可以用 host 模式
    1. # docker-compose 性能调优示例
    2. services:
    3.   myapp:
    4.     # ...
    5.     deploy:
    6.       resources:
    7.         limits:
    8.           memory: 2G
    9.           cpus: "2.0"
    10.         reservations:
    11.           memory: 1G
    12.           cpus: "1.0"
    13.     dns:
    14.       - 223.5.5.5
    15.       - 8.8.8.8
    复制代码

    小结

    今天我们走完了存量应用容器化的全流程:

  • 评估先行:用评估清单给每个应用打分排序,先易后难
  • 12-Factor 改造:重点搞定配置外置、日志标准化、状态外置这三板斧
  • 数据迁移:根据数据量选择合适方案,小数据导出导入、大数据主从同步
  • 灰度切换:从 5% 流量开始,逐步放量,全程监控对比

    记住一句话:容器化不是一蹴而就的事,而是一个渐进式的过程。不要追求一步到位,先让最简单的应用跑起来,积累经验后再攻克复杂的。就像学游泳,先在浅水区扑腾几下,别一上来就往深水区跳。

    明天是我们 Docker 30 天实战系列的最后一天(Day 30),我们会做一个全系列的总结回顾,并给出从 Docker 到 K8s 的进阶学习路线。30 天的旅程马上就要画上句号了,别掉队。
  • 您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    扫码关注微信公众号

    Archiver|手机版|小黑屋|风叶林

    GMT+8, 2026-5-8 23:01 , Processed in 0.158922 second(s), 20 queries .

    Powered by 风叶林

    © 2001-2026 Discuz! Team.

    快速回复 返回顶部 返回列表