基于.net core+Docker+jenkins pipeline+harbor+helm+Kubernetes的企业级DevOps实践(2020.07)
前言
作为白嫖滴神,一是白嫖多了也不好意思,二是自己做过的东西,理解了的知识点总是忘,写点东西记录下。当然我也不是完全白嫖,主要是因为太穷了,卑微打工仔,只能投币三连,最大极限了。.net很多资料都是隔靴搔痒,讲的太浅未深入原理,又或者人云亦云,自己都没搞明白就照着抄,发博客,再就是好点的都收费。所以希望能反馈点东西为.net生态做点贡献。这个虽然由于我做了脱敏处理没办法直接跑起来,但是学东西重要的是理解,不是背题和抄,相信看完后,你能够按自己的想法完整的搭建一套。
原本想的将代码和yaml以及jenkinsfile一起放git上的,但是一是因为个人太懒了,二是其实我讲的这个内容跟代码没多大关系,还是等我微服务的项目写完后,再把代码放上去。
实践准备
硬件资源
测试环境: CentOS7.4版本以上 虚拟机3台(4C+8G+50G),内网互通,可访问外网
生产环境: 腾讯云服务器 CentOS7.6(2C+4G)+CentOs7.5(1C+1G)网络环境
最好能科学上网,很多镜像需要从国外镜像源下载
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代码质量扫描的内容,因为缺少相关服务器,暂未落地,后续会补上,目前缺少这个也不影响整体阅读
部署架构
我们准备了两台服务器,一台服务器(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流程
流程示意
整个流程,我们入口点为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参数。
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
然后我们解决完离线问题后,出现页面
#执行命令
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进行项目代码分析。
要安装使用SonarQube服务端需要一些前提要求,由于SonarQube服务端需要安装elasticsearch作为搜索引擎,所以主要端要求的限制大多在elasticsearch。
Linux系统,需要确保一些内核参数,主要是为了满足Elasticsearch的运行,如官方文档所示,需要设置几个内核参数,查看命令也列出,使用其中的命令,查看是否满足。
这些参数保证后,开始安装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中的应用程序。
以HelloWorld项目为例,HelloWorld项目git地址。(我没有写代码)
- 下载代码到本地,将下载的代码拷贝到安装了helm的服务器
- 执行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.
使用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
这里内置了GIT_REPO是为了设置镜像地址、service的名称等,具体参考jenkinsfile。
这里内置了kafka、mongo信息,因为不同环境,以来的服务地址不一样,内置在这里方便改动(但安全性会相应降低)
这里的git参数必须对应SCM配置为git才能生效
因为我们私有仓库是bitbucket,因此它必须配置token,不然会报错404.在我们jenkins这里配置后,去自己的git仓库配置webhook,url填:http://jenkins账户:jenkins密码@jenkins地址:jenkins端口/generic-webhook-trigger/invoke?token=jenkins中配置的token值
这里的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仓库。