제가 진행했던 make-delivery 프로젝트를 배포하기 위해서는 리눅스 서버에서 Git Pull을 하여 소스코드를 가져온 다음 maven을 이용하여 build를 하고 jar파일을 실행해야 했습니다. 하지만 매번 git pull을 하고 빌드를 하고 jar파일을 실행하는 것은 지루하고 반복적인 과정이며 불필요한 시간 낭비였습니다.
따라서 이러한 문제를 해결하기 위해 여러 글들을 읽어봤고 예전에 Agile방식에 대한 글을 읽었던 경험이 생각나 검색 후 CI (지속적 통합)이라는 개념을 알게 되었습니다.
CI란 개발자들이 빠른 주기로 작업한 내용을 통합 브랜치에 통합하고 빌드하는 개발방식을 말합니다.
# CI를 해야하는 이유
tjdrnr05571.tistory.com/12
Jenkins를 이용한 CI 과정
1. 개발자가 Github에 수정한 코드를 Push한다.
2. Github은 이를 감지하고 Jenkins에 Github Webhook을 보낸다.
3. Jenkins에서는 Github Webhook을 JSON파일로 받고 파싱한다.
4. 파싱하여 변화된 branch를 파악하고 이 branch의 코드를 Git clone한다.
5. Jenkins에서는 Maven 빌드과정, 컴파일 -> 테스트 -> 패키징을 거쳐 빌드한다.
이 과정을 개발하기 위해 여러 글들을 읽어보니 모두 젠킨스 플러그인을 이용하는 글들이었습니다.하지만 제가 젠킨스를 서버에 설치할 때 젠킨스 공식 사이트의 플러그인 파일 서버에 오류가 있어 플러그인을 다운받지 못하는 상황이었습니다. (플러그인 수동 설치조차 불가능했습니다). 어차피 CI와 CD에 대해 제대로 이해를 하고 구현하고 제 프로젝트에 맞게 자동화를 하려면 bash script를 이용해 프로젝트에 맞는 자동화가 필요하다고 생각하여 젠킨스 플러그인을 아예 사용하지 않고 bash shell script를 이용해 구현해 보기로 결정했습니다.
Jenkins를 이용해 전체적으로 구현해야할 방향은 다음과 같았습니다.
1. Github Webhook
2. bash parse using python
3. git clone -b {해당 branch}
4. maven compile, test, build
5. github api를 이용해 github으로 빌드&테스트 성공 실패 여부 전송
6. 배포서버로 빌드파일 전송
7. 젠킨스 서버에서 ssh로 배포서버 접속 (pub키 설정 먼저해야함)
8. 젠킨스 서버에 있던 빌드 스크립트를 배포 서버에서 실행
위 과정을 한번에 적용하기 위해 Jenkins pipeline 사용했습니다. 젠킨스의 잡들은 원래 하나하나 지정해줘서 동작하게끔 작동했는데 젠킨스 파이프라인을 이용하면 이 과정을 스크립트 한곳에서 모아서 적용시킬 수 있으며 소스코드에 Jenkinsfile로 포함시키기도 용이합니다.
1. Github Webhook
Github Webhook 설정페이지에서 "Github토큰@{젠킨스서버url}/job/{job-name}/buildWithParameters?token={job-token} " 으로 Github Webhook을 보내면 Jenkins에서는 토큰을 확인하고 payload를 받습니다. 젠킨스 관리 페이지에서 String Parameter를 payload로 지정하면 json형태의 payload를 받아서 사용할 수 있습니다.
2. bash parse using python & git clone -b {해당 branch}
payload를 받으면 다음과 같이 python을 이용하여 shell 상에서 parsing을 한 뒤 젠킨스에서 Git Checkout & Git Clone할 Branch와 Commit Id를 변수로 파싱합니다.
GIT_CHANGE_BRANCH_NAME = sh(returnStdout: true, script: 'echo ${payload} | python3 -c \"import sys,json;print(json.load(sys.stdin,strict=False)[\'ref\'][11:])\"').trim()
GIT_COMMIT_SHA = sh(returnStdout: true, script: 'echo ${payload} | python3 -c \"import sys,json;print(json.load(sys.stdin,strict=False)[\'head_commit\'][\'id\'])\"').trim()
echo "arrive ${GIT_CHANGE_BRANCH_NAME}"
sh "git clone -b ${GIT_CHANGE_BRANCH_NAME} --single-branch \"https://github.com/f-lab-edu/make-delivery.git\""
3. maven compile, test, build
젠킨스 서버에서 변경된 코드를 git clone받으면 빌드,테스트 할 통합된 코드가 젠킨스 서버에 생성됩니다. 이 코드를 이용해 maven build를 해줘야 합니다.
다음과 같이 maven build를 해주면 컴파일-> 테스트 -> 패키징 까지 완료하고 jar파일을 다음과 같은 경로에 저장합니다.
4. github api를 이용해 github으로 빌드&테스트 성공 실패 여부 전송
빌드가 성공한다면 이를 Github에 빌드가 성공했다고 GITHUB API를 이용해 알려줘 Pull request에 체크표시로 성공여부를 표시해줄 수 있습니다.
젠킨스 파이프라인에서는 post를 이용해 어떠한 단계 후 성공하거나 실패 했을 때 어떠한 행위를 할 수 있도록 post를 제공해줍니다. 리눅스 쉘 상에서 post를 보내야 하므로 curl을 사용하여 토큰값을 포함해 보내줍니다. 빌드가 성공하고 GITHUB API를 이용해 빌드의 성공 여부를 GITHUB에 보내주는 것까지 완료했다면 CI 과정은 완료된 것입니다.
5. 배포서버로 빌드파일 전송
이렇게 코드의 지속적 통합을 완료하고 나면 서버에 jar파일을 배포해야 합니다. 요즘은 보통 CD에 Docker를 이용하지만 직접 jar파일을 배포해보고 싶어서 쉘에서 구현해봤습니다.
scp를 이용하여 빌드 완료된 jar파일을 젠킨스 서버에서 배포 서버로 전송합니다. 이 과정에서는 ssh 공개 키 설정이 양쪽 서버에서 완료되어야 scp 파일 전송이 가능합니다. (id_rsa.pub , authorized_keys를 이용하여)
6. 젠킨스 서버에서 ssh로 배포서버 접속 (pub키 설정 먼저해야함) & 젠킨스 서버에 저장해둔 빌드 스크립트를 배포 서버에서 실행
ssh 공개 키 설정이 완료되었다면 젠킨스 서버에서 배포서버로 접속한 뒤 젠킨스 서버에 저장해두었던 deploy.sh 배포 스크립트를 실행해줄 수 있습니다.
만약 현재 배포가되어 실행되고 있는 어플리케이션이 있다면 이를 pgrep -f make-delivery 로 백그라운드로 돌아가는 어플리케이션을 확인하여 이를 kill -15로 중단시켜줍니다. 만약 돌아가는 어플리케이션이 없다면 그냥 배포를 새로 시작해주면 됩니다.
또한 jenkins pipeline내에서 스크립트가 실행되는 것이니 터미널에서 백그라운드로 실행시켜주어야 합니다. 따라서 nohup java~~ >> nohupt.out 2>&1 & 로 실행시켜줍니다.
이 과정까지 완료되었다면 CI/CD 까지 전부 구현이 완료된 것입니다. CD쪽은 도커를 이용하는 것이 여러모로 좋은 방법이니 이는 다음 글에서 작성하겠습니다.
CI/CD 과정을 적용한 전체 파이프라인 Jenkinsfile은 아래 github url에서 확인하실 수 있습니다.
github.com/f-lab-edu/make-delivery/blob/develop/Jenkinsfile
프로젝트 url
github.com/f-lab-edu/make-delivery
참고
git-scm.com/book/ko/v2/GitHub-GitHub-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8C%85
'make-delivery 프로젝트' 카테고리의 다른 글
[#7] TransactionManager가 DataSource정하는 로직을 늦추기 - LazyConnectionDataSourceProxy (1) | 2020.12.05 |
---|---|
[#8] Mysql Replication - Spring에서 Master/Slave 이중화 with Docker (1) | 2020.12.03 |
[#5] 성능을 위해 Redis keys 대신 scan 이용하기 (0) | 2020.10.25 |
[#4] Spring에서 중복되는 로그인 체크 기능을 AOP로 분리하기 (0) | 2020.10.16 |
[#3] 정확히 트랜잭션이 롤백 되었을 때 장바구니를 복원하기 -TransactionSynchronization afterCompletion (Rollback hook) (0) | 2020.10.15 |
댓글