이번 포스팅에서는 Jenkins
와 Docker
를 사용하여 Evertrip
프로젝트를 CI/CD 배포하는 시간을 가져보도록 하겠습니다.
Github Actions
를 이용한 CI/CD는 아래 포스팅을 참고하시면 좋을 것 같습니다.
여러 가지 CI/CD 관련 툴 중에서 AWS EC2 서버를 이용해 무료로 사용할 수 있으며, 다른 툴에 비해 활용 사례와 레퍼런스가 다양한 Jenkins를 선택하였습니다.
또한 Docker를 사용한 이유는 OS 환경에 종속적이지 않도록 하기 위함이었습니다. 로컬 서버는 윈도우 OS를 사용하였지만 배포 서버는 ubuntu OS였기에 애플리케이션을 OS 환경에 종속적이지 않게 Docker를 활용하여 애플리케이션 이미지를 빌드하고 Docker hub저장소에 올리고 배포 서버에서 해당 이미지를 다운받고 실행하도록 구현하였습니다.
이제 실제 CI/CD
를 구축해보도록 하겠습니다. Jenkins
는 Github Actions
와 다르게 Github
와 직접적으로 연동되어 있지 않기 때문에 Github
의 코드 변동 사항이 발생할 경우 Jenkins
에 알리기 위해 부가적인 설정을 따로 해줘야합니다.
아래와 같은 순서로 진행해보도록 하겠습니다.
먼저 Github Repository
에서 Token을 발급 받아야 합니다. 해당 Token을 이용하여 Jenkins
에서 Github Repository
에 접근하여 코드를 가져오고 빌드 과정을 수행하게 됩니다.
Settings -> Developer settings -> Personal access tokens -> Generate new token (classic)
를 선택하셔서 Token을 발급 받습니다.
repo
, admin:org
, admin:repo_hook
을 선택해줍니다. 그리고 발급 받은 Token은 따로 저장해줍니다.
다음은 Jenkins
에서 Github
과 Docker
및 SSH
접속 관련 플러그인을 설치하여야 합니다.
- Generic Integration
- Generic Webhook Trigger
- GitHub API Plugin
- Docker Pipeline
- SSH Agent Plugin
다음은 Jenkins Credentials
생성입니다. Jenkins 관리 -> Manage Credentials
를 클릭하시고 Stores scoped to Jenkins > global > Add Credntials
선택하여 생성해주시면 됩니다.
각각 Docker hub
, SSH
, GitHub
에 접근하기 위한 세 가지의 Credentials
로 생성하였습니다.
먼저 Docker hub
에 접근하기 위한 Credentials
를 생성하겠습니다. Docker hub
사이트에 접속하여 로그인 후 아래와 같이 Account settings
를 클릭합니다.
Security
태그에 들어가셔서 New Access Token
을 클릭해줍니다.
Read
,Write
,Delete
권한을 설정한 후 Access Token
을 발급 받습니다.
이후 아래와 같이 Jenkins
의 Credentials
를 생성해줍니다. Password
에 위에서 발급 받은 Token을 넣으시면 됩니다.
다음으로 접근하려는 EC2 서버의 SSH 인증 정보를 Jenkins
에 등록해줍니다.
Kind에서 SSH Username with private Key
를 선택하시고 ID를 입력하신 후 Username은 접근하려는 EC2 서버의 사용자 이름을 기입하시면 됩니다. Private Key에는 SSH의 Private Key인 pem
파일의 전체 내용을 넣어주시면 됩니다.
다음으로 Gitgub Repository에 접근할 수 있도록 아래의 Credentials
를 생성해줍니다.
Kind는 Username with password
로 선택해주시고 Username에는 Github
의 ID를 넣어주시면 됩니다. 위에서 발급 받은 Github Access Token
값을 Password에 기입해주시면 됩니다.
다음은 Jenkins Pipeline
을 설정해보도록 하겠습니다. 새로운 아이템 -> Pipeline
으로 생성하시면 됩니다.
General -> GitHub project -> Project url
에서 프로젝트의 GitHub Repository
경로를 기입해줍니다.
Build Triggers 탭에서 GitHub hook trigger for GITScm polling
을 체크해줍니다.
Pipeline 탭에서 아래와 같이 설정해줍니다. Repository URL
에 프로젝트의 GitHub Repository
경로를 기입하고 Credentials
에 위에서 생성한 Github Credentials
를 선택해줍니다.
Branch Specifier
에서 트리거가 될 브랜치를 선택해줍니다. 저희 프로젝트에서는 main
브랜치에 코드 변경 사항이 생길 경우 Jenkins
에 알려주기 위해 */main
으로 설정해주었습니다.
마지막으로 Script Path
에서 루트 프로젝트에서 Jenkinsfile
의 경로를 기입해줍니다.
다음은 Jenkins
에서 사용할 환경변수를 설정해보도록 하겠습니다.
Jenkins 관리 -> System -> Global properties
에서 설정할 수 있습니다. 저는 EC2 서버 호스트 주소와 Jasypt 비밀번호를 생성하였습니다.
다음은 Jenkinsfile
을 작성해보도록 하겠습니다. GitHub Webhook
을 통해 Jenkins
에 코드 변경 사항에 대한 알림이 왔을 때 해당 Jenkinsfile
이 실행되게 됩니다. 루트 디렉토리 경로 하위에 Jenkinsfile
을 작성해주었습니다.
pipeline {
agent any
tools {
jdk 'JDK'
}
environment {
SPRING_PROFILES_ACTIVE = 'prod'
AWS_METADATA_DISABLED = 'true'
JASYPT_PASSWORD = "${JASYPT_PASSWORD}"
}
stages {
stage('Checkout') {
steps {
git branch: 'main', url: 'https://github.com/evertrip/evertrip-backend.git'
}
}
stage('Set Permissions') {
steps {
sh 'chmod +x gradlew'
}
}
stage('Build and Test') {
steps {
sh '''
echo "JASYPT_PASSWORD=${JASYPT_PASSWORD}"
./gradlew clean build --info --stacktrace -Djasypt.encryptor.password=${JASYPT_PASSWORD} -Dspring.profiles.active=${SPRING_PROFILES_ACTIVE} -Dcom.amazonaws.sdk.disableEc2Metadata=${AWS_METADATA_DISABLED}
'''
}
}
stage('Build Docker Image') {
steps {
script {
def image = docker.build('rlarkddnr1686/evertrip-image:latest')
}
}
}
stage('Push Docker Image') {
steps {
withCredentials([usernamePassword(credentialsId: 'dockerhub-jenkins', usernameVariable: 'DOCKERHUB_USERNAME', passwordVariable: 'DOCKERHUB_PASSWORD')]) {
script {
docker.withRegistry('https://index.docker.io/v1/', 'dockerhub-jenkins') {
sh '''
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
docker tag rlarkddnr1686/evertrip-image:latest rlarkddnr1686/evertrip-image:latest
docker push rlarkddnr1686/evertrip-image:latest
'''
}
}
}
}
}
stage('Deploy to EC2') {
steps {
withCredentials([sshUserPrivateKey(credentialsId: 'ec2-ssh-key-id', keyFileVariable: 'SSH_KEY')]) {
script {
sshagent(['ec2-ssh-key-id']) {
sh '''
#!/bin/bash
ssh -i ${SSH_KEY} -o StrictHostKeyChecking=no ubuntu@${EC2_HOST} << EOF
JASYPT_PASSWORD=${JASYPT_PASSWORD}
docker pull rlarkddnr1686/evertrip-image:latest &&
docker stop evertrip-container || true &&
docker rm evertrip-container || true &&
echo "Starting container with JASYPT_PASSWORD=\${JASYPT_PASSWORD}" &&
docker run -d --network host -p 8080:8080 --name evertrip-container -e JASYPT_PASSWORD=\${JASYPT_PASSWORD} -e SPRING_PROFILES_ACTIVE=prod -e DISABLE_EC2_METADATA=true rlarkddnr1686/evertrip-image:latest
'''
}
}
}
}
}
}
post {
success {
echo 'Pipeline succeeded!'
}
failure {
echo 'Pipeline failed!!'
}
}
}
위의 파이프라인 스크립트는 여러 단계를 거쳐 애플리케이션을 배포하게 됩니다. 각 스테이지는 아래와 같은 순서로 동작하게 됩니다.
Git 저장소의 main
브랜치에서 코드를 체크아웃하고 가져오게 됩니다.
gradlew
파일에 실행 권한을 부여합니다.
환경 변수와 함께 Gradle
명령을 사용하여 애플리케이션을 빌드하고 테스트합니다.
애플리케이션의 Docker
이미지를 빌드합니다. rlarkddnr1686/evertrip-image:latest
이미지를 빌드하게 됩니다.
위에서 생성한 DockerHub Credentials
를 이용하여 Docker Hub
에 로그인하고, Docker
이미지를 푸시합니다.
위에서 생성한 SSH Credentials
를 사용하여 배포하려는 EC2 인스턴스에 접속하고, 기존 컨테이너를 중지하고 제거한 후, 새로운 Docker
이미지를 다운받아 실행합니다. 환경 변수를 설정하여 컨테이너를 실행하게 됩니다.
파이프라인의 성공 유무에 따른 메시지를 출력하게 됩니다.
다음으로 GitHub Repository Webhooks
를 설정해보도록 하겠습니다.
GitHub의 Repository -> Settings -> Webhooks -> Add webhook
를 클릭하여 Webhook
을 설정 해줍니다.
위의 사진과 같이 Payload URL
에 http://Jenkins주소/github-webhook/
를 기입하고 Content type
에 application/json
을 선택해줍니다.
이제 main
브랜치에 Pull Request
를 올려 테스트를 해보도록 하겠습니다. Pull Request
후 Webhook -> RecentDeliveries
를 확인해보니 Jenkins
에게 코드 변경 사항에 대한 알림이 제대로 간 것을 확인할 수 있습니다.
이제 Jenkins
로 가서 위에서 생성한 Pipeline이 제대로 동작하는지 확인해보도록 하겠습니다.
해당 빌드가 성공적으로 마친 것을 확인할 수 있습니다.
마지막으로 배포 서버의 주소로 API 명세를 확인할 수 있습니다.
이번 포스팅에서는 Jenkins + Docker
를 활용한 CI/CD 작업을 진행하였습니다. 해당 과정을 거치면서 로컬에서 작성한 개발 코드들의 배포 과정을 자동화하여 효율성을 높일 수 있었습니다.
CI/CD 파이프라인을 설정하는 데 다소 시간이 걸렸지만, 일단 완성되고 나니 자동화된 CI/CD가 수동 배포와 비교하여 훨씬 간편하고 효율적이라는 것을 직접 깨달을 수 있었습니다.
rungoat.log의 [CI/CD] Jenkins와 GitHub 연동하기 포스팅
[Jenkins] SSH 사용 - pipeline SSH Agent 포스팅
bagt13.log의 Jenkins와 Docker로 CI/CD pipeline 구축하기 포스팅