작년에 개발했던 서비스이다.
서버를 AWS를 사용하고 있었는데 기존 아키텍처가 불안정해서 안정성과 가용성을 높여서 새롭게 아키텍처를 설계했다.
이후에, 또 비슷한 설계를 대비해 블로그에 기록을 해놓는다.
AWS 배포 아키텍처 구조도
깃허브 Webhook을 통한 젠킨스 빌드 후 CodeDeploy를 통한 블루/그린 배포
이 때, Auto Scaling 형태로 배포해 가용성을 높였다.
IAM
IAM 사용자
- 루트 계정 MFA 적용: Google OTP 사용.
- 각자의 iam을 사용하는 것을 권장(han, park, song, ...): 누가 어떤 작업을 했는지 추적할 수 있음.
easy-yum-jenkins-user
사용자 추가
jenkins에서 S3와 CodeDeploy에 접근하기 위한 IAM 유저 추가, Access key 파일은 따로 보관.
<권한>
AmazonS3FullAccess
AWSCodeDeployFullAccess
IAM 역할
easy-yum-ec2-deploy
역할 추가
ec2에 적용할 IAM 역할, S3와 CodeDeploy에 접근할 수 있다.
AWS Parameter Store에 접근할 수 있다.
CloudWatch Agent에 접근할 수 있다.
<권한>
AmazonS3FullAccess
AWSCodeDeployFullAccess
AmazonSSMFullAccess
CloudWatchAgentServerPolicyeasy-yum-codedeploy
역할 추가
CodeDeploy 배포 그룹에 적용할 IAM 역할, 기본적으로 AWSCodeDeployRole이 적용되어 있어야 한다.
<권한>
AWSCodeDeployRole
easy-yum-blue-green-policy (다음에서 설명)
IAM 정책
easy-yum-blue-green-policy
정책 추가
CodeDeploy 블루 그린 배포를 위한 정책 추가, 해당 정책을easy-yum-codedeploy
역할에 추가한다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"iam:PassRole",
"ec2:CreateTags",
"ec2:RunInstances"
],
"Resource": "*"
}
]
}
젠킨스 및 테스트용 EC2
CI를 위해 젠킨스 사용. 따라서, 젠킨스 설치를 위한 EC2를 생성한다.
인스턴스 생성
이름: easy-yum-jenkins
플랫폼: Ubuntu 20.04, x86
인스턴스 유형: t3.micro
스토리지: EBS 범용 SSD(gp3) 16GB
IAM 역할: easy-yum-ec2-deploy
보안 그룹: jenkins-security-group
-> 인바운드 허용 포트: 22(ssh), 80(http), 8080(테스트), 8090(젠킨스)
탄력적 IP easy-yum-for-jenkins
적용.
EC2에 젠킨스 설치
EC에 8090포트로 젠킨스를 설치했다.
Ubuntu Jenkins 설치
젠킨스 설정
- 시스템 설정
깃허브 서버 연결.
Credentials는github personal access token
사용. - 플러그인 설치
environment injector: 젠킨스 빌드시 환경변수 input.
post build task: 젠킨스 빌드 이후 추가 작업 설정.
AWS CodeDeploy: CodeDeploy와 연동.
Item 생성
easy-yum-development
테스트용 Item.
Easy-Yum/SpringBoot-Application 리포지토리의develop
브랜치와 webhook 연결.
빌드와 동시에 젠킨스를 설치한 EC2의 8080 포트에 프로젝트를 배포한다.
post build task 코드
#!/bin/bash
echo $(pwd)
if [[ "$(lsof -t -i:${SERVICE_PORT})" -gt "0" ]]
then
echo kill previous project
if [[ "$(kill -9 $(lsof -t -i:${SERVICE_PORT}))" -eq "0" ]]
then
echo successfully killed previous project
else
echo error killing previous project
fi
else
echo no previous project
fi
echo start new project
nohup java -Dspring.profiles.active=development -jar $(pwd)/build/libs/easy-yum-0.0.1-SNAPSHOT.jar > /home/ubuntu/test.log &
현재 실행중인 프로젝트가 있으면 kill 이후 새로운 프로젝트 백그라운드 실행.
profile은 development
로 설정.
로그는 /home/ubuntu/test.log에서 확인할 수 있다.
easy-yum-production
운영용 Item.
Easy-Yum/SpringBoot-Application 리포지토리의master
브랜치와 webhook 연결.
빌드와 동시에 프로젝트를 S3에 업로드하고 CodeDeploy 배포 그룹에 배포를 요청한다. 더 자세한 내용은 아래 CodeDeploy 설정 부분에서 다룬다.
깃허브 Webhook 설정
[Easy-Yum/SpringBoot-Application 리포지토리 -> Settings -> Webhooks]에서 젠킨스에 대한 깃허브 Webhook을 생성했다.
젠킨스 작동을 위한 EC2 swap 공간 설정
프리티어 t2.micro 유형은 RAM이 1GB이기 때문에, 젠킨스를 돌리기에 매우 부족하다.
따라서 SSD의 공간의 일정 부분을 RAM과의 swap 공간으로 사용했다.
EC2 프리티어(t2.miocro)에서 Jenkins 용량 초과 문제
AMI용 EC2
CodeDeploy 배포를 위한 EC2의 기본 세팅을 위해 AMI용 EC2를 생성한다.
인스턴스 생성
이름: easy-yum-for-ami
플랫폼: Ubuntu 20.04, x86
인스턴스 유형: t3.micro
스토리지: EBS 범용 SSD(gp3) 30GB
IAM 역할: easy-yum-ec2-deploy
보안 그룹: ec2-security-group
-> 인바운드 허용 포트: 22(ssh), 80(http), 9000(운영)
인스턴스 세팅
- apt 업데이트
sudo apt update sudo apt upgrade
- java 설치
sudo apt install openjdk-11-jdk
~/.bashrc에 환경변수 추가
export JAVA_HOME=$(dirname $(dirname $(readlink -f $(which java))))
export PATH=$PATH:$JAVA_HOME/bin
- CodeDeploy Agent 설치
sudo apt install ruby cd /home/ubuntu curl -O https://aws-codedeploy-us-east-2.s3.us-east-2.amazonaws.com/latest/install sudo chmod +x ./install sudo ./install auto # 설치 확인 sudo service codedeploy-agent status
- rc.local 설정
rc.local은 EC2 인스턴스가 처음 생성될 때 실행될 명령어이다. 이후 auto-scaling을 통한 인스턴스 자동 생성시 활용할 수 있다.
처음 인스턴스를 생성했을 때, rc.local이 없었기 때문에 파일을 추가하고 활성화시킨다.
Ubuntu Ubuntu에서 rc.local 없을 때, 생성하는 방법
rc.local 내용
#!/bin/bash
BUILD_JAR=$(ls /home/ubuntu/jenkins/build/libs/easy-yum-0.0.1-SNAPSHOT.jar) # jar가 위치하는 곳
JAR_NAME=$(basename $BUILD_JAR)
echo "> build 파일명: $JAR_NAME" >> /home/ubuntu/deploy.log
echo "> build 파일 복사" >> /home/ubuntu/deploy.log
DEPLOY_PATH=/home/ubuntu/
cp $BUILD_JAR $DEPLOY_PATH
echo "> 현재 실행중인 애플리케이션 pid 확인" >> /home/ubuntu/deploy.log
CURRENT_PID=$(pgrep -f $JAR_NAME)
if [ -z $CURRENT_PID ]
then
echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다." >> /home/ubuntu/deploy.log
else
echo "> kill -15 $CURRENT_PID"
sudo kill -15 $CURRENT_PID
sleep 5
fi
DEPLOY_JAR=$DEPLOY_PATH$JAR_NAME
echo "> DEPLOY_JAR 배포" >> /home/ubuntu/deploy.log
nohup java -jar -Dspring.profiles.active=production $DEPLOY_JAR >> /home/ubuntu/deploy.log 2>/home/ubuntu/deploy_err.log &
실행 중인 프로젝트가 있으면 kill 이후 새로운 프로젝트 실행.
실행 로그는 deploy.log에 저장되고, 에러 발생시 로그는 deploy_err.log에 저장된다.
- CloudWatch Agent 설치
AWS CloudWatch logs - 로그 파일 수집
메모리 이용률 등의 CloudWatch custom metric을 설정하고 CloudWatch logs에 프로덕션 로그를 통합하기 위함. -
wget https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb sudo dpkg -i -E ./amazon-cloudwatch-agent.deb
/opt/aws/amazon-cloudwatch-agent/bin/config.json 설정파일 작성
{
"agent": {
"metrics_collection_interval": 60,
"run_as_user": "root"
},
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{
"file_path": "/home/ubuntu/deploy.log",
"log_group_name": "easy-yum-log-group",
"log_stream_name": "deploy"
},
{
"file_path": "/home/ubuntu/deploy_err.log",
"log_group_name": "easy-yum-log-group",
"log_stream_name": "error"
}
]
}
}
},
"metrics": {
"metrics_collected": {
"disk": {
"measurement": [
"used_percent",
"used",
"total"
],
"metrics_collection_interval": 60,
"resources": [
"*"
]
},
"mem": {
"measurement": [
"mem_used_percent",
"mem_total",
"mem_used"
],
"metrics_collection_interval": 60
}
}
}
}
ami 생성 전 /home/ubuntu의 기본 세팅은 다음과 같다.
AMI
앞서 만든 인스턴스에 대한 AMI를 생성한다.
CodeDeploy를 통한 배포 혹은 Auto Scaling 시 해당 AMI 세팅을 기반으로 인스턴스가 생성된다.
이미지 생성
이름: easy-yum-ec2-ami
스토리지: EBS 범용 SSD(gp3) 30GB
인스턴스 유형: t3.micro
시작 템플릿
Auto Scaling Group을 위해서 앞서 만든 AMI를 사용한 시작 템플릿을 생성한다.
시작 템플릿 생성
이름: easy-yum-ec2-launch-template
AMI: easy-yum-ec2-ami
스토리지: EBS 범용 SSD(gp3) 30GB
IAM 역할: easy-yum-ec2-deploy
보안 그룹: ec2-security-group
-> 인바운드 허용 포트: 22(ssh), 80(http), 9000(운영)
대상 그룹
로드밸런서가 target할 대상 그룹을 생성한다.
대상 그룹 생성
이름: easy-yum-target-group
프로토콜: HTTP:9000 -> 로드밸런서가 대상 그룹의 9000포트로 트래픽을 보낸다.
상태 검사 경로: /health -> 프로젝트 GET: /health에 미리 200 상태 코드를 반환하도록 설정해 놓았다.
고급 상태 검사 설정:
- 정상 임계 값: 2
- 비정상 임계 값: 2
- 제한 시간: 2
- 간격: 5
- 성공 코드: 200
로드밸런서
Application Load Balancer 생성
이름: easy-yum-load-balancer
매핑: ap-northeast-2a ~ 2d
보안 그룹: ec2-security-group
-> 인바운드 허용 포트: 80(http)
리스너: HTTP:80 -> 로드밸런스의 80포트로 연결 요청
라우팅: easy-yum-target-group
Auto Scaling 그룹
가용성을 위해 CodeDeploy를 통한 Auto Scaling 형태로 배포한다. 이를 위해 Auto Scaling 그룹을 생성한다.
Auto Scaling 그룹 생성
이름: easy-yum-auto-scaling-group
-> 이는 초기 그룹 이름으로 CodeDeploy 블루그린 배포시 새로운 Auto Scaling 그룹을 생성하고 이전 그룹은 삭제하기 때문에 이름은 계속 변경된다.
시작 템플릿: easy-yum-ec2-launch-template
가용영역: ap-northeast-2a ~ 2d
대상 그룹: easy-yum-target-group
상태 확인: ELB, 50초
모니터링: CloudWatch 내에서 그룹 지표 수집 활성화.
원하는 용량: 2
최소 용량: 2
최대 용량: 3
대상 추적 조정 정책:
- 지표 유형: 평균 CPU 사용률
- CPU 점유율이 50% 이상인 상태가 3분 이상 지속되었을 때, 인스턴스 확대.
- CPU 점유율이 30% 이하인 상태가 15분 이상 지속되었을 때, 인스턴스 축소.
생성 인스턴스 이름: ec2-auto-scaling
Auto Scaling 테스트
jmeter를 활용해 서버에 부하를 주어 인스턴스가 자동으로 확장, 축소되는지 테스트.
아래 그래프는 100명의 사용자가 쉬지 않고 요청을 주어 약 9만 건의 요청을 보냈을 때의 상황이다.
초록선은 CPU 사용률, 파란선은 인스턴스의 개수이다.
자세히 보면 CPU 사용률이 50%를 넘고 5분 후에 인스턴스가 2개에서 3개로 확장되었고, 30% 이하가 된 후(jmeter 요청을 멈췄을 때) 15분 후에 인스턴스가 3개에서 2개로 축소되었다.
실제로 부하에 따라 auto scaling group에서 인스턴스가 자동으로 생성, 삭제, 그리고 rc.local을 통해 알아서 프로젝트 배포까지 되는 것을 확인하였다.
S3
배포용 버킷 생성
이름: easy-yum-deploy
젠킨스에서 빌드 이후 프로젝트 파일을 zip 형태로 압축해서 easy-yum-deploy
버킷에 저장.
CodeDeploy에서 해당 버킷의 프로젝트 파일을 받아 배포를 시작할 수 있다.
CodeDeploy
애플리케이션 생성
이름: easy-yum-deploy-app
컴퓨팅 플랫폼: EC2/온프레미스
배포 그룹 생성
이름: easy-yum-deploy-group
IAM 역할: easy-yum-codedeploy
배포 유형: 블루/그린
Auto Scaling 그룹: easy-yum-auto-scaling-group
배포 설정
- 배포 그룹의 원본 인스턴스 종료: 그린 그룹 배포 5분 후 블루 그룹 종료
- 배포 구성: CodeDeployDefault.HalfAtATime
대상 그룹: easy-yum-target-group
젠킨스 설정
앞서 생성한 젠킨스 easy-yum-production
Item -> 구성 에서 빌드 후 조치로 다음과 같이 설정한다.
맨 아래 User Access/Secret keys 부분은 앞서 생성한 IAM easy-yum-jenkins-user
의 key 값을 넣는다.
이제 프로젝트 코드를 깃허브에 push 하면 CodeDeploy를 통해 블루/그린 배포를 할 수 있다.
배포용 EC2는 기본적으로 Auto Scaling 그룹에 속하고 최소 2개의 인스턴스에 대해 로드밸런서에 의해 관리된다.
프로젝트에 appspec.yml 추가
appspec.yml
appspec.yml은 CodeDeploy 배포시 실행하는 설정파일이다.
본 프로젝트에서는 다음과 같이 설정한다.
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/jenkins
overwrite: yes
permissions:
- object: /
pattern: "**"
owner: ubuntu
group: ubuntu
hooks:
BeforeInstall:
- location: scripts/remove_before.sh
ApplicationStart:
- location: scripts/deploy.sh
timeout: 60
runas: ubuntu
- file.destination: /home/ubuntu/jenkins -> 해당 디렉토리에 프로젝트 파일을 저장한다.
- hooks: CodeDeploy는 배포 진행 상황에 따라 단계가 있는데 각 단계별로 어떤 스크립트를 실행할지 설정할 수 있다.
- BeforeInstall: 프로젝트 설치 전에 이전 배포 파일과 이전 프로젝트 파일을 삭제한다.
- ApplicationStart: 애플리케이션을 백그라운드 실행한다.
Hooks 스크립트
hooks에서 각 단계에 해당하는 스크립트의 내용은 다음과 같다.
- remove_before.sh
#!/bin/bash find /opt/codedeploy-agent/deployment-root/334c4da0-0a61-47a0-8e33-4e4037a8e2d6/* -maxdepth 0 -type 'd' | grep -v $(stat -c '%Y:%n' /opt/codedeploy-agent/deployment-root/334c4da0-0a61-47a0-8e33-4e4037a8e2d6/* | sort -t: -n | tail -1 | cut -d: -f2- | cut -c 3-) | xargs rm -rf sudo rm -rf /home/ubuntu/*
- deploy.sh실행 시 프로파일은
production
으로 설정. #!/bin/bash BUILD_JAR=$(ls /home/ubuntu/jenkins/build/libs/easy-yum-0.0.1-SNAPSHOT.jar) # jar가 위치하는 곳 JAR_NAME=$(basename $BUILD_JAR) echo "> build 파일명: $JAR_NAME" >> /home/ubuntu/deploy.log echo "> build 파일 복사" >> /home/ubuntu/deploy.log DEPLOY_PATH=/home/ubuntu/ cp $BUILD_JAR $DEPLOY_PATH echo "> 현재 실행중인 애플리케이션 pid 확인" >> /home/ubuntu/deploy.log CURRENT\_PID=$(pgrep -f $JAR\_NAME) if \[ -z $CURRENT\_PID \] then echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다." >> /home/ubuntu/deploy.log else echo "> kill -15 $CURRENT\_PID" >> /home/ubuntu/deploy.log sudo kill -15 $CURRENT\_PID sleep 5 fi DEPLOY\_JAR=$DEPLOY\_PATH$JAR\_NAME echo "> DEPLOY\_JAR 배포" >> /home/ubuntu/deploy.log nohup java -jar -Dspring.profiles.active=production $DEPLOY\_JAR >> /home/ubuntu/deploy.log 2>/home/ubuntu/deploy\_err.log &
Rout53
프론트와 백의 통신이 개발, 운영 버전 모두 ip 주소로 통신하고 있는데, 헷갈릴 수 있기 때문에 프론트와 백 통신시 도메인을 사용하기로 결정.
도메인 구매
AWS에서 직접 도메인을 구매하는 것은 비싸기 때문에 GoDaddy에서 도메인 구매.
api-easy-yum.xyz 구매 (2022. 1. 12 구매, 2023. 1. 13 갱신)
호스팅 영역 생성
- AWS에서 호스팅 영역 생성
이름: `api-easy-yum.xyz`
- GoDaddy 네임 서버에 AWS 네임 서버 입력
[image:C548BF56-5912-435F-8557-C49E9AEA02BC-49452-0000006902791E61/스크린샷 2022-01-21 오후 5.32.24.png]
레코드 생성
- 개발 환경
이름: `development.api-easy-yum.xyz`
레코드 유형: `A`
값: `easy-yum-for-jenkins의 public ip`
- 프로덕션 환경
이름: `production.api-easy-yum.xyz`
레코드 유형: `A`
값: `로드밸런서 public dns`
Rout53
프론트와 백의 통신이 개발, 운영 버전 모두 ip 주소로 통신하고 있는데, 헷갈릴 수 있기 때문에 프론트와 백 통신시 도메인을 사용하기로 결정.
도메인 구매
AWS에서 직접 도메인을 구매하는 것은 비싸기 때문에 GoDaddy에서 도메인 구매.
api-easy-yum.xyz 구매 (2022. 1. 12 구매, 2023. 1. 13 갱신)
호스팅 영역 생성
- AWS에서 호스팅 영역 생성
이름:api-easy-yum.xyz
- GoDaddy 네임 서버에 AWS 네임 서버 입력
레코드 생성
- 개발 환경
이름:development.api-easy-yum.xyz
레코드 유형:A
값:easy-yum-for-jenkins의 public ip
- 프로덕션 환경
이름:production.api-easy-yum.xyz
레코드 유형:A
값:로드밸런서 public dns
프론트 -> 백
테스트(깃허브 develop 브랜치) 서버는 easy-yum-for-jenkins
의 8080포트에 배포되며, development.api-easy-yum.xyz:8080
을 통해 서버에 접근할 수 있다. (route53 적용)
운영(깃허브 master 브랜치) 서버는 로드밸런서 dns를 통해 접근할 수 있다.production.api-easy-yum.xyz
(route53 적용)
'devops > cicd' 카테고리의 다른 글
Jenkins - Slack 연동 (0) | 2022.08.19 |
---|---|
[CI/CD] Gitlab + Jenkins + Nginx + Docker + AWS EC2 - 무중단 배포 (4) | 2022.08.18 |
[Jira] Gitlab 서버 - Jira Cloud 연동 (2) | 2022.07.01 |