7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

くふうカンパニーAdvent Calendar 2018

Day 11

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

Last updated at Posted at 2018-12-10

はじめに

こんにちは。みんなのウェディングのインフラエンジニア横山です。
この記事はくふうカンパニーアドベントカレンダー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さんの「業務プロセスにおいてのデザインツールを組み合わせ方や学んだこと、今後の展望とか書いてみた」です。

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?