AzureのコンテナーインスタンスでDevOpsをやろう

最近、Azureネタが続いています。ちょっと先を見越して、いろいろと実験中です。
今回は、Azureのコンテナーインスタンスを使ってDevOpsをやろうというネタです。

# 環境
こんな環境で実験しています。

* Node.js(NestJS)を使ったAPIサーバーを開発中(コードネームはまだない)
* VisualStudio Codeを使って開発(これで十分)
* Azureのコンテナーインスタンスを使います。
* Azureのコンテナーレジストリを使います。
* Azure DevOpsのRepos、Pipelinesを使います(ここでは紹介しませんが、Boardsも使ってます)

# Azure DevOpsを始める
まずは、Azure DevOpsを無料で始めましょう。無料で **始められる** ことは本当です。

[Azure DevOps](https://azure.microsoft.com/ja-jp/services/devops/)

# プロジェクトを作成
Azure DevOpsにサインインしたら、プロジェクトを作ります。
VisibilityにPublicとPrivateがあります。公開するかどうかってことですが、Public(公開する)にすると、ビルドが同時に動くようになるし、ビルド時間の制約もなくなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/ddbae7fe-8e77-1abe-053f-ebf29921d482.png)

# ソース管理
ソース管理には、Azure DevOpsのReposを使います。GitHubでも全然OKです。
ネイティブな環境の方が得があると思い、GitHubから乗り換えました(結果的には、得はありませんでした)
あと、GitHubからの乗り換えは、インポートで対応可能です。
GitHubってMicrosoftが買収したわけなので、GitHubをもっと推してもいいように思うのですが、なぜ、独自のGitリポジトリサービスを提供するんでしょうね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/42593fa8-0cca-4520-6eaf-2fbc272ad0f5.png)

# Dockerfileの準備
Dockerを使いますので、Dockerイメージをビルドするためのファイル、Dockerfileを作成します。

```file:Dockerfile
# 環境
# サーバー: Node.js,NestJS,GraphQL,TypeORM,SQL Server
# ビルド環境
#   サーバー: webpack+gulp

# 使用するNode.jsのバージョンを設定(ローカル環境に合わせておく)
FROM node:10.15.3

COPY package.json package-lock.json ./

# package.jsonをコンテナにコピーし、必要なパッケージをインストールする
RUN npm --unsafe-perm ci

# PM2をインストールする
RUN npm install pm2 -g

# ソースをコピーする(コピーしないファイルは.dockerignoreで制御)
COPY . .

# 環境変数を設定する
ENV NODE_ENV=production \
    PORT=80

# ポート番号を指定
EXPOSE 80

# ビルドする(gulpでサーバー、DBマイグレーションをビルド)
RUN npm run build

# node.jsアプリケーションを実行する
CMD ["pm2-runtime", "/dist/server/server.js"]
```

# Pipelinesの作成
CI/CDを行うためのPepelinesを作って行きます。

## Buildsの作成
まずは、PipelinesのBuildsを作成します。ここでは、Dockerfileに沿ってDockerイメージをビルドし、コンテナーレジストリにpushすると言うことを行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/b96abe35-d155-a83f-e214-614313a5bab9.png)

### ソースの選択
ソースの在処を指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/975adf8f-9b99-da53-90b4-6912b5ca3df5.png)

### Dockerイメージのビルド
Commandにbuildを指定し、Dockerfileのファイル名を指定します。見えてませんが、tagも指定できます。デフォルトではビルドIDが設定されます。
CIで作成したDockerイメージをCDでリリースするときにタグでフィルタする必要性が出てくることがあります。例えば、ステージング環境用であれば、「staging-$(Build.BuildId)」などとフィルタしやすくしておくことをオススメします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/b7950d6a-c9f3-3446-1088-af79af358905.png)

### コンテナーレジストリへ登録
Commandにpushを指定し、ビルドしたDockerイメージをコンテナーレジストリへ登録します。
Commandに、buildAndPushというものがあります。それだとビルドと登録を行ってくれます。ただ、エラーが起きた時に面倒だなと思って分けてます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/ef0bb35b-89d0-8649-edbb-c5545c70b6f9.png)

### Pull Requestが作成されたらビルドするには？
Pull Requestが作成されたとき、ソースのレビューも行うが、動くシステムでも確認したいと言うことはありませんか？特に、フロントを開発していると、動くものだとすぐに分かるので便利ですよね？
対象のブランチに対して、Pull Requestが作成されたときに、作成したBuildsを実行したい場合は、ブランチに設定が必要です。

対象のブランチの「...」をクリックし、「Branch policies」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/b63f1db1-f15c-2b5d-3367-29f15e504134.png)

Build validationで、「+ Add build policy」をクリックし、Pull Requestが作成された時に実行するBuildsといつ実行するかを指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/46ff5915-538e-e1f4-4393-55e512a628a7.png)

## Releasesの作成
コンテナーレジストリに登録したDockerイメージを使って、コンテナーインスタンスを作成します。
全体像はこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/2bae3127-56c2-9a73-ce7a-9ab75ab6b9e6.png)

### Artifactsの指定
ArtifactsにAzure Cotainer Repositoryを指定します。初期表示されませんので、moreをクリックする必要があります。
Service connection(Azureのサブスクリプションとリソースを紐付けたもの)、Resource Group、Azure Container Registry、Repositoryを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/2a8878f9-e2fb-6c9d-2af4-4c75b9545b3f.png)

### CDトリガーの有効化
Artifactsの稲妻マークをクリックします。Continuous deployment triggerでEnabledにします。
特定のタグのものだけを対象にする場合は、Tag Filterにタグ名を入力しておきます。タグ名には正規化表現が使えます。以下はタグがstaging-で始まると言う例になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/fca1c40a-802a-9cc6-adcc-14e60aaa1fb9.png)

例えば、Pull Requestが作成されたらtsと言うタグを使い確認用のコンテナーインスタンスを更新し、マージされたら(対象ブランチが更新されたら)stと言うタグを使いステージング用のコンテナーインスタンスを更新すると言うようなことができます。

### Deployの設定
残念ですが、2019年4月時点では、コンテナーインスタンスに簡単にデプロイできる仕組みはありません。
どうしても諦められないので、いろいろと考えた結果、Azure CLIを使おうってことになりました。
DeployでAzure CLIを使ってコンテナーインスタンスを更新します。
Script Locationは、Inline scriptで、スクリプトは以下のように指定します。
これだとコンテナーインスタンスがなければ作成し、すでに存在すれば更新してくれます。但し、何かのパラメータ値が変化しないと更新しないようです。** $(Build.BuildId) ** はビルドのたびに変化しますので、これであれば大丈夫です。
すでに存在する場合、os-type、ports、memory、cpuが異なるとエラーになりますので、予め削除しておく必要があります。

```shell:InlineScript
az container create \
--name application-ts \
--resource-group resourcegroup\
--location japaneast \
--image acrname.azurecr.io/application-api:$(Build.BuildId) \
--registry-login-server acrname.azurecr.io \
--registry-username <コンテナーレジストリのユーザ名> \
--registry-password <コンテナーレジストリのパスワード> \
--dns-name-label application-api-ts \
--query ipAddress.fqdn \
--os-type Linux \
--ports 80 \
--memory 1 \
--cpu 1 \
--environment-variables \
DATABASE_DATABASE=$(DATABASE_DATABASE) \
DATABASE_HOST=$(DATABASE_HOST) \
DATABASE_PASSWORD=$(DATABASE_PASSWORD) \
DATABASE_PORT=$(DATABASE_PORT) \
DATABASE_USERNAME=$(DATABASE_USERNAME)
```

### 環境変数の設定
上記のスクリプトで、--environment-variablesを指定しています。これはコンテナーインスタンスに設定する環境変数になります。
例えば、DATABASE_USERNAMEには$(DATABASE_USERNAME)を設定しています。
これは、Releaseの設定の中で、Variablesと言うタブがありますので、その中の、Pipeline valiablesで設定します。
複数のPipelinesで同じものを使う場合は、LibraryのVariable groupsを使うと便利です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/2b2aba18-be75-ecf3-bc38-8dfd99751d8c.png)

# 最後に
かなり苦戦しましたが、チャレンジを達成した瞬間には、「Azure DevOpsやるな！」と感動しました。
このセットを、テスト用、ステージング用、本番用と準備しておけば、自動的にリリースされるようになります(本番用は何をトリガーにするのかという決めごとは必要ですが)

コンテナーインスタンスを使い、この仕組みでデプロイすると、一瞬でデプロイされます。
かなり便利です。