DevOps

CI/CD 무중단 배포 구현 가이드: 블루-그린 배포를 통한 안정적인 애플리케이션 업데이트

422haha 2024. 10. 13. 22:47

안녕하세요! 이번 포스팅에서는 CI/CD 파이프라인을 구축하고 블루-그린 배포 방식을 적용하여 무중단 배포를 구현한 과정을 상세히 공유하고자 합니다. 이 글은 CI/CD를 처음 접하시는 분들도 따라 할 수 있도록 각 단계별로 명령어와 설정 파일을 포함하고 있으며, 필요한 개념에 대한 설명과 파일 생성 및 수정 방법까지 함께 제공합니다.

저희 프로젝트는 자연 탐험과 학습을 돕기 위한 게이미피케이션 기반의 AR/AI 동식물 탐험 애플리케이션인 "이게모야"입니다. 이번 포스팅을 통해 CI/CD 구축 과정에서 사용한 도구와 기술, 그리고 그 선택 이유를 자세히 설명하겠습니다.

목차

  1. 프로젝트 소개
  2. CI/CD 및 블루-그린 배포란?
  3. 환경 설정 및 초기 구성
  4. Docker 및 Docker Compose 설치
  5. DockerHub 이해하기
  6. Jenkins 설치 및 설정
  7. 데이터베이스 및 캐시 서버 설치
  8. 프로젝트 Docker화 및 Docker Compose 파일 작성
  9. Nginx 설정 및 무중단 배포 구현
  10. Jenkins 파이프라인 설정
  11. SonarQube를 통한 코드 품질 관리
  12. 보안 강화 설정 (Fail2Ban 및 ModSecurity)
  13. 마무리 및 개선 사항

1. 프로젝트 소개

"이게모야"는 사용자가 실제 공원을 탐험하며 동식물 정보를 수집하고, AR 기술과 AI를 활용하여 자연과 상호작용할 수 있는 애플리케이션입니다.

  • 주요 기능
    • 온디바이스 AI 기반 실시간 동식물 판별: 사용자가 촬영한 동식물 이미지를 기기 내에서 AI 모델을 통해 실시간으로 판별합니다.
    • AR을 활용한 네비게이션: AR 기술을 통해 공원 내에서 사용자에게 길 안내 및 위치 정보를 제공합니다.
    • LLM(대규모 언어 모델)을 활용한 동식물 정보 제공: 사용자가 질문하면 AI가 동식물에 대한 상세 정보를 제공합니다.
    • Redis를 활용한 실시간 데이터 캐싱: 사용자의 위치 데이터와 탐험 정보를 빠르게 처리하기 위해 Redis를 사용합니다.

2. CI/CD 및 블루-그린 배포란?

CI/CD란?

CI/CDContinuous Integration (지속적 통합)Continuous Deployment/Delivery (지속적 배포/전달)의 약어로, 소프트웨어 개발과 배포 과정을 자동화하여 효율성과 품질을 높이는 방법론입니다.

  • Continuous Integration (CI): 개발자들이 코드 변경 사항을 중앙 저장소에 자주 병합하여 자동으로 빌드하고 테스트하는 프로세스입니다. 이를 통해 코드의 품질을 유지하고, 통합 과정에서 발생할 수 있는 문제를 조기에 발견할 수 있습니다.
  • Continuous Deployment/Delivery (CD):
    • Continuous Delivery: 자동화된 테스트를 거친 코드를 프로덕션 환경에 배포할 준비를 하는 단계입니다.
    • Continuous Deployment: 테스트를 모두 통과한 코드를 자동으로 프로덕션 환경에 배포하는 단계입니다.

CI/CD의 장점:

  • 빠른 피드백 루프: 코드 변경 사항이 즉시 빌드되고 테스트되므로 문제를 빠르게 발견하고 해결할 수 있습니다.
  • 자동화된 배포: 배포 과정을 자동화하여 인적 오류를 줄이고, 배포 속도를 향상시킵니다.
  • 지속적인 개선: 지속적인 통합과 배포를 통해 소프트웨어의 지속적인 개선과 업데이트가 가능합니다.

블루-그린 배포란?

블루-그린 배포는 두 개의 동일한 운영 환경(블루, 그린)을 사용하여 애플리케이션을 배포하는 방식입니다. 이를 통해 무중단 배포를 실현할 수 있습니다.

  • 블루 환경: 현재 프로덕션에서 서비스 중인 환경입니다.
  • 그린 환경: 새로운 버전의 애플리케이션을 배포하는 환경입니다.

배포 과정:

  1. 새로운 버전을 그린 환경에 배포하고 테스트합니다.
  2. 문제가 없으면 트래픽을 블루에서 그린으로 전환합니다.
  3. 이전 버전(블루 환경)은 대기 상태로 유지하거나 제거합니다.

장점:

  • 무중단 배포: 사용자에게 서비스 중단 없이 새로운 버전을 제공할 수 있습니다.
  • 빠른 롤백: 문제가 발생할 경우, 이전 환경으로 빠르게 전환하여 롤백할 수 있습니다.

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 계정 생성

  1. DockerHub에 접속하여 계정을 생성합니다.
  2. 계정을 생성한 후, 로그인합니다.

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-bluespringboot-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_reqlimit_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 생성

  1. Jenkins 웹 UI에 접속합니다. (http://[서버 IP]:9090)
  2. 새로운 Item을 클릭합니다.
  3. Item 이름을 입력합니다. 예: moya-ci-cd-pipeline
  4. Pipeline을 선택한 후 OK를 클릭합니다.

10.2 Pipeline 설정

  1. Configure 버튼을 클릭합니다.
  2. Pipeline 섹션으로 스크롤합니다.
  3. DefinitionPipeline script로 선택합니다.
  4. 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:
    1. Git Checkout: GitLab 저장소에서 코드를 클론하여 빌드를 준비합니다.
    2. Set Permissions: backend 디렉토리의 파일 권한을 설정합니다.
    3. Copy Secret Files: 시크릿 파일(.env, Spring 설정 파일 등)을 backend 디렉토리에 복사하여 애플리케이션이 올바르게 설정되도록 합니다.
    4. Maven Compile: Maven을 사용하여 프로젝트를 컴파일합니다.
    5. Maven Package: Maven을 사용하여 프로젝트를 패키징합니다.
    6. Docker Build and Push: Docker 이미지를 빌드하고 Docker Hub에 푸시합니다.
    7. Deploy to Inactive Environment: 블루-그린 배포 전략에 따라 비활성화된 환경에 새 버전을 배포합니다.
    8. Wait for Stabilization: 새로운 컨테이너의 안정화를 위해 잠시 대기합니다.
    9. Health Check: 배포된 애플리케이션의 상태를 확인합니다.
    10. Switch Nginx to New Environment: Nginx 설정을 변경하여 트래픽을 새로운 버전으로 전환합니다.
    11. SonarQube Analysis: SonarQube를 사용하여 코드 품질을 분석합니다.
    12. 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 초기 설정

  1. 브라우저에서 http://yourdomain.com:9000에 접속합니다.
  2. 기본 계정(admin/admin)으로 로그인합니다.
  3. 보안을 위해 비밀번호를 변경합니다.
  4. My Account > Security 탭에서 Token을 생성합니다. 이 토큰은 Jenkins와 연동할 때 사용됩니다.

3) Jenkins에 SonarQube 연동

  1. Jenkins 관리 페이지로 이동합니다.
  2. Manage Jenkins > Configure System으로 들어갑니다.
  3. SonarQube servers 섹션에서 Add SonarQube를 클릭합니다.
  4. Name: SonarQube
  5. Server URL: http://yourdomain.com:9000
  6. Server authentication token: Jenkins의 Credentials에 저장한 SonarQube 토큰을 선택합니다.
  7. 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)

서버의 보안을 강화하기 위해 Fail2BanModSecurity를 설치하고 설정합니다.

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 파이프라인과 블루-그린 배포 방식을 통해 효율적이고 안정적인 애플리케이션 배포를 경험해보세요!