📄Jenkins slave pod的pipeline实现
2023-4-19
| 2023-4-25
0  |  0 分钟
type
status
date
slug
summary
tags
category
icon
password

kube-jenkins示意图

 
notion image

1. scripted pipline 常规

描述: Scripted Pipeline 是基于 groovy 的一种 DSL 语言相比于 Declarative pipeline,它为jenkins用户提供了更巨大的灵活性和可扩展性。
如无特殊说明,后续pipeline全部采用scripted pipline。

1.1 Scripted Pipeline 基础结构说明:

  • Node:节点,一个 Node 就是一个 Jenkins 节点,Master 或者 Agent,是执行 Step 的具体运行环境,比如我们之前动态运行的 Jenkins Slave 就是一个 Node 节点
  • Stage:阶段,一个 Pipeline 可以划分为若干个 Stage,每个 Stage 代表一组操作,比如:Build、Test、Deploy,Stage 是一个逻辑分组的概念,可以跨多个 Node

1.2 Scripted Pipeline 语法示例:

// Jenkinsfile (Scripted Pipeline) // #结构1 node { // @变量定义 def mvnHome // #结构2 stage('Preparation') { // # 结构3 - 它就是 Step 基本操作单元 echo "Scripted Pipeline" } }
💡
Tips : 注释(Comments)和Java一样,支持单行(使用//)、多行(/* */)和文档注释(使用/** */)

1.3 Jenkins slave pod pipeline简单样本

node('haimaxy-jnlp') { stage('Clone') { echo "1.Clone Stage" /* stage("Git 远程仓库拉取"){ //使用鉴权credentialsId git url: "https://github.com/my-dlq/test.git", credentialsId: "git-global-credentials", branch: "master" } */ //branch默认是master,完整写法git branch: 'main', url:'https://github.com/naiveskill/devops.git' git url: "https://github.com/cnych/jenkins-demo.git" // script调用groovy脚本 script { build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim() } } stage('Test') { echo "2.Test Stage" } stage('Build') { echo "3.Build Docker Image Stage" sh "docker build -t cnych/jenkins-demo:${build_tag} ." } stage('Push') { echo "4.Push Docker Image Stage" //使用鉴权credentialsId withCredentials([usernamePassword(credentialsId: 'dockerHub', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) { sh "docker login -u ${dockerHubUser} -p ${dockerHubPassword}" sh "docker push cnych/jenkins-demo:${build_tag}" } } stage('Deploy') { echo "5. Deploy Stage" //input调出交互式 def userInput = input( id: 'userInput', message: 'Choose a deploy environment', parameters: [ [ $class: 'ChoiceParameterDefinition', choices: "Dev\nQA\nProd", name: 'Env' ] ] ) echo "This is a deploy step to ${userInput}" sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml" //env.BRANCH_NAME是Jenkins自带env sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s.yaml" if (userInput == "Dev") { // deploy dev stuff } else if (userInput == "QA"){ // deploy qa stuff } else { // deploy prod stuff } sh "kubectl apply -f k8s.yaml" } }
样本解析的补充:

1.3.1 credentialsId配置

在首页点击 Credentials -> Stores scoped to Jenkins 下面的 Jenkins -> Global credentials (unrestricted) -> 左侧的 Add Credentials:添加一个 Username with password 类型的认证信息,如下:
notion image

1.3.2 deploy $env stuff 的实现

  • 可以把多个集群的context集中起来,一般默认放在$Home/.kube ,需要时use context 。由于slave pod可能存在于每个节点,故每台节点的$Home/.kube 要一致。
$Home/.kube 是配置 pod template时挂载的。
notion image
  • 读取Secret text,在pod创建~/.kube ,解密Credentials生成context。这样只需要在凭据菜单配置多个集群的Credentials,不需要每个节点都配置~/.kubemore
💡
注意:凭据设置界面,类型需要选择为“Secret text”
样例1:声明式pipeline,K8S_CONFIG = credentials('jenkins-k8s-config')
K8S_CONFIG = credentials('jenkins-k8s-config') sh "mkdir -p ~/.kube" sh "echo ${K8S_CONFIG} | base64 -d > ~/.kube/config"
样例2:scripted pipeline,withCredentials string more
node { withCredentials([string(credentialsId: 'prod-k8s-config', variable: 'TOKEN')]) { sh ''' set +x echo "Token: $TOKEN" ''' } }
  • 参考此文第四部分secret设置

1.3.3 maven打包-插件

可以看到stage部分不涉及maven build,如果需要使用maven,可以先安装插件,在加入stage。more
既然是插件就涉及到安装目录,因为是pod形式,slave pod能否读取到master pod插件的安装目录?未验证。可以考虑映射到NFS,或者每台节点都有对应的插件目录,pod template用hostpath挂载。
node{ stage('get clone'){ //check CODE echo 'Checkout==========》》》' checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[url: 'https://source.enncloud.cn/qinzhao/spring-boot-demo.git']]]) } //定义mvn环境 //插件里定义的maven变量名称为maven3.5.3,接下来在Jenkinsfile中使用tool获取变量值 def mvnHome = tool 'maven3.5.3' env.PATH = "${mvnHome}/bin:${env.PATH}" stage('mvn test'){ //mvn 测试 sh "mvn test" } stage('mvn build'){ //mvn构建 sh "mvn clean install -Dmaven.test.skip=true" } stage('deploy'){ //执行部署脚本 echo "deploy ......" } }
The def keyword in Groovy is the quickest way to define a new variable (with no specific type). In the sample syntax discussed above, a variable is defined by the following expression:
def mvnHome = tool 'M3'
This ensures that M3 is installed somewhere accessible to Jenkins and assigns the return value of the step (an installation path) to the mvnHome variable.
notion image

1.3.4 maven打包-自建jnlp镜像

Jenkins是master/agent的架构。而master与agent之间通信方法有两种:
  1. 通过JNLP协议:需要启动JNLP客户端主动连接master。这是Kubernetes插件使用的方式。
  1. 通过SSH协议:master使用SSH主动连接agent机器。
Kubernetes插件的具体的做法就是连接到Kubernetes集群,然后启动一个Pod。Pod中包含一个JNLP客户端,容器名约定为:jnlp。jnlp 会主动连接Jenkins master。
所以,当你发现Jenkins任务的日志中,一直在等待jnlp连接时,我们可以这样查问题:
  1. 查看相应的Pod是否存活。
  1. jnlp 容器连接不上master:大概率是配置不对。
💡
pod template的containerTemplate的image尽量写详细,即$registy/$image-name:$version,如果简写,插件会拉取官方仓库的镜像,非常慢或者拉取失败。
关于image简写的另一个问题:
我的容器运行时是contained,我简写手动拉取docker镜像时总是失败,比如命令是crictl --debug pull kubectl ,则一直失败,报错如下。但是换完整的image则正常,如crictl --debug pull docker.io/bitnami/kubectl:latest
crictl --debug pull kubectl DEBU[0000] get image connection DEBU[0000] PullImageRequest: &PullImageRequest{Image:&ImageSpec{Image:kubectl,Annotations:map[string]string{},},Auth:nil,SandboxConfig:nil,} E0422 13:17:37.290471 83274 remote_image.go:171] "PullImage from image service failed" err="rpc error: code = Unknown desc = failed to pull and unpack image \"docker.io/library/kubectl:latest\": failed to resolve reference \"docker.io/library/kubectl:latest\": pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed" image="kubectl" FATA[0002] pulling image: rpc error: code = Unknown desc = failed to pull and unpack image "docker.io/library/kubectl:latest": failed to resolve reference "docker.io/library/kubectl:latest": pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failedm'lm'lm'l
crictl --debug pull kubectl 拉取失败
crictl --debug pull docker.io/bitnami/kubectl:latest DEBU[0000] get image connection DEBU[0000] PullImageRequest: &PullImageRequest{Image:&ImageSpec{Image:docker.io/bitnami/kubectl:latest,Annotations:map[string]string{},},Auth:nil,SandboxConfig:nil,} DEBU[0017] PullImageResponse: &PullImageResponse{ImageRef:sha256:1fb61441fdb4b8e3d637219d960edea85f52319a0d76a2346da0dfacc8f1ed8f,} Image is up to date for sha256:1fb61441fdb4b8e3d637219d960edea85f52319a0d76a2346da0dfacc8f1ed8f
crictl --debug pull docker.io/bitnami/kubectl:latest 成功拉取镜像
为什么即使我们不显示配置jnlp的容器,slave pod也可以run呢?这是因为Jenkins kubernates插件在真正创建pod前,为我们加入了默认的jnlp的container。
默认的jnlp拉取镜像时可能存在网络问题,而且功能单一,故更多人会选择定制镜像,然后显示指定。自定义镜像因为把maven等工具写死了,故欠缺灵活性。显示指定有两种方式:
  • 插件配置
notion image
  • pipeline配置
def label = "jenkins-slave" podTemplate(label: label, containers: [ containerTemplate(name: 'kubectl', image: 'boer0924/jenkins-slave:1.18.3', command: 'cat', ttyEnabled: true)], serviceAccount: 'jenkins', volumes: [ hostPathVolume(mountPath: '/root/.m2', hostPath: '/var/lib/cache/.m2'), hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'), hostPathVolume(mountPath: '/var/lib/docker', hostPath: '/var/lib/docker') ]) { node(label) { //depoly } }

1.3.5 maven打包-一个pod内多个container

pod内可以存在多个container,故可以考虑每个工具一个container,合并成slave pod。这样做灵活性增强,研发可以根据需求指定不同的工具。多个container是否存在目录共享的问题?即假如maven容器创建了app.jar,那docker容器的docker命令是否可以感知到这个jar?很多博文未提及这点,或许因为现在我还不知道某些细节。当然,sidecar模式共享一个emptyDIR肯定可以解决文件共享的问题。我杞人忧天了,官方已经解决了这个问题😢。
多个container时,即使不显示指定inbound-agent镜像,执行任务时也会自动下载。故建议先提前下载到本地仓库,避免网络问题。
多container共享目录的疑问(已解决,见下一条toggle list)
此文或许可以解答我的问题:
dockerfile的ADD target/*.jar app.jar 使用了相对路径,读取pom时pom = readMavenPom file: './pom.xml’ 也使用了相对路径,而在pipeline中上一步是maven容器在$jenkinshome 输出了前面两个文件,下一步docker容器直接使用相对路径引用jar和pom.xml,说明多个容器都在$jenkinshome 执行任务。
日志样本:
[INFO] --- spring-boot-maven-plugin:2.1.4.RELEASE:repackage (repackage) @ springboot-helloword --- [INFO] Replacing main artifact with repackaged archive [INFO] [INFO] --- maven-install-plugin:2.5.2:install (default-install) @ springboot-helloword --- [INFO] Installing /home/jenkins/workspace/k8s-pipeline/target/springboot-helloword-0.0.1.jar to /root/.m2/repository/club/mydlq/springboot-helloword/0.0.1/springboot-helloword-0.0.1.jar [INFO] Installing /home/jenkins/workspace/k8s-pipeline/pom.xml to /root/.m2/repository/club/mydlq/springboot-helloword/0.0.1/springboot-helloword-0.0.1.pom [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS
pipeline样本:
stage('Maven阶段'){ echo "Maven 阶段" container('maven') { //这里引用上面设置的全局的 settings.xml 文件,根据其ID将其引入并创建该文件 configFileProvider([configFile(fileId: "75884c5a-4ec2-4dc0-8d87-58b6b1636f8a", targetLocation: "settings.xml")]){ sh "mvn clean install -Dmaven.test.skip=true --settings settings.xml" } } } stage('Docker阶段'){ echo "Docker 阶段" container('docker') { // 读取pom参数 echo "读取 pom.xml 参数" //'./pom.xml'是相对目录,maven容器输出的是/home/jenkins/workspace/k8s-pipeline/pom.xml pom = readMavenPom file: './pom.xml' } }
多container共享目录的解答(验证)
kubernetes 插件就是用sidecar模式共享一个emptyDIR解决的此问题,即所有pod的$workdir都挂载共同的emptyDIR。验证很简单:1.构造一个多容器pipeline,2.观察日志里生成的slave deployment spec。
  • 多容器pipeline
def label = "jenkins-slave-${UUID.randomUUID().toString()}" podTemplate(label: label, cloud: 'kubernetes', containers: [ containerTemplate(name: 'maven', image: 'docker.io/library/maven:latest', ttyEnabled: true, command: 'cat'), containerTemplate(name: 'kubectl', image: 'docker.io/bitnami/kubectl:latest', command: 'cat', ttyEnabled: true)], volumes: [ persistentVolumeClaim(mountPath: '/root/.m2', claimName: 'jenkins-m2'), persistentVolumeClaim(mountPath: '/home/jenkins/agent/workspace', claimName: 'jenkins-agent'), secretVolume(secretName: 'jenkins-k8s-cfg', mountPath: '/home/jenkins/agent/.kube/config')] ){ node(label){ stage('build'){ git branch: 'master', url: 'https://github.com/my-dlq/springboot-helloworld.git' container('maven') { stage('Build a Maven project') { sh 'mvn clean package -Dfile.encoding=UTF-8 -DskipTests=true' } } } stage('kubectl'){ container('kubectl') { sh 'kubectl get pod -A' } } } }
  • 日志输出含spec的部分
spec: containers: - command: - "cat" image: "docker.io/library/maven:latest" imagePullPolicy: "IfNotPresent" name: "maven" resources: {} tty: true volumeMounts: - mountPath: "/home/jenkins/agent/.kube/config" name: "volume-2" readOnly: false - mountPath: "/root/.m2" name: "volume-0" readOnly: false - mountPath: "/home/jenkins/agent/workspace" name: "volume-1" readOnly: false #挂载了emptyDIR "workspace-volume" - mountPath: "/home/jenkins/agent" name: "workspace-volume" readOnly: false - command: - "cat" image: "docker.io/bitnami/kubectl:latest" imagePullPolicy: "IfNotPresent" name: "kubectl" resources: {} tty: true volumeMounts: - mountPath: "/home/jenkins/agent/.kube/config" name: "volume-2" readOnly: false - mountPath: "/root/.m2" name: "volume-0" readOnly: false - mountPath: "/home/jenkins/agent/workspace" name: "volume-1" readOnly: false #挂载了emptyDIR "workspace-volume" - mountPath: "/home/jenkins/agent" name: "workspace-volume" readOnly: false - env: - name: "JENKINS_SECRET" value: "********" - name: "JENKINS_TUNNEL" value: "10.110.43.229:50000" - name: "JENKINS_AGENT_NAME" value: "jenkins-slave-83079b52-d127-4e36-9cc7-649b56b60209-fvjqh-1fnh3" - name: "JENKINS_NAME" value: "jenkins-slave-83079b52-d127-4e36-9cc7-649b56b60209-fvjqh-1fnh3" - name: "JENKINS_AGENT_WORKDIR" value: "/home/jenkins/agent" - name: "JENKINS_URL" value: "http://10.110.43.229:8080/" image: "jenkins/inbound-agent:3107.v665000b_51092-5" name: "jnlp" resources: requests: memory: "256Mi" cpu: "100m" volumeMounts: - mountPath: "/home/jenkins/agent/.kube/config" name: "volume-2" readOnly: false - mountPath: "/root/.m2" name: "volume-0" readOnly: false - mountPath: "/home/jenkins/agent/workspace" name: "volume-1" readOnly: false #挂载了emptyDIR "workspace-volume" - mountPath: "/home/jenkins/agent" name: "workspace-volume" readOnly: false nodeSelector: kubernetes.io/os: "linux" restartPolicy: "Never" volumes: - name: "volume-0" persistentVolumeClaim: claimName: "jenkins-m2" readOnly: false - name: "volume-2" secret: secretName: "jenkins-k8s-cfg" - name: "volume-1" persistentVolumeClaim: claimName: "jenkins-agent" readOnly: false - emptyDir: medium: "" name: "workspace-volume"
podTemplate(containers: [ containerTemplate(name: 'maven', image: 'maven:3.8.1-jdk-8', command: 'sleep', args: '99d'), containerTemplate(name: 'golang', image: 'golang:1.16.5', command: 'sleep', args: '99d') ]) { node(POD_LABEL) { stage('Get a Maven project') { git 'https://github.com/jenkinsci/kubernetes-plugin.git' container('maven') { stage('Build a Maven project') { sh 'mvn -B -ntp clean install' } } } stage('Get a Golang project') { git url: 'https://github.com/hashicorp/terraform.git', branch: 'main' container('golang') { stage('Build a Go project') { sh ''' mkdir -p /go/src/github.com/hashicorp ln -s `pwd` /go/src/github.com/hashicorp/terraform cd /go/src/github.com/hashicorp/terraform && make ''' } } } } }

1.4 Jenkins slave pod pipeline复杂样本

参数解释:
  1. 定义agent label是为在k8s中调度job的pod名字
  1. 定义parameters来选择需要部署的环境。
  1. Jenkinsfile的两个全局变量:env/params。 设置env变量: env.KEY = value ,使用env变量: ${KEY}
  1. username&password凭证的使用: registryCre = credentials('registry') [_USR/_PSW],获取username: ${registryCre_USR} ,获取passowrd: ${registryCre_PSW}
  1. 使用short commit_id作为image_tag 和 kubernetes.io/change-cause, 以保证镜像唯一,和可以回退到指定版本。
  1. sed动态修改k8s资源定义文件manifests/k8s.yaml:
  • : 便于指定版本回退
  • : 指定版本
  • : 不同环境不同域名
def label = "jenkins-slave" properties([ parameters([ choice(name: 'ENV', choices: ['test', 'pre', 'prod'], description: '选择部署环境?') ]) ]) podTemplate(label: label, containers: [ containerTemplate(name: 'maven', image: 'maven:3.6.3-jdk-8', command: 'cat', ttyEnabled: true), containerTemplate(name: 'docker', image: 'docker:19.03.8', command: 'cat', ttyEnabled: true), containerTemplate(name: 'kubectl', image: 'boer0924/kubectl:1.18.3', command: 'cat', ttyEnabled: true)], serviceAccount: 'jenkins', volumes: [ hostPathVolume(mountPath: '/root/.m2', hostPath: '/var/lib/cache/.m2'), hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'), hostPathVolume(mountPath: '/var/lib/docker', hostPath: '/var/lib/docker') ]) { node(label) { if ("${params.ENV}" == 'test') { env.NAMESPACE = 'devops' env.INGRESS = 'test' } if ("${params.ENV}" == 'pre') { env.NAMESPACE = 'pre' env.INGRESS = 'pre' } if ("${params.ENV}" == 'prod') { env.NAMESPACE = 'prod' env.INGRESS = 'prod' } def myRepo = checkout scm def gitCommit = myRepo.GIT_COMMIT def gitBranch = myRepo.GIT_BRANCH def imageTag = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim() def changeCause = sh(script: "git log --oneline -1 HEAD", returnStdout: true).trim() def dockerRegistryUrl = "registry.boer.xyz" def imageEndpoint = "public/spring-produce" def image = "${dockerRegistryUrl}/${imageEndpoint}" stage('单元测试') { echo "1.测试阶段" } stage('代码编译打包') { try { container('maven') { echo "2. 代码编译打包阶段" sh "mvn clean package -Dmaven.test.skip=true" } } catch (exc) { println "构建失败 - ${currentBuild.fullDisplayName}" throw(exc) } } stage('构建 Docker 镜像') { withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'dockerhub', usernameVariable: 'DOCKER_HUB_USER', passwordVariable: 'DOCKER_HUB_PASSWORD']]) { container('docker') { echo "3. 构建 Docker 镜像阶段" sh """ docker login ${dockerRegistryUrl} -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD} docker build -t ${image}:${imageTag} . docker push ${image}:${imageTag} """ } } } stage('运行 Kubectl') { withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) { container('kubectl') { sh "mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config" echo "查看当前目录" sh """ sed -i "s|<CHANGE_CAUSE>|${changeCause}|g" manifests/k8s.yaml sed -i "s|<IMAGE>|${image}|g" manifests/k8s.yaml sed -i "s|<IMAGE_TAG>|${imageTag}|g" manifests/k8s.yaml sed -i "s|<INGRESS>|${INGRESS}|g" manifests/k8s.yaml kubectl apply -f manifests/k8s.yaml --namespace ${NAMESPACE} """ } } } stage('快速回滚?') { withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) { container('kubectl') { sh "mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config" def userInput = input( id: 'userInput', message: '是否需要快速回滚?', parameters: [ [ $class: 'ChoiceParameterDefinition', choices: "N\nY", name: '回滚?' ] ] ) if (userInput == "Y") { sh "kubectl rollout undo deployment produce-deployment -n ${NAMESPACE}" } } } } } }
版本回退
  • 1.根据${COMMIT_ID}找出REVISION (自动)
  • 2.根据commit_msg找出REVISION (人工)
$ kubectl -n ${NAMESPACE} rollout history deployment consume-deployment deployment.apps/consume-deployment REVISION CHANGE-CAUSE 1 a03d0eb optimize change-cause msg 2 73f5a5c resource quota 3 2772a88 resource quota up 4 254b592 plus probe time 5 87989d8 更新配置文件 # 1.根据${COMMIT_ID}找出REVISION(自动) 2.根据commit_msg找出REVISION(人工) $ kubectl -n ${NAMESPACE} rollout undo deployment consume-deployment --to-revision=$(kubectl -n ${NAMESPACE} rollout history deployment consume-deployment | grep ${COMMIT_ID} | awk '{print $1}') $ kubectl -n ${NAMESPACE} rollout status deployment consume-deployment

2. scripted pipline 参数

2.1 node模块内使用parameters参数

💡
引用parameters模块的参数时,params. 可以省略
node(){ properties([ parameters([ string(defaultValue: 'world', name: 'Name',description: "this is sth") ]) ]) sh "hello ${Name}" //sh "hello ${params.Name}" }
  • 拓展:使用自定义env
node{ withEnv(['FName=Naive','LName=skill']) { stage('Build') { sh 'echo $FName $LName' } } }

2.2 node模块外使用parameters参数

properties( [ parameters([ string(defaultValue: '/data', name: 'Directory'), string(defaultValue: 'Dev', name: 'DEPLOY_ENV') ]) ] ) node { // params.DEPLOY_ENV ... }

2.3 parameters模块更多参考用法

properties( parameters ([ string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?'), text(name: 'BIOGRAPHY', defaultValue: '', description: 'Enter some information about the person'), booleanParam(name: 'TOGGLE', defaultValue: true, description: 'Toggle this value'), choice(name: 'CHOICE', choices: ['One', 'Two', 'Three'], description: 'Pick something'), password(name: 'PASSWORD', defaultValue: 'SECRET', description: 'Enter a password') ]) ])

2.4 直接使用def

node(){ def Name = 'world' sh "hello ${Name}" }

3. scripted pipline 引号

  • 支持单引号、双引号。双引号支持插值(变量),单引号不支持。
  • 支持三引号。三引号分为三单引号和三双引号。它们都支持换行,区别在于只有三双引号支持插值(变量)。
应用场景举例:需要用sh执行多条shell命令,第一步需要设置alias,后面引用这个alias。多行挤在一行可读性查,可以用三单引号。
💡
As you know each sh command runs in it's own shell. It has same location as agent/workspace, but since it's a new shell, environment variables, aliases, etc, can be lost. So combine the lines into a single sh
stage('kubectl'){ container('kubectl') { sh ''' alias k="kubectl --kubeconfig=/home/jenkins/agent/.kube/config" k version k get pod -n demo ''' }

4. scripted pipline 使用EOF

sh模块使用多行可以用三单引号或者三双引号,如果sh的内容更复杂、嵌套多层,可以考虑配合EOF使用。不过groovy使用EOF有个不一样的点,通常<<\-EOF (反斜杠是避免notion解析成箭头,实际没有),就可以让结尾的EOF忽略tab和空格,但是这里不行,需要配合.stripIndent()
💡
使用参数时带上花括号,即${agrs}
stage('ssh2remote'){ sshagent(credentials: ['mysshkey']) { //第一步scp dist包到目的服务器,第二步,ssh登录到remote server,执行删除、解压等命令 //sh的三双引号是为了解析里面的变量 //.stripIndent() 是为了让第二个(尾部)EOF不用定格写,第一个EOF前的<<- 可以换成<< ,没区别 sh """ scp -o StrictHostKeyChecking=no ${tar}.tar.gz ${user}@${server1}:/tmp/ ssh -o StrictHostKeyChecking=no ${user}@${server1} <<- EOF rm -f /tmp/{a.txt,b.txt} tar -zxvf /tmp/${tar}.tar.gz -C /tmp/ ls -lht /tmp EOF """.stripIndent() } }
技术
  • devops
  • k8s
  • Jenkins pod的存储和网络centos7 pam密码策略
    目录