젠킨스의 파이프라인(Pipeline as a Code)는 코드로서 파이프라인을 구현하는 것이다. CI/CD 를 하기 위해 버전 제어에서 최종 목표까지 소프트웨어를 가져오는 자동화된 프로세스를 지원하는 기능/플러그인이다. 파이프라인은 DSL을 통해 코드로 작성된다. Jenkinsfile은 Pipeline as a Code 를 구현하기 위한 dsl로 작성된 텍스트 파일이며 Git과 같은 소스 제어 저장소에 커밋할 수 있다.
물리적인 환경에서는 물리적으로 시스템을 세팅하는 등 해야했기 때문에 논리적인 구성이 어려웠다. 하지만 점점 기구축된 피지컬 시스템 위에 가상화된 인프라를 동적으로 만들기 쉬워지면서, 코드로서 원하는 형태를 정의하고 관리하는 것이 더 효율적으로 되었다. 코드로 관리하게 되면 추적이 가능하고, 버전 컨트롤 시스템을 이용하며 원하는 형태로 어떻게 변하는지 버전 관리를 할 수 있고, 코드의 재사용도 추구할 수 있다. 따라서 현재의 인프라는 코드로 배포하는 것이 기본이다.
일반적인 Jenkins의 웹 폼과 비슷하다. 비교적 가독성이 더 좋고, 블루오션 인터페이스를 사용 가능하다. 스크립트 방식의 파이프 라인에 비해서 문법 확인과 에러 확인이 비교적 쉽고 반복되는 로직에 대한 지원이 없다. 왜냐하면 프로그래밍 언어가 아니기 때문이다.
서술적 파이프라인은 지시문과 섹션을 포함하는 블록으로 구성된다. 각 섹션에는 다른 섹션과 지시문, 스테이지, 스텝이 있을 수 있다.
pipeline {
agent {label 'worker_node1'} #작업을 실행할 장소
stages {
stage('Checkout') { #단계 이름
steps {
git 'git@repository address'
}
}
stage('Build') {
steps {
sh 'mvn clean package'
}
}
}
}
Jenkins에는 다양한 파이프라인을 쉽게 생성할 수 있도록 문서 기능이 웹에 내장되어 있고, 플러그인을 설치하면 내장 문서도 관련된 내용이 자동으로 업데이트된다. 참고
시작과 끝이 있는 코드의 묶음이다. 파이프라인의 모든 구성은 블록이지만, 일반적으로 pipeline 블록을 지칭한다. 서술적 파이프라인은 반드시 pipeline 블록으로 시작하기 때문이다.
pipeline {
# code..
}
파이프라인의 흐름 안에서 하나 이상의 지시문 또는 스텝을 묶은 것이다.
agent {
kubernetes {
yaml '''
apiVersions: v1
kind: Pod
spec: XXX
'''
}
}
pipeline {
agent any
stages {
stage('Example') {
steps {
echo 'Hello World'
}
}
}
}
steps
stage 지시문에서 실행할 하나 이상의 작업. git, sh, echo와 같은 DSL 문장을 넣을 수 있다.
파이프라인 스텝 참조/검색
post
stages 또는 stage의 마지막에 실행할 추가 작업. 전통적인 프리스타일 잡의 빌드 후 처리 동작과 비슷하다. stages 또는 steps를 실행한 후 상태에 따라 실행 여부를 결정할 수 있다.
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'
}
}
}
post {
always {
echo 'I will always say Hello again!'
}
}
}
environment
환경 변수를 정의하고, 자격증명을 참조한다. shell에서 참조할 수 있는 변수이다. stage 안에서 정의한 environment는 stage 안에서만 사용이 가능하고, pipleline에서 정의한 environment는 파이프라인 전체에서 사용이 가능하다. credentials() 메소드를 사용하여 미리 정의된 자격 증명에 접근할 수 있다.
option
파이프라인에 적용할 값을 미리 정의한다.
parameter
parameter는 젠킨스 내에서만 참조할 수 있는 변수이다. 파이프라인에서 프로젝트의 매개 변수를 정의 및 참조할 수 있다.
trigger
파이프라인을 자동으로 실행할 트리거이다.
triggers { cron('H */4 * * 1-5') }
triggers { pollSCM('H */4 * * 1-5') }
triggers { upstream(upstreamProjects: 'job1,job2', threshold: hudson.model.Result.SUCCESS) }
triggers { githubPush() }
stage
파이프라인에서 stages 섹션에 실행할 작업을 stage 지시문으로 순서 정의한다. stage 지시문에서는 실행할 작업을 steps에 정의한다.
pipeline {
stages {
stage('stage 1') {
steps { echo "step 1" }
}
stage('stage 2') {
steps { echo "step 2" }
}
}
}
pipeline {
agent any
tools { maven 'apache-maven-3.0.1' {
when { branch 'master' }
when { environment name: 'DEPLOY_TO', value: 'production' }
when { expression { return params.DEBUG_BUILD } }
when { allOf { branch 'master'; environment name:
'DEPLOY_TO', value: 'production' } }
when { anyOf { branch 'master'; branch 'staging' } }
when { not { branch 'master' } }
Jenkins 아이템 추가에서 유형을 Pipeline으로 하고 생성한다. 프로젝트의 이름과 SCP의 WorkSpace 주소를 같게 해야 한다. 다음과 같은 서술형 파이프라인을 작성하고 Apply 후 저장한다.
pipeline {
agent { label 'jenkins-node' }
stages {
stage('Checkout') {
steps {
git branch: 'main', url: 'github url'
}
}
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@private ip:/var/lib/tomcat9/webapps'
}
}
}
}
Pipeline Script from SCM을 이용해 github 저장소와 연동한다. Jenkinsfile에 trigger와 parameter로 작성된 tomcat 주소, 작업 디렉터리를 환경변수로 사용한다.
vi Jenkinsfile
pipeline {
agent { label 'jenkins-node' }
triggers {
pollSCM '* * * * *'
}
parameters {
string defaultValue: 'ubuntu', name: 'TOMCAT_USER_ID'
string defaultValue: 'private ip', name: 'TOMCAT_IP'
string defaultValue: '/var/lib/tomcat9/webapps', name: 'TOMCAT_WEBAPP_DIR'
}
stages {
stage('Checkout') {
steps {
git branch: 'main', url: 'github url'
}
}
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}"
} #절대경로
}
}
}
trigger의 존재를 인식시키기 위해 한 번은 수동으로 빌드를 실행시켜줘야 한다. 여기서, 홑따옴표는 글자 그대로 명령어를 인식해 실행시키고, 쌍따옴표는 특수문자를 명령으로 적용해 특수문자와 함께 실행시킨다.
EC2 인스턴스에 설치한 Docker를 통해 Jenkins를 컨테이너로 설치할 것이다. Build할 때 필요한 Node는 동적으로 생성 및 삭제할 것이고, 최종적으로 이미지를 빌드해 docker-hub에 push 후 다시 가져와 배포를 진행할 것이다.
새로 생성한 Jenkins-Docker 인스턴스(t3.medium, 40g 스토리지)에 설정한다.
sudo hostnamectl set-hostname jenkins-docker
exec bash
sudo usermod -aG docker $USER #user에 docker그룹 부여
exit
docker network create jenkins #도커 네트워크 생성
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 #docker in docker 컨테이너 실행
jenkins/jenkins 이미지에 docker 명령을 설치하고 필수 플러그인을 설치한다
mkdir jenkins
cd jenkins
vi Dockerfile
FROM jenkins/jenkins:lts-jdk11 #java11 이 설치되어 잇는 jenkins 이미지
USER root #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 .
안에 Docker 명령어가 존재하는 Jenkins 컨테이너를 실행한다. env는 docker 클라이언트가 참조하는 환경변수이고, jenkins-docker:lts-jdk11은 docker in docker 컨테이너를 의미한다. jenkins에서 docker 명령을 실행하면 docker의 가짜 서버라고 할 수 있는 DinD로 통신이 가고, DinD는 가장 큰 도커 데몬으로 통신을 연결한다. 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
jenkins-docker 안에서도 docker 명령을 사용가능하지만, 분리된 공간으로 취급되어 밖과는 다른 내용이 출력된다.
docker container exec -it jenkins-docker bash
$ docker container ls
http://DOCKER_HOST_IP:8080으로 Jenkins 대시보드에 접속한다.
docker exec jenkins-docker cat /var/jenkins_home/secrets/initialAdminPassword
명령어를 실행해 Unlock을 진행하고, admin을 만들어 접속한다. 플러그인에서 docker 플러그인이 설치되어 있는 것을 확인할 수 있다. 노드 관리의 Configure Cloud에서 Docker를 선택하고 tcp://docker:2376으로 통신이 되나 확인해 보면 400 error가 뜬 채로 연결되지 않는다. 이것은 인증서가 등록되어 있지 않기 때문으로, certs 디렉터리에 만들어져 있는 인증서를 Server Credentials에 등록 해 주면 오류가 나오지 않고 연결되는 것을 확인할 수 있다.
docker container exec jenkins-docker ls /certs/client/key.pem #Client Key
docker container exec jenkins-docker ls /certs/client/cert.pem #Client Certificate
docker container exec jenkins-docker ls /certs/client/ca.pem #Server CA Certificate
ID는 docker-client-certs로 지정한다.
Pipeline 유형의 프로젝트에서 Pipeline Script from SCM을 선택한 후 참고 github의 레포지토리를 등록한다. branch를 main으로 변경하고 저장한다.
docker container exec jenkins-docker docker ps
작업을 진행하는 동안엔 컨테이너가 보이지만, 종료되면 컨테이너가 보이지 않는다(제거된다). Jenkins에는 npm 명령이 없지만, docker에서 생성한 컨테이너 내부에서 명령을 실행한다. 즉 stage일 때만 docker 컨테이너를 띄우고 작업을 종료하면 컨테이너를 제거한다.