Skip to content

Commit

Permalink
cicd: codedeploy 방식으로 배포 설정 변경
Browse files Browse the repository at this point in the history
  • Loading branch information
jemin committed Dec 7, 2023
1 parent 9217e47 commit 90d56c6
Show file tree
Hide file tree
Showing 108 changed files with 1,116 additions and 1,002 deletions.
69 changes: 69 additions & 0 deletions .github/workflows/backend-cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Deploy to Production

on:
push:
branches:
- develop

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout source code
uses: actions/checkout@master

- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'zulu'

- 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: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Build with Gradle
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew build

# 배포에 필요한 여러 설정 파일과 프로젝트 빌드 파일을 zip 파일로 모아준다.
- name: Make zip file
run: |
mkdir deploy
cp ./docker/docker-compose.blue.yml ./deploy/
cp ./docker/docker-compose.green.yml ./deploy/
cp ./appspec.yml ./deploy/
cp ./docker/Dockerfile ./deploy/
cp ./core/core-api/build/libs/core-api-0.0.1-SNAPSHOT.jar ./deploy/
zip -r -qq -j ./spring-build.zip ./deploy
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2

- name: Upload to S3
run: |
aws s3 cp \
--region ap-northeast-2 \
./spring-build.zip s3://melly-s3
# 추가
- name: Code Deploy
run: aws deploy create-deployment --application-name melly-codedeploy
--deployment-config-name CodeDeployDefault.HalfAtATime
--deployment-group-name spring-deploy-group
--s3-location bucket=melly-s3,bundleType=zip,key=spring-build.zip
5 changes: 0 additions & 5 deletions Dockerfile

This file was deleted.

35 changes: 35 additions & 0 deletions appspec.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
version: 0.0

# Deploy 대상 서버의 운영체제를 표시
os: linux

# 코드 파일 전송과 관련된 설정
files:
# 코드 파일의 소스 경로
- source: /
# 코드 파일의 대상 경로 -> /home/ec2-user/app 디렉토리로 파일을 복사한다.
destination: /home/ec2-user/app
# 대상 경로에 이미 파일이 존재하는 경우, 덮어쓰기를 허용할지 여부
overwrite: yes

# 파일 및 디렉토리 권한에 관련된 설정
permissions:
# 권한을 설정할 대상 경로
- object: /
# 모든 파일 및 디렉토리를 의미
pattern: "**"
# 파일 및 디렉토리의 소유자를 ec2-user로 설정
owner: ec2-user
# 파일 및 디렉토리의 그룹을 ec2-user로 설정
group: ec2-user

# Deploy 전후에 실행할 스크립트 또는 명령에 관련된 설정
hooks:
# 애플리케이션 시작시 실행할 스크립트 또는 명령에 관련된 설정
ApplicationStart:
# 실행할 스크립트 또는 명령의 위치
- location: deploy.sh
# 스크립트 또는 명령 실행의 제한 시간을 설정
timeout: 60
# CodeDeploy 중 실행되는 스크립트 또는 명령을 실행할 사용자를 지정
runas: ec2-user
54 changes: 27 additions & 27 deletions clients/client-auth/src/main/resources/client-auth.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,30 @@ spring.cloud.openfeign:
loggerLevel: full # openFeign 관련 모든 로그 출력

httpclient:
max-connections: 2000 # 전체 커넥션 수
max-connections-per-route: 500 # Route당 할당되는 최대 커넥션 수

# circuitbreaker:
# enabled: true
#
#resilience4j.circuitbreaker:
# configs:
# default:
# slidingWindowType: COUNT_BASED
# minimumNumberOfCalls: 3 # Circuit Open 여부를 판단하기 위해 실행해야 하는 최소 요청 횟수
# slidingWindowSize: 5 # Circuit Open 여부를 체크하는 윈도우 크기
# waitDurationInOpenState: 5s # Circuit이 Open 상태에 머무는 시간
#
# failureRateThreshold: 80 # 슬라이딩 윈도우 내에서 몇 %이상되면 Circuit을 Open할지 결정하는 비율
#
# slowCallDurationThreshold: 2000 # 몇초동안 요청이 지속되면 슬로우 콜로 판별되는지 기준
# slowCallRateThreshold: 80 # 슬라이딩 윈도우 내 요청 중 몇 %이상 슬로우 콜이 발생하면 Circuit을 Open할지 결정하는 비율
#
# permittedNumberOfCallsInHalfOpenState: 5 # Half Open 상태에서 상태 변화를 위해 테스트 해보는 요청 횟수
# automaticTransitionFromOpenToHalfOpenEnabled: true # Half Open으로 자동으로 변경시킬지 여부
#
# eventConsumerBufferSize: 10 # Actuator에서 CircuitBreaker 상태를 보여줄때 이벤트를 몇개까지 버퍼에 쌓을지 결정
#
# instances:
# default: # Circuit Breaker 인스턴스 이름
# baseConfig: default # 어떤 설정을 사용할지 결정. default라면 상단의 default config를 그대로 사용함
max-connections: 400 # 전체 커넥션 수
max-connections-per-route: 100 # Route당 할당되는 최대 커넥션 수

circuitbreaker:
enabled: true

resilience4j.circuitbreaker:
configs:
default:
slidingWindowType: COUNT_BASED
minimumNumberOfCalls: 3 # Circuit Open 여부를 판단하기 위해 실행해야 하는 최소 요청 횟수
slidingWindowSize: 5 # Circuit Open 여부를 체크하는 윈도우 크기
waitDurationInOpenState: 5s # Circuit이 Open 상태에 머무는 시간

failureRateThreshold: 80 # 슬라이딩 윈도우 내에서 몇 %이상되면 Circuit을 Open할지 결정하는 비율

slowCallDurationThreshold: 2000 # 몇초동안 요청이 지속되면 슬로우 콜로 판별되는지 기준
slowCallRateThreshold: 80 # 슬라이딩 윈도우 내 요청 중 몇 %이상 슬로우 콜이 발생하면 Circuit을 Open할지 결정하는 비율

permittedNumberOfCallsInHalfOpenState: 5 # Half Open 상태에서 상태 변화를 위해 테스트 해보는 요청 횟수
automaticTransitionFromOpenToHalfOpenEnabled: true # Half Open으로 자동으로 변경시킬지 여부

eventConsumerBufferSize: 10 # Actuator에서 CircuitBreaker 상태를 보여줄때 이벤트를 몇개까지 버퍼에 쌓을지 결정

instances:
default: # Circuit Breaker 인스턴스 이름
baseConfig: default # 어떤 설정을 사용할지 결정. default라면 상단의 default config를 그대로 사용함
2 changes: 2 additions & 0 deletions core/core-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ dependencies {
// Test
testImplementation 'org.springframework.security:spring-security-test'

implementation 'org.ehcache:ehcache:3.10.8'
implementation 'javax.cache:cache-api:1.1.1'

// Rest Docs
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
TimeUnit timeUnit() default TimeUnit.SECONDS;

// 락 대기 시간
long waitTime() default 3L;
long waitTime() default 7L;

// 락 사용 시간
long leaseTime() default 2L;
long leaseTime() default 5L;

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
@Aspect
@Component
@RequiredArgsConstructor
public class DistributedLockAop {
public class DistributedLockAspect {

private final RedissonClient redissonClient;

Expand All @@ -34,7 +34,6 @@ public Object lock(final ProceedingJoinPoint joinPoint) throws Throwable {
@Cacheable 어노테이션에서 key값을 설정하는 방식과 유사하도록 구현
*/
String key = LockKeyParser.parse(signature.getParameterNames(), joinPoint.getArgs(), lock.key());

RLock rLock = redissonClient.getLock(key);

try {
Expand All @@ -46,7 +45,11 @@ public Object lock(final ProceedingJoinPoint joinPoint) throws Throwable {
throw new BusinessException(ErrorCode.SERVER_ERROR);
}

// 락을 획득했다면 실제 메소드 실행
/*
락을 획득한 후 기존 메서드를 실행할때, 락을 사용하는 기능은 기존 트랜잭션에서 분리하기위해 REQUIRED_NEW를 사용했습니다.
분산락이 적용된 기능을 완료한 뒤, 커밋하고 나면 그때 락을 해제하는 작업을 합니다.
만약 기존의 트랜잭션을 그대로 이어받아 사용한다면 다른 클라이언트에 의해 갱신 손실이 발생할 수 있습니다.
*/
return joinPoint.proceed();

} catch (InterruptedException e) {
Expand All @@ -55,5 +58,4 @@ public Object lock(final ProceedingJoinPoint joinPoint) throws Throwable {
rLock.unlock();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package cmc.mellyserver.common.aspect.lock;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OptimisticLock {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package cmc.mellyserver.common.aspect.lock;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import cmc.mellyserver.support.exception.BusinessException;
import cmc.mellyserver.support.exception.ErrorCode;
import jakarta.persistence.OptimisticLockException;
import lombok.RequiredArgsConstructor;

@Aspect
@Component
@RequiredArgsConstructor
public class OptimisticLockAspect {

// TODO : Retry Count와 Wait Time 산정 기준 명확히 하기
private static final int RETRY_COUNT = 3;
private static final int WAIT_TIME = 50;

// TODO : Spring Retry가 아닌 직접 AOP를 구현한 이유를 명확히 설명하기
// TODO : AOP에서 Object를 반환형으로 사용하면 어떻게 실제 로직은 구체 값을 받는지 제대로 체크하기
@Around("@annotation(cmc.mellyserver.common.aspect.lock.OptimisticLock)")
public Object lock(final ProceedingJoinPoint joinPoint) throws Throwable {

for (int i = 0; i < RETRY_COUNT; i++) {

try {
return joinPoint.proceed();
} catch (OptimisticLockException e) {
Thread.sleep(WAIT_TIME);
}
}

// TODO : Retry Count가 초과했을때, 시스템이 어떻게 동작해야 하는지를 확실히 하기
throw new BusinessException(ErrorCode.SERVER_ERROR);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;

import cmc.mellyserver.dbcore.memory.Memory;
import cmc.mellyserver.dbcore.memory.memory.Memory;
import cmc.mellyserver.dbcore.notification.enums.NotificationType;
import cmc.mellyserver.dbcore.user.User;
import cmc.mellyserver.domain.comment.event.CommentEnrollEvent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,26 +41,21 @@ public class CacheConfig {
@Value("${spring.redis.cache.port}")
private int port;

/*
Redis를 운영 환경에서 사용할때는 Sentinel이나 Cluster 모드를 사용해서 고가용성을 보장할 것이고,
RedisConfiguration으로 RedisSentinelConfiguration이나 RedisClusterConfiguration을 사용할 것입니다.
현재 프로젝트에서는 가용성 보장을 위한 Replication을 하지는 못했기에 싱글 노드 기반의 RedisStandaloneConfiguration을 사용했습니다.
*/
@Bean(name = "redisCacheConnectionFactory")
RedisConnectionFactory redisCacheConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName(host);
redisStandaloneConfiguration.setPort(port);

/*
Redis 서버가 다운됐을 시, Redis에 요청을 보낸 뒤 200ms동안 응답이 없으면 Timeout이 발생하도록 구현했습니다.
현재 프로젝트 내 Redis 캐시의 응답 시간이 보통 100ms 아래로 나오는 것을 참고했을때 200ms로 command timeout을 설정한다면,
Redis 서버가 죽지 않은 상황에서 일시적인 지연으로 인해 Timeout이 발생하고, DB 쿼리가 발생하는 상황을 방지할 수 있다고 생각했습니다.
Redis 서버로 요청을 보낸 뒤 2초간 응답이 없으면 QueryTimeout을 발생시키도록 구현했습니다.
Linux 서버는 TCP 커넥션을 맺을때 SYN 패킷을 보낸 뒤 InitialRTO 값인 1초간 응답 패킷이 없으면 SYN 패킷을 재전송합니다.
따라서 한번의 재시도 요청을 고려하여 2초라는 타임아웃을 설정했습니다.
*/
LettuceClientConfiguration lettuceClientConfiguration = LettuceClientConfiguration.builder()
.commandTimeout(Duration.ofMillis(200L))
.commandTimeout(Duration.ofSeconds(1))
.build();

return new LettuceConnectionFactory(redisStandaloneConfiguration, lettuceClientConfiguration);
}

Expand Down Expand Up @@ -105,10 +100,10 @@ public CacheManager customCacheManager(

/* Cache TTL 설정 */
Map<String, RedisCacheConfiguration> redisCacheConfigMap = new ConcurrentHashMap<>();
redisCacheConfigMap.put(CacheNames.USER, defaultConfig.entryTtl(Duration.ofHours(1)));
redisCacheConfigMap.put(CacheNames.MEMORY, defaultConfig.entryTtl(Duration.ofHours(1)));
redisCacheConfigMap.put(CacheNames.GROUP, defaultConfig.entryTtl(Duration.ofHours(1)));
redisCacheConfigMap.put(CacheNames.SCRAP, defaultConfig.entryTtl(Duration.ofHours(1)));
redisCacheConfigMap.put(CacheNames.USER, defaultConfig.entryTtl(Duration.ofMinutes(5)));
redisCacheConfigMap.put(CacheNames.MEMORY, defaultConfig.entryTtl(Duration.ofMinutes(5)));
redisCacheConfigMap.put(CacheNames.GROUP, defaultConfig.entryTtl(Duration.ofMinutes(5)));
redisCacheConfigMap.put(CacheNames.SCRAP, defaultConfig.entryTtl(Duration.ofMinutes(5)));

RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory)
.withInitialCacheConfigurations(redisCacheConfigMap)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ public abstract class CacheNames {

public static final String USER = "user";

public static final String SCRAP = "scrap";
public static final String MEMORY = "memory";

public static final String GROUP = "group";

public static final String MEMORY = "memory";
public static final String SCRAP = "scrap";
}
Loading

0 comments on commit 90d56c6

Please sign in to comment.