Jenkins Syntax (Scripted vs Declarative)

Doveloper·2022년 7월 22일
1
post-thumbnail

super app server - jenkinsfile

https://www.jenkins.io/doc/book/pipeline/syntax/

기존의 scripted(directive) 한 방식에서 조금 더 정통 Jenkins 문법에 가까운 Declarative 방식으로 refactoring.

현재 짜여져 있는 코드는 Scripted 방식

장점

  • 더 많은 절차적인 코드를 작성 가능
  • program 작성과 유사한 방식
  • 기존 pipline 문법이라 친숙하고 이전 version과 호환 가능
  • 필요한 경우 customizing이 가능, 유연성 좋음
  • 보다 복잡한 workflow 및 pipline modeling 가능

단점

  • 일반적으로 더 많은 programming이 필요
  • Groovy 언어 및 환경으로 제한된 구문 검사
  • 전통적인 Jenkins 모델과는 맞지 않음
  • 같은 작업이라면 Declarative 문법보다 잠재적으로 더 복잡

Jenkinsfile - scripted 방식

node {
    def version = "${params.majorVersion}.${params.minorVersion}.${params.hotfixVersion}"
    def branch = "release-${version}"
    def dockerImgRemote = "${params.dockerImageRepo}"
    def dockerUser = "${params.dockerUser}"
    def dockerPassword = "${params.dockerPassword}"
    def publishUrl = "http://${params.publishHost}:${params.publishPort}${params.publishDir}"
    def repoUser = "${params.repoUser}"
    def repoPassword = "${params.repoPassword}"


    switch(distributionType){
        case 'integrated':
            BuildJar(version, branch)
            UploadJar(version, publishUrl, repoUser, repoPassword)
            BuildImg(version)
            UploadImg(version, dockerImgRemote, dockerUser, dockerPassword)
            break
        case 'build-jar':
            BuildJar(version, branch)
            break
        case 'build-and-upload-jar':
            BuildJar(version, branch)
            UploadJar(version, publishUrl, repoUser, repoPassword)
            break
        case 'build-img':
            BuildJar(version, branch)
            BuildImg(version)
            break
        case 'build-and-upload-img':
            BuildJar(version, branch)
            BuildImg(version)
            BuildImg(version)
            UploadImg(version, dockerImgRemote, dockerUser, dockerPassword)
            break
        default:
            break
    }
}

void BuildJar(version, branch){
    stage('Build Jar') {
        echo 'Git fetching...'
        sh 'git fetch --all'
//        sh 'git reset --hard origin/master'
        sh "git reset --hard origin/${branch}"
//        sh "git pull origin ${branch}"

        echo 'Getting Commit ID...'
        commitId = sh(returnStdout: true, script: "git log | head -1 | cut -b 7-15")
        commitId = commitId.substring(1)

        echo 'Building...'
        sh 'chmod +x ./gradlew'
        sh "./gradlew clean build jenkins -PbuildVersion=${version} -PcommitId=${commitId}"
    }
}

void UploadJar(version, publishUrl, repoUser, repoPassword) {
    stage('Upload Jar') {
        sh "./gradlew publish -PbuildVersion=${version} -PpublishUrl=${publishUrl} -PrepoUser=${repoUser} -PrepoPassword=${repoPassword}"
    }
}

void BuildImg(version) {
    stage('Build Docker img') {
        echo 'Building img...'
        sh "sudo docker build --tag super-app-server:${version} --build-arg version=${version} ."
    }
}

void UploadImg(version, dockerImgRemote, dockerUser, dockerPassword) {
    stage('Upload Docker img') {
        //////////////////////////////////////// testing docker img (img run)
        sh "sudo docker ps -a"
//        sh 'sudo docker stop $(sudo docker ps -a -q)'
//        sh 'sudo docker rm $(sudo docker ps -a -q)'
        sh "sudo docker run --name super-app-server-test -d -p 8888:8888 super-app-server:${version}"
        sh "sudo docker ps | grep super-app-server"
        sh 'sudo docker stop $(sudo docker ps -a -q)'
        sh 'sudo docker rm $(sudo docker ps -a -q)'
        ////////////////////////////////////////

        echo 'Uploading img...'
        sh "sudo docker login -u ${dockerUser} -p ${dockerPassword}"
        sh "sudo docker tag super-app-server:${version} ${dockerImgRemote}:${version}"
        sh "sudo docker tag super-app-server:${version} ${dockerImgRemote}:latest"
        sh "sudo docker push dohyunkim12/super-app-server:${version}"
        sh "sudo docker push dohyunkim12/super-app-server:latest"
    }
}

After refactor to Declarative way


Declarative pipeline syntax 특징

red box - 필수요소
black box - 선택요소

  1. 항상 pipeline block 안에 존재해야 함
pipeline {
	/* insert Declarative Pipeline here */
}

Section - 각 section은 하나 또는 그 이상의 Directives나 Step들로 구성됨.

agent - agent는 pipeline 전체 또는 특정 stage가 어느 jenkins env에서 실행될 것인지 정함.

Top Level Agent

node("myAgent") {
    timeout(unit: 'SECONDS', time: 5) {
        stage("One"){
            sleep 10
            echo 'hello'
        }
    }
}

agent가 이렇게 상위 레벨에서 선언되면, timeout은 agent에 들어온 이후에 호출됨. (agent에 들어와야지만 실행)

Stage Agent

timeout(unit: 'SECONDS', time: 5) {
    stage("One"){
        node {
            sleep 10
            echo 'Hello'
        }
    }
}

반대로 이렇게 timeout이 먼저 호출되고, stage 안에서 agent(node)가 선언되면 agent에 들어오기 이전에 timeout 호출 (agent할당 이전에 실행된다. )
위의 예제는 timeout이 agent provisioning 시간을 포함하고 있으므로 agent할당이 지연되고 pipeline은 fail될 것.

agent - parameter
agent는 몇가지 parameter 타입을 받을 수 있음. (top-level, stage 둘 다 사용 가능)

  • any : 어느 agent이던 사용가능한 놈을 사용하겠다.
  • none : top-level 에서 agent를 none으로 지정한다면, pipeline의 global agent는 할당되지 않는다. (각 stage마다 일일이 agent를 할당해줘야 함.)
  • label : agent에 label을 붙여서 해당 label이 달려있는 agent에서 실행시킨다. (조건 할당도 가능. ex - agent {label 'lb1 && lb2' } 또는 agent {label 'lb2 || lb2' }
  • node : agent {node {label 'labelName' } }agent {label 'labelName' }과 동일하게 작동한다. 그러나 node는 customWorkspace 같은 추가적인 option을 제공.
  • docker : docker-based pipeline에서, docker를 정의하면 label 옵션을 기반으로 컨테이너가 동적 할당되고 해당 컨테이너 위에서 동작하게 됨.
    docker의 args field는 docker run 할 때 함께 줄 옵션 파라미터로 전달할 수 있음.
    docker agent example
agent {
    docker {
        image 'myregistry.com/node'
        label 'my-defined-label'
        registryUrl 'https://myregistry.com/'
        registryCredentialsId 'myPredefinedCredentialsInJenkins'
        args  '-v /tmp:/tmp'
    }
}
  • dockerfile : project src repo에 존재하는 Dockerfile을 기반으로 도커 이미지 생성해서 그 위에서 돌림. (개쩌네!)
agent {
    // Equivalent to "docker build -f Dockerfile.build --build-arg version=1.0.2 ./build/
    dockerfile {
        filename 'Dockerfile.build'
        dir 'build'
        label 'my-defined-label'
        additionalBuildArgs  '--build-arg version=1.0.2'
        args '-v /tmp:/tmp'
    }
}

이런식으로 사용하는데, 실 사용시 자세한 소스는 더 찾아봐야 겠다. filename은 Dockerfile 이름이 Dockerfile이 아닐 경우 명시, dir 는 path지정. 위 예에서는 도커파일이 /build/Dockerfile.build에 존재하는 경우.
dockerfile agent도 역시 registryUrlregistryCredentialsId 를 사용할 수 있다.

  • kubernetes : pipeline을 돌릴 때, k8s cluster의 해당 파드 위에서 돌아가게 끔 할 수 있다.
agent {
    kubernetes {
        defaultContainer 'kaniko'
        yaml '''
kind: Pod
spec:
  containers:
  - name: kaniko
    image: gcr.io/kaniko-project/executor:debug
    imagePullPolicy: Always
    command:
    - sleep
    args:
    - 99d
    volumeMounts:
      - name: aws-secret
        mountPath: /root/.aws/
      - name: docker-registry-config
        mountPath: /kaniko/.docker
  volumes:
    - name: aws-secret
      secret:
        secretName: aws-secret
    - name: docker-registry-config
      configMap:
        name: docker-registry-config
'''
   }

examples
Stage-level agent section

pipeline {
    agent none 
    stages {
        stage('Example Build') {
            agent { docker 'maven:3.8.1-adoptopenjdk-11' } 
            steps {
                echo 'Hello, Maven'
                sh 'mvn --version'
            }
        }
        stage('Example Test') {
            agent { docker 'openjdk:8-jre' } 
            steps {
                echo 'Hello, JDK'
                sh 'java -version'
            }
        }
    }
}

top-level agent를 none으로 설정하여 executor가 초기에 할당되지 않음. (각 stage section에서 agent를 지정해 주어야 함)

stage Example Build 에서 해당 이미지 (maven~~)으로 새롭게 생성된 컨테이너를 agent로 사용.
stage Example Test에서 해당 이미지(openjdk:8-jre)로 새롭게 생성된 컨테이너를 agent로 사용.

post

post를 이용하여 추가적인 step에 대하여 정의할 수 있음.
conditions

  • always : 항상 실행
  • changed : 변경이 있을 때 실행
  • fixed : 현재 stage(또는 pipeline)는 success, 이전 run은 fail일 때 실행
  • regression : 이전은 success, 현재는 fail일 때 실행
  • aborted : 현재 pipeline 상태가 aborted 이면 실행(보통 manually aborted 될 경우 의도)
  • failure : 현재 pipeline 상태가 failed 이면 실행
  • success : 현재 pipeline 상태가 success 이면 실행
  • unstable
  • unsuccessful : 현재 pipeline에서 success 상태가 없으면 실행
  • cleanup

example

pipeline{
    agent{ label "node" }
    stages{  // 1️⃣ stages 
        stage("A"){
            steps{
                echo "========executing A========"  // 2️⃣ steps
            }
            post{
                always{
                    echo "========always========" // 2️⃣ steps
              }
                success{
                    echo "========A executed successfully========"
                }
                failure{
                    echo "========A execution failed========"
                }
            }
        }
    }
    post{   // 3️⃣  posts
        always{
            echo "========always========"
        }
        success{
            echo "========pipeline executed successfully ========"
        }
        failure{
            echo "========pipeline execution failed========"
        }
    }
}
profile
Hungry Developer

0개의 댓글