AWS .NET Deployment Toolで作成されるスタックをカスタマイズする

2021.10.19

いわさです。

先日、.NETツールの「AWS .NET Deployment Tool」を使って、App Runnerへ.NET Coreアプリケーションをデプロイする方法をご紹介しました。

デプロイ時のオプションは、ASP.NET Core MVCサンプルプロジェクトの場合だと、ECSまたはApp Runnerへのデプロイのどちらかを選択することが出来ました。
また、設定できるオプションとしてはポートとIAMロール、そしてCPUとメモリサイズでした。

AWS .NET Deployment Toolにはデプロイメントプロジェクトという機能があり、CDKベースでスタックのカスタマイズを行うことが出来ます。
例えば、コンテナのデプロイとあわせてデータベースやネットワークなど様々なリソースをデプロイすることが可能です。

デプロイメントプロジェクトの作成

プロジェクトファイルが存在する場所(この記事ではwebapp)でdotnet aws deployment-project generateを実行します。
実行すると実行ディレクトリと同列の階層にデプロイメントプロジェクトが作成されます。

デプロイメントプロジェクト作成時には、前回デプロイを行ったときと同じようにデプロイするテンプレートを選択します。
ここではApp Runnerを選択してみましょう。

iwasa.takahito@hoge webapp % dotnet aws deployment-project generate
AWS .NET deployment tool for deploying .NET Core applications to AWS.
Project Home: https://github.com/aws/aws-dotnet-deploy

Recommended Deployment Option
-----------------------------
1: ASP.NET Core App to Amazon ECS using Fargate
This ASP.NET Core application will be deployed to Amazon Elastic Container Service (Amazon ECS) with compute power managed by AWS Fargate compute engine. If your project does not contain a Dockerfile, it will be automatically generated. Recommended if you want to deploy your application as a container image on Linux.

Additional Deployment Options
------------------------------
2: ASP.NET Core App to AWS App Runner
This ASP.NET Core application will be built as a container image and deployed to AWS App Runner. If your project does not contain a Dockerfile, it will be automatically generated. Recommended if you want to deploy your application as a container image on a fully managed environment.

Choose deployment option (recommended default: 1)
2

Warning: The target directory is not being tracked by source control. If the saved deployment project is used for deployment it is important that the deployment project is retained to allow future redeployments to previously deployed applications. 

Do you still want to continue?: y/n (default y)
y

Saving AWS CDK deployment project to: /Users/iwasa.takahito/work/hoge-app/webapp.Deployment

デプロイメントプロジェクトが作成されました。
Visual Studio Codeで開いてみます。

この図だとwebappがWebアプリケーションで、webapp.Deploymentがデプロイメントプロジェクトです。

デプロイメントプロジェクトを使ったスタックのカスタマイズ

Program.cs->AppStack.cs->Recipe.csと辿っていくとわかりますが、テンプレート相当のリソース(App RunnerとIAMリソース)が作成されるCDKコードとなっています。

Recipe.cs

private void ConfigureAppRunnerService(IRecipeProps<Configuration> props)
{
    if (ServiceAccessRole == null)
        throw new InvalidOperationException($"{nameof(ServiceAccessRole)} has not been set. The {nameof(ConfigureIAMRoles)} method should be called before {nameof(ConfigureAppRunnerService)}");
    if (TaskRole == null)
        throw new InvalidOperationException($"{nameof(TaskRole)} has not been set. The {nameof(ConfigureIAMRoles)} method should be called before {nameof(ConfigureAppRunnerService)}");

    if (string.IsNullOrEmpty(props.ECRRepositoryName))
        throw new InvalidOrMissingConfigurationException("The provided ECR Repository Name is null or empty.");

    var ecrRepository = Repository.FromRepositoryName(this, "ECRRepository", props.ECRRepositoryName);

    Configuration settings = props.Settings;
    var appRunnerServiceProp = new CfnServiceProps
    {
        ServiceName = settings.ServiceName,
        SourceConfiguration = new CfnService.SourceConfigurationProperty
        {
            AuthenticationConfiguration = new CfnService.AuthenticationConfigurationProperty
            {
                AccessRoleArn = ServiceAccessRole.RoleArn
            },
            ImageRepository = new CfnService.ImageRepositoryProperty
            {
                ImageRepositoryType = "ECR",
                ImageIdentifier = ContainerImage.FromEcrRepository(ecrRepository, props.ECRImageTag).ImageName,
                ImageConfiguration = new CfnService.ImageConfigurationProperty
                {
                    Port = settings.Port.ToString(),
                    StartCommand = !string.IsNullOrWhiteSpace(settings.StartCommand) ? settings.StartCommand : null
                }
            }
        }
    };

    if (!string.IsNullOrEmpty(settings.EncryptionKmsKey))
    {
        var encryptionConfig = new CfnService.EncryptionConfigurationProperty();
        appRunnerServiceProp.EncryptionConfiguration = encryptionConfig;

        encryptionConfig.KmsKey = settings.EncryptionKmsKey;
    }

    var healthCheckConfig = new CfnService.HealthCheckConfigurationProperty();
    appRunnerServiceProp.HealthCheckConfiguration = healthCheckConfig;

    healthCheckConfig.HealthyThreshold = settings.HealthCheckHealthyThreshold;
    healthCheckConfig.Interval = settings.HealthCheckInterval;
    healthCheckConfig.Protocol = settings.HealthCheckProtocol;
    healthCheckConfig.Timeout = settings.HealthCheckTimeout;
    healthCheckConfig.UnhealthyThreshold = settings.HealthCheckUnhealthyThreshold;

    if (string.Equals(healthCheckConfig.Protocol, "HTTP"))
    {
        healthCheckConfig.Path = string.IsNullOrEmpty(settings.HealthCheckPath) ? "/" : settings.HealthCheckPath;
    }

    var instanceConfig = new CfnService.InstanceConfigurationProperty();
    appRunnerServiceProp.InstanceConfiguration = instanceConfig;


    instanceConfig.InstanceRoleArn = TaskRole.RoleArn;

    instanceConfig.Cpu = settings.Cpu;
    instanceConfig.Memory = settings.Memory;

    AppRunnerService = new CfnService(this, nameof(AppRunnerService), InvokeCustomizeCDKPropsEvent(nameof(AppRunnerService), this, appRunnerServiceProp));

    var output = new CfnOutput(this, "EndpointURL", new CfnOutputProps
    {
        Value = $"https://{AppRunnerService.AttrServiceUrl}/"
    });
}

ただし、カスタマイズする場合はGeneratedフォルダ以下のレシピコードを修正することは推奨されていません。
AppStack.cs上での修正、あるいは別スタックの作成が推奨されています。

ここでは、VPCリソースをスタックに追加してみたいと思います。

AppStack.cs

internal AppStack(Construct scope, IDeployToolStackProps<Configuration> props)
    : base(scope, props.StackName, props)
{
    _configuration = props.RecipeProps.Settings;

    // Setup callback for generated construct to provide access to customize CDK properties before creating constructs.
    CDKRecipeCustomizer<Recipe>.CustomizeCDKProps += CustomizeCDKProps;

    // Create custom CDK constructs here that might need to be referenced in the CustomizeCDKProps. For example if
    // creating a DynamoDB table construct and then later using the CDK construct reference in CustomizeCDKProps to
    // pass the table name as an environment variable to the container image.

    // Create the recipe defined CDK construct with all of its sub constructs.
    var generatedRecipe = new Recipe(this, props.RecipeProps);

    // Create additional CDK constructs here. The recipe's constructs can be accessed as properties on
    // the generatedRecipe variable.
    var hoge = new Amazon.CDK.AWS.EC2.Vpc(this, "hoge", new Amazon.CDK.AWS.EC2.VpcProps { Cidr = "10.0.0.0/16"});
}

デプロイメントプロジェクトを使ったデプロイ

デプロイ時はcdk synthは行いません。
cdk deployも使いません。

前回使ったdotnet aws deployを使います。

iwasa.takahito@hoge webapp.Deployment % cdk synth
Unhandled exception. AWS.Deploy.Recipes.CDK.Common.InvalidAWSDeployToolSettingsException: Missing CDK context parameter specifying the AWS .NET deployment tool settings file.

なお、明示的なビルドは不要です。
dotnet aws deployコマンドで必要な処理はすべて行ってくれます。

dotnet aws deployコマンドはデプロイ対象アプリケーションのディレクトリで行います。
デプロイメントプロジェクトのディレクトリではないのでご注意ください。

iwasa.takahito@hoge webapp % dotnet aws deploy --profile hoge
AWS .NET deployment tool for deploying .NET Core applications to AWS.
Project Home: https://github.com/aws/aws-dotnet-deploy

Configuring AWS Credentials from Profile hoge.
Configuring AWS region using AWS SDK region search to ap-northeast-1.

Name the AWS stack to deploy your application to
(A stack is a collection of AWS resources that you can manage as a single unit.)
--------------------------------------------------------------------------------
Enter the name of the new stack (default webapp): 

Recommended Deployment Option
-----------------------------
1: Deployment project for webapp to AWS App Runner
This ASP.NET Core application will be built as a container image and deployed to AWS App Runner. If your project does not contain a Dockerfile, it will be automatically generated. Recommended if you want to deploy your application as a container image on a fully managed environment.

Additional Deployment Options
------------------------------
2: ASP.NET Core App to Amazon ECS using Fargate
This ASP.NET Core application will be deployed to Amazon Elastic Container Service (Amazon ECS) with compute power managed by AWS Fargate compute engine. If your project does not contain a Dockerfile, it will be automatically generated. Recommended if you want to deploy your application as a container image on Linux.

3: ASP.NET Core App to AWS App Runner
This ASP.NET Core application will be built as a container image and deployed to AWS App Runner. If your project does not contain a Dockerfile, it will be automatically generated. Recommended if you want to deploy your application as a container image on a fully managed environment.

Choose deployment option (recommended default: 1)

デプロイオプションを問われるので、1: Deployment project for XXX to AWS App Runnerを選択します。
実は、デプロイメントプロジェクトを作成したタイミングで、以下のように関連付けのファイルがアプリケーションプロジェクトに追加されています。
これによってアプリケーションプロジェクトでデプロイ処理を行ってもデプロイメントプロジェクトの存在を認識してくれます。

aws-deployments.json

{
  "DeploymentProjects": [
    {
      "Path": "../webapp.Deployment"
    }
  ]
}

後はデプロイ処理に関しては前回の記事と同じ流れになります。
作成されたCloudFormationリソースだけ確認してみましょう。

期待どおり、VPCおよび配下のネットワークリソースが作成されました。

まとめ

本日は、AWS .NET Deployment Toolのデプロイオプションの構成をカスタマイズする方法をご紹介しました。
レシピファイルを使ったカスタマイズ方法もあるようなのですが、ちょっとうまくいかず今回ご紹介出来ないのでまた改めて記事にできればと思います。

今回の方法を使うと、デプロイコマンド一発でカスタマイズされた.NET CoreアプリとAWSインフラのデプロイを繰り替えることが出来るようになります。
また、ブラックボックスになっているテンプレート構成の詳細を把握するためにも利用出来そうです。

.NET Core + AWS環境で開発している方は是非使ってみてください。