Upgrade to Pro — share decks privately, control downloads, hide ads and more …

社内向け CDK Construct ライブラリの舞台裏

Avatar for sakurai-ryo sakurai-ryo
July 18, 2026
52

社内向け CDK Construct ライブラリの舞台裏

Avatar for sakurai-ryo

sakurai-ryo

July 18, 2026

Transcript

  1. 発表の位置付け https://speakerdeck.com/gotok365/aws-summit-japan-2026 弊社チーフエンジニア後藤が AWS Summit Japan 2026 で登壇した内容の続き 社内ライブラリ化の動機や Construct

    の設計ポイントはぜひこちらをご覧ください この発表では実際の運用や技術的内容に踏み込んでご紹介予定 4 / 41
  2. トーク内容 1. Construct ライブラリの構成 2. 品質を支える仕組み 3. Projen について 4.

    リリースの自動化 5. Construct ライブラリの参考情報 6. まとめ 7. 会社紹介 5 / 41
  3. ディレクトリ構成 src 直下は以下のような構造で、各 Construct を配置 基本的には AWS のサービスごとにディレクトリを分ける aws-cdk-lib などと近い構成

    ある特定のユースケースに特化した Construct は専用のディレクト リを作成 例) spa-hosting は SPA をホスティングするために様々なサービ スを組み合わせた Construct src ├── alb ├── athena ├── cloudfront ├── core ├── credential ├── domain-healthcheck ├── ecs ├── logs-transfer ├── monitoring ├── redis ├── spa-hosting # 特定ユースケース向け ├── ssm ├── test-util └── waf 7 / 41
  4. ライブラリが提供する 3 パターン ③ L1 Construct の L2 化 L1

    Construct しかないリソースの L2 を用意して導入しやすくする ① 社内でよく使うインフラの構成 複数プロジェクトでよく利用する構成を再利用可能にする ② CDK コードで利用する Utility 関数 CDK コードでよく使う処理をまとめた Utility 関数を提供 ユニットテストのヘルパー etc… 8 / 41
  5. ライブラリが提供する 3 パターン ③ L1 Construct の L2 化 L1

    Construct しかないリソースの L2 を用意して導入しやすくする ① 社内でよく使うインフラの構成 複数プロジェクトでよく利用する構成を再利用可能にする ② CDK コードで利用する Utility 関数 CDK コードでよく使う処理をまとめた Utility 関数を提供 ユニットテストのヘルパー etc… 8 / 41
  6. ① 社内でよく使うインフラの構成 コンテナ基盤 API を ECS Fargate で運用することが多いので専用の Construct を用意

    コンテナイメージのビルド Trivy による脆弱性スキャン BuildKit のキャッシュを活用した高速 ビルド ECS Cluster, Service, TaskDefinition の作成 オートスケーリング設定 Firehose を活用したログの転送設定 export class Fargate extends Construct { constructor(scope: Construct, id: string, props: FargateProps) { // DockerImage Construct は次のスライドで紹介します const image = new DockerImage(this, "DockerImage", { ... }); const ecsCluster = new EcsCluster(this, 'Cluster', { ... }); // ... } } 10 / 41
  7. ① 社内でよく使うインフラの構成 コンテナ基盤 API を ECS Fargate で運用することが多いので専用の Construct を用意

    コンテナイメージのビルド Trivy による脆弱性スキャン BuildKit のキャッシュを活用した高速 ビルド ECS Cluster, Service, TaskDefinition の作成 オートスケーリング設定 Firehose を活用したログの転送設定 const image = new DockerImage(this, "DockerImage", { ... }); export class Fargate extends Construct { constructor(scope: Construct, id: string, props: FargateProps) { // DockerImage Construct は次のスライドで紹介します const ecsCluster = new EcsCluster(this, 'Cluster', { ... }); // ... } } 10 / 41
  8. ① 社内でよく使うインフラの構成 > コンテナ基盤 1. ビルドキャッシュの設定 aws_ecr_assets.DockerImageAsset をラップした DockerImage Construct

    を用意 利用側は意識することなくビルドキャッシュを活用できる export class DockerImage extends Construct { constructor(scope: Construct, id: string, props: DockerImageProps) { // ... const image = new DockerImageAsset(this, "Image", { // `docker build` コマンドのキャッシュ設定 cacheTo: { type: "inline" }, cacheFrom: [{ type: "registry", params: { ... }, }], }); } } 11 / 41
  9. ① 社内でよく使うインフラの構成 > コンテナ基盤 2. イメージの脆弱性スキャン 同じく DockerImage Construct 内で、Trivy

    による脆弱性スキャンを設定 デプロイ時に脆弱性が検知された場合は cdk deploy が失敗する image-scanner-with-trivy を内部で利用: https://github.com/go-to-k/image-scanner-with-trivy // 利用側: スキャナを選択する new DockerImage(this, "Image", { containerImageScans: [ContainerImageScan.withTrivy()], }); // ライブラリ側: スキャンが通ったあとにのみECRへデプロイされるよう依存関係を張る export abstract class ContainerImageScan { public static withTrivy(props?: ContainerImageScanWithTrivyProps): ContainerImageScanWithTrivy { // `image-scanner-with-trivy` を利用したコンテナイメージの脆弱性スキャンを設定 } } 12 / 41
  10. ① 社内でよく使うインフラの構成 SPA ホスティング SPA を S3 + CloudFront でホスティングする場合の

    Construct を用意 S3, CloudFront の作成 カスタムドメインの設定 デプロイ後に CloudFront の Invalidation を自動実行する仕組み CloudFront のアクセスログをクエリするための Athena 設定 13 / 41
  11. ① 社内でよく使うインフラの構成 > SPA ホスティング 1. ホスティングに必要なリソースをまとめて作成 S3, CloudFront, CachePolicy

    をまとめて作成する Construct を用意 Invalidation, AccessLog 設定はさらに別 Construct に切り出している これらの仕組みは SPA のホスティング以外でも利用したいため export class SpaHosting extends Construct { constructor(scope: Construct, id: string, props: SpaHostingProps) { const originBucket = new s3.Bucket(this, "OriginBucket", { ... }); const cachePolicy = new cloudfront.CachePolicy(this, "CachePolicy", { ... }); const distribution = new cloudfront.Distribution(this, 'Resource', { ... }); // ... new Invalidation(this, 'Invalidation', { ... }); new AccessLogConfigV2(this, 'AccessLogConfig', { ... }); } } 14 / 41
  12. ① 社内でよく使うインフラの構成 > SPA ホスティング 1. ホスティングに必要なリソースをまとめて作成 S3, CloudFront, CachePolicy

    をまとめて作成する Construct を用意 Invalidation, AccessLog 設定はさらに別 Construct に切り出している これらの仕組みは SPA のホスティング以外でも利用したいため const originBucket = new s3.Bucket(this, "OriginBucket", { ... }); const cachePolicy = new cloudfront.CachePolicy(this, "CachePolicy", { ... }); const distribution = new cloudfront.Distribution(this, 'Resource', { ... }); export class SpaHosting extends Construct { constructor(scope: Construct, id: string, props: SpaHostingProps) { // ... new Invalidation(this, 'Invalidation', { ... }); new AccessLogConfigV2(this, 'AccessLogConfig', { ... }); } } 14 / 41
  13. ① 社内でよく使うインフラの構成 > SPA ホスティング 1. ホスティングに必要なリソースをまとめて作成 S3, CloudFront, CachePolicy

    をまとめて作成する Construct を用意 Invalidation, AccessLog 設定はさらに別 Construct に切り出している これらの仕組みは SPA のホスティング以外でも利用したいため new Invalidation(this, 'Invalidation', { ... }); new AccessLogConfigV2(this, 'AccessLogConfig', { ... }); export class SpaHosting extends Construct { constructor(scope: Construct, id: string, props: SpaHostingProps) { const originBucket = new s3.Bucket(this, "OriginBucket", { ... }); const cachePolicy = new cloudfront.CachePolicy(this, "CachePolicy", { ... }); const distribution = new cloudfront.Distribution(this, 'Resource', { ... }); // ... } } 14 / 41
  14. ① 社内でよく使うインフラの構成 > SPA ホスティング 2. Invalidation S3 Object 作成/

    削除 SQS Queue CloudWatch Alarm EventBridge Rule SSM Automation Document CloudFront Invalidation Lambda 不要 SSM Automation Document から Invalidation API を直接呼ぶ S3 に複数ファイルがアップされても、まとめ て 1 回だけ Invalidation するように Alarm を 設定 export class Invalidation extends Construct { constructor(scope, id, props: InvalidationProps) { const queue = new Queue(this, 'Queue', { ... }); const doc = new CfnDocument(this, 'Document', { ... }); const alarm = new Alarm(this, 'SqsAlarm', { metric: queue.metricNumberOfMessagesSent(), }); new Rule(this, 'AlarmEventRule', { ... }).addTarget({ ... }); } } 15 / 41
  15. ① 社内でよく使うインフラの構成 > SPA ホスティング 3. アクセスログ CloudFront のアクセスログを標準ログ v2

    形式で S3 に出力する AWS::Logs::Delivery などのリソースは CFn 対応しているがあえて AwsCustomResource で作成 ap-northeast-1 でデプロイしている CloudFront とログ設定を同じリージョンでデプロイしたい Athena でクエリするための Glue Table も一緒に作成 export class AccessLogConfigV2 extends Construct { constructor(scope: Construct, id: string, props: AccessLogConfigV2Props) { const logBucket = new s3.Bucket(this, 'LogBucket', { ... }); const deliverySource = new cr.AwsCustomResource(this, 'DeliverySource', { ... }); const deliveryDestination = new cr.AwsCustomResource(this, 'DeliveryDestination', { ... }); const delivery = new cr.AwsCustomResource(this, 'Delivery', { ... }); new CfnTable(this, 'Table', { ... }); } } 16 / 41
  16. ① 社内でよく使うインフラの構成 > SPA ホスティング 3. アクセスログ CloudFront のアクセスログを標準ログ v2

    形式で S3 に出力する AWS::Logs::Delivery などのリソースは CFn 対応しているがあえて AwsCustomResource で作成 ap-northeast-1 でデプロイしている CloudFront とログ設定を同じリージョンでデプロイしたい Athena でクエリするための Glue Table も一緒に作成 const deliverySource = new cr.AwsCustomResource(this, 'DeliverySource', { ... }); const deliveryDestination = new cr.AwsCustomResource(this, 'DeliveryDestination', { ... }); const delivery = new cr.AwsCustomResource(this, 'Delivery', { ... }); export class AccessLogConfigV2 extends Construct { constructor(scope: Construct, id: string, props: AccessLogConfigV2Props) { const logBucket = new s3.Bucket(this, 'LogBucket', { ... }); new CfnTable(this, 'Table', { ... }); } } 16 / 41
  17. ① 社内でよく使うインフラの構成 > SPA ホスティング 3. アクセスログ CloudFront のアクセスログを標準ログ v2

    形式で S3 に出力する AWS::Logs::Delivery などのリソースは CFn 対応しているがあえて AwsCustomResource で作成 ap-northeast-1 でデプロイしている CloudFront とログ設定を同じリージョンでデプロイしたい Athena でクエリするための Glue Table も一緒に作成 new CfnTable(this, 'Table', { ... }); export class AccessLogConfigV2 extends Construct { constructor(scope: Construct, id: string, props: AccessLogConfigV2Props) { const logBucket = new s3.Bucket(this, 'LogBucket', { ... }); const deliverySource = new cr.AwsCustomResource(this, 'DeliverySource', { ... }); const deliveryDestination = new cr.AwsCustomResource(this, 'DeliveryDestination', { ... }); const delivery = new cr.AwsCustomResource(this, 'Delivery', { ... }); } } 16 / 41
  18. ① 社内でよく使うインフラの構成 > アプリケーションの監視 1. よく使う Widget をメソッド化 メソッド呼び出しで Widget

    を返すことで、利用側の可読性が向上する // ライブラリ側 export class AlbMonitoringFactory extends Construct { private readonly alb: alb.IApplicationLoadBalancer; public unhealthyHostCountGraphWidget( props?: Partial<cw.GraphWidgetProps> ): GraphWidget { return new cw.GraphWidget({ title: "UnHealthyHostCount", // Widget の詳細設定 ...props, }); } // ... } // 利用側 // ライブラリの初期化 const factory = new AlbMonitoringFactory( this, "AlbMonitoringFactory", { ... } ); const dashboard = new cw.Dashboard(this, "Dashboard"); // Widget を行に追加 dashboard.addWidgets( new cw.Row( factory.unhealthyHostCountGraphWidget(), factory.requestCountGraphWidget(), ), ); 18 / 41
  19. ① 社内でよく使うインフラの構成 > アプリケーションの監視 2. Alarm も同様にメソッド化 Widget と同様によくあるアラームもメソッド化すると便利 export

    class AlbMonitoringFactory extends Construct { private readonly alarmActionTopic: sns.ITopic; private readonly alb: alb.IApplicationLoadBalancer; public unhealthyHostCountAlarm(id: string, props?: Partial<cw.AlarmProps>): cw.IAlarm { const alarm = new cw.Alarm(this, id, { // ALB の UnHealthyHostCount を監視するアラーム ...props, }); alarm.addAlarmAction(new cwa.SnsAction(this.alarmActionTopic)); return alarm; } } 19 / 41
  20. ② CDK コードで利用する Utility 関数 テストヘルパー CDK の ユニットテストの際、 cdk.json

    にある機能フラグは渡されない テスト時に cdk.json を読み込み、機能フラグを渡すためのヘルパー関数を用意 テスト時の Bundling も無効化 // ライブラリ側 import { BUNDLING_STACKS, DISABLE_ASSET_STAGING_CONTEXT } from "aws-cdk-lib/cx-api"; export const getYourOwnCdkJsonContext = (props: getContextOptions): Record<string, any> => { // ... const context = JSON.parse(readFileSync(props.cdkJsonPath, "utf-8")).context ?? {}; return { ...context, [DISABLE_ASSET_STAGING_CONTEXT]: true, [BUNDLING_STACKS]: [], }; }; 21 / 41
  21. ② CDK コードで利用する Utility 関数 テストヘルパー CDK の ユニットテストの際、 cdk.json

    にある機能フラグは渡されない テスト時に cdk.json を読み込み、機能フラグを渡すためのヘルパー関数を用意 テスト時の Bundling も無効化 // ライブラリ側 import { BUNDLING_STACKS, DISABLE_ASSET_STAGING_CONTEXT } from "aws-cdk-lib/cx-api"; export const getYourOwnCdkJsonContext = (props: getContextOptions): Record<string, any> => { // ... const context = JSON.parse(readFileSync(props.cdkJsonPath, "utf-8")).context ?? {}; return { ...context, [DISABLE_ASSET_STAGING_CONTEXT]: true, [BUNDLING_STACKS]: [], }; }; 21 / 41
  22. Unit Tests Fine-grained assertions テストを利用したユニットテストを作成 特に書いておきたいテスト ループ処理 条件分岐 セキュリティなどの重要な設定 CI

    で必ず実行することで、Construct の変更が意図しない影響を与えていないかを検証する CDK のテストについては AWS CDK における単体テストの使い所を学ぶ が参考になります。 23 / 41
  23. integ-tests の活用 @aws-cdk/integ-tests-alpha を利用したインテグレーションテストを活用 この仕組みを利用すると、AWS 上に CDK スタックをデプロイしてテストが可能 ユニットテストでは検証できない、デプロイ時の挙動を検証できる リソースの破壊的な変更等も検知可能

    1 Construct に対して正常系 1 ケース書いておくだけでも、リリース時の安心感が大きく変わる テストケース例 ( aws-cdk-lib ): https://github.com/aws/aws-cdk/tree/v2.261.0/packages/%40aws-cdk-testing/framework-integ/test 24 / 41
  24. eslint-plugin-awscdk の活用 https://awscdk-lint.dev/ ESLint の Plugin として CDK コードを機械的 にチェック

    Oxlint でも利用可能 ( oxlint-plugin-awscdk ) CDK のセオリーに基づくルールが多数用意 弊社のエンジニアが開発 ルール例: Props Interface のプロパティは全て readonly にする grant* メソッドではなく grants プロパティを 使用する // https://awscdk-lint.dev/rules/prefer-grants-property.html import { Role, ServicePrincipal } from "aws-cdk-lib/aws-iam"; import { Topic } from "aws-cdk-lib/aws-sns"; const role = new Role(stack, "MyRole", { ... }); const topic = new Topic(stack, "MyTopic"); // ✅ `grants.*` の利用は OK topic.grants.subscribe(role); // ❌ `grants*` を利用した場合はルール違反 topic.grantSubscribe(role); 25 / 41
  25. Projen とは https://projen.io/docs/project-types/aws-cdk-construct-library#getting-started Construct ライブラリを作成する際によく使われる ツール .projenrc.ts を通じてプロジェクト設定をコードで 管理する 以下の仕組みを自動で作成

    Linter, Testing Framework パッケージリリースのための GitHub Actions 多言語配布のための jsii 設定 // .projenrc.ts import { awscdk } from "projen"; const project = new awscdk.AwsCdkConstructLibrary({ cdkVersion: "2.0.0", defaultReleaseBranch: "main", }); // dependencies を追加 project.addDeps("hoge"); // package.json の scripts に追加 project.addTask("start", { execArgs: ["ts-node", "src/index.ts"], }); project.synth(); 27 / 41
  26. Projen は利用しない方針へ 最初は導入してたものの、途中で Projen を利用しない方針に切り替え GitHub 前提のエコシステム 社内では GitLab を利用しているが、GitLab

    CI は自動生成されない jsii は利用しないこととした 社内向けなので、ライブラリ利用側の言語は TypeScript に限定可能 TS に限定することで、 jsii の制約を受けず TS の型定義をフルに活用できる .projenrc.ts を利用したプロジェクト管理のオーバーヘッド 複数プロジェクト・モノレポの設定を一括管理する場合は便利 ただ単一プロジェクトではオーバーヘッドが大きくなりがち 28 / 41
  27. リリースまでのフロー ① MR 作成 ② main ブランチへマージ ③ CI/CD 上でリリース処理

    ④ GitLab Package Registry へ公開 ⑤ API ドキュメント生成 リリースフローは MR の main ブランチへのマージを起点に処理が行われる 一般的な Construct ライブラリのリリースフローと同様 Projen が生成する GitHub Actions もこれとほぼ同じフロー ※ MR (Merge Request )≒ GitHub の Pull Request 30 / 41
  28. ① MR 作成 1 つの MR に対して、バージョニングやリリースノートの生成が行う 1 MR はリリースノートで見やすい単位にまとめる

    MR のタイトルは Conventional Commit の形式に従う 機能追加: feat: add new feature バグ修正: fix: fix bug その他: chore: update dependencies Conventional Commit の形式に従うことで、以下の利点がある バージョニングの自動化 リリースノートの自動生成 31 / 41
  29. ③ CI/CD 上でリリース処理 Projen が生成する GitHub Actions のリリースフローとほぼ同様の処理を自作 commit-and-tag-version を利用したバージョニングと

    CHANGELOG の自動生成 Conventional Commit の形式に従って、Semantic Versioning に基づき自動でバージョンを決定 例: feat → minor バージョンアップ、 fix → patch バージョンアップ # Changelog All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/ab ## [0.1.0] (2026-01-01) ### Features - 新機能の説明 ({コミットハッシュ}) 32 / 41
  30. ⑤ API ドキュメント生成 Projen が生成するワークフローを利用する場合、 jsii-docgen を利用して API ドキュメントが生成される jsii-docgen:

    https://github.com/cdklabs/jsii-docgen open-constructs/aws-cdk-library での例 代わりとして、TypeDoc を利用して API ドキュメント生成 33 / 41
  31. Amazon の社内事例 https://www.youtube.com/watch?v=ugtsm3Z3VgU AWS re:Invent 2023 で Amazon 社内の Construct

    ライブラリの事例が紹介 リポジトリ: https://github.com/amzn/buy-with-prime-cdk-constructs 36 / 41
  32. Amazon の社内事例 buy-with-prime-cdk-constructs 主要な AWS サービス向けの Construct を提供 alb /

    dynamodb / ecs / kms / s3 / sns / sqs / cloudwatch-data-protection L2 を extends した Construct が多く用意されている 組織が求める要件にマッチするようにデフォルト値を変更 export class Bucket extends s3.Bucket { constructor(scope: Construct, id: string, props: BucketProps) { // s3.Bucket とは異なるデフォルト値を設定している const encryption = props.encryption ?? s3.BucketEncryption.KMS; const versioned = props.versioned ?? true; // new s3.Bucket(this, id, { ...props }) と同様 super(scope, id, { ...{ encryption, versioned }, ...props }); } } 37 / 41
  33. Amazon の社内事例 buy-with-prime-cdk-constructs の注意点 L2 を直接 extends する場合は意図しないメソッドのオーバーライドに気を付ける tsconfig で

    noImplicitOverride を活用 L2 のデフォルト値を変更したい場合、現在は Property Injector で対応が可能 https://aws.amazon.com/blogs/devops/standardizing-construct-properties-with-aws-cdk-property-injection/ export class MyBucketPropsInjector implements IPropertyInjector { // ... public inject(originalProps: BucketProps, _context: InjectionContext): BucketProps { return { // Props を上書きする ...originalProps, }; } } 38 / 41
  34. まとめ 以下 3 パターンを中心にライブラリ化 ① よく使うインフラ構成 ② Utility 関数 ③

    L1 の L2 化 まずは Projen のレールにのっかるのがおすすめ ライブラリ公開まで必要な設定が Projen によって自動化される 要件に応じて、Construct ライブラリでも Projen を使わない選択肢もある 設計やテストの書き方は Construct Hub や Amazon 社内の事例もぜひ参考に 39 / 41