Published on

Python으로 만든 Slackapp을 Light sail container 배포하고 CI CD 구축하기

Authors

전체구조 및 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 로 들어갑니다.

Python으로-만든-Slackapp을-Light-sail-container-배포하고-CICD-구축하기_1

Create container service 로 생성합니다.

중간에 보면 서비스이름을 설정할 수 있습니다.

퍼블릭도메인 까지 제공되어 도메인네임이 굳이 필요없는 경우 이 정도로 충분히 통신할 수도 있을 것 같습니다.

Python으로-만든-Slackapp을-Light-sail-container-배포하고-CICD-구축하기_2

두개의 도커 컨테이너를 각 lightsail 컨테이너에 배포하기 위해 다음과 같이 slackbot, slackbot-fastapi 라는 이름의 컨테이너를 생성했습니다.

Python으로-만든-Slackapp을-Light-sail-container-배포하고-CICD-구축하기_3

macOS에 Lightsail 플러그 인 설치

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로 생성됩니다.

Python으로-만든-Slackapp을-Light-sail-container-배포하고-CICD-구축하기_4

이 최신 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 을 별도의 콘솔로 분리해 놓은 것이 인상적이었습니다. 메인 페이지 내에 인스턴스, 컨테이너, 데이터베이스 등 생각보다 다양한 서비스를 운영하고 있었고, 토이프로젝트나 사이드 프로젝트 진행 시 가볍게 시작하기에 좋을 것 같습니다.

lightsail 공식문서 링크 slack_bot 개발 및 인프라 전체과정 repository

  • hongreat 블로그의 글을 봐주셔서 감사합니다!^^
  • 내용에 잘못된 부분이나 의문점이 있으시다면 댓글 부탁 & 환영 합니다~!
  • (하단의 버튼을 누르시면 댓글을 보거나 작성할 수 있습니다.)
Buy Me A Coffee