ASG 적용 배경
우선 우리 프로젝트는 지난 데이터가 부족하기 때문에 트래픽의 예측이 불가능하다.
이러한 상황에서 단순하게 서버를 수직 스케일링 하는 것은 자원의 낭비일 뿐이다.
좀 더 유연하게 트래픽에 대처하고, 서버 가용성을 높여 24시간 서버의 다운 없이 서비스를 제공하기 위해
우리는 Auto Scaling Group을 적용하기로 하였다.
User Data 적용
시작 템플릿에서 유저 데이터를 정의해줘야 한다.
- 유저 데이터란 인스턴스를 처음 시작할 때 실행되는 스크립트를 의미한다.
- 이 스크립트를 통해 우리는 인스턴스를 우리가 원하는 대로 초기화 할 수 있다.
https://2junbeom.tistory.com/116
[AWS] aws 강의 섹션 5 (EC2 심층 분석 - EC2 유형, 접속 방법, 구성 옵션, 보안 그룹)
Amazon EC2EC2란 Elastic Compute Cloud의 약자로 Infrastructure as a Service다.EC2란 단순한 하나의 서비스가 아니라 여러 기능을 포괄하는 개념이다.가상 머신 빌리기EBS 볼륨이라는 드라이브에 데이터 저장여
2junbeom.tistory.com
- 자세한 내용은 위 글에 정리돼있다.
- 각설하고 내가 유저데이터를 통해 하려는 바는 다음과 같다.
- s3에서 도커 컴포즈 파일 가져오기
- s3에서 도커 컴포즈 파일에 등록할 환경변수 파일 가져오기
- 도커 컴포즈 파일 기반으로 컨테이너 동작 시키기
- 이제 이 흐름에 맞추어 유저 데이터를 작성해보자, 이를 통해 도커 이미지를 기반으로 바로 톰캣 서버를 띄울 수 있다.
User Data 작성
#!/bin/bash
# --- 시스템 업데이트 및 필수 패키지 설치 ---
sudo yum update -y
sudo yum install -y docker aws-cli
# --- Docker 서비스 시작 및 부팅 시 자동 실행 설정 ---
sudo service docker start
sudo systemctl enable docker
# Docker 명령어를 ec2-user가 사용할 수 있도록 설정
sudo usermod -aG docker ec2-user
# --- 작업 디렉토리 준비 ---
DEPLOY_DIR="/home/ec2-user/deploy"
sudo mkdir -p $DEPLOY_DIR
sudo chown ec2-user:ec2-user $DEPLOY_DIR
# --- S3에서 Docker Compose 파일과 .env 파일 다운로드 ---
BUCKET_NAME="버킷 이름"
COMPOSE_FILE="도커 컴포즈 파일 이름"
ENV_FILE="환경변수 파일 이름"
AWS_REGION="ap-northeast-2"
# AWS CLI를 사용할 때 EC2 IAM 역할에 S3 접근 권한이 필요합니다.
aws s3 cp s3://$BUCKET_NAME/$COMPOSE_FILE $DEPLOY_DIR/ --region $AWS_REGION
aws s3 cp s3://$BUCKET_NAME/$ENV_FILE $DEPLOY_DIR/ --region $AWS_REGION
# 파일이 성공적으로 다운로드되었는지 확인
if [ ! -f "$DEPLOY_DIR/$COMPOSE_FILE" ] || [ ! -f "$DEPLOY_DIR/$ENV_FILE" ]; then
echo "Failed to download $COMPOSE_FILE or $ENV_FILE from S3. Exiting." >> /var/log/user-data.log
exit 1
fi
# --- Docker Compose 설치 ---
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# Docker Compose 버전 확인 (디버깅 용도)
docker-compose --version >> /var/log/user-data.log
# --- Docker Compose 실행 ---
cd $DEPLOY_DIR
sudo docker-compose pull
sudo docker-compose up -d >> $DEPLOY_DIR/deployment.log 2>&1
if [ $? -ne 0 ]; then
echo "Docker Compose failed to start. Check $DEPLOY_DIR/deployment.log for details." >> /var/log/user-data.log
exit 1
fi
# --- Docker 이미지 정리 ---
sudo docker image prune -f
# --- 완료 로그 출력 ---
echo "Deployment completed successfully on $(date)" >> $DEPLOY_DIR/deployment.log
흐름은 아까 위에서 설명한 것과 같다. 다만 여기서 주의해야할 사항이 있다.
- s3에서 객체를 가져오기 위해 필요한 IAM 정책이다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::*/*"
}
]
}
- 이렇게 간단하게 s3에서 객체를 가져올 수만 있다는 정책을 생성해주었다.
- 참고로 버킷은 따로 권한 설정을 해주지 않는 이상, 외부에서 접근이 불가능하도록 설정하여 환경변수 파일이 외부로 노출되지 않도록 하였다.
- 현재 버킷의 객체는 ec2 인스턴스에서만 조회가 가능하다.
- 이 외에도 클라우드 워치 로그 등 필요한 정책들을 시작 템플릿에 정의해주었다.
인스턴스 용량 설정
- 우선 시작 용량은 1로 설정했다. 회원 모집 기간 이외에는 트래픽이 많이 몰리지 않을 것이기 때문이다.
- 최대는 3으로 설정했으며, 데이터가 조금 쌓여봐야 최대값을 정밀하게 설정할 수 있을 것 같다.
스케일링 기준
- 현재 동적 스케일링 기준은 CPU 사용률을 70으로 유지하는 것이 기준이다.
- 현재 동적 스케일링 기준을 확실하게 잡을 수 없기에 AWS 사용자 가이드를 참고했다.
- 밑의 가이드에서 cpu 사용률 60~70이 넘어가는 것을 임계점으로 수평 스케일링을 권장한다고 나와있다.
Amazon EC2 Auto Scaling에 대한 단계 및 간단한 조정 정책 - Amazon EC2 Auto Scaling
PercentChangeInCapacity - 그룹의 현재 용량을 지정된 퍼센트만큼 늘리거나 줄입니다. 양의 값은 용량을 늘리고, 음의 값은 용량을 줄입니다. 예: 현재 용량이 10개이고 조절이 10%인 경우, 이 정책이 수
docs.aws.amazon.com
인스턴스 가용률
- 최소 가용률을 50%로 설정해주고, 최대 가용률을 100%로 설정해주었다. (사진은 다르게 돼있음)
- 최소 가용 백분율이란 최소한으로 유지되는 정상 상태의 인스턴스 %를 의미한다.
- 최대 가용률이란 업데이트를 할 때 띄울 수 있는 인스턴스의 %를 의미한다.
- 예를 들어 2개의 인스턴스가 실행중일 때 롤링 업데이트를 실시한다면,
- 최소 1개의 인스턴스는 정상 작동을 보장하고, 최대 2개 이상의 인스턴스는 운용하지 않겠다는 의미이다.
- 이렇게 설정한 이유는 우리가 돈이 없기도 하며, 대규모 서비스도 아닐 뿐더러, 새로운 기능이 추가되는 시기는
회원 모집 홍보 시기 이전이기 때문에 항상 100%의 가용성을 유지하며 업데이트를 진행할 필요가 없기 때문이다. - 따라서 롤링 업데이트 방식을 50%, 100%로 설정하여 무중단 배포를 구현하고자 했다.
로드밸런서와 타깃 그룹 연결
- 우리 프로젝트는 공식 홈페이지 개발 즉, 7계층의 HTTP, HTTPS 프로토콜을 이용한다.
- 애플리케이션 로드밸런서는 HTTP, HTTPS의 트래픽 분산에 특화 되어 있다.
- 또한 NLB, GWLB 같은 경우는 굉장한 고성능을 요구하는 작업에 적합하며, 동시에 비용도 굉장히 많이 들기 때문에,
고민의 여지 없이 바로 애플리케이션 로드 밸런서를 선택했다. - 아직 프론트엔드가 HTTPS 배포가 이뤄지지 않았기 때문에 SSL 인증서는 연결하지 않았다.
- 타깃그룹과 연결하고, ASG와 타깃그룹을 간단하게 연결 시켰다.
ASG 설정 성공
- 다음과 같이 현재 한 개의 인스턴스가 ASG에서 잘 돌아가고 있는 것을 확인할 수 있다.
로드밸런서를 이용한 테스트
- 로드밸런서의 DNS 주소로 바로 요청을 보내보겠다.
- 로드밸런서로 보낸 요청이 무사히 타깃 그룹에 전달된 것을 확인할 수 있다.
- 유저 데이터를 이용한 부트스트래핑도 잘 이뤄진 것을 확인할 수 있다.
CI/CD 파이프라인 재구축
- 이제 파이프라인을 재구축 해야한다.
- 깃허브 액션을 조금만 수정해주었다.
- 수정할 워크 플로우는 다음과 같다.
- 빌드를 통한 빌드 오류 사전 검출
- 빌드에 성공했다면, 도커 허브에 도커 이미지 생성 후 푸시
- S3에 도커 컴포즈 파일 업로드
- 환경변수 파일은 깃허브가 아닌 오로지 S3에서만 관리한다.
- ASG 인스턴스 새로고침 명령
- 이러한 워크플로우를 통해 인스턴스를 새로고침 시키면 유저데이터에서 새롭게 업로드된 도커 컴포즈 파일을 가져와,
도커 이미지를 바탕으로 서버에서 도커 컨테이너를 구축한다.
CI/CD 코드
name: Java CI/CD with Gradle
# 워크플로우가 실행될 조건을 정의합니다.
on:
push:
branches: [ "feat/#64/aws-auto-scaling-group" ] # main 브랜치에 push가 발생하면 실행됩니다.
# 워크플로우가 접근할 수 있는 권한을 설정합니다.
permissions:
contents: read
jobs:
# Docker 이미지를 빌드하고 Docker Hub에 푸시하는 작업을 정의합니다.
build-docker-image:
runs-on: ubuntu-latest # 워크플로우가 실행될 환경을 지정합니다.
steps:
# 리포지토리의 코드를 체크아웃합니다.
- uses: actions/checkout@v3
# JDK 17을 설치하고 환경을 설정합니다.
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
# Gradle Wrapper 파일에 실행 권한을 부여합니다.
- name: Grant Execute Permission For Gradlew
run: chmod +x gradlew
# gradle caching - 빌드 시간 향상
- 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-
# Gradle을 사용하여 프로젝트를 빌드합니다.
- name: Build With Gradle
run: ./gradlew build -x test
# Docker 이미지를 빌드합니다.
- name: docker image build
run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/github-actions-demo .
# Docker Hub에 로그인합니다. 로그인 정보는 GitHub Secrets를 통해 안전하게 관리됩니다.
- name: docker login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }} # Docker Hub 사용자 이름
password: ${{ secrets.DOCKERHUB_PASSWORD }} # Docker Hub 비밀번호
# 빌드된 Docker 이미지를 Docker Hub에 푸시합니다.
- name: docker Hub push
run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/github-actions-demo
# 권한 주입
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.SECRET_KEY }}
aws-region: ${{ secrets.REGION }}
# S3에 Docker Compose 파일 업로드
- name: Upload Docker Compose to S3
run: |
aws s3 cp docker-compose.yml s3://${{ secrets.BUCKET_NAME }}/docker-compose.yml --region ${{ secrets.REGION }}
# ASG 재시작
- name: Trigger ASG Rolling Update
run: |
aws autoscaling start-instance-refresh \
--auto-scaling-group-name ${{ secrets.ASG_NAME }} \
--region ${{ secrets.REGION }} \
--preferences '{"MinHealthyPercentage": 50}'
- 주의해야할 부분은 역시 권한 주입이다.
- aws IAM 사용자에서 액세스키와 시크릿 키를 다운받아 유저 권한을 바탕으로 S3 업로드와 인스턴스 새로고침을 수행할 수 있도록 해야한다.
- 물론 해당 유저는 적절한 권한 설정이 있어야 한다.
- 이렇게 만들고 작업 브랜치에 push를 해서 간단하게 테스트를 진행해보았다.
- ASG의 인스턴스가 새로 고침 상태에 들어갔다.
- 기존 인스턴스가 1개인 상황에서 롤링 업데이트가 이뤄졌다.
- 이러한 상황에서는 최대 가용률이 100% 이지만 최소 가용률을 보장하기 위해 1개의 인스턴스를 실행하며 동시에 새로운 서버가 증설되는 것을 확인할 수 있었다.
- 이후 새로운 서버가 만들어지고 나서 바로 기존의 서버가 종료되었다.
- 새로운 테스트 api를 만들어 배포했으며,
무사히 새로운 이미지를 기반으로 업데이트에 성공한 것을 확인할 수 있다.
우리가 얻을 수 있는 것
- 예기치 못한 오류로 인해 서버가 다운 됐을 때 health check를 통해 바로 새로운 서버를 만들어 서버 가용성을 높일 수 있다.
- 트래픽이 갑자기 급증하였을 때 동적 스케일링을 통해 트래픽에 유연하게 대처할 수 있다.
추가로 필요한 작업
- 이제 로드밸런서를 만들었으니, 인스턴스들은 더 이상 퍼블릭 서브넷에 존재할 이유가 없다.
- 프라이빗 서브넷으로 옮겨 VPC 내부의 NAT를 통해서만 소통이 가능하도록 하여 보안성을 확보해야 한다.
'☃️❄️개발일지, 트러블슈팅❄️☃️' 카테고리의 다른 글
[개발일지] Fetch join을 통한 N+1 문제 해결 (0) | 2025.03.18 |
---|---|
[개발일지] 시스템 아키텍처 설계 (0) | 2025.01.21 |
[개발일지] webp 확장자를 통한 이미지 제공 최적화 작업 (0) | 2025.01.03 |
[트러블 슈팅] 로그인 방식과 보안에 대한 고찰 - 2 (jwt 토큰과 XSS, CSRF 공격, 액세스 토큰과 리프레시 토큰의 저장 위치) (4) | 2024.12.03 |
[트러블 슈팅] 로그인 방식과 보안에 대한 고찰 - 1 (세션 로그인 방식과 jwt 토큰 발급 방식) (1) | 2024.12.02 |