Help us understand the problem. What is going on with this article?

くふうしてLambdaをデプロイする

More than 1 year has passed since last update.

はじめに

こんにちは。みんなのウェディングのインフラエンジニア横山です。
この記事はくふうカンパニーアドベントカレンダー11日目になります。
今回は、LambdaをSAM(Serverless Application Model)でデプロイするにあたり困ったことを、くふうで解決したのでまとめていきます。

SAM(Serverless Application Model)

SAMとはLambda、API Gateway、CloudWatch Ruleなどのリソースを管理できるツールです。
https://github.com/awslabs/serverless-application-model
これだけ聞くとCloudFormationと何が違うの?と思う方もいるかもしれません。
お気付きの通り何も違いはありません。SAMはCloudFormationの拡張という位置付けになっています。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/transform-aws-serverless.html
よって、SAMで書いた設定ファイルを使い、ClodFormationと同様にaws cloudformation packageaws cloudformation deployといったCLIコマンドでスタックを構築することができます。

課題

前述したaws cloudformation packageというコマンドには便利な機能があります。
例えば以下のように設定ファイルのCodeUriを指定しておくと、該当ディレクトリsource配下をzipで圧縮してコマンドで指定したS3バケットに配置してくれます。

test.yml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: test
Resources:
  AutoDeployStaging:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: test.lambda_handler
      Runtime: python3.6
      FunctionName: Test
      CodeUri: ./source
      Description: test
      Role: arn:aws:iam::123456789:role/test
      MemorySize: 128
      Timeout: 60

さらに、CodeUriが該当のS3オブェクトに入れ替わった設定ファイルも出力してくれます。

packaged-test.yml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: test
Resources:
  AutoDeployStaging:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: test.lambda_handler
      Runtime: python3.6
      FunctionName: Test
      CodeUri: s3://temp/37ba0504e4d9a488624001dae6afc957
      Description: test
      Role: arn:aws:iam::123456789:role/test
      MemorySize: 128
      Timeout: 60

あとはこのpackaged-test.ymlを指定してaws cloudformation deployするだけでスタックが作成、更新されます。

よって、必要な外部ライブラリをsource配下に配置しておくと、以下の2コマンドでLambdaがデプロイできます。
1. aws cloudformation package
2. aws cloudformation deploy

これは大変便利なのですが、ここで問題になるのが外部ライブラリの配置場所です。aws cloudformation packageでは指定したディレトリ配下をzip圧縮してS3にアップロードすることしかできません。
つまり、gitで管理したい自分の書いたコードと、git管理したくない外部ライブラリを同じディレクトリ(source)配下に置かなきゃいけないのです。

解決策

.gitignoreで解決できないかと考えましたが、前述した通り、外部ライブラリは自作のコードと同じディレクトリに配置するという制約があり、除外対象ディレクトリ名が変化するため難しかったです。
また、SAMで管理する対象が増えてきたこともあり、これを機にSAMデプロイをCodeBuildを使って自動化することにしました。
具体的な手順は以下の通りです。
1. 必要な外部ライブラリをsource/requirements.txtに記載する。
2. requirements.txtを元にpip installを行う。
3. aws cloudformation package
4. aws cloudformation deploy

上記2〜4の手順をCodeBuild上で実行することにしました。
これらの手順をまとめたCodeBuild上で実行されるコードは以下の通りです。

sam_deploy.py
import subprocess
import re
import os
import glob
import requests
import json


def post_slack(text, failed=False):
    SLACK_URL = '???'

    message = {
        "attachments": [
            {
                "color": "#e73613" if failed else "#2fc3a7",
                "text": text
            }
        ]
    }
    params = {
        'url': SLACK_URL,
        'data': json.dumps(message).encode("utf-8")
    }
    requests.post(**params)


def pip_install(path):
    requirement_files = glob.glob(f"{path}/**/*.txt", recursive=True)
    try:
        for requirement_file in requirement_files:
            requirement_path = requirement_file.replace("/requirements.txt", "")
            os.chdir(requirement_path)
            cmd = f'pip install -r requirements.txt -t ./'
            subprocess.run(cmd, shell=True, check=True)
    except subprocess.CalledProcessError as e:
        print(e.stderr)
        post_slack(e.stderr, failed=True)


def cloud_formation_update(path):
    sam = path.split('/')[-1]
    try:
        cmd = (f'aws cloudformation package --template-file {path}/{sam}.yml '
               '--s3-bucket s3バケット '
               f'--output-template-file {path}/packaged-{sam}.yml')
        subprocess.run(cmd, shell=True, check=True)
        cmd = (f'aws cloudformation deploy --template-file {path}/packaged-{sam}.yml '
               '--role-arn ロールARN '
               f'--stack-name {"".join(sam.title().split("_"))}')
        subprocess.run(cmd, shell=True, check=True)
    except subprocess.CalledProcessError as e:
        print(e.stderr)
        post_slack(e.stderr, failed=True)


if __name__ == '__main__':
    ROOT_DIR = os.getenv('CODEBUILD_SRC_DIR')
    cmd = 'git diff HEAD~ --name-only'
    modify_files = subprocess.check_output(cmd, shell=True).decode('utf-8').split()
    modify_sam_paths = set([ROOT_DIR + '/' + '/'.join(i.split('/')[0:2]) for i in modify_files if re.match(r'SAM/', i)])

    for path in modify_sam_paths:
        if not os.path.exists(path):
            continue
        pip_install(path)
        cloud_formation_update(path)
        post_slack(f"{path} deployed!")

ディレクトリ構成は以下の通りです。

SAM
├── sam_1
│   ├── sam_1.yml
│   └── source
│       ├── sam_1.py
│       └── requirements.txt
└──  sam_2
    ├── sam_2.yml
    └── source
        ├── sam_2.py
        └── requirements.txt

CodeBuildはマスターブランチにマージがあるたびに実行されるので、git diff HEAD~ --name-onlyで変更されたファイルを特定し、SAM関連のファイルに変更があった場合のみデプロイが行われるようにしています。

まとめ

CodeBuildはこういったちょっとした用途の際にサクッと使えて便利だなと感じました。
弊社ブログは静的サイトなのですが、こちらでもS3へのデプロイをCodeBuildで行ってたりします。
https://blog.mwed.info/posts/change_ci.html
これからも、もっとくふうして世の中を良くしていきたいと思います。
くふうカンパニーアドベントカレンダー、12日目となる明日はryosterさんの「業務プロセスにおいてのデザインツールを組み合わせ方や学んだこと、今後の展望とか書いてみた」です。

tatsuo48
とあるHamee株式会社でごにょごにょ
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした