AWS CDK 統合的アプローチによるベストプラクティス

どうも、クラ本部の黒田です。
7月最初の土曜日、午後はAWS CDK Conference Japan 2024 オンライン参加し、AWS CDKについて、アウトプットしていきます。

はじめに

AWS Cloud Development Kit (CDK) は、クラウドインフラストラクチャをコードとして定義する強力なツールです。
今回は、インフラエンジニア、アプリケーションエンジニア、AWSエンジニアの視点を統合し、CDKを最大限に活用するためのベストプラクティスをアウトプットしていきます。

1. インフラストラクチャの設計と分離

効果的なCDK利用の基盤は、適切なインフラストラクチャ設計です。以下のポイントに注目しましょう:

  • 論理的なスタック分離
  • 環境ごとの設定管理
  • クロススタックレファレンスの活用

例えば、ネットワークとデータベースを別々のスタックに分離する方法を考えてみましょう:

class NetworkStack extends cdk.Stack {
  public readonly vpc: ec2.Vpc;

  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    this.vpc = new ec2.Vpc(this, 'MainVPC', {
      maxAzs: 2,
      natGateways: 1
    });
  }
}

class DatabaseStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, vpc: ec2.Vpc, props?: cdk.StackProps) {
    super(scope, id, props);

    new rds.DatabaseInstance(this, 'Database', {
      engine: rds.DatabaseInstanceEngine.POSTGRES,
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
      vpc: vpc,
      vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }
    });
  }
}

この approach により、各コンポーネントの独立した管理と再利用が可能になります。

2. セキュリティとコンプライアンスの統合

セキュリティは全てのエンジニアの責任です。CDKを使用して、以下のようなセキュリティベストプラクティスを組み込みましょう:

  • 最小権限の原則に基づいたIAMポリシーの設定
  • リソースの暗号化
  • ネットワークセキュリティの強化

例えば、S3バケットのセキュリティ設定は以下のように実装できます:

const myBucket = new s3.Bucket(this, 'MyBucket', {
  encryption: s3.BucketEncryption.S3_MANAGED,
  enforceSSL: true,
  versioned: true,
  removalPolicy: cdk.RemovalPolicy.RETAIN
});

myBucket.addLifecycleRule({
  expiration: cdk.Duration.days(90),
  noncurrentVersionExpiration: cdk.Duration.days(7)
});

3. CI/CDパイプラインの統合

継続的インテグレーション/継続的デリバリー(CI/CD)は、モダンな開発プラクティスの要です。CDKを使用してCI/CDパイプラインを定義することで、インフラストラクチャの変更を自動的にテストし、デプロイできます。

const pipeline = new codepipeline.Pipeline(this, 'MyPipeline', {
  pipelineName: 'MyServicePipeline',
  crossAccountKeys: true
});

const sourceOutput = new codepipeline.Artifact();
const sourceAction = new codepipeline_actions.GitHubSourceAction({
  actionName: 'GitHub',
  output: sourceOutput,
  owner: 'myorg',
  repo: 'myrepo',
  branch: 'main',
  oauthToken: cdk.SecretValue.secretsManager('github-token')
});

pipeline.addStage({
  stageName: 'Source',
  actions: [sourceAction],
});

4. パフォーマンスとスケーラビリティの最適化

CDKを使用して、アプリケーションのパフォーマンスとスケーラビリティを向上させるための設定を簡単に実装できます。例えば:

  • Auto Scalingグループの設定
  • ElastiCacheやCloudFrontの活用
  • データベースのリードレプリカ設定

以下は、Auto Scaling設定の例です:

const asg = new autoscaling.AutoScalingGroup(this, 'ASG', {
  vpc: props.vpc,
  instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
  machineImage: ec2.MachineImage.latestAmazonLinux2(),
  minCapacity: 2,
  maxCapacity: 5
});

asg.scaleOnCpuUtilization('CpuScaling', {
  targetUtilizationPercent: 70
});

5. モニタリングとロギング

効果的なモニタリングとロギングは、問題の早期発見と解決に不可欠です。CDKを使用して、CloudWatchアラームやログ設定を簡単に実装できます:

const api = new apigateway.RestApi(this, 'myapi');

new cloudwatch.Alarm(this, 'API5XXAlarm', {
  metric: api.metricServerError(),
  threshold: 1,
  evaluationPeriods: 1,
  alarmDescription: '5XX errors > 1'
});

const logGroup = new logs.LogGroup(this, 'APILogs');
api.deploymentStage.accessLogSettings = {
  destinationArn: logGroup.logGroupArn,
  format: JSON.stringify({
    requestId: '$context.requestId',
    ip: '$context.identity.sourceIp',
    user: '$context.identity.user',
    requestTime: '$context.requestTime',
    httpMethod: '$context.httpMethod',
    resourcePath: '$context.resourcePath',
    status: '$context.status',
    protocol: '$context.protocol',
    responseLength: '$context.responseLength'
  })
};

インフラエンジニアの視点から見るAWS CDKのベストプラクティス

インフラエンジニアの視点から見るAWS CDKのベストプラクティスは、運用性、スケーラビリティ、セキュリティ、コスト最適化に重点を置き、主要なポイントをまとめます:

  1. インフラストラクチャの設計と分離:

    論理的な分離

    • ネットワーク、コンピュート、ストレージ、データベースなど、リソースタイプごとにスタックを分離します。
    • 環境ごと(開発、ステージング、本番)に異なるスタックを作成します。

    モジュール化

    • 共通のインフラストラクチャパターンを再利用可能なコンストラクトとして実装します。
    • マイクロサービスアーキテクチャを採用する場合、各サービスを独立したスタックとして管理します。

    環境固有の設定

    • 環境変数やコンテキスト値を使用して、環境固有の設定を外部化します。
    • パラメータストアやSecretsManagerを活用して、機密情報を安全に管理します。

    スケーラビリティ

    • Auto Scalingグループを適切に設定し、需要に応じてリソースを自動調整します。
    • リージョン間でのリソース複製を考慮し、グローバルな展開を容易にする設計
  2. セキュリティとコンプライアンス:

    • ネットワークセキュリティグループとNACLを適切に設定し、最小限の必要なポートのみを開放します。
    • IAMロールとポリシーを最小権限の原則に基づいて設定します。
    • KMSを使用してデータ暗号化を実装し、センシティブな情報を保護します。
    • AWS Config Rules や Security Hub を活用して、コンプライアンスチェックを自動化します。
  3. 監視とロギング:

    • CloudWatch Logsを使用して、アプリケーションとインフラストラクチャのログを一元管理します。
    • CloudWatch Alarmsを設定して、重要なメトリクスの異常を検知します。
    • X-Rayを統合して、分散システムのトレーシングを実現します。
  4. 災害復旧とバックアップ:

    • マルチAZ構成を採用し、単一障害点を排除します。
    • S3バケットのバージョニングとクロスリージョンレプリケーションを設定します。
    • RDSのスナップショットやDynamoDBのバックアップなど、データベースのバックアップ戦略を実装します。
  5. コスト最適化:

    • リザーブドインスタンスやSavings Plansを活用して、長期的なコスト削減を図ります。
    • Auto Scalingを適切に設定し、需要に応じてリソースを調整します。
    • S3のIntelligent-Tieringや、EBSの適切なボリュームタイプの選択など、ストレージコストを最適化します。
  6. CI/CDパイプラインの統合:

    • AWS CodePipelineを使用して、CDKデプロイメントを自動化します。
    • 環境ごとに異なるパイプラインを設定し、段階的なデプロイメントを実現します。
    • CDKのシンセサイズステップをパイプラインに組み込み、変更を事前に確認します。
  7. ドリフト検出と修復:

    • CDK Diffを定期的に実行し、インフラストラクチャのドリフトを検出します。
    • AWS Config Rulesを使用して、リソースの設定変更を監視し、自動修復を設定します。
  8. パフォーマンス最適化:

    • ElastiCacheやCloudFrontを活用して、アプリケーションのレスポンス時間を改善します。
    • RDSのリードレプリカやDynamoDBのグローバルテーブルを使用して、読み取りパフォーマンスを向上させます
  9. コードレビューとドキュメンテーション:

    • インフラストラクチャの変更に対して、厳格なコードレビュープロセスを実施します。
    • アーキテクチャ図やリソース依存関係を文書化し、チーム全体で共有します。
  10. 継続的な改善:

    • 新しいAWSサービスやCDKの機能を定期的に評価し、適切に採用します。
    • パフォーマンスメトリクスやコスト分析を定期的に行い、最適化の機会を特定します。

アプリケーションエンジニアの視点から見るAWS CDKのベストプラクティス

アプリケーションエンジニアの視点から見るAWS CDKのベストプラクティスは、開発効率、アプリケーションのパフォーマンス、デプロイメントの容易さに重点を置きます。
以下に主要なポイントをまとめます:

  1. アプリケーションアーキテクチャとの整合性:

    • CDKスタック構造をアプリケーションのアーキテクチャに合わせて設計します。
    • マイクロサービスアーキテクチャの場合、各サービスを独立したCDKスタックとして実装します。
  2. 開発環境の一貫性:

    • Docker Composeと組み合わせて、ローカル開発環境を本番環境に近づけます。
    • CDKを使用してAWS SAM Localと統合し、ローカルでのLambda関数のテストを容易にします。
  3. テストの自動化:

    ユニットテスト

    • Jest や Mocha を使用して、個々のCDKコンストラクトをテストします。
    • スナップショットテストを活用し、生成されるCloudFormationテンプレートの変更を検証します。

    統合テスト

    • CDK Synthを使用して、スタック全体の構成を検証します。
    • AWS SAM CLIを活用し、Lambda関数の統合テストを実行します。

    E2Eテスト

    • CDKデプロイ後に、実際のAWSリソースに対してE2Eテストを実行します。
    • Cypress や Selenium などのツールを使用して、フロントエンドのE2Eテストを自動化します。

    セキュリティテスト

    • cdk-nag を使用して、セキュリティベストプラクティスの違反をチェックします。
    • IAMポリシーシミュレーターを活用し、適切な権限設定をテストします。

    パフォーマンステスト

    • Artillery や Gatling を使用して、デプロイされたインフラストラクチャに対する負荷テストを実行します。
    • CloudWatch Synthetics を活用し、定期的なアプリケーションの可用性チェックを実施します。
  4. CI/CD パイプラインの最適化:

    • GitHubActionsやAWS CodePipelineを使用して、CDKデプロイメントを自動化します。
    • 環境ごとに異なるパイプラインを設定し、段階的なデプロイメントを実現します。
    • プルリクエストごとにCDK Diffを実行し、インフラストラクチャの変更を事前に確認します。
  5. アプリケーションのスケーラビリティ:

    • ECS FargateやEKSを使用して、コンテナベースのデプロイメントを実装します。
    • Auto Scalingを適切に設定し、アプリケーションの負荷に応じて自動的にスケールします。
    • DynamoDBのオンデマンドキャパシティやAurora Serverlessを活用し、データベースのスケーラビリティを確保します。
  6. パフォーマンス最適化:

    • CloudFrontとS3を組み合わせて、静的アセットの配信を最適化します。
    • ElastiCacheを使用して、頻繁にアクセスされるデータをキャッシュします。
    • RDSのリードレプリカを設定し、読み取り操作のパフォーマンスを向上させます。
  7. セキュリティとコンプライアンス:

    • Secrets ManagerやSystems Manager Parameter Storeを使用して、機密情報を安全に管理します。
    • WAFを設定し、Webアプリケーションを一般的な攻撃から保護します。
    • CloudTrailを有効にし、APIコールのログを記録・監査します。
  8. モニタリングとロギング:

    • CloudWatch Logsを使用して、アプリケーションログを一元管理します。
    • X-Rayを統合し、分散システムのトレーシングを実現します。
    • カスタムメトリクスを作成し、アプリケーション固有の指標を監視します。
  9. 開発者体験の向上:

    • CDK Watchを使用して、ローカルの変更を即座にAWS環境に反映します。
    • CDK Constructsライブラリを活用し、共通のパターンを再利用可能なモジュールとして実装します。
    • プロジェクトテンプレートを作成し、新しいマイクロサービスやコンポーネントの迅速な追加を可能にします。
  10. ドキュメンテーションとナレッジ共有:

    • CDKスタックの構成や依存関係を図示化し、アーキテクチャドキュメントを作成します。
    • READMEファイルを充実させ、プロジェクトのセットアップ手順や使用方法を詳細に記述します。
    • チーム内でCDKのベストプラクティスや学びを共有するための定期的なセッションを開催します。

AWSエンジニアの立場から見るAWS CDKのベストプラクティス

  1. 環境分離とスタック構造:
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as rds from 'aws-cdk-lib/aws-rds';

class NetworkStack extends cdk.Stack {
  public readonly vpc: ec2.Vpc;

  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    this.vpc = new ec2.Vpc(this, 'MainVPC', {
      maxAzs: 2,
      natGateways: 1
    });
  }
}

class DatabaseStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, vpc: ec2.Vpc, props?: cdk.StackProps) {
    super(scope, id, props);

    new rds.DatabaseInstance(this, 'Database', {
      engine: rds.DatabaseInstanceEngine.POSTGRES,
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
      vpc: vpc,
      vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }
    });
  }
}

const app = new cdk.App();

const devEnv = {
  account: process.env.CDK_DEFAULT_ACCOUNT,
  region: 'us-west-2'
};

const prodEnv = {
  account: process.env.CDK_DEFAULT_ACCOUNT,
  region: 'us-east-1'
};

const devNetworkStack = new NetworkStack(app, 'DevNetworkStack', { env: devEnv });
new DatabaseStack(app, 'DevDatabaseStack', devNetworkStack.vpc, { env: devEnv });

const prodNetworkStack = new NetworkStack(app, 'ProdNetworkStack', { env: prodEnv });
new DatabaseStack(app, 'ProdDatabaseStack', prodNetworkStack.vpc, { env: prodEnv });

ネットワークとデータベースを別々のスタックに分離し、開発環境と本番環境で異なる設定を適用しています。これにより、環境ごとの管理が容易になり、変更の影響範囲を限定できます。

  1. クロススタックレファレンス:

上記の例で、DatabaseStackNetworkStackからVPCを参照しています。スタック間で資源を共有しつつ、論理的な分離を維持できます。

  1. 環境固有の設定:
const config = {
  dev: { instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO) },
  prod: { instanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE) }
};

const env = this.node.tryGetContext('env') || 'dev';
const instanceType = config[env].instanceType;

このようにコンテキスト値を使用することで、環境ごとに異なる設定を簡単に適用できます。

  1. カスタムコンストラクトの利用:
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';

interface WebServerProps {
  vpc: ec2.Vpc;
  instanceType: ec2.InstanceType;
}

class WebServer extends cdk.Construct {
  constructor(scope: cdk.Construct, id: string, props: WebServerProps) {
    super(scope, id);

    const asg = new autoscaling.AutoScalingGroup(this, 'ASG', {
      vpc: props.vpc,
      instanceType: props.instanceType,
      machineImage: ec2.MachineImage.latestAmazonLinux2(),
      minCapacity: 2,
      maxCapacity: 5
    });

    const alb = new elbv2.ApplicationLoadBalancer(this, 'ALB', {
      vpc: props.vpc,
      internetFacing: true
    });

    const listener = alb.addListener('Listener', { port: 80 });
    listener.addTargets('WebServerFleet', {
      port: 80,
      targets: [asg]
    });

    asg.scaleOnRequestCount('AModestLoad', {
      targetRequestsPerMinute: 60
    });
  }
}

// Usage in a stack
const webServer = new WebServer(this, 'WebServer', {
  vpc: vpc,
  instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO)
});

このカスタムコンストラクトは、Auto Scaling Group、Application Load Balancer、スケーリングポリシーを含む完全なウェブサーバーセットアップをカプセル化しています。複雑なインフラストラクチャパターンを再利用可能な形で実装できます。

  1. タグ付けとメタデータ:
cdk.Tags.of(this).add('Environment', env);
cdk.Tags.of(this).add('Project', 'MyAwesomeProject');

すべてのリソースに一貫したタグを適用することで、コスト管理や運用管理が容易になります。

  1. セキュリティのベストプラクティス:
const myBucket = new s3.Bucket(this, 'MyBucket', {
  encryption: s3.BucketEncryption.S3_MANAGED,
  enforceSSL: true,
  versioned: true
});

myBucket.addLifecycleRule({
  expiration: cdk.Duration.days(90),
  noncurrentVersionExpiration: cdk.Duration.days(7)
});

この例では、S3バケットに対して暗号化、SSLの強制、バージョニング、ライフサイクルルールを適用しています。これらのセキュリティベストプラクティスを標準で適用することで、セキュリティリスクを軽減できます。

  1. CI/CDパイプラインの統合:
const pipeline = new codepipeline.Pipeline(this, 'MyPipeline', {
  pipelineName: 'MyServicePipeline',
  crossAccountKeys: true
});

const sourceOutput = new codepipeline.Artifact();
const sourceAction = new codepipeline_actions.GitHubSourceAction({
  actionName: 'GitHub',
  output: sourceOutput,
  owner: 'myorg',
  repo: 'myrepo',
  branch: 'main',
  oauthToken: cdk.SecretValue.secretsManager('github-token')
});

pipeline.addStage({
  stageName: 'Source',
  actions: [sourceAction],
});

// Add build, test, and deploy stages...

この例では、GitHub、CodeBuild、CloudFormationを使用したシンプルなCI/CDパイプラインを定義しています。コードの変更を自動的にテストし、デプロイすることができます。

以上、ベストプラクティスと実例を適用することで、AWSエンジニアはCDKを使用して、より管理しやすく、セキュアで、スケーラブルなインフラストラクチャを構築できます。また、これらの原則はコードの再利用性を高め、開発プロセス全体を効率化するのに役立ちます。

まとめ

AWS CDKを効果的に活用するには、異なる専門性を持つエンジニア間の協力が不可欠です。
インフラストラクチャのコード化、自動化、セキュリティ、スケーラビリティ、コスト最適化などの側面に注力しつつ、チーム全体でベストプラクティスを共有し、継続的に改善していくことが、成功への鍵となります。
より迅速で信頼性の高い、コスト効率の良いクラウドインフラストラクチャの構築と運用が可能になります。

では、また!

Last modified: 2024-07-06

Author