- Published on
Python으로 만든 Slackapp을 Light sail container 배포하고 CI CD 구축하기
- Authors
- Name
- hongreat
- ✉️hongreat95@gmail.com
전체구조 및 docker container
모노repo로 구성하여 python bolt 를 이용한 slackapp과 서드파티앱의 통신을 위한 fastapi app 2개의 패키지를 각각의 컨테이너로 배포했습니다.
repo구조는 아래와 같습니다.
└── slack-bot/ # 최상단
├── bolt_python/ # slackapp dir
├── router_fastapi/ # fastapi dir
├── main.py # slackapp runner
├── app_fastapi.py # fastapi runner
└── … # etc
도커 컨테이너 분리
python 3.10 환경 위에서 무리없이 동작 하는 것을 로컬환경과 docker 환경에서 확인했습니다.
- slackbot.Dockerfile
FROM python:3.10
MAINTAINER hongreat95@gmail.com
RUN mkdir /slackbot
WORKDIR /slackbot
ADD . /slackbot
# Use Gitsecrets
ARG NOTION
ARG SLACK_APP_TOKEN
ARG SLACK_BOT_TOKEN
ARG SLACK_SIGNING_SECRET
ARG NOTION_reservation
ARG NOTION_lunch
RUN chmod +x setup_env.sh && ./setup_env.sh
RUN pip install --upgrade pip && pip install -r requirements.txt
ENTRYPOINT ["python","main.py"]
- fastapi.Dockerfile
FROM python:3.10
MAINTAINER hongreat95@gmail.com
RUN mkdir /slackbot
WORKDIR /slackbot
ADD . /slackbot
# Use Gitsecrets
ARG NOTION
ARG SLACK_APP_TOKEN
ARG SLACK_BOT_TOKEN
ARG SLACK_SIGNING_SECRET
ARG NOTION_reservation
ARG NOTION_lunch
RUN chmod +x setup_env.sh && ./setup_env.sh
RUN pip install --upgrade pip && pip install -r requirements.txt
EXPOSE 8080
CMD ["uvicorn","app_fastapi:api","--reload","--host","0.0.0.0","--port","8080"]
Lightsail container 생성
공식문서 : https://lightsail.aws.amazon.com/ls/webapp/home/instances
AWS 의 lightsail 콘솔로 접속한고 containers 로 들어갑니다.
Create container service 로 생성합니다.
중간에 보면 서비스이름을 설정할 수 있습니다.
퍼블릭도메인 까지 제공되어 도메인네임이 굳이 필요없는 경우 이 정도로 충분히 통신할 수도 있을 것 같습니다.
두개의 도커 컨테이너를 각 lightsail 컨테이너에 배포하기 위해 다음과 같이 slackbot, slackbot-fastapi 라는 이름의 컨테이너를 생성했습니다.
macOS에 Lightsail 플러그 인 설치
lightsail를 사용하기 위해 공식문서를 참고하여 환경에 맞게 설치합니다.
macOS 에서는 Homebrew 를 이용해 다음 명령어로 손쉽게 설치가 가능하다.
brew install aws/tap/lightsailctl
컨테이너이미지 Build 하고 Push하기
Image Push(Local)
Lightsail의 deploy와 CI/CD 를 구축합니다. 원래 CI CD 까지는 계획에 없었는데, 작업하면서 무조건 필요할 것 같다고 생각했습니다..
로컬환경(CLI)에서 먼저 배포한 후 git action을 위한 스크립트를 작성했습니다.
- 로컬환경에서 image를 생성합니다.
한참 해맸는데, 도커파일이 위치한 경로에서 docker build를 하는데 플랫폼 아키텍처에 대한 에러가 발생했습니다.
수많은 구글링끝에.. 플랫폼 옵션을 하지 않았을 때 컨테이너가 정상적으로 작동하지 않는다는 글을 우연히 발견하고 아키텍쳐를 지정후 빌드했습니다.
--platform linux/amd64 옵션 값을 지정해서 이미지를 amd기반 아키텍쳐로 빌드합니다.
- slackbot.Dockerfile 로 빌드
docker build --platform linux/amd64 -t slackbot:latest . -f slackbot.Dockerfile
- 앞서, Lightsail 콘솔에서 만든 컨테이너가 구축되어있다면 image를 Lightsail-Container에 푸쉬합니다.
aws lightsail push-container-image --service-name slackbot --label slackbot --image slackbot:latest
CI/CD
로컬에서 Image Push가 이뤄지고, container가 정상적으로 Running 된다면 container의 depoly가 성공한 것 입니다.
이 로직을 main branch 에 code push가 이뤄질 때 마다 적용시키고 싶었고 이를 위해 git workflow를 이용한 CI/CD를 구축했습니다.
CI
- secrets 관리는 아래의 이유로 .env 로 관리했습니다.
- slack api에서 제공받은 token 들을 test용과 dev용으로 전환하며 개발했는데, 테스트와 잦은 secrets 변경이 필요했습니다.
- OS환경변수나 secret manager를 사용함에 불편함이 너무 많았습니다.
- notion database_id 도 보안에 포함시키고자 했고, 역시 잦은 변환에 불편했습니다.
이러한 특성때문에 빌드과정에서 github secrets를 당겨오고 dockerfile에서 ARG로 받아 사용했습니다.
echo "SLACK_APP_TOKEN=$SLACK_APP_TOKEN" >> .env
스크립트 처럼.. 토큰들을 .env에 넣어줍니다.
빌드과정에서는setup_env.sh를아래 코드로 읽게했습니다.
RUN chmod +x setup_env.sh && ./setup_env.sh
#setup_env.sh 스크립트
#!/bin/sh
echo "NOTION=$NOTION" >> .env
echo "SLACK_APP_TOKEN=$SLACK_APP_TOKEN" >> .env
echo "SLACK_BOT_TOKEN=$SLACK_BOT_TOKEN" >> .env
echo "SLACK_SIGNING_SECRET=$SLACK_SIGNING_SECRET" >> .env
echo "NOTION_reservation=$NOTION_reservation" >> .env
echo "NOTION_lunch=$NOTION_lunch" >> .env
CI과정의 스트립트
- name: Slackbot Build
uses: docker/build-push-action@v2
with:
context: .
file: ./slackbot.Dockerfile
tags: slackbot:latest
platforms: linux/amd64
push: false
build-args: |
NOTION=${{secrets.NOTION}}
SLACK_APP_TOKEN=${{secrets.SLACK_APP_TOKEN}}
SLACK_BOT_TOKEN=${{secrets.SLACK_BOT_TOKEN}}
SLACK_SIGNING_SECRET=${{secrets.SLACK_SIGNING_SECRET}}
NOTION_reservation=${{secrets.NOTION_reservation}}
NOTION_lunch=${{secrets.NOTION_lunch}}
CD
cd과정은 생각보다 복잡했습니다.
Lightsail-Container에 push 하고 실행시키기 위해서 create-container-service-deployment 를 수행해야 합니다.
이때 container에 적용할 것인지 지정하려면 {template}.json
을 이용하라는 문서의 내용이 있었습니다.
(template이름은 중요하지않습니다. 뒤에 나오지만 --cli-input-json로 지정하기때문입니다. )
container에 올라간 Image의 정보도 이 json안에 담겨있어야 합니다.
{template}.json
{
"containers": {
"slackbot": {
"image": "",
"environment": {
"APP_ENV": "release"
}
}
}
}
맥락을 짚고넘어가면 아래와 같습니다.
Image build → Image get → template(include Image)기반 depoloyment
Image number를 template에서 알게하기 위해서는 별도의 로직이 필요했습니다.
get-container-images 로 Image들의 정보를 가져오면 아래처럼 최신의 Image가 첫번째 index로 생성됩니다.
이 최신 Image를 지정해서 {template}.json
에 넣어줘야 합니다.
jq를 이용해 Image 정보를 txt로 빼낸뒤에 json파일을 만들어주고, 이 json 으로 deploy 합니다.
CI과정의 스트립트
- name: Slackbot Deploy
run: |
service_name=slackbot
aws lightsail push-container-image \\
--region ap-northeast-2 \\
--service-name ${service_name} \\
--label ${service_name} \\
--image ${service_name}:latest
aws lightsail get-container-images --service-name ${service_name} | jq --raw-output ".containerImages[0].image" > image.txt
jq --arg image $(cat image.txt) '.containers.slackbot.image = $image' slackbot.template.json > slackbot.json
aws lightsail create-container-service-deployment --service-name ${service_name} --cli-input-json file://$(pwd)/slackbot.json
CI/CD 전체 스크립트
name: CI/CD
on:
push:
branches:
- main
env:
AWS_REGION: ap-northeast-2
AWS_LIGHTSAIL_SLACKBOT: slackbot
AWS_LIGHTSAIL_FASTAPI: slackbot-fastapi
jobs:
build_and_deploy:
name: CI/CD
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-region: ${{ env.AWS_REGION }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Install AWS Lightsail # AWS SDK on ubuntu (for cli of LightSail)
run: |
curl "<https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip>" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install || true
aws --version
curl "<https://s3.us-west-2.amazonaws.com/lightsailctl/latest/linux-amd64/lightsailctl>" -o "lightsailctl"
sudo mv "lightsailctl" "/usr/local/bin/lightsailctl"
sudo chmod +x /usr/local/bin/lightsailctl
- name: Checkout
uses: actions/checkout@v2
- name: Slackbot Build
uses: docker/build-push-action@v2
with:
context: .
file: ./slackbot.Dockerfile
tags: ${{ env.AWS_LIGHTSAIL_SLACKBOT }}:latest
platforms: linux/amd64
push: false
build-args: |
NOTION=${{secrets.NOTION}}
SLACK_APP_TOKEN=${{secrets.SLACK_APP_TOKEN}}
SLACK_BOT_TOKEN=${{secrets.SLACK_BOT_TOKEN}}
SLACK_SIGNING_SECRET=${{secrets.SLACK_SIGNING_SECRET}}
NOTION_reservation=${{secrets.NOTION_reservation}}
NOTION_lunch=${{secrets.NOTION_lunch}}
- name: Slackbot Deploy
run: |
service_name=${{ env.AWS_LIGHTSAIL_SLACKBOT }}
aws lightsail push-container-image \\
--region ${{ env.AWS_REGION }} \\
--service-name ${service_name} \\
--label ${service_name} \\
--image ${service_name}:latest
aws lightsail get-container-images --service-name ${service_name} | jq --raw-output ".containerImages[0].image" > image.txt
jq --arg image $(cat image.txt) '.containers.slackbot.image = $image' slackbot.template.json > slackbot.json
aws lightsail create-container-service-deployment --service-name ${service_name} --cli-input-json file://$(pwd)/slackbot.json
- name: Fastapi Build
uses: docker/build-push-action@v2
with:
context: .
file: ./fastapi.Dockerfile
tags: ${{ env.AWS_LIGHTSAIL_FASTAPI }}:latest
platforms: linux/amd64
push: false
build-args: |
NOTION=${{secrets.NOTION}}
SLACK_APP_TOKEN=${{secrets.SLACK_APP_TOKEN}}
SLACK_BOT_TOKEN=${{secrets.SLACK_BOT_TOKEN}}
SLACK_SIGNING_SECRET=${{secrets.SLACK_SIGNING_SECRET}}
NOTION_reservation=${{secrets.NOTION_reservation}}
NOTION_lunch=${{secrets.NOTION_lunch}}
- name: Fastapi Deploy
run: |
service_name=${{ env.AWS_LIGHTSAIL_FASTAPI }}
aws lightsail push-container-image \\
--region ${{ env.AWS_REGION }} \\
--service-name ${service_name} \\
--label ${service_name} \\
--image ${service_name}:latest
aws lightsail get-container-images --service-name ${service_name} | jq --raw-output ".containerImages[0].image" > image.txt
jq --arg image $(cat image.txt) '.containers.fastapi.image = $image' slackbot_fastapi.template.json > slackbot_fastapi.json
aws lightsail create-container-service-deployment --service-name ${service_name} --cli-input-json file://$(pwd)/slackbot_fastapi.json
느낀점
Lightsail 을 써보고 Lightsail 을 별도의 콘솔로 분리해 놓은 것이 인상적이었습니다. 메인 페이지 내에 인스턴스, 컨테이너, 데이터베이스 등 생각보다 다양한 서비스를 운영하고 있었고, 토이프로젝트나 사이드 프로젝트 진행 시 가볍게 시작하기에 좋을 것 같습니다.