AWS App2Container で .NET ウェブアプリケーションをコンテナ化しよう

この記事は、AWS Containers Advent Calendar 2023 の 12 日目 (12/12) のエントリーです。
[Modernize with AWS App2Container Workshop](https://catalog.us-east-1.prod.workshops.aws/workshops/2c1e5f50-0ebe-4c02-a957-8a71ba1e8c89/en-US) を元に、.NET ウェブアプリケーションのコンテナ化の手順を紹介します。

ちなみに本日 12/12 は自分の誕生日でもあります。誕生日おめでとう自分！！

## AWS App2Container とは

Java および .NET ウェブアプリケーションをコンテナ形式に移行およびモダナイズするためのコマンドラインツールです。AWS A2C は、ベアメタル、仮想マシン、Amazon Elastic Compute Cloud (EC2) インスタンス、またはクラウドで実行されているアプリケーションのインベントリを分析して構築します。

なお、App2Container がサポートするアプリケーションは以下に記載されています。
https://docs.aws.amazon.com/app2container/latest/UserGuide/supported-applications.html

この記事では .NET のウェブアプリケーションを App2Container を使ってコンテナ化します。
App2Container を使用して、Windows または Linux サーバー上で実行されている 1 つ以上のアプリケーションから、OCI (Open Container Initiative) 互換のコンテナーイメージを生成できます。App2Containerは、アプリケーションをコンテナ化するためのソースコードを必要としません。
App2Container は以下の4つのタスクを実行します。

* 発見と分析
    * アプリケーションサーバー上で実行されているアプリケーションのインベントリを作成し、インベントリ内の特定のアプリケーションでコンテナ化の候補となる可能性のあるものを分析します。
    * 連携しているプロセスやネットワークポートの依存関係など、実行中のサポート対象アプリケーションの実行時の依存関係を分析します。
* 抽出とコンテナ化
    * コンテナ化のためにアプリケーションアーティファクトを抽出し、Dockerfile を生成します。
    * アプリケーションコンテナをビルドします。
* デプロイするアーティファクトの作成
    * アプリケーションコンテナを AWS にデプロイするために必要なアーティファクトを生成します。
    * AWS CloudFormation テンプレートを構築して、Amazon ECS、AWS App Runner を使用してコンテナをデプロイするために必要なコンピューティング、ネットワーク、およびセキュリティインフラストラクチャを設定します。
    * プロファイル、アプリケーション分析、およびベストプラクティスに基づいて、アーティファクトにキー値を事前に入力します。
* AWS 上にデプロイ
    * Amazon ECS または AWS App Runner にコンテナをデプロイします。

これらのタスクを実行して .NET のウェブアプリケーションをコンテナ化していきます。

※筆者は .NET に詳しくないため基本的な構成からの移行を行っています。
「こういう場合はどのように移行すればよいのか？」などの意見があれば [@anorlondo448](https://twitter.com/Anorlondo448) までご連絡ください！

## 移行元の構成
移行前のウェブアプリケーションは Windows 20212 R2 サーバー上の IIS で実行されています。アプリケーションは、別のサーバーで実行されている SQL サーバーと通信します。
Windows 2012 上で実行されているレガシーな .NET アプリケーションを Windows 2019 のコンテナにします。

![スクリーンショット 2023-12-10 15.11.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94838/dba3c205-de0b-e357-7c3b-6b573221d32e.png)

## 初期設定
App2Container はアプリケーションを実行しているサーバ内で実行することもできますし、別の作業用マシンから実行することもできます。今回は作業用のマシンから App2Container を実行します。

### App2Container のインストール
作業用マシンでブラウザを開き、App2Container をダウンロードします。
https://app2container-release-us-east-1.s3.us-east-1.amazonaws.com/latest/windows/AWSApp2Container-installer-windows.zip

![スクリーンショット 2023-12-09 21.59.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94838/8f92a4c3-1732-27ee-b98a-354794e33783.png)

PowerShell を起動し、ダウンロードした zip を解凍します。
※ Systems Manager Session Manager からも PowerShell が起動できますが、コマンドがエラーになる場合があります。リモートデスクトップクライアントから接続して実行します。

```
PS C:\Users\Administrator\Downloads> Expand-Archive -Path .\AWSApp2Container-installer-windows
PS C:\Users\Administrator\Downloads> cd .\AWSApp2Container-installer-windows
PS C:\Users\Administrator\Downloads\AWSApp2Container-installer-windows> ls


    Directory: C:\Users\Administrator\Downloads\AWSApp2Container-installer-windows


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       11/16/2023  10:04 PM       26234046 AWSApp2Container.zip
-a----       11/16/2023  10:03 PM          23625 install.ps1
-a----       11/16/2023  10:03 PM            221 README.txt
-a----       11/16/2023  10:03 PM            496 termsandconditions.txt
-a----       11/16/2023  10:04 PM          19349 uninstall.ps1
```
App2Container をインストールします。
```
PS C:\Users\Administrator\Downloads\AWSApp2Container-installer-windows> .\install.ps1
Verifying current user permissions...
Checking the current installed A2C version
The AWS App2Container tool is licensed as \ AWS Content\ under the terms and conditions of the AWS Customer Agreement, located at https://aws.amazon.com/agreement and the Service Terms, located at https://aws.amazon.com/service-terms. By installing, using or accessing the AWS App2Container tool, you agree to such terms and conditions. The term \AWS Content\ does not include software and assets distributed under separate license terms (such as code licensed under an open source license).
Do you accept the terms and conditions above? (y/n): y
Installing AWS App2Container ...
Setting up Path ...
Obtained current version: 1.34
Version folder rename succeeded

Setting up powershell auto completion for the CLI...

Installation of AWS App2Container completed successfully!
You are currently running version 1.34.
To get started, run 'app2container init'
AWS App2Container was installed in C:\Users\Administrator\app2container\AWSApp2Container.
```
### アーティファクト保存用の S3 バケットの作成
App2Container がアーティファクトを保存するための S3 バケットを作成します。
※このコマンドは上記の PowerShell ではなく、AWS CLI が実行可能かつ S3 バケット作成に必要な IAM ポリシーが設定された環境で実行します。
```
$ aws s3api create-bucket --bucket app2container-artifact-20231212
{
    "Location": "/app2container-artifact-20231212"
}
```
### リモート接続のための Credential を Secrets Manager に格納する
username / password をキーとした Secretes Manager を作成します。
https://docs.aws.amazon.com/app2container/latest/UserGuide/manage-secrets.html#ra-secrets

### 初期化
init コマンドを使い、セットアップに必要な情報を対話型で入力していきます。
※ここからは PowerShell に戻ります
前の手順で作成した S3 バケット名 (ここでは app2container-artifact-20231212 )を入力します。
```
PS C:\Windows\system32\config\systemprofile> cd C:\Users\Administrator\
PS C:\Users\Administrator\Downloads\AWSApp2Container-installer-windows> app2container init
Workspace directory path for artifacts[default: C:\Users\Administrator\AppData\Local\app2container]:
Use AWS EC2 Instance profile 'arn:aws:iam::xxxxxxxxxxxx:instance-profile/App2Container-EC2InstanceProfile-xxxxxxxxxxxx' configured with this instance? (Y/N)[default: y]:
Which AWS Region to use?[default: us-west-2]:
Optional S3 bucket for application artifacts: app2container-artifact-20231212
Report usage metrics to AWS? (Y/N)[default: y]:
Automatically upload logs and App2Container generated artifacts on crashes and internal errors? (Y/N)[default: y]:
Require images to be signed using Docker Content Trust (DCT)? (Y/N)[default: n]:
Configuration saved
All application artifacts will be created under C:\Users\Administrator\AppData\Local\app2container. Please ensure that the folder permissions are secure.
```
作業用サーバからのリモート接続の設定をします。
https://docs.aws.amazon.com/app2container/latest/UserGuide/start-intro.html#setup-remote-access

ウェブサーバーのローカル IP (ここでは 10.0.0.223) と、前の手順で作成した Secrets Manager の ARN を入力します。
```
PS C:\Users\Administrator> app2container remote configure
Server IP address: 10.0.0.223
Server FQDN (Fully Qualified Domain Name):
Secret ARN for remote connection credentials: arn:aws:secretsmanager:us-west-2:xxxxxxxxxxxx:secret:remotecredentials
Continue to configure another server? (y/N)[default: n]:
Configure successful, you can view hosts config file at: C:\Users\Administrator\AppData\Local\.app2container-config\remote_hosts.conf
```
## 発見と分析
前述した「発見と分析」の「発見」を行い、インベントリを作成します。
ターゲットに分析対象のサーバの IP を指定します。
```
PS C:\Users\Administrator> app2container remote inventory --target 10.0.0.223
√ Server inventory has been stored under C:\Users\Administrator\AppData\Local\app2container\remote\10.0.0.223\inventory.json
Remote inventory retrieved successfully
```
成功すると、アーティファクト用のフォルダに inventory.json が出力されます。
AppData フォルダは非表示になっているので Hidden Item の表示を行うと見れるようになります
![スクリーンショット 2023-12-10 11.05.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94838/153e9b3e-2ff9-df92-c5c2-3ebc13a9b98a.png)
inventory.json にはアプリケーションごとに JSON オブジェクトコレクションが作成されます。
以下の iis-default-web-site-d8bf950cb46d がアプリケーション ID となります。
このアプリケーション ID を次の分析で使用します。
```
{
        "iis-default-web-site-d8bf950cb46d": {
                "siteName": "Default Web Site",
                "bindings": "http/*:8081:",
                "applicationType": "IIS"
        }
}
```
### 分析の実施
アプリケーション ID と分析対象のサーバの IP を指定します。
```
PS C:\Users\Administrator> app2container remote analyze --application-id iis-default-web-site-d8bf950cb46d --target 10.0.0.223
Analysis successful for application iis-default-web-site-d8bf950cb46d

Next Steps:
1. View the application analysis file at C:\Users\Administrator\AppData\Local\app2container\remote\10.0.0.223\iis-default-web-site-d8bf950cb46d\analysis.json.
2. Edit the application analysis file as needed.
3. Start the extraction process using the following command: app2container remote extract --target 10.0.0.223 --application-id iis-default-web-site-d8bf950cb46d
```
アーティファクト用のフォルダにアプリケーション ID のフォルダが作られ、その中に分析結果の analysis ファイルが作成されます。
![スクリーンショット 2023-12-10 11.12.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94838/94a9ae2a-a06c-df11-c68f-5f95d76d28a5.png)
analysis ファイルは後のコンテナ化の手順で利用します。
このファイルには、コンテナ化をする際のベースイメージやタグなどの「EDITABLE:」のセクションと、OSデータ、使用中のポート、依存関係、ソフトウェアライブラリなど、app2container がコンテナ化に使用するアプリケーションレベルの分析情報が含まれる「NON-EDITABLE:」のセクションがあります。
この分析はアプリケーションのドキュメントが十分でない場合に特に重要な情報になります。
```
{
  "a2CTemplateVersion": "1.0",
  "createdTime": "2023-12-10-02:11:24",
  "containerParameters": {
    "_comment": "*** EDITABLE: The below section can be edited according to the application requirements. Please see the Analysis Results section further below for details discovered regarding the application. ***",
    "containerBaseImage": "",
    "imageRepositoryName": "iis-default-web-site-d8bf950cb46d",
    "imageTag": "latest",
    "additionalExposedPorts": [
      
    ],
    "appIncludedFiles": [
      
    ],
    "appExcludedFiles": [
      
    ],
    "enableLogging": false,
    "includedWebApps": [
      
    ],
    "additionalApps": [
      
    ],
    "applicationPort": 8081,
    "enableServerConfigurationUpdates": true
  },
  "analysisInfo": {
    "_comment": "*** NON-EDITABLE: Analysis Results ***",
    "hostInfo": {
      "os": "Microsoft Windows Server 2012 R2 Standard",
      "osVersion": "6.3.9600",
      "osWindowsDirectory": "C:\\Windows",
      "arch": "64-bit"
    },
    "appId": "iis-default-web-site-d8bf950cb46d",
    "appServerIp": "localhost",
    "appType": "IIS",
    "appName": "Default Web Site",
    "ports": [
      {
        "localPort": 8081,
        "protocol": "http"
      }
    ],
    "features": [
      "File-Services",
      "FS-FileServer",
      "Web-Http-Redirect",
      "Web-DAV-Publishing",
      "Web-Custom-Logging",
      "Web-Log-Libraries",
      "Web-ODBC-Logging",
      "Web-Request-Monitor",
      "Web-Http-Tracing",
      "Web-Dyn-Compression",
      "Web-Basic-Auth",
      "Web-CertProvider",
      "Web-Client-Auth",
      "Web-Net-Ext",
      "Web-AppInit",
      "Web-ASP",
      "Web-Asp-Net",
      "Web-CGI",
      "Web-Mgmt-Tools",
      "Web-Mgmt-Console",
      "Web-Mgmt-Compat",
      "Web-Metabase",
      "WDS",
      "WDS-Deployment",
      "WDS-Transport",
      "NET-Framework-Features",
      "NET-Framework-Core",
      "RSAT",
      "RSAT-Feature-Tools",
      "RSAT-Feature-Tools-BitLocker",
      "RSAT-Feature-Tools-BitLocker-RemoteAdminTool",
      "RSAT-Feature-Tools-BitLocker-BdeAducExt",
      "RSAT-Bits-Server",
      "RSAT-Role-Tools",
      "RSAT-AD-Tools",
      "RSAT-ADDS",
      "RSAT-ADDS-Tools",
      "WDS-AdminPack",
      "FS-SMB1",
      "User-Interfaces-Infra",
      "Server-Gui-Mgmt-Infra",
      "Server-Gui-Shell",
      "PowerShell-V2",
      "PowerShell-ISE"
    ],
    "appPoolName": "DefaultAppPool",
    "poolIdentityType": "ApplicationPoolIdentity",
    "dotNetVersion": "v4.0",
    "iisVersion": "IIS 8.5",
    "sitePhysicalPath": "C:\\Unicornshop",
    "discoveredWebApps": [
      
    ],
    "reportPath": "C:\\Users\\Administrator\\AppData\\Local\\app2container\\iis-default-web-site-d8bf950cb46d\\report.txt",
    "siteUsesWindowsAuth": false,
    "serverBackupFile": ""
  }
}
```
## 抽出とコンテナ化
アプリケーションサーバからアーティファクトを抽出し、作業用マシンに保存します。
アプリケーション ID と分析対象のサーバの IP を指定します。
```
PS C:\Users\Administrator> app2container remote extract --target 10.0.0.223 --application-id iis-default-web-site-d8bf950cb46d
Extraction successful for application iis-default-web-site-d8bf950cb46d

Next Step:
1. Please initiate containerization using "app2container containerize --input-archive C:\Users\Administrator\AppData\Local\app2container\remote\10.0.0.223\iis-default-web-site-d8bf950cb46d\iis-default-web-site-d8bf950cb46d.zip"
```
analysis ファイルが出力されたフォルダに、抽出対象のアプリケーションのアーティファクトを含んた zip ファイルが作成されます。
※この以降、アプリケーションサーバへの通信は発生しません。
![スクリーンショット 2023-12-10 11.41.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94838/c6878a95-e904-2e65-75c2-364e53a6c4a7.png)
このアーティファクトには以下が含まれています。
```
└───Artifacts
    └───Unicornshop
        ├───App_Data
        │   ├───cache
        │   ├───Logs
        │   ├───Models
        │   ├───packages
        │   └───TEMP
        │       ├───ClientDependency
        │       ├───DistCache
        │       ├───ExamineIndexes
        │       │   ├───External
        │       │   ├───Internal
        │       │   ├───Members
        │       │   └───UmbracoFormsRecords
        │       ├───NuCache
        │       └───TypesCache
        ├───App_Plugins
        │   └───UmbracoForms
        │       ├───Assets
        │       │   ├───BaremetricsCalendar
        │       │   │   └───public
        │       │   │       ├───css
        │       │   │       └───js
        │       │   ├───Email-Example
        │       │   ├───moment
        │       │   │   └───min
        │       │   ├───pikaday
        │       │   └───themes
        │       │       └───default
        │       ├───Backoffice
        │       │   ├───Common
        │       │   │   ├───FieldTypes
        │       │   │   ├───RenderTypes
        │       │   │   └───SettingTypes
        │       │   ├───Dashboards
        │       │   ├───DataSource
        │       │   │   └───dialogs
        │       │   ├───Form
        │       │   │   ├───dialogs
        │       │   │   ├───overlays
        │       │   │   │   ├───fieldsetsettings
        │       │   │   │   ├───fieldsettings
        │       │   │   │   ├───fieldtypepicker
        │       │   │   │   ├───formpicker
        │       │   │   │   ├───themepicker
        │       │   │   │   └───workflows
        │       │   │   └───views
        │       │   │       ├───design
        │       │   │       └───settings
        │       │   ├───FormSecurity
        │       │   ├───GridEditors
        │       │   ├───PreValueSource
        │       │   └───PropertyEditors
        │       ├───css
        │       ├───Data
        │       │   └───Templates
        │       ├───Directives
        │       ├───Fonts
        │       ├───Images
        │       │   └───workflows
        │       │       └───slack
        │       ├───js
        │       ├───lang
        │       ├───RazorTemplates
        │       └───Xslt
        │           └───Templates
        │               └───Schema2
        ├───bin
        │   ├───amd64
        │   │   └───Microsoft.VC90.CRT
        │   ├───cs
        │   ├───de
        │   ├───es
        │   ├───fr
        │   ├───it
        │   ├───ja
        │   ├───ko
        │   ├───pl
        │   ├───pt-BR
        │   ├───roslyn
        │   ├───ru
        │   ├───tr
        │   ├───x86
        │   │   └───Microsoft.VC90.CRT
        │   ├───zh-Hans
        │   └───zh-Hant
        ├───config
        │   ├───BackOfficeTours
        │   ├───imageprocessor
        │   ├───Lang
        │   └───splashes
        ├───Content
        ├───css
        ├───fonts
        ├───img
        ├───Media
        │   ├───3okigf30
        │   ├───created-packages
        │   ├───fs0pmnxg
        │   ├───gk3n3fkm
        │   ├───lhceugsf
        │   ├───oynkalza
        │   ├───rgcnfbf4
        │   └───syepduhe
        ├───obj
        │   ├───Debug
        │   └───Release
        │       ├───Package
        │       │   └───PackageTmp
        │       │       ├───App_Data
        │       │       └───bin
        │       └───TransformWebConfig
        │           ├───assist
        │           ├───original
        │           └───transformed
        ├───Properties
        │   └───PublishProfiles
        ├───scripts
        ├───umbraco
        │   ├───assets
        │   │   ├───css
        │   │   ├───fonts
        │   │   │   ├───helveticons
        │   │   │   └───lato
        │   │   └───img
        │   │       ├───application
        │   │       ├───applicationIcons
        │   │       ├───forms
        │   │       └───uploader
        │   ├───Config
        │   │   └───Lang
        │   ├───Install
        │   │   └───Views
        │   ├───Js
        │   ├───lib
        │   │   ├───ace-builds
        │   │   │   └───src-min-noconflict
        │   │   │       └───snippets
        │   │   ├───ace-razor-mode
        │   │   │   └───theme
        │   │   ├───angular
        │   │   ├───angular-animate
        │   │   ├───angular-chart.js
        │   │   ├───angular-cookies
        │   │   ├───angular-dynamic-locale
        │   │   ├───angular-i18n
        │   │   ├───angular-local-storage
        │   │   ├───angular-messages
        │   │   ├───angular-mocks
        │   │   ├───angular-route
        │   │   ├───angular-sanitize
        │   │   ├───angular-touch
        │   │   ├───angular-ui-sortable
        │   │   ├───animejs
        │   │   ├───bootstrap
        │   │   │   ├───css
        │   │   │   ├───img
        │   │   │   └───less
        │   │   │       └───tests
        │   │   ├───bootstrap-social
        │   │   ├───chart.js
        │   │   ├───clipboard
        │   │   ├───flatpickr
        │   │   │   └───l10n
        │   │   ├───font-awesome
        │   │   │   ├───css
        │   │   │   └───fonts
        │   │   ├───jquery
        │   │   ├───jquery-ui
        │   │   ├───jquery-ui-touch-punch
        │   │   ├───jsdiff
        │   │   ├───lazyload-js
        │   │   ├───markdown
        │   │   ├───moment
        │   │   ├───ng-file-upload
        │   │   ├───nouislider
        │   │   ├───signalr
        │   │   ├───spectrum
        │   │   ├───tinymce
        │   │   │   ├───langs
        │   │   │   ├───plugins
        │   │   │   │   ├───advlist
        │   │   │   │   ├───anchor
        │   │   │   │   ├───autolink
        │   │   │   │   ├───autoresize
        │   │   │   │   ├───autosave
        │   │   │   │   ├───bbcode
        │   │   │   │   ├───charmap
        │   │   │   │   ├───code
        │   │   │   │   ├───codesample
        │   │   │   │   │   └───css
        │   │   │   │   ├───colorpicker
        │   │   │   │   ├───contextmenu
        │   │   │   │   ├───directionality
        │   │   │   │   ├───emoticons
        │   │   │   │   │   └───img
        │   │   │   │   ├───fullpage
        │   │   │   │   ├───fullscreen
        │   │   │   │   ├───help
        │   │   │   │   │   └───img
        │   │   │   │   ├───hr
        │   │   │   │   ├───image
        │   │   │   │   ├───imagetools
        │   │   │   │   ├───importcss
        │   │   │   │   ├───insertdatetime
        │   │   │   │   ├───legacyoutput
        │   │   │   │   ├───link
        │   │   │   │   ├───lists
        │   │   │   │   ├───media
        │   │   │   │   ├───nonbreaking
        │   │   │   │   ├───noneditable
        │   │   │   │   ├───pagebreak
        │   │   │   │   ├───paste
        │   │   │   │   ├───preview
        │   │   │   │   ├───print
        │   │   │   │   ├───save
        │   │   │   │   ├───searchreplace
        │   │   │   │   ├───spellchecker
        │   │   │   │   ├───tabfocus
        │   │   │   │   ├───table
        │   │   │   │   ├───template
        │   │   │   │   ├───textcolor
        │   │   │   │   ├───textpattern
        │   │   │   │   ├───toc
        │   │   │   │   ├───visualblocks
        │   │   │   │   │   └───css
        │   │   │   │   ├───visualchars
        │   │   │   │   └───wordcount
        │   │   │   ├───skins
        │   │   │   │   └───lightgray
        │   │   │   │       ├───fonts
        │   │   │   │       └───img
        │   │   │   └───themes
        │   │   │       ├───inlite
        │   │   │       ├───mobile
        │   │   │       └───modern
        │   │   ├───typeahead.js
        │   │   ├───umbraco
        │   │   └───underscore
        │   ├───PartialViewMacros
        │   │   └───Templates
        │   └───Views
        │       ├───common
        │       │   ├───drawers
        │       │   │   └───help
        │       │   ├───infiniteeditors
        │       │   │   ├───compositions
        │       │   │   ├───copy
        │       │   │   ├───datatypepicker
        │       │   │   ├───datatypesettings
        │       │   │   ├───embed
        │       │   │   ├───iconpicker
        │       │   │   ├───insertcodesnippet
        │       │   │   ├───insertfield
        │       │   │   ├───itempicker
        │       │   │   ├───linkpicker
        │       │   │   ├───macropicker
        │       │   │   ├───mediapicker
        │       │   │   ├───membergrouppicker
        │       │   │   ├───move
        │       │   │   ├───nodepermissions
        │       │   │   ├───propertysettings
        │       │   │   ├───querybuilder
        │       │   │   ├───rollback
        │       │   │   ├───sectionpicker
        │       │   │   ├───templatesections
        │       │   │   ├───treepicker
        │       │   │   ├───usergrouppicker
        │       │   │   └───userpicker
        │       │   ├───notifications
        │       │   ├───overlays
        │       │   │   ├───default
        │       │   │   ├───itempicker
        │       │   │   ├───keyboardshortcuts
        │       │   │   ├───logviewersearch
        │       │   │   ├───mediatypepicker
        │       │   │   ├───user
        │       │   │   └───ysod
        │       │   └───tours
        │       │       ├───umbintrocreatecontent
        │       │       │   └───nodename
        │       │       ├───umbintrocreatedoctype
        │       │       │   ├───doctypename
        │       │       │   ├───propertyname
        │       │       │   └───tabname
        │       │       ├───umbintromediasection
        │       │       │   ├───foldername
        │       │       │   └───uploadimages
        │       │       └───umbintrorenderintemplate
        │       │           └───templatetree
        │       ├───components
        │       │   ├───application
        │       │   │   ├───umbdrawer
        │       │   │   └───umbtour
        │       │   ├───buttons
        │       │   ├───content
        │       │   ├───editor
        │       │   │   └───subheader
        │       │   ├───grid
        │       │   ├───html
        │       │   │   └───umb-box
        │       │   ├───imaging
        │       │   ├───media
        │       │   ├───member
        │       │   ├───notifications
        │       │   ├───overlays
        │       │   ├───property
        │       │   ├───tabs
        │       │   ├───tags
        │       │   ├───tree
        │       │   │   └───umbcontextdialog
        │       │   ├───upload
        │       │   └───users
        │       ├───content
        │       │   ├───apps
        │       │   │   ├───content
        │       │   │   ├───info
        │       │   │   └───listview
        │       │   └───overlays
        │       ├───contentblueprints
        │       ├───dashboard
        │       │   ├───content
        │       │   │   └───overlays
        │       │   ├───default
        │       │   ├───forms
        │       │   ├───media
        │       │   ├───members
        │       │   └───settings
        │       │       └───overlays
        │       ├───datatypes
        │       ├───dictionary
        │       ├───documenttypes
        │       │   └───views
        │       │       ├───design
        │       │       ├───listview
        │       │       ├───permissions
        │       │       └───templates
        │       ├───install
        │       ├───languages
        │       │   └───overlays
        │       ├───logviewer
        │       ├───macros
        │       │   ├───infiniteeditors
        │       │   └───views
        │       ├───media
        │       │   └───apps
        │       │       ├───content
        │       │       ├───info
        │       │       └───listview
        │       ├───mediatypes
        │       │   └───views
        │       │       ├───design
        │       │       ├───listview
        │       │       └───permissions
        │       ├───member
        │       │   └───listview
        │       ├───membergroups
        │       ├───membertypes
        │       │   └───views
        │       │       └───design
        │       ├───packages
        │       │   ├───overlays
        │       │   └───views
        │       ├───partialviewmacros
        │       ├───partialviews
        │       ├───prevalueeditors
        │       ├───Preview
        │       ├───propertyeditors
        │       │   ├───boolean
        │       │   ├───changepassword
        │       │   ├───checkboxlist
        │       │   ├───colorpicker
        │       │   ├───contentpicker
        │       │   ├───datepicker
        │       │   ├───decimal
        │       │   ├───dropdownFlexible
        │       │   ├───email
        │       │   ├───entitypicker
        │       │   ├───fileupload
        │       │   ├───folderbrowser
        │       │   ├───grid
        │       │   │   ├───config
        │       │   │   ├───dialogs
        │       │   │   └───editors
        │       │   ├───idwithguid
        │       │   ├───imagecropper
        │       │   ├───integer
        │       │   ├───listview
        │       │   │   ├───layouts
        │       │   │   │   ├───grid
        │       │   │   │   └───list
        │       │   │   └───overlays
        │       │   ├───macrocontainer
        │       │   ├───markdowneditor
        │       │   ├───mediapicker
        │       │   ├───membergrouppicker
        │       │   ├───membergroups
        │       │   ├───memberpicker
        │       │   ├───multipletextbox
        │       │   ├───multiurlpicker
        │       │   ├───nestedcontent
        │       │   ├───radiobuttons
        │       │   ├───readonlyvalue
        │       │   ├───relatedlinks
        │       │   ├───rte
        │       │   ├───sensitivevalue
        │       │   ├───slider
        │       │   ├───tags
        │       │   ├───templatepicker
        │       │   ├───textarea
        │       │   ├───textbox
        │       │   ├───urllist
        │       │   └───validationtest
        │       ├───relationtypes
        │       │   └───views
        │       ├───scripts
        │       ├───stylesheets
        │       │   ├───infiniteeditors
        │       │   │   └───richtextrule
        │       │   └───views
        │       │       ├───code
        │       │       └───rules
        │       ├───templates
        │       └───users
        │           └───views
        │               ├───groups
        │               ├───user
        │               └───users
        └───Views
            ├───MacroPartials
            ├───Partials
            │   ├───Comments
            │   ├───Contact
            │   ├───Content
            │   ├───FileUpload
            │   ├───Forms
            │   │   ├───Emails
            │   │   ├───Export
            │   │   ├───Fieldtypes
            │   │   └───Themes
            │   │       ├───bootstrap3-horizontal
            │   │       │   └───Fieldtypes
            │   │       └───default
            │   │           └───Fieldtypes
            │   ├───Grid
            │   │   └───Editors
            │   ├───Members
            │   └───SiteLayout
            └───Shared
```
### アプリケーションのコンテナ化
作業用マシンでアプリケーションをコンテナ化します。
```
PS C:\Users\Administrator> app2container containerize --input-archive C:\Users\Administrator\AppData\Local\app2container\remote\10.0.0.223\iis-default-web-site-d8bf950cb46d\iis-default-web-site-d8bf950cb46d.zip
√ Docker prerequisite check succeeded
√ Dockerfile generated under C:\Users\Administrator\AppData\Local\app2container\iis-default-web-site-d8bf950cb46d\Artifacts
√ Generated dockerfile.update under C:\Users\Administrator\AppData\Local\app2container\iis-default-web-site-d8bf950cb46d\Artifacts
√ Generated deployment file at C:\Users\Administrator\AppData\Local\app2container\iis-default-web-site-d8bf950cb46d\deployment.json
√ Pre-validation succeeded.
Containerization successful. Generated docker image iis-default-web-site-d8bf950cb46d

Please use "docker images" to view the generated container image.
You're all set to test and deploy your container image.

Next Steps:
1. View the container image with "docker images" and test the application with "docker run --name iis-default-web-site-d8bf950cb46d -Pit iis-default-web-site-d8bf950cb46d".
2. When you're ready to deploy to AWS, adjust the appropriate fields in C:\Users\Administrator\AppData\Local\app2container\iis-default-web-site-d8bf950cb46d\deployment.json to generate the desired deployment artifact. Note that by default "createEcsArtifacts" is set to true.
3. Generate deployment artifacts using "app2container generate app-deployment --application-id iis-default-web-site-d8bf950cb46d".
```
Dockerfile や AWS 上にデプロイするための deployment.json が生成されます。
```Dockerfile
# escape= `
FROM mcr.microsoft.com/dotnet/framework/aspnet:4.8-windowsservercore-ltsc2019
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]

RUN Remove-Website 'Default Web Site';

# Set up Website: Default Web Site
RUN New-Item -Path 'C:\Unicornshop' -Type Directory -Force;
RUN if(-NOT (Get-IISAppPool 'DefaultAppPool')) {New-WebAppPool -Name 'DefaultAppPool' }
RUN New-Website -Name 'Default Web Site' -PhysicalPath 'C:\Unicornshop' -Port 8081 -ApplicationPool 'DefaultAppPool' -Force;
COPY ["Unicornshop", "C:/Unicornshop"]
RUN $path='C:\Unicornshop'; `
    $acl = Get-Acl $path; `
    $newOwner = [System.Security.Principal.NTAccount]('BUILTIN\IIS_IUSRS'); `
    $acl.SetOwner($newOwner); `
    dir -r $path | Set-Acl -aclobject $acl

# Windows Authentication was not detected. Please Uncomment the below RUN commands if you want to use Windows Authentication.
# AppPool's Identity is set as Network Service to enable Windows Authentication using GMSA.
# RUN Install-WindowsFeature -Name Web-Windows-Auth -IncludeAllSubFeature
# RUN Import-Module WebAdministration; Set-ItemProperty 'IIS:\AppPools\DefaultAppPool' -name processModel.identityType -value 2
# RUN Import-Module WebAdministration; Set-WebConfigurationProperty -Filter '/system.webServer/security/authentication/anonymousAuthentication' -Name Enabled -Value False -PSPath 'IIS:\' -Location 'Default Web Site'
# RUN Import-Module WebAdministration; Set-WebConfigurationProperty -Filter '/system.webServer/security/authentication/windowsAuthentication' -Name Enabled -Value True -PSPath 'IIS:\' -Location 'Default Web Site'

EXPOSE 8081

# Included files:

# Export additional ports:

# Set up log volumes if needed, for example:
# VOLUME C:\inetpub\logs
# VOLUME C:\yourApp\App_Data\Logs

# The following Windows features are installed on your host, but not in the container.
# You can uncomment the ones you want to have them installed while building docker image.
# RUN Install-WindowsFeature -Name File-Services -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name FS-FileServer -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-Http-Redirect -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-DAV-Publishing -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-Custom-Logging -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-Log-Libraries -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-ODBC-Logging -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-Request-Monitor -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-Http-Tracing -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-Dyn-Compression -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-Basic-Auth -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-CertProvider -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-Client-Auth -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-Net-Ext -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-AppInit -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-ASP -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-Asp-Net -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-CGI -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-Mgmt-Tools -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-Mgmt-Console -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-Mgmt-Compat -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Web-Metabase -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name WDS -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name WDS-Deployment -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name WDS-Transport -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name NET-Framework-Features -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name NET-Framework-Core -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name RSAT -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name RSAT-Feature-Tools -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name RSAT-Feature-Tools-BitLocker -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name RSAT-Feature-Tools-BitLocker-RemoteAdminTool -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name RSAT-Feature-Tools-BitLocker-BdeAducExt -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name RSAT-Bits-Server -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name RSAT-Role-Tools -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name RSAT-AD-Tools -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name RSAT-ADDS -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name RSAT-ADDS-Tools -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name WDS-AdminPack -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name FS-SMB1 -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name User-Interfaces-Infra -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Server-Gui-Mgmt-Infra -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name Server-Gui-Shell -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name PowerShell-V2 -IncludeAllSubFeature
# RUN Install-WindowsFeature -Name PowerShell-ISE -IncludeAllSubFeature

# HTTP Health check on exposed port
HEALTHCHECK --interval=5s --timeout=60s CMD powershell -command`
  try {`
    $response = Invoke-WebRequest http://localhost:8081 -UseBasicParsing;`
    if ($response.StatusCode -le 500) {return 0}`
    else {return 1};`
  } catch {return 1}
```
deployment.json にAWS サービス(ECS/EKS)ごとの構成設定が出力されます。デプロイする環境に合わせて設定を変更します。
```
{
       "a2CTemplateVersion": "1.0",
       "applicationId": "iis-default-web-site-d8bf950cb46d",
       "imageName": "iis-default-web-site-d8bf950cb46d",
       "exposedPorts": [
              {
                     "localPort": 8081,
                     "protocol": "http"
              }
       ],
       "environment": [],
       "ecrParameters": {
              "ecrRepoTag": "latest"
       },
       "ecsParameters": {
              "createEcsArtifacts": true,
              "ecsFamily": "iis-default-web-site-d8bf950cb46d",
              "cpu": 2,
              "memory": 4096,
              "dockerSecurityOption": "",
              "enableCloudwatchLogging": false,
              "publicApp": true,
              "stackName": "a2c-iis-default-web-site-d8bf950cb46d-ECS",
              "resourceTags": [
                     {
                            "key": "example-key",
                            "value": "example-value"
                     }
              ],
              "reuseResources": {
                     "vpcId": "",
                     "reuseExistingA2cStack": {
                            "cfnStackName": "",
                            "microserviceUrlPath": ""
                     },
                     "sshKeyPairName": "",
                     "acmCertificateArn": ""
              },
              "gMSAParameters": {
                     "domainSecretsArn": "",
                     "domainDNSName": "",
                     "domainNetBIOSName": "",
                     "createGMSA": false,
                     "gMSAName": ""
              },
              "deployTarget": "FARGATE",
              "dependentApps": []
       },
       "eksParameters": {
              "createEksArtifacts": false,
              "cpu": 1.5,
              "memory": 3072,
              "stackName": "a2c-iis-default-web-site-d8bf950cb46d-EKS",
              "reuseResources": {
                     "vpcId": "",
                     "reuseExistingA2cStack": {
                            "cfnStackName": ""
                     },
                     "sshKeyPairName": "",
                     "rootDomain": "",
                     "acmCertificateArn": ""
              },
              "gMSAParameters": {
                     "domainSecretsArn": "",
                     "domainDNSName": "",
                     "domainNetBIOSName": "",
                     "createGMSA": false,
                     "gMSAName": ""
              },
              "ingress": "alb",
              "dnsRecordName": "",
              "applicationPath": "/",
              "resourceTags": [
                     {
                            "key": "example-key",
                            "value": "example-value"
                     }
              ],
              "dependentApps": []
       }
}
```

## デプロイするアーティファクトの作成
CloudFormation テンプレートを生成します。
```
PS C:\Users\Administrator> app2container generate app-deployment --application-id iis-default-web-site-d8bf950cb46d
To enable HTTPS, please provide the ARN of an ACM certificate for your DNS record in the AcmCertificateArn field
√ Docker prerequisite check succeeded
√ AWS prerequisite check succeeded
√ Processing application iis-default-web-site-d8bf950cb46d...
√ Created ECR repository xxxxxxxxxxxx.dkr.ecr.us-west-2.amazonaws.com/iis-default-web-site-d8bf950cb46d
√ Pushed docker image xxxxxxxxxxxx.dkr.ecr.us-west-2.amazonaws.com/iis-default-web-site-d8bf950cb46d:latest to ECR repository
√ Local ECS Task Definition file created
√ Uploaded CloudFormation resources to S3 Bucket: app2container-artifact-20231212
√ Generated CloudFormation Master template at: C:\Users\Administrator\AppData\Local\app2container\iis-default-web-site-d8bf950cb46d\EcsDeployment\ecs-master.yml
CloudFormation templates and additional deployment artifacts generated successfully for application iis-default-web-site-d8bf950cb46d

You're all set to use AWS CloudFormation to manage your application stack.

Next Steps:
1. Edit the CloudFormation template as necessary.
2. Create an application stack using the AWS CLI or the AWS Console. AWS CLI command:

        aws cloudformation deploy --template-file C:\Users\Administrator\AppData\Local\app2container\iis-default-web-site-d8bf950cb46d\EcsDeployment\ecs-master.yml --capabilities CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND --stack-name a2c-iis-default-web-site-d8bf950cb46d-ECS


3. Set up a pipeline for your application stack using app2container:

        app2container generate pipeline --application-id iis-default-web-site-d8bf950cb46d
```
ECS へデプロイするための設定や、タスク定義、CI/CD パイプラインを作るための設定が出力されます。
![スクリーンショット 2023-12-10 14.22.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94838/52eeb0dc-eac0-674f-f0c3-efa0957be587.png)
taskdef.json
```
{
   "ContainerDefinitions": [
      {
         "Command": null,
         "Cpu": 2048,
         "CredentialSpecs": null,
         "DependsOn": null,
         "DisableNetworking": null,
         "DnsSearchDomains": null,
         "DnsServers": null,
         "DockerLabels": null,
         "DockerSecurityOptions": null,
         "EntryPoint": null,
         "Environment": null,
         "EnvironmentFiles": null,
         "Essential": null,
         "ExtraHosts": null,
         "FirelensConfiguration": null,
         "HealthCheck": null,
         "Hostname": null,
         "Image": "xxxxxxxxxxxx.dkr.ecr.us-west-2.amazonaws.com/iis-default-web-site-d8bf950cb46d:latest",
         "Interactive": null,
         "Links": null,
         "LinuxParameters": null,
         "LogConfiguration": null,
         "Memory": 4096,
         "MemoryReservation": null,
         "MountPoints": null,
         "Name": "iis-default-web-site-d8bf950cb46d",
         "PortMappings": [
            {
               "AppProtocol": null,
               "ContainerPort": 8081,
               "ContainerPortRange": null,
               "HostPort": 8081,
               "Name": null,
               "Protocol": "http"
            }
         ],
         "Privileged": null,
         "PseudoTerminal": null,
         "ReadonlyRootFilesystem": null,
         "RepositoryCredentials": null,
         "ResourceRequirements": null,
         "Secrets": null,
         "StartTimeout": null,
         "StopTimeout": null,
         "SystemControls": null,
         "Ulimits": null,
         "User": null,
         "VolumesFrom": null,
         "WorkingDirectory": null
      }
   ],
   "Cpu": "2048",
   "EphemeralStorage": null,
   "ExecutionRoleArn": "arn:aws:iam::xxxxxxxxxxxx:role/ecsTaskExecutionRole",
   "Family": "iis-default-web-site-d8bf950cb46d",
   "InferenceAccelerators": null,
   "IpcMode": null,
   "Memory": "4096",
   "NetworkMode": null,
   "PidMode": null,
   "PlacementConstraints": null,
   "ProxyConfiguration": null,
   "RequiresCompatibilities": [
      "EC2"
   ],
   "RuntimePlatform": {
      "CpuArchitecture": "X86_64",
      "OperatingSystemFamily": "WINDOWS_SERVER_2019_CORE"
   },
   "Tags": [
      {
         "Key": "a2c-generated",
         "Value": "ecs-taskdef"
      }
   ],
   "TaskRoleArn": null,
   "Volumes": null
}
```
また同時に Amazon ECR 上にリポジトリが作成され、Build されたコンテナイメージが格納されます。
![スクリーンショット 2023-12-10 14.23.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94838/0ba1e6a3-fe17-eb0f-47b5-9355c8068ab8.png)
## AWS 上へのデプロイ
生成された CloudFormation テンプレートをデプロイします。
```
PS C:\Users\Administrator> aws cloudformation deploy --template-file C:\Users\Administrator\AppData\Local\app2container\iis-default-web-site-d8bf950cb46d\EcsDeployment\ecs-master.yml --capabilities CAPABILITY_NAMED_IAM --stack-name a2c-iis-default-web-site-d8bf950cb46d-ECS

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - a2c-iis-default-web-site-d8bf950cb46d-ECS
```
CloudFormation で、ALBStack、ClusterStack、LBWebAppStack が作られます。

* ALBStack
    * ALB (Application Load Balancer) やリスナー、セキュリティグループが作成されます。
* ClusterStack
    * ECS のクラスターが作成されます。
* LBWebAppStack
    * ECS のタスク定義、タスクロール、タスク実行ロール、ECSサービス、ALB のリスナールール、ターゲットグループ、CloudWatch Logs のロググループ が作成されます。

ECS の画面で ECS サービスが実行されていることが確認できます
![スクリーンショット 2023-12-10 15.03.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94838/22d31b70-d472-c87d-98d4-2c201487df67.png)
ALB の画面から ECS タスクが登録されていることが確認できます。
![スクリーンショット 2023-12-10 15.06.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94838/72778b97-1f44-1b93-831e-8973ed3938ce.png)
また DNS 名の URL にアクセスすると、ECS 上で動作しているアプリケーションにアクセスすることが可能です。
![スクリーンショット 2023-12-10 15.06.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94838/5073dcf7-bbdf-ddbd-0f65-197a638b3da8.png)
## まとめ
今回は App2Container を利用して .NET アプリケーションを ECS で動かしました。しかしただ単にアプリケーションをコンテナ化して動作させただけです。このアプリケーションを継続して運用するには改修し続ける環境が必要です。.NET 環境をそのまま利用し続けることもできますが、最適なアプリケーション言語、フレームワークを利用して、変化に対応し続ける必要があります。このコンテナアプリケーションのリファクタリングを手助けする [AWS Migration Hub Refactor Spaces](https://aws.amazon.com/jp/migration-hub/features/?nc=sn&loc=2#Incremental_app_refactoring) というサービスがありますので次回はこちらのサービスを紹介したいと思います。