Azure DevOpsの使い方

アプリケーションを開発していると、ローカルで開発、テスト環境でのテスト(ユニットテスト、UIテスト)、ステージング環境での運用テスト、本番環境と環境を分けてやりたくなる事がよくあります(お客様に提供するようなアプリケーションの場合はよくある話)
これを、人手でやるのは大変。
そこで、Azure DevOpsを使って環境構築してみました。

ちなみに、プレビューのAzure Dev Spacesもやってみましたが、こちらは大規模開発向けのような感じだったのと、最後のazds upがどうもうまく動かないので、断念しました･･･。

# 前提
今回取り上げるプロジェクトは、以下の環境が前提です。

* クライアント: Angular
* サーバー: Node.js,NestJS,GraphQL,TypeORM,SQL Server
* ビルド環境
    *   クライアント: ng build
    *   サーバー: webpack+gulp
    *   Docker利用

# アカウントの登録
AzureとAzure DevOpsのアカウントを登録しましょう。
いつか統合されることを願います。
ちなみに、Azureを使いますので、費用は多少かかります。

* Azureは、https://azure.microsoft.com/ja-jp/
* Azure DevOpsは、https://azure.microsoft.com/ja-jp/services/devops/

# Azure コンテナーレジストリの作成
Azureにログインして、すべてのリソースのコンテナーからコンテナーレジストリを使います。
コンテナーレジストリは他にも、Docker Hub、オリジナルのレジストリを使う方法もありますが、Azureを使う事が前提なので、レジストリーもAzureに作ります。

<img width="995" alt="registry.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/3c2654a1-9937-ea09-7e27-3b546ff0226c.png">

SKUは一番安いBasic、管理者ユーザは認証が必要なので有効にします。

![createregistry.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/9e6d6ed9-ff4d-b034-dd5d-09504fa32f36.png)

# Dockerfileの作成
Azure DevOpsのパイプラインを作成すると、Dockerfileを元にしたビルドが実行されます。
ですので、まずDockerfileを作成し、ソース管理(GitHub)にpushしておきましょう。

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

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

# ポート番号を指定
ENV PORT 3000
EXPOSE 3000

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

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

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

# Azure DevOps環境の設定
Azure DevOpsの環境を構築していきましょう。

## 組織の作成
Azure DevOpsにログインするとまずは組織を作成が必要です。
以下の画面が表示されるので、Continueで進みましょう。

![startorg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/a128793b-f25d-c849-0cfb-ab676ae0a0db.png)

## 組織名とリージョンの指定
組織名とリージョンを指定します。日本から近いであろうEast Asiaを選択します(あまり拘らなくてもいいと思いますが)

![nameorg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/f294ec40-0939-d30b-1f6f-8ccfd4cf8a94.png)

## プロジェクトの作成
次にDevOpsしたいプロジェクトを作成します。

![project.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/8282fecb-26b0-ce7c-b270-a78134fa9e4f.png)

作成できました。

![welcome.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/71517aaa-9f70-e8cf-b3a5-990819341e86.png)

# ビルドパイプラインの作成
左のメニューから、Pipelinesを選択します。
次に右の画面の、New PipelineをクリックしてPipelineを作成します。

![newpipeline.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/f4d480e1-5bca-31ec-d2a9-1100ec4e473f.png)

どこにあるコードと接続するかを聞いてきます。今回はGitHubを使いますので、GitHubを選択します(ご自分の環境に合わせて選択してください)

<img width="445" alt="connect.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/6b371651-0feb-319c-3fbe-f172ed95686d.png">

GitHubにあるリポジトリが表示されますので、使うリポジトリを選択します。

![selrepo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/981f0df7-b1c8-a736-db87-3dcc65f5e571.png)

作成するPipelineのタイプを選択します。前提条件にあるとおり、ここではDockerを利用しますので、「Docker image」を選択します。

<img width="455" alt="confpipeline.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/3d4f429f-ece5-2c24-8f0c-5846aac8e04f.png">

azure-pipelines.ymlのひな形が作成されます。
imageNameを作成したイメージ名(普通はプロジェクト名と同じでOK)を指定します。

![yml.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/38f25a3f-5aef-2867-5a3c-8a2cb1abc26f.png)

「Save and Run」をクリックします。
パイプラインの名称を入力します。

事前に準備しておいたDockerfileの定義に沿ってビルドが実行されます。

<img width="586" alt="build.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/8f2419d1-907a-9081-de53-2d5453ee346a.png">

ここでは、ビルドが成功しても失敗しても構いません。次に進みましょう。

# Dokerイメージを作成して、レジストリにpush
ここでは、Dockerイメージを作成し、Azureコンテナーレジストリにpushすると言うことを行います。
大まかな手順は以下の通りです。

* Dockerイメージをビルドする
* DockerイメージをAzureコンテナーレジストリに登録する

## Azureコンテナーレジストリの認証情報の取得
Azureコンテナーレジストリは、認証を行うように設定しましたので、pushする際にユーザ名とパスワードが必要になります。
Dockerイメージをpushするために、認証に必要な情報を取得します。Azureに作成したAzureコンテナーレジストリを開き、アクセスキーを開いてください。

![accesskey.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/bd96f9fd-ab7d-f090-6d87-cb8fa2043b60.png)

以下の情報をメモします。

*ログインサーバー
* ユーザ名
* Password

## 環境変数の設定
上で取得した情報をdocker loginのパラメータに渡す必要があります(-uと-pオプションを使います)
これを、azure-pipelines.ymlに書いて行くのですが、ここで注意が必要です。
azure-pipelines.ymlは、GitHub経由でAzure DevOpsに渡されます。docker loginに渡すユーザID、パスワード、レジストリ名は、重要な情報なので、azure-pipelines.ymlに書くべきではありません。
そこで、パイプラインの実行に必要な情報を、Azure DevOpsに定義します。
作成したパイプラインを開くと、Libraryというメニューがあります。
そこから、Variable groupを作成し、変数を定義します。設定したら、Saveで保存します。

<img width="400" alt="val.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/bd202f70-d62b-a0e2-7536-97cffaac77d7.png">

各列に鍵マークが付いているので、クリックして見えないようにしておきましょう(重要)

## azure-pipelines.ymlの編集
azure-pipelines.ymlは以下のように編集します。

* Azure DevOpsのLibraryでVariable groupを作成します(パスワードなどのシークレット情報を管理)
    * 下で<variable group>となっているところがそれです(具体的にはdokerPasswordだけをVariable groupで管理しています)
* Dockerレジストリには、Azureコンテナーレジストリを利用
    * Azureコンテナーレジストリは管理ユーザーが有効
    * $(dockerId)は、Azureコンテナーレジストリのユーザ名


```yaml:azure-pipelines.yml
# Docker image
# Build a Docker image to deploy, run, or push to a container registry.
# Add steps that use Docker Compose, tag images, push to a registry, run an image, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/docker

trigger:
- develop

pool:
  vmImage: 'Ubuntu-16.04'

variables:
- group: <variable group>
- name: dockerId
  value: <dockerid>
- name: imageName
  value: <imagename>:$(build.buildid)
- name: dockerUrl
  value: $(dockerId).azurecr.io

steps:
- script: |
    docker build -t $(dockerUrl)/$(imageName) .
    docker login -u $(dockerId) -p $(dockerPassword) $(dockerUrl)
    docker push $(dockerUrl)/$(imageName)
  displayName: Build Docker image
```

## Buildの実行
ここでビルドして、Dockerイメージが正常に作成され、Azureコンテナーレジストリに登録されることを確認しましょう。
Dockerイメージの作成に慣れた人なら問題無くクリアできるのかも知れませんが、私はすごく苦労しました。
つまづきポイントは以下の通りです(参考になれば･･･)

* まずpackage.jsonとpackege-lock.jsonだけをコピーして、npm ciでインストールする
* インストールの過程でnodeなんかを実行する必要がある場合(例えば、bcryptを使っている場合)、npm --unsafe-perm ciとする必要がある
* azure-pipelines.ymlの書き方を調べて見ると、いろんなバリエーションがあり(情報が古いのか、動作しない情報も多い)、正解を見つけるのが大変。

## 作成されたDockerイメージの作成
Azureコンテナーレジストリで、サービスのリポジトリを確認して、Dockerイメージが作成されているのを確認しましょう。

![repo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334727/0fd66122-d016-bc4f-2d4d-03e8536868b2.png)
 
思いのほか、大変です。