侧边栏壁纸
博主头像
Ivan Zhang

所谓更牛,就是换个罪受

  • 累计撰写 48 篇文章
  • 累计创建 54 个标签
  • 累计收到 6 条评论

目 录CONTENT

文章目录

从手工到容器化:软件部署的进化之旅

Ivan Zhang
2025-08-11 / 0 评论 / 0 点赞 / 63 阅读 / 6,912 字
温馨提示:
本文最后更新于 ,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。
有什么问题或观点欢迎评论留言,或者 交流。
如果觉得文章对您有所帮助,可以给博主打赏鼓励一下。

🛠️ 原始阶段:手工部署时代

⚙️ 一、典型部署流程

  1. 💻 开发环境构建

    • Java项目:mvn package生成jar包
    • 前端项目:npm run build生成dist
    • 环境差异:开发者本地 Windows/Mac vs 生产环境 CentOS
  2. 📤 文件传输

    scp target/app.jar user@prod-server:/home/user/upload
    
    • 痛点:100MB+文件通过VPN上传耗时10+分钟(以某内部平台为例,一次需更新9个jar包)
  3. 🖥️ 服务器部署

    mkdir -p /opt/myapp/{bin,conf,logs}
    vim /opt/myapp/conf/application.properties
    
  4. 🚀 服务启动

    nohup java -jar app.jar > console.log 2>&1 &
    
    • 典型启动脚本:
    # restart.sh
    kill $(ps aux | grep 'myapp' | awk '{print $2}')
    nohup java -jar /opt/myapp/app.jar &
    

🔍 2. 核心问题分析

问题类型具体表现影响程度
🌐 环境差异开发机 Windows/Mac vs 生产 CentOS,JDK 版本不一致★★★★★
⏱️ 部署效率重复执行10+命令,VPN上传大文件耗时★★★★☆
📝 配置管理配置文件散落各处,无版本控制★★★★☆
🛡️ 服务可靠性nohup 进程可能崩溃,无自动重启机制★★★★☆
👀 运维可见性日志分散,无监控指标★★★☆☆

⚠️ 3. 典型故障案例

  • 📦 依赖缺失
    libfreetype.so.6: cannot open shared object file
    原因:生产环境缺少fontconfig库

  • 🔌 端口冲突
    Address already in use
    原因:多服务共用8080端口

  • ⚙️ 配置错误
    测试环境误连开发数据库
    表现:网络不通,或者开发数据出现在测试环境

🔄 4. 过渡期改进尝试

  • 📄 基础文档
    deploy_manual.md
    问题: 文档更新不及时

  • 📜 简单脚本

    # deploy.sh
    scp $1 user@server:/opt/myapp
    ssh user@server "./restart.sh"
    

    局限: 无错误校验,且需要可直接ssh连接服务器(一般是需要通过堡垒机)

  • 📊 基础监控

    */5 * * * * pgrep -f myapp || ./restart.sh
    

    缺陷: 无报警通知

🏭 瘦身包方案(缩短部署时长)

🧩 核心实现原理

graph TD A[原始Fat Jar] --> B[Thin Layout转换] B --> C[分离依赖管理] C --> D[thin.properties] D --> E[运行时动态加载]

⚙️ 关键配置说明

  1. 📦 Spring Boot Thin Layout

    <dependency>
    <groupId>org.springframework.boot.experimental</groupId>
    <artifactId>spring-boot-thin-layout</artifactId>
    <version>1.0.28.RELEASE</version>
    </dependency>
    

    作用: 将传统Fat Jar拆分为:

    • 应用代码(主Jar)
    • 依赖描述文件(thin.properties)
    • 外部依赖库(可共享)
  2. 📂 依赖解压配置

    <requiresUnpack>
    <dependency>
    <groupId>com.erbantou.platform</groupId>
    <artifactId>demo-service</artifactId>
    </dependency>
    </requiresUnpack>
    
    • 必要性:确保嵌套JAR被正确解压
    • 典型问题:未解压时出现的FileNotFoundException
  3. 🔧 特殊依赖处理

    <artifactItem>
    <groupId>com.erbantou.platform</groupId>
    <artifactId>demo-override</artifactId>
    <excludes>META-INF/MANIFEST.MF</excludes>
    </artifactItem>
    

    作用: 避免MANIFEST.MF冲突

🔄 部署流程优化

  1. 🏗️ 构建阶段

    mvn clean package
    

    生成产物:

    target/
    ├── demo-cloud.jar# 主应用(瘦身版)
    ├── thin.properties # 依赖清单
    └── lib/# 可选:本地依赖库
    
  2. ⚡ 运行时依赖解析

    java -Dthin.root=lib -jar aas-cloud.jar
    
    • 自动从以下位置加载依赖:
    • 本地lib/目录
    • Maven本地仓库
    • 远程仓库(可配置)

🏗️ 标准化构建:Jenkins + systemd 服务化

🔄 部署方案实施流程

  1. 🔄 核心工作流

    graph TB A[开发者] -->|提交代码| B[GitLab] B -->|触发流水线| C[Jenkins] C -->|构建打包| D[Nexus] C -->|SSH部署| E[应用服务器] E -->|服务注册| F[systemd]
  2. ⚙️ 详细执行步骤

    • 📥 代码提交阶段
      开发人员将代码提交到 Gitlab 仓库

    • 🏗️ 自动化构建阶段
      通过 Jenkins 配置构建流水线,生成 jar 包或者前端的 dist 目录

    • 🚀 服务部署阶段
      通过 Jenkins 配置 ssh 连接信息,将生成的部署资源推送到目标应用服务器,执行目标应用服务器上的部署脚本。

✅ 方案核心价值

解决的问题实现方式效果提升
🌐 统一构建环境固定Jenkins构建节点OS+工具链构建成功率从70%提升至98%
⚡ 简化部署流程标准化部署脚本部署耗时从30分钟缩短至5分钟
🛡️ 服务可靠性systemd的自动重启机制服务意外终止率降低80%
👁️ 状态可视化集成systemctl status输出故障定位时间缩短50%
👥 非技术人员可操作Jenkins Web界面提供"一键部署"按钮测试人员自主部署比例提升至60%

⚠️ 现存问题深度分析

  1. 🔒 网络权限问题

    • 📜 必要权限清单
    graph LR J[Jenkins] -->|22/tcp| A[所有应用服务器] J -->|80/tcp| N[Nexus] J -->|443/tcp| G[GitLab]

    安全风险

    • 需长期开放SSH密钥对
    • 防火墙需放行大量端口
  2. 🔄 多版本环境管理

    • ⚠️ 典型冲突案例
    # 同时构建两个项目时
    ProjectA: 需要Node16 (依赖glibc-2.17)
    ProjectB: 需要Node18 (依赖glibc-2.28)
    # 系统实际glibc版本
    $ ldd --version
    ldd (GNU libc) 2.23
    
  3. 🗃️ 公共缓存冲突

    • ❌ 问题表现
    # Maven本地仓库冲突
    [ERROR] Failed to execute goal:
    Could not resolve dependencies for artifact com.example:api:jar:1.0
    

🔄 标准化构建:Jenkins 多版本环境管理

🌐 多版本环境冲突的典型场景

  1. 🖥️ 前端项目环境冲突

    graph TB A[项目A] -->|要求Node16| B[需要glibc-2.17] C[项目B] -->|要求Node18| D[需要glibc-2.28] E[服务器] -->|当前glibc-2.23| F[无法同时满足]
  2. ☕ Java项目环境冲突

    • 同时维护的项目需求:
    项目名称JDK要求Maven要求
    A系统JDK8u202Maven 3.6
    B系统JDK11Maven 3.8
    数据分析JDK17Maven 3.9

⚠️ 具体问题表现

  1. 📦 依赖库版本冲突

    • 案例:Node.js 不同版本对OpenSSL的要求
    # Node16需要的OpenSSL 1.1.x
    $ ldd /usr/local/node16/bin/node | grep ssl
    libssl.so.1.1 => /lib64/libssl.so.1.1 (0x00007f8c3a1e0000)
    
    # Node18需要的OpenSSL 3.0.x
    $ ldd /usr/local/node18/bin/node | grep ssl
    libssl.so.3 => not found
    
  2. ⚙️ 系统库不兼容

    • 错误示例:
    /lib64/libm.so.6: version `GLIBC_2.27' not found
    required by /opt/node18/bin/node
    
  3. 🧩 工具链污染

    # 全局安装的Maven冲突
    $ mvn -v
    Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
    # 但项目B需要3.8.5
    

⚠️ 现有解决方案的局限性

  1. 🔄 传统隔离方案

    • 使用alternatives切换版本:
    sudo alternatives --config java
    

    缺陷:

    • 全局生效,无法并行构建
    • 需要root权限
  2. ⚙️ 手动环境切换

    # 在Jenkinsfile中硬编码路径
    stage('Build') {
    sh '''
    export JAVA_HOME=/opt/jdk8u202
    export PATH=$JAVA_HOME/bin:$PATH
    mvn clean package
    '''
    }
    

    问题:

    • 维护成本高(每个项目需特殊配置)
    • 容易遗漏环境变量
  3. ⚠️ 服务器污染风险
    常见问题:

    • 升级Node18导致旧项目构建失败
    • 安装新JDK影响现有服务

✅ 优化实践

通过 docker run --rm 指定镜像,将源码映射到容器内,再通过镜像提供的标准构建环境进行构建。

  1. 🐳 工具链容器化
    # 专用构建镜像
    sudo docker run --rm \
    -v "$(pwd):/app" \
    -w /app \
    node:18.20.5 \
    sh -c "yarn config set registry http://erbantou.com/ && yarn install --verbose && yarn run build:h5:dev"
    

⚖️ 方案优缺点对比

解决方案优点缺点
alternatives切换无需额外资源全局影响/无法并行
容器化构建完全隔离/版本精确控制需要Docker环境/存储消耗大

🐳 容器化部署

🔄 部署方案实施流程

  1. 🏗️ 核心架构

    graph TB A[Jenkins] -->|构建镜像| B[Dockerfile] B --> C[镜像仓库] C -->|拉取| D[生产服务器] D --> E[docker-compose] E --> F[watchtower]
  2. ⚙️ 详细工作流
    构建示例:

    stage('Build & Deploy') {
        steps {
            script {
                def DEPLOY_ENV=env.DEPLOY_ENV
    
                // 1. 构建前端
                sh """
                    echo "${DEPLOY_ENV}"
                    sed -i -E 's#resolved "https?://[^/]+#resolved "http://erbantou.com#' yarn.lock
                    docker run --rm \
                      -v "${WORKSPACE}:/app" \
                      -w /app \
                      erbantou.com/node:18.20.5 \
                      sh -c "yarn config set registry 'http://erbantou.com' && yarn install --verbose && yarn run build:h5:${DEPLOY_ENV}"
                """
    
                // 2. 动态生成 Dockerfile
                sh '''
                    echo "FROM erbantou.com/alpine" > Dockerfile
                    echo "COPY dist/build/h5 /app/" >> Dockerfile
                '''
    
                // 3. 构建镜像并推送
                sh '''
                    # 获取构建时间,作为镜像版本名
                    BUILD_DATE=$(date +%Y%m%d%H%M)
                    # 镜像名称
                    IMAGE_NAME="erbantou.com/project_name/app_name/${DEPLOY_ENV}"
                    # 镜像版本名
                    IMAGE_TAG="${IMAGE_NAME}:${BUILD_DATE}"
                    # 最新版本镜像名
                    LATEST_TAG="${IMAGE_NAME}:latest"
    
                    echo "正在构建 Docker 镜像..."
                    echo "镜像版本: $IMAGE_TAG"
                    echo "最新标签: $LATEST_TAG"
    
                    # 构建镜像
                    echo "正在执行 Docker 构建..."
                    docker build \
                      -t ${IMAGE_TAG} \
                      -t ${LATEST_TAG} \
                      . || {
                      echo "错误: Docker 构建失败!"
                      exit 1
                    }
    
                    echo "Docker 构建成功完成!"
    
                    echo "正在推送镜像到仓库..."
                    # 推送镜像到私有仓库
                    echo "正在推送镜像: $IMAGE_TAG"
                    docker push "$IMAGE_TAG" || {
                      echo "错误: 推送 $IMAGE_TAG 失败!"
                      exit 1
                    }
    
                    echo "正在推送镜像: $LATEST_TAG"
                    docker push "$LATEST_TAG" || {
                      echo "错误: 推送 $LATEST_TAG 失败!"
                      exit 1
                    }
    
                    echo "Docker 镜像构建和推送全部完成!"
                '''
            }
        }
    }
    

✅ 方案核心价值

解决的问题实现方式技术实现示例
🔑 Jenkins权限过大消除SSH直连仅需镜像仓库访问权限
🔄 多版本环境问题容器化构建环境docker run -it node:16 bash
🧹 构建缓存污染独立容器空间-v maven-repo:/root/.m2
🕰️ 版本追溯困难镜像标签版本化app:latestapp:202508151600

⚠️ 现存问题分析

  1. 💾 镜像仓库存储压力
    由于不同环境均通过镜像的方式推送到镜像仓库,将导致镜像仓库存在较大的存储压力,需配置适当的清理策略。

  2. ⚡ watchtower的管控风险

    • 🛡️ 生产环境配置示例
    # docker-compose.yml
    services:
      watchtower:
        profiles: ["watchtower"]             # 默认不启动 watchtower 配置,需要手动开启
        image: erbantou.com/containrrr/watchtower
        container_name: watchtower
        logging:
          driver: "json-file"
          options:
            max-size: "10m"  # 单个日志文件最大 10MB
            max-file: "3"    # 保留最多 3 个日志文件
        volumes:
          - /var/run/docker.sock:/var/run/docker.sock
        command:
          - --interval=60                    # 每分钟检查一次
          - --label-enable                   # 仅更新带com.centurylinklabs.watchtower.enable=true标签的容器
          - --cleanup                        # 清理旧镜像
        restart: unless-stopped
    

⚖️ 方案优缺点对比

维度传统方案容器化方案
🌐 环境一致性依赖人工维护镜像保证100%一致
⚡ 部署速度5-10分钟1-3分钟
🔑 权限管控需SSH和sudo权限仅需仓库访问权限
💾 存储消耗较小较大(需镜像仓库)

💬 互动环节设计

❓ 问题讨论

Q1:在您当前环境中,最棘手的部署问题是什么?
选项参考:

  • 环境不一致问题
  • 多版本管理困难
  • 部署流程复杂
  • 权限管控风险

Q2:如果实施容器化改造,您最关心哪些方面?
选项参考:

  • 技术学习成本
  • 现有系统兼容性
  • 运维工具链改造
  • 团队接受度

图标使用说明:

  • 🛠️ - 基础工具/手工操作
  • ⚙️ - 流程/机制
  • 💻 - 开发环境
  • 📤 - 文件传输
  • 🖥️ - 服务器操作
  • 🚀 - 服务启动/部署
  • 🔍 - 分析/诊断
  • ⚠️ - 警告/问题
  • 🔄 - 转换/过渡
  • 🏭 - 优化方案
  • 🧩 - 架构原理
  • ✅ - 优势/价值
  • ⚠️ - 风险/问题
  • 🐳 - 容器化相关
  • 💾 - 存储问题
  • ⚡ - 速度/效率
  • 🛡️ - 安全/防护
  • ⚖️ - 对比/权衡
  • 💬 - 互动交流
  • ❓ - 问题讨论

(注:所有原始内容保持完全不变,仅在最前部添加了Unicode图标)

0

评论区