배포 개요
도커와 젠킨스로 CICD를 구축하고, EC2로 백엔드 서버와 프론트 서버를 배포해보자.
초기에는 백엔드, 프론트, 젠킨스 각각의 EC2를 생성해서 배포를 진행하였는데, 비용 문제로 프론트 서버는 추후에 S3로 변경했다.
서비스 아키텍처
- 백엔드 배포 : 도커 파일을 젠킨스가 감지해서 jar 파일 생성하여 EC2로 배포한다.
- 프론트 배포 : 마찬가지로 진행한다.
구축 과정
1. 젠킨스 서버 EC2 설정
- jenkins-server EC2 생성
- AMI : Ubuntu Server 22.04 LTS (HVM), SSD Volume Type
- 아키텍처 : 64비트 x86
- Quick start : ubuntu
- 인스턴스 유형 : t2.micro
- 키페어 생성 후 저장 (jenkins-key-pair)
- 네트워크 설정 : 보안그룹 생성, SSH 트래픽 허용
- 스토리지 구성 : 1x30, gp3
- 인바운드 규칙
- 8080 열어주기
- EC2 내부에 도커 설치하기
sudo apt update
sudo apt upgrade
sudo apt install build-essential
$ sudo apt update
$ sudo apt install apt-transport-https ca-certificates curl software-properties-common
#자동 설치 스크립트 활용
$ sudo wget -qO- https://get.docker.com/ | sh
#Docker 서비스 실행하기 및 부팅 시 자동 실행 설정
$ sudo systemctl start docker
$ sudo systemctl enable docker
#Docker 그룹에 현재 계정 추가
$ sudo usermod -aG docker ${USER}
$ sudo systemctl restart docker
#Docker 설치 확인
$ docker -v
2. 도커로 젠킨스 설치
- 프리티어 메모리 부족 문제가 발생할 수 있어 SWAP으로 메모리 할당해줍니다.
- Jenkins 이미지 파일을 내려받아서 컨테이너를 실행해줍니다.
// 메모리 상태 확인
$ free -h
// swap 파일을 생성해준다. (메모리 상태 확인 시 swap이 있었지만 디렉토리 파일은 만들어줘야한다.)
$ sudo mkdir /var/spool/swap
$ sudo touch /var/spool/swap/swapfile
$ sudo dd if=/dev/zero of=/var/spool/swap/swapfile count=2048000 bs=1024
// swap 파일을 설정한다.
$ sudo chmod 600 /var/spool/swap/swapfile
$ sudo mkswap /var/spool/swap/swapfile
$ sudo swapon /var/spool/swap/swapfile
// swap 파일을 등록한다.
$ sudo vim /etc/fstab
파일이 열리면 해당 파일 아래쪽에 하단 내용 입력 후 저장
/var/spool/swap/swapfile none swap defaults 0 0
// 메모리 상태 확인
$ free -h
# 1 명령어 안되면 2 명령어 실행
# 1
sudo docker run -d -p 8080:8080 -p 50000:50000 -v /jenkins:/var/jenkins -v /home/ubuntu/.ssh:/root/.ssh -v /var/run/docker.sock:/var/run/docker.sock --name jenkins -u root jenkins/jenkins:lts
# 2
sudo docker run --name jenkins -p 8080:8080 -p 50000:50000 -d jenkins/jenkins:lts
#Jenkins 이미지 파일 내려받기(lts 버전)
$ sudo docker pull jenkins/jenkins:lts
#내려받아진 이미지 확인
$ sudo docker images
#Jenkins 이미지를 Container로 실행
sudo docker start jenkins
#돌아가고 있는 Container 확인
sudo docker ps
3. 젠킨스 접속
- { 젠킨스 EC2 퍼블릭 IP }:8080 으로 접속하면 젠킨스 로그인 화면이 뜹니다.
- ` $docker logs jenkins ` 명령어로 초기 패스워드를 얻을 수 있습니다.
- Install suggested plugins로 플러그인을 설치하고 젠킨스 계정 설정을 해줍니다.
4. 젠킨스 깃허브 연동
- ssh 키 생성
- Github Deploy Key 등록
- Github Repository > Settings > Deploy Keys > Add deploy key로 접속
- Title은 Jenkins로 지어 주고(마음대로 해도 됨), Key 부분에 id_rsa.pub에 들어있는 public key 값을 넣어준다.
- 만약 깃허브 레포가 프라이빗이라면 자신의 깃 토큰을 크리덴셜로 주입해주고 파이프라인에 추가해주면 된다.
#ssh 키 생성(/home/ubuntu/.ssh)
ssh-keygen
#Jenkins 컨테이너에 SSH 키 복사
sudo docker cp /home/ubuntu/.ssh/id_rsa.pub jenkins:/root/.ssh/
# 키 값 확인
$ cd /home/ubuntu/.ssh
$ cat id_rsa.pub
ssh-rsa AAAAB...cHovq0= ubuntu@ip-172-31-38-163
5. 젠킨스 도커허브 연결
- Docker Plugin 설치
- Jenkins 대시보드 > Jenkins 관리 > 플러그인 관리 > 설치 가능 > Docker 검색 > Docker, Docker Pipeline 플러그인 설치 및 재실행
- Docker Hub Credentials 등록
- Jenkins 대시보드 > Jenkins 관리 > Manage Credentials > Credentials에 접속
- Store Jenkins에 Domain이 (global)인 화살표를 눌러 Global credentials (unrestricted)로 이동
- credentials를 추가
- Kind : Username with password
- Username : 본인의 Docker Hub ID
- Password : 본인의 Docker Hub Password
- ID : docker-hub ← Pipeline Script 작성 시 credentialsId로 사용됨
- Jenkins Container 내부에 Docker 설치
- Jenkins Pipeline에서 Docker 명령어를 사용할 수 있도록 Jenkins Container 내부에 Docker를 설치해야 함
#Jenkins Container에 접속
sudo docker exec -it jenkins bash
sudo docker exec -u root -it jenkins bash #루트권한으로 접속
# sudo, vi, wget 설치
apt update
apt install -y sudo vim wget
# 도커 설치 "2. 도커로 젠킨스 설치" 참고해서 설치
6. 젠킨스 + 프/백 서버 SSH 연결
편의를 위해 프론트 서버로 가정후 설명 (백도 동일하게 진행)
- SSH Agent Plugin 설치
- Jenkins 대시보드 > Jenkins 관리 > 플러그인 관리 > 설치 가능 > SSH Agent 플러그인 설치
- front-server EC2 생성 후 접속
- EC2 안에 도커 깔아주기
- front-server의 .ssh/authorized_keys 파일에 Jenkins Server의 public key 추가
- ‼️ 퍼블릭 키 추가할 때 기존 퍼블릭키에 줄바꿈 하여 젠킨스 서버 퍼블릭키 추가하기. 빈 줄 없어야함!!
- Jenkins Credentials 등록
- Jenkins 대시보드 > Jenkins 관리 > Manage Credentials > Credentials에 접속
- 왼쪽 메뉴의 Add credentials를 눌러 credentials를 추가
- Kind : SSH Username with private key
- ID : ssh ← Pipeline Script 작성 시 credentialsId로 사용됨
- Username : ubuntu ‼️내 ec2의 username 확인후 입력
- Private KeyEnter : directly 체크 -> private key 입력
- 여기서 private key는 Jenkins Server에서 생성한 id_rsa
- Store Jenkins에 Domain이 (global)인 화살표를 눌러 Global credentials (unrestricted)로 이동
- ‼️ BEGIN, END 줄도 같이 넣어놔야함
- ` -----BEGIN OPENSSH PRIVATE KEY----- ... -----END OPENSSH PRIVATE KEY----- `
# jenkins-server에서 public key 확인
$ cat /home/ubuntu/.ssh/id_rsa.pub
ssh-rsa AAA...fx6Lc= ubuntu@ip-...
ssh-rsa AAAA...CcHovq0= ubuntu@ip-...
# front-server에 Jenkins Server public key 추가
$ vi /home/ubuntu/.ssh/authorized_keys
ssh-rsa AAAAB3N....vq0= ubuntu@ip-...
7. 젠킨스 + 깃허브 Web Hook 연결
- 새로운 Item 만들기
- pipeline 선택
- Github Integration Plugin 설치
- Jenkins 대시보드 > Jenkins 관리 > 플러그인 관리 > 설치 가능 > Github Integration 플러그인 설치
- Jenkins Pipeline 설정
- Github project 설정
- Pipeline 구성 화면 > General 영역에서 Github project
- Project url에 Github Repository Url을 입력 (HTTPS Url)
- Build Triggers 설정
- Pipeline 구성 화면 > Build Triggers 영역에서 GitHub hook trigger for GITScm polling을 선택
- Github project 설정
- Github Webhook 추가
- Github Repository에서 Settings > Webhooks > Add Webhook 을 눌러 Webhook을 추가
- Payload URL : [Jenkins Server URL]:[Jenkins Server 포트]/github-webhook/
- Content type : application/x-www-form-urlencoded
- 나머지는 default
8. 젠킨스 파이프라인, 도커파일, 빌드
프론트 파이프라인
pipeline {
agent any
environment {
// 환경 변수 설정
DOCKER_IMAGE = '{도커 아이디/도커 이미지 이름}'
DOCKER_TAG = 'latest'
CONTAINER_NAME = '{컨테이너 이름}'
EC2_HOSTNAME = '{프론트 EC2 IP}'
}
stages {
stage('Checkout') {
steps {
// GitHub 메인 브랜치에서 소스 코드 체크아웃
git branch: 'main', url: '{.git으로 끝나는 깃허브 url}'
}
}
stage('Build & Push Docker Images') {
steps {
script {
// Docker Hub에 로그인
withCredentials([usernamePassword(credentialsId: 'docker-hub', usernameVariable: 'DOCKER_USERNAME', passwordVariable: 'DOCKER_PASSWORD')]) {
sh 'echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin'
}
// Docker 이미지 빌드 및 푸시
sh 'docker build -t $DOCKER_IMAGE:$DOCKER_TAG ./'
sh 'docker push $DOCKER_IMAGE:$DOCKER_TAG'
}
}
}
stage('Deploy to EC2') {
steps {
// Docker 이미지를 실행하기 위해 SSH 에이전트를 사용합니다.
sshagent(credentials: ['ssh']) {
// SSH를 통해 EC2 인스턴스에 접속하여 Docker 이미지 실행
sh """
ssh -o StrictHostKeyChecking=no ubuntu@$EC2_HOSTNAME '
sudo docker pull $DOCKER_IMAGE:$DOCKER_TAG
sudo docker stop $CONTAINER_NAME || true
sudo docker rm $CONTAINER_NAME || true
sudo docker run -d --name $CONTAINER_NAME -p 80:80 $DOCKER_IMAGE:$DOCKER_TAG
'
"""
}
}
}
}
// 디스코드 웹 훅 연결
post {
success {
withCredentials([string(credentialsId: 'Discord-Webhook', variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
link: env.BUILD_URL, result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 성공",
webhookURL: "$DISCORD"
}
}
failure {
withCredentials([string(credentialsId: 'Discord-Webhook', variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
link: env.BUILD_URL, result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 실패",
webhookURL: "$DISCORD"
}
}
}
}
백엔드 파이프라인
- 환경변수 주입을 하드코딩된 yml 파일을 복사해서 주입했습니다.
pipeline {
agent any
environment {
// Docker 및 이미지 관련 환경 변수
DOCKER_IMAGE = '{도커 아이디/도커 이미지 명}'
DOCKER_TAG = 'latest'
CONTAINER_NAME = '{도커 컨테이너 명}'
EC2_HOSTNAME = '{백엔드 서버 EC2 IP}' // 백엔드 서버
}
stages {
stage('Checkout') {
steps {
// GitHub 메인 브랜치에서 소스 코드 체크아웃
git branch: 'main', url: '{.git으로 끝나는 깃허브 url}'
}
}
stage('Add Env') {
steps {
dir('.') {
withCredentials([file(credentialsId: 'application', variable: 'application')]) {
sh 'cp ${application} src/main/resources/application.yml'
}
}
}
}
stage('Build Gradle') {
steps {
echo 'Building Gradle'
dir('.') {
sh './gradlew clean build'
}
}
post {
failure {
error 'Gradle build failed. Stopping the pipeline...'
}
}
}
stage('Build & Push Docker Images') {
steps {
script {
// Docker Hub에 로그인
withCredentials([usernamePassword(credentialsId: 'docker-hub', usernameVariable: 'DOCKER_USERNAME', passwordVariable: 'DOCKER_PASSWORD')]) {
sh 'echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin'
}
// Docker 이미지 빌드 및 푸시
sh 'docker build -t $DOCKER_IMAGE:$DOCKER_TAG ./'
sh 'docker push $DOCKER_IMAGE:$DOCKER_TAG'
}
}
}
stage('Deploy to EC2') {
steps {
// Docker 이미지를 실행하기 위해 SSH 에이전트를 사용합니다.
sshagent(credentials: ['ssh']) {
// SSH를 통해 EC2 인스턴스에 접속하여 Docker 이미지 실행
sh """
ssh -o StrictHostKeyChecking=no ubuntu@$EC2_HOSTNAME '
sudo docker pull $DOCKER_IMAGE:$DOCKER_TAG
sudo docker stop $CONTAINER_NAME || true
sudo docker rm $CONTAINER_NAME || true
sudo docker run -d --name $CONTAINER_NAME -p 8080:8080 $DOCKER_IMAGE:$DOCKER_TAG
'
"""
}
}
}
}
post {
success {
withCredentials([string(credentialsId: 'Discord-Webhook', variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
link: env.BUILD_URL, result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 백엔드 성공",
webhookURL: "$DISCORD"
}
}
failure {
withCredentials([string(credentialsId: 'Discord-Webhook', variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
link: env.BUILD_URL, result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 백엔드 실패",
webhookURL: "$DISCORD"
}
}
}
}
'인프라' 카테고리의 다른 글
[AWS,Docker] No space left on device 오류 + cron으로 docker미사용 컨테이너 삭제 (0) | 2024.09.22 |
---|---|
[인프라] Vue.js, React 배포 이후 새로고침하면 404 Not Found (0) | 2024.07.11 |
[AWS] 로드밸런서, 도메인 없이 EC2 서버 HTTPS 연결 (3) | 2024.07.03 |
[Docker] docker-compse로 배포 서버에서 redis 통신하기 (1) | 2024.06.26 |
[Jenkins] 젠킨스 Built-In Node 오프라인 문제 (0) | 2024.06.26 |