GitHub Actions+AWS SAM で CI/CD を構築してみた
はじめに
はじめまして。 SHIFTでインフラエンジニアをしている kinno です。
今年の5月にSHIFTに入社して早8ヶ月、優秀な方々に囲まれて切磋琢磨しています。
現在、クラウド環境へのCI/CD構築の案件に携わっており、CI/CDの設計・構築をしています。
CI/CDにも色々ありますが、GitHubでソースコードを管理している場合、
GitHub Actions という機能を使って簡単にCI/CDが実現できることを知りました。
そこで、今回は CI/CD の学習目的で、GitHub Actions と AWS SAM を使用して簡単な CI/CD の構築をしてみました。
CI/CD 初心者の方、GitHub で CI/CD をしたい方の参考になれば幸いです。
※ AWSの基礎用語を理解されている方向けの内容となります。
目標
目標は以下の通りです。
CI/CD の学習はもちろんですが、GitHub Actions や AWSクラウド、IaC についての理解も深めていきます。
・GitHub Actions + AWS SAM で CI/CD を構築・実行する
ブランチへの push をトリガーに起動する GitHub Actions ワークフローを作成する
ワークフローの中で、テストツール(pytest)を使ったコードの単体テストを実行する
単体テストの結果がOKであれば、AWSクラウド上に Cloud Formation を使用してスタック(Lambda、API Gateway)を作成する。
・サービス構成図
用語解説
CI/CDの構築に取りかかる前に、今回使用するサービスと用語について簡単に紹介します。
CI/CD
継続的インテグレーション/継続的デリバリー(又はデプロイ)のこと。
アプリケーション開発における一連のプロセスを自動化することで、開発から本番リリースを効率的に行う開発手法。
GitHub Actions
GitHub が提供するワークフローエンジン。
リポジトリやイシューに対する操作をトリガーに、ワークフローを実行でき、GitHub で CI/CD を実現することができる。
AWS サーバーレスアプリケーションモデル (AWS SAM)
サーバーレスアプリケーション構築用のオープンソースフレームワーク。
CloudFormation の拡張機能であり、AWSリソースをコードで管理することができる。
事前準備
早速、CI/CDの構築に取り掛かります。まずは事前準備からです。
サインアップ
AWS クラウド上にアプリケーションをデプロイするので、
AWS アカウントのサインアップ、
また、GitHub Actions を利用するため、 GitHub アカウントのサインアップをします。
※ サインアップ手順は割愛します。
GitHub リポジトリの作成
GitHub Actions を実行するリポジトリを作成します。
ソースコードを格納するリポジトリなので、プライベートで作成しました。
このリポジトリには、以下のファイルを格納します。
GitHub Actions のワークフローファイル
AWS SAM のテンプレートおよび設定ファイル
アプリケーションのソースコード
pytestのテスト用コード
ディレクトリ構成は以下の通りです。
/
├── .github/
│ └── workflows/
│ └── github-actions.yml # GitHub Actions のワークフローファイル
├── app/
│ └── functions/
│ └── hello_world.py # アプリケーションのソースコード
├── tests/
│ └── test_hello_world.py # pytestのテスト用コード
├── requirements.txt # インストールパッケージ記載ファイル
├── samconfig.toml # AWS SAM の設定ファイル
└── template.yml # AWS SAM のテンプレート
アプリケーションのコードの作成
テストおよびデプロイの対象となるアプリケーションのコードを作成します。今回はCI/CDの構築がメインなので、簡単な Lambda関数 のコードを用意しました。使用言語は Python です。
import json
def lambda_handler(event, context):
return {
'statusCode': 200,
'body': json.dumps('Hello world!')
}
テストツールについて
テストツールには、Pythonの単体テストツールのデファクトスタンダードである「pytest」を使用します。
pytestは、Python スクリプトをテストするためのフレームワークであり、
Python コードでテスト用プログラムを記述することができます。
テストコードは以下の通りです。
import os
import sys
sys.path.append(os.environ["REPOSITORY_HOME"] + "/app/functions")
import hello_world
def test_ok ():
res = hello_world.lambda_handler(1,2)
status_code = res['statusCode']
assert status_code == 200
AWSコンソールの作業
GitHub Actions が AWS 認証をするための設定を行います。
GitHub Actions では、OpenID Connect がサポートされており、一時的なアクセストークンを使用してAWS リソースにアクセスすることができます。
(詳細はこちらを参照。)
IDプロバイダーの登録
AWS に GitHub OIDCプロバイダを登録します。
IDプロバイダを登録することで、AWSアカウントとGitHub OIDCプロバイダ間で信頼関係を確立します。
IAM コンソールの [IDプロバイダ] を開き、 [プロバイダを追加] をクリックします。
以下の通りに設定して、[サムプリントを取得] ⇒ [プロバイダを追加] の順にクリックします。
プロバイダのタイプ:OpenID Connect
対象者(Audience):sts.amazonaws.com
IAMロールの作成
GitHub Actions で使用する IAM ロールを作成していきます。
IAM ロールには信頼ポリシーと、AWSリソースへのアクセス許可ポリシーを設定します。
IAM コンソールの [IDプロバイダ] を開いて、[ロールを作成]をクリックします。
信頼ポリシー
信頼ポリシーには、ロールの引き受け先として前の手順で登録したIDプロバイダ(GitHubのOIDC)を指定します。
ポリシーの JSON は以下の通りです。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::{AWSアカウント名}:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:{GitHubユーザー名}/sample-git-repository:*"
}
}
}
]
}
Principal に、前の手順で作成したIDプロバイダのARNを設定します。
また、Condition にStringLike を使用して、事前準備で作成したリポジトリ
「sample-git-repository」からのみ認証を許可するように設定しています。
アクセス許可ポリシー
アクセス許可ポリシーには、GitHub Actions で操作するAWSリソースの許可ポリシーを設定します。
AWS SAM のデプロイをするために必要な権限を設定していきます。
詳細は後ほど説明しますが、SAMデプロイにはS3のフルアクセス権限をつけておく必要があります。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"cloudformation:*",
"s3:*",
"lambda:*",
"apigateway:*",
"iam:CreateRole",
"iam:DeleteRole",
"iam:GetRole",
"iam:PassRole",
"iam:DeleteRolePolicy",
"iam:GetRolePolicy",
"iam:AttachRolePolicy",
"iam:DetachRolePolicy",
"iam:PutRolePolicy"
],
"Resource": "*"
}
]
}
S3バケットの作成
AWS SAM ではデプロイ時に、アプリケーションのコードを圧縮して S3 にアップロードしています。
前手順でIAMロールにS3へのアクセス権限が必要となるのは、このデプロイ時にS3にアクセスするためです。
事前にアップロード先の S3 を作成しておきます。
ファイルの作成
AWSの設定が完了したところで、CI/CD 構築に必要となるファイルを作成していきます。
GitHub Actionsワークフロー
GitHub Actionsのワークフローファイルを作成します。
このワークフローファイルには、GitHub Actionsで自動化する処理を書いていきます。
ワークフローファイルは、リポジトリの .github/workflows/ 配下に YML 形式で保存します。
name: Deploy-AWS-CloudFormation
on:
push:
branches:
- 'develop'
workflow_dispatch:
env:
TEMPLATE_FILE: template.yml
CONFIG_ENV: sample
jobs:
deploy:
runs-on: ubuntu-22.04
permissions:
id-token: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Unit-test
id: unit-test
continue-on-error: true
run: |
pip install -r requirements.txt -q
export REPOSITORY_HOME=$(pwd)
cd tests/
pytest -v --cov --cov-branch --cov-report=html test_hello_world.py
cd ${REPOSITORY_HOME}
- name: Setup python
if: steps.unit-test.outcome == 'success'
uses: actions/setup-python@v3
- name: Setup aws-sam
if: steps.unit-test.outcome == 'success'
uses: aws-actions/setup-sam@v2
- name: Configure AWS credentials
if: steps.unit-test.outcome == 'success'
uses: aws-actions/configure-aws-credentials@v1-node16
with:
aws-region: ap-northeast-1
role-to-assume: arn:aws:iam::{AWSアカウント名}:role/github-actions-oidc-depoloy
- run: aws sts get-caller-identity
- name: Build & Deploy Cloudformation stacks
if: steps.unit-test.outcome == 'success'
run: |
sam build --use-container --config-env ${CONFIG_ENV} --template-file ${TEMPLATE_FILE} --parameter-overrides CompanyName=shift ProjectName=blog
sam deploy --config-env ${CONFIG_ENV} --template-file ${TEMPLATE_FILE} --parameter-overrides CompanyName=shift ProjectName=blog
トリガーは develop ブランチへの push と、手動でも実行できるように workflow_dispatchにしています。
ジョブの内容を細かく見ていくと、
permissions:
id-token: write
contents: read
permission: ではジョブに対して、IDトークンの書き込み権限を割り当てています。
ワークフローがGitHub OIDCのトークンを要求するために必要になります。
- name: Unit-test
id: unit-test
continue-on-error: true
run: |
pip install -r requirements.txt -q
export REPOSITORY_HOME=$(pwd)
cd tests/
pytest -v --cov --cov-branch --cov-report=html test_hello_world.py
cd ${REPOSITORY_HOME}
ステップ「Unit-test」では、pytest を使ってユニットテストを実行します。
ユニットテストの結果を元に後続の処理(デプロイ)を実行するかどうか、条件分岐をしたいので、
id: で、ステップに一意の識別子「unit-test」を割り当てておきます。
- name: Setup python
if: steps.unit-test.outcome == 'success'
uses: actions/setup-python@v3
- name: Setup aws-sam
if: steps.unit-test.outcome == 'success'
uses: aws-actions/setup-sam@v2
次のステップで、python パッケージ、aws-sam パッケージをインストールおよびセットアップします。
if: steps.unit-test.outcome == 'success' でステップ「Unit-test」の結果が正常終了であれば処理を実行するように、
ステップの実行条件を指定しています。
- name: Configure AWS credentials
if: steps.unit-test.outcome == 'success'
uses: aws-actions/configure-aws-credentials@v1-node16
with:
aws-region: ap-northeast-1
role-to-assume: arn:aws:iam::{AWSアカウント名}:role/github-actions-oidc-depoloy
- run: aws sts get-caller-identity
AWSの認証部分です。
aws-actions/configure-aws-credentials@v1-node16 では、GitHub OIDCトークンを取得して、
AWSから指定したロール(github-actions-oidc-depoloy)を引き受けています。
- name: Build & Deploy Cloudformation stacks
if: steps.unit-test.outcome == 'success'
run: |
sam build --use-container --config-env ${CONFIG_ENV} --template-file ${TEMPLATE_FILE} --parameter-overrides CompanyName=shift ProjectName=blog
sam deploy --config-env ${CONFIG_ENV} --template-file ${TEMPLATE_FILE} --parameter-overrides CompanyName=shift ProjectName=blog
ここまでのステップを経て、最後にAWS SAMのビルド、デプロイを実行します。
AWS SAM テンプレート
AWS SAM テンプレートファイルを作成します。
このファイルには、AWS 上にデプロイする AWS リソース(Lambda と API Gateway)を定義します。
テンプレートは、CloudFormation 構文を拡張した SAM 構文で記述します。
(実際は、SAM構文で記述されたテンプレートを CloudFormation 構文に変換してデプロイされます。)
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Sample-App
Globals:
Function:
Runtime: python3.9
Parameters:
CompanyName:
Type: String
AllowedValues:
- shift
ProjectName:
Type: String
AllowedValues:
- blog
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${CompanyName}-${ProjectName}-sample-function
Description: Sample-Function
CodeUri: ./app/functions
Policies:
- AWSLambdaBasicExecutionRole
- Version: 2012-10-17
Statement:
- Resource: "*"
Effect: Allow
Action:
- lambda:InvokeFunction
Handler: hello_world.lambda_handler
Events:
AppMethod:
Type: Api
Properties:
RestApiId: !Ref HelloWorldAPI
Path: /hello_world
Method: GET
Tags:
Name: !Sub ${CompanyName}-${ProjectName}-sample-function
CompanyName: !Sub ${CompanyName}
ProjectName: !Sub ${ProjectName}
HelloWorldAPI:
Type: AWS::Serverless::Api
Properties:
Name: !Sub ${CompanyName}-${ProjectName}-sample-api
Description: Sample-API
StageName: stg
EndpointConfiguration:
Type: REGIONAL
Tags:
Name: !Sub ${CompanyName}-${ProjectName}-sample-api
CompanyName: !Sub ${CompanyName}
ProjectName: !Sub ${ProjectName}
AWS SAM設定ファイル
AWS SAM のコマンド実行時に参照される設定ファイル(samconfig.toml)を作成します。
version = 0.1
[sample.deploy.parameters]
stack_name = "sample-app-stack"
s3_bucket = "shift-kinno-awssam"
s3_prefix = "sample-app-stack"
region = "ap-northeast-1"
confirm_changeset = false
capabilities = "CAPABILITY_IAM"
fail_on_empty_changeset = false
CI/CD の実行
長くなってしまいましたが、これにて準備完了です。
実際に CI/CD を動かしてみたいと思います。
ワークフローの実行
developブランチへのプルリクエストを作成して、マージします。
↓
マージが正常に完了しました。
develop ブランチに push されたことをトリガーに、GitHub Actions のワークフローが動いているはずです。
成功していました!
結果の詳細画面では、各ステップの結果を確認することができます。
成功したステップの左側には、チェックマークがついています。
AWSリソースの確認
AWSクラウドにデプロイされた、Lambda 関数と API ゲートウェイを確認します。
想定通り、Lambda関数が作成されていますね。
API ゲートウェイのエンドポイントから Lambda関数にアクセスしてみます。
Lambda関数の処理結果が返ってきました!
デプロイの結果も問題なしです。
ユニットテストが失敗した場合
最後に、ユニットテスト(pytest)の結果が"NG"だった場合、後続の処理がスキップされるかどうか確認しておきます。
Lambda関数の内容を書き換えて、ワークフローを動かします。
ユニットテストの後続のステップが、全てスキップされていますね。
GitHub Actionsの画面で参照できるログから、pytest の結果が「FAILED」となっていることが確認できます。
まとめ
GitHub ActionsとAWS SAMを組み合わせて簡単なCI/CDを作成してみました。
作業工程はそこそこ多かったですが、ワークフローの作成まで済ませてしまえば、コードを作成および修正 ⇒ テスト ⇒ デプロイ を自動で行うことができます。
ファイルを GitHub で管理したかったので、GitHub Actions を使用しましたが、パイプラインに CodePipeline や CodeCommit を使うのもいいかと思います。
CI/CD について単語の意味をなんとなく覚えていただけでしたが、今回の取り組みで、その仕組みが理解できたような気がします。
次は作成した CI/CD を使って、実際にアプリケーション開発をしてみたいです。
参考
お問合せはお気軽に
https://service.shiftinc.jp/contact/
SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/
SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/
SHIFTの導入事例
https://service.shiftinc.jp/case/
お役立ち資料はこちら
https://service.shiftinc.jp/resources/
SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/