GitHub ActionsでIAM Roleが利用できるようになったと聞いたので、簡単なパイプラインを組んでみた(lambda編)
GitHubActionsについてはあまり触れてこなかったのですが、どうもこれまではAWSのIAMユーザーを作成してアクセスキーとシークレットアクセスキーを発行して、環境変数に持たせるようなやり方をする必要があったようなのですが、クレデンシャルを持たせないでもIAM Roleを利用できるようになったとのこと。
簡単なLamdba関数のソースをGit管理し、それがmainブランチに適用されたタイミングでLamdba関数を更新するパイプラインを組んでみました。
Lambdaの概要
特定のタグ付けがなされていて、停止中のインスタンスを起動させます。
AWS認定試験の問題にも出てきそうですが
- 平日の夜になれば開発環境用のEC2を停止させる
- また、平日の朝になればそれらEC2を起動する
といったユースケーズで使うようなイメージです。
前準備
IAMポリシー
GithubActionsのワークフローではこちらのIAMポリシーを利用します。
// GithubActions用
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"iam:ListRoles",
"lambda:UpdateFunctionCode",
"lambda:CreateFunction",
"lambda:UpdateFunctionConfiguration"
],
"Resource": "*"
}
]
}
また lamdba関数にアタッチするIAMポリシーも作成しておきます。
// lambda関数用(余分なActionsが含まれています)
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"ec2:StartInstances",
"ec2:DescribeInstances",
"ec2:DescribeImages",
"ec2:DescribeTags",
"ec2:DescribeSnapshots"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
IAM IDプロバイダー
OpenIDConnect を利用するための IDプロバイダを準備します。
- プロバイダ
- vstoken.actions.githubusercontent.com
- プロバイダタイプ
- OpenID Connect
- 対象者
- sigstore
【参考】
AWS federation comes to github actions
OpenID Connectとは
IAMロール
Github Actions用
- 信頼されたエンティティ
- ↑で作成した IDプロバイダを選択
- ポリシー
- GithubActions用に作成したIAMポリシー
lambda関数用
- 信頼されたエンティティ
- AWSサービス → Lamda関数を選択
- ポリシー
- Lambda関数用に作成したポリシー
GitHubActions用に作成したロールのARNをコピーしておく
Lambda関数を準備
今回は aws-cliの update-functionsでアップデートすることを想定しているため、一旦元となるlamdba関数を作成する。
- 関数の名前
- aws_ec2_start
- IAMロール
- ↑でLamda関数用に作成したIAMロールを指定
Secretsを設定
ロールのARNをgithubのSecretsに登録しておきます。
ROLE_ARN=${GithubActions用に作成したIAMロールのARN}
# ${}の部分は消してください
コードの用意
実際のリポジトリはこちらです。
ファイル構造などはこちらをご覧ください
GithubActions用のyaml設定
name: DeployLamdaFunctions
on:
push:
branches:
- main
jobs:
build_and_deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
env:
WORK_DIR: ./aws_ec2_start
steps:
- uses: actions/checkout@v2
with:
ref: main
- uses: actions/setup-python@v1
with:
python-version: 3.8
- name: Configure AWS
run: |
export AWS_ROLE_ARN=${{ secrets.ROLE_ARN }}
export AWS_WEB_IDENTITY_TOKEN_FILE=/tmp/awscreds
export AWS_DEFAULT_REGION=ap-northeast-1
echo AWS_WEB_IDENTITY_TOKEN_FILE=$AWS_WEB_IDENTITY_TOKEN_FILE >> $GITHUB_ENV
echo AWS_ROLE_ARN=$AWS_ROLE_ARN >> $GITHUB_ENV
echo AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION >> $GITHUB_ENV
curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sigstore" | jq -r '.value' > $AWS_WEB_IDENTITY_TOKEN_FILE
- name: zip source code
run: |
pip3 install -r requirements.txt -t ./src/
cd src;zip -r ../package.zip .
- name: deploy lambda function
run: |
# aws_ec2_start という名前で先にlambda関数を作成しておく必要がある
aws lambda update-function-code --function-name aws_ec2_start --zip-file fileb://package.zip --publish
lambda関数用のコードはこのような感じ
import boto3
from botocore.exceptions import ClientError
import json
class ResponseHandler:
ERR_TEMPLATE = {
"errorType": "",
"errorMessage": ""
}
RES_TEMPLATE = {
"Message": ""
}
def __init__(self):
self.errors = []
def append_eroor(self, type: str, message: str):
"""返却するエラー内容をセット
Args:
type str: エラータイプ
message str: エラーメッセージ
"""
dic = ResponseHandler.ERR_TEMPLATE.copy()
dic['errorType'] = type
dic['errorMessage'] = message
self.errors.append(dic)
def return_errors(self) -> str:
"""エラーメッセージの返却
Returns:
str: JSON形式のエラー内容
"""
raise Exception(json.dumps(self.errors))
def get_respose(self, body: str) -> str:
"""
レスポンスをJsonで返却
通常成功時はEC2インスタンスのidをリスト形式で返却
Args:
body str: レスポンス内容
"""
dic = ResponseHandler.RES_TEMPLATE.copy()
dic['Message'] = body
return json.dumps(dic)
response = ResponseHandler()
def lambda_handler(event, context):
instances = get_ec2_instances()
try:
instances.start()
except ClientError as e:
response.append_eroor(type="ClientError", message=str(e))
response.return_errors()
except Exception as e:
response.append_eroor(type="UnExpectedError", message=str(e))
response.return_errors()
id_list = [instance.id for instance in instances]
return response.get_respose(",".join(id_list))
def get_ec2_instances():
"""EC2インスタンスを取得
"""
ec2 = boto3.resource('ec2')
filter = [
{
'Name': 'tag:Reboot',
'Values': ['True']
},
{
'Name': 'instance-state-name',
'Values': ['stopped']
}
]
result = ec2.instances.filter(Filters=filter)
return result
デプロイの経過
こんな感じで githubの mainリポジトリにコードがコミットされると・・・
Lanmda関数も更新されている!
さらに変更がマージされると・・・・
バージョンがアップされている!
CloudWatchEventsの設定
CI / CDパイプラインとは関係ありませんが、Cloud Watch Eventsで特定の時間にLambda関数が実行されるように設定しました。
Discussion