1.はじめに
1.1.まえがき
本ブログは以下項目の4つに分かれており、本ブログはその2つ目になります。
※ 本ハンズオンの全コードはこちらのGitHubリポジトリで参照できます。
項番 | ブログタイトル | 概要 |
---|---|---|
1 | 概要とAWS事前準備編 | Lambdaコンテナの仕組みと環境準備 |
2 | アプリケーションとCDK実装編(本ブログ) | LambdaアプリとCDKコードの解説 |
3 | GitHub Actions設定とデプロイ・挙動確認編 | CI/CDパイプライン構築 |
4 | トラブルシューティング集 | ハマりポイントと解決方法まとめ |
1.2.今回の内容
事前準備ができたので、今回はアプリケーションとインフラコードを作成します。
今回でやること:
- Lambda関数コードの作成:シンプルなHTTP APIエンドポイントのコード作成
- Dockerfileの作成:コンテナベースLambdaの環境構築コード作成
- スタック定義の実装:全インフラリソースをコードで定義
前提条件:
以下が完了していることを前提とします:
2.ハンズオン
2.1.前提
2.1.1.実行環境
- Amazo SageMaker Studio Code Editor(IDE)で実施
Amazon SageMaker Studio Code Editor での 検証環境構築手順で構築済み
2.1.2.全体のファイル構成
- 以下のファイル構成を
Amazo SageMaker Studio Code Editor(IDE)
で構築する
■ 本手順の最終的なファイル構成
your-app/
├── .gitignore #【前回作成】
├── .github/
│ └── workflows/
│ └── deploy.yml # 【次回作成】GitHub Actions ワークフロー定義
├── app/
│ ├── app.py #【★今回作成★】
│ └── Dockerfile #【★今回作成★】
└── cdk/ # AWS CDK アプリケーション (Python)
├── app.py #【★今回作成★】CDK エントリーポイント
├── cdk.json #【★今回作成★】CDK 設定ファイル
├── requirements.txt #【★今回作成★】CDK 依存関係
├── .venv/ # Python 仮想環境
└── pipeline_stack/ # スタック定義を置くディレクトリ
├── __init__.py #【★今回作成★】
└── pipeline_stack.py #【★今回作成★】 CDK スタック定義 (Python)
2.1.3.アプリケーションの説明
- 機能: HTTP GETリクエストを受け取ると、事前に設定されたメッセージと、現在実行されているLambda関数のバージョン情報、リクエストIDをJSON形式で返却する。
- CI/CDパイプライン構築が目的のため、アプリケーション自体のロジックは最小限にしている。
2.1.4.インフラの説明
- AWS上にサーバーレスなAPIバックエンドと、そのCI/CDパイプラインを構築する。
■ 利用するリソース
項番 | 提供元 | リソース名 | 概要 |
---|---|---|---|
1 | AWS | AWS Lambda | アプリケーションコード(コンテナイメージ)を実行するコンピューティングサービス。 |
2 | AWS | Amazon ECR | Lambda関数で利用するDockerコンテナイメージを保存・管理するためのプライベートリポジトリです。GitHub Actionsでビルドしたイメージがここにプッシュされる。 |
3 | AWS | Amazon API Gateway | Lambda関数を外部(インターネット)からHTTPリクエストで呼び出すためのエンドポイントを作成 |
4 | AWS | AWS CodeDeploy | Lambda関数の新しいバージョンを安全にデプロイするためのサービスです。今回 カナリアリリース戦略を採用する。 |
5 | AWS | IAM | GitHub ActionsがAWSリソース(ECRへのプッシュ、CDKによるデプロイなど)を操作するため、またLambda関数やCodeDeployが必要な権限で動作するためのロールとポリシーを作成・管理。OIDC (OpenID Connect) を利用してGitHub ActionsとAWS間の安全な認証連携を実現する。 |
6 | AWS | AWS CDK (Cloud Development Kit) | 上記のAWSリソース構成をPythonコードで定義し、CloudFormationスタックとしてプロビジョニング(作成・更新)する。 |
7 | GitHub | GitHub Actions | GitHubリポジトリへのコードプッシュをトリガーとして、アプリケーションのビルド(Dockerイメージ作成)、ECRへのプッシュ、CDKによるAWS環境へのデプロイを自動化するCI/CDパイプラインを構築。 |
2.1.5.本項目での構築箇所
2.2.gitignoreファイル作成
Amazo SageMaker Studio Code Editor(IDE)
以下コマンドを実行していく作業- ファイル作成の目的としては、Gitに追跡させたくないファイルを指定する。
# フォルダ作成
mkdir your-app
# ファイル作成
cat > your-app/.gitignore << EOF
# Python関連(コンパイル済みファイルや一時ファイル)
*.py[cod] # コンパイル済みPythonファイル(*.pyc, *.pyo, *.pyd)
*.pyo # Python最適化ファイル
__pycache__/ # Pythonキャッシュフォルダ
.env # 環境変数ファイル(シークレット含むかも!)
*.egg-info/ # パッケージ情報フォルダ
dist/ # ビルド成果物
build/ # ビルド一時フォルダ
wheels/ # パッケージ配布用ファイル
*.egg # 古い形式のパッケージ
.venv/ # Python仮想環境(ローカル用)
venv/ # 別名の仮想環境フォルダ
# AWS CDK関連(ビルド時に自動生成される)
cdk.out/ # CDKが生成するCloudFormationテンプレート置き場
cdk.context.json # CDKが保存する設定キャッシュ
.cdk.staging/ # CDKステージング用
# IDE/エディタ固有(ローカル設定)
.vscode/ # VSCode設定フォルダ
.idea/ # IntelliJ/PyCharm設定
*.swp # Vimのスワップファイル
*~ # バックアップファイル
# OS固有(無駄ファイル)
.DS_Store # macOSが作るサムネ情報
Thumbs.db # Windowsが作るサムネ情報
EOF
2.3.アプリケーション作成
- アプリケーションレイヤにあたる部分を作成。
- コンテナベースのLambdaのため、Dockerfileで環境を制御できるという特徴がある。
2.3.1.Lambda関数作成
- HTTP GETを受け取ると、事前に設定されたメッセージをJSON形式で返却する。
# フォルダ作成
mkdir -p your-app/app
# ファイル作成
cat > your-app/app/app.py << EOF
import json
import os
def handler(event, context):
# Lambda実行時の環境変数 'MESSAGE' からメッセージを取得
# 取得できない場合はデフォルトメッセージを使用
message = os.environ.get('MESSAGE', 'Hello from Lambda Container!')
print("Received event: " + json.dumps(event, indent=2))
return {
'statusCode': 200,
'body': json.dumps({
'message': message,
'version_id': context.function_version,
'aws_request_id': context.aws_request_id
})
}
EOF
2.3.2.Dockerfile作成
- LambdaコンテナイメージをビルドするためのDockerfileを作成。
- AWS が提供する公式ベースイメージを使うことで、必要な環境が含まれている。
# ファイル作成
cat > your-app/app/Dockerfile << EOF
# AWS公式提供のベースイメージを使用
FROM public.ecr.aws/lambda/python:3.11
# ハンドラコードをコピー
COPY app.py ./
# Lambdaが呼び出すハンドラを指定
CMD [ "app.handler" ]
EOF
2.4.CDK 作成
- CDK(AWS Cloud Development Kit)を使ってインフラストラクチャをコードで定義していく。
2.4.1.app.py 作成
- CDKアプリケーションのエントリーポイント。ここで必要なスタックを読み込み、AWS環境に対してデプロイを実行する。
CDK_DEFAULT_ACCOUNT
とCDK_DEFAULT_REGION
はCDK実行時に自動設定される環境変数。app.synth()
により CloudFormationテンプレートが生成される。
# フォルダ作成
mkdir -p your-app/cdk
# CDKアプリケーションファイル作成
cat > your-app/cdk/app.py << EOF
import os
import aws_cdk as cdk
# 実際のインフラ定義(スタック)をインポート
from pipeline_stack.pipeline_stack import PipelineStack
# CDKアプリケーションのインスタンス作成
app = cdk.App()
# スタックをアプリに追加(インフラの実体)
PipelineStack(app, "LambdaPipelineStackPy", # CFnスタック名
# デプロイ先のAWS環境を指定
env=cdk.Environment(
account=os.getenv('CDK_DEFAULT_ACCOUNT'),
region=os.getenv('CDK_DEFAULT_REGION')
)
)
# CloudFormationテンプレートを生成(cdk.out/ディレクトリに出力)
app.synth()
EOF
2.4.2.requirements.txt 作成
- CDKで利用するライブラリを定義するファイル
# requirementsファイルの作成
cat > your-app/cdk/requirements.txt << EOF
# 基本ライブラリ
aws-cdk-lib>=2.194.0,<3.0.0 # CDKコアライブラリ(V2)
constructs>=10.0.0,<11.0.0 # コンストラクトベースライブラリ
# Alpha モジュールのため、APIが変更される可能性がある
aws-cdk.aws-apigatewayv2-alpha
aws-cdk.aws-apigatewayv2-integrations-alpha
EOF
2.4.3.cdk.json 作成
- CDKアプリケーションの設定ファイル。CDK CLIがどのようにアプリを実行するかを指定する。
context
配下に設定されてるものが1つずつの機能フラグ
-"@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true
であれば、APIGatewayの機能(APIキーの順序を気にしない)という設定
cat > your-app/cdk/cdk.json << EOF
{
"app": "python3 app.py",
"context": {
"@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true,
"@aws-cdk/core:stackRelativeExports": true,
"@aws-cdk/aws-rds:lowercaseDbIdentifier": true,
"@aws-cdk/aws-lambda:recognizeVersionProps": true,
"@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true,
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"aws-cdk:enableDiffNoFail": "true",
"aws-cdk:cx-api": "true"
}
}
EOF
2.5.スタック定義部分の作成
2.5.1.スタック定義の意味
app.py
から呼び出されて実行される、実際のAWSリソースを定義
する重要なファイル- 本アプリでは以下のような役割を設計している
- 設定値の一元管理:リソースの設定値を上部にまとめて定義
- リソース依存関係の管理:AWS各サービスを適切な順序で定義
- CodeDeployによるデプロイ:カナリアリリースで段階的デプロイを定義
2.5.2.本アプリでの AWSリソース設定値
項番 | リソース名 | 設定項目 | デフォルト値 | 説明 |
---|---|---|---|---|
1 | Lambda | メモリ | 512MB | 十分な動作速度と適度なコスト |
– | – | タイムアウト | 30秒 | API応答には充分 |
– | – | メッセージ | Hello from CDK! | 環境変数として注入 |
2 | API Gateway | API名 | LambdaContainerApiPy | 識別しやすい名前 |
– | – | APIパス | /hello | シンプルなGETエンドポイント |
3 | CodeDeploy | アプリ名 | MyLambdaApplicationPy | デプロイアプリ識別用 |
– | – | エイリアス名 | live | 本番トラフィックを表す |
4 | ECRイメージ | タグ | latest/カスタム | GitHub Actionsから渡される |
2.5.3.Qualifierの取得
スタック定義で必要なECRリポジトリ名にはQualifier
が含まれます。
以下のコマンドをCloudShell環境で実行して、自身の環境の値を取得しコード内 # ECRリポジトリ名 (Bootstrapで作成されたもの)
を修正
# CDKが作成したS3バケット名を取得
BUCKET_NAME=$(aws cloudformation describe-stack-resources --stack-name CDKToolkit --query "StackResources[?ResourceType=='AWS::S3::Bucket'].PhysicalResourceId" --output text --region us-east-1)
# Qualifierを抽出(バケット名のパターン:cdk-{QUALIFIER}-...)
YOUR_QUALIFIER=$(echo "$BUCKET_NAME" | cut -d'-' -f2)
# 確認出力
echo $YOUR_QUALIFIER
2.5.4.ハンズオン開始
# フォルダ作成
mkdir -p your-app/cdk/pipeline_stack
# パッケージ初期化ファイル作成
touch your-app/cdk/pipeline_stack/__init__.py
# ファイル作成
cat > your-app/cdk/pipeline_stack/pipeline_stack.py << EOF
import os
import aws_cdk as cdk
from constructs import Construct
from aws_cdk import (
aws_ecr as ecr,
aws_lambda as lambda_,
aws_iam as iam,
aws_codedeploy as codedeploy,
aws_apigatewayv2_alpha as apigwv2,
aws_apigatewayv2_integrations_alpha as apigw_integrations,
Duration,
RemovalPolicy,
Stack,
)
class PipelineStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
# ======== 設定変数(ここにまとめる) ========
# Lambda関連
LAMBDA_MEMORY = 512 # Lambda関数のメモリサイズ(MB)
LAMBDA_TIMEOUT = 30 # Lambda関数のタイムアウト(秒)
LAMBDA_MESSAGE = "Hello from CDK !" # 出力させるメッセージ
# API Gateway関連
API_NAME = "LambdaContainerApiPy" # API名
API_PATH = "/hello" # APIパス
# CodeDeploy関連
APP_NAME = "MyLambdaApplicationPy" # CodeDeployアプリケーション名
ALIAS_NAME = "live" # Lambdaエイリアス名
# ECRリポジトリ名 (Bootstrapで作成されたもの)
ECR_REPO_NAME = f"cdk-★★自身の Qualifier★★-container-assets-{cdk.Aws.ACCOUNT_ID}-{cdk.Aws.REGION}"
# GitHub Actionsから渡されるイメージタグ (Contextから取得)
IMAGE_TAG = self.node.try_get_context("image_tag")
if not IMAGE_TAG:
# フォールバックまたはエラー処理
print("Warning: image_tag context not found. Using 'latest'.")
IMAGE_TAG = "latest"
# raise ValueError("Context variable 'image_tag' is required.")
print(f"--- Using Image Tag from context: {IMAGE_TAG} ---") # 確認用プリント
# ======== リソース定義 ========
# --- ECR リポジトリの参照 ---
repository = ecr.Repository.from_repository_name( # リポジトリを参照
self, "LambdaEcrRepo",
repository_name=ECR_REPO_NAME
)
# --- Lambda 関数の IAM ロール ---
lambda_role = iam.Role(
self, "LambdaExecutionRole",
assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"),
managed_policies=[
iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AWSLambdaBasicExecutionRole")
],
)
# --- Lambda 関数 (ECRイメージを参照) ---
my_function = lambda_.Function(
self, "MyLambdaFunction",
code=lambda_.Code.from_ecr_image(
repository=repository, # リポジトリオブジェクトを渡す
tag_or_digest=IMAGE_TAG # タグを渡す
# image_uri は使わない
),
handler=lambda_.Handler.FROM_IMAGE,
runtime=lambda_.Runtime.FROM_IMAGE,
role=lambda_role,
environment={"MESSAGE": LAMBDA_MESSAGE},
memory_size=LAMBDA_MEMORY,
timeout=Duration.seconds(LAMBDA_TIMEOUT),
current_version_options=lambda_.VersionOptions(
removal_policy=RemovalPolicy.RETAIN,
)
)
# --- Lambda エイリアス (CodeDeployが管理) ---
alias = lambda_.Alias(
self, "LiveAlias",
alias_name=ALIAS_NAME,
version=my_function.current_version, # 初期状態では最新バージョンを指す
)
# --- CodeDeploy アプリケーション & デプロイグループ ---
application = codedeploy.LambdaApplication(
self, "CodeDeployApplication",
application_name=APP_NAME,
)
codedeploy.LambdaDeploymentGroup(
self, "DeploymentGroup",
application=application,
alias=alias,
deployment_config=codedeploy.LambdaDeploymentConfig.CANARY_10_PERCENT_5_MINUTES, # カナリアリリース設定
auto_rollback=codedeploy.AutoRollbackConfig(
# 必要に応じて自動ロールバック設定
failed_deployment=True, # デプロイ失敗時にロールバック
# deployment_in_alarm=True, # アラーム発報時にロールバック (別途CloudWatch Alarmの設定が必要)
)
)
# --- API Gateway (HTTP API) ---
http_api = apigwv2.HttpApi(
self, "MyHttpApi",
api_name=API_NAME,
description="API Gateway for Lambda Container (Python CDK)",
# CORS設定など必要に応じて追加
)
http_api.add_routes(
path=API_PATH,
methods=[apigwv2.HttpMethod.GET],
integration=apigw_integrations.HttpLambdaIntegration("LambdaIntegration", alias)
)
# --- Outputs ---
cdk.CfnOutput(self, "LambdaFunctionName", value=my_function.function_name)
cdk.CfnOutput(self, "LambdaFunctionArn", value=my_function.function_arn)
cdk.CfnOutput(self, "LambdaLiveAliasArn", value=alias.function_arn)
# API Gateway の URL の末尾スラッシュを削除してからパスを結合
api_endpoint_url = http_api.url.rstrip('/') if http_api.url else None
cdk.CfnOutput(
self, "ApiEndpoint",
value=f"{api_endpoint_url}{API_PATH}" if api_endpoint_url else "NoAPIGateway"
)
EOF
3.おわりに
今回は第2回 アプリケーションとCDK実装編を記載しました。
今回実施したことサマリ:
- シンプルなLambdaアプリケーションファイル作成
- Dockerfileで公式イメージをベースにLambda環境を構築するファイル作成
- CDKでインフラをコード化
- ECR・Lambda・API Gateway・CodeDeployをPythonで定義
- カナリアリリース設定でデプロイの安全性を確保
次回は実際のGitHub Actions設定とデプロイ・挙動確認編
で続きの構築をしていきたいと思います。