본문 바로가기

개발노트/LINUX

AWS lambda function 배포 자동화 하기

배경

사내에서 AWS 를 위주로 인프라가 구성되어 있긴 하지만, 거의 대부분 갖춰져있는 구성이 많기도 하고 내가 AWS 자체에 아직 익숙하지 않은 상황이었다. 업무를 보다보면 가~끔 aws lambda function 으로 구성해놓은 부분에 코드 수정이 필요한 경우가 생겼는데, 그 때마다 AWS web console 에서 inline edit 기능을 이용하여 직접 수정했었다. 불편하긴 하지만 다른 방법에 대해 딱히 생각해보지 못했다. 수정할 일이 자주 있는 것도 아니기도 하다는 합리화를 하면서. 이 lambda 코드를 어찌됐든 형상관리를 하긴 해야겠기에 git repo 에다가 똑같은 코드로 관리하고 있긴 했었는데, 이 과정이 많이 번거롭기도 하고 사람이 실수하기 딱 좋았다. 양 쪽(git repo, lambda)의 코드가 동기화되어있지 않는 상황이 자주 발생하는 것이다. 차이점을 발견할 때마다 lambda 가 service 되고 있는 코드이기 때문에 그 코드를 기준으로 git repo 에서 수정해주곤 했었다. 이런 불편한 점들을 개선하기 위해 EC2, ECS 에 배포하는 다른 서비스와 차이 없이 git repo 에 있는 코드가 lambda 에 배포되도록 하기 위해 CodePipeline, CodeDeploy, CodeBuild 를 이용하여 배포 자동화를 구축하였다.

lambda function 배포 자동화와 관련해서 자료를 찾아보면 SAM(Serverless Application Model) 또는 CloudFormation 으로 배포하는 자료가 많이 있던데, 좀 복잡하기도 하고 지금 하려는 것과 비교해서 그걸 씀으로 얻는 이익이 크지 않을 것이란 판단이 있었다.

 

참고했던 자료

내가 구글링해봤을 때 대부분 SAM 또는 CloudFormation 으로 배포 자동화를 구축하는 자료가 많이 나왔다. 그러다 CodeDeploy 로 배포하는 것에 대해 누군가 유튜브로 만들어 올린 것을 보게됐는데, 이를 많이 참고하여 진행하였다.

www.youtube.com/watch?v=XPdILGvLoIQ

 

 

사전에 필요한 것

  • IAM Role : Trusted entities 가 codebuild.amazonaws.com 인 Role
    • 여기에는 CodeBuild, CodeDeploy 를 이용하여 배포가 되도록 필요한 IAM Policies 들이 Attach 되어 있어야 한다.
      • AWSLambdaFullAccess
      • AmazonS3FullAccess
      • CloudWatchLogsFullAccess
      • AWSCodeBuildAdminAcccess
      • AWSCodeDeployDeployerAccess
    • 기타 더 필요한 Policy 가 있을 수 있는데, 필요함이 인지되는 Policy 가 생길때마다 attach 해주면 된다.
  • CodePipeline, CodeBuild, CodeDeploy 에 대한 기본적인 이해

 

해야할 작업 목록

  • lambda function 에 배포할 소스 코드 작성
    • CodeBuild 를 이용할 것이기 때문에 buildspec.yml 작성
    • 실제 CodeBuild 에서 실행될 Shell Script 작성
      • 이 Shell script 가 핵심
  • CodeDeploy 생성
    • Deployment Group 생성
  • CodeBuild 생성
    • 환경변수 설정 등
    • buildspec.yml 파일 작성
  • CodePipeline 생성
    • Source 단계에서 git repo 의 특정 branch 연결
    • Build 단계에서 CodeBuild 를 실행하는 stage 추가

 

매커니즘?

  1. git repo 에서 소스를 commit & push 를 하면
  2. CodePipeline 에서 변경을 감지하여 Source code 를 가져온다
  3. 가져온 Source Code 를 가지고 CodeBuild 가 Build 를 시작한다
  4. CodeBuild 에서 실행되도록 작성한 Shell Script 에서 aws cli 를 이용하여 CodeDeploy 의 deployment 를 생성한다.
  5. 정상적으로 CodeDeploy 의 deployment 가 생성되고, deployment 가 정상적으로 끝나면 update 된 lambda function 을 확인할 수 있다.

* 작업한 프로젝트는 Ruby 로 작성되었고, 외부 gem 을 의존성으로 갖고 있다.

소스코드 구조는 function.rb, Gemfile, Gemfile.lock, buildspec.yml, scripts/deploy-lambda.sh 이 최소한의 스켈레톤이라고 볼 수 있다. (실제 내가 작업한 소스코드의 구조는 이보다 더 많긴 함)

function.rb, Gemfile, Gemfile.lock 에 대한 설명은 필요없을 것 같고, buildspec.yml 과 scripts/deploy-lambda.sh 파일의 내용을 올려보면

# buildspec.yml
version: 0.2

phases:
  build:
    commands:
      - bash scripts/deploy-lambda.sh
#!/usr/bin/env bash

set -x -e

aws lambda get-alias \
  --function-name $DEPLOY_FUNCTION_NAME \
  --name $DEPLOY_ALIAS_NAME \
  > lambda-alias.json

DEVELOPMENT_ALIAS_VERSION=$(cat lambda-alias.json | jq -r '.FunctionVersion')

bundle install --path vendor/bundle

zip -r mailchimp-syncer.zip *

aws lambda update-function-code \
  --function-name $DEPLOY_FUNCTION_NAME \
  --zip-file fileb://mailchimp-syncer.zip \
  --publish \
  > updated-output.json

LATEST_VERSION=$(cat updated-output.json | jq -r '.Version')

if [[ $DEVELOPMENT_ALIAS_VERSION -ge $LATEST_VERSION ]]; then
  exit 0
fi

# Create appspec file in s3 bucket to create a deployment for CodeDeploy
cat > $DEPLOY_APPSPEC_FILE <<- EOM
version: 0.0
Resources:
  - mailchimpSyncer:
      Type: AWS::Lambda::Function
      Properties:
        Name: "$DEPLOY_FUNCTION_NAME"
        Alias: "$DEPLOY_ALIAS_NAME"
        CurrentVersion: "$DEVELOPMENT_ALIAS_VERSION"
        TargetVersion: "$LATEST_VERSION"

EOM

aws s3 cp $DEPLOY_APPSPEC_FILE \
  s3://$S3_BUCKET_FOR_DEPLOY_LAMBDA/$DEPLOY_APPSPEC_FILE

REVISION=revisionType=S3,s3Location={bucket=$S3_BUCKET_FOR_DEPLOY_LAMBDA,key=$DEPLOY_APPSPEC_FILE,bundleType=yaml}
aws deploy create-deployment \
  --application-name $DEPLOY_APPLICATION_NAME \
  --deployment-group-name $DEPLOY_DEPLOYMENT_GROUP_NAME \
  --deployment-config-name CodeDeployDefault.LambdaAllAtOnce \
  --revision $REVISION

 

이 Shell script 는 여러 시행착오를 겪으면서 만들어나간 것인데, 이 중 유의해야하는 부분이 bundle install --path vendor/bundle 이 부분과 zip 으로 압축하는 부분이다.

* 유의 사항

  • zip 으로 압축할 때 -r 옵션을 빼먹고 올려서 nested directory structure 에서 많은 파일이 업로드 되지 않은 적도 있다.
  • lambda 가 실행되는 환경에서 외부 gem 을 찾을 수 없다는 오류가 뜬 적도 있다.

이 Shell script 파일을 보면 외부로부터 주입받는 변수들이 정의되어 있다.

$DEPLOY_FUNCTION_NAME, $DEPLOY_ALIAS_NAME 와 같은 것들이 바로 그것인데, 이 변수에 들어갈 값은 CodeBuild 의 Environment 에서 Variable 을 설정해주면 된다.

 

그리고 lambda function 에 버전을 publish 한 적이 한 번도 없다면 version 을 하나 publish 해주고, alias 도 생성한 다음(위 스샷에서는 development 라는 별칭), 해당 alias 에 version 을 연결해줘야한다.

CodePipeline, CodeDeploy, CodeBuild 를 생성하는 방법은 조금만 찾아보면 잘 알 수 있었다.

CodeDeploy 가 lambda function 을 배포 성공한 모습

 

이렇게 CodeDeploy 에서 배포가 성공적으로 끝나면, lambda 에서도 새 버전이 publish 된 것을 확인할 수 있고, alias 도 새 버전에 연결돼있음을 확인할 수 있다.

 

Lambda 에서 새롭게 Publish 된 Version 과 그 Version 에 매핑되어있는 alias 확인