jenkins调用rancher实现war包实现应用的更新
注意:
1.https://rancher.goldwind.com/v1/apps.deployments中只有rancher local集群的deployment信息,不能拿到rancher中管理的其他集群的信息
2.使用https://rancher.goldwind.com/v3/projects的json中请求项目名对应的["data"]["links"]["workloads"]的负载地址,然后请求这个负载地址中对应负载的["data"]["links"]["update"],这个地址是可编辑edit的,可以发起put请求,替换镜像。但是如果多个集群中有同样的项目名,不同的命名空间,并不能使用命名空间进行区分,只有同样的项目名下不用的负载名才能用这种方法。
3.通过api调用python只能通过遴选项目名和容器名的方式,不能通过指定集群与项目名进行部署,所以要求rancher中不同集群中的项目名是不同的,否则可能会有重复的可能。使用sh脚本可实现通过集群名与项目名进行部署的方式。
python脚本实现:
1.jenkins中的调用rancher的脚本rancher.py
#!/usr/bin/env python3 #coding: utf-8 import requests import argparse class Deploy(): def __init__(self, **kwargs): self.modulename = kwargs['modulename'] self.imagename = kwargs['imagename'] self.projectname = kwargs['projectname'] def rancher_deploy(self): baseurl = "https://rancher.goldwind.com/v3/projects" headers = { "Authorization": "xxxx:xxxxx", "Content-Type": "application/json", } requests.packages.urllib3.disable_warnings() response = requests.get(baseurl, headers=headers, verify=False) prors = response.json()["data"] for pror in prors: if pror["name"] == self.projectname: pro_url = pror["links"]["workloads"] pro_response = requests.get(pro_url, headers=headers, verify=False) pro_rss = pro_response.json() pro_rs = pro_rss["data"] for pro_r in pro_rs: if pro_r["name"] == self.modulename: work_up_url = pro_r["links"]["update"] work_response = requests.get(work_up_url, headers=headers, verify=False) work = work_response.json() print(f"容器{self.modulename}的原镜像为:{work['containers'][0]['image']}") print(f"容器{self.modulename}的新镜像为:{self.imagename}") work["containers"][0]["image"] = self.imagename response = requests.put(work_up_url, headers=headers, json=work, verify=False) print('update-image complete') if __name__ == "__main__": """ 参数说明: """ parser = argparse.ArgumentParser(description='trigger rancher deploy') parser.add_argument('--modulename', type=str, default=None) parser.add_argument('--imagename', type=str, default=None) parser.add_argument('--projectname', type=str, default=None) args = parser.parse_args() deploy = Deploy(modulename=args.modulename, imagename=args.imagename, projectname=args.projectname) deploy.rancher_deploy()
2.jenkins中镜像构建的脚本build_image.py
#!/usr/bin/env python3 #coding: utf-8 import argparse from runDeploy import run_cmd class Build_image(): def __init__(self, **kwargs): self.app_name = kwargs['app_name'] self.image_name = kwargs['image_name'] self.registry_url = kwargs['registry_url'] self.username = kwargs['username'] self.password = kwargs['password'] def build_image(self): #模块名 app_name = self.app_name.split('-')[-1] app_name_u = app_name.upper() app_name_a = self.app_name print("构建镜像") build_cmd = f"docker build --build-arg APP_NAME={app_name_u} -t {self.image_name} -f Dockerfile {app_name_a}" print(build_cmd) run_cmd(build_cmd) print("登录harbor") login_cmd = f"docker login -u {self.username} -p {self.password} {self.registry_url}" print(login_cmd) run_cmd(login_cmd) print("推送镜像") push_cmd = f"docker push {self.image_name}" print("push_cmd") run_cmd(push_cmd) if __name__ == "__main__": """ 参数说明: """ parser = argparse.ArgumentParser(description='web build') parser.add_argument('--app_name', type=str, default=None) parser.add_argument('--image_name', type=str, default=None) parser.add_argument('--registry_url', type=str, default=None) parser.add_argument('--username', type=str, default=None) parser.add_argument('--password', type=str, default=None) args = parser.parse_args() buildimage = Build_image(app_name=args.app_name,image_name=args.image_name,registry_url=args.registry_url,username=args.username,password=args.password) buildimage.build_image()
3.在python中使用shell的脚本runDeploy.py
#!/usr/bin/env python3 #coding: utf-8 import subprocess import threading import sys def read_output(stream): while True: line = stream.readline() if not line: break print(line.strip()) sys.stdout.flush() def run_cmd(command): """ 用于运行shell命令,主要是build的使用 :param command: :return: 标准输出stdout """ result = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, bufsize=1, encoding="utf-8") #创建线程以读取输出 output_thread = threading.Thread(target=read_output, args=(result.stdout,), daemon=True) output_thread.start() #等待命令执行完成 result.wait() if result.returncode != 0: sys.exit(1) # 等待线程结束 output_thread.join()
4.jenkins从外部下载war包的脚本load_war.py
#!/usr/bin/env python3 #coding: utf-8 import urllib.request import requests import argparse import os from tqdm import tqdm import re class Load_war(): def __init__(self, **kwargs): self.app_name = kwargs['app_name'] def war_load(self): #模块名 app_name = self.app_name.split('-')[-1] app_name_u = app_name.upper() app_name_a = self.app_name if os.path.exists(app_name_a): pass else: os.makedirs(app_name_a) url_pre = f'https://dl.digital-tsuite.com.cn' #info文件特殊情况vatrep->vat if app_name == "vatrep": file_info = 'vat' else: file_info = app_name #获取info地址 url_info = f'{url_pre}/package2/goldwind/upgrade/{file_info}.info' username = 'xxx' password = 'xxx' print('url_info', url_info) #下载info文件 response_info = requests.get(url_info, auth=(username, password)) if response_info.status_code == 200: # 成功获取到文件内容 with open(f'{app_name_a}/{file_info}.info', 'wb') as file: file.write(response_info.content) else: # 下载失败 print('下载info文件失败:', response_info.status_code) # 从info文件获取war包地址后缀 with open(f'{app_name_a}/{file_info}.info', 'r') as file: lines = file.readlines() last_line = lines[-1].strip() war_file = last_line.split()[2] #拼接war包地址 url_war = f'{url_pre}/{war_file}' print('url_war', url_war) #下载war包 response_war = requests.get(url_war, auth=(username, password), stream=True) if response_war.status_code == 200: file_size = int(response_war.headers.get("Content-Length", 0)) chunk_size = 1024 # 每次下载的缓存块大小 with open(f"{app_name_a}/{app_name_u}.war", "wb") as f, tqdm( total=file_size, unit="B", unit_scale=True, desc="Downloading" ) as pbar: for chunk in response_war.iter_content(chunk_size=chunk_size): if chunk: f.write(chunk) pbar.update(len(chunk)) print("下载完成!") else: # 下载失败 print('下载war文件失败:', response_war.status_code) if __name__ == "__main__": """ 参数说明: """ parser = argparse.ArgumentParser(description='web build') parser.add_argument('--app_name', type=str, default=None) args = parser.parse_args() load_war = Load_war(app_name=args.app_name) load_war.war_load()
5.Dockerfile 是将war包打成镜像,其中APP_NAME为jenkins中传入的变量,可以通过在Dockerfile中使用ARG APP_NAME的方式来使用变量。
FROM harbor.goldwind.com/shuiwu/prod/centos7_openjdk8_tomcat8.5 MAINTAINER shuiwu ARG APP_NAME ADD ${APP_NAME}.war /usr/local/tomcat/webapps/${APP_NAME}.war ADD setenv.sh /usr/local/tomcat/bin/setenv.sh RUN sudo unzip -oq /usr/local/tomcat/webapps/${APP_NAME}.war -d /usr/local/tomcat/webapps/${APP_NAME} RUN sudo chown -R 10001.10001 /usr/local/tomcat/webapps RUN sudo rm -f /usr/local/tomcat/webapps/${APP_NAME}.war
6.Jenkinsfile
//设置仓库名与命名空间前缀相同,如,仓库名为shuiwu,命名空间为shuiwu-dev shuiwu-test shuiwu-pre shuiwu-uat shuiwu-prod等 def REGISTRY_PROJECT = "xx" //rancher中项目名 def PROJECT_NAME = "xx" //镜像名 def IMAGE_NAME = "${REGISTRY_URL}/${REGISTRY_PROJECT}/prod/${APP_NAME}:prod-${BUILD_NUMBER}" pipeline { agent { node { label 'master' } } //指定运行选项 options { //设置构建记录保留个数,构建包保留个数,和构建记录保留天数,可以自行修改。 buildDiscarder(logRotator(numToKeepStr: '30', artifactNumToKeepStr: '30', daysToKeepStr: '15')) //设置禁止并行构建 disableConcurrentBuilds() } stages { stage("Initializationt") { steps{ echo "初始化信息" script { buildDescription "构建的模块是${APP_NAME}" } } } stage("Load_war") { steps{ sh "python3 load_war.py --app_name=${APP_NAME}" } } //打包阶段,打包成镜像 stage("package-stage") { steps { script { echo "打包阶段,打包成镜像" withCredentials([usernamePassword(credentialsId: "${HARBOR_TOKEN}", passwordVariable: 'password', usernameVariable: 'username')]) { sh "python3 build_image.py --app_name=${APP_NAME} --image_name=${IMAGE_NAME} --registry_url=${REGISTRY_URL} --username=${username} --password=${password}" } } } } //部署生产环境阶段 stage('prod-enviroment-deploy') { steps { script { //部署生产环境 echo "部署到生产环境" def ns = "${REGISTRY_PROJECT}-prod" def cluster = "kubernetes-prod" sh "python3 rancher.py --modulename=${APP_NAME} --imagename=${IMAGE_NAME} --projectname=${PROJECT_NAME}" } } } } }
shell脚本实现
1.rancher.sh
#!/bin/bash cluster=$3 pro=Default echo "1" | rancher login https://rancher.goldwind.com/ --token $5:$6 --skip-verify > b.txt proid=`cat b.txt | grep $cluster | grep $pro |awk '{print $1}'` echo "$proid"| rancher context switch rancher kubectl set image deployment/$1 $1=$2 -n $4
2.Jenkinsfile脚本
//定义gitlab的project_id,需要从gitlab仓库查看 def GITLAB_PROJECT_ID = "xx" //定义rancher中的项目名 def PROJECT_NAME = "xx" //设置仓库名与命名空间前缀相同,如,仓库名为eis,命名空间为eis-dev eis-test eis-pre eis-uat eis-prod等 def REGISTRY_PROJECT= "xx" //设置deployname及容器名均要设置为为moudle_name //设置部署应用的类型java,web,python def SERVICE= "java" //设置是否为单模块项目,若为单项目模块(代码仓库src在最外层)则为0,若不为单项目模块则为1 def MOUDLE_SINGLE = "1" //定义动态tag def dynamic_tag = "1.0." + new Date().format('yyyyMMddHHmm') //定义邮件列表,最后要加一个逗号,多个邮箱用逗号,隔开 def SENDOBJECT = "xxx@goldwind.com," //pipeline流水线开始处 pipeline { agent { node { label 'jenkins-slave1' } } //指定使用工具,包括maven,jdk,gradle ,格式例如 jdk 'jdk8' tools { jdk 'jdk8' maven 'mvn3' } //指定交互参数,在自动触发过程中,所有parameters下定义的参数均为空 parameters { string( name: 'build_test', defaultValue: 'build:uat' ) string( name: 'build_prod', defaultValue: 'build' ) string( name: 'build_dev', defaultValue: 'build:uat' ) //定义要部署的模块,为下拉列表,每次选择一个 choice( description: '请选择要部署的模块 ?', name: 'moudle_name', choices: ['xx'] ) choice( description: '请选择npm 环境', name: 'web_env', choices: ['npm', 'yarn'] ) //定义发布生产环境时,需要向gitlab中的master分支打tag,若不填写则使用自动生成动态tag。 string( name: 'git_tag', description: '发布生产环境时,请输入需要向gitlab中master分支打的tag', ) } //全局环境变量定义,可以通过sh命令对已有的系统变量进行修改。 environment { //工程名如gw-ercp-fems-server PROJECT_NAME = "${sh(returnStdout: true, script: "echo ${JOB_NAME}|rev |cut -d / -f2|rev").trim()}" //从工程名中提取环境,如dev/test/prod ENV = "${sh(returnStdout: true, script: "echo ${PROJECT_NAME}|rev | cut -d- -f 1 | rev").trim()}" EV = "${sh(returnStdout: true, script: "echo ${PROJECT_NAME}|rev | cut -d_ -f 1 | rev").trim()}" //设置镜像名 IMAGE_NAME = "${sh(returnStdout: true, script: "echo ${REGISTRY_URL}/${REGISTRY_PROJECT}/$EV/${moudle_name}:$EV-${BUILD_NUMBER}").trim()}" } //指定运行选项 options { //设置构建记录保留个数,构建包保留个数,和构建记录保留天数,可以自行修改。 buildDiscarder(logRotator(numToKeepStr: '30', artifactNumToKeepStr: '30', daysToKeepStr: '15')) //设置禁止并行构建 disableConcurrentBuilds() } //指定各个阶段stages stages { //CI(持续集成)过程:按照规范包括单元测试和代码扫描。只有gitlab自动触发时才会进行这两个阶段。 //初始化信息 stage("Initializationt") { steps { echo "初始化信息" echo "拉取python脚本" checkout([$class: 'GitSCM', branches: [[name:"k8s"]], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'SubmoduleOption', disableSubmodules: false, parentCredentials: true, recursiveSubmodules: true, reference: '', trackingSubmodules: true], [$class: 'SparseCheckoutPaths', sparseCheckoutPaths: [[path: 'python'], [path: 'ansible']]], [$class: 'RelativeTargetDirectory', relativeTargetDir: 'temp']], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${GIT_AUTH}", url: "${DEVOPS_ADDRESS}"]]]) script { sh """ cp -rf temp/* ./ chmod +x python/*.* """ if ( ENV == 'CD_prod' && BRANCH_NAME != 'master' ) { if (!params.git_tag) { buildDescription "构建的模块是${moudle_name};master的tag打为${dynamic_tag}" } else { buildDescription "构建的模块是${moudle_name};master的tag打为${params.git_tag}" } } else { buildDescription "构建的模块是${moudle_name}" } } } } //设置分支保护阶段 stage("Freezing a Git branch") { //当是生产流水线且为非master分支时设置分支保护 when { expression { ENV =~ 'CD_prod.*' } not { branch 'master' } } steps { script { echo "冻结${BRANCH_NAME}分支" withCredentials([string(credentialsId: "${GITLAB_API_TOKEN}", variable: 'gitlabtoken')]) { sh "python3 python/freezing.py --branch=${BRANCH_NAME} --gitidnum=${GITLAB_PROJECT_ID} --jobname=${JOB_NAME} --token=${gitlabtoken} --option='freeze'" } } } } stage("build-stage") { steps { script { echo "打包构建" sh "python3 python/build.py --jobname=${JOB_NAME} --service=${SERVICE} --build_dev=${params.build_dev} --web_env=${params.web_env} --build_test=${params.build_test} --build_prod=${params.build_prod} --branch=${BRANCH_NAME} --workspace=${WORKSPACE}" } } } stage("code-sonar") { //只有不选择部署即“deploy_flag”不勾选时,才会运行 when { expression { env.JOB_NAME.contains('CI') } } steps{ echo "代码扫描" script { sh "python3 python/update_sonar.py --modulename=${params.moudle_name} --jobname=${JOB_NAME} --branch=${BRANCH_NAME} --workspace=${WORKSPACE} --service=${SERVICE} --moudle_single={MOUDLE_SINGLE}" withSonarQubeEnv('sonar') { sh "${scannerHome}/bin/sonar-scanner" } } script { //设置超时时间为10分钟,超时自动结束pipeline timeout(time: 10, unit: 'MINUTES') { //利用sonar webhook功能通知pipeline代码检测结果,未通过质量阈,pipeline将会fail def qg = waitForQualityGate('sonar') //注意:这里waitForQualityGate()中的参数也要与之前SonarQube servers中Name的配置相同 if (qg.status != 'OK') { error "未通过Sonarqube的代码质量阈检查,请及时修改!failure: ${qg.status}" } } } } } //CD(持续交付/持续部署)过程:按照规范在dev,test,pre,uat,prod环境进行部署,只有手动触发才会执行此阶段。可以按照各自情况进行增删。 //打包阶段,打包成镜像,在cd阶段进行 stage("package-stage") { when { not { expression { ENV =~ 'CI.*' } } } steps { script { echo "打包阶段,打包成镜像" withCredentials([usernamePassword(credentialsId: "${HARBOR_TOKEN}", passwordVariable: 'password', usernameVariable: 'username')]) { if (MOUDLE_SINGLE == "0") { sh "docker build -t ${IMAGE_NAME} -f Dockerfile ." } else { sh "docker build -t ${IMAGE_NAME} -f Dockerfile ${moudle_name}" } sh """ docker login -u ${username} -p ${password} ${REGISTRY_URL} docker push ${IMAGE_NAME} """ } } } } //部署开发环境阶段 stage('dev-enviroment-deploy') { when { //在开发流水线进行部署 expression { ENV =~ 'CD_dev.*' } //只有分支为develop*,release*,hotfix*时才会运行 anyOf { branch 'dev*'; branch 'develop*'; branch 'release*'; branch 'hotfix*' } } steps { script { //部署开发环境 echo "部署开发" def ns = "${REGISTRY_PROJECT}-dev" def cluster = "kubernetes-dev" sh "python3 rancher.py --modulename=${params.moudle_name} --imagename=${IMAGE_NAME} --projectname=${PROJECT_NAME}" //sh "python3 python/rancher.py --modulename=${params.moudle_name} --imagename=${IMAGE_NAME} --cluster=${cluster} --ns=${ns}" } } } //部署测试环境阶段 stage('test-enviroment-deploy') { when { //在测试流水线进行部署 expression { ENV =~ 'CD_test.*' } //只有分支为develop*,release*,hotfix*时才会运行 anyOf { branch 'dev*'; branch 'develop*'; branch 'release*'; branch 'hotfix*' } } steps { script { //部署测试环境 echo "部署测试" def ns = "${REGISTRY_PROJECT}-test" def cluster = "kubernetes-dev" sh "python3 rancher.py --modulename=${params.moudle_name} --imagename=${IMAGE_NAME} --projectname=${PROJECT_NAME}" } } } //部署预生产环境阶段 stage('pre-enviroment-deploy') { when { //在预生产流水线进行部署 expression { ENV =~ 'CD_pre.*' } //只有分支为develop*,release*,hotfix*时才会运行 anyOf { branch 'dev*'; branch 'develop*'; branch 'release*'; branch 'hotfix*' } } steps { script { //部署预生产pre环境 echo "部署预生产pre环境" def ns = "${REGISTRY_PROJECT}-pre" def cluster = "kubernetes-dev" sh "python3 rancher.py --modulename=${params.moudle_name} --imagename=${IMAGE_NAME} --projectname=${PROJECT_NAME}" } } } //部署用户验证环境阶段 stage('uat-enviroment-deploy') { when { //在用户验证流水线进行部署 expression { params.deploy_uat_flag } //只有分支为develop*,release*,hotfix*时才会运行 anyOf { branch 'dev*'; branch 'develop*'; branch 'release*'; branch 'hotfix*' } } steps { script { //部署用户验证uat环境 echo "部署用户验证uat环境" def ns = "${REGISTRY_PROJECT}-uat" def cluster = "kubernetes-dev" sh "python3 rancher.py --modulename=${params.moudle_name} --imagename=${IMAGE_NAME} --projectname=${PROJECT_NAME}" } } } //部署生产环境阶段 stage('prod-enviroment-deploy') { when { //在生产流水线进行部署 expression { ENV =~ 'CD_prod.*' } //只有分支为release*,hotfix*时才会运行 anyOf { branch 'master*' branch 'release*'; branch 'hotfix*' } } steps { script { //部署生产环境 echo "部署到生产环境" def ns = "${REGISTRY_PROJECT}-prod" def cluster = "kubernetes-prod" sh "python3 rancher.py --modulename=${params.moudle_name} --imagename=${IMAGE_NAME} --projectname=${PROJECT_NAME}" } } } //人工测试 /*stage('Manual testing') { when { not { expression { env.JOB_NAME.contains('CI') } } } steps { script { env.COMPLETE_INTEGRATION_TEST = input( message: '请确认是否已完成人工测试?!', ok: '确定', parameters: [ booleanParam(name: 'complete_integration_test', defaultValue: false, description: '确认是否已完成人工测试?确认请勾选') ] ) switch(env.COMPLETE_TEST_TEST){ case 'true': sh'echo "确定已完成人工测试"' break; case 'false': sh """ echo "未完成人工测试,暂时退出部署" exit 1 """ break } } } }*/ //确认是否已完成分支合并 stage('Confirm has been completed branch merge&&tag') { when { expression { env.JOB_NAME.contains('CD_prod') } //只有分支为release*,hotfix*时才会运行 anyOf { branch 'dev*' branch 'release*'; branch 'hotfix*' } } steps { //确认是否完成合并分支 echo "确认是否完成合并分支" /* script { env.MERGE_FLAG = input ( message: '请确认是否已完成合并分支?!', ok: '确定', parameters: [ booleanParam(name: 'merge_flag', defaultValue: false, description: '请去gitlab代码仓库进行分支合并,确认已完成合并分支后请勾选') ] ) switch(env.MERGE_FLAG) { case 'true': sh'echo "确定已经完成合并分支"' break; case 'false': sh """ echo "没有完成合并分支,退出部署" exit 1 """ break } }*/ } post { success { script { //给master分支打tag if (!params.git_tag) { echo "给master分支打tag:${dynamic_tag}" withCredentials([string(credentialsId: "${GITLAB_API_TOKEN}", variable: 'gitlabtoken')]) { sh "python/gitlab_tag.py --project_id=${GITLAB_PROJECT_ID} --tag_name=${dynamic_tag} --token=${gitlabtoken}" } } else { echo "给master分支打tag:${params.git_tag}" withCredentials([string(credentialsId: "${GITLAB_API_TOKEN}", variable: 'gitlabtoken')]) { sh "python/gitlab_tag.py --project_id=${GITLAB_PROJECT_ID} --tag_name=${params.git_tag} --token=${gitlabtoken}" } } } } } } } //post的步骤将会在pipeline流水线末尾操作 post { //发送邮件,接收人为def定义的SENDOBJECT与PROVIDERS always { //解除分支保护 script { //当是生产流水线且不是master分支时进行解除分支保护 if (env.JOB_NAME.contains('CD_prod') && BRANCH_NAME != 'master') { echo "解除${BRANCH_NAME}分支保护" withCredentials([string(credentialsId: "${GITLAB_API_TOKEN}", variable: 'gitlabtoken')]) { sh "python3 python/freezing.py --branch=${BRANCH_NAME} --gitidnum=${GITLAB_PROJECT_ID} --jobname=${JOB_NAME} --token=${gitlabtoken} --option='un_freeze'" } } } //发送邮件 script{ //providers定义了提交gitlab的人,RequesterRecipientProvider 在jenkins页面上手动构建的人 def PROVIDERS = [[$class: 'CulpritsRecipientProvider'], [$class:'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']] //定义邮件信息 emailext( subject: '$DEFAULT_SUBJECT', body: '$DEFAULT_CONTENT', to : "${SENDOBJECT}", recipientProviders: PROVIDERS ) } } } }
浙公网安备 33010602011771号