从0到1企业级DevOps实践

基于.net core+Docker+jenkins pipeline+harbor+helm+Kubernetes的企业级DevOps实践(2020.07)

前言

作为白嫖滴神,一是白嫖多了也不好意思,二是自己做过的东西,理解了的知识点总是忘,写点东西记录下。当然我也不是完全白嫖,主要是因为太穷了,卑微打工仔,只能投币三连,最大极限了。.net很多资料都是隔靴搔痒,讲的太浅未深入原理,又或者人云亦云,自己都没搞明白就照着抄,发博客,再就是好点的都收费。所以希望能反馈点东西为.net生态做点贡献。这个虽然由于我做了脱敏处理没办法直接跑起来,但是学东西重要的是理解,不是背题和抄,相信看完后,你能够按自己的想法完整的搭建一套。
原本想的将代码和yaml以及jenkinsfile一起放git上的,但是一是因为个人太懒了,二是其实我讲的这个内容跟代码没多大关系,还是等我微服务的项目写完后,再把代码放上去。

实践准备

  1. 硬件资源
    测试环境: CentOS7.4版本以上 虚拟机3台(4C+8G+50G),内网互通,可访问外网
    生产环境: 腾讯云服务器 CentOS7.6(2C+4G)+CentOs7.5(1C+1G)

  2. 网络环境
    最好能科学上网,很多镜像需要从国外镜像源下载

  3. git仓库
    https://gitee.com/Gao06/hello-world.git

4.教学视频
Docker+K8S+Jenkins项目实战视频教程
老男孩Kubernetes教程 k8s企业级DevOps实践
尚硅谷Kubernetes教程(17h深入掌握k8s)
Docker最新超详细版教程通俗易懂

5.教学文档
《kubernetes权威指南》
《阿里巴巴DevOps实践手册》

上述资料需要整体阅读,并不会看了某一个就能实现完整CI/CD落地,具体落地实践方案参考本文章
上述资料实践部分,会有一部分坑,但不影响这些资料整体质量,具体踩坑位置,会在本文章指出
本文章实践环境,以上述 硬件资源->实际环境为标准来讲解,个人可以自己做相应调整,相信看完本文章再进行调整也很简单。
其他一些方案、ppt资料为私人资料,能分享的都会放git上,有问题的可以留言,基本都会回答。文中IP密码等敏感内容都是自己瞎写的。

6.工作计划

序号 工作内容 人天
1 Jenkins安装配置(docker安装)
2 k8s集群安装
3 Helm安装
4 Baget安装配置及nuget包迁移)
5 制品库harbor安装
6 SonarQuebe和SonarScanner安装配置
7 编写k8s相关yaml文件
8 修改dockerfile(指定docker上下文)
9 编写jenkinsfile相关流水线脚本
10 测试调整

7.软件版本

序号 名称 版本 镜像
1 Docker 19.03.12
2 Docker-Compose 1.26.0
3 Helm v3.2.1 dtzar/helm-kubectl:latest
4 K8s 服务端 v1.16.3-tke.9
5 Progeresql Postgres:latest
6 SonarQube Sonarqube:latest
7 SonarScaner nosinovacao/dotnet-sonar:latest
8 Baget loicsharma/baget:latest
9 Jenkins jenkinsci/blueocean:latest

软件版本中,含镜像的内容都是在docker中部署,含版本的直接在centos上安装
软件版本可以根据自己实际情况而定,老一点新一点没关系
关于Sonar代码质量扫描的内容,因为缺少相关服务器,暂未落地,后续会补上,目前缺少这个也不影响整体阅读

部署架构

系统部署架构.jpg

我们准备了两台服务器,一台服务器(10.129.55.18)仅部署docker和Baget,只是单纯做一个nuget私有服务器使用。另一台服务器(10.129.55.110)作为运行我们整个CI/CD的服务器,部署整个k8s集群、镜像仓库(腾讯云或harbor二选一)、helm、jenkins等。图中没有IP的服务器,是单纯作为私有github代码仓库。(这个IP是我随便写的)

此处并没做k8s和jenkins高可用集群,一是因为服务器资源有限,二是单节点容易理解学习。如果需要部署成高可用集群,参考下列部署文档即可,高可用集群和单节点其实思路差不多,相信单节点完整跑完后,部署高可用也可以很快实现落地。
此处没有部署sonar,因为sonar很吃内存和硬盘资源,我这两台服务器资源不够部署不了,所以文章会缺少集成sonar代码扫描的内容,不过整体不影响CI/CD流程

流程示意

1596183240(1).jpg

整个流程,我们入口点为git私有仓库。首先从git仓库拉取(checkout)项目代码,然后将代码通过sonar进行扫描,查看是否有不合规范的代码,如果扫描通过,则用docker build构建镜像。构建镜像时,从baget拉取还原nuget包,当镜像构建成功后,将镜像打tag,再把镜像推送到私有镜像仓库(harbor或腾讯云),最后通过helm把我们推送的镜像发布到k8s集群中。这样我们的应用就发布成功了。
我们的用户有管理员和普通用户,管理员可以用图上三种方式访问控制k8s集群。生产环境下用户通过nginx反向代理访问到k8s集群,这样保障安全性。

我们有两个jenkins任务 一个“HelloWorld”,一个“HelloWorld - 预发”。分别对应发布到不同k8s集群
推送代码时自动触发jenkins的任务->拉取代码->代码扫描->镜像构建->镜像推送->部署到测试环境k8s集群
测试完成后,手动点击触发jenkins任务->部署到生产环境k8s集群(从镜像仓库拉取刚推送的镜像)

jenkins安装

#执行命令
docker run \
  -u root \
  -d \
  -p 8080:8080 \
  -p 50000:50000 \
  -v $(which docker):/bin/docker \
  -v /var/jenkins_home:/var/jenkins_home \
  -v /var/run/docker.sock:/var/run/docker.sock \
  --restart=always \
  --name=jenkinsci \
  jenkinsci/blueocean

这里相对于网上的使用 Docker 安装 Jenkins 的方式有一个避坑点,因为我们的所有整个CI/CD流程是用jenkinsfile写的脚本,这些脚本在用docker安装的jenkins里执行,这样就容易发生“docker in docker”的情况,所以加上了“ -v /var/run/docker.sock:/var/run/docker.sock \”,具体原理参考博客,docker的/var/run/docker.sock参数

1596187735(1).jpg

jenkins安装完成后,我们访问可能会出现jenkins离线的页面,原因是在我们jenkins里有一个配置文件default.json,这个文件默认会根据地址www.google.com去检测你的网络状况,因为没配置科学上网,你访问不了google,然后就网络不通,我们通过修改这个配置,并设置jenkins镜像加速。就解决了离线问题,而且下插件也改成了国内的清华的源。

$ cd  /var/jenkins_home/updates  #进入更新配置位置
sed -i 's/http:\/\/updates.jenkins-ci.org\/download/https:\/\/mirrors.tuna.tsinghua.edu.cn\/jenkins/g' default.json && sed -i 's/http:\/\/www.google.com/https:\/\/www.baidu.com/g' default.json

然后我们解决完离线问题后,出现页面


1596188771(1).jpg
#执行命令
cat /var/jenkins_home/secrets/initialAdminPassword

将得到的结果,输入到jenkins解锁,我们jenkins就安装好了,剩下的填用户密码什么的就参考网上教程,自己填。也没啥踩坑的点了。针对通过配置jenkins,连接k8s、git等内容,先不着急配,后面再处理。

k8s集群安装

参考教程使用kubeadm安装kubernetes_v1.18.x,基本可以全程无坑安装。如果对k8s很熟,可以考虑使用二进制安装。

Helm安装

Helm的安装很简单,这里安装建议是安装Helm3及以上版本,因为Helm2和Helm3与k8s通信的方式改变了。本文内容“软件版本”中,既在cenos中直接安装了,又用docker镜像安装了。因为cenos中直接安装,对于整个CI/CD流程没有任何影响,仅仅就是为了有个客户端工具,可以手动输入命令与k8s集群交互。docker中安装helm是因为,我们的jenkinsfile在jenkins里执行,但是jenkins没有Helm客户端,没办法执行Helm命令发布到K8s集群,所以在jenkinsfile中使用agent,动态的将Helm安装到docker中,当jenkinsfile中这个发布操作执行结束后,删除这个docker容器。

#下载Helm客户端
$ wget https://get.helm.sh/helm-v3.2.1-linux-amd64.tar.gz
#解压 Helm
$ tar -zxvf helm-v3.2.1-linux-amd64.tar.gz
#复制客户端执行文件到 bin 目录下,方便在系统下能执行 helm 命令
$ cp linux-amd64/helm /usr/local/bin/

执行完上述命令,我们的Helm就安装好了,但是这里有个坑。我们思考下,kubectl作为k8s客户端工具,是怎么跟k8s交互的?怎么验证身份信息的呢?
默认情况下,kubectl$HOME/.kube 目录下查找名为 config 的文件(安装目录/root/.kube/)。这个config文件中的内容就是相关的用户权限、集群地址、CA证书相关信息,这样你根据这个文件就可以访问相应的k8s集群了。但是我想自己设置可访问的K8S集群怎么做呢,可以通过设置 KUBECONFIG 环境变量或者设置 [--kubeconfig]参数来指定其他 kubeconfig 文件。我们的helm也是一样,通过访问环境变量KUBECONFIG,获取到相应的kubeconfig文件,访问到相应的集群。
说明: 用于配置集群访问的文件称为 kubeconfig 文件。这是引用配置文件的通用方法。这并不意味着有一个名为 kubeconfig 的文件

#执行命令 设置环境变量
export KUBECONFIG=/root/.kube/config

如果想配置访问多k8s集群,可参考使用 kubeconfig 文件组织集群访问

Baget安装

安装Baget是因为我们jenkins和原有nuget两个服务器网络不通,没办法,只能迁移私有nuget仓库,私有nuget仓库有很多,对比下来这个Baget是最好的,开源简单,就功能支持少一点,不过可以自己写。这个Baget安装可以忽略,如果不需要搭建私有nuget库的话。
搭建过程参考在Linux上搭建基于开源技术的nuget私人保密仓库,无坑搭建。说明一点就是,baget目前不支持用户权限这块,所以用的nginx的auth做的用户认证。github上作者说,这个支持用户权限这个功能,后续版本即将实现,看了下PR已经有相关功能的代码了。不过目前还是不支持的。

harbor安装

harbor无论是http还是https安装都很简单,没有什么坑,参考Docker 企业级镜像仓库 Harbor 的搭建与维护企业级镜像仓库 Harbor 的安装与配置

SonarQuebe安装

SonarQube整个架构由4个组件组成:

  • SonarQube 服务端,包括web服务界面,elasticsearch搜索引擎,计算引擎三个主要部分。
  • SonarQube 数据库存储端,用于SonarQube 服务端数据。
  • SonarQube插件,可能包括语言,SCM,集成,身份验证和治理插件等,用于jenkins中集成sonarqube。
  • SonarScanner客户端,开发人员或持续集成服务器通过SonarScanner进行项目代码分析。


    image.png

    要安装使用SonarQube服务端需要一些前提要求,由于SonarQube服务端需要安装elasticsearch作为搜索引擎,所以主要端要求的限制大多在elasticsearch。
    Linux系统,需要确保一些内核参数,主要是为了满足Elasticsearch的运行,如官方文档所示,需要设置几个内核参数,查看命令也列出,使用其中的命令,查看是否满足。


    image.png

    这些参数保证后,开始安装sonarquebe数据库和服务端。
#安装数据库
docker run -d -p 6000:5432 -v /var/sonar/db:/var/lib/postgresql/data \
-e POSTGRES_USER=sonar -e POSTGRES_PASSWORD=sonar \
--name=sonar-postgres --restart=unless-stopped postgres:latest
#安装服务端
 docker run -d --name sonarqube \
    -p 9000:9000 \
    -e sonar.jdbc.url="jdbc:postgresql://192.888.10.122:6000/sonar" \
    -e sonar.jdbc.username=sonar \
    -e sonar.jdbc.password=sonar \
    -v sonarqube_conf:/opt/sonarqube/conf \
    -v sonarqube_extensions:/opt/sonarqube/extensions \
    -v sonarqube_logs:/opt/sonarqube/logs \
    -v sonarqube_data:/opt/sonarqube/data \
    --restart unless-stopped \
    sonarqube

编写dockerfile文件

对于dockerfile的编写可以参考教程,尚硅谷Docker核心技术。对于.net core使用VS开发,通常会自动生成一个Dockerfile,但有些时候这个dockerfile文件是不适用的,需要针对不同情况调整。
这里还有个坑就是,我们根据dockerfile进行build生成镜像时,经常会报错“找不到文件路径”,这是因为我们不同的项目代码结构可能不一样,docker build时,会根据上下文,把当前上下文的内容传输到docker容器中,如果上下文指定不正确就容易找不到相应的文件。具体参考深入理解 Docker 构建上下文
以采集报告为例,编写dockerfile如下,然后执行命令。

#dockerfile
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM ccr.ccs.tencentyun.com/demaxiya/dotnet-core-sdk:3.1-buster-auth-nuget AS build
WORKDIR /src
COPY . .
RUN dotnet restore "Gy.HelloWorld.Api.csproj" -s http://nuget.demaxiya.com/v3/index.json -s https://api.nuget.org/v3/index.json
RUN dotnet build "Gy.HelloWorld.Api.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "Gy.HelloWorld.Api.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Gy.HelloWorld.Api.dll"]
#指定上下文构建
docker build --add-host nuget.demaxiya.com:192.222.222.22 \
-t ccr.ccs.tencentyun.com/demaxiya/helloworld:v1.0.0.7232 src/Gy.HelloWorld.Api

ccr.ccs.tencentyun.com/demaxiya/dotnet-core-sdk:3.1-buster-auth-nuget这个镜像是自己制作的镜像,基于.net core sdk:3.1-buster,内置了http://nuget.demaxiya.com/这个私有nuget服务器的账户信息,避免每个dockerfile都要写nuget认证信息。

编写k8s相关yaml文件

首先我们参考阅读一下 ASP.NET Core on K8S学习初探(3)部署API到K8S。从文章中我们可以看出,我们部署应用到k8s集群时,需要写一个yaml文件,然后执行“kubectl create -f 文件名.yaml”,这样就把应用发布到k8s集群了。从博客中的示例我们看到yaml文件有一个"kind: Deployment"和“kind: Service”,我们执行命令“kubectl create -f 文件名.yaml”时,会根据这个yaml文件在k8s集群中创建一个Deployment对象和Service对象。这两个对象的作用、原理参考Kubernetes中文手册名词解释。

可是直接用命令“kubectl create -f 文件名.yaml”有几个问题,第一,yaml文件中的内容参数都写死了,我每次修改都得改yaml文件,没办法用一个变量来表示具体的参数值。第二,假设我有很多不同的对象,每个对象我都单独写一个yaml文件,这样我执行命令“kubectl create ”要执行很多次很麻烦。于是我们就将这个yaml文件改为Helm方式。Helm教程参考 Helm 用户指南

我们首先参考k8s发布 以“采集报告服务为例”,可以大体看到helm文件夹中的结构。当我们Helm文件编写好后,执行命令“Helm upgrade -参数 你的helm文件夹路径”,就会根据文件夹中的yaml文件生成各个对象。首先 chart.yaml 作为这个chart的说明,描述应用的版本信息和应用名称等等。然后values.yaml,configmap.yaml配置应用和k8s的一些参数。_helpers.tpl设置一些公共设置如应用的名称、label、账户等等。然后deployment.yaml读取values.yaml,_helpers.tpl等等这些配置的值,生成deployment对象,生成和管理Pod,然后用户通过访问service.yaml的对象,找到对应的Pod,访问pod中的应用程序。

image.png

以HelloWorld项目为例,HelloWorld项目git地址。(我没有写代码)

  1. 下载代码到本地,将下载的代码拷贝到安装了helm的服务器
  2. 执行helm upgrade命令
#创建名称空间
kubectl create namespace ${kube_namespace}
#镜像仓库为私有镜像仓库 需要再k8s集群中设置secret信息
kubectl create secret docker-registry qcloudregistrykey --docker-server=ccr.ccs.tencentyun.com --docker-username=demaxiya --docker-password=helloworld  -n ${kube_namespace}
#执行安装
helm upgrade -i  --namespace=${kube_namespace}  --set image.repository=${DOCKER_IMAGE} \
--set image.tag=${GIT_TAG} --set replicaCount=${params.replica_count} --set nameOverride=${GIT_REPO}  ${GIT_REPO} ./deploy/helm/

成功运行后可以看到,这里ready是1/1,假如出现错误可以使用命令kubectl -n 名称空间 describe pod pod的name来查看详细的错误,或者通过kubectl -n 名称空间 logs pod的name查看错误日志排错。使用helm upgrade成功发布应用到k8s集群后,我们准备工作算是做完了,开始正式使用jenkins实现CI/CD.


image.png

使用jenkins实现CI/CD

参考上文的流程示意,安装上文的步骤我们一步步实现流水线。

jenkins配置

  • jenkins安装插件Git Parameter、Environment Injector Plugin、Generic Webhook Trigger Plugin
  • 新建任务->填写任务名称->选择“流水线”(并非多分支流水线)->确定
  • 勾选Prepare an environment for the run,配置自己需要使用的内置的环境变量
  • 勾选参数化构建,配置用户自己填写的变量
  • 勾选Generic Webhook Trigger,填写token值,使私有git仓库能发送请求到jenkins,使jenkins实现自动构建
  • 配置流水线scm


    image.png

这里内置了GIT_REPO是为了设置镜像地址、service的名称等,具体参考jenkinsfile。
这里内置了kafka、mongo信息,因为不同环境,以来的服务地址不一样,内置在这里方便改动(但安全性会相应降低)


image.png

这里的git参数必须对应SCM配置为git才能生效

image.png

image.png

因为我们私有仓库是bitbucket,因此它必须配置token,不然会报错404.在我们jenkins这里配置后,去自己的git仓库配置webhook,url填:http://jenkins账户:jenkins密码@jenkins地址:jenkins端口/generic-webhook-trigger/invoke?token=jenkins中配置的token值
image.jpg

这里的Credentials来自配置,系统管理->manager credentials->全局->添加凭据。这里配置git仓库的账户密码。
这样我们整个流水线就配置好了,可能你会有疑惑,怎么没有连接镜像仓库和k8s集群。怎么和网上的教程不一样,下面我们看下整个CI/CD的核心,jenkinsfile pipeline。

jenkinsfile解析

首先看下jenkinfile的代码

def createTag() {
    // 定义一个版本号作为当次构建的版本,输出结果 20191210175842_69
    def yesterday= sh(returnStdout: true,script: 'date  +"%Y-%m-%d" -d  "-1 days"').trim()
    def commitCount= sh(returnStdout: true,script: "git rev-list HEAD --count --since=${yesterday}").trim()
    def dateNow= sh(returnStdout: true,script: 'date "+%-m%d"').trim()   
    return  "v1.0.${dateNow}.${commitCount}"
}
image_tag = "default"  //定一个全局变量,存储Docker镜像的tag(版本)
pipeline {
    agent any
    environment {
        GIT_BRANCH = "${env.gitTargetBranch}"  //项目的分支
        GIT_TAG = createTag()
        GIT_REPO= "${env.GIT_REPO}"
        DOCKER_CONTEXT_PATH="${env.CONTEXT_PATH}"
        DOCKER_REGISTER_CREDS = credentials('e2e1cb5f-5538-43d7-2-14561') //docker registry凭证
        DOCKER_REGISTRY = "fwafa.421.42.com" //Docker仓库地址
        DOCKER_NAMESPACE = "321"  //命名空间
        DOCKER_IMAGE = "${DOCKER_REGISTRY}/${DOCKER_NAMESPACE}/${GIT_REPO}" //Docker镜像地址
        KUBECONFIG="/root/.kube/config:/root/.kube/develop-config" //k8s集群信息
    }
    parameters {
        string(name: 'replica_count', defaultValue: '2', description: '容器副本数量')
    }
    stages {
        stage('Test Pipeline Stages') {
            when {
                 environment name: 'deploy_env', value: 'test'
            }
            stages {
                stage('Params Analyze') {
                    agent any
                    steps {
                    echo "1. 构建参数检查"
                    echo "项目名称 ${env.GIT_REPO}"
                    
                    //    script {
                    //         if (env.gitTargetBranch.indexOf("develop") == -1&&"${env.deploy_env}"=="develop") {
                    //           error "当前分支,非开发分支,不允许构建!"
                    //         } else if (env.gitTargetBranch.indexOf("release") == -1&&"${env.deploy_env}"=="pre-release") {
                    //             error "当前分支,非预发布分支,不允许构建!"
                    //         } else if (env.gitTargetBranch.indexOf("master") == -1&&"${env.deploy_env}"=="prod"){
                    //            error "当前分支,非生产分支,不允许构建!"
                    //         }                  
                    //     }  
                    }
                }
                stage('Code Pull') {
                    steps {
                        echo "2. 代码拉取"
                        script {
                            checkout([$class: 'GitSCM', branches: [[name: '$gitTargetBranch']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "09d880cf-48af-4ce1-ac85-5f0c223e2559", url: "${env.GIT_URL}"]]])
                            }               
                                    
                    }
                }
                stage('Code Analyze') {
                    agent any
                    steps {
                    echo "3. 代码静态检查"
                    }
                }
                stage('Docker Build') {
                    steps {
                        echo "4. 构建Docker镜像"
                        echo "镜像地址: ${DOCKER_IMAGE}"
                        echo "镜像标签: ${GIT_TAG}"
                        script {
                            //登录Docker仓库
                            sh "docker login -u ${DOCKER_REGISTER_CREDS_USR} -p ${DOCKER_REGISTER_CREDS_PSW} ${DOCKER_REGISTRY}"  
                            //通过--build-arg将profile进行设置,以区分不同环境进行镜像构建
                            sh "docker build --add-host nuget.degea31.com:182.132.313.37 -t ${DOCKER_IMAGE}:${GIT_TAG} ${DOCKER_CONTEXT_PATH}"
                            sh "docker push ${DOCKER_IMAGE}:${GIT_TAG}"
                            sh "docker rmi -f ${DOCKER_IMAGE}:${GIT_TAG}"
                        }
                    }
                }
            }
        }
        //发布部分 测试、预发、生产都执行
        stage('Helm Deploy') {
            agent {
                docker {
                    image 'wzrdtales/helm-kubectl'
                    args '-u root:root -v /root/.kube:/root/.kube'
                }
            }
            steps {
                echo "5. 部署到K8s"  
                script {
                    //设置k8s上下文
                    def kube_context = ""
                    def kube_namespace = ""
                    if ("${env.deploy_env}"== "test") {
                        kube_context = "kubernetes-admin@kubernetes"
                        kube_namespace = "test"
                    } else if ("${env.deploy_env}" == "pre-release") {
                        kube_context = "master"
                        kube_namespace = "kube-pre-release"
                    } else if ("${env.deploy_env}" == "master"){
                         kube_context = "kubernetes-admin@kubernetes"
                    }
                    //多集群时 设置上下文
                    sh "kubectl config use-context ${kube_context}"
                    //cat将文件重定向输出到临时文件 envsubst替换环境变量
                    sh "cat ./deploy/helm/values.yaml > tmp.yaml"
                    sh "envsubst < tmp.yaml > ./deploy/helm/values.yaml"
                     //根据不同环境将服务部署到不同的namespace下,这里使用分支名称
                    sh "helm upgrade -i  --namespace=${kube_namespace}  --set image.repository=${DOCKER_IMAGE} --set image.tag=${GIT_TAG} --set replicaCount=${params.replica_count} --set nameOverride=${GIT_REPO}  ${GIT_REPO} ./deploy/helm/"                
                }
            } 
        }
    }
}

jenkins pipeline用的是Groovy写的,跟java使用同样的jvm运行,而jvm和CLR又很相似,所以这些代码理解起来也很简单。所以只讲几个不容易理解的点。

            when {
                 environment name: 'deploy_env', value: 'develop'
            }

还记得我们说的有两个jenkins任务,一个对应测试环境k8s,一个对应生产环境k8s。这里是因为Params Analyze、Code Pull、Code Analyze、Docker Build这几个步骤都仅在测试环境那个jenkins任务执行,所以这里根据环境设置了执行条件(Params Analyze应该生产环境的jenkins任务也需要这个步骤,但是为了方便调试,就直接把Params Analyze中的内容全屏蔽了,等同于两个jenkins任务都没有这个步骤)

 sh "docker build --add-host nuget.3131.com:182.2fa.2wq312.37 -t ${DOCKER_IMAGE}:${GIT_TAG} ${DOCKER_CONTEXT_PATH}"

docker build时,加了参数 --add-host nuget.bazhuayu.com:182.254.222.37这是因为我们的Dockerfile中的基础镜像没有相应host,当docker restore时,无法识别私有nuget服务器域名,这里还指定“”${DOCKER_CONTEXT_PATH}"为构建上下文,至于构建上下文,之前的内容也说过了,具体参考相关博客。

      agent {
                docker {
                    image 'wzrdtales/helm-kubectl'
                    args '-u root:root -v /root/.kube:/root/.kube'
                }
            }

这里agent docker表示当流水线执行这一步时,在基于镜像wzrdtales/helm-kubectl生成的容器中执行,当steps 中内容执行完成后,自动销毁这个容器。这个镜像wzrdtales/helm-kubectl是含有kubectl、helm、gettext这三个客户端工具的镜像。使我们能使用helm命令连接k8s集群、envsubst 命令替换values.yaml中环境变量。wzrdtales/helm-kubectl这个镜像的dockerfile可以看下
这里 -v /root/.kube:/root/.kube是因为为了避免“docker in docker”,具体参考前文。

#wzrdtales/helm-kubectl的dockerfile
FROM dtzar/helm-kubectl #基础镜像
MAINTAINER Tobias Gurtzick <magic@wizardtales.com> 
RUN apk add --update --no-cache gettext && rm -fr /var/cache/apk/* #安装gettext 
#dtzar/helm-kubectl 的dockerfile
FROM alpine:3.11

ARG VCS_REF
ARG BUILD_DATE

# Metadata
LABEL org.label-schema.vcs-ref=$VCS_REF \
      org.label-schema.name="helm-kubectl" \
      org.label-schema.url="https://hub.docker.com/r/dtzar/helm-kubectl/" \
      org.label-schema.vcs-url="https://github.com/dtzar/helm-kubectl" \
      org.label-schema.build-date=$BUILD_DATE

# Note: Latest version of kubectl may be found at:
# https://github.com/kubernetes/kubernetes/releases
ENV KUBE_LATEST_VERSION="v1.18.2"
# Note: Latest version of helm may be found at
# https://github.com/kubernetes/helm/releases
ENV HELM_VERSION="v3.2.1"

RUN apk add --no-cache ca-certificates bash git openssh curl \
    && wget -q https://storage.googleapis.com/kubernetes-release/release/${KUBE_LATEST_VERSION}/bin/linux/amd64/kubectl -O /usr/local/bin/kubectl \
    && chmod +x /usr/local/bin/kubectl \
    && wget -q https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz -O - | tar -xzO linux-amd64/helm > /usr/local/bin/helm \
    && chmod +x /usr/local/bin/helm

WORKDIR /config

CMD bash

最后这一段就是将服务发布到k8s集群中了,因为我们是两个jenkin任务发布到不同的k8s集群,但是需要使用同一个jenkinsfile。因此根据环境判断,设置不同的k8s上下文,使用 Kubernetes API 访问集群
。然后envsubst 命令替换掉之前设置的kafka、mongo等环境变量,最后根据helm upgrade -i命令发布到k8s集群。

 environment {
               KUBECONFIG="/root/.kube/config:/root/.kube/develop-config" //k8s集群信息
         }
               //多集群时 设置上下文
                    sh "kubectl config use-context ${kube_context}"
                    //cat将文件重定向输出到临时文件 envsubst替换环境变量
                    sh "cat ./deploy/helm/values.yaml > tmp.yaml"
                    sh "envsubst < tmp.yaml > ./deploy/helm/values.yaml"
                     //根据不同环境将服务部署到不同的namespace下,这里使用分支名称
                    sh "helm upgrade -i  --namespace=${kube_namespace}  --set image.repository=${DOCKER_IMAGE} --set image.tag=${GIT_TAG} --set replicaCount=${params.replica_count} --set nameOverride=${GIT_REPO}  ${GIT_REPO} ./deploy/helm/"

然后我们构建多了很可能会出现很多的构建记录,感觉写代码多了后,人多少有点强迫症,没用的东西就喜欢删除,这个就是删除构建历史的脚本

def jobName = "hello"
def maxNumber = 1888
  
Jenkins.instance.getItemByFullName(jobName).builds.findAll {
  it.number <= maxNumber
}.each {
  it.delete()
}

然后记起来,迁移nuget也是用脚本,网上可以找到,脚本大概就是循环推送到nuget仓库。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349