이번 프로젝트에선 Python Flask를 이용하여 서버를 구축했다.
이전부터 하고싶었던 CI/CD작업을 해보고자 Github Action을 이용해서 EC2 인스턴스에 Flask 서버를 배포하는 방법을 찾아봤지만,
eb에 배포하는 내용들밖에 없었다.
결국엔 Java를 사용해 CI/CD를 구축하신 분의 블로그를 참고하여 Python의 Flask를 배포해보기로 했다.
정말 많은 우여곡절들이 있었지만, 위의 블로그가 기본 인프라 구축에 많은 도움이 됐다.
Github Actions를 통한 CI/CD구조는 다음과 같다.
CI/CD의 구조에서 S3버킷은 Github Actions로부터 만들어진 zip를 저장하는 역할을 한다.
Actions Runner를 aws 서비스에 접근 가능한 상태로 만들기 위해서 AWS계정에 대한 ACCESS_TOKEN이 필요하므로
S3FullAccess, CodeDeployFullAccess를 가진 IAM사용자를 만든 뒤 배포하고자 하는 github repository에 엑세스키를 등록한다.
액세스 키 등록은 아래의 경로로 들어가서 등록할 수 있다.
github repository의 Settings -> Secrets and variables -> Actions -> New repository secret
EC2는 S3에서 zip파일을 가져와 CodeDeploy를 수행하고, 애플리케이션을 배포하는 역할을 한다.
따라서 EC2인스턴스에 S3FullAccess, CodeDeployFullAccess 권한을 추가한다.
실습에 사용된 운영체제 및 버전은 Ubuntu - 22.04 version이다.
아래의 명령어를 차례대로 입력한다.
# apt 업데이트
sudo apt-get update && sudo apt-get upgrade
# JDK 11 설치
sudo apt-get install openjdk-11-jdk
# Ruby 설치 (3.xx 버전 설치됨)
sudo apt install ruby-full
# wget 모듈 설치
sudo apt install wget
cd /home/ubuntu
# codedeploy 파일 다운로드
sudo wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
모든 명령어를 차례대로 입력했다면 디렉토리에 install 파일이 추가된 것을 볼 수 있다.
다시 다음 명령어들을 차례대로 입력하여 설정 및 사용 가능한 Codeploy의 버전을 확인한다.
# 권한 설정
sudo chmod +x ./install
# 출력을 임시 로그파일에 씀
sudo ./install auto > /tmp/logfile
# aws cli 설치
sudo apt-get install awscli
# 서울 리전에서 사용 가능한 codedeploy 버전 확인
sudo aws s3 ls s3://aws-codedeploy-region-identifier/releases/ | grep '\.deb$'
실습에선 가장 최신버전인 1.6.0버전을 사용했다.
# 1.6.0-49 버전의 codedeploy-agent 설치
sudo ./install auto -v releases/codedeploy-agent_1.6.0-49_all.deb > /tmp/logfile
# 서비스가 실행중인지 확인
sudo service codedeploy-agent status
name: Build and Deploy Flask to AWS EC2
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
env:
PROJECT_NAME: jonghun_project
BUCKET_NAME: jonghun-cicd-bucket
CODE_DEPLOY_APP_NAME: jonghun_cicd
DEPLOYMENT_GROUP_NAME: jonghun_instance
MONGO_USER: ${{ secrets.MONGO_USER }}
MONGO_PASS: ${{ secrets.MONGO_PASS }}
SECRET_KEY: ${{ secrets.SECRET_KEY }}
FLASK_SECRET_KEY: ${{ secrets.FLASK_SECRET_KEY }}
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
KAKAO_CLIENT_ID: ${{ secrets.KAKAO_CLIENT_ID }}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install dependencies
run: |
python -m venv venv
. venv/bin/activate
pip install -r requirements.txt
- name: Create .env file
run: |
echo "MONGO_USER=${{ secrets.MONGO_USER }}" > .env
echo "MONGO_PASS=${{ secrets.MONGO_PASS }}" >> .env
echo "SECRET_KEY=${{ secrets.SECRET_KEY }}" >> .env
echo "FLASK_SECRET_KEY=${{ secrets.FLASK_SECRET_KEY }}" >> .env
echo "GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }}" >> .env
echo "GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }}" >> .env
echo "KAKAO_CLIENT_ID=${{ secrets.KAKAO_CLIENT_ID }}" >> .env
- name: Make Zip File
run: |
. venv/bin/activate
zip -qq -r ./$GITHUB_SHA.zip .
shell: bash
- 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_PRIVATE_ACCESS_KEY }}
aws-region: ap-northeast-2
- name: Upload to S3
run: |
aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$BUCKET_NAME/$PROJECT_NAME/$GITHUB_SHA.zip
- name: Code Deploy To EC2 instance
run: aws deploy create-deployment
--application-name $CODE_DEPLOY_APP_NAME
--deployment-config-name CodeDeployDefault.AllAtOnce
--deployment-group-name $DEPLOYMENT_GROUP_NAME
--s3-location bucket=$BUCKET_NAME,bundleType=zip,key=$PROJECT_NAME/$GITHUB_SHA.zip
배포할 프로젝트의 root 디렉토리에 appspec.yml파일을 추가한다. CodeDeploy가 참조하는 파일이다.
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/jonghun
permissions:
- object: /home/ubuntu/jonghun/
owner: ubuntu
group: ubuntu
hooks:
AfterInstall:
- location: scripts/deploy.sh
timeout: 60
runas: ubuntu
version
appspec.yml 파일 버전을 정의한다. 현재는 0.0 이외의 버전이 지원되지 않는다.
files
배포할 파일 및 디렉토리를 정의한다. 위의 예시에서는 애플리케이션을 루트 디렉토리에서 가져와 "/home/ubuntu/jonghun" 디렉토리로 복사한다.
permissions
애플리케이션 파일에 대한 권한을 정의한다. 위의 예시에서는 "/home/ubuntu/jonghun" 디렉토리의 그룹과 소유자를 ubuntu로 설정한다.
hooks
배포 단계에서 실행할 훅 스크립트를 정의한다. 위의 예시에서는 배포 후 실행할 스크립트인 deploy.sh 파일을 지정하고, 스크립트 실행 시간 제한을 60초로 설정한다.
AfterInstall
여러 배포 단계 중 AfterInstall 단계에서 스크립트를 실행한다.
배포할 프로젝트의 root 디렉토리에 scripts폴더를 생성한 후 deploy.sh를 추가한다.
#!/usr/bin/env bash
REPOSITORY=/home/ubuntu/jonghun
FLASK_APP_DIR=/home/ubuntu/jonghun
ENV_PATH=$FLASK_APP_DIR/.env
cd $REPOSITORY
# Flask 앱 인스턴스 종료
FLASK_PID=$(pgrep -f gunicorn)
if [ -z $FLASK_PID ]
then
echo "> 종료할 Flask 애플리케이션이 없습니다."
else
echo "> kill Flask app with PID: $FLASK_PID"
kill -15 $FLASK_PID
sleep 5
fi
if [ -f $ENV_PATH ]; then
source $ENV_PATH
fi
echo "> Removing existing venv directory"
rm -rf $FLASK_APP_DIR/venv
echo "> Setting up new virtual environment"
python3 -m venv $FLASK_APP_DIR/venv
source $FLASK_APP_DIR/venv/bin/activate
echo "> Installing dependencies"
pip install -r $FLASK_APP_DIR/requirements.txt
# Flask 앱 시작
echo "> Starting Flask app with gunicorn"
cd $FLASK_APP_DIR
source $FLASK_APP_DIR/venv/bin/activate
nohup gunicorn -w 4 app:app -b 0.0.0.0:5002 > /dev/null 2> /dev/null < /dev/null &
명령어를 통해 애플리케이션을 실행하는 것 처럼 순서대로 적으면 된다.