AZ-204対策 ソリューションに対するコンテナイメージの作成

#初めに
先日AZ-204に合格したのですが、試験を受けてみて初めて、各サービスを何となくしか理解出来ていないことに気付きました。

「これは受かったとは言えないぞ？」と反省の気持ちでいっぱいですので、これから自分の為に復習していこうと思っています。

#本記事で取り扱うこと

AZ-204ではコンテナの知識が問われます。
今回は__「ソリューションに対するコンテナイメージの作成」__対策として、MSの公式ドキュメントのチュートリアルをやりながら、ACRに格納したイメージからAzure App Service(以下、ACR)を使ってWebアプリをデプロイしてみます。

#ポイント
* ACRの仕様を理解する
* ACRのイメージからAzure App Serviceを使ってWebアプリをデプロイする
* 構築手順、構築のためのAzure CLIのコマンドを理解する
* dockerの基本的なコマンド、Dockerfileの記載のお作法を理解する

#参考になる公式サイト
##MSドキュメント
[Azure App Service を使ってコンテナー化された Web アプリをデプロイして実行する](https://docs.microsoft.com/ja-jp/azure/app-service/tutorial-custom-container?pivots=container-linux)

##MS Learn
[Azure App Service を使ってコンテナー化された Web アプリをデプロイして実行する](https://docs.microsoft.com/ja-jp/learn/modules/deploy-run-container-app-service/)

#知識
## Azure Container Registryとは
AzureのプライベートなDocker Registryサービスです。

###3つのSKU
Basic、Standard、Premiumの3つのサービスレベルから利用できます。

|SKU|説明|料金|
|:---|:---|:---|
|Basic|学習用途、PoC等使用頻度が少ないシナリオの場合に利用します。<br>__Azure AD認証__、__Webhook統合__等のStandard・Premiumで利用できる機能も試すことが出来ます。<br>ダウンロード帯域やRead/WriteOpsなどのパフォーマンスは制限されています。|¥18.660/日|
|Standard|利用できる機能の種類はBasicと同数ですが、__Basicよりもパフォーマンスに優れています__。|¥74.660/日|
|Premium|Basic、Standardで利用できる機能に加え、__geoレプリケーション__、__PrivateLink__、__カスタマーマネージドキーによる暗号化__、__Docker Content Trust__等の機能を利用できます。|¥186.660/日|

#実装
##作業環境に関して
本作業ではOSに__Windows__を利用し、取得済みの__Azure Subscription__を利用します。
__無料Subscriptionは利用していない__ので、無料アカウントでコマンドを実行しようとした際に齟齬が出た場合は大変申し訳ありません。
作業前に、必要なミドルウェアをインストールしてください。

* Azure CLI (2.22.1) -> まだの場合は[CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-windows?WT.mc_id=thomasmaurer-blog-thmaure&tabs=azure-cli)をインストール
* Git（2.31.1.windows.1） -> まだの場合は[Git](https://git-scm.com/book/ja/v2/%E4%BD%BF%E3%81%84%E5%A7%8B%E3%82%81%E3%82%8B-Git%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)をインストール
* Docker -> まだの場合は[Docker(Windows版)](https://docs.docker.com/docker-for-windows/install/)をインストール

##イメージの作成
まずMicrosoftが提供するサンプルアプリを利用してACRにDockerイメージを作成します。
### サンプルアプリをクローン
私はC:\Dev\az-204\1-4_ACR_imageというフォルダを作成してクローンしています。

__コマンド__

```
git clone https://github.com/Azure-Samples/docker-django-webapp-linux.git --config core.autocrlf=input
```

__実行結果__
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/92f6697b-aa76-1c92-7110-51ed4e93b89c.png)


### Dockerfileの確認

```dockerfile
#ベースのイメージを記載。必ず先頭に記述
#FROM image名:tag名
FROM tiangolo/uwsgi-nginx-flask:python3.6

#シェルコマンドの実行。image内にcodeディレクトリを作成
RUN mkdir /code

#後続のコマンドの作業ディレクトリを指定
WORKDIR /code

#ローカルのrequirements.txtをコンテナ内のcodeディレクトリに移動
ADD requirements.txt /code/

# requirements.txtにリストされたパッケージをインストールする
RUN pip install -r requirements.txt --no-cache-dir

#クローンしたソースをまとめてコンテナ内に作成したcodeディレクトリにコピーする
ADD . /code/

#Key:SSH_PASSWD, Value:"root:Docker!"の環境変数を宣言
ENV SSH_PASSWD "root:Docker!"

# openssh-serverをインストールする
RUN apt-get update \
        && apt-get install -y --no-install-recommends dialog \
        && apt-get update \
	&& apt-get install -y --no-install-recommends openssh-server \
	&& echo "$SSH_PASSWD" | chpasswd 

#ローカルにクローンしたファイル（sshd_config, init.sh）を指定したイメージのパス配下にコピーする
COPY sshd_config /etc/ssh/
COPY init.sh /usr/local/bin/
	
#コンテナ内でinit.shを実行
RUN chmod u+x /usr/local/bin/init.sh
EXPOSE 8000 2222

#CMD ["python", "/code/manage.py", "runserver", "0.0.0.0:8000"]
#init.shを呼び出してSSHサービスとPythonサーバを起動
ENTRYPOINT ["init.sh"]
```

### イメージをローカルで作成してテストする

__イメージ作成のコマンド__
dockerイメージを作成します。タグを指定していないのでlatestタグが作成されます。

```bash
docker build --tag appsvc-tutorial-custom-image .
```

ローカルにイメージが出来たことを確認します。

```bash
docker images | grep app*
```

![2_docker_ps.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/48e4bfbc-4468-ac55-6d64-00c68c043fad.png)

### ローカルでコンテナを実行
```bash
docker run -p 8000:8000 appsvc-tutorial-custom-image
```

OpenSSHサーバが起動したことを確認します。
![3_docker_build.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/938ff2ac-dde6-38a8-441d-698f9aa5cf16.png)

__ブラウザで確認__
↓↓URL
http://localhost:8000/

![4_brawser.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/dc6f6df3-a580-32cb-ea96-fc3bf3ce8df7.png)

## テナントにログイン
ログインコマンド後、ブラウザに認証画面が表示されるのでユーザ・パスワードを入力します。


```bash
az login
```


認証完了後、ローカルでAzure CLIを実行できる状態になります。
![5_az_login.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/169f84d3-80b1-5afb-5025-6222c40b4e7c.png)


## リソースグループを作成する
__AppSvc-DockerTutorial-rg__という名前のリソースグループを作成します。

__コマンド__

```bash
az group create --name AppSvc-DockerTutorial-rg --location japaneast
```

__実行結果__
![6_rp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/6dffe02d-f066-005d-38bd-de6c2cfd3f91.png)

__AppSvc-DockerTutorial-rg__が作成されました。
![6_rp_screen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/94ad7521-0960-ecd3-8ad5-95916868f88f.png)


## Azure Container Registryにイメージをプッシュする
###Azure Container Registryを作成

__コマンド__
コンテナを使ってAzure Web Appにデプロイするため、__admin-enabled__オプションをtrueにします。

```bash
az acr create --name acrkumamatsu --resource-group AppSvc-DockerTutorial-rg \
 --sku Basic --admin-enabled true
```

__実行結果__
Azure PortalでRegistryが作成されたことを確認します。
ログインサーバ名(registry-name.azurecr.io)を確認しておきます。
![7_acr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/5fee1dd9-42e2-6485-c89b-99a2aa34298f.png)


###Registryの資格情報を取得
コンテナにログインするための認証情報を確認します。

__コマンド__

```bash
az acr credential show --resource-group AppSvc-DockerTutorial-rg --name acrkumamatsu
```

__実行結果__
※acrは記事の投稿時には削除します。
![8_password.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/296e6758-626e-1ea3-4086-84385a6c8b0a.png)

__Azure Portalから確認__
Azure Portalからも確認できます。 
管理者ユーザも有効になっていますね。
![9_password_acr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/4f0ed96d-36a8-fb9d-d9b1-5f2b4f72cccd.png)


###Container Registryにサインイン

__コマンド__
コンテナサーバにサインインします。
パスワードの入力が求められますので、先ほど取得したパスワードを入力してEnterを押します。

```bash
docker login acrkumamatsu.azurecr.io --username acrkumamatsu
```
__実行結果__
![10_container_login.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/b3c3c830-2762-5541-a5c0-04182681ef9b.png)

###Dockerイメージにタグ(バージョン)を付与

__コマンド__

```powershell
docker tag appsvc-tutorial-custom-image acrkumamatsu.azurecr.io/appsvc-tutorial-custom-image:latest
```

###Registryにイメージをプッシュ


```bash
docker push acrkumamatsu.azurecr.io/appsvc-tutorial-custom-image:latest
```
![12_acr_push.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/241d9ad8-b22e-e048-85ae-c2980ad134f1.png)

##プッシュしたイメージの確認

latest: digest:…と表示されたらlistコマンドを実行して確認してみます。

```bash
az acr repository list -n acrkumamatsu
```

![13_acr_list.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/199a4a0c-f2e4-ac5f-3fbc-efcfc1e85c65.png)


Portalで確認もできます。
リポジトリにイメージがありますね。
![13_repo_image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/d14c1122-542e-388e-3ffd-036b3d76b764.png)

タグが付与されています。
![13_repo_Tag.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/9b18552e-818e-c11b-a026-9b1861e08016.png)

## RegistryからイメージをデプロイするようにApp Serviceを構成
###App Serviceプランを作成
Web Appsの構築・運用の方法もAZ-204に合格するためには理解しておかなくてはなりません。
Azure CLIやPowerShellでの構築方法、各Azureサービスの構築順序を理解しておきましょう。

まずは__App Serviceプランを作成__します。
プランとは、Webアプリをホストする為のハードウェアに採用するスペックのことです。

Linuxベースのプランを使用するので__-is-linux__オプションが設定されています。

```bash
az appservice plan create --resource-group AppSvc-DockerTutorial-rg \
 --name AppSvc-DockerTutorial-plan --is-linux
```

![14_result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/2d54dc82-50d4-7263-f9a3-d11950296070.png)

Portalで確認することもできます。
![14_result_screen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/bcf8d64e-2e1e-9386-1e34-baae90e9f622.png)



###Web アプリを作成
作成したApp Serviceプラン上で__Web アプリ__を作成します。


```bash
az webapp create --resource-group AppSvc-DockerTutorial-rg `
 --plan AppSvc-DockerTutorial-plan --name kumamatsuapp `
 --deployment-container-image-name acrkumamatsu.azurecr.io/appsvc-tutorial-custom-image:latest
```

![15_app.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/4db0b6b9-2a87-08af-dcc2-55b7bb30df39.png)

###WEBSITES_PORT環境変数を設定
__WEBSITES_PORT__とはWEBサイトを公開する際にコンテナ側のポートとして利用する値を設定する項目です。
チュートリアル通り、8000を利用します。


```bash
az webapp config appsettings set --resource-group AppSvc-DockerTutorial-rg \
 --name kumamatsuapp --settings WEBSITES_PORT=8000
```
結果をPortalで確認しましょう。
コンテナの引数として、アプリケーション設定に新たな項目が追加されました。

![16_app_setting.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/76013d0c-8750-8cda-ff8d-6ecb782e24f2.png)


###Web アプリのマネージドIDを有効

Webアプリで__マネージドID__を有効化します。

az webapp identity assignコマンドを利用します。
__--query__オプションを使っていますが、これはjsonで出力されるcliの結果から指定したKey項目に紐づくValueを出力したい際に設定します。
有効化した__マネージドID__は後工程で利用します。

```bash
principalId=`az webapp identity assign --resource-group AppSvc-DockerTutorial-rg --name kumamatsuapp \
 --query principalId --output tsv`
```
このコマンドを実行するとAzure Portalの[App Service]>[ID]の状態がオンになります。

![17_managedid_screen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/a1683373-edb8-2da2-21ee-33ca7d8c9a91.png)

###サブスクリプション ID を取得

```bash
subscription_id=`az account show --query id --output tsv`
```

###Container Registryへのアクセス許可をWebアプリに与える

```bash
az role assignment create --assignee $principalId \
 --scope /subscriptions/$subscription_id/resourceGroups/AppSvc-DockerTutorial-rg/providers/Microsoft.ContainerRegistry/registries/acrkumamatsu \
 --role "AcrPull"
```
何やらエラーが発生します。
![18_error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/f1e2e784-9742-71aa-9417-e1f5c51c78cb.png)

以下のサイトによると、GitをインストールしているWindows OSでAzure CLIを利用する際に出るようです。
[Unable to create service principal with azure cli from git bash shell, no connection adapters were found.
](https://github.com/Azure/azure-cli/issues/16317)

もう一度同じコマンドを実行しますが、先頭に「MSYS_NO_PATHCONV=1 」を付けます。

```bash
MSYS_NO_PATHCONV=1 az role assignment create --assignee $principalId \
 --scope /subscriptions/$subscription_id/resourceGroups/AppSvc-DockerTutorial-rg/providers/Microsoft.ContainerRegistry/registries/acrkumamatsu \
 --role "AcrPull"
```
WebアプリのマネージドIDにacrkumamatsu Registryに対するロールを設定します。

![19_avoid_error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/d17ecc20-8ef6-3557-7bb4-daf9a3188a5b.png)

Azure Portalで設定を見てみましょう。
Container Registryのアクセス制御(IAM)に先ほど設定した__AcrPull__というロールが表示されています。
このロールを利用できるのはApp Service __kumamatsuapp__です。
![20_IAM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/df1023af-a82e-f876-51e7-45fafb66289f.png)

マネージドIDを利用した認証のイメージです。

![managed_id.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/c195c933-87a7-d363-5dbc-dace68ee609a.jpeg)

##イメージをデプロイしてアプリをテスト
###Container RegistryとWebアプリ用にデプロイするイメージを指定

```bash
az webapp config container set --name kumamatsuapp \
 --resource-group AppSvc-DockerTutorial-rg \
 --docker-custom-image-name acrkumamatsu.azurecr.io/appsvc-tutorial-custom-image:latest \
 --docker-registry-server-url https://acrkumamatsu.azurecr.io
```

コマンドを実行したら、「No credential was provided to access Azure Container Registry. Trying to look up... 」というWarningが出力されました。

これはRegistryに対する認証にユーザ名・パスワードではなくアプリのマネージドIDを使用していることを表しています。

![21_deploy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/d0fc1c00-7ae0-208d-09f6-79b0d00a3460.png)

App ServiceのDeploy Centerを見てみましょう。
Web AppがAzure Container Registryの特定のイメージ・タグと紐づいていることがわかります。
![22_deploycenter.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/d3986185-369f-7a66-e8a6-4e0cbf7680c1.png)


###URLにアクセス

ブラウザからデプロイが完了したかどうかを確認してみましょう。
↓↓↓URL
https://kumamatsuapp.azurewebsites.net/

##アプリのコードを変更して再デプロイ
###index.htmlの変更

ではソースコードを変更して再デプロイしてみます。
docker-django-webapp-linux フォルダーにあるファイルapp/templates/app/index.htmlの中身を変更します。
![24_navbar-brand.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/f2a3048c-9a21-e356-9486-78faf4963ab3.png)

###イメージをリビルド
docker-django-webapp-linux フォルダーに移動してイメージ(v1.0.1)をリビルドします。

```bash
docker build -t appsvc-tutorial-custom-image:v1.0.1 .
```

###イメージをRegistryにプッシュ

```bash
docker push acrkumamatsu.azurecr.io/appsvc-tutorial-custom-image:v1.0.1
```

Azure Portalで見てみましょう。
v1.0.1タグでイメージがプッシュされたことがわかります。

![25_version.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/8c4fc047-9f2e-515f-a694-356de2c3829c.png)

###Container RegistryとWebアプリ用にデプロイするイメージを指定

```bash
az webapp config container set --name kumamatsuapp \
 --resource-group AppSvc-DockerTutorial-rg \
 --docker-custom-image-name acrkumamatsu.azurecr.io/appsvc-tutorial-custom-image:v1.0.1 \
 --docker-registry-server-url https://acrkumamatsu.azurecr.io
```

PortalのDeploy Centerを見てましょう。タグが置き換わっています。

![26_deploy_center.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/5124b142-6661-2274-9d98-1ce0ab59ce29.png)

###Webアプリを再起動

再起動コマンドを実行し、Webアプリを再起動します。

```bash
az webapp restart --name kumamatsuapp --resource-group AppSvc-DockerTutorial-rg
```

ブラウザから確認します。
修正が反映されていることが確認できました。

![27_updated_here.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307047/90c57aaa-b9dc-ca9c-1ad6-59ad0d30a09b.png)



#参考
以下のサイト・動画を参考にさせていただきました。ありがとうございました。

##Webサイト
[DockerfileのADDとCOPY、CMDとENTRYPOINTの違いを整理](https://hisa-web.net/archives/923)
[3つの観点から見たコンテナの構造とは？](https://news.mynavi.jp/article/zerocontena-6/)
[WindowsでDockerを始める手順](https://qiita.com/techpit-jp/items/f4a1319a909dd508f372)
[【Microsoft Azure】DockerコンテナをACR経由でAppServiceにデプロイする](https://www.simpletraveler.jp/2020/09/02/microsoftazure-deploy-dockerimage-to-appservice-via-acr/)
[Content trust in Docker](https://docs.docker.com/engine/security/trust/)
##本
[ひと目でわかるAzure Active Directory 第3版](https://www.amazon.co.jp/dp/B08PXZ5TZN/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)