AWS CDKで定義したAレコードを変更する

この記事はカバー株式会社 Advent Calendar 2023 21日目の記事になります。
カバー株式会社でSREをしているSIです。よろしくお願いします。

# はじめに
今回は、ホロプラスのCDK環境でCloudFrontからApplication Load BalancerへRoute53のレコードを変更した際の話について書きます。
本記事ではアーキテクチャなどは紹介しませんのでご興味がある方は、チームメンバーが書いた下記の記事をお読みいただければと思います。

https://note.cover-corp.com/n/neaa0c11a32d0?magazine_key=mb173ca3b0d76

https://note.cover-corp.com/n/ndf90bb1cc07b?magazine_key=mb173ca3b0d76

# 変更前の課題
ホロプラスでは、CloudFrontで全てのAPIアクセスにLambda@Edgeを利用しJWT認証を行なっていましたが、リクエストの度にLambdaがJWT情報の取得を行なっていたためアクセスに300ms ~ 400msの処理時間がかかっていました。
この課題を解決するためにAPI側でJWT認証処理を行うように変更を行うこととしました。
その為、Route53のAレコードを変更しApplication Load Balancerへ直接アクセスを行うように構成を変更することになりました。

# Stack状況
ホロプラスのCDK構成はAPIサーバに関するリソースを1つのStack内でAWSが定義するL3 constructsとしてある程度のリソース単位に抽象化を行い定義をしています。

https://docs.aws.amazon.com/cdk/v2/guide/constructs.html#constructs_lib

```distribution.ts
import { 
  Stack, 
  StackProps, 
  Duration,
  aws_lambda as lambda,
  aws_cloudfront as cloudfront,
  aws_route53 as route53,
  aws_route53_targets as targets,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Distribution, OriginRequestPolicy, ViewerProtocolPolicy, AllowedMethods, CachePolicy, ResponseHeadersPolicy, LambdaEdgeEventType } from 'aws-cdk-lib/aws-cloudfront';
import { HttpOrigin } from 'aws-cdk-lib/aws-cloudfront-origins';
import { Function } from 'aws-cdk-lib/aws-lambda';

export class MyCloudFrontDistribution extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // Lambda@Edgeの設定（auth Lambda関数）を定義
    const authLambda = new Function(this, 'AuthFunction', {
        stackId: `auth-edge-function`,
        code: lambda.Code.fromAsset('lambda/auth/dist'),
        handler: 'index.handler',
        runtime: lambda.Runtime.NODEJS_16_X,
        functionName: `auth-function`,
    });


    // CloudFrontディストリビューションの作成
    const distribution = new Distribution(this, 'MyDistribution', {
      comment: 'Example service distribution',
      defaultBehavior: {
        origin: new HttpOrigin('example-load-balancer.amazonaws.com', {
          customHeaders: {},
          protocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        }),
        allowedMethods: AllowedMethods.ALLOW_ALL,
        viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        originRequestPolicy: new OriginRequestPolicy(this, 'MyOriginRequestPolicy', {
          originRequestPolicyName: 'MyOriginRequestPolicy',
          comment: 'A policy for api',
          cookieBehavior: cloudfront.OriginRequestCookieBehavior.none(),
          headerBehavior: cloudfront.OriginRequestHeaderBehavior.allowList(
            'OS-Version',
            'Accept-Language',
            'App-Version',
            'User-ID',
            'Device-ID',
            'Device-Name',
            'Origin',
            'FCM-Registration-Token',
            'Access-Control-Request-Headers',
            'Access-Control-Request-Method'
          ),
          queryStringBehavior: cloudfront.OriginRequestQueryStringBehavior.all(),
        }),
        cachePolicy: CachePolicy.CACHING_DISABLED,
        responseHeadersPolicy: ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS_WITH_PREFLIGHT,
        edgeLambdas: [{
          functionVersion: authLambda.currentVersion,
          eventType: LambdaEdgeEventType.VIEWER_REQUEST,
        }],
      },
      domainNames: ['api.example.com'],
      certificate: DummyApiCertificate,
      priceClass: PriceClass.PRICE_CLASS_ALL,
      webAclId: DummyWebAclArn,
    });

    // Route53の設定
    if (ExampleZone) {
      new route53.ARecord(this, `ExampleDNSRecord`, {
        recordName: 'api.example.com',
        zone: 'XYZ0123456789',
        target: route53.RecordTarget.fromAlias(
          new targets.CloudFrontTarget(distribution)
        ),
        ttl: Duration.minutes(5),
      })
    }
  }
}
```

# 変更方法
先ほどのdistribution.tsに記述している``route53.ARecord``を削除しfargate-api.tsにALBをTargetに変更したリソースとして定義します。

```fargate-api.ts
new route53.ARecord(this, `AlbExampleDNSRecord`, {
  recordName: 'api.example.com',
  zone: 'XYZ0123456789',
  target: route53.RecordTarget.fromAlias(
    new targets.LoadBalancerTarget(fargate.loadBalancer)
  ),
  ttl: Duration.minutes(5),
})
```
# デプロイ
実際にデプロイを行なってみると下記のメッセージが出力されました。
```
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:999999999999:stack/dev/60581c90-812e-11ee-9a6a-06dc22ec1947",
            "EventId": "devassethostingdevarecord5960E62B-CREATE_FAILED-2023-11-11T09:46:28.155Z",
            "StackName": "dev",
            "LogicalResourceId": "devassethostingdevarecord5960E62B",
            "PhysicalResourceId": "",
            "ResourceType": "AWS::Route53::RecordSet",
            "Timestamp": "2023-11-11T09:46:28.155000+00:00",
            "ResourceStatus": "CREATE_FAILED",
            "ResourceStatusReason": "[Tried to create resource record set [name='api.example.com.', type='A'] but it already exists]",
            "ResourceProperties": "{\"AliasTarget\":{\"HostedZoneId\":\"Z2FDTNDATAQYW2\",\"DNSName\":\"abcd.cloudfront.net\"},\"Type\":\"A\",\"HostedZoneId\":\"XYZ0123456789\",\"Name\":\"api.example.com.\"}",
            "ClientRequestToken": "5e5876bd7d4c5af1180d6011d6cb8f5f5f8024967e8f423e166dd3bbfe6d7697"
        },
```

## 原因
原因はCloud FormationのデプロイフローとしてCreateが完了した後にDeleteが実行されるためdistribution.tsの``route53.ARecord``削除が行われていない状態でfargate-api.tsの``route53.ARecord``の作成が行われていたためでした。

## 解決策
RecordSetの情報を確認していると``deleteExisting``というオプションを利用すると解決することがわかりました。
``deleteExisting``はレコードがある場合は既存のレコードを削除するオプションとなります。
これをfargate-api.tsの``route53.ARecord``へ付与すると強制的にレコードを変更することができました。
レコードが変更が完了した後に``deleteExisting``を削除してデプロイを行いレコード変更作業は完了となります。
```fargate-api.ts
new route53.ARecord(this, `AlbExampleDNSRecord`, {
  recordName: 'api.example.com',
  zone: 'XYZ0123456789',
  target: route53.RecordTarget.fromAlias(
    new targets.LoadBalancerTarget(fargate.loadBalancer)
  ),
  ttl: Duration.minutes(5),
  deleteExisting: true,
})
```

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_route53.RecordSet.html#deleteexisting

# おわりに
今回は、CDKのレコード変更について書かせていただきました。
レコード変更自体はよくある作業だと思いますので少しでも参考になれば幸いです。
最後まで読んでいただいてありがとうございます。

次回は[@kh-cover](https://qiita.com/kh-cover)による「Unity Editor拡張でPrefabのセットアップを効率化」です。こちらも是非ご覧ください。