mazyu36の日記

某SIer所属のクラウドエンジニアのブログ

AWS App Runner Workshop を AWS CDKで実装する

App Runnerの学習にあたり、以下のWorkshopをCDKで実装してみました。そのまとめです。

catalog.us-east-1.prod.workshops.aws

コードは以下にあります。

github.com

※App Runnerが何であるかは一切書いてありません。基礎知識を得たい場合は以下の記事がおすすめです。

go-to-k.hatenablog.com

動画が良い場合は以下。

www.youtube.com

目次

アーキテクチャ

以下のようにApp Runnerの主要な機能を一通り学習することができます。

  • WAF, X-RAY連携
  • SSM Parameter Store, Secrets Managerからの読み取り
  • VPCリソースとの接続

CDKプロジェクトの構成

.
├── README.md
├── bin
│   └── cdk.ts
├── cdk.json
├── cdk.out
├── lib
│   ├── apprunner-rds-workshop-stack.ts
│   └── construct
│       ├── apprunner.ts # App Runnerを実装
│       ├── database.ts # Auroraを実装
│       └── network.ts # VPCを実装
├── package-lock.json
├── package.json
├── test
│   └── cdk.test.ts
└── tsconfig.json

CDKの実装

networkの実装

Workshopに従い、以下の条件を満たすVPCを作成します。

  • NAT Gatewayは1つ
  • AZは3つにまたがる
  • 各AZはパブリックサブネットとプライベートサブネットを持つ

今回は以下のようにVPCのCIDRを指定し、上記に関するプロパティ設定を行った上で後はおまかせの形にしました。

    const vpc = new ec2.Vpc(this, 'Vpc', {
      natGateways: 1,   // NAT Gatewayは1つ
      maxAzs: 3,  // AZは3つ
      ipAddresses: ec2.IpAddresses.cidr(`10.0.0.0/16`),
      subnetConfiguration: [ // サブネットはパブリックとプライベート(アウトバウンドはNAT Gateway)を1つずつ。
        {
          cidrMask: 24,
          name: 'PublicSubnet',
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 24,
          name: 'PrivateSubnet',
          subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
        },
      ],
      enableDnsHostnames: true,
      enableDnsSupport: true
    }
    )

なお、3つ以上のAZにサブネットを作成する場合は、アカウントとリージョンを明示する必要があります。

dev.classmethod.jp

databaseの実装

Workshopに従いAurora MySQLを構築します。

今回はAurora Serverless v2にしてみました。

    const databaseCluster = new rds.DatabaseCluster(this, 'Database', {

      engine: rds.DatabaseClusterEngine.auroraMysql({
        version: rds.AuroraMysqlEngineVersion.VER_3_05_0
      }),
      vpc: props.vpc,
      vpcSubnets: props.vpc.selectSubnets({ subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }),  // 配置されるサブネットのタイプのみ指定
      credentials: rds.Credentials.fromGeneratedSecret('apprunner'),
      writer: rds.ClusterInstance.serverlessV2('Writer', {}),  // Aurora Serverless v2を指定
      serverlessV2MaxCapacity: 1.0,
      serverlessV2MinCapacity: 0.5,
      cloudwatchLogsRetention: logs.RetentionDays.ONE_DAY,

      removalPolicy: cdk.RemovalPolicy.DESTROY
    })

apprunnerの実装

今回のメインです。αモジュールを使用しています。

@aws-cdk/aws-apprunner-alpha module · AWS CDK

Dockerイメージによるデプロイ

今回はマネージドランタイムではなく、Dockerイメージによるデプロイにしてみました。

モノレポ構成でアプリ(ap)とインフラ(cdk)を同居させているので、DockerImageAssetによりcdk deploy時にイメージのビルド、プッシュを行います。

    // Dockerイメージのビルド・プッシュ
    const image = new DockerImageAsset(this, 'CDKAppRunnerSampleImage', {
      directory: '../ap',
      platform: Platform.LINUX_AMD64,
    });

なお、M2 Macbook AirApple Silicon)を使用しているのですが、Docker Desktopではうまくビルドができませんでした。そのため今回 Finchを使用しています。

Finchの解説は以下が参考になりました。

zenn.dev

CDKでFInchを使用する場合は、以下のようにFinchVMを起動して、環境変数CDK_DOCKERを設定します。CDK_DOCKERの説明は以下が参考になりました。

dev.classmethod.jp

# finch vm起動
finch vm start

# CDK_DOCKERを設定
export CDK_DOCKER=finch

VPC Connectorの設定

VPCリソースに接続するためVPC Connector を作成します。

セキュリティグループの設定はconnectionsを使用して簡易に実装しています。

※参考

mazyu36.hatenablog.com

    // VPC Connector
    const vpcConnector = new apprunner.VpcConnector(this, 'VpcConnector', {
      vpc: props.vpc,
      vpcSubnets: props.vpc.selectSubnets({ subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }),  // プライベートサブネットを指定
    })

    // AppRunner -> RDSのアクセス許可
    props.databaseCluster.connections.allowDefaultPortFrom(vpcConnector)  // connectionsでVPC ConnectorからAuroraに接続できるよう設定

App RunnerのServiceを作成

サンプルAPで使用するSSM Parameter Storeと、App RunnerのServiceを定義します。

SSM Parameter StoreやSecrets ManagerはenvironmentSecretsで参照させます。

    // SSM Parameter Store
    const ssmParameter = new ssm.StringParameter(this, 'SsmParameter', {
      parameterName: 'HOTEL_NAME',
      stringValue: 'YOUR HOTEL NAME'
    });

    // App Runner Service
    const appRunnerService = new apprunner.Service(this, 'AppRunnerService', {
      cpu: apprunner.Cpu.ONE_VCPU,
      memory: apprunner.Memory.TWO_GB,
      source: apprunner.Source.fromAsset({
        asset: image,  // DockerImageAssetで定義したassetを指定
        imageConfiguration: {
          port: 8080,
          environmentSecrets: {
            MYSQL_SECRET: apprunner.Secret.fromSecretsManager(props.databaseCluster.secret!),  // AuroraのSecrets Managerを参照
            HOTEL_NAME: apprunner.Secret.fromSsmParameter(ssmParameter),  // SSM Parameter Storeを参照
          }
        },
      }),
      vpcConnector: vpcConnector
    })

X-RAYの有効化

執筆時点ではαモジュールで有効化できなかったので、Escape Hatchesを使用して有効化しています。

    // X-RAY有効化
    const cfnObservabilityConfig = new CfnObservabilityConfiguration(this, 'SampleAppRunnerObserveConfig', {
      observabilityConfigurationName: 'SampleAppRunnerObserveConfig',
      traceConfiguration: {
        vendor: 'AWSXRAY'
      }
    });
    const cfnService = appRunnerService.node.defaultChild as CfnService;
    cfnService.addPropertyOverride('ObservabilityConfiguration', {
      'ObservabilityEnabled': true,
      'ObservabilityConfigurationArn': cfnObservabilityConfig.ref
    })

WAF設定

WebACLを作成して、ARNで紐付けを行います。

    // WAF設定
    const appWaf = new wafv2.CfnWebACL(this, "AppRunnerWaf", {
      defaultAction: { allow: {} },
      scope: "REGIONAL",
      visibilityConfig: {
        cloudWatchMetricsEnabled: true,
        sampledRequestsEnabled: true,
        metricName: "App-Runner-Waf",
      },
      rules: [
        // AWSManagedRulesCommonRuleSet
        {
          priority: 1,
          overrideAction: { none: {} },
          visibilityConfig: {
            sampledRequestsEnabled: true,
            cloudWatchMetricsEnabled: true,
            metricName: "AWS-AWSManagedRulesCommonRuleSet",
          },
          name: "AWSManagedRulesCommonRuleSet",
          statement: {
            managedRuleGroupStatement: {
              vendorName: "AWS",
              name: "AWSManagedRulesCommonRuleSet",
            },
          },
        },
        // AWSManagedRulesKnownBadInputsRuleSet
        {
          name: "AWSManagedRulesKnownBadInputsRuleSet",
          priority: 2,
          statement: {
            managedRuleGroupStatement: {
              vendorName: "AWS",
              name: "AWSManagedRulesKnownBadInputsRuleSet",
            },
          },
          overrideAction: { none: {} },
          visibilityConfig: {
            cloudWatchMetricsEnabled: true,
            sampledRequestsEnabled: true,
            metricName: "AWSManagedRulesKnownBadInputsRuleSet",
          },
        },
      ],
    });

    // WebACLをApp Runnerに紐付け
    new wafv2.CfnWebACLAssociation(this, "Waf-App-Runner", {
      webAclArn: appWaf.attrArn,
      resourceArn: appRunnerService.serviceArn,
    });

動作確認

デプロイ後、App Runnerのエンドポイントにブラウザからアクセスし、サンプルアプリが起動していることを確認します。

Parameterタブで、SSM Parameter StoreやSecrets Managerの値を確認することができます。DBのパスワードはマスクされていますね。

Createタブを押下すると、アプリからDBに対してDDLが発行されテーブルが作成されます。

その後AddRoomタブでDBの更新や内容の確認が行えます。

おわりに

AWS App Runner WorkshopでApp Runnerの主要機能を一通り試せるので、使用を検討されてる方はおすすめです。