AWS EC2에서 Nginx + Docker Compose + Jenkins를 활용한 CI/CD 구축
젠킨스와 nginx를 통해서 배포 자동화를 구현해보려고 하였습니다.
여기에 관련해서 시행착오가 많았는데 정리하려고 합니다.
일단 구조는 다음과 같습니다.
1. aws ec2 instance jenkins 설치
2. docker compose 로 nginx, postgresql, certbot, spring 이미지 빌드 후 실행
이상하다고 생각할 수 있는데, 맞습니다.
1번 방법을 먼저 수행해봤고, 2번 방법으로 나중에 도입했기 때문에
보통은 jenkins도 docker compose로 띄워야 네트워크 이슈도 적고, 관리도 편하다는 것을 나중에 깨달았습니다.
AWS EC2 프리티어 기준입니다.
1. SWAP 메모리 설정
- 관련해서 이전에 포스팅을 한 적이 있습니다.
- https://iii.ad/67bfb7
2. Docker-compose, Docker 설치
- 필수 패키지 설치
sudo yum update -y # Amazon Linux 기준 (Ubuntu는 `apt update`)
sudo yum install -y docker git unzip
sudo systemctl enable docker && sudo systemctl start docker
sudo usermod -aG docker ec2-user # Docker 권한 추가
- Docker-compose 설치
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version # 설치 확인
3. Jenkins 설치
sudo yum install -y java-11-amazon-corretto # Jdk 설치 (저는 21이라 바꿔서)
wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key
sudo yum install -y jenkins
sudo systemctl enable jenkins && sudo systemctl start jenkins
4. Jenkins 비밀번호 확인
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
5. 젠킨스 접속 확인
- 기본은 http://EC2-PUBLIC-IP:8080
- 8080 포트를 인바운드 규칙에서 열어주어야 합니다.
6. 도커 컴포즈 작성
version: '3.8'
services:
db:
image: postgres:latest
container_name: postgres-db
restart: always
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
backend:
image: my-backend:latest
container_name: backend
restart: always
depends_on:
- db
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/${POSTGRES_DB}
SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER}
SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}
ports:
- "8080:8080"
nginx:
image: nginx:latest
container_name: nginx
restart: always
depends_on:
- backend
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
ports:
- "80:80"
- "443:443"
volumes:
postgres_data:
- 설명
- rds를 사용하지 않고, ec2 인스턴스 환경에 postgres db를 올렸습니다.
- 실무에서는 인스턴스 하나에 애플리케이션과 디비를 같이 설정한다는 것은 지양한다는 점 유의하시길 바랍니다.
- 각 컨테이너들은 aws ec2 인스턴스의 특정 디렉토리와 마운트를 해주었습니다.
7. Dockerfile 작성
FROM amazoncorretto:21.0.4-alpine3.18 AS builder
WORKDIR /app
COPY gradlew gradlew
COPY gradle gradle
COPY build.gradle build.gradle
COPY settings.gradle settings.gradle
RUN chmod +x gradlew && ./gradlew dependencies || echo "Ignore dependency error"
COPY . .
RUN ./gradlew build -x test
FROM amazoncorretto:21.0.4-alpine3.18
WORKDIR /app
COPY --from=builder /app/build/libs/Foody-0.0.1-SNAPSHOT.jar app.jar
CMD ["java", "-jar", "app.jar"]
1. 빌드 이미지 생성(builder 스테이지)
- amazoncorretto:21.0.4-alpine3.18(경량 Java 21 버전) 기반으로 빌드 컨테이너 생성
- WORKDIR /app으로 작업 디렉토리 설정
FROM amazoncorretto:21.0.4-alpine3.18 AS builder
WORKDIR /app
2. Gradle 관련 파일 복사
- Gradle Wrapper(gradlew), gradle 폴더, build.gradle, settings.gradle을 복사
- 이유: Gradle 캐시를 활용하여 빌드 속도를 높이기 위함
COPY gradlew gradlew
COPY gradle gradle
COPY build.gradle build.gradle
COPY settings.gradle settings.gradle
3. Gradle 의존성 다운로드 (캐싱 최적화)
- chmod +x gradlew → Gradle Wrapper 실행 권한 부여
- ./gradlew dependencies → 의존성만 미리 다운로드
- 실패해도 echo "Ignore dependency error"로 무시
RUN chmod +x gradlew && ./gradlew dependencies || echo "Ignore dependency error"
4. 전체 프로젝트 복사 및 빌드
- COPY . . → 프로젝트 전체 복사
- ./gradlew build -x test → 테스트 제외하고 빌드 수행 (build/libs/*.jar 생성됨)
FROM amazoncorretto:21.0.4-alpine3.18
WORKDIR /app
5. 2단계 (런타임 이미지 생성)
FROM amazoncorretto:21.0.4-alpine3.18
WORKDIR /app
- 빌드에 사용한 이미지를 그대로 사용하지 않고, 새 컨테이너를 만들고 실행 환경만 유지
6. 빌드된 Jar 파일 복사
COPY --from=builder /app/build/libs/Foody-0.0.1-SNAPSHOT.jar app.jar
- --from=builder → 이전 빌드 단계에서 생성된 JAR 파일을 가져옴
- /app/build/libs/Foody-0.0.1-SNAPSHOT.jar → app.jar로 복사
7. 컨테이너 실행 시 JAR 파일 실행
CMD ["java", "-jar", "app.jar"]
멀티스테이지 빌드란?
멀티스테이지 빌드는 빌드 과정과 실행 환경을 분리해서 컨테이너의 크기를 줄이고, 실행 환경을 최적화하는 방식입니다.
1. 첫 번째 FROM amazoncorretto:21.0.4-alpine3.18 AS builder
- 이 단계에서는 Gradle을 사용하여 애플리케이션을 빌드하는 역할을 합니다.
- Gradle을 실행하려면 JDK(Java Development Kit)가 필요합니다.
- 하지만 실행 환경에서는 JDK가 필요 없고, JRE(Java Runtime Environment)만 필요합니다.
- 즉, 빌드 전용 컨테이너라고 보면 됩니다.
2. 두 번째 FROM amazoncorretto:21.0.4-alpine3.18
- 실행을 위한 경량 컨테이너로, 불필요한 빌드 도구(Gradle 등)는 포함하지 않습니다.
- 오직 애플리케이션 실행을 위한 JAR 파일만 복사해서 실행합니다.
- 덕분에 컨테이너 크기가 작아지고, 실행 속도도 빨라집니다.
9. jenkins pipeline script 작성
왼쪽 상단의 + 새로운 Item을 클릭합니다.
- item name을 입력하고, pipeline을 선택합니다.
- github project는 다음과 같이 작성합니다.
- github에 있는 repository (public 기준)
Triggers 방법으로는 pipeline script 방식을 사용할 것이기 때문에 다음과 같이 체크해줍니다.(github webhook 연동)
- github의 repository에 변경사항이 있을 시 polling 당겨와서 trigger (빌드 수행)
jenkins pipeline script 작성
- 일반적으로 Pipeline script from SCM 으로 쉽게 사용할 수 있는데
- 저의 상황에서는 github repository에 jenkins file을 업로드를 안 하고 진행하였습니다.
- 즉, jenkinsfile을 업로드하지 않으면 저와 같이 pipeline script를 작성해야합니다. (기본적으로 jenkins file과 동일)
pipeline {
agent any
triggers {
githubPush() // 📌 GitHub Webhook을 통한 자동 빌드 트리거
}
stages {
stage('Checkout') {
steps {
git branch: 'main', url: 'https://github.com/beginner0107/Foody.git'
}
}
stage('Build') {
steps {
sh '''
chmod +x ./gradlew
./gradlew clean build -x test || exit 1
'''
}
}
stage('Docker Build & Deploy') {
steps {
sh '''
echo '[INFO] Backend 컨테이너만 재시작'
cd /home/ec2-user/ci-cd-project
docker-compose up -d --build backend
'''
}
}
}
}
해상 스크립트를 작성하면서 생긴 이슈들이 있는데 이후 설명하겠습니다.
- pipeline script는 다음과 같이 작성되어 있습니다.
- webhook 요청이 오면, 발생할 이벤트들이 stages 밑에 있는 작업들이라고 생각하면 됩니다.
- githubPush(): 빌드 트리거
- jenkins가 요청을 받게 되면 stages 별로 작업이 수행된 것을 확인할 수 있습니다.
10. Jenkins에서 빌드 수행
- 지금 빌드를 클릭합니다.
- 빨간색 X 표시 : 실패
- 초록색 체크 표시 : 성공
- 콘솔 출력에서는 어떤 부분에서 실패했는지 로그를 확인할 수 있습니다.
11. Webhook 설정
- 본인의 project > Settings > Code and automation > Webhooks
- Add webhook 선택
기입해야할 설정
- Payload URL * : http://본인도메인 or 포트:포트/gihub-webhook/
- 저는 nginx로 https 설정을 해두어서 https로 설정해두었습니다.
- Content type : application/json
- SSL verification: GitHub Webhook에서 SSL verification(SSL 검증) 설정은 Webhook이 데이터를 전송할 때 HTTPS 인증서를 검증할지 여부를 결정하는 옵션이라서, https 설정이 되어 있지 않으면 Disable을 체크합니다.
- Which events would you like to trigger this webhook?
- "Just the push event" (push 이벤트만 트리거)
- "Send me everything" (모든 이벤트 전송)
- "Let me select individual events" (개별 이벤트 선택)
- 저는 1번 push event가 있을 때를 선택했습니다.
- Add webhook 클릭
- Recent Delivers 를 누르면 다음과 같이 확인할 수 있습니다.
12. 404나 다른 오류 메시지가 발생되었을 때
- AWS의 보안그룹과 nginx 설정을 살펴봅니다.
- 8080포트로 젠킨스가 돌고 있으면 8080포트 규칙을 추가해야하고
- 80포트 or 443 포트로 nginx설정을 추가해놓았을 때는
- nginx 설정 스크립트를 수정해야 합니다.
- 요청이 왔을 때, jenkins로 리버스 프록시 되게 설정을 추가해야 합니다.
location /jenkins/ {
proxy_pass http://IP:PORT/jenkins/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port 443;
# Jenkins WebSocket 지원
proxy_http_version 1.1;
proxy_request_buffering off;
proxy_buffering off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_redirect off;
}
- https 설정을 하고 https://도메인/jenkins로 요청이 왔을 때 내부적으로 띄어져있는 jenkins 서비스로 포워딩해주는 설정을 추가하였습니다.
- 여기서 docker로 띄운 jenkins랑, ec2 instance 로 띄운 젠킨스는 설정이 다릅니다.
- 도커 호스트에서 Nginx를 실행하고, Jenkins도 직접 설치한 경우
- proxy_pass http://127.0.0.1:포트/jenkins/;
- 도커에서 Jenkins를 실행하고, Nginx도 도커 컨테이너로 실행한 경우
- proxy_pass http://컨테이너이름:포트/jenkins/;
- 도커에서 Jenkins를 실행하고, Nginx는 호스트(EC2)에서 실행한 경우
- Jenkins 컨테이너의 IP 확인
- docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' jenkins
- Nginx 설정 변경
- proxy_pass http://172.18.0.2:8080/jenkins/; (가상 IP, 포트 예시)
- Jenkins 컨테이너의 IP 확인
다른 에러 메시지나 트러블 슈팅에 대해서는 따로 정리하겠습니다.
80번이나 시도할 만큼 실패를 너무 많이 해서..
참고 사이트
https://1minute-before6pm.tistory.com/52
Github WebHooks 연동하여 Jenkins 빌드 자동화
Jenkins로 배우는 CI/CD 파이프라인 구축 1. Install and Run Jenkins With Docker Compose 2. Jenkins Pipeline 개념 및 예제 3. Github WebHooks 연동하여 Jenkins 빌드 자동화 4. Jenkins Multibranch Pipeline: 효과적인 브랜치 관리
1minute-before6pm.tistory.com
https://potato-yong.tistory.com/134
[Jenkins] Jenkins에 Github를 연동하고 빌드 자동화하기
Jenkins를 이용하여 Github repository에 push 했을 때, 자동으로 빌드가 되도록 만들어보자. Jenkins와 Github를 연동하는 방법은 ID/PW를 인증하는 방식과 ssh 연동 방식이 있다. 하지만, ID/PW 인증으로 연동하
potato-yong.tistory.com
https://zuminternet.github.io/JENKINS-BUILD/
젠킨스 사용하여 자동 배포환경 만들기!
젠킨스 사용하여 자동 배포환경 만들기!
zuminternet.github.io
https://as-i-am-programing.tistory.com/33
[Jenkins] GitLab 푸시 이벤트 훅 테스트 방법 (push event hook test)
안녕하세요Jenkins로 GitLab Branch에 push되면 자동으로 빌드가 일어나도록 설정하기 위해서 가장 중요한건 hook 설정인데요. 이 방법에 대해 자세히 알아보겠습니다.# VersionJenkins 2.346.1GitLab 11.5.1-ee# Git
as-i-am-programing.tistory.com