Serverless Frameworkで実現するGitHub-flow
AI要約
Serverless FrameworkとGitHub Actionsを組み合わせ、PR作成時にブランチ別のテスト環境へ自動デプロイし、クローズ時に自動削除するブランチデプロイをGitHub-flowに組み込む実装方法を解説している。
三度の飯よりGitHub-Flowが好き
GitHub-flowはシンプルなブランチ戦略で、以下のような手順で本番環境に変更を反映する
- mainブランチからfeatureブランチを作成
- 変更をコミットし、プルリクエストを作成
- レビュワーからの指摘を受けつつ、リモートレポジトリの内容を変更
- プルリクエストがapproveされたら、mainブランチに反映
図解するとこんな感じ:
![]()
GitHub-flowについて
詳しくは以下参照:
Repositorydocs.github.comGitHub flow - GitHub Docsja / get-started
上記図の「リモートにpushしてPull Requestをオープン」の部分で、テスト用環境にデプロイできればさらに便利になりそうだ。ローカル環境でのテスト実行がうまくいっても、クラウド環境上でうまくいくとは限らない。GitHub Actionsを工夫して書けば、PRを提出した段階でテスト用の環境にデプロイする「ブランチデプロイ」が実現でき、開発フローをのシンプルさを維持できる。実際、オーケストレーションツールのdagsterはこのBranch Deploymentをネイティブ機能として組み込んでいたりする。
このブランチデプロイをServerless Frameworkで実行してみたいと思う。
Serverless Frameworkについて
Serverless Frameworkはサーバーレスのアプリケーションのデプロイを簡素化するフレームワークである。詳細は割愛するが、AWS Lambda関数のデプロイが簡単になったりして嬉しい。自分の業務だと、データパイプラインの一部でAWS Lambdaを使っているため、関数の管理を抽象化する用途でServerless Frameworkを利用している。
なお、Serverless FrameworkにもBranch Deploymentsの機能はあるが、思っていたのとは違ったのでGithub Actionsで実装する方針をとっている。
Documentationwww.serverless.comServerless Dashboard - CI/CD Branch Deploymentsframework
実装
要件定義:どんなふうにするか
- PRを提出したら、PRごとに個別に作成されたステージにデプロイされるようにする
- cron式は除外して、定期実行されないようにする
- PRをクローズしたら自動的に関数が削除されるようにする
ディレクトリ構成
.
├── .github
│ └── workflows
│ ├── delete_cron_trigger_from_serverless_yml.py
│ ├── sls_branch_deployment.yml
│ └── sls_main_deploy.yml
├── ServerlessFramework
│ ├── serverless.yml
│ ├── DockerImagePattern
│ │ ├── Dockerfile
│ │ ├── lambda_function.py
│ │ └── requirements.txt
│ └── SampleFunction
│ ├── lambda_function.py
│ └── requirements.txt
...
ファイル
sls_branch_deployment.ymlの中身は以下の通り: ステージ名はブランチ名からとっており、AWS Lambdaの関数の命名規則上使えない特殊文字は全てエスケープしている。
name: Serverless Framework branch deployment
on:
pull_request:
types: [opened, synchronize, reopened, closed]
paths:
- "ServerlessFramework/**"
workflow_dispatch:
env:
SERVERLESS_ACCESS_KEY: ${{ secrets.SERVERLESS_ACCESS_KEY }}
jobs:
branch_deployment:
name: Branch Deployment
runs-on: ubuntu-latest
if: github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened'
steps:
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: 3.9
- name: Install serverless framework
run: |
echo "install serverless framework"
npm install -g serverless
npm install serverless-python-requirements
- name: checkout for deployment
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
sparse-checkout: |
ServerlessFramework
.github
path: 'project-repo'
- name: Remove Cron trigger
run: |
cd project-repo
pwd
pip3 install pyyaml
python3 .github/workflows/delete_cron_trigger_from_serverless_yml.py ServerlessFramework/serverless.yml
- name: Make dot env file
run: |
touch .env
echo env_val=${{vars.ENV_VAL}} >> .env
// 必要に応じて追加
cp .env project-repo/ServerlessFramework/.env
- name: Deploy Functions
run: |
# Deploy to lambda
echo "deploy to lambda"
cd project-repo/ServerlessFramework
branch_name=${{ github.head_ref }}
escaped_branch_name=${branch_name//[^[:alnum:]]/-}
short_branch_name=${escaped_branch_name:0:10}
sls deploy --stage ${short_branch_name} --verbose
delete_functions:
name: Delete Function
runs-on: ubuntu-latest
if: github.event.action == 'closed'
steps:
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: 3.9
- name: Install serverless framework
run: |
echo "install serverless framework"
npm install -g serverless
npm install serverless-python-requirements
- name: checkout main branch
uses: actions/checkout@v4
with:
ref: main
sparse-checkout: |
ServerlessFramework
path: 'project-repo'
- name: Make dot env file
run: |
touch .env
echo env_val=${{vars.ENV_VAL}} >> .env
// 必要に応じて追加
cp .env project-repo/ServerlessFramework/.env
- name: Delete Functions
run: |
echo "remove from lambda"
cd project-repo/ServerlessFramework
branch_name=${{ github.head_ref }}
escaped_branch_name=${branch_name//[^[:alnum:]]/-}
short_branch_name=${escaped_branch_name:0:10}
sls remove --stage ${short_branch_name} --verbose
ブランチデプロイ環境で関数が定期実行されては困るので、serverless.ymlからcron式を除去する必要がある。 この動作を実行するPythonスクリプトdelete_cron_trigger_from_serverless_yml.pyの中身は以下の通り:
import yaml
import sys
serverless_yml_filepath = sys.argv[1]
# YAMLファイルを読み込む
with open(serverless_yml_filepath, "r") as f:
raw_yaml = yaml.safe_load(f)
for function_name in raw_yaml["functions"].keys():
function_detail_infomation = raw_yaml["functions"][function_name]
if ("events" in function_detail_infomation.keys()) and (
"schedule" in function_detail_infomation["events"][0].keys()
):
del raw_yaml["functions"][function_name]["events"]
else:
pass
# 編集後のYAMLをファイルに書き戻す
with open(serverless_yml_filepath, "w") as f:
yaml.safe_dump(raw_yaml, f)
本番用にデプロイしたい場合は、発動条件を変更し、ブランチデプロイ用のymlからcron式の除外部分を削除し、ステージ名をmainに変えれば良い。 本番用のGithub Actionsのコードsls_main_deployment.ymlは以下の通り:
name: Serverless Framework prod deployment
on:
push:
branches:
- main
paths:
- "ServerlessFramework/**"
workflow_dispatch:
env:
SERVERLESS_ACCESS_KEY: ${{ secrets.SERVERLESS_ACCESS_KEY }}
jobs:
main_deployment:
name: main_deployment
runs-on: ubuntu-latest
steps:
- name: checkout for deployment
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
sparse-checkout: |
ServerlessFramework
.github
path: 'project-repo'
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: 3.9
- name: Install serverless framework
run: |
echo "install serverless framework"
npm install -g serverless
npm install serverless-python-requirements
- name: Make dot env file
run: |
touch .env
echo env_val=${{vars.ENV_VAL}} >> .env
// 必要に応じて追加
cp .env project-repo/ServerlessFramework/.env
- name: Deploy Functions
run: |
echo "deploy to lambda"
cd project-repo/ServerlessFramework
sls deploy --stage dev --verbose