概要
AWS Lambdaで動かしているPythonアプリがあり、そのCI/CDについての記事になります。
本記事では、「Gitでマージしたら、自動でソースコードのZIPがS3にアップされ、また、そのZIPがLambdaにアップロードされる」という流れを実装しました。
また、S3アップ前にはAlembicでDBマイグレーションを行うようにしました。
(意外とこの部分の記事が少なかったので参考になればと思います)
CI/CDの流れ
- Gitでマージ -> CodePipeline自動起動 -> Codebuild実行。
buildspec.yaml
にて以下を自動実行- AlembicでRDS DBへマイグレーション
- ソースコードをzip化してS3にアップ
- S3にアップしたzipをLambdaへアップロード
手順
①Codebuild作成
- Codebuild用のセキュリティグループを作成
- 作成後、RDS用のセキュリティグループのインバウンドルールに追加する
- Codebuildプロジェクトを作成
- RDSへアクセスするため、VPCに置く必要がある。RDSがあるVPCとサブネットを選択。セキュリティグループは上記で作成したものを選択する
- IAMロールには、s3やcloudwatchlogsへの許可、また
"lambda:UpdateFunctionCode"
のアクセス許可を付与すること(参考までに私が使ったIAMロールは以下に後述)。 - 環境変数には、DB情報や必要なENVパラメータを登録する。また、今回は後述の
buildspec.yaml
で使うS3バケット名、ZIP名、Lambda関数名も登録しておくこと - 作成時、自動でVPCアクセスを許可するIAMポリシーが実行ロールに付与されます
※RDSへのアクセス許可が設定されていない場合、ビルド時に以下のタイムアウトエラーになる
lib/python3.12/socket.py", line 853, in create_connection
raise exceptions0
File "/***/.pyenv/versions/3.12.4/lib/python3.12/socket.py", line 838, in create_connection
sock.connect(sa)
TimeoutError: timed out
(参考)Codebuildの実行ロール
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"s3:PutObject",
"s3:GetObject",
"s3:GetObjectVersion",
"s3:GetBucketAcl",
"s3:GetBucketLocation",
"codebuild:CreateReportGroup",
"codebuild:CreateReport",
"codebuild:UpdateReport",
"codebuild:BatchPutTestCases",
"codebuild:BatchPutCodeCoverages",
"codebuild:StartBuild",
"codebuild:StopBuild",
"codebuild:RetryBuild",
"lambda:UpdateFunctionCode" ## これ大事!
],
"Resource": "*"
}
]
}
②CodePipeline作成
- CodePipelineを作成
- SourceステージはGithub version2にして、該当リポジトリの指定ブランチを選択
- Buildステージは上記のCodebuildプロジェクトを選択
③buildspec.yaml
作成
まず、ビルド時に必要なライブラリはrequirements_for_build.txt
に記載し、ルートにおいておく。
# 以下は必要に応じて変更してください
awscliv2==2.3.1
alembic==1.13.2
pymysql==1.1.1
SQLAlchemy==2.0.32
以下の内容でbuildspec.yaml
を作成してこちらもルートに置く。
(環境変数用の.env
もルートに)
version: 0.2
phases:
install:
runtime-versions:
python: 3.12
commands:
- yum update -y
- pip install -r requirements_for_build.txt
pre_build:
commands:
- echo Pre-build phase
- echo DB_HOST=$DB_HOST >> .env
- echo DB_PORT=3306 >> .env
- echo DB_USER=$DB_USER >> .env
- echo DB_PASSWORD=$DB_PASSWORD >> .env
- echo DB_NAME=$DB_NAME >> .env
- echo $S3_BUCKET_NAME
- echo $ZIP_FILE_NAME
- echo $LAMBDA_FUNCTION_NAME
build:
commands:
- echo Build phase
- cd app/database ## alembicディレクトリがapp/databaseにある場合
- alembic upgrade head
- cd ../../
- export TIMESTAMP=$(date +%Y%m%d%H%M%S)
- export UNIQUE_ZIP_FILE_NAME="${ZIP_FILE_NAME%.zip}_$TIMESTAMP.zip"
- echo $UNIQUE_ZIP_FILE_NAME
- zip -r $UNIQUE_ZIP_FILE_NAME .
- aws s3 cp $UNIQUE_ZIP_FILE_NAME s3://$S3_BUCKET_NAME/
post_build:
commands:
- echo Post-build phase
- aws lambda update-function-code --function-name $LAMBDA_FUNCTION_NAME --s3-bucket $S3_BUCKET_NAME --s3-key $UNIQUE_ZIP_FILE_NAME
artifacts:
files:
- $UNIQUE_ZIP_FILE_NAME
pre_build
フェーズの環境変数は、Codebuildプロジェクトから取得しています。あらかじめ登録しておいてください。
zip
コマンドでルート以下全てをZIP化し、
aws s3 cp
でS3にアップ、
aws lambda update-function-code
でLambdaにアップロード、
という手順です。
また、上記でタイムスタンプを利用しているのは、S3に保存されるZIP名称を一意にして、どの時点のZIPかを明示的にわかりやすくするためです。こうすることで、以下のようにZIP名に日時が含まれるようになります。
以上で、正常にマイグレーションとLambda関数の更新ができたことを確認できました。
参考になれば幸いです。