はじめに
こんにちは。みんなのウェディングのインフラエンジニア横山です。
この記事はくふうカンパニーアドベントカレンダー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 package
やaws cloudformation deploy
といったCLIコマンドでスタックを構築することができます。
課題
前述したaws cloudformation package
というコマンドには便利な機能があります。
例えば以下のように設定ファイルのCodeUri
を指定しておくと、該当ディレクトリsource
配下をzipで圧縮してコマンドで指定したS3バケットに配置してくれます。
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オブェクトに入れ替わった設定ファイルも出力してくれます。
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がデプロイできます。
aws cloudformation package
aws cloudformation deploy
これは大変便利なのですが、ここで問題になるのが外部ライブラリの配置場所です。aws cloudformation package
では指定したディレトリ配下をzip圧縮してS3にアップロードすることしかできません。
つまり、gitで管理したい自分の書いたコードと、git管理したくない外部ライブラリを同じディレクトリ(source
)配下に置かなきゃいけないのです。
解決策
.gitignore
で解決できないかと考えましたが、前述した通り、外部ライブラリは自作のコードと同じディレクトリに配置するという制約があり、除外対象ディレクトリ名が変化するため難しかったです。
また、SAMで管理する対象が増えてきたこともあり、これを機にSAMデプロイをCodeBuildを使って自動化することにしました。
具体的な手順は以下の通りです。
- 必要な外部ライブラリを
source/requirements.txt
に記載する。 - requirements.txtを元に
pip install
を行う。 aws cloudformation package
aws cloudformation deploy
上記2〜4の手順をCodeBuild上で実行することにしました。
これらの手順をまとめたCodeBuild上で実行されるコードは以下の通りです。
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さんの「業務プロセスにおいてのデザインツールを組み合わせ方や学んだこと、今後の展望とか書いてみた」です。