0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS CDK + GitHub Actionsで作るLambdaコンテナCI/CDパイプライン(2/4)

Last updated at Posted at 2025-07-03

1.はじめに

1.1.まえがき

本ブログは以下項目の4つに分かれており、本ブログはその2つ目になります。

※ 本ハンズオンの全コードはこちらのGitHubリポジトリで参照できます。

項番 ブログタイトル 概要
1 概要とAWS事前準備編 Lambdaコンテナの仕組みと環境準備
2 アプリケーションとCDK実装編(本ブログ) LambdaアプリとCDKコードの解説
3 GitHub Actions設定とデプロイ・挙動確認編 CI/CDパイプライン構築
4 トラブルシューティング集 ハマりポイントと解決方法まとめ

1.2.今回の内容

事前準備ができたので、今回はアプリケーションとインフラコードを作成します。

今回でやること:

  1. Lambda関数コードの作成:シンプルなHTTP APIエンドポイントのコード作成
  2. Dockerfileの作成:コンテナベースLambdaの環境構築コード作成
  3. スタック定義の実装:全インフラリソースをコードで定義

前提条件:

以下が完了していることを前提とします:

2.ハンズオン

2.1.前提

2.1.1.実行環境

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.本項目での構築箇所

image.png

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_ACCOUNTCDK_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リソースを定義する重要なファイル
  • 本アプリでは以下のような役割を設計している
  1. 設定値の一元管理:リソースの設定値を上部にまとめて定義
  2. リソース依存関係の管理:AWS各サービスを適切な順序で定義
  3. 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設定とデプロイ・挙動確認編で続きの構築をしていきたいと思います。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?