[Jenkins] 파이프라인 & jenkins-docker 통합

신현식·2023년 3월 7일
0

구름_Jenkins

목록 보기
2/3

파이프라인

파이프라인은 CI/CD를 하기 위해 버전 제어에서 최종 목표까지 소프트웨어를 가져오는 자동화된 프로세스를 지원하는 기능/플러그인이다.

파이프라인은 DSL(Domain Specific Language) 를 통해 코드로 작성한다.
Jenkinsfile은 'pipeline-as-conde'를 구현하기 위한 DSL로 작성된 텍스트 파일이며 Git과 같은 소스제어 저장소에 커밋될 수 있다.

Jenkins Pipeline의 CI/CD 예시

서술적 방식의 파이프라인

젠킨스 버전2에서 사용하는 방식이다. 일반적인 jenkins의 웹 폼에서 구성하는 특정 구성과 작업을 정의한다.

  • Jenkins의 웹폼과 비슷
  • 가독성 좋음
  • 블루오션 인터페이스 사용가능
  • 문법 및 에러 확인이 비교적 쉬움
  • 반복되는 로직에 대한 지원이 없음(프로그램적 요소 X)
pipeline {
  agent {label worker_node1}  # 작업을 실행한 장소 지정
  stages {
    stage('Checkout') {    # 각 단계의 이름
      steps {
        git 'git@github.com:hsshin0602/source-maven-java-spring-hello-webapp.git'
      }
    }
    stage('Build') {
      steps {
        sh 'mvn clean package'
      }
    }
  }
}

서술적 파이프라인 구조

블록: 지시문과 섹션을 포함하는 블록을 구성됨, 서술적 파이프라인은 반드시 pipeline으로 시작

섹션

파이프라인 흐름 내에서 하나 이상의 지시문 또는 스텝의 묶음

  • agent 섹션: 전체 파이프라인 또는 특정 단계가 실행되는 노드 지정
    • agent any: 어떤 노드에서도 실행될 수 있음
    • agent none: 최상위 레벨에 지정되면 개별 스테이지에서 노드를 지정해야함
    • agent { label "LABEL" }: 특정 레이블을 가진 에이전트에서 실행
    • agent { docker { image 'IMAGE' } }: 도커에서 특정 컨테이너 이미지를 실행하는 컨테이너에 실행될 수 있음
    • 쿠버네티스에서 특정 이미지를 실행하는 파드에서 실행될 수 있음
    agent {
      kubernetes {
    yaml ```
  • stages 섹션: 하나 이상의 순서가 있는 stage 묶음
  • steps 섹션: stage지시문에서 실행한 하나 이상의 작업, git, sh, echo DSL 문장을 넣을 수 있음
    파이프라인 스텝 참조/검색
  • post 섹션: stages 또는 stage의 마지막에 실행할 추가작업, 위 작업을 실행한 후 상태에 따라 실행 여부를 결정할 수 있음
    • always: 항상 실행
    • changed: 현재 빌드 작업이 이전 빌드의 상태와 다른 경우 실행
    • success: 현재 작업이 성공한 경우 실행
    • failure: 현재 작업이 실해한 경우 실행(웹 인터페이스에서 빨간색)
    • unstable: 현재 빌드 상태가 불안정(테스트 실패, 코드 이상) 할 경우 실행(노란색)

지시문


parameters는 젠킨스 내에서만 참조할 수 있는 변수이고 environment는 shell에서 참조할 수 있는 변수이다.

  • environment 지시문
    pipeline에서 정의한 것과 stage안에서 정의한 변수는 차이가 있다. stage안에서 정의한 environment는 stage내부에서만 사용가능하고 pipeline에서 정의한 것은 전체에서 사용 가능하다.
    credentials() 메소드를 사용하여 미리 정의된 자격증명에 접근할 수 있다.

  • option 지시문
    파이프라인에 적용할 옵션을 정의한다.

  • parameter 지시문
    parameter를 참조 할때는 앞에 params. 를 붙여주는 것이 좋다. params.PERSON 등

  • tirggers 지시문
    파이프라인을 자동으로 실행한 트리거를 정의한다.

1 cron: 파이프라인이 트리거 되어야 하는 주기, 깃에 커밋과 상관없이 계속 작업함

triggers { cron('H */4 * * 1-5') }

2 pollSCM: 새 소스 변경 사항이 있는지 확인하는 주기, 깃에 변화가 있으면 빌드

triggers { pollSCM('H */4 * * 1-5') }
  • H(ash)는 균일한 부하를 생성하기 위함이다. ' 0 0 * * * ' 주기를 가지고 있는 파이프라인이 여러개인 경우 특정 시각에 부하가 몰리게 되는데 이를 조금씩 분산시켜주는 역할을 한다.

3 upstream: 특정 파이프라인 잡이 실행되면 후속으로 파이프라인 잡을 실행할 수 있음, 하나의 프로젝트가 끝나면 다음 프로젝트가 바로 실행되도록 지정

triggers { upstream(upstreamProjects: 'job1,job2', threshold:
hudson.model.Result.SUCCESS) }

4 githubPush: GitHub 저장소의 웹훅(WebHook)에 의한 트리거

  • Github Integration 플러그인 설치
triggers { githubPush() }

pollSCM VS githubPush
Github <-> Jenkins
pollSCM은 젠컨스가 주체적으로 해당되는 저장소에 새로운 커밋이 있는지 확인한다.(polling)
githubPush는 깃허브에 새로운 커밋이 발생하면 깃허브가 젠킨스에 push hook 하는 것이다.

  • stage 지시문
    파이프 라인에서 stages 섹션에 실행할 작업을 stage 지시문으로 순서 정의
    • stage 지시문에는 실행할 작업을 steps에 정의
  • tools 지시문
    Jenkins의 글로벌 도구에서 정의된 도구를 파이프라인에서 사용할 도구로 명시한다.
    • agent none 인 경우에는 이 지시문이 무시된다.
      maven, jdk,gradle 등
pipeline {
 agent any
 tools { maven 'apache-maven-3.0.1' }  # maven과 툴의 이름 지정
  • input 지시문
    stage에서 사용자 입력 값 요청을 정의한다.
  • when 지시문

📢 파이프라인 구문 참조
-> Snippet Generator: 스텝에서 실제로 사용할 명령을 DSL에 맞게 생성해준다.
-> Declarative Directive Generator(지시어 생성기): 파이프라인 지시문에 대한 스크립트를 생성
-> Declarative Online Documentation: 젠킨스 파일의 문법 확인
-> Global Variables Reference(전역변수 참조): 젠킨스에서 사용가능한 전역변수 모음
전역변수는 env.BUILD_NUMBER 형식으로 참조

파이프라인 작성

새 프로젝트의 이름를 작성하고 유형을 pipeline으로 선택해서 프로젝트를 만든다.
이름: maven_pipeline

  • 프로젝트의 이름과 밑에 scp의 workspace의 주소를 맞춰줘야 한다.

  • 다른 것은 일단 건들지 않고 맨 밑에 pipeline에서 지금 실습에서는 pipeline script를 선택해서 서술형 파이프라인을 작성한 후 업로드해서 저장한다.

pipeline {
  agent { label 'jenkins-node' }

  stages {
    stage('Checkout') {
      steps {
        git branch: 'main', url: 'https://github.com/hsshin0602/source-maven-java-spring-hello-webapp.git'
      }
    }

    stage('Maven Build') {
      steps {
        sh 'mvn clean package'
      }
    }

    stage('Deploy to Tomcat') {
      steps {
        sh 'scp /var/lib/jenkins/workspace/maven_pipeline/target/hello-world.war ubuntu@172.31.47.106:/var/lib/tomcat9/webapps'
      }
    }
  }
}

Jenkinsfile 작성

이번 실습에서는 pipeline을 pipeline script from SCM를 이용해서 깃허브 저장소와 연동해 Jenkinsfile를 가져와 자동화를 할 것이다.

  • 깃배쉬로 들어가서 작업을 진행한다. 홈디렉터리에 git 디렉터리를 만든 후 git 디렉터리 안에 내 저장소를 클론해온 다음 진행한다. 어제 작업을 진행했다면 생략가능

  • Jenkinsfile 조건: 트리거 추가, tomcat 주소 파라미터화, 작업 디렉터리를 환경변수로 사용

cd git/source-maven-java-spring-hello-webapp

# 젠킨스파일 작성, 내용은 위와 동일하게 진행해도 되지만 이번에는 조건에 부합한 형태로 진행
vi Jenkinsfile

pipeline {
  agent { label 'jenkins-node' }
   
  triggers {
    pollSCM '* * * * *'
  }
  
  
  parameters {
    string defaultValue: 'ubuntu', name: 'TOMCAT_USER_ID'
    string defaultValue: '172.31.47.106', name: 'TOMCAT_IP'  
    string defaultValue: '/var/lib/tomcat9/webapps', name: 'TOMCAT_WEBAPP_DIR'
  }

  stages {
    stage('Checkout') {
      steps {
        git branch: 'main', url: 'https://github.com/hsshin0602/source-maven-java-spring-hello-webapp.git'
      }
    }

    stage('Maven Build') {
      tools { maven 'Maven-3' }  # maven 툴 지정
      steps {
        sh 'mvn clean package'
      }
    }

    stage('Deploy to Tomcat') {
      steps {
        sh "scp $(env.WORKPASCE)/target/hello-world.war ${params.TOMCAT_LOGIN_USER}@${params.TOMCAT_IP}:${params.TOMCAT_WEBAPP_DIR}"
      }
    }
  }
}

# Jenkinsfile 원격 깃 저장소에 푸쉬
git add .
git commit -m 'add Jenkinsfile'
git push

-> env.WORKPASCE환경변수: 프로젝트의 workspace의 절대경로
이전에는 트리거가 없었기 때문에 트리거의 존재를 모른다. 따라서 맨 처음에 한번은 수동으로 빌드를 실행시켜줘야한다.

""(쌍따옴표)와 ''(홑따옴표) 차이

''는 안쪽의 들어가는 모든 특수문자를 탈출 시킨다. 따라서 글자 그대로 명령어를 실행시킨다.
""는 안쪽에 들어가는 특수문자를 인식하고 $ ,` ,\ ,! 등은 탈출시키지 않는다. 즉 이런 특수문자를 명령으로 적용한다는 것이다.

MSG=hello
echo '${MSG} world'
-> ${MSG} world

echo "${MSG} world"
-> hello world

도커에서의 젠킨스 통합

작업 플로우: ec2 인스턴스(VM)에 도커 설치, 컨테이너를 젠킨스로 설치, 빌드할떄 노드가 필요한데 이는 동적으로 생성 삭제를 할것, 최종적으로 해당되는 이미지를 빌드를해서 docker-hub(저장소)에 push를 하고 이를 다시 가져와서 배포를 진행할 것
-> 젠킨스 파드는 영구적으로 실행, 대신 노드로 되는 것들은 필요할떄만 생성 후 삭제

인스턴스 생성
이름: Jenkins-Docker, 우분투 20.4 이미지, t3 medium, 보안그룹: SSH, HTTP, TCP 8080, 키페어 등록, 스토리지: 40G 로 설정한 이후 인스턴스를 생성한다.

# 이름 설정
sudo hostnamectl set-hostname jenkins-docker
exec bash

# USER에 docker 그룹 부여
sudo usermod -aG docker $USER
exit

# 재접속 시 도커 사용 가능
# 관리 목적상 네트워크 하나 생성
docker network create jenkins

# DinD(Docker in Docker) 컨테이너 실행
docker container run --name docker-dind --detach \
 --privileged --network jenkins --network-alias docker \
 --env DOCKER_TLS_CERTDIR=/certs \
 --volume jenkins-docker-certs:/certs/client \
 --volume jenkins-data:/var/jenkins_home \
 --volume docker:/var/lib/docker \
 --publish 2376:2376 \
 --restart=always \
 docker:dind --storage-driver overlay2

Jenkins 컨테이너가 호스트의 Docker 데몬으로 접근하기 위한 컨테이너이다.

  • jenkins/jenkins 이미지 내에 docker 명령 설치(젠킨스가 도커를 사용하기 위함) / 필수 젠킨스 플러그인 설치
mkdir jenkins
cd jenkins

# 도커 파일 작성
vi Dockerfile

FROM jenkins/jenkins:lts-jdk11   # 자바 11이 설치되어있는 젠킨스 이미지
USER root  # 루트권한으로 도커명령 설치
RUN apt-get update && apt-get install -y lsb-release
RUN curl -fsSLo /usr/share/keyrings/docker-archive-keyring.asc \
 https://download.docker.com/linux/debian/gpg
RUN echo "deb [arch=$(dpkg --print-architecture) \
 signed-by=/usr/share/keyrings/docker-archive-keyring.asc] \
 https://download.docker.com/linux/debian \
 $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list
RUN apt-get update && apt-get install -y docker-ce-cli
USER jenkins   # 플러그인 설치
RUN jenkins-plugin-cli --plugins "docker-plugin docker-workflow"

# 이미지 로컬에 빌드
docker image build -t jenkins-docker:lts-jdk11 .
  • Jenkins 컨테이너 실행(안에 도커 명령어 존재)
    밑 env는 도커 클라이언트가 참조하는 환경변수, 밑에 docker의 의미는 dind 컨테이너를 의미(위에서 별명 지정)
    따라서 젠킨스에서 도커명령을 실행하면 dind로 통신이 간다. dind는 Docker In Docker로 도커의 가짜서버라 보면 된다. 따라서 dind는 가장 큰 도커 데몬으로 통신을 연결한다.
    • 통신 과정: Jenkins -> dind -> docker daemin
      젠킨스도 호스트에 있는 해당 도커 데몬을 이용해서 컨테이너를 만들 수 있음
# Jenkins 컨테이너 실행
docker run --name jenkins-docker \
 --restart=always \
 --detach \
 --network jenkins \
 --env DOCKER_HOST=tcp://docker:2376 \
 --env DOCKER_CERT_PATH=/certs/client \
 --env DOCKER_TLS_VERIFY=1 \
 --publish 8080:8080 \
 --publish 50000:50000 \
 --volume jenkins-data:/var/jenkins_home \
 --volume jenkins-docker-certs:/certs/client:ro \
 jenkins-docker:lts-jdk11
  • enkins-docker 내부에서 도커 명령 사용가능. 다만 밖에서 사용하는 것과는 분리된 공간으로 취급한다. 따라서 같은 명령을 실행해도 밖과는 다른 내용이 보인다.
# jenkins-docker 내부로 진입
docker container exec -it jenkins-docker bash
# 안에서 도커 명령 실행 가능
$ docker container ls

  • 젠킨스 대시보드에 접속한다. http://(Jenkins_Docker_IP):8080, 접속 후 Unlock진행, 플러그 생성까지 진행한다. 이후에는 똑같이 계정을 생성한 이후 접속한다.
# Unlock Jenkins 비번 복붙
docker exec jenkins-docker cat /var/jenkins_home/secrets/initialAdminPassword
  • 젠킨스 관리 -> 플러그인 관리 -> Installed plug에 들어가서 docker라 검색하면 도커관련 플러그인이 설치되어 있는 것을 확인 할 수 있다.
  • 노드관리 -> Configure Clouds -> Docker 선택(도커 플러그인을 설치했기에 보임)
    이름: Docker

    • Docker Host URI: tcp://docker:2376(dind 컨테이너를 의미, 위에서 포트와 이름을 지정함)
      -> test connection을 눌러 통신이 되나 확인해보는데 현재 테스트를 해보면 400오류(client측 오류)가 나온다. 여기서는 젠킨스가 도커 데몬에게 통신하기 위해서 인증서를 사용하는데 이 인증서는 certs 디렉터리에 만들어져있다.
      따라서 Server credentials에 등록를 해준다.

    • Kind: X.509 client Certificate

# Client Key 복붙
docker container exec jenkins-docker ls /certs/client/key.pem

# Client Certificate 복붙
docker container exec jenkins-docker ls /certs/client/cert.pem

# Server CA Certificate 복붙
docker container exec jenkins-docker ls /certs/client/ca.pem

ID: docker-client-certs로 지정한 이후 add 진행
  • 이후 생성한 것을 선택한 후 test connection를 진행하면 오류가 나오지 않는 것을 확인할 수 있다.
  • enabled 활성화, Container Cap은 젠킨스가 생성할 수 있는 노드의 최대 수를 의미, 기본값으로 놔둠, 여기까지 진행하고 apply후 save

젠킨스-도커 파이프라인 생성

프로젝트 생성: docker_test_pipeline, pipeline 유형 선택
도커-젠킨스 파일 테스트 깃허브
pipeline script from SCM을 선택한 후 위 깃 저장소의 주소로 등록한다.브랜치를 main으로 변경한 이후 순차적으로 실습을 진행할 것이기에 처음에는 Script Path를 Jenkinsfile-1로 설정한 후 저장함

# 작업을 진행하는 동안에 잠깐 컨테이너가 생성됨
docker container exec jenkins-docker docker ps
-> 컨테이너 보임
# 작업이 종료되면 컨테이너가 제거됨
docker container exec jenkins-docker docker ps
-> 컨테이너 안보임

docker { image 'node:16-alpine' }: 젠킨스에는 노드라는 명령이 없는데 docker로 진행하면 지정한 이미지로 컨테이너를 띄울 수있고 여기서 생성한 컨테이너 내부에서 명령을 실행한다.

  • 즉 stage 작업을 할때만 도커 컨테이너를 띄우고 작업을 종료하면 컨테이너를 종료시킨다는 것이다.
profile
전공 소개

0개의 댓글