Nuwa Project - Project Setting
이번에 새롭게 협업 프로젝트를 진행을 하기로 했습니다.
처음엔 이것 저것 어떤 프로젝트를 진행을 할까 고민을 하다 협업툴을 만들어보기로 결정을 했습니다.
협업툴이면 이것 저것 다양한 구현이 있을 것이라고 생각을 했고 재밌지 않을까 싶습니다.
일단 프로젝트 구성을 먼저 진행했습니다.
Spring Boot: 3.2.2
JDK: 17
MariaDB: 10.6.14
Redis: 7.2.4
MongoDB: 7.0.5
간략한 버전 정보 입니다.
프로젝트 설정 후 사용하는 dependency를 주입해주고 Global 관련 설정을 해주도록 했습니다.
반환 값 설정, RestControllerAdvice로 예외 처리 설정 등을 하였습니다.
@JsonInclude(JsonInclude.Include.NON_NULL) // DTO 를 JSON으로 변환 시 null값인 field 제외
public record GlobalResponseDto<Data>(String status, String message,
Data data) {
@Builder
public GlobalResponseDto {
}
}
@Slf4j
@RestControllerAdvice
@RequiredArgsConstructor
public class GlobalExceptionAdviceController {
}
public GlobalResponseDto<Object> successResponse(String message, Object data) {
return GlobalResponseDto.builder()
.status(SUCCESS.getValue())
.message(message)
.data(data)
.build();
}
public GlobalResponseDto<Object> errorResponse(String message) {
return GlobalResponseDto.builder()
.status(FAIL.getValue())
.message(message)
.data(null)
.build();
}
}
@Getter
public enum GlobalResponseStatus {
SUCCESS("success"),
FAIL("fail");
private final String value;
GlobalResponseStatus(String value) {
this.value = value;
}
}
다음과 같이 설정을 해주었습니다.
그리고 먼저 배포를 진행을 할건데 sentry 설정을 해주기 위해 yml 파일과 logback에 설정 정보를 추가를 했습니다.
sentry 가입과 같은 부분은 생략하기로 하겠습니다.
sentry:
dsn: ${SENTRY_KEY}
exception-resolver-order: -2147483647
enable-tracing: true
send-default-pii: true # 사용자 정보 기록
environment: dev # 개발 환경
sample-rate: 1
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!-- Configure the Console appender -->
<property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%5level) %cyan(%logger) - %msg%n" />
<property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %5level %logger - %msg%n" />
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- Configure the Sentry appender, overriding the logging threshold to the WARN level -->
<appender name="Sentry" class="io.sentry.logback.SentryAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<!-- Optionally add an encoder -->
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!--로컬 환경-->
<springProfile name="local">
<logger name="org.project.nuwabackend" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</springProfile>
<!--개발 환경 -->
<springProfile name="dev">
<root level="INFO">
<appender-ref ref="Console" />
<appender-ref ref="Sentry" />
</root>
</springProfile>
</configuration>
logback.xml과 sentry.yml을 설정을 해줬습니다.
이제 AWS를 이용해서 서버를 만들고 RDS 설정과 탄력 IP 설정을 해줍니다.
AWS Ubuntu Server 20.04 LTS를 사용하였고 RDS는 MariaDB 10.6.14 를 사용했습니다.
탄력적 IP를 생성한 인스턴스에 연결합니다.
이제 AWS EC2에 접속해서 기본적인 설정을 해주도록 하겠습니다.
먼저 jre 17 버전 설치를 위해
sudo apt update
sudo apt install openjdk-17-jre-headless
다음 명령어를 차례대로 실행하여 설치를 진행하고 java -version을 통해 정상적으로 설치가 되었는지 확인을 합니다.
그리고 nginx를 설치해줍니다.
sudo apt install nginx
HTTPS를 사용하기 위해 도메인을 연결해주고 SSL 설정을 해줍니다.
도메인은 무료 도메인 한국에서 발급 받았습니다.
내도메인.한국 - 한글 무료 도메인 등록센터
한글 무료 도메인 내도메인.한국, 웹포워딩, DNS 등 무료 도메인 기능 제공
xn--220b31d95hq8o.xn--3e0b707e
도메인 발급을 받은 후 cerbot으로 SSL 연결을 합니다.
Let’s Encrypt 인증서로 NGINX SSL 설정하기
이 모범 사례에서는 Let’s Encrypt 클라이언트를 사용하여 인증서를 생성하는 방법과 이를 사용하도록 NGINX 오픈소스 및 NGINX Plus를 사용하여 nginx ssl 설정을 자동으로 구성하는 방법을 다룹니다.
nginxstore.com
설정 방법은 위의 nginxstore에 나와있습니다.
SSL 설정을 마쳤으면 이제 nginx.conf 파일을 설정해주도록 하겠습니다.
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 1024;
}
http {
include mime.types;
client_max_body_size 50M;
server {
server_name your_server_name;
location / {
proxy_pass http://127.0.0.1:8081;
proxy_set_header Host $host;
}
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/your_server_name/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your_server_name/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
}
server {
listen 80;
server_name your_server_name;
return 301 https://$host$request_uri;
}
}
nignx.green.conf 입니다 blue와 다른 점은 포트 번호입니다. proxy_pass를 green은 8081을 사용하고 blue는 8082를 사용합니다.
이제 Docker와 Docker compose, Github Actions를 설정하도록 하겠습니다.
aws에서 docker와 docker-compose를 설치를 합니다.
apt install docker
apt install docker-compose
현재 Spring Boot를 docker hun로 push 하기 위해 Dockerfile을 생성합니다.
# base-image
FROM openjdk:17-alpine
# 변수 설정 (빌드 파일 경로)
ARG JAR_FILE=build/libs/nuwa-backend-0.0.1-SNAPSHOT.jar
# 환경 변수 설정
ENV MARIADB_URL=${MARIADB_URL} \
MARIADB_USERNAME=${MARIADB_USERNAME} \
MARIADB_PASSWORD=${MARIADB_PASSWORD} \
MARIADB_NAME=${MARIADB_NAME} \
REDIS_USERNAME=${REDIS_USERNAME} \
REDIS_PORT=${REDIS_PORT} \
REDIS_PASSWORD=${REDIS_PASSWORD} \
MONGODB_USERNAME=${MONGODB_USERNAME} \
MONGODB_PORT=${MONGODB_PORT} \
MONGODB_NAME=${MONGODB_NAME}
# 빌드 파일 컨테이너로 복사
COPY ${JAR_FILE} nuwa.jar
# jar 파일 실행
ENTRYPOINT ["java", "-Dspring.profiles.active=dev", "-jar", "/nuwa.jar"]
그리고 실행을 위한 docker_compose도 생성합니다.
version: '3'
services:
green:
container_name: green
image: 개인 이미지
ports:
- "8081:8080"
environment:
MARIADB_URL: ${MARIADB_URL}
MARIADB_USERNAME: ${MARIADB_USERNAME}
MARIADB_PASSWORD: ${MARIADB_PASSWORD}
MARIADB_NAME: ${MARIADB_NAME}
REDIS_USERNAME: ${REDIS_USERNAME}
REDIS_PORT: ${REDIS_PORT}
REDIS_PASSWORD: ${REDIS_PASSWORD}
MONGODB_USERNAME: ${MONGODB_IP}
MONGODB_PORT: ${MONGODB_PORT}
MONGODB_NAME: ${MONGODB_NAME}
blue:
container_name: blue
image: 개인 이미지
ports:
- "8082:8080"
environment:
MARIADB_URL: ${MARIADB_URL}
MARIADB_USERNAME: ${MARIADB_USERNAME}
MARIADB_PASSWORD: ${MARIADB_PASSWORD}
MARIADB_NAME: ${MARIADB_NAME}
REDIS_USERNAME: ${REDIS_USERNAME}
REDIS_PORT: ${REDIS_PORT}
REDIS_PASSWORD: ${REDIS_PASSWORD}
MONGODB_USERNAME: ${MONGODB_IP}
MONGODB_PORT: ${MONGODB_PORT}
MONGODB_NAME: ${MONGODB_NAME}
redis:
image: redis:alpine
command: redis-server /usr/local/etc/nuwa-redis/redis.conf --requirepass ${REDIS_PASSWORD}
ports:
- "6379:6379"
volumes:
- ./data/nuwa-redis:/data
- ./data/nuwa-redis/redis.conf:/usr/local/etc/nuwa-redis/redis.conf
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD}
시작을 위한 deploy.sh도 설정 합니다.
#!/bin/bash
# Redis 컨테이너 상태 확인 및 시작
IS_REDIS_RUNNING=$(docker ps | grep redis)
if [ -z "$IS_REDIS_RUNNING" ]; then
echo "Starting Redis container..."
docker-compose up -d redis
else
echo "Redis container is already running."
fi
IS_GREEN=$(docker ps | grep green) # 현재 실행중인 App이 blue인지 확인
DEFAULT_CONF=" /etc/nginx/nginx.conf"
if [ -z $IS_GREEN ];then # blue라면
echo "### BLUE => GREEN ###"
echo "1. get green image"
docker-compose pull green # 이미지 받아서
echo "2. green container up"
docker-compose up -d green # 컨테이너 실행
while [ 1 = 1 ]; do
echo "3. green health check..."
sleep 3
REQUEST=$(curl http://127.0.0.1:8081) # green으로 request
if [ -n "$REQUEST" ]; then # 서비스 가능하면 health check 중지
echo "health check success"
break ;
fi
done;
echo "4. reload nginx"
sudo cp /etc/nginx/nginx.green.conf /etc/nginx/nginx.conf
sudo nginx -s reload
echo "5. blue container down"
docker-compose stop blue
else
echo "### GREEN => BLUE ###"
echo "1. get blue image"
docker-compose pull blue
echo "2. blue container up"
docker-compose up -d blue
while [ 1 = 1 ]; do
echo "3. blue health check..."
sleep 3
REQUEST=$(curl http://127.0.0.1:8082) # blue로 request
if [ -n "$REQUEST" ]; then # 서비스 가능하면 health check 중지
echo "health check success"
break ;
fi
done;
echo "4. reload nginx"
sudo cp /etc/nginx/nginx.blue.conf /etc/nginx/nginx.conf
sudo nginx -s reload
echo "5. green container down"
cd $DOCKER_DIR && docker-compose stop green
fi
다음과 같이 설정을 하고 mongodb는 EC2에 바로 설치하도록 하겠습니다.
EC2에 접속해서 mongodb를 docker로 설치합니다.
docker pull mongo로 이미지를 내려받고
sudo docker run -i -t --name {컨테이너 이름} -p 27017:27017 -v ~/data:/data/db -d mongo
사용해서 container를 생성합니다.
sudo docker exec -it {컨테이너 이름} /bin/bash
터미널로 mongodb로 접속하기 위해서 먼저 컨테이너로 접속한 후
mongodb 6.0 이상
mongosh
mongosh를 사용하여 mongodb에 접속이 가능합니다.
mongodb:
uri: mongodb://${MONGO_IP}:${MONGO_PORT}/${MONGO_DATABASE_NAME}
yml을 uri 형식으로 작성하여 연결 테스트까지 완료 했습니다.
이제 Githun Actions를 사용해서 CI/CD 연결을 하도록 하겠습니다.
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
name: Java CI with Gradle
on:
push:
branches: [ "dev" ]
pull_request:
branches: [ "dev" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
# JDK 설정
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'adopt'
# 환경 변수 설정
- name: Set up Environment
run: echo "${{ secrets.ENV_PROPERTIES }}" > ./.env # GitHub SecretKey 에서 설정한 내용으로 .env 파일 생성
# Gradle을 캐싱해둡니다 -> 빌드 속도가 증가하는 효과가 있습니다.
- name: Gradle Caching
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
# 권한 부여
- name: Gradle Authority
run: chmod +x gradlew
# 빌드
- name: Gradle build
run: ./gradlew bootJar
# 빌드 후 도커 허브로 push
- name: Docker Build & Push to Hub
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }} -f ./Dockerfile .
docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}
# 환경 변수 파일 서버로 전달하기(복사 후 붙여넣기)
- name: Send env file
uses: appleboy/scp-action@master
with:
username: ubuntu
host: ${{ secrets.EC2_HOST }}
key: ${{ secrets.EC2_PRIVATE_KEY }}
source: "./.env"
target: "/home/ubuntu"
# 도커 컴포즈 설정 파일 서버로 전달하기(복사 후 붙여넣기)
- name: Send docker-compose.yml
uses: appleboy/scp-action@master
with:
username: ubuntu
host: ${{ secrets.EC2_HOST }}
key: ${{ secrets.EC2_PRIVATE_KEY }}
port: 22
source: "./docker-compose.yml"
target: "/home/ubuntu"
# deploy.sh 파일 서버로 전달하기(복사 후 붙여넣기)
- name: Send deploy.sh
uses: appleboy/scp-action@master
with:
username: ubuntu
host: ${{ secrets.EC2_HOST }}
key: ${{ secrets.EC2_PRIVATE_KEY }}
port: 22
source: "./deploy.sh"
target: "/home/ubuntu"
# 도커 컴포즈 실행하기
# 도커 허브에서 jar파일 및 pull후에 컴포즈 up
- name: Deploy to Dev
uses: appleboy/ssh-action@master
with:
username: ubuntu
host: ${{ secrets.EC2_HOST }}
key: ${{ secrets.EC2_PRIVATE_KEY }}
script: |
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}
chmod 777 ./deploy.sh
./deploy.sh
docker image prune -f
github secret을 이용해서 중요 정보들은 화면에 보이지 않게 합니다.