Chalice를 활용한 AWS Serverless Application 개발 및 배포기

Jeuk Oh·2022년 7월 15일
0

개발기

목록 보기
1/1
post-thumbnail

이 글을 읽으면 좋은 독자

  • AWS Console에서 API Gateway, Lambda를 작성 및 배포해본 분
  • Python이 주 언어인 분
  • Gateway, Lambda 활용 중 유지 보수 및 테스트에 어려움을 느껴본 분

Chalice란

  • A framework for writing serverless applications
  • AWS에서 제공, Python Flask 규격과 비슷.
  • Test Tool을 지원해서 배포 없이도 테스트 가능.
  • Event handler, Scheduler, lambda 등도 작성 가능.
  • 커맨드라인으로 쉽게 인프라 Deploy, Update, Delete 가능.
  • Auto IAM policy generation

도입 배경

배경

  • 서로 다른 서비스를 제공하는 A 앱과 A와 크게 결합된 B 앱이 있습니다.
  • A는 B의 endpoint를 호출하는데 운영 중 B 자체의 안정성에 의문이 많이 드는 상황입니다.
  • B 대신 비슷한 기능을 제공하는 유료 SaaS C를 사용해서 보다 안정적인 서비스를 제공하고자 합니다.

문제 인식

  • B를 C로 바로 바꿀 시 A에서 B를 요청하는 코드에 많은 수정이 필요합니다.
    • 이러한 수정은 A의 기능 작동에도 영향을 미칠 수도 있고 공수가 많이 드는 불필요한 수정으로 판단됩니다.
    • 또한 추후 C를 다시 자체 개발 앱으로 대체할 계획이 있어 그 때 A를 또 다시 수정해주어야 하는 일이 생깁니다.
    • 마찬가지로 C를 그대로 쓰고 A를 수정할 일이 있을 수 있습니다. A와 C 간의 의존성을 최대한 걷어내야 합니다.

목표

  • A와 C 중간에 B의 API 엔드포인트를 똑같이 가지고 C의 엔드포인트를 대신 호출해주는 서버리스 어플리케이션을 만들어 A의 수정을 최소화 하고자 합니다.

왜 Chalice인가

  • AWS gateway, lambda를 콘솔에서 직접 작성하는 PoC를 진행해보았으나, 테스트가 너무 귀찮고 여러 리소스를 관리하는 것이 매우 까다롭습니다. CloudFormation이나 SAM을 활용할 수 있으나, 어플리케이션 코드보다 리소스 프로비저닝에 중점을 둔 서비스들이라 저같은 주니어 백엔드 개발자들에게 접근성이 좋지 않았습니다.
  • Chalice는 Flask처럼 python 데코레이터 기반으로 쉽게 API 앤드포인트를 작성할 수 있습니다. 러닝 커브가 낮습니다.
  • test clientlocal mode 등을 지원해서 배포 없이 모노리틱 웹 서버를 만드는 것처럼 쉽게 개발할 수 있습니다.
  • CloudFormation template pipeline 생성 및 SAM으로 Package 기능 제공하여 IaC (Infrastructure as Code)를 배우지 않고도 쉽게 배포 가능.

개발기

QuickStart
먼저 해당 문서에 hello world project Create, Deploy, Clean up까지 너무나 쉽게 나와 있습니다.

간단하게 health check용 endpoint를 작성한 예제를 보겠습니다.

# app.py
from chalice import Chalice

app = Chalice(app_name='my_app')
...
@app.route('/')
@app.route('/health')
def index():
    return health()

이후 배포 시 gateway_url/ 또는 gateway_url/health 요청 시 health 함수의 결과가 잘 반환되는 것을 볼 수 있습니다.

다음은 Chalice를 쓰기로 한 큰 이유 중 하나인 테스트 코드 예제입니다.

# tests/test_endpoint.py

from chalice.test import Client
from app import app as test_app
import pytest
...

@pytest.fixture(scope="module")
def client():
    k = Client(test_app)
    yield k

def test_health_ok(client):
    r = client.http.get('/')
    assert r.status_code == 200
    assert r.json_body == None

    r = client.http.get('/health')
    assert r.status_code == 200
    assert r.json_body == None

chalice.testClientapp.py에서 작성한 Chalice 인스턴스를 인자로 테스트 클라이언트를 생성합니다.
예제와 같이 http 요청을 보내면 실제 배포된 서비스에 요청이 가는 것이 아닌 LocalGateway라는 mock API gateway를 거쳐 해당 route에 대응되는 view function을 실행합니다. 그 밖에 lambda를 호출하거나, sns 이벤트를 생성하는 테스트도 가능합니다.
pytest와 같이 쓰면 기존 AWS Console을 사용하는 것에 비교해서 정말 빠르고 편하게 개발이 가능합니다.


배포

다만 커맨드라인에서 chalice deploy를 입력해서 배포하는 방법은 편리하지만 팀 단위 개발에서 문제가 발생합니다.

  • 팀 단위로 코드를 관리 시 매 배포마다 deployed.json 파일을 업데이트하고 공유해야 합니다.
  • 휴먼 에러로 테스트를 거치지 않고 잘못 된 코드가 배포되어 서비스가 마비될 수 있습니다.

Chalice에서는 이러한 문제를 해결하기 위해 Continuous Deployment 파이프라인을 쉽게 구성할 수 있는 generate-pipeline 커맨드라인을 제공합니다. 해당 커맨드로 파이프라인을 구성 시 AWS (CodeCommit), CodePipleline, CodeBuild, S3 리소스를 사용합니다.

Source로는 Github와 CodeCommit을 지원하며 해당 글에서는 Github 기준으로 작성하였습니다.

먼저 AWS 서비스가 Github 레포지토리에 접근할 수 있도록 Github OAuthToken을 발급 받으셔야 합니다.

소스 코드를 가져갈 수 있도록 access token을 발급받아 줍시다.

다음은 해당 토큰을 aws secretmanager에 등록하여 이후 파이프라인 생성 시 토큰을 사용할 수 있도록 합니다.

$ touch secrets.json
# secrets.json
{"OAuthToken": "github_access_token"}
$ aws secretsmanager create-secret --name GithubRepoAccess \
  --description "Token for Github Repo Access" \
  --secret-string file://secrets.json

secrets.json을 삭제하고,

codePipeline 리소스를 생성할 수 있는 cloudFormation template을 반환하는 generate-pipeline 커맨드를 실행합니다.

$ chalice generate-pipeline --pipeline-version v2 \
  --source github --buildspec-file buildspec.yml pipeline.json

buildspec.yml 파일과 pipeline.json 파일이 생성되는데, pipeline.json은 codePipeline을 생성하는 cloudformation template 파일이며, buildspec.yml은 codeBuild 단계에서 사용하게 될 빌드사양입니다.

pipeline.json을 열어보면 Source Stage에서 앞서 등록한 Token을 받아오는 구문을 확인할 수 있습니다.
450줄 가량의 json 파일이 자동 생성되는 것을 보면 마음이 웅장해집니다.

# pipeline.json 
{
    "Name": "Source",
    "Actions": [
      {
        "Name": "Source",
        "ActionTypeId": {
          "Category": "Source",
          "Owner": "ThirdParty",
          "Version": "1",
          "Provider": "GitHub"
        },
        "RunOrder": 1,
        "OutputArtifacts": [
          {
            "Name": "SourceRepo"
          }
        ],
        "Configuration": {
          "Owner": {
            "Ref": "GithubOwner"
          },
          "Repo": {
            "Ref": "GithubRepoName"
          },
          "OAuthToken": {
            "Fn::Join": [
              "",
              [
                "{{resolve:secretsmanager:",
                {
                  "Ref": "GithubRepoSecretId"
                },
                ":SecretString:",
                {
                  "Ref": "GithubRepoSecretJSONKey"
                },
                "}}"
              ]
            ]
          },
          "Branch": "main",
          "PollForSourceChanges": true
        }
      }
    ]
  }

두 파일을 guthub repo에 푸시한 뒤 다음 커맨드로 cloudFormation을 배포하는 것으로 배포가 마무리 됩니다.

$ aws cloudformation deploy --template-file pipeline.json \
  --stack-name MyChaliceApp --parameter-overrides \
  GithubOwner=repo-owner-name \
  GithubRepoName=repo-name \
  --capabilities CAPABILITY_IAM

(너무 쉽다.)

두 파일을 그대로 쓰게 된다면, github repo에 main branch에 커밋이 푸시될 때 마다 빌드 후 배포하는 pipeline이 생성됩니다.

여기서 두 파일을 수정하여 3가지 수정사항을 주고자 합니다.
1. main branch말고 다른 branch (deploy)를 deploy용으로 쓰고 싶다.
2. build 단계에서 test를 거쳐서 통과하지 못할 시 배포 중단을 하고 싶다.
3. test 결과를 aws console에서 확인하고 싶다.

3가지 사항에 대한 구현 방법은 다음과 같습니다.

  1. pipeline.json의 Source Stage에서 Branch 키 값을 원하는 branch 이름으로 바꿔줍니다.

상술한 pipeline.json의 Branch를 수정한 뒤 해당 브런치를 생성합니다.

  1. buildspec.yaml에서 build phase에 pytest 구문을 추가합니다.
  2. buildspec.yaml에 test 결과를 report하는 구문과 codeBuild의 IAM에 report, testcase를 생성할 수 있는 Policy를 추가합니다.
# buildspec.yaml

artifacts:
	...
phases:
  build:
    commands:
    - python -m pytest -s -v --junitxml=tests/test_report.xml
	...
  install:
	commands:
    ...
    - pip install -r requirements-test.txt
reports:
  pytest_reports:
    files:
      - test_report.xml
    base-directory: tests
    file-format: JUNITXML

build phase에서 pytest를 수행하고 결과를 junitxml 형식으로 남깁니다.
reports 구문으로 해당 파일을 aws codebuild report에 등록할 수 있도록 합니다.
처음에 pre_build phase에서 테스트를 하였더니 성공할 땐 문제 없었으나 테스트가 실패하였을 땐 build phase가 진행되지 않아 report에 결과가 저장 안되는 문제가 있었습니다. codePipeline에서 테스트만을 위한 test pipeline을 추가하는 방법도 있으나 저는 build pipeline에서 build와 test를 동시에 수행하기로 하였습니다.

# pipeline.json

 "CodeBuildPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "CodeBuildPolicy",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
			...,
            
            {
                "Effect": "Allow",
                "Resource": [
                    "*"
                ],
                "Action": [
                    "codebuild:CreateReportGroup",
                    "codebuild:CreateReport",
                    "codebuild:UpdateReport",
                    "codebuild:BatchPutTestCases",
                    "codebuild:BatchPutCodeCoverages"
                ]
            }
          ]
        },
        "Roles": [
          {
            "Ref": "CodeBuildRole"
          }
        ]
      }
    },

다음과 같이 CodeBuildPolicy에 Report를 다루기 위한 Action을 추가해주었습니다.

모든 코드를 deploy branch에 푸시하고 CloudFormation에 배포합시다.

일부러 틀리는 테스트 코드를 푸시해보며 자동 테스트와 자동 배포가 잘 되는지 확인해봅시다.

deploy branch에 코드 푸시 시 API Gateway, Lambda ARN가 재배포된 것을 확인할 수 있습니다.

CodeBuild reports에서 build pipeline에서 실행한 테스트 결과 확인 가능합니다.


ToDo

  1. Chalice Custom Domain
    api gateway에 사용자 custom domain을 붙이는 일은 AWS console 수동으로 처리했으나, Chalice에서 설정할 수 있습니다.
    다만 한번 설정하면 이후 고정되니 실제로 작업할 일은 없을 듯 합니다.

  2. Automatic Lambda Layers
    기본적으로 Chalice는 단일 zip 파일 하나로 어플리케이션을 패키지하는데 수정 사항이 없는 코드들도 매번 배포마다 패키지되는 낭비가 있습니다. 아직은 어플리케이션이 작아 문제가 없다 판단하지만, 추후 외부 라이브러리 및 수정이 적은 공용 코드들은 상위 람다 레이어로 올려 리소스를 절약할 수 있습니다.

  3. Authentication
    Chalice는 CognitoUserPoolAuthorizer와 IAMAuthorizer, CustomAuthorizer 클래스를 지원합니다.
    추후 인증 절차를 구현할 때 쓰일 것입니다.
    workshop


한계

아쉽게도 Step Function을 지원하지 않는 듯 합니다. Step Function 상태 머신을 구현할 필요가 있다면, Chalice 앱을 Package해서 나온 SAM 아티팩트를 수정하는 방법을 고려해야할지 고민 중입니다. 자료조사 및 PoC가 필요한 부분입니다.


참고 Docs

profile
개발을 재밌게 하고싶습니다.

0개의 댓글