CI/CD 무중단 배포 구현 가이드: 블루-그린 배포를 통한 안정적인 애플리케이션 업데이트
안녕하세요! 이번 포스팅에서는 CI/CD 파이프라인을 구축하고 블루-그린 배포 방식을 적용하여 무중단 배포를 구현한 과정을 상세히 공유하고자 합니다. 이 글은 CI/CD를 처음 접하시는 분들도 따라 할 수 있도록 각 단계별로 명령어와 설정 파일을 포함하고 있으며, 필요한 개념에 대한 설명과 파일 생성 및 수정 방법까지 함께 제공합니다.
저희 프로젝트는 자연 탐험과 학습을 돕기 위한 게이미피케이션 기반의 AR/AI 동식물 탐험 애플리케이션인 "이게모야"입니다. 이번 포스팅을 통해 CI/CD 구축 과정에서 사용한 도구와 기술, 그리고 그 선택 이유를 자세히 설명하겠습니다.
목차
- 프로젝트 소개
- CI/CD 및 블루-그린 배포란?
- 환경 설정 및 초기 구성
- Docker 및 Docker Compose 설치
- DockerHub 이해하기
- Jenkins 설치 및 설정
- 데이터베이스 및 캐시 서버 설치
- 프로젝트 Docker화 및 Docker Compose 파일 작성
- Nginx 설정 및 무중단 배포 구현
- Jenkins 파이프라인 설정
- SonarQube를 통한 코드 품질 관리
- 보안 강화 설정 (Fail2Ban 및 ModSecurity)
- 마무리 및 개선 사항
1. 프로젝트 소개
"이게모야"는 사용자가 실제 공원을 탐험하며 동식물 정보를 수집하고, AR 기술과 AI를 활용하여 자연과 상호작용할 수 있는 애플리케이션입니다.
- 주요 기능
- 온디바이스 AI 기반 실시간 동식물 판별: 사용자가 촬영한 동식물 이미지를 기기 내에서 AI 모델을 통해 실시간으로 판별합니다.
- AR을 활용한 네비게이션: AR 기술을 통해 공원 내에서 사용자에게 길 안내 및 위치 정보를 제공합니다.
- LLM(대규모 언어 모델)을 활용한 동식물 정보 제공: 사용자가 질문하면 AI가 동식물에 대한 상세 정보를 제공합니다.
- Redis를 활용한 실시간 데이터 캐싱: 사용자의 위치 데이터와 탐험 정보를 빠르게 처리하기 위해 Redis를 사용합니다.
2. CI/CD 및 블루-그린 배포란?
CI/CD란?
CI/CD는 Continuous Integration (지속적 통합)과 Continuous Deployment/Delivery (지속적 배포/전달)의 약어로, 소프트웨어 개발과 배포 과정을 자동화하여 효율성과 품질을 높이는 방법론입니다.
- Continuous Integration (CI): 개발자들이 코드 변경 사항을 중앙 저장소에 자주 병합하여 자동으로 빌드하고 테스트하는 프로세스입니다. 이를 통해 코드의 품질을 유지하고, 통합 과정에서 발생할 수 있는 문제를 조기에 발견할 수 있습니다.
- Continuous Deployment/Delivery (CD):
- Continuous Delivery: 자동화된 테스트를 거친 코드를 프로덕션 환경에 배포할 준비를 하는 단계입니다.
- Continuous Deployment: 테스트를 모두 통과한 코드를 자동으로 프로덕션 환경에 배포하는 단계입니다.
CI/CD의 장점:
- 빠른 피드백 루프: 코드 변경 사항이 즉시 빌드되고 테스트되므로 문제를 빠르게 발견하고 해결할 수 있습니다.
- 자동화된 배포: 배포 과정을 자동화하여 인적 오류를 줄이고, 배포 속도를 향상시킵니다.
- 지속적인 개선: 지속적인 통합과 배포를 통해 소프트웨어의 지속적인 개선과 업데이트가 가능합니다.
블루-그린 배포란?
블루-그린 배포는 두 개의 동일한 운영 환경(블루, 그린)을 사용하여 애플리케이션을 배포하는 방식입니다. 이를 통해 무중단 배포를 실현할 수 있습니다.
- 블루 환경: 현재 프로덕션에서 서비스 중인 환경입니다.
- 그린 환경: 새로운 버전의 애플리케이션을 배포하는 환경입니다.
배포 과정:
- 새로운 버전을 그린 환경에 배포하고 테스트합니다.
- 문제가 없으면 트래픽을 블루에서 그린으로 전환합니다.
- 이전 버전(블루 환경)은 대기 상태로 유지하거나 제거합니다.
장점:
- 무중단 배포: 사용자에게 서비스 중단 없이 새로운 버전을 제공할 수 있습니다.
- 빠른 롤백: 문제가 발생할 경우, 이전 환경으로 빠르게 전환하여 롤백할 수 있습니다.
3. 환경 설정 및 초기 구성
CI/CD 구축을 위해 먼저 서버 환경을 설정하고 초기 구성을 진행해야 합니다.
1) 서버 접속 및 기본 설정
AWS EC2 인스턴스(Ubuntu 20.04 LTS)에 접속하여 기본적인 업데이트와 설정을 진행합니다.
1.1 서버 업데이트 및 업그레이드
# 서버 업데이트 및 업그레이드
sudo apt update
sudo apt upgrade -y
설명:
sudo apt update
: 패키지 목록을 업데이트합니다.sudo apt upgrade -y
: 설치된 패키지를 최신 버전으로 업그레이드합니다.
1.2 필수 빌드 도구 설치
# 필수 빌드 도구 설치
sudo apt install -y build-essential
설명:
sudo apt install -y build-essential
: 빌드에 필요한 도구들을 설치합니다.
1.3 시간대를 한국 시간대로 설정
# 시간대를 한국 시간대로 설정
sudo ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
# 시간 확인
date
설명:
sudo ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
: 서버의 시간대를 한국 시간대로 설정합니다.date
: 현재 서버의 시간을 확인합니다.
2) 사용자 권한 설정
Docker 명령을 사용할 때 매번 sudo
를 사용하지 않도록 현재 사용자를 Docker 그룹에 추가합니다.
2.1 현재 사용자 Docker 그룹에 추가
# 현재 사용자 Docker 그룹에 추가
sudo usermod -aG docker $USER
설명:
sudo usermod -aG docker $USER
: 현재 사용자를 Docker 그룹에 추가하여 Docker 명령을sudo
없이 실행할 수 있게 합니다.
2.2 변경 사항 적용을 위해 로그아웃 후 재로그인
# 현재 세션에서 로그아웃
exit
설명:
- 변경 사항을 적용하기 위해 현재 SSH 세션에서 로그아웃하고 다시 로그인합니다.
4. Docker 및 Docker Compose 설치
Docker는 애플리케이션을 컨테이너화하여 일관된 환경에서 실행할 수 있게 해줍니다. Docker Compose는 여러 개의 Docker 컨테이너를 정의하고 실행할 수 있게 해주는 도구입니다.
1) Docker 설치
# 필요한 패키지 설치
sudo apt-get update
sudo apt-get install -y \
ca-certificates \
curl \
gnupg \
lsb-release
설명:
- Docker 설치에 필요한 패키지들을 설치합니다.
1.2 Docker의 공식 GPG 키 추가
# Docker의 공식 GPG 키 추가
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
설명:
- Docker 공식 GPG 키를 추가하여 패키지의 신뢰성을 확보합니다.
1.3 Docker 저장소 추가
# Docker 저장소 추가
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \
https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
설명:
- Docker 저장소를 추가합니다.
1.4 Docker 엔진 설치
# Docker 엔진 설치
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
설명:
- Docker 엔진을 설치합니다.
1.5 Docker 설치 확인
# Docker 버전 확인
docker --version
설명:
- 설치된 Docker의 버전을 확인합니다.
2) Docker Compose 설치
sudo apt-get install -y docker-compose
설명:
docker-compose
는 여러 Docker 컨테이너를 정의하고 관리하는 데 사용됩니다.
설치 확인
docker-compose --version
5. DockerHub 이해하기
DockerHub는 Docker 컨테이너 이미지를 저장하고 공유할 수 있는 클라우드 기반 레지스트리 서비스입니다. 개발자들은 DockerHub를 통해 자신이 만든 이미지를 업로드하고, 다른 사람들이 이를 다운로드하여 사용할 수 있습니다.
DockerHub의 주요 기능
- 이미지 저장소: Docker 이미지를 저장하고 관리할 수 있는 공간을 제공합니다.
- 공개 및 비공개 저장소: 공개 저장소를 통해 누구나 이미지를 볼 수 있으며, 비공개 저장소를 통해 특정 사용자만 접근할 수 있습니다.
- 자동 빌드: GitHub나 Bitbucket과 연동하여 코드 변경 시 자동으로 Docker 이미지를 빌드할 수 있습니다.
- 협업 기능: 팀 단위로 이미지를 관리하고 협업할 수 있는 기능을 제공합니다.
DockerHub 사용 이유
- 손쉬운 이미지 배포: 전 세계 어디서나 접근 가능한 중앙 저장소를 통해 이미지를 쉽게 배포하고 공유할 수 있습니다.
- 자동화된 워크플로우: 자동 빌드와 연동 기능을 통해 CI/CD 파이프라인과 원활하게 통합할 수 있습니다.
- 버전 관리: 이미지의 다양한 버전을 관리하고 추적할 수 있습니다.
DockerHub 계정 생성 및 이미지 푸시
5.1 DockerHub 계정 생성
- DockerHub에 접속하여 계정을 생성합니다.
- 계정을 생성한 후, 로그인합니다.
5.2 로컬에서 Docker 이미지 빌드 및 DockerHub에 푸시
5.2.1 DockerHub에 로그인
# DockerHub에 로그인
docker login -u your_dockerhub_username -p your_dockerhub_password
설명:
- DockerHub에 로그인하여 인증을 받습니다.
5.2.2 Docker 이미지 태깅
# Docker 이미지 태깅
docker tag your_image:latest your_dockerhub_username/moya:latest
설명:
- 로컬에서 빌드한 이미지를 DockerHub에 업로드할 수 있도록 태깅합니다.
5.2.3 DockerHub에 이미지 푸시
# DockerHub에 이미지 푸시
docker push your_dockerhub_username/moya:latest
설명:
- 태깅한 이미지를 DockerHub에 푸시하여 저장소에 업로드합니다.
참고:
- DockerHub의 저장소 이름은
your_dockerhub_username/repository_name:tag
형식을 따릅니다. - 개인 프로젝트는 비공개 저장소로 설정할 수 있으며, 팀 프로젝트는 협업 기능을 활용할 수 있습니다.
6. Jenkins 설치 및 설정
Jenkins는 오픈 소스 자동화 서버로, CI/CD 파이프라인을 구축하는 데 사용됩니다. Jenkins를 사용하면 코드의 빌드, 테스트, 배포 과정을 자동화할 수 있습니다.
1) Jenkins 설치
6.1 Jenkins 데이터 저장 디렉토리 생성
# Jenkins 데이터 저장 디렉토리 생성
mkdir -p ~/jenkins
설명:
- Jenkins 데이터를 호스트의
~/jenkins
디렉토리에 저장하여 컨테이너 재시작 시 데이터 유실을 방지합니다.
6.2 Jenkins 컨테이너 실행
# Jenkins 컨테이너 실행
sudo docker run -d \
-p 9090:8080 \
-p 50000:50000 \
--name jenkins \
-v ~/jenkins:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
jenkins/jenkins:jdk17
설명:
-p 9090:8080
: 호스트의 9090 포트를 컨테이너의 8080 포트에 매핑하여 Jenkins UI에 접근할 수 있게 합니다.-p 50000:50000
: Jenkins 에이전트 통신을 위한 포트를 매핑합니다.-v ~/jenkins:/var/jenkins_home
: Jenkins 데이터를 호스트의~/jenkins
디렉토리에 저장합니다.-v /var/run/docker.sock:/var/run/docker.sock
: Jenkins가 Docker를 제어할 수 있도록 Docker 소켓을 마운트합니다.
2) Jenkins 초기 설정
6.3 Jenkins 초기 비밀번호 확인
# Jenkins 초기 비밀번호 확인
sudo docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
설명:
- 초기 비밀번호를 확인한 후, 웹 브라우저에서
http://[서버 IP]:9090
으로 접속하여 Jenkins 초기 설정을 완료합니다. - 기본 계정(
admin
)으로 로그인하고, 비밀번호를 변경합니다. - 필요한 플러그인들을 설치합니다.
3) Jenkins에서 Docker 사용 권한 설정
6.4 Jenkins 컨테이너에서 root 사용자로 접속
# Jenkins 컨테이너에서 root 사용자로 접속
sudo docker exec -it --user root jenkins bash
설명:
- Jenkins 컨테이너에 root 사용자로 접속하여 Docker 그룹에 Jenkins 사용자를 추가합니다.
6.5 Docker 그룹에 Jenkins 사용자 추가
# Docker 그룹에 Jenkins 사용자 추가
usermod -aG docker jenkins
설명:
usermod -aG docker jenkins
: Jenkins 사용자를 Docker 그룹에 추가하여 Docker 명령을sudo
없이 실행할 수 있게 합니다.
6.6 컨테이너에서 나와 Jenkins 재시작
# 컨테이너에서 나와 Jenkins 재시작
exit
sudo docker restart jenkins
설명:
- 컨테이너에서 나와 Jenkins를 재시작하여 변경 사항을 적용합니다.
4) Maven 설치
Jenkins에서 Maven 빌드를 수행하기 위해 Maven을 설치합니다.
6.7 Jenkins 컨테이너에서 root 사용자로 접속
# Jenkins 컨테이너에서 root 사용자로 접속
sudo docker exec -it --user root jenkins bash
6.8 Maven 설치
# Maven 설치
apt-get update
apt-get install -y maven
설명:
- Maven을 설치하여 Java 프로젝트를 빌드할 수 있게 합니다.
6.9 Maven 설치 디렉토리 권한 설정
# Maven 설치 디렉토리 권한 설정
chown -R jenkins:jenkins /usr/share/maven
설명:
- Maven 디렉토리의 소유권을 Jenkins 사용자로 변경하여 권한 문제를 방지합니다.
6.10 Maven 버전 확인
# Maven 버전 확인
su jenkins
mvn --version
설명:
- Maven이 올바르게 설치되었는지 확인합니다.
6.11 컨테이너에서 나와 Jenkins 재시작
# 컨테이너에서 나와 Jenkins 재시작
exit
sudo docker restart jenkins
설명:
- 컨테이너에서 나와 Jenkins를 재시작하여 변경 사항을 적용합니다.
7. 데이터베이스 및 캐시 서버 설치
애플리케이션의 데이터 저장과 빠른 데이터 접근을 위해 PostgreSQL과 Redis를 설치합니다.
1) PostgreSQL 설치 및 설정
PostgreSQL은 강력한 오픈 소스 관계형 데이터베이스 관리 시스템입니다. PostGIS는 PostgreSQL의 공간 확장 모듈로, 지리 공간 데이터를 처리할 수 있습니다.
7.1 PostgreSQL Docker 컨테이너 실행
# PostgreSQL Docker 컨테이너 실행
docker run -d \
-p 5432:5432 \
-v /var/lib/postgres-data:/var/lib/postgresql/data \
--name postgres \
-e POSTGRES_PASSWORD=your_password \
postgres:14
설명:
-p 5432:5432
: 호스트의 5432 포트를 컨테이너의 5432 포트에 매핑합니다.-v /var/lib/postgres-data:/var/lib/postgresql/data
: 데이터 지속성을 위해 호스트의 디렉토리를 마운트합니다.-e POSTGRES_PASSWORD=your_password
: PostgreSQL의postgres
사용자 비밀번호를 설정합니다.
7.2 PostGIS 확장 설치
# PostgreSQL 컨테이너에 접속
docker exec -it postgres bash
설명:
- PostgreSQL 컨테이너에 접속하여 PostGIS를 설치합니다.
# 패키지 목록 업데이트 및 PostGIS 설치
apt-get update
apt-get install -y postgis postgresql-14-postgis-3
설명:
- PostGIS 확장을 설치하여 공간 데이터를 처리할 수 있게 합니다.
# PostgreSQL에 접속
psql -U postgres
설명:
- PostgreSQL에 접속하여 데이터베이스를 생성하고 확장을 추가합니다.
-- 데이터베이스 생성
CREATE DATABASE moya;
-- moya 데이터베이스로 전환
\c moya
-- PostGIS 확장 추가
CREATE EXTENSION postgis;
설명:
CREATE DATABASE moya;
:moya
라는 이름의 데이터베이스를 생성합니다.\c moya
:moya
데이터베이스로 전환합니다.CREATE EXTENSION postgis;
:moya
데이터베이스에 PostGIS 확장을 추가합니다.
# PostgreSQL 컨테이너에서 나가기
exit
7.3 PostgreSQL 설정 파일 위치 확인 및 권한 설정
# PostgreSQL 데이터 디렉토리 권한 확인
ls -la /var/lib/postgresql/data
설명:
- 데이터 디렉토리의 권한을 확인하여 데이터가 안전하게 저장되고 있는지 확인합니다.
2) Redis 설치
Redis는 빠른 메모리 기반의 키-값 저장소로, 실시간 데이터 캐싱에 적합합니다.
# Redis Docker 컨테이너 실행
docker run -d \
-p 6379:6379 \
-v /var/lib/redis-data:/data \
--name redis \
redis:latest
설명:
-p 6379:6379
: 호스트의 6379 포트를 컨테이너의 6379 포트에 매핑합니다.-v /var/lib/redis-data:/data
: 데이터 지속성을 위해 호스트의 디렉토리를 마운트합니다.
8. 프로젝트 Docker화 및 Docker Compose 파일 작성
프로젝트를 Docker 컨테이너화하여 일관된 환경에서 실행할 수 있도록 설정합니다.
1) 프로젝트 Dockerfile 작성
Spring Boot 애플리케이션을 Docker 이미지로 빌드하기 위한 Dockerfile
을 작성합니다.
1.1 Dockerfile
생성 및 편집
파일 생성 위치: 프로젝트의 backend
디렉토리 내에 Dockerfile
을 생성합니다.
# backend 디렉토리로 이동
cd backend
# Dockerfile 생성 및 편집
nano Dockerfile
설명:
cd backend
:backend
디렉토리로 이동합니다.nano Dockerfile
:Dockerfile
을 생성하고 편집합니다. (nano
대신vi
,vim
등 다른 편집기를 사용할 수도 있습니다.)
1.2 Dockerfile
내용 복사 및 저장
Dockerfile
에 다음 내용을 복사하여 붙여넣습니다:
# backend/Dockerfile
# OpenJDK 17 슬림 버전 사용
FROM openjdk:17-jdk-slim
# 작업 디렉토리 설정
WORKDIR /app
# 필요한 패키지 설치
RUN apt-get update && apt-get install -y curl
# 시간대 설정
RUN ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
# 애플리케이션 JAR 파일 복사
COPY target/moya-0.0.1-SNAPSHOT.jar app.jar
# 환경 변수 파일 복사
COPY .env .env
# Spring Boot 설정 파일 복사
COPY src/main/resources/application.properties application.properties
COPY src/main/resources/application-oauth-kakao.properties application-oauth-kakao.properties
COPY src/main/resources/application-oauth-naver.properties application-oauth-naver.properties
# 애플리케이션 실행
ENTRYPOINT ["java", "-jar", "app.jar"]
설명:
- Base Image: OpenJDK 17 슬림 버전을 사용하여 Java 애플리케이션을 실행할 환경을 구성합니다.
- WORKDIR: 컨테이너 내 작업 디렉토리를
/app
으로 설정합니다. - RUN: 필요한 패키지인
curl
을 설치하고, 시간대를 한국 시간대로 설정합니다. - COPY: 빌드된 JAR 파일과 환경 변수 파일, Spring Boot 설정 파일들을 컨테이너로 복사합니다.
- ENTRYPOINT: 애플리케이션을 실행하는 명령어를 설정합니다.
1.3 Dockerfile
저장 및 종료
- Nano에서 저장:
Ctrl + O
,Enter
- Nano에서 종료:
Ctrl + X
2) Docker Compose 파일 작성
블루-그린 배포를 위한 docker-compose.yml
파일을 작성합니다.
2.1 docker-compose.yml
생성 및 편집
파일 생성 위치: 프로젝트의 backend
디렉토리 내에 docker-compose.yml
파일을 생성합니다.
# backend 디렉토리로 이동
cd backend
# docker-compose.yml 파일 생성 및 편집
nano docker-compose.yml
설명:
cd backend
:backend
디렉토리로 이동합니다.nano docker-compose.yml
:docker-compose.yml
파일을 생성하고 편집합니다.
2.2 docker-compose.yml
내용 복사 및 저장
docker-compose.yml
에 다음 내용을 복사하여 붙여넣습니다:
# backend/docker-compose.yml
version: '3.8'
services:
springboot-blue:
image: your_dockerhub_username/moya:blue
container_name: springboot-blue
ports:
- "8081:8080"
env_file:
- .env
environment:
- REDIS_HOST=redis
- REDIS_PORT=6379
- TZ=Asia/Seoul
networks:
- app-network
restart: unless-stopped
springboot-green:
image: your_dockerhub_username/moya:green
container_name: springboot-green
ports:
- "8082:8080"
env_file:
- .env
environment:
- REDIS_HOST=redis
- REDIS_PORT=6379
- TZ=Asia/Seoul
networks:
- app-network
restart: unless-stopped
redis:
image: redis:latest
container_name: redis
ports:
- "6379:6379"
networks:
- app-network
restart: unless-stopped
sonarqube:
image: sonarqube:latest
container_name: sonarqube
ports:
- "9000:9000"
networks:
- app-network
restart: unless-stopped
postgres:
image: postgres:14
container_name: postgres
ports:
- "5432:5432"
volumes:
- /var/lib/postgres-data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=your_password
networks:
- app-network
restart: unless-stopped
jenkins:
image: jenkins/jenkins:jdk17
container_name: jenkins
ports:
- "9090:8080"
- "50000:50000"
volumes:
- ~/jenkins:/var/jenkins_home
- /var/run/docker.sock:/var/run/docker.sock
networks:
- app-network
restart: unless-stopped
networks:
app-network:
driver: bridge
설명:
- Services:
springboot-blue
및springboot-green
: 블루-그린 배포를 위한 두 개의 Spring Boot 애플리케이션 인스턴스입니다. 각기 다른 포트(8081, 8082)를 사용하여 동시에 실행됩니다.redis
: 캐시 서버로 사용됩니다.sonarqube
: 코드 품질 분석 도구인 SonarQube를 실행합니다.postgres
: PostgreSQL 데이터베이스 서버를 실행합니다.jenkins
: CI/CD 파이프라인을 관리하는 Jenkins 서버입니다.
- Networks:
app-network
: 모든 서비스가 동일한 네트워크 내에서 통신할 수 있도록 설정합니다.
2.3 docker-compose.yml
저장 및 종료
- Nano에서 저장:
Ctrl + O
,Enter
- Nano에서 종료:
Ctrl + X
3) Docker Compose 실행
# backend 디렉토리로 이동
cd backend
# Docker Compose 실행
docker-compose up -d
설명:
docker-compose up -d
: 백그라운드에서 모든 서비스를 실행합니다.
4) 현재 Docker 컨테이너 상태 확인
docker ps -a
출력 결과 예시:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ba605bb2fc66 your_dockerhub_username/moya:blue "java -jar app.jar" 2 days ago Up 2 days 0.0.0.0:8081->8080/tcp springboot-blue
bbb8bb79563b your_dockerhub_username/moya:green "java -jar app.jar" 2 days ago Up 2 days 0.0.0.0:8082->8080/tcp springboot-green
3e789de5ad79 postgres:14 "docker-entrypoint.s…" 2 weeks ago Up 2 weeks 0.0.0.0:5432->5432/tcp postgres
d57bb671e670 redis:latest "docker-entrypoint.s…" 2 weeks ago Up 2 weeks 0.0.0.0:6379->6379/tcp redis
7f31b90ff248 sonarqube:latest "/opt/sonarqube/dock…" 2 weeks ago Up 2 weeks 0.0.0.0:9000->9000/tcp sonarqube
b22097030b69 jenkins/jenkins:jdk17 "/usr/bin/tini -- /u…" 4 weeks ago Up 2 weeks 0.0.0.0:9090->8080/tcp, 0.0.0.0:50000->50000/tcp jenkins
설명:
- 각 컨테이너가 정상적으로 실행되고 있는지 확인합니다.
- 포트 매핑을 통해 외부에서 서비스에 접근할 수 있는지 확인할 수 있습니다.
9. Nginx 설정 및 무중단 배포 구현
Nginx는 리버스 프록시 서버로, 트래픽을 블루 또는 그린 환경으로 라우팅하여 무중단 배포를 구현합니다.
1) Nginx 설치 및 SSL 인증서 발급
1.1 Nginx 설치
# Nginx 설치
sudo apt-get update
sudo apt-get install nginx -y
설명:
- 웹 서버인 Nginx를 설치합니다.
1.2 UFW 방화벽 설정 (필요 시)
# UFW 방화벽에서 Nginx 트래픽 허용
sudo ufw allow 'Nginx Full'
# UFW 방화벽 활성화
sudo ufw enable
설명:
- UFW 방화벽을 사용 중이라면 Nginx 트래픽을 허용합니다.
1.3 Let's Encrypt 설치 및 SSL 인증서 발급
# Let's Encrypt 및 Certbot 설치
sudo apt-get install certbot python3-certbot-nginx -y
설명:
- 무료 SSL 인증서를 발급받기 위한 Certbot을 설치합니다.
# SSL 인증서 발급 및 Nginx 설정 자동 업데이트
sudo certbot --nginx -d yourdomain.com
설명:
yourdomain.com
을 실제 도메인으로 교체하여 SSL 인증서를 발급받고 Nginx 설정을 자동으로 업데이트합니다.- 과정 중 이메일 입력, 서비스 약관 동의, HTTPS 리디렉션 설정 등을 진행합니다.
1.4 인증서 갱신 테스트
# 인증서 갱신 테스트
sudo certbot renew --dry-run
설명:
- 인증서 갱신이 정상적으로 작동하는지 테스트합니다.
2) Nginx 업스트림 설정 파일 작성
블루-그린 배포를 위해 Nginx의 업스트림 서버를 설정합니다.
2.1 업스트림 설정 파일 생성
# 업스트림 설정 디렉토리로 이동
sudo mkdir -p /etc/nginx/conf.d
# upstream.conf 파일 생성 및 편집
sudo nano /etc/nginx/conf.d/upstream.conf
설명:
/etc/nginx/conf.d/
디렉토리에upstream.conf
파일을 생성하여 업스트림 서버 설정을 정의합니다.
2.2 upstream.conf
내용 복사 및 저장
upstream.conf
파일에 다음 내용을 복사하여 붙여넣습니다:
# /etc/nginx/conf.d/upstream.conf
upstream app_servers {
server localhost:8081; # 블루 서버 활성화
# server localhost:8082; # 그린 서버 비활성화
}
설명:
upstream app_servers
: Nginx가 트래픽을 전달할 서버 그룹을 정의합니다.- 현재는
springboot-blue
서버만 활성화되어 있습니다.
2.3 upstream.conf
저장 및 종료
- Nano에서 저장:
Ctrl + O
,Enter
- Nano에서 종료:
Ctrl + X
3) Nginx 설정 파일 수정
Nginx의 기본 설정 파일을 수정하여 SSL과 리버스 프록시를 설정합니다.
3.1 기본 설정 파일 편집
# 기본 설정 파일 열기
sudo nano /etc/nginx/sites-available/default
설명:
/etc/nginx/sites-available/default
파일을 열어 편집합니다.
3.2 기본 설정 파일 내용 복사 및 저장
default
파일에 다음 내용을 복사하여 붙여넣습니다:
# /etc/nginx/sites-available/default
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name yourdomain.com;
# HTTP를 HTTPS로 리디렉션
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name yourdomain.com;
# SSL 인증서 경로 설정
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# DDoS 방어 및 보안 설정
client_body_timeout 10s;
client_header_timeout 10s;
client_max_body_size 1m;
# ModSecurity 설정
modsecurity on;
modsecurity_rules_file /etc/nginx/modsecurity/modsecurity.conf;
location / {
proxy_pass http://app_servers;
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 $scheme;
# DDoS 방어 설정
limit_req zone=ddos_limit burst=20 nodelay;
limit_conn addr 10;
}
}
설명:
- HTTP 서버 블록: 모든 HTTP 요청을 HTTPS로 리디렉션합니다.
- HTTPS 서버 블록: SSL 인증서를 사용하여 HTTPS 요청을 처리하고,
app_servers
업스트림으로 트래픽을 전달합니다. - 보안 설정: 클라이언트 요청의 타임아웃, 최대 본문 크기 등을 설정하여 보안을 강화합니다.
- ModSecurity: 웹 애플리케이션 방화벽을 활성화하여 보안 위협으로부터 애플리케이션을 보호합니다.
- DDoS 방어:
limit_req
및limit_conn
지시어를 사용하여 DDoS 공격을 방어합니다.
3.3 default
파일 저장 및 종료
- Nano에서 저장:
Ctrl + O
,Enter
- Nano에서 종료:
Ctrl + X
4) Nginx 전환 스크립트 작성
블루-그린 배포 시 Nginx의 업스트림 서버를 전환하는 스크립트를 작성합니다.
4.1 스크립트 파일 생성
# 스크립트 파일 생성
sudo nano /usr/local/bin/switch_nginx.sh
설명:
/usr/local/bin/
디렉토리에switch_nginx.sh
스크립트 파일을 생성합니다.
4.2 switch_nginx.sh
내용 복사 및 저장
switch_nginx.sh
파일에 다음 내용을 복사하여 붙여넣습니다:
#!/bin/bash
UPSTREAM_CONF="/etc/nginx/conf.d/upstream.conf"
BACKUP_CONF="/etc/nginx/conf.d/upstream.conf.bak"
# 현재 설정 백업
sudo cp $UPSTREAM_CONF $BACKUP_CONF
# 현재 활성화된 포트 확인
ACTIVE_PORT=$(grep -E '^\s*server localhost:808[12];' $UPSTREAM_CONF | grep -o '808[12]' | head -n1)
# 새로운 설정 생성 및 전환
if [ "$ACTIVE_PORT" == "8081" ]; then
NEW_CONFIG="upstream app_servers {
# server localhost:8081; # 블루 서버 비활성화
server localhost:8082; # 그린 서버 활성화
}"
elif [ "$ACTIVE_PORT" == "8082" ]; then
NEW_CONFIG="upstream app_servers {
server localhost:8081; # 블루 서버 활성화
# server localhost:8082; # 그린 서버 비활성화
}"
else
NEW_CONFIG="upstream app_servers {
server localhost:8081; # 블루 서버 활성화
# server localhost:8082; # 그린 서버 비활성화
}"
fi
# 새로운 설정을 파일에 쓰기
echo "$NEW_CONFIG" | sudo tee $UPSTREAM_CONF
# Nginx 설정 테스트 및 적용
if sudo nginx -t; then
sudo systemctl reload nginx
echo "Nginx 업스트림 서버 전환 성공: 활성 포트 $ACTIVE_PORT에서 전환됨."
else
# 설정 오류 시 백업 파일 복원
sudo cp $BACKUP_CONF $UPSTREAM_CONF
sudo systemctl reload nginx
echo "Nginx 설정 테스트 실패: 백업 파일을 복원합니다."
exit 1
fi
설명:
- 업스트림 설정 백업: 현재 Nginx 업스트림 설정을 백업합니다.
- 활성화된 포트 확인: 현재 활성화된 서비스 포트를 확인하여 블루 또는 그린 환경을 판단합니다.
- 새로운 설정 생성: 현재 활성화된 환경을 비활성화하고, 다른 환경을 활성화하는 새로운 업스트림 설정을 생성합니다.
- Nginx 재시작: 설정 파일을 테스트하고 문제가 없으면 Nginx를 재시작하여 변경 사항을 적용합니다. 오류가 발생하면 백업 파일을 복원합니다.
- 로그 메시지 추가: 성공 및 실패 시 로그 메시지를 출력하여 상태를 알립니다.
4.3 스크립트 저장 및 종료
- Nano에서 저장:
Ctrl + O
,Enter
- Nano에서 종료:
Ctrl + X
5) 스크립트 실행 권한 부여
# 스크립트에 실행 권한 부여
sudo chmod +x /usr/local/bin/switch_nginx.sh
설명:
- 스크립트에 실행 권한을 부여하여 실행할 수 있게 합니다.
10. Jenkins 파이프라인 설정
Jenkins를 사용하여 CI/CD 파이프라인을 구축합니다. 이 파이프라인은 코드 변경 사항을 자동으로 빌드, 테스트, 배포하며, SonarQube를 통해 코드 품질을 분석합니다.
1) Jenkins에서 Pipeline 형식의 Item 생성
10.1 Jenkins 대시보드에서 새로운 Pipeline 생성
- Jenkins 웹 UI에 접속합니다. (
http://[서버 IP]:9090
) - 새로운 Item을 클릭합니다.
- Item 이름을 입력합니다. 예:
moya-ci-cd-pipeline
- Pipeline을 선택한 후 OK를 클릭합니다.
10.2 Pipeline 설정
- Configure 버튼을 클릭합니다.
- Pipeline 섹션으로 스크롤합니다.
- Definition을
Pipeline script
로 선택합니다. - Pipeline Script 입력란에 다음 스크립트를 작성합니다.
2) Pipeline Script 작성
아래는 프로젝트에 맞게 작성한 Jenkins Pipeline 스크립트입니다.
pipeline {
agent any
tools {
maven 'Maven'
}
environment {
MAVEN_HOME = tool 'Maven'
PATH = "${MAVEN_HOME}/bin:${env.PATH}"
DOCKER_IMAGE = 'your_dockerhub_username/moya:latest'
SSH_CREDENTIALS = 'ssh-key-id'
SERVER_IP = 'yourdomain.com'
SSH_USER = 'ubuntu'
}
stages {
stage('Git Checkout') {
steps {
script {
echo "=== Git 레포지토리 체크아웃 중 ==="
checkout([$class: 'GitSCM', branches: [[name: '*/develop']],
userRemoteConfigs: [[url: 'https://lab.ssafy.com/s11-ai-image-sub1/S11P21D202.git',
credentialsId: 'gitlab']]])
sh 'ls -la'
}
}
}
stage('Set Permissions') {
steps {
echo "=== 파일 권한 설정 중 ==="
sh 'chmod -R 777 backend'
}
}
stage('Copy Secret Files') {
steps {
script {
echo "=== 시크릿 파일을 복사 중 ==="
withCredentials([file(credentialsId: 'env', variable: 'ENV_FILE'),
file(credentialsId: 'application.properties', variable: 'APP_PROPERTIES'),
file(credentialsId: 'application-oauth-kakao.properties', variable: 'OAUTH_PROPERTIES')]) {
sh 'cp $ENV_FILE backend/.env'
sh 'cp $APP_PROPERTIES backend/src/main/resources/application.properties'
sh 'cp $OAUTH_PROPERTIES backend/src/main/resources/application-oauth-kakao.properties'
}
}
}
}
stage('Maven Compile') {
steps {
dir('backend') {
script {
echo "=== Maven Compile ==="
if (fileExists('pom.xml')) {
sh 'mvn clean compile'
} else {
error 'pom.xml 파일이 존재하지 않습니다.'
}
}
}
}
}
stage('Maven Package') {
steps {
dir('backend') {
echo "=== Maven 패키징 중 ==="
sh 'mvn package -DskipTests'
}
}
}
stage('Docker Build and Push') {
steps {
dir('backend') {
script {
echo "=== Docker 이미지 빌드 중 ==="
sh 'docker build -t your_dockerhub_username/moya:latest .'
echo "=== DockerHub로 이미지 푸시 중 ==="
withCredentials([usernamePassword(credentialsId: 'docker-credentials-id', passwordVariable: 'DOCKER_HUB_PASSWORD', usernameVariable: 'DOCKER_HUB_USERNAME')]) {
sh 'echo $DOCKER_HUB_PASSWORD | docker login -u $DOCKER_HUB_USERNAME --password-stdin'
sh 'docker push your_dockerhub_username/moya:latest'
}
}
}
}
}
stage('Deploy to Inactive Environment') {
steps {
script {
echo "=== 비활성화된 환경에 배포 중 ==="
sshagent([SSH_CREDENTIALS]) {
sh '''
ACTIVE_PORT=$(ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_IP} "sudo grep -E '^\\s*server localhost:808[12];' /etc/nginx/conf.d/upstream.conf | grep -o '808[12]' | head -n1")
echo 현재 활성 환경 포트: ${ACTIVE_PORT}
if [ "${ACTIVE_PORT}" = "8081" ]; then
INACTIVE_SERVICE="springboot-green"
IMAGE_TAG="green"
INACTIVE_PORT="8082"
echo "비활성 환경: 그린 (포트 8082)"
elif [ "${ACTIVE_PORT}" = "8082" ]; then
INACTIVE_SERVICE="springboot-blue"
IMAGE_TAG="blue"
INACTIVE_PORT="8081"
echo "비활성 환경: 블루 (포트 8081)"
else
INACTIVE_SERVICE="springboot-blue"
IMAGE_TAG="blue"
INACTIVE_PORT="8081"
fi
ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_IP} "docker-compose -f /home/ubuntu/moya/backend/docker-compose.yml stop ${INACTIVE_SERVICE} && docker-compose -f /home/ubuntu/moya/backend/docker-compose.yml rm -f ${INACTIVE_SERVICE}"
ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_IP} "docker pull your_dockerhub_username/moya:latest && docker tag your_dockerhub_username/moya:latest your_dockerhub_username/moya:${IMAGE_TAG} && cd /home/ubuntu/moya/backend && docker-compose -f /home/ubuntu/moya/backend/docker-compose.yml up -d --no-deps ${INACTIVE_SERVICE}"
ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_IP} "docker-compose -f /home/ubuntu/moya/backend/docker-compose.yml ps ${INACTIVE_SERVICE}"
'''
}
}
}
}
stage('Wait for Stabilization') {
steps {
echo "=== 새로운 컨테이너 안정화 대기 중 ==="
sleep time: 10, unit: 'SECONDS'
}
}
stage('Health Check') {
steps {
script {
echo "=== 헬스 체크 수행 중 ==="
sshagent([SSH_CREDENTIALS]) {
sh '''
set +e
MAX_RETRIES=12
RETRY_INTERVAL=5
ACTIVE_PORT=$(ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_IP} "sudo grep -E '^\\s*server localhost:808[12];' /etc/nginx/conf.d/upstream.conf | grep -o '808[12]' | head -n1")
if [ "${ACTIVE_PORT}" = "8081" ]; then
INACTIVE_PORT="8082"
else
INACTIVE_PORT="8081"
fi
HEALTH_CHECK_URL="http://${SERVER_IP}:${INACTIVE_PORT}/health"
echo "헬스 체크 URL: ${HEALTH_CHECK_URL}"
RETRIES=0
while [ $RETRIES -lt $MAX_RETRIES ]; do
STATUS=$(curl -s -o /dev/null -w '%{http_code}' ${HEALTH_CHECK_URL})
if [ "$STATUS" -eq 200 ]; then
echo "헬스 체크 성공!"
exit 0
else
echo "헬스 체크 실패, ${RETRY_INTERVAL}초 후 재시도... (${RETRIES}/${MAX_RETRIES})"
RETRIES=$((RETRIES+1))
sleep $RETRY_INTERVAL
fi
done
echo "헬스 체크 실패: 최대 재시도 횟수 초과"
exit 1
'''
}
}
}
}
stage('Switch Nginx to New Environment') {
steps {
script {
echo "=== Nginx를 새로운 환경으로 전환 중 ==="
sshagent([SSH_CREDENTIALS]) {
sh '''
ssh -T -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_IP} << EOF
echo "현재 Nginx 설정:"
sudo cat /etc/nginx/conf.d/upstream.conf
echo "스크립트 실행 중..."
sudo bash /usr/local/bin/switch_nginx.sh
echo "스크립트 실행 완료"
echo "변경 후 Nginx 설정:"
sudo cat /etc/nginx/conf.d/upstream.conf
echo "Nginx 상태 확인:"
sudo systemctl --no-pager status nginx
exit
EOF
'''
}
}
}
}
stage('SonarQube Analysis') {
steps {
dir('backend') {
script {
echo "=== SonarQube 분석 중 ==="
withSonarQubeEnv('moya') {
withCredentials([string(credentialsId: 'sonarQubeToken', variable: 'SONAR_TOKEN')]) {
sh 'mvn sonar:sonar -Dsonar.projectKey=com.e22e:moya -Dsonar.host.url=http://yourdomain.com:9000 -Dsonar.login=$SONAR_TOKEN'
}
}
}
}
}
}
stage('Notify Mattermost') {
steps {
script {
echo "=== Mattermost에 알림 전송 중 ==="
mattermostSend color: '#32a852', message: """빌드 성공 (${env.JOB_NAME}) #(${env.BUILD_NUMBER}) (<${env.BUILD_URL}|Open>)
See the (<${env.BUILD_URL}console|console>)"""
}
}
}
}
post {
always {
script {
echo "=== Cleanup 또는 에러 처리 중 ==="
}
}
failure {
script {
echo "=== 빌드 실패, 알림 전송 ==="
mattermostSend color: '#ff0000', message: """빌드 실패 (${env.JOB_NAME}) #(${env.BUILD_NUMBER}) (<${env.BUILD_URL}|Open>)
See the (<${env.BUILD_URL}console|console>)"""
}
}
}
}
설명:
- Environment Variables:
MAVEN_HOME
,PATH
: Maven 빌드 도구의 경로 설정.DOCKER_IMAGE
: Docker Hub에 푸시할 이미지 이름.SSH_CREDENTIALS
: Jenkins에 등록한 SSH 자격 증명 ID.SERVER_IP
,SSH_USER
: 배포 대상 서버의 IP와 사용자 이름.
- Stages:
- Git Checkout: GitLab 저장소에서 코드를 클론하여 빌드를 준비합니다.
- Set Permissions:
backend
디렉토리의 파일 권한을 설정합니다. - Copy Secret Files: 시크릿 파일(.env, Spring 설정 파일 등)을
backend
디렉토리에 복사하여 애플리케이션이 올바르게 설정되도록 합니다. - Maven Compile: Maven을 사용하여 프로젝트를 컴파일합니다.
- Maven Package: Maven을 사용하여 프로젝트를 패키징합니다.
- Docker Build and Push: Docker 이미지를 빌드하고 Docker Hub에 푸시합니다.
- Deploy to Inactive Environment: 블루-그린 배포 전략에 따라 비활성화된 환경에 새 버전을 배포합니다.
- Wait for Stabilization: 새로운 컨테이너의 안정화를 위해 잠시 대기합니다.
- Health Check: 배포된 애플리케이션의 상태를 확인합니다.
- Switch Nginx to New Environment: Nginx 설정을 변경하여 트래픽을 새로운 버전으로 전환합니다.
- SonarQube Analysis: SonarQube를 사용하여 코드 품질을 분석합니다.
- Notify Mattermost: 빌드 및 배포 결과를 팀 커뮤니케이션 도구인 Mattermost에 알림으로 전송합니다.
- Post Actions:
- always: 모든 빌드 후에 수행되는 단계입니다. (예: Cleanup)
- failure: 빌드 실패 시 Mattermost에 실패 알림을 전송합니다.
10.3 Jenkinsfile
저장 및 종료
- Nano에서 저장:
Ctrl + O
,Enter
- Nano에서 종료:
Ctrl + X
2) 파이프라인 설명
- Git Checkout: GitLab에서 소스 코드를 클론하여 빌드를 준비합니다.
- Set Permissions: 빌드와 배포에 필요한 파일 권한을 설정합니다.
- Copy Secret Files: 시크릿 파일(.env, Spring 설정 파일 등)을
backend
디렉토리에 복사하여 애플리케이션이 올바르게 설정되도록 합니다. - Maven Compile: Maven을 사용하여 프로젝트를 컴파일합니다.
- Maven Package: Maven을 사용하여 프로젝트를 패키징합니다.
- Docker Build and Push: Docker 이미지를 빌드하고, Docker Hub에 푸시하여 배포를 준비합니다.
- Deploy to Inactive Environment: 블루-그린 배포 전략에 따라 비활성화된 환경에 새로운 버전을 배포합니다.
- Wait for Stabilization: 배포된 애플리케이션의 안정화를 위해 잠시 대기합니다.
- Health Check: 애플리케이션의 상태를 확인하여 정상적으로 동작하는지 확인합니다.
- Switch Nginx to New Environment: Nginx 설정을 변경하여 트래픽을 새로운 버전으로 전환합니다.
- SonarQube Analysis: SonarQube를 사용하여 코드의 버그, 보안 취약점, 코드 스멜 등을 분석하여 코드 품질을 관리합니다.
- Notify Mattermost: 빌드 및 배포 결과를 팀 커뮤니케이션 도구인 Mattermost에 알림으로 전송합니다.
11. SonarQube를 통한 코드 품질 관리
SonarQube는 소스 코드의 품질을 자동으로 분석하고, 코드의 버그, 보안 취약점, 코드 스멜 등을 감지하여 개선할 수 있는 도구입니다. 이를 통해 지속적으로 코드 품질을 유지하고 향상시킬 수 있습니다.
1) SonarQube 설치 및 설정
SonarQube를 Docker 컨테이너로 설치합니다.
# SonarQube Docker 컨테이너 실행
docker run -d \
--name sonarqube \
-e SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true \
-p 9000:9000 \
sonarqube:latest
설명:
-e SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true
: Elasticsearch 관련 초기 체크를 비활성화하여 메모리 부족 문제를 방지합니다. (실제 환경에서는 적절한 메모리 할당을 권장)-p 9000:9000
: SonarQube UI에 접근하기 위한 포트를 매핑합니다.
SonarQube 컨테이너 상태 확인
docker ps
출력 결과 예시:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7f31b90ff248 sonarqube:latest "/opt/sonarqube/dock…" 2 weeks ago Up 2 weeks 0.0.0.0:9000->9000/tcp sonarqube
2) SonarQube 초기 설정
- 브라우저에서
http://yourdomain.com:9000
에 접속합니다. - 기본 계정(
admin
/admin
)으로 로그인합니다. - 보안을 위해 비밀번호를 변경합니다.
- My Account > Security 탭에서 Token을 생성합니다. 이 토큰은 Jenkins와 연동할 때 사용됩니다.
3) Jenkins에 SonarQube 연동
- Jenkins 관리 페이지로 이동합니다.
- Manage Jenkins > Configure System으로 들어갑니다.
- SonarQube servers 섹션에서 Add SonarQube를 클릭합니다.
- Name: SonarQube
- Server URL:
http://yourdomain.com:9000
- Server authentication token: Jenkins의 Credentials에 저장한 SonarQube 토큰을 선택합니다.
- Save를 클릭하여 설정을 저장합니다.
4) Jenkins Pipeline에서 SonarQube 분석 단계 추가
이미 작성한 Jenkins Pipeline 스크립트에 SonarQube Analysis 단계가 포함되어 있습니다. 이를 통해 빌드 과정 중에 코드 품질을 자동으로 분석할 수 있습니다.
stage('SonarQube Analysis') {
steps {
dir('backend') {
script {
echo "=== SonarQube 분석 중 ==="
withSonarQubeEnv('moya') {
withCredentials([string(credentialsId: 'sonarQubeToken', variable: 'SONAR_TOKEN')]) {
sh 'mvn sonar:sonar -Dsonar.projectKey=com.e22e:moya -Dsonar.host.url=http://yourdomain.com:9000 -Dsonar.login=$SONAR_TOKEN'
}
}
}
}
}
}
설명:
withSonarQubeEnv
: SonarQube 서버와의 연동 환경을 설정합니다.withCredentials
: SonarQube 토큰을 안전하게 Jenkins 파이프라인에 주입합니다.mvn sonar:sonar
: Maven을 사용하여 SonarQube 분석을 실행합니다.
SonarQube 분석 결과:
- Jenkins 빌드가 완료된 후, SonarQube 웹 UI에서 코드 품질 보고서를 확인할 수 있습니다.
- Jenkins의 콘솔 로그에서도 SonarQube 분석 진행 상황과 결과를 확인할 수 있습니다.
12. 보안 강화 설정 (Fail2Ban 및 ModSecurity)
서버의 보안을 강화하기 위해 Fail2Ban과 ModSecurity를 설치하고 설정합니다.
1) Fail2Ban 설치 및 설정
Fail2Ban은 악의적인 IP 주소로부터 서버를 보호하는 데 사용됩니다. 주로 SSH 로그인 시도, DDoS 공격 등을 방어합니다.
1.1 Fail2Ban 설치
# Fail2Ban 설치
sudo apt-get update
sudo apt-get install fail2ban -y
설명:
sudo apt-get install fail2ban -y
: Fail2Ban을 설치합니다.
1.2 설정 파일 생성 및 수정
# jail.local 파일 생성 및 편집
sudo nano /etc/fail2ban/jail.local
설명:
jail.local
: Fail2Ban의 규칙을 정의하는 파일입니다. 사용자 정의 규칙을 추가할 수 있습니다.
1.3 jail.local
파일에 다음 내용 추가
[nginx-req-limit]
enabled = true
filter = nginx-req-limit
action = iptables-multiport[name=ReqLimit, port="http,https", protocol=tcp]
logpath = /var/log/nginx/error.log
findtime = 600
bantime = 7200
maxretry = 10
설명:
- [nginx-req-limit]: Nginx의 요청 제한을 모니터링하는 Fail2Ban 규칙입니다.
enabled = true
: 이 규칙을 활성화합니다.filter = nginx-req-limit
: 사용할 필터 이름입니다.action = iptables-multiport[name=ReqLimit, port="http,https", protocol=tcp]
: 차단 시 사용할 액션을 정의합니다.logpath = /var/log/nginx/error.log
: 로그 파일 경로를 지정합니다.findtime = 600
: 10분 동안 시도 횟수를 셉니다.bantime = 7200
: 2시간 동안 IP를 차단합니다.maxretry = 10
: 최대 시도 횟수입니다.
1.4 필터 설정 파일 생성
sudo nano /etc/fail2ban/filter.d/nginx-req-limit.conf
설명:
nginx-req-limit.conf
: Nginx의 요청 제한 로그 패턴을 정의하는 Fail2Ban 필터 파일입니다.
1.5 nginx-req-limit.conf
파일에 다음 내용 추가
[Definition]
failregex = limiting requests, excess:.* by zone
설명:
- failregex: 로그 파일에서 Fail2Ban이 차단할 패턴을 정의합니다.
1.6 Fail2Ban 재시작 및 상태 확인
# Fail2Ban 재시작
sudo systemctl restart fail2ban
# Fail2Ban 상태 확인
sudo fail2ban-client status
sudo fail2ban-client status nginx-req-limit
설명:
sudo systemctl restart fail2ban
: Fail2Ban 서비스를 재시작하여 설정을 적용합니다.sudo fail2ban-client status
: Fail2Ban의 전반적인 상태를 확인합니다.sudo fail2ban-client status nginx-req-limit
: 특정 jails의 상태를 확인합니다.
2) ModSecurity 웹 방화벽 설치 및 설정
ModSecurity는 웹 애플리케이션 방화벽으로, 악의적인 요청을 필터링하여 서버를 보호합니다.
2.1 ModSecurity 및 OWASP CRS 설치
# ModSecurity 및 OWASP CRS 설치
sudo apt-get update
sudo apt-get install -y libnginx-mod-security
sudo apt-get install -y modsecurity-crs
설명:
libnginx-mod-security
: Nginx용 ModSecurity 모듈을 설치합니다.modsecurity-crs
: OWASP Core Rule Set을 설치하여 기본 보안 규칙을 제공합니다.
2.2 ModSecurity 설정 파일 수정
sudo nano /etc/nginx/modsecurity/modsecurity.conf
설명:
modsecurity.conf
: ModSecurity의 주요 설정 파일입니다.
2.3 modsecurity.conf
파일에 다음 내용 추가
SecRuleEngine On
설명:
SecRuleEngine On
: ModSecurity를 활성화합니다.
2.4 OWASP CRS 설정 파일 복사 및 링크 생성
# CRS 설정 파일 복사
sudo cp /usr/share/modsecurity-crs/crs-setup.conf.example /etc/nginx/modsecurity/crs-setup.conf
# CRS 규칙 파일 링크 생성
sudo ln -s /usr/share/modsecurity-crs/rules /etc/nginx/modsecurity/rules
설명:
crs-setup.conf.example
: OWASP CRS의 기본 설정 파일입니다./usr/share/modsecurity-crs/rules
: CRS 규칙 파일들이 위치한 디렉토리입니다.
2.5 ModSecurity 설정 파일에 CRS 포함
echo 'Include /etc/nginx/modsecurity/crs-setup.conf' | sudo tee -a /etc/nginx/modsecurity/modsecurity.conf
echo 'Include /etc/nginx/modsecurity/rules/*.conf' | sudo tee -a /etc/nginx/modsecurity/modsecurity.conf
설명:
Include
지시어를 사용하여 CRS 설정과 규칙 파일들을 ModSecurity 설정에 포함시킵니다.
2.6 Nginx 설정 테스트 및 재시작
# Nginx 설정 테스트
sudo nginx -t
# Nginx 재시작
sudo systemctl reload nginx
설명:
sudo nginx -t
: Nginx 설정 파일의 문법을 검사합니다.sudo systemctl reload nginx
: 설정 파일을 재적용하여 변경 사항을 반영합니다.
13. 마무리 및 개선 사항
결과
- 무중단 배포 구현 성공: 블루-그린 배포 방식을 통해 서비스 중단 없이 새로운 버전을 배포할 수 있게 되었습니다.
- 자동화된 배포 프로세스 구축: CI/CD 파이프라인을 통해 배포 과정이 자동화되어 배포 시간과 인적 오류를 줄일 수 있었습니다.
- 코드 품질 관리 강화: SonarQube를 통해 지속적인 코드 품질 관리를 할 수 있게 되었습니다.
- 보안 강화: Fail2Ban과 ModSecurity를 적용하여 서버 보안을 강화하였습니다.
- DockerHub 활용: DockerHub를 통해 Docker 이미지를 효과적으로 관리하고 배포할 수 있게 되었습니다.
개선 사항
- 자동 롤백 기능 도입: 배포 실패 시 자동으로 이전 버전으로 롤백하는 기능을 추가하여 배포 안정성을 높일 계획입니다.
- 모니터링 도구 도입: Prometheus와 Grafana 등을 도입하여 시스템 및 애플리케이션 모니터링을 강화할 예정입니다.
- CI/CD 파이프라인 최적화: 빌드 시간 단축 및 효율적인 리소스 사용을 위한 최적화를 진행할 예정입니다.
- 헬스 체크 강화: 애플리케이션의 상태를 더욱 정확하게 확인하기 위해 다양한 헬스 체크 방법을 도입할 계획입니다.
- DockerHub 보안 강화: DockerHub 저장소의 접근 권한을 세분화하고, 보안 설정을 강화하여 이미지 보안을 높일 예정입니다.
이번 포스팅에서는 CI/CD를 통해 블루-그린 배포 방식을 적용하여 무중단 배포를 구현하는 과정을 상세히 설명하였습니다. CI/CD를 처음 접하시는 분들도 따라 할 수 있도록 각 단계별 명령어와 설정 파일 생성 및 수정 방법을 최대한 상세히 포함하였으며, 필요한 개념에 대한 설명도 함께 제공하였습니다. 또한, DockerHub의 활용 방법과 그 중요성에 대해 추가로 설명하였습니다. 이 글이 여러분의 프로젝트에 도움이 되었기를 바랍니다.
감사합니다!
추가 팁 및 참고 자료
- 파일 편집기 사용법: 본 가이드에서는
nano
편집기를 사용하였으나, 다른 편집기(vi
,vim
,emacs
등)를 사용할 수도 있습니다. 각 편집기의 기본 사용법을 익히면 더욱 편리하게 파일을 수정할 수 있습니다. - Jenkins 플러그인 설치: Jenkins에서 다양한 기능을 활용하기 위해 필요한 플러그인들을 사전에 설치해두면 파이프라인 설정이 용이합니다.
- 보안 키 관리: SSH 키, API 토큰 등 민감한 정보는 Jenkins의 Credentials 관리 기능을 통해 안전하게 관리해야 합니다.
- 로그 모니터링: 배포 및 운영 중 발생하는 문제를 신속하게 파악하기 위해 로그 파일을 지속적으로 모니터링하는 습관을 들이세요.
- 백업 전략: 중요한 데이터와 설정 파일에 대한 정기적인 백업을 통해 예상치 못한 문제 발생 시 신속하게 복구할 수 있도록 준비하세요.
이 외에도 다양한 자료와 튜토리얼이 온라인에 많이 있으니, 필요에 따라 추가적으로 학습하시기를 권장드립니다.
참고 자료:
주의 사항:
- 실제 운영 환경에서는 보안 설정을 더욱 강화하고, 비밀번호와 API 키는 절대로 공개하지 않도록 주의하세요.
- 본 가이드는 기본적인 설정을 다루고 있으며, 실제 프로젝트의 요구 사항에 따라 추가적인 설정이 필요할 수 있습니다.
이제 CI/CD 파이프라인과 블루-그린 배포 방식을 통해 효율적이고 안정적인 애플리케이션 배포를 경험해보세요!