C#/.NET 微服务架构:从入门到精通(六):Docker部署与 Jenkins 自动构建发布

前一篇我们完成了。但随着微服务数量增多、迭代节奏加快,环境不一致、部署效率低、发布风险高成为了制约团队交付速度的核心瓶颈 —— 传统手动打包、FTP 上传的方式,不仅耗时费力,还极易出现 “开发能跑、生产报错” 的环境问题。

本篇将聚焦单服务独立 Docker 容器化与单服务独立 Jenkins 自动化流水线的全流程落地,严格遵循微服务 “独立构建、独立部署” 的核心原则,实现 “代码提交→自动构建→镜像打包→一键部署” 的 CI/CD 闭环,让微服务发布从 “手工操作” 升级为 “自动化、标准化、可追溯” 的工业级流程。

一、核心理念:为什么必须 “每个微服务单独构建 + 容器化部署”?

1. 传统单体构建的致命问题

在微服务架构中,如果沿用单体应用的 “一个仓库、一次构建、一起发布” 模式,会带来无法解决的耦合问题:
  • 构建效率极低:修改用户服务的一行代码,需要编译整个解决方案的所有服务,数十个服务的构建时间可能长达数小时;
  • 发布完全耦合:A 服务的紧急 Bug 修复,必须等待 B 服务的功能开发完成才能一起上线;
  • 回滚风险巨大:一次发布包含多个服务的变更,出问题时难以定位根因,回滚会影响所有服务;
  • 团队协作阻塞:不同团队负责不同服务,共用一条流水线会导致发布冲突、互相等待

2. Docker + 独立构建的核心价值

我们采用 **“一服务一仓库、一服务一流水线、一服务一镜像”** 的标准微服务 CI/CD 架构:
  • 环境绝对一致:Docker 将应用及其所有依赖(.NET 运行时、配置、依赖库)打包成一个不可变的镜像,一次构建,到处运行;
  • 构建完全解耦:每个服务有独立的构建流水线,修改哪个服务就只构建哪个服务,构建时间从小时级缩短到分钟级;
  • 发布风险可控:每次只部署变更的服务,出问题只回滚单个服务,影响范围最小化;
  • 扩缩容秒级完成:容器是进程级隔离,启动速度远快于虚拟机,完美适配微服务动态扩缩容的需求。

二、前期准备:在服务器中安装Docker与Jenkins

 这里不做具体展示,详情可查看之前的文章:https://www.cnblogs.com/lantingxu/p/19859131

三、实战步骤 1:单服务 Docker 容器化(以商品服务为例)

我们将所有精力聚焦在单个服务的容器化上,订单服务只需重复完全相同的步骤。

image

 

1. 编写.NET 8 优化版 Dockerfile

采用多阶段构建 + Alpine 基础镜像策略,最终镜像体积可控制在 100MB 以内,比默认镜像小 70% 以上。

在ProductApi项目根目录创建Dockerfile

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80

# 设置中文编码
ENV LANG=zh_CN.UTF-8
ENV LC_ALL=zh_CN.UTF-8

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src

COPY ["ProductApi/ProductApi.csproj", "ProductApi/"]

RUN dotnet restore "ProductApi/ProductApi.csproj"

COPY . .

# 构建
WORKDIR "/src/ProductApi"
RUN dotnet build "ProductApi.csproj" -c Release -o /app/build

# 发布
FROM build AS publish
RUN dotnet publish "ProductApi.csproj" -c Release -o /app/publish /p:UseAppHost=false

# 最终运行
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ProductApi.dll"]

四、实战步骤 2:单服务独立 Jenkins 自动化流水线(以商品服务为例)

1. 编写单服务 Jenkinsfile

在ProductApi项目根目录创建Jenkinsfile,这条流水线只负责商品服务的全生命周期,与其他服务完全无关:
pipeline {
    agent any
    
    options {
        timeout(time: 30, unit: 'MINUTES')
        disableResume() // 符合你的禁用Resume要求
        skipStagesAfterUnstable()
    }

    environment {
        // 基础配置
        GIT_URL = 'https://gitee.com/kb248/e-commerce-demo.git'
        GIT_CRED_ID = 'gitee-auth'
        GIT_BRANCH = '*/master'
        
        SERVICE_NAME = 'productapi'
        DOCKERFILE_PATH = 'ProductApi/Dockerfile'
        HOST_PORT = '5002'
        CONTAINER_PORT = '80'
        DOCKER_IMAGE_TAG = "${SERVICE_NAME}:${BUILD_NUMBER}"
    }

    stages {
        stage('1. 拉取全量代码') {
            steps {
                echo "正在拉取代码..."
                checkout scmGit(
                    branches: [[name: GIT_BRANCH]], 
                    extensions: [[$class: 'CleanBeforeCheckout']], // 构建前清理工作空间
                    userRemoteConfigs: [[
                        credentialsId: GIT_CRED_ID, 
                        url: GIT_URL
                    ]]
                )
            }
        }

        stage('2. 验证 Dockerfile') {
            steps {
                script {
                    if (!fileExists(DOCKERFILE_PATH)) {
                        error "❌ 未找到 Dockerfile: ${DOCKERFILE_PATH},请在ProductApi目录下创建Dockerfile"
                    }
                    echo "✅ Dockerfile 验证通过"
                }
            }
        }

        stage('3. 构建 Docker 镜像') {
            steps {
                echo "构建镜像: ${DOCKER_IMAGE_TAG}"
                sh "docker build -t ${DOCKER_IMAGE_TAG} -f ${DOCKERFILE_PATH} ."
                echo "✅ 镜像构建完成"
            }
        }

        stage('4. 清理旧容器和镜像') {
            steps {
                echo "清理旧容器和悬空镜像..."
                sh """
                docker stop ${SERVICE_NAME} || true
                docker rm ${SERVICE_NAME} || true
                docker image prune -f
                """
            }
        }

        stage('5. 启动新容器') {
            steps {
                echo "启动容器: ${SERVICE_NAME}"
                sh "docker run -d --network dorm-network -p ${HOST_PORT}:${CONTAINER_PORT} -e ASPNETCORE_ENVIRONMENT=Development -e ASPNETCORE_URLS=http://0.0.0.0:${CONTAINER_PORT} -e LANG=zh_CN.UTF-8 -e LC_ALL=zh_CN.UTF-8 --name ${SERVICE_NAME} ${DOCKER_IMAGE_TAG}"
                
                // 等待容器启动并验证健康状态
                sleep 10
                sh "docker ps | grep ${SERVICE_NAME}"
                echo "✅ 容器启动成功"
            }
        }
    }

    post {
        always {
            cleanWs() // 构建完成后清理工作空间
            echo "工作空间清理完成"
        }
        success {
            script {
                def containerId = sh(
                    script: "docker ps -qf name=${SERVICE_NAME}",
                    returnStdout: true
                ).trim()
                
                echo "========================================="
                echo "✅ 产品服务发布成功!"
                echo "📖 访问地址: http://你的服务器IP:${HOST_PORT}/swagger"
                echo "🐳 容器ID: ${containerId}"
                echo "========================================="
            }
        }
        failure {
            echo "❌ 构建失败!请查看详细日志"
            // 输出容器日志帮助排查(容器可能已停止,使用docker logs -f 会卡住,去掉-f)
            sh "docker logs ${SERVICE_NAME} 2>&1 || true"
        }
    }
}

 

五、实战步骤 3:使用Jenkins创建Pipeline任务(以商品服务为例)

上述文件添加后,提交到代码仓库中。

1. 创建凭证

  • 进入 Jenkins 首页,点击「设置」;
  • 找到「安全」,选择「凭证」,点击「新增凭证」,选择「用户名与密码」模式
  • 配置凭证:
    • 「范围」选择「Global (Jenkins, nodes, items, all child items, etc)」;
    • 「用户名」输入你的Gitee用户名;
    • 「密码」输入你的Gitee密码;
    • 「ID」填写的要和Jenkinsfile文件中GIT_CRED_ID一致;
    • 「描述」填写凭证描述;
  • 点击「Save」保存凭证。

image

 

image

 

image

 

image

 2. 在 Jenkins 创建独立任务

  • 进入 Jenkins 首页,点击「新建 Item」;
  • 输入任务名称:商品服务,选择「流水线」,点击「确定」;
  • 配置 流水线:
    • 「Definition」选择「Pipeline script from SCM」;
    • 「SCM」选择「Git」,填写用户服务的仓库 URL;
    • 「Credentials」选择之前配置的凭证;
    • 「Branch Specifier」填写*/master
    • 「Script Path」填写Jenkinsfile
  • 点击「Save」保存配置。

image

 

image

 image

image

 3.构建

构建前,记得在部署服务器上将ProductApi使用的5002端口在防火墙打开,要不然即使构建成功,也访问不了接口文档

image

 

点击构建

image

 构建成功

3a14c02378cc0072ed37b36249e9299f

 然后通过你部署的服务器IP+5002端口访问,出现swagger文档就部署成功了

image

 

六、常见问题解决

1. Docker 构建慢,NuGet 包还原失败

  • 配置 Docker 镜像加速器(如阿里云加速器),加快基础镜像拉取速度;
  • 在 Dockerfile 中配置国内 NuGet 源(如 Azure NuGet 源),避免官方源访问慢;
  • 利用 Docker 缓存机制,将COPY .csprojdotnet restore放在前面,代码不变时无需重复还原依赖。

2. 容器内时间与本地不一致

  • 在 Dockerfile 中安装tzdata包并配置时区,如本文示例所示;
  • 不要通过挂载宿主机/etc/localtime的方式解决,这在 Alpine 镜像中会导致问题。

3. Jenkins 没有权限执行 Docker 命令

  • 将 Jenkins 用户添加到 docker 用户组:
    sudo usermod -aG docker jenkins
    sudo systemctl restart jenkins
  • 重启 Jenkins 服务后生效。

4. 镜像推送失败,提示认证错误

  • 确认 Jenkins 凭据中的镜像仓库用户名和密码正确;
  • 如果使用阿里云 ACR,密码是访问密钥,不是登录密码;
  • 确认镜像名称格式正确:仓库地址/命名空间/镜像名:标签

七、总结

本篇我们彻底贯彻了微服务 “独立构建、独立部署” 的核心原则,完成了单服务 Docker 容器化与单服务独立 Jenkins 流水线的全流程落地:
  1. 理解了传统单体构建的致命问题,以及 Docker + 独立构建在微服务架构中的核心价值;
  2. 实现了.NET 8 微服务的优化版 Docker 容器化,镜像体积控制在 100MB 以内;
  3. 搭建了企业级单服务独立 Jenkins 流水线,实现了代码提交、构建、测试、打包、部署的全自动化;
  4. 梳理了版本号管理、共享代码处理、部署策略、镜像安全等最佳实践。
这种 “一服务一流水线” 的模式,是企业级微服务 CI/CD 的标准做法,它能让团队的交付效率提升数倍,同时大幅降低发布风险。
posted @ 2026-05-21 16:05  挺秃然的i  阅读(51)  评论(0)    收藏  举报