CI/CD

目前,我们的 CI 过程使用自建的 Jenkins 服务完成。

大致的自动构造流程如下:

  1. 在我们的 git 项目中添加 jenkins 的构造脚本,并编写脚本完成“拉代码、编译 Dockerfile 、上传镜像仓库” 这几个步骤。

  2. jenkins 的可视化界面中添加构造任务

  3. 构造任务中使用 SCM Pipeline script 方式指定我们的 git 代码仓库地址和登陆的账号密码

  4. 为了使我们可以在 jenkins 可视化页面中每次选择不同分支来构造,需要在构造任务中勾选 “参数化构建过程” 进行配置。

  5. 完成以上 4 个步骤,我们便可以在 jenkins 中启动构造,并得到一个腾讯云的镜像地址

  6. 在 k8s 中更新镜像文件

ASP.NET Core 项目

  1. 增加 dockerfile

    小技巧

    在传统的项目编译中,我们需要在本地编译源代码,然后将编译好的文件打包,上传到服务器。
    这样对于不同类型的项目需要安装不同的依赖,比如:java 需要安装 jdk,maven 等, vue、angular 需要支持不同的 nodejs 版本。
    所以,目前主流的构造方案是在 dockerfile 中完成源代码的编译,打包等工作。这样的好处是编译在 docker 内完成,减少了对 docker 宿主机上运行时依赖,不同的编译运行时在源代码的 dockerfile 中指定。

    其他如 vue、java 等项目请直接 google : xxx build in dockerfile

    dotnet 8 dockerfile 示例

    FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
    WORKDIR /build
    
    ARG NUGET=/tmp/nuget
    
    # copy project file and nuget to build container
    COPY ["src/Demo.Api.csproj", "src/"]
    COPY ["src/nuget.config", "src/"]
    
    # restore nuget packages
    RUN mkdir -p ${NUGET} && \
        dotnet restore "src/Demo.Api.csproj" --disable-parallel --packages ${NUGET}
    
    # Copy everything else and build
    COPY . .
    
    RUN dotnet publish "src/Demo.Api.csproj" -c release -o /dist/publish --packages ${NUGET}
    
    FROM mcr.microsoft.com/dotnet/aspnet:8.0
    WORKDIR /opt
    
    # copy publish files to container
    COPY --from=build /dist/publish .
    
    RUN sed -i 's/MinProtocol = TLSv1.2/MinProtocol = TLSv1/' /etc/ssl/openssl.cnf && \
        sed -i 's/CipherString = DEFAULT@SECLEVEL=2/CipherString = DEFAULT@SECLEVEL=1/' /etc/ssl/openssl.cnf
    
    # install tools
    RUN sed -i "s/deb.debian.org/mirrors.aliyun.com/g" /etc/apt/sources.list
    RUN apt-get update  && apt-get install -y curl
    
    # set env
    ENV ASPNETCORE_URLS=http://+:8080
    ENV TZ=Asia/Shanghai
    EXPOSE 8080
    
    ENTRYPOINT ["dotnet", "Demo.Api.dll"]
    
  2. 在项目文件夹下创建构造脚本文件 Jenkinsfile, 并编写构造脚本

    podTemplate {
        node('jenkins-agent') {
            // 获取分支
            parameters {
                gitParameter branchFilter: 'origin/(.*)', defaultValue: 'main', name: 'BRANCH', type: 'PT_BRANCH'
            }
    
            // 设置环境变量
            stage('set env') {
                script {
                    env.service = 'demo'
                    env.git = 'https://github.com/yoholiao/demo.git'
                }
            }
    
            // 拉取代码,credentialsId 是在 jenkins 中提前配置好能获取git的账号密码。
            stage('git pull') {
                git branch: "${params.BRANCH}", credentialsId: 'xxx-xxx-xx', url: "${env.git}"
            }
    
            // 获取分支当前 commit id
            stage('set repo url') {
                script {
                    // Get the Git commit ID and store it in an environment variable
                    env.GIT_COMMIT_ID = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
                    env.CURRENT_DATE_TIME = sh(script: 'date "+%Y%m%d%H%M%S"', returnStdout: true).trim()
                    env.imageUrl = "registry.cn-hangzhou.aliyuncs.com/yxliao/${env.service}:${env.GIT_COMMIT_ID}.${env.CURRENT_DATE_TIME}.${env.BRANCH}.${env.BUILD_ID}"
                }
            }
    
            stage('docker build and push') {
                container('jnlp') {
                    docker.withRegistry('https://registry.cn-hangzhou.aliyuncs.com', 'xxx-xxx-xx') {
                        def image = docker.build("${env.imageUrl}")
                        image.push()
                    }
                }
                sh "sleep 5 && docker image rm ${env.imageUrl}"
            }
    
            stage('build image info') {
                ansiColor('xterm') {
                    echo "\033[0;34m imageUrl : ${env.imageUrl}\033[0m"
                    echo "\033[0;34m update cmd : kubectl set image deployment/${env.service} ${env.service}=${env.imageUrl} --record\033[0m"
                    echo "\033[0;31m rollback cmd : kubectl rollout undo deployment ${env.service}\033[0m"
                }
            }
        }
    }
    

    小技巧

    demo:4b21355.20241205091711.develop.290
    我们生产的镜像名称是由 项目名称:commit id.时间.分支.构建号 组成。以方便运维对更新管理和应用回滚 \

  3. 在 jenkins 中添加构造任务

    1

    2

  4. 编辑任务详情,设置 git 地址和登陆账号密码。

    3

    4

    5

    注意

    jenkins 的构造文件 Jenkinsfile 目前不支持切换分支,所以我们把文件创建在 master 分支上。 这就变相的要求,其他分支都需要使用同一个构造文件。 如果有分支需要使用不同的构造文件,可临时在 jenkins 的任务页面修改分支名称。

  5. 指定 Jenkinsfile 在文件夹位置,及配置分支的参数化构建

    6

    7

  6. 执行构建并查看构建过程

    8

    9

    10

VUE 项目

备注

jenkins 上的创建项目的基本操作,参考上面的 ASP.NET Core 的构造过程。其他前端项目过程也大致一致。
如果需要增加多余的参数化构造,参考上面的第 5 步。下面的例子中增加了一个 NPM_SCRIPT 的参数

  1. 在项目文件夹下添加 nginx.config 并将如下配置保存到此文件

    events{}
    http {
        include /etc/nginx/mime.types;
        server {
            listen 80;
            server_name localhost;
            root /usr/share/nginx/html;
            index index.html;
            location / {
                try_files $uri $uri/ /index.html;
            }
        }
    }
    

    注意

    注意,上面的 nginx 的路由配置,是 UI 页面做为主站部署时,既 https://xxx.demo.com/ 即可访问 UI。 如果是子站部署 https://xxx.demo.com/eshop , 需要使用下面的配置。

    events{}
    http {
        include /etc/nginx/mime.types;
        server {
            listen 80;
            server_name localhost;
            root /usr/share/nginx/html;
            index index.html;
            location /eshop {
                try_files $uri $uri/ /eshop/index.html;
            }
        }
    }
    
  2. 在项目文件夹下 Dockerfile ,并将如下配置保存到此文件

    FROM node:14.21.3 as builder
    # 定义变量此变量可从 jenkins 页面选择传入
    ARG NPM_SCRIPT
    
    WORKDIR /tmp
    # 设置淘宝镜像
    RUN npm config set registry https://registry.npmmirror.com
    # 拷贝依赖文件、依赖变更文件到tmp目录下
    COPY package.json package-lock.json /tmp/
    RUN npm install
    # 拷贝源代码文件至tmp目录
    COPY . /tmp
    # 执行所定义的变量
    RUN npm run ${NPM_SCRIPT}
    
    # 运行镜像
    FROM nginx:1.16.0
    # 创建子目录
    RUN mkdir -p /usr/share/nginx/html/eshop
    # 拷贝 nginx 配置文件
    COPY ./nginx.conf /etc/nginx/nginx.conf
    # 从 builder 镜像复制编译后文件到运行目录
    COPY --from=builder /tmp/dist /usr/share/nginx/html/eshop
    EXPOSE 80
    CMD ["nginx", "-g", "daemon off;"]
    

    重要

    COPY ./dist /usr/share/nginx/html 这里的 dist 文件夹是 vue build 的输出目录。根据自己项目修改

  3. 在项目文件夹下增加 Jenkinsfile ,并根据自己的项目修改。

    小技巧

    Jenkinsfile 内容参考 ASP.NET Core 项目内容。但是为了给 vue 编译时传递 NPM_SCRIPT 参数,需要在 jenkins 的任务页面中添加参数化构建,并增加如下内容。

    parameters {
        // npm run <script>
        string(name: 'NPM_SCRIPT', defaultValue: '')
    }
    
    stage('docker build and push') {
        container('jnlp') {
            // 这里的腾讯镜像仓库配置不需要改动。
            docker.withRegistry('https://registry.cn-hangzhou.aliyuncs.com', 'xxx-xxx-xx') {
                def image = docker.build("${env.imageUrl}" , "--build-arg NPM_SCRIPT=${params.NPM_SCRIPT} .")
                image.push()
            }
        }
    }
    

VUE 项目作为子站部署

当我们的 vue 项目需要以子站 https://www.demo.com/eshop 方式访问时,需要针对 vue 、nginx 及 dockerfile 都做出相应的修改,才能使得不同项目的 vue 路由都正常工作。

下面,我们以/eshop子站来简单介绍下 vue 需要子站部署时该如何对接。

  1. 修改 vue.config.js

    1module.exports = {
    2    publicPath: '/eshop'
    3}
    
  2. nginx.conf

     1events{}
     2http {
     3    include /etc/nginx/mime.types;
     4    access_log off;
     5    server {
     6        listen 80;
     7        server_name localhost;
     8        root /usr/share/nginx/html;
     9        index index.html;
    10        location /eshop {
    11            try_files $uri $uri/ /eshop/index.html;
    12        }
    13    }
    14}
    
  3. dockerfile

    1FROM nginx:1.16.0-alpine
    2# copy artifact build from the 'build environment'
    3RUN mkdir -p /usr/share/nginx/html/eshop
    4COPY ./nginx.conf /etc/nginx/nginx.conf
    5COPY ./dist /usr/share/nginx/html/eshop
    6# expose port 80
    7EXPOSE 80
    8# run nginx
    9CMD ["nginx", "-g", "daemon off;"]
    

容器平台 K8S

目前,我们在 IDC 中部署了一套自建的 K8S 集群,用于部署我们企业内部服务。对于 C 端服务,我们使用腾讯云容器服务 TKE 及阿里云的容器服务。
部分外采系统使用 K3S 搭建的小集群。

Dashboard

Dashboard 使用飞致云开源的多集群管理工具 kubepi 作为集群的管理工具。 \

kubepi 支持配置 LDAP、OIDC 等认证方式登录,支持多集群管理,支持多种资源的管理,如:deploy configMap service ingress 等。

发布应用

  1. 进入集群 kubepi-cluster-list

  2. 工作负载中 -> Deployment -> YAML -> 从文件读取 kubepi-deploy-upload

  3. 预览 yaml 内容,提交。 kubepi-deploy-add

小技巧

我们可以将 deploy configMap service 相关 yaml 内容合并到同一个 yaml 文件中。

更新应用新版本

  1. 在 deployment 中找到需要更新版本的应用并进入查看详情。 kubepi-edit-image

  2. 在“调整镜像版本”中,更新应用最近的版本号。 kubepi-update-image

危险

我们并不推荐在 jenkins 中增加自动脚本来实现 CI 到 CD 的自动化。
因为当团队的项目较多时,根据 Git 更变自动触发会导致 jenkins 任务过多,持续的 CI、CD 过程会过多消耗集群资源。
所以,我们推荐CI、CD的过程都通过人工操作来完成,按需构建、按需发布!!!

扩容、重启

  1. 扩容、缩容、重启应用 kubepi-scale

  2. 查看应用日志、进入容器、查看文件 kubepi-log kubepi-log-console