GitLabのCI/CDパイプラインからCloud RunにC#のドキュメントをデプロイする

この記事は、シリーズ記事「[「C#のライブラリプロジェクトをGitLabにpushしたら自動でDLLが書き出されてついでにDocFXで作成されたドキュメントがCloud Run上にデプロイされる」っていうやつをやる](https://qiita.com/soumi/items/c13668052209a35a0965)」の一部です。

# はじめに

GitLabは.gitlab-ci.ymlというファイルを書く事で、データが ``push`` されたタイミングでCIが走ります。
.gitlab-**ci**.ymlという名前ではありますが、もちろんCDを実現する事も可能です。
今回は、以前書いた記事の続きとしてビルドした成果物をデプロイするところまでやってみたいと思います。

**関連記事**
- [C#でドキュメントつきライブラリプロジェクトを作成する](https://qiita.com/soumi/items/ddb81dc02ea445081aa4)
- [GitLabのCI/CDパイプラインでC#のDLLとドキュメントを書き出す](https://qiita.com/soumi/items/ae24cf779ca539b875d6)

# Cloud Run の設定

まずは、デプロイ先を準備する必要があります。
今回は Cloud Run にデプロイするので、ますは https://console.cloud.google.com/ にアクセスして、プロジェクトを作成します。

## プロジェクトを作成

既に対象のプロジェクトがある場合は改めて作る必要はありません。任意の名前のプロジェクトを選択、あるいは作成し、プロジェクトIDを控えておいてください。後で使用します。

グローバルで一意なプロジェクト名を設定した場合はほぼ同じ名前のIDがふられますが、例えば「My Project」等のプロジェクト名だった場合は**ランダムなプロジェクトIDが付与されます。**

プロジェクトのダッシュボードにアクセスした時に、

```
https://console.cloud.google.com/home/dashboard?project=(プロジェクトID)
```

となっているはずですので、分からなければここを確認してください。

## Cloud Build を有効に

``docker`` のビルドに関してはクラウドでやらずにローカルでやってしまっても問題ないのですが、~~面d~~せっかくなので Cloud Build を使ってみようと思います。

まずは API を有効にしておく必要があるので、プロジェクトから有効化しておきます。

<img width="600px" alt="d329273d-3445-c7dd-4b2c-754328f7b82c.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321330/d329273d-3445-c7dd-4b2c-754328f7b82c.png">

## Cloud Run を有効に

同じく、Cloud Run に関しても API を有効にしておく必要があります。

<img width="600px" alt="2ecd2370-174f-56ba-f7c3-98944574531a.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321330/2ecd2370-174f-56ba-f7c3-98944574531a.png">

<img width="600px" alt="9563bde4-f19f-aedf-bb29-43f060c3a322.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321330/9563bde4-f19f-aedf-bb29-43f060c3a322.png">

## サービスアカウントを作成

新しいサービスアカウントを作成し、Cloud Run と Cloud Build を実行するために必要な権限を与えます。

> IAM と管理 > サービスアカウント > サービスアカウントを追加

と辿ると新しいサービスアカウントの作成画面になり、必要な項目を埋めていくと、次は権限の設定画面になります。

<img width="600px" alt="4f30e1a3-1511-d479-1aa1-25e2a5beb1ef.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321330/4f30e1a3-1511-d479-1aa1-25e2a5beb1ef.png">

ここで、僕は試行錯誤の結果以下の項目を追加しました。

- Cloud Build サービス アカウント
- Cloud Run 管理者
- Cloud Run サービス エージェント
- ストレージ管理者
- 閲覧者

なんか権限与えすぎな感じがするのですが、すべてないと動きません…
もし不要な権限に気付いた方いましたら、コメントでご指摘頂けると嬉しいです。

最後に、アカウント情報が含まれた ``json`` をダウンロードします。

<img width="600px" alt="e1b97cba-7b63-f0be-b2b4-9cbfa0159921.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321330/e1b97cba-7b63-f0be-b2b4-9cbfa0159921.png">

この ``json`` が認証情報になります。後で使用しますので、取っておいてください。

# GitLab に CD で使う環境変数をセット

認証情報等の機密情報を、たとえ内部サーバーだったとしてもソースコードに含めるのは抵抗がありますよね。GitLabには、そういった情報を CI/CD 時の環境変数として格納しておける場所があります。

> プロジェクト > Settings > CI/CD > Variables

<img width="600px" alt="02820230-1661-304f-c350-418cbc6fb3a6.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321330/02820230-1661-304f-c350-418cbc6fb3a6.png">

ここに必要な情報を格納しておけば、ソースコードが流出したとしても認証情報は流出しません。
(ソースコードの流出だけでも大きな問題ですけど)

今回は、以下の変数をセットします。

## PROJECT_ID

そのまま、プロジェクトのIDです。
控えておいたプロジェクトIDをそのまま入力してください。

## SERVICE_ACCOUNT

前項で取得したサービスアカウント情報ですが、ファイルをアップロードする機能は無いので、 ``json`` で取得した情報の中身を**全てコピペします。**

# Dockerfile を追加

Cloud Run は ざっくり言うと Docker のコンテナをうまく動かしてくれるサービスなので、なにはともあれ Docker のイメージにする必要があります。

今回は、``ClassLibrary/_site`` にある HTML のファイル群を Nginx コンテナでサーブします。

```Dockerfile
FROM nginx:1.16-alpine
COPY ./_site /usr/share/nginx/html
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
```

## Nginx の設定を変更

ここで一つ注意なのが、Cloud Runは **環境変数 ``Port`` で指定されるポートで動作する必要がある、という点です。** Nginx は当然 80 番ポートで待ち受けているので、これを変更する必要があります。
厳密には環境変数で渡されたポートを使用する必要があるのですが、基本的に渡される値は 8080 で固定されているようです。なので、とりあえず 8080 で待機するように変更します。
(手軽に環境変数からの値を使用できる方法を知っている方いましたら、コメントで教えて頂けると幸いです)

上記 Dockerfile でも参照していますが、 Dockerfileと同じディレクトリに nginx というディレクトリを作成し、その中に default.conf として以下の内容のテキストを配置して下さい。

```nginx/default.conf
server {
    listen       8080;
    server_name  localhost;
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
    # redirect server error pages to the static page /50x.html
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
```

これで、Cloud Run で動作するようになります。

## .gcloudignore を追加

今回使用する Cloud Run は、gitignoreの内容に従ってビルド用のサーバーにアップロードするファイルを決めます。
.gitignore の設定次第なのですが、ビルドされた成果物は基本的に ignore される傾向にあると思っていて、かくいう僕も ignore していました。今回はビルドで生成された _site というディレクトリを使用する事になるので、そのファイルは絶対にアップするように、.gcloudignore というファイルを置きます。

中身は .gitignore と同じような感じになります。

```
!_site
!nginx
!Dockerfile
```

ignore と言いながら今回は除外しない項目しかありませんが、これで必要なファイルがビルドサーバーにアップされるようになりました。

# Runner の設定

[以前の記事](https://qiita.com/soumi/items/ae24cf779ca539b875d6)で設定したものに加えて、今回は ``gcloud`` コマンドを使用するので、Runner 側に Google Cloud SDK をインストールしておく必要があります。

https://cloud.google.com/sdk/docs/

通常であればインストール後に認証設定を行う必要があるのですが、今回はサービスアカウントを使用して認証するので、インストールするだけでOKです。

# .gitlab-ci.ymlを書く

こちらも、[以前の記事で書いたもの](https://qiita.com/soumi/items/ae24cf779ca539b875d6)に CD の分を加筆します。

```.gitlab-ci.yml
variables:
  MSBuild: C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools/MSBuild/Current/Bin/MSBuild.exe
  NuGet: C:/Tools/nuget.exe

stages:
  - build
  - deploy

build:
  stage: build
  tags:
    - C#
    - powershell
  script:
    - chcp 65001
    - Start-Process -FilePath $env:NuGet restore -Wait -NoNewWindow
    - Start-Process -FilePath $env:MSBuild ClassLibrary/ClassLibrary.csproj -Wait -NoNewWindow
  artifacts:
    name: ClassLibrary
    paths:
      - ClassLibrary/bin/Debug
      - ClassLibrary/_site

deploy:
  stage: deploy
  tags:
    - gcloud
    - powershell
  script: 
    - chcp 65001
    - cd ClassLibrary
    - New-Item token.json -Value $env:SERVICE_ACCOUNT -Force
    - gcloud auth activate-service-account --key-file token.json
    - gcloud --project $env:PROJECT_ID builds submit --tag gcr.io/$env:PROJECT_ID/class-library
    - gcloud --project $env:PROJECT_ID run deploy class-library --image gcr.io/$env:PROJECT_ID/class-library --platform managed --region asia-northeast1 --quiet
    - Remove-Item token.json -Force
```

## ポイント

今回加筆した CD 用のジョブ ``deploy`` について、基本的には ``build`` と変わらないのですが、自分的なポイントを挙げておきます。

### ディレクトリを適宜移動

```yml
    - cd ClassLibrary
```

ジョブ実行時、カレントディレクトリはプロジェクトのルートになっています。
今回、Dockerfile は ClassLibrary 以下にあるので、以降のコマンドを簡単にするために移動しておきます。

### 環境変数からの認証情報を使用する

```yml
    - New-Item token.json -Value $env:SERVICE_ACCOUNT -Force
    - gcloud auth activate-service-account --key-file token.json
    - (中略)
    - Remove-Item token.json -Force
```

環境変数に入っている文字列を一度ファイルにして、それを ``gcloud auth activate-service-account`` に食わせています。認証が終わったら、安全のためにファイルを削除しています。

### イメージを作成してデプロイ

```yml
    - gcloud auth activate-service-account --key-file token.json
    - gcloud --project $env:PROJECT_ID builds submit --tag gcr.io/$env:PROJECT_ID/class-library
    - gcloud --project $env:PROJECT_ID run deploy class-library --image gcr.io/$env:PROJECT_ID/class-library --platform managed --region asia-northeast1 --quiet
```

上記の記述でビルドからデプロイまで可能ですが、 ``class-library`` となっている部分は任意で変更できるので、必要に応じて分かりやすいものに変更してください。

# デプロイテスト

ここまで出来たら、 ``push`` する前に一度ローカルでデプロイを試してみます。

環境変数は、設定が面倒なのでコマンドに直接入れていきます。
認証については、先程ダウンロードした ``json`` 形式の認証情報を、ClassLibrary 以下に token.json という名前で配置して下さい。
テストのための措置なので、**間違ってもこのまま ``push`` しないようにご注意を。**

まずはサービスアカウントの認証をして

```
> gcloud auth activate-service-account --key-file token.json

Activated service account credentials for: [xxxxxxxx@xxxxxxxx-262512.iam.gserviceaccount.com]
```

ビルドを実行し

```
> gcloud --project (プロジェクトID) builds submit --tag gcr.io/(プロジェクトID)/class-library

(省略)
xxxxxxxxxx.tgz  gcr.io/xxxxxxxxxxxxxxx-262512/class-library (+1 more)  SUCCESS
```

デプロイします

```
gcloud --project (プロジェクトID) run deploy class-library --image gcr.io/(プロジェクトID)/class-library --platform managed --region asia-northeast1 --quiet

Service [class-library] revision [class-library-00001-vak] has been deployed and is serving 100 percent of traffic at https://class-library-xxxxxxxxxxxx.a.run.app
```

全て問題なく完了したら、コマンドの設定は正しい事になります。
コマンドの最後に、公開URLが表示されていますね。

一点だけ注意があって、Cloud Run はデフォルト設定では認証済みの状態でないと表示ができないようになっており、テストでブラウザから見るのは少々骨が折れる状態になっています。

<img width="600px" alt="896abeda-19d0-87a4-496b-22cef5bb9d68.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321330/896abeda-19d0-87a4-496b-22cef5bb9d68.png">

公開する事に問題が無ければ、画像にもありますが 「allUsers」 というユーザーを作成し、 「Cloud Run 起動元」 という役割を与えてあげればアドレスを知っていれば誰でもアクセスできる状態になります。

# push!

もう何も言う事はありません。
きっと GitLab がよしなにやってくれるでしょう。

# おわりに

クラウドにデプロイをしようと思うと、デプロイ先の設定があったり、認証情報が必要だったりと結構手間が増える印象です。ただ、それらの作業はクラウドを使用する以上は結局は必ず発生するので、一度やっておけば勝手にデプロイしてくれる CD はやはり便利だなと感じました。

引っかかりやすそうなところはあらかじめ僕が引っかかっておいたので、同じ事をやりたいと思っている人が、この記事を見てスムーズに CD を導入できれば幸いです。