Jenkins
Jenkins란 오픈소스 자동화 서버로 구축, 테스트, 배포와 관련된 소프트웨어 개발 프로세스를 자동화하고 CI/CD 제공하는 툴이다.
Java Runtime 위에서 작동되며 다양한 플러그인을 지원하여 인프라 운영 상황에 맞게 입맛대로 파이프라인을 구성할 수 있다.
Jenkins Pipeline
Jenkins Pipeline이란 CI/CD를 Jenkins에 구현하고 통합하기 위한 플러그인의 모음이다.
코드가 커밋된 후부터 실환경에 배포되기까지 릴리즈 과정은 꽤나 복잡하다.
코드를 빌드하고 여러 단계의 테스트를 거쳐 각각의 환경에 배포해야 한다.
우리는 Pipeline을 미리 정의하여, 이러한 프로세스를 자동화할 수 있다.
Jenkins Pipeline은 Pipeline DSL 구문을 통해 코드 형태로 복잡한 파이프라인을 모델링할 수 있으며, Jenkinsfile
이라는 text 파일로 정의하여 프로젝트에 포함시켜 활용할 수 있다.
Jenkinsfile로 Pipeline을 관리하는 경우 프로젝트의 여러 구성원이 쉽게 공유하고 리뷰/수정할 수 있으며, Pipeline audit에 대한 추적이 가능하다.
또한 Pipeline 빌드 프로세스를 자동으로 생성해주며, Jenkins 노드가 재시작되어도 안전하다.
Pipeline의 2가지 주요 개념에 대해 알아보자.
Stage
: 전체 파이프라인에서 수행되는 작업을 개념적 구분해놓은 단계이다. (스텝들의 모음)
Step
: 젠킨스가 특정 시점에서 수행해야 하는 단순한 하나의 task를 의미한다.
아래는 Jenkins Pipeline 시나리오 예시이다.
오늘은 Jenkins를 통해 AWS S3 배포와 Docker 이미지/컨테이너 배포를 해 볼 계획이다.
또한 Git과 연동하여 master 브랜치는 Prod 환경에, develop 브랜치는 Dev 환경에 배포되도록 Pipeline을 구성할 것이다.
1. 환경 설정
1.1. Jenkins Node 준비
AWS EC2를 하나 준비하였다.
- AMI : Amazon Linux 2 AMI
- 인스턴스 유형 : t2.small (1Core/2GB)
1.2. Jenkins 설치
$ sudo yum update -y
# Jenkins 패키지 추가
$ sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins.io/redhat/jenkins.repo &&
$ sudo rpm --import https://pkg.jenkins.io/redhat/jenkins.io.key
# Install java, docker, git
$ sudo yum install -y java-1.8.0-openjdk jenkins git docker
# 자바 버전 8 로 설정
$ sudo alternatives --config java
$ sudo service jenkins start
1.3. docker 그룹에 Jenkins 추가
# docker 그룹에 jenkins 추가
$ sudo usermod -aG docker jenkins
$ sudo systemctl start docker
$ sudo service docker start
$ sudo chmod 666 /var/run/docker.sock
$ sudo -su jenkins
1.4. Jenkins Dashboard 접속
Jenkins Dashboard에 접속하기 위해서는 우선 기본 port인 8080을 열어줘야 한다.
최초 접속 시 Password를 입력해야 한다.
초기 Password는 화면에 적혀있듯이 아래 경로에서 확인 가능하다.
# 초기 비밀번호 확인
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
1.5. 시작
1.6. 추가 Pulg-in 설치
Pipeline에서 사용하기 위한 plugin을 추가한다.
1.7. Git Credentials 추가
Jenkins와 Git의 연결을 위해 Credentials을 추가해줘야 한다.
우선 Git Access Token을 발급받는다.
Jenkins관리 > Manage Credentials 메뉴에서 Credentials를 추가한다.
1.8. AWS Credentials 추가
동일하게 AWS와의 연결을 위해 우선 AWS에서 jenkins 사용자를 만들고
Jenkins Credentials를 추가한다.
방금 만든 2개의 Credentials이다.
1.9. Pipeline 생성
git을 붙이기 위해 Multibranch Pipeline
item을 만들었다.
이름과 설명을 간단하게 입력하고
Git Project Repository를 선택한다.
마지막으로 Jenkinsfile의 위치를 설정하고, Trigger 배치 간격을 정해준다.
(test를 원활하게 진행하기 위해 1분으로 설정하였다.)
2. Parameter 관리 (AWS Parameter Store)
본격적으로 배포를 하기 전에 Parameter를 받아오는 방법에 대해 알아보고 가자.
인프라 운영에서 Parameter 관리는 매우 중요하다.
자체적으로 Parameter 서버를 따로 운영하기도 하고, 심지어는 DB나 File로 관리하기도 한다.
이번에는 AWS Parameter Store에 파라미터를 설정하고 Jenkinsfile에서 이를 받아오도록 구현해보려 한다.
2.1 AWS parameter store에 환경 별 파라미터 저장
2.2 jq install
Jenkins node에서 jq 명령을 수행하기 위해 yum 설치
$ sudo yum jq
$ sudo yum install jq
2.3 Jenkinsfile 설정
def DEPLOY_TO
pipeline {
agent any
parameters {
string(name:'MYSQL_HOST', defaultValue: 'devMysql', description: 'dev mysql host')
string(name:'MYSQL_PASSWORD', defaultValue: 'new1234!', description: 'dev mysql password')
}
stages {
// branch명을 기준으로 배포할 환경 설정
stage('Decide Deploy To') {
steps {
script {
if (env.BRANCH_NAME == 'master'){
DEPLOY_TO = 'prod'
} else if (env.BRANCH_NAME == 'develop'){
DEPLOY_TO = 'dev'
} else if (env.BRANCH_NAME == 'qa'){
DEPLOY_TO = 'qa'
}
}
echo "DEPLOY_TO: ${DEPLOY_TO}"
}
}
// aws Parameter Store의 파라미터 정보 가져오기
stage('Check deploy parameter') {
steps {
script {
withAWS(region:'ap-northeast-2', credentials:'jenkinsaws') {
def mysql_host = sh(script: "aws ssm get-parameters --name /${DEPLOY_TO}/MYSQL_HOST | jq '.Parameters[0].Value'", returnStdout: true).trim()
def mysql_password = sh(script: "aws ssm get-parameters --name /${DEPLOY_TO}/MYSQL_PASSWORD | jq '.Parameters[0].Value'", returnStdout: true).trim()
echo "${mysql_host}"
}
}
}
}
}
}
2.4 log 확인
master 브랜치에 push 후 log이다.
prod 환경도 정상적으로 설정되며, aws Parameter Store의 /prod/MYSQL_HOST
파라미터도 잘 가져온 것을 확인할 수 있다.
3. Front-end 배포 (AWS S3)
3.1. Jenkinsfile 설정
front-end를 배포하기 위한 'Deploy website' stage 추가
stage('Deploy website') {
steps{
dir ('./website') {
script {
withAWS(region:'ap-northeast-2', credentials:'jenkinsaws') {
sh """
aws s3 sync ./ s3://${DEPLOY_TO}-suyeon-website
"""
}
}
}
}
}
3.2 AWS S3 버킷 생성
정적 page를 배포하기 위해 환경별로 AWS S3 버킷을 생성하고
3.3 코드 준비
간단하게 배포할 코드를 준비하였다.
./website/index.html
<html>
<body>
<h1>Hello World</h1>
</body>
</html>
3.4 Prod 배포
Prod 환경에 배포하기 위해 master
브랜치에 새로 추가한 index.html을 push 하였다.
Jenkins Dashboard에서 정상적으로 Pipeline이 동작한 것을 확인할 수 있다.
'Deploy website' Stage의 Log를 확인해보니 prod용으로 만들어 놓은 s3 버킷에 index.html이 잘 배포되었다고 한다.
확인사살 1
확인사살 2
3.5 Dev 배포
Dev 환경에도 배포해보자.
develop
branch에 push를 하자 Trigger에 의해 develop Pipeline이 동작한다.
dev 환경에도 정상적으로 배포가 되었다.
4. Docker Image 배포 (AWS ECR)
이번에는 docker를 빌드하여 이미지 생성 후 이미지를 AWS ECR(Elastic Container Registry)에 배포해보자.
4.1 Jenkins node docker 설치 확인
$ docker ps
4.2 Jenkinsfile 설정
docker 이미지를 배포하기 위한 'Build Server' stage 추가
stage('Build Server') {
steps {
dir('./server') {
script {
withAWS(region:'ap-northeast-2', credentials:'jenkinsaws') {
def login = ecrLogin()
def mysql_host = sh(script: "aws ssm get-parameters --name /${DEPLOY_TO}/MYSQL_HOST | jq '.Parameters[0].Value'", returnStdout: true).trim()
def mysql_password = sh(script: "aws ssm get-parameters --name /${DEPLOY_TO}/MYSQL_PASSWORD | jq '.Parameters[0].Value'", returnStdout: true).trim()
echo "${login}"
// 실제 로그인
sh "${login}"
sh """
docker build --build-arg env=${DEPLOY_TO} --build-arg mysqlHost=${mysql_host} --build-arg mysqlPassword=${mysql_password} -t 000000000000.dkr.ecr.ap-northeast-2.amazonaws.com/${DEPLOY_TO}-webserver .
docker push 000000000000.dkr.ecr.ap-northeast-2.amazonaws.com/${DEPLOY_TO}-webserver
"""
}
}
}
}
}
000000000000 대신 AWS 계정 ID를 넣어줘야 한다.
4.3 AWS ECR Repositories 생성
docker 이미지를 배포할 AWS ECR Repository를 환경별로 생성하였다.
4.4 Docker 빌드 환경 준비
./server/package.json
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "tsc",
"start": "node ./dist/index.js",
"test": "node_modules/.bin/jest .",
"lint": "npx eslint index.ts"
},
"author": "",
"license": "ISC",
"dependencies": {
"@types/jest": "^26.0.20",
"express": "^4.17.1",
"typescript": "^4.1.3"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.14.1",
"@typescript-eslint/parser": "^4.14.1",
"eslint": "^7.18.0",
"jest": "^26.6.3"
}
}
./server/Dockerfile
FROM node:11.13.0
LABEL name="server"
RUN mkdir app
WORKDIR /app
COPY ./package.json /app/package.json
COPY ./package-lock.json /app/package-lock.json
RUN npm install
ARG env
ARG mysqlHost
ARG mysqlPassword
ENV APP_ENV=$env
ENV MYSQL_HOST=$mysqlHost
ENV MYSQL_PASSWORD=$mysqlPassword
COPY . /app
EXPOSE 80
RUN echo 'Build Completed'
# The first parameter of docker CMD is executable and second parameter is file or parameters.
# Docker cmd internally uses sh -C
CMD ["npm","start"]
4.5 Prod 배포
prod 용 AWS ECR Repository에 이미지가 잘 들어갔다.
5. Docker Container 배포 (AWS ECS)
AWS ECR에 push 된 이미지를 AWS ECS(Elastic Container Service)에 배포해보자.
5.1 Jenkinsfile 설정
docker 컨테이너를 배포하기 위한 'Deploy Server' stage 추가
stage('Deploy Server') {
steps {
script {
withAWS(region:'ap-northeast-2', credentials:'jenkinsaws') {
sh """
aws ecs update-service \
--region ap-northeast-2 \
--cluster ${DEPLOY_TO}-app \
--service ${DEPLOY_TO}-webserver \
--force-new-deployment \
--desired-count 2
aws ecs wait services-stable \
--cluster ${DEPLOY_TO}-app \
--services ${DEPLOY_TO}-webserverD
"""
}
}
}
}
5.2 AWS ECS 클러스터 생성
5.3 Prod 배포
AWS ECS 클러스터 작업 확인