AWS 배포 관련 포스팅 주제 |
---|
Github Action + Elastic Beanstalk으로 CI/CD |
deploy.yml 뜯어보기 (예정) |
AWS Route 53 / ACM 을 활용한 사설 도메인 연결 및 HTTPS 적용 (예정) |
AWS RDS 설정 (예정) |
AWS S3 설정 (예정) |
AWS 프리티어 과금 요소 / 배포 과정에서의 트러블 슈팅 (예정) |
- 들어가기 전에..
틀린 정보에 대한 지적을 해주신다면 감사하겠습니다!
- 토이프로젝트로 웹/앱 개발을 하는 것이므로 기본값인 '웹 서버 환경'을 선택
- 애플리케이션 이름 및 환경 이름, 도메인 등은 알아서 적당한 이름을 작성
- 필자는 Spring 백엔드 코드를 배포할 것이기 때문에 플랫폼을 Java로 선택
- 사전 설정을 반드시 '사용자 지정 구성' 선택
- 이 단계에 앞서, 서비스 역할(IAM) 생성 필요
IAM 이란?
↳ AWS의 리소스에 대한 접근제어, 권한을 "개별적으로" 가지도록 계정이나 그룹을 생성, 관리하는 서비스
↳ 처음 AWS 계정 생성 시 루트 사용자 하나가 AWS의 모든 서비스 및 리소스에 대한 권한을 갖게 됨 ➡ 보안 취약
IAM을 사용함으로써 얻는 이점
↳ 암호나 access key의 공유 없이 AWS 계정의 특정 리소스 관리 및 사용 권한을 타인에게 부여 가능, 권한의 세분화 등
- 잠시 Beanstalk 진행상황에서 멈추고, IAM에 들어가서 역할 생성 진행
- 신뢰할 수 있는 엔터티는 기본값인 'AWS 서비스' 선택
- Elastic Beanstalk을 통해 EC2 인스턴스에 배포를 진행해야 하므로 Elastic Beanstalk에 대한 권한을 갖는 역할이 필요 (EB 2단계의 "서비스 역할" 해당)
- 따라서 서비스 또는 사용 사례에 'EC2' 선택
- 다음 단계로 넘어가면 권한을 추가할 수 있는 창 표시
- 아래의 권한들을 추가
AWSElasticBeanstalkEnhancedHealth, AWSElasticBeanstalkManagedUpdatesCustomerRolePolicy
- 역할명, 설명 작성 후 역할 생성
- 역할이 생성되면 해당 역할 정보에 들어가서 신뢰 정책 편집
- 다음과 같이 수정 후 정책 업데이트
"Service":"ec2.amazonaws.com" ➡ "elasticbeanstalk.amazonaws.com"- Elastic Beanstalk으로 자동 배포를 진행해야하므로 Elastic Beanstalk이 역할을 가질 수 있도록 하는 과정
- 마찬가지 방법으로 EC2에 대한 권한을 갖는 역할을 하나 더 생성 (EB 2단계의 "EC2 인스턴스 프로파일" 해당)
- 과정은 동일하며, 2단계에서 다음의 권한들을 추가
AWSElasticBeanstalkMulticontainerDocker, AWSElasticBeanstalkWebTier, AWSElasticBeanstalkWorkerTier- EC2에 대한 신뢰정책이므로 이 역할에 대해서는 역할 생성 후 신뢰 정책을 따로 편집해 줄 필요 X
- IAM을 생성한 뒤, 다시 EB 2단계로
- 앞서 만든 두 개의 역할을 각각 선택, EC2 키 페어(암호) 선택
- 선택사항이지만, 한 걸음 더 나아가 VPC 설정까지 진행
VPC 란?
↳ 사용자만의 "가상 네트워크", AWS 클라우드의 다양한 사용자들로부터 논리적으로 분리된 독자적인 네트워크 환경을 가질 수 있음
- 목표1 : Blue Green 배포를 위해, Public Subnet을 생성
- 목표2 : RDS는 Private Subnet으로 두고 이 RDS에 접근할 수 있는 Bastion host를 따로 두어, 외부로부터 RDS 접근을 할 수 없으며 관리자만 Bastion host를 통해 RDS에 접근할 수 있도록 보안 강화
- 잠시 Beanstalk 진행상황에서 멈추고, VPC에 들어가서 진행
지원하는 VPC 대역 및 서브넷 마스크 지정 후 VPC 생성
VPC - 인터넷 게이트웨이 생성
VPC - 서브넷 생성
가용영역을 다르게 선택해서 두개 생성
VPC - 라우팅 테이블 생성
VPC의 리소스 맵이 다음과 같이 구성되면 성공
- VPC 작업을 완료한 뒤, 다시 EB 3단계로
- 앞서 만든 VPC를 선택
- 앞서 구성한 두 개의 서브넷 선택
- RDS 설정도 한번에 진행할 수 있지만, 이 글의 양이 너무 많은 관계로 EB 생성한 뒤 따로 설정과정 진행
- 기본값으로 설정
- EC2 인스턴스의 보안 그룹에 대한 설정 필요
보안그룹 이란?
↳ EC2 인스턴스로 들어오고 나가는(인바운드, 아웃바운드) 트래픽에 대한 제어
- 잠시 Beanstalk 진행상황에서 멈추고, EC2 - 보안그룹에 들어가서 진행
- 보안그룹 생성
- 인바운드 규칙을 다음과 같이 설정
MYSQL/Aurora (필자가 DB로 MySQL을 사용) : TCP : 3306
사용자 지정 TCP : TCP : 8080
HTTP : TCP : 80
HTTPS : TCP : 443
SSH : TCP : 22
- 소스를 0.0.0.0/0 으로 지정 시 모든 IP주소를 의미
- EC2 보안그룹 생성을 완료한 뒤, 다시 EB 4단계로
- 앞서 만든 보안그룹 선택
- 환경 유형 : '밸런싱된 로드', 인스턴스 : '1' ~ '2'
- 밸런싱된 로드가 즉 무중단 배포를 의미하게 됨, 토이 프젝 수준이므로 1개의 배포된 인스턴스가 최소이며 Blue Green 배포를 위해서는 최대 2개의 인스턴스 필요
- 인스턴스 유형 : 't3.micro' 제외하고 모두 삭제 (초기에는 t3.micro, t3.small이 기본값으로 지정되어 있음)
- t3.micro 인스턴스만 프리티어 제공 (다른 인스턴스 쓰면 과금!)
- 기본값으로 설정
- 앞서 생성해두었던 VPC 서브넷 선택
- 리스너의 경우, HTTPS 등을 사용할 때 추가 필요
- 프로세스에서, HTTP : 80일 때 상태 확인 경로를 /health로 지정 (추후 /health API 생성하면 배포가 정상적으로 이루어졌는지 확인하는 용도로 사용) 후 아래의 나머지 설정은 기본값으로 설정
- 기본값으로 설정
- 관리형 업데이트 비활성화 (기본값 : 활성화이므로 체크 해제)
- 배포 정책 : "추가 배치를 사용한 롤링" (기본값 : 한번에 모두)
- 추가 배치를 사용한 롤링 > 무중단 배포를 의미
- 환경 속성에서 "PORT : 8080" 추가
- 기본값은 5000이며, 필자는 SPRING을 사용하므로 SPRING 기본 포트로 지정
- 1~5의 사항을 확인 후 제출하면 EB 완성
- AWS의 IAM - 사용자에 들어가서 사용자 생성
Github Action이 배포를 위해 Elastic Beanstalk에 접근할 수 있도록 하는 사용자를 만드는 과정
- 1단계에서는 이름 기입
- 2단계 권한 설정 - 권한 옵션 : "직접 정책 연결" 후 다음의 권한 정책 추가
AdministratorAccess-AWSElasticBeanstalk- 3단계 검토 후 사용자 생성
- 생성한 사용자 정보 - 보안 자격 증명 - 액세스 키 에서 액세스 키 만들기
- "AWS 외부에서 실행되는 애플리케이션" 선택
2단계 태그(선택사항) 기본값으로 설정
- 3단계에서 액세스 키 발급됨
- .csv 파일 다운로드 시 액세스 키, 비밀 액세스 키를 로컬에 파일로 저장
- 연결할 깃허브 레포 - Settings - Secrets and variables - Actions
- New Repository Secret 후 앞서 받았던 두 개의 키 값을 적당한 키 이름과 함께 저장
- 필자는 Spring을 사용 중이므로 IntelliJ 환경을 캡처
- 디렉토리, 파일이 없는 경우 생성
- 다음 경로에 아래의 코드를 추가
files:
"/sbin/appstart":
mode: "000755"
owner: webapp
group: webapp
content: |
#!/usr/bin/env bash
JAR_PATH=/var/app/current/application.jar
# run app
killall java
java -Dfile.encoding=UTF-8 -jar $JAR_PATH
- 다음 경로에 아래의 코드를 추가
user nginx;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 33282;
events {
use epoll;
worker_connections 1024;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
include conf.d/*.conf;
map $http_upgrade $connection_upgrade {
default "upgrade";
}
upstream springboot {
server 127.0.0.1:8080;
keepalive 1024;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
location / {
proxy_pass http://springboot;
proxy_http_version 1.1;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
access_log /var/log/nginx/access.log main;
client_header_timeout 60;
client_body_timeout 60;
keepalive_timeout 60;
gzip off;
gzip_comp_level 4;
# Include the Elastic Beanstalk generated locations
include conf.d/elasticbeanstalk/healthd.conf;
}
}
- 프로젝트 최상위폴더에 아래의 코드 추가
web: appstart
- 다음 경로에 아래의 코드를 추가
- Github Action이 동작할 때 실제로 이 파일의 명령 순서로 동작하기 때문에 파일의 자세한 내용은 추가 포스팅 진행 예정
name: carGive Dev CI/CD
permissions:
checks: write
pull-requests: write
on:
pull_request:
types:
[ opened, synchronize, reopened, closed ]
workflow_dispatch: # (2) 수동 실행
jobs:
test:
runs-on: ubuntu-latest # (3) OS환경
env:
S3_BUCKET_NAME: ${{secrets.S3_BUCKET_NAME}}
S3_ACCESS_KEY: ${{secrets.S3_ACCESS_KEY}}
S3_SECRET_KEY: ${{secrets.S3_SECRET_KEY}}
if: startsWith(github.head_ref, 'feature/')
steps:
- name: Checkout
uses: actions/checkout@v2 # (4) 코드 check out : 스프링부트 프로젝트의 소스코드를 내려받는다
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: 17
distribution: adopt-hotspot # (5) 자바 설치
- name: Cache Gradle packages
uses: actions/cache@v2
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
shell: bash # (6) 권한 부여
- name: Build with Gradle
run: ./gradlew clean build
shell: bash # (8) build 시작
- name: Get current time
uses: 1466587594/get-current-time@v2
id: current-time
with:
format: YYYY-MM-DDTHH-mm-ss
utcOffset: "+09:00" # (9) build 시점의 시간확보
- name: Show Current Time
run: echo "CurrentTime=${{steps.current-time.outputs.formattedTime}}"
shell: bash # (10) 확보한 시간 보여주기
- name: Code Test
run: ./gradlew test
shell: bash # (11) 테스트 코드 실행
- name: Publish Unit Test Results
uses: EnricoMi/publish-unit-test-result-action@v1
if: ${{ always() }} # 테스트가 실패하여도 Report를 보기 위해 `always`로 설정
with:
files: build/test-results/**/*.xml
- name: Cleanup Gradle Cache
if: ${{ always() }}
run: |
rm -f ~/.gradle/caches/modules-2/modules-2.lock
rm -f ~/.gradle/caches/modules-2/gc.properties
build:
runs-on: ubuntu-latest # (3) OS환경
env:
S3_BUCKET_NAME: ${{secrets.S3_BUCKET_NAME}}
S3_ACCESS_KEY: ${{secrets.S3_ACCESS_KEY}}
S3_SECRET_KEY: ${{secrets.S3_SECRET_KEY}}
if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'develop'
steps:
- name: Checkout
uses: actions/checkout@v2 # (4) 코드 check out : 스프링부트 프로젝트의 소스코드를 내려받는다
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: 17 # (5) 자바 설치
distribution: adopt-hotspot
- name: Cache Gradle packages
uses: actions/cache@v2
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
shell: bash # (6) 권한 부여
- name: Build with Gradle
run: ./gradlew clean bootjar -Pprofile=prod
shell: bash # (8) build 시작
- name: Get current time
uses: 1466587594/get-current-time@v2
id: current-time
with:
format: YYYY-MM-DDTHH-mm-ss
utcOffset: "+09:00" # (9) build 시점의 시간확보
- name: Show Current Time
run: echo "CurrentTime=${{steps.current-time.outputs.formattedTime}}"
shell: bash # (10) 확보한 시간 보여주기
- name: Generate deployment package
run: |
mkdir -p deploy
cp build/libs/*.jar deploy/application.jar
cp Procfile deploy/Procfile
cp -r .ebextensions deploy/.ebextensions
cp -r .platform deploy/.platform
cd deploy && zip -r deploy.zip .
- name: Beanstalk Deploy
uses: einaregilsson/beanstalk-deploy@v20
with:
aws_access_key: ${{secrets.AWS_ACCESS_KEY}}
aws_secret_key: ${{secrets.AWS_SECRET_KEY}}
application_name: carGiveBeanstalk
environment_name: CarGiveBeanstalk-env
version_label: github-action-${{steps.current-time.outputs.formattedTime}}
region: ap-northeast-2
deployment_package: deploy/deploy.zip
wait_for_environment_recovery: 60
- name: Cleanup Gradle Cache
if: ${{ always() }}
run: |
rm -f ~/.gradle/caches/modules-2/modules-2.lock
rm -f ~/.gradle/caches/modules-2/gc.properties
- 이제 다음과 같이 Github Action을 통해 무중단 배포까지 이어짐
- 참고로 EB 설정의 4-2 마지막 부분에서
/health
로 상태 확인 경로를 지정해주었기에, 아래와 같이 API를 만들어둔 모습