0️⃣들어가기 전
이 글은 프로젝트를 완성한 뒤, 프로젝트를 직접 배포하는 과정을 기록하는 글입니다.
위와 같이 Ec2로 서버를 구성하고 Github, Jenkins, Docker를 사용해 CI/CD를 구축해보겠습니다.
현재 Spring boot 서버로 통합하여 구현하긴 했지만, mysql과 swagger 서버도 각자 분리하는 것이 더 바람직합니다.
✔️ 플로우
Jenkins 서버
Git clone → Gradle 빌드 → Dockerfile로 도커 이미지를 빌드 → Docker Hub에 Image Push
Spring boot 서버
Docker Image pull → docker compose up
1️⃣ 인스턴스 생성
jenkins 서버와 개발서버(spring boot 서버)를 위해 총 2가지 인스턴스를 생성해야 합니다.
인스턴스를 생성한 뒤 포트를 허용해주기 위해서 각 인스턴스마다 인바운드 규칙을 수정해주어야 합니다.
1. 인바운드 규칙 및 용량 설정
- Jenkins 인스턴스
- 보안그룹의 인바운드 규칙에 사용자 지정 TCP로 8080(젠킨스 포트) 추가
- storage 용량을 30gb로 (build시 용량 부족오류 발생 방지)
- Spring boot 인스턴스
- 보안그룹의 인바운드 규칙에 사용자 지정 TCP로 8080(spring boot 포트) 추가
2. 인스턴스 접속
인스턴스에 접속하려면, pem키를 발급받은 뒤 사전 설정을 해야합니다.
✔️ pem(키페어)의 권한 변경
$ chmod 600 {키페어파일}
- chmod: 파일 모드 변경 (읽기/쓰기/실행)
- 600 : 나에게만 읽기/쓰기 권한
✔️ pem으로 인스턴스 접속
$ ssh -i {키 페어 파일 경로} {사용자 이름}@{인스턴스의 IPv4 주소나 도메인}
2️⃣ Jenkins 서버 설정
pem키를 통해 인스턴스에 접속한 뒤 다음 설정을 진행합니다.
1. 필요 패키지 설치
$ sudo apt update
$ sudo apt upgrade
$ sudo apt install build-essential
$ sudo apt install apt-transport-https ca-certificates curl software-properties-common
2. docker 설치
$ sudo wget -qO- <https://get.docker.com/> | sh
$ sudo systemctl start docker
$ sudo systemctl enable docker
✔️docker group 설정
sudo를 사용하지 않고 docker 명령어를 사용하기 위해서는 현재 사용자계정을 docker group에 포함시켜주면 됩니다. sudo는 관리자의 권한을 사용하기 위한 명령어이므로, 꼭 필요한 계정에만 docker group을 설정해주어야 합니다.
$ sudo usermod -aG docker ${USER}
$ id -nG
3. jenkins 설치
$ docker pull jenkins/jenkins:lts
$ 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
- ssh 설정을 위해 볼륨을 마운팅해놓습니다.
4. jenkins 접속
1) 브라우저에서 {ec2 IPv4주소}:8080 에 접속합니다.
접속하면 비밀번호를 입력하는 창이 나타납니다.
$ docker logs jenkins
비밀번호를 입력한 뒤, install suggested plugins를 클릭해 설치합니다.
2) 계정 생성
계정 생성 후 메인보드가 나타나면 성공입니다.
3️⃣ Jenkins와 Github 연동
1. SSH Key 생성
Jenkins 외부에서 서버에 접근할 수 있도록 public key와 private key를 설정해주어야합니다.
SSH 키에 대한 설명은 다음에서 간단하게 정리하였습니다. SSH Key 동작원리
✔️ SSH Key 생성
$ ssh-keygen
- Container 밖에서 ssh 키를 생성하면 Jenkins Container를 생성할 때 미리 마운팅해놓았던 볼륨 경로에 연결됩니다. (호스트 /home/ubuntun/.ssh → 컨테이너 /root/.ssh)
- EC2에 접속하면 기본 유저가 ubuntu이기 때문에 /home/ubuntu/.ssh에 프라이빗키(id_rsa 또는 id_ed25519)와 퍼블릭키(id_rsa.pub 또는 id_ed25519.pub)가 생성됩니다.
✔️ SSH Key 확인
$ cat /home/ubuntu/.ssh/id_rsa.pub 또는 cat id_ed25519.pub # public key 확인
$ cat /home/ubuntu/.ssh/id_rsa 또는 cat id_ed25519 # private key 확인
2. github에 Jenkins public key를 deploy key로 등록
✔️ github에서 등록
- github repository → settings → deploy keys → add deploy key에 접속합니다.
- key 부분에 위 명령어를 통해 확인한 ssh public key를 입력합니다.
3. Jenkins에 github의 credentials 등록
github에서 public key를 등록했으므로, Jenkins 서버에서 private key를 등록해 정보를 가져올 수 있도록 합니다.
✔️ jenkins에서 github이 사용할 private key 등록
- jenkins 관리 → security → credentials에 접속합니다.
- Store Jenkins에 Domain이 (global)인 화살표를 눌러 Global credentials (unrestricted)로 이동합니다.
- 왼쪽 메뉴의 Add credentials를 눌러 credentials를 추가합니다.
- kind: SSH Username with private key
- id: pipeline 작성 시 CredentialsId로 식별할 수 있는 값을 설정합니다. 저는 github으로 등록했습니다.
- private key에 enter directly 체크 Jenkins 서버의 SSH private key를 입력합니다.
4️⃣ Jenkins와 docker hub 연동
1. Jenkins에서 docker, docker pipeline 플러그인 설치
jenkins 관리 → plugins → available plugins → docker, docker pipeline을 설치합니다.
2. Jenkins에서 docker hub의 credentials 등록
jenkins 관리 → security → credentials에 접속합니다. docker hub에 대한 정보가 필요하기때문에, 없다면 가입이 필요합니다.
- kind: Username with password
- username: 본인 docker hub의 username(email이 아닙니다!)
- id: pipeline에서 식별할 수 있는 값을 입력합니다. 저는 dockerhub로 등록하였습니다.
- password: 본인 docker hub의 비밀번호
3. Jenkins 컨테이너 내부에서 docker.sock 권한 추가
pipeline을 작성하면 Jenkins 컨테이너 내부에서 docker 명령어를 사용해야 하는데, 컨테이너 내부에는 docker가 설치되어있지 않습니다.
이 때 docker container에서 또 docker container를 추가하기보다는, 호스트인 container의 docker.sock의 권한만 추가하면 사용할 수 있습니다.
✔️ 볼륨 마운트 확인
이는 Jenkins container를 가동할 때 아래의 명령어처럼 외부 docker volume을 마운트해놓았기 때문에 가능합니다.
$ 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
✔️ docker.sock 접근 허용
아래의 명령어로 Jenkins container에 접속한 뒤 docker.sock에 대한 접근을 허용해줍니다.
$ docker exec -it jenkins bash
# sudo 설치 (docker 컨테이너에서는 sudo 명령어를 따로 설치해주어야합니다)
$ apt-get update && apt-get install -y sudo
$ adduser --disabled-password --gecos "" user \\
&& echo 'user:user' | chpasswd \\
&& adduser user sudo \\
&& echo 'user ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
$ sudo chmod 666 /var/run/docker.sock
✔️ 잘 안되는 경우
하지만 저는 이게 잘 먹히지 않아서 container 내부에 docker를 설치해주었습니다. 참고: 2️⃣ Jenkins 서버 설정
5️⃣ Jenkins 와 Spring boot Server 연동
Jenkins서버가 Spring Boot 서버에서 도커 이미지를 Pull해서 실행하게 하기 위해 필요합니다.
Jenkins Pipeline Script에서 SSH를 사용하여 Spring Boot Server의 명령어 실행을 할 수 있도록 합니다.
1. Jenkins에서 SSH Agent 플러그인 설치
2. Spring Boot 서버에서 Jenkins public key 등록
Spring boot에서 Jenkins서버의 접속을 허용해주기 위해서 필요합니다.
# Jenkins Server에서 public key 확인
$ cat /home/ubuntu/.ssh/id_ed25519.pub
# Spring Boot Server에 Jenkins Server public key 추가
$ vi /home/ubuntu/.ssh/authorized_keys
원래 있던 authorized_keys 내용을 건드리면 ssh 접속 오류가 날 수 있으므로 주의해야합니다.
잘 되던 pem키로 접속이 안되는 이슈 발생중간에 빌드하면서 자꾸 실패해서그런지, 잘 접속되던 pem key로 접속하려고 하니까 Permission Denied(Public Key) 오류가 발생했습니다.
authorized_keys도 다시 변경해보고 새로 keygen을 발급해서 변경해봐도 효과가 없던 찰나에, 아래처럼 권한을 다시 주니까 성공했습니다.
시도1. 파일 재생성 & 복사
$ echo 'ssh-rsa {프라이빗키}' >> /home/ubuntu/.ssh/authorized_keys
시도2. 권한 부여 -> 성공
$ sudo chown root:root /home
$ sudo chmod 755 /home
$ sudo chown ubuntu:ubuntu /home/ubuntu -R
$ sudo chmod 700 /home/ubuntu /home/ubuntu/.ssh
$ sudo chmod 600 /home/ubuntu/.ssh/authorized_keys
참고 : https://repost.aws/knowledge-center/ec2-linux-fix-permission-denied-errors
2. Jenkins에 Spring Boot서버 SSH 등록
앞서 github Credentials를 등록했던것과 마찬가지로 등록합니다. (참고: ✔️ jenkins에서 github이 사용할 private key 등록)
동일한 Credentials를 사용해도 되지만, 서비스를 구분하기 위해 추가로 등록하였습니다.
6️⃣ Jenkins와 Git webhook 연동
Git의 특정 repository에서 push event가 발생했을 때 Jenkins build가 실행되게끔 구현하기 위해서 pipeline과 github webhook을 연동해야합니다.
1. Jenkins에서 Github Integration Plugin 설치
Jenkins 대시보드 > Jenkins 관리 > 플러그인 관리 > 설치 가능 > Github Integration 플러그인을 검색하고 설치 및 재실행합니다.
2. Jenkins pipeline 설정
- Github project 설정Pipeline 구성 화면 > General 영역에서 Github project를 선택한다. Project url에 본인의 Github Repository Url을 입력한다. 이 때 Repository Url은 Clone 시 사용하는 HTTPS Url(.git으로 끝남)을 입력합니다.
- Build Triggers 설정Pipeline 구성 화면 > Build Triggers 영역에서 GitHub hook trigger for GITScm polling을 선택합니다.
3. 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 설정 유지 Add webhook 버튼을 눌러 Webhook을 추가 → 목록에서 녹색 체크 아이콘이 생성되면 성공입니다.
7️⃣ Jenkins Pipeline 작성
pipeline {
agent any
environment {
imagename = '{docker hub에 올린 spring boot image}'
registryCredential = 'dockerhub'
dockerImage = ''
}
stages {
stage('Git Clone') {
steps {
echo 'Cloning Repository'
git url: 'git@github.com:{git ssh주소}',
branch: 'prd',
credentialsId: 'github'
}
post {
success {
echo 'Successfully Cloned Repository'
}
failure {
error 'This pipeline stops here...'
}
}
}
stage('Build Gradle') {
steps {
echo 'Build Gradle'
dir('.') {
sh 'chmod +x gradlew'
sh './gradlew clean build -x test'
}
}
post {
failure {
error 'This pipeline stops here...'
}
}
}
stage('Build Docker') {
steps {
echo 'Build Docker'
script {
dockerImage = docker.build("${imagename}")
}
}
post {
failure {
error 'This pipeline stops here...'
}
}
}
stage('Push Docker') {
steps {
echo 'Push Docker'
script {
docker.withRegistry('', registryCredential) {
dockerImage.push()
}
}
}
post {
failure {
error 'This pipeline stops here...'
}
}
}
stage('Docker Run') {
steps {
echo 'Pull Docker Image & Docker Image Run'
sshagent (credentials: ['ssh']) {
sh "ssh -o StrictHostKeyChecking=no ubuntu@{서버IP} 'docker pull ${imagename}'"
sh "ssh -o StrictHostKeyChecking=no ubuntu@{서버IP} 'docker compose -f /home/ubuntu/spring/compose/docker-compose.yml up --build -d'"
sh "ssh -o StrictHostKeyChecking=no ubuntu@{서버IP} 'docker image prune -f'"
}
}
}
}
}
- docker-compose로 docker를 빌드합니다.
- 컨테이너의 볼륨 설정을 통해서 application.yml을 따로 관리할 수 있도록 설정하였습니다.
'백엔드 Backend > 서버 Server' 카테고리의 다른 글
Spring Cloud Config - 분산 시스템 설정 관리 (0) | 2025.01.14 |
---|---|
Eureka + Spring Cloud Gateway 구현하기 (0) | 2025.01.07 |
API 게이트웨이 서비스 - (2) Spring Cloud Gateway (0) | 2025.01.06 |
API 게이트웨이 서비스 - (1) Netfilx Zuul (1) | 2025.01.06 |
Spring Boot 프로젝트를 Docker 이미지로 빌드하기 (1) | 2024.08.02 |