GCP Compute Engine (だけ) でVMインスタンスの自動起動、停止

[GCPのCompute Engine (GCE)](https://cloud.google.com/compute) は時間単位で仮想マシンを利用できるサービスです。AWSでいうところのEC2に該当するものです。料金は時間単位で課金されるため、必要でないときはできるだけ停止しておく方が安上がりです。そのため、自動で起動したり停止したりする設定を施したくなります。

GCEインスタンスの起動・停止をコントロールできるサービスはいくつかあります。たとえば [Cloud Scheduler](https://cloud.google.com/scheduler)や[App Engine](https://cloud.google.com/appengine)などのサービスと連携させる方法がよく使われます。
ですが、単純な起動・停止のルールであれば、GCEの機能（+IAMによる権限設定）だけで設定することが可能です。その手順を紹介します。

参考：

- [VM の停止と起動](https://cloud.google.com/compute/docs/instances/stop-start-instance)
- [VM インスタンスの起動と停止をスケジュールする](https://cloud.google.com/compute/docs/instances/schedule-instance-start-stop)

## 手順

### 準備

1. GCEインスタンスを実行するサービスアカウントの作成
1. GCEインスタンスの作成
1. GCEインスタンスの起動・停止を行うロールの作成

### ケース１：スケジューラーによる起動と停止

- 実験用のスクリプトの作成
- GCEのインスタンススケジューラーの作成
- インスタンスを起動・停止する権限をインスタンススケジューラーを実行するサービスアカウントに付与
- 実験結果
 
### ケース２：インスタンス内からの停止

- インスタンスの起動・停止権限をインスタンスを実行するサービスアカウントに付与
- 実験
   

## 準備

### GCEインスタンスを実行するサービスアカウントの作成

コンソール上でGCPにログインし、 [IAM & Admin > Service Account](https://console.cloud.google.com/iam-admin/serviceaccounts) から、サービスアカウントの管理ページを開きます。
そこで、ページの上の方にある「+ CREATE SERVICE ACCOUNT」をクリックします。

- Service account nameに任意の名前を付けます（ここでは `gce-worker` としました）。
- 現時点では特に権限を付与せずに「DONE」をクリックして完了します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80588/d188139b-7a7e-f220-041d-f0a302c4ed38.png" width="650">


### GCEインスタンスの作成

[Compute Engine](https://console.cloud.google.com/compute/instances) から、VMの管理ページを開きます。
ページの上の方にある「+ CREATE INSTANCE」をクリックします。

- "Name"には任意の名前を付けます（ここでは`my-test-vm`）。
- "Region"もどこでも良いと思いますが、`us-central1`にしました（実験であれば[E2-microが月に１インスタンスなら無料](https://cloud.google.com/free/docs/gcp-free-tier/#compute)のところがお薦めです）。
- "Machine type"は最小の`e2-micro`で良いと思います。
- "Boot disk" (OS) も任意ですが、使い慣れたUbuntuを選択肢ました。
- 最後に、"Service account"には、上で作成したサービスアカウントを指定します。
- 「Create」をクリックして完了します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80588/c01a8d76-f5cb-f0b3-5e16-6827ed9d64b5.png" width=450">


### GCEインスタンスの起動・停止を行うロールの作成

[IAM & Admin > Roles](https://console.cloud.google.com/iam-admin/roles)から、ロールの管理ページを開きます。
GCPにおけるロールは、権限(Permission)の集合のようなものです。デフォルトで数多くのロールが定義されていますが、ぴったり合うものがない場合には自分でカスタムロールを定義して使います。
上の方の「+ CREATE ROLE」をクリックします。

- "Title"には任意の名称を指定します（ここでは`gce-start-stop-role`）。
- "ID"には任意の名前を指定します（ここでは`gceStartStopRole`）。
- "Role launch stage"は、情報提供が目的のフィールドなので、チーム開発での運用ルールなどがない限り`Alpha`のままで良いと思います。
- 「+ ADD PERMISSIONS」をクリックします。
 
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80588/4b86e73f-f1a9-bd33-6fbd-7d8c61d0ba1e.png" width="670">

- `compute.instances.start`, `compute.instances.stop`を選択します。フィルタをうまく使うと見つけやすいです。
- 「ADD」をクリックします。
- もとのページに戻ったら、「CREATE」をクリックしてロールを作成します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80588/a4e01278-55f1-8916-9a55-4b91887dcad6.png" width="500">



## ケース１：スケジューラーによる起動と停止


### 実験用のスクリプトの作成

GCEインスタンスが停止している場合は起動（コンソール上でリストな中から選択し、再生ボタンをクリック）します。
起動後、インスタンスにログインします。おすすめのログイン方法は次のコマンドです。Terminal、コマンドプロンプト、またはCloud Shellなどで実行してください（`gcloud`コマンドがインストールされていなければ適宜インストールします）。

```shell
gcloud beta compute ssh --zone "us-central1-a" --project "{project_id}" "ubuntu@my-test-vm"
```
- `--zone`, `--project`オプションは適宜変更してください。
- ユーザー名は`ubuntu`を選んでいます。他のOSの場合は適宜変更してください。また、指定しないと新しいユーザーが作られるようです。
- `my-test-vm`の部分はインスタンス名です。適宜変更してください。
- `gcloud`を使わずに普通のSSHコマンドでログインすることも可能です。インスタンスの詳細ページで必要な情報を確認できます。

下記のシェルスクリプトを作成します。これはファイルに現在の日時を書き込むだけのスクリプトです。

```shell:~/add-line.sh
#!/bin/bash

# This script adds a line to a test file.
echo "$(date +%Y-%m-%d_%H:%M:%S)" >> ~/testfile.txt
```

スクリプトを実行可能にしておきます。

```shell
chmod 700 ~/add-line.sh
```

Crontabを開き、スクリプトを毎分実行するよう設定します。

```shell
crontab -e

# 下記を追記
* * * * * /home/ubuntu/add-line.sh
# every minute, add a new line
```

設定後、5分ほど待って、`testfile.txt`に日時がいくつか追加されていれば成功です。

```shell
cat ~/testfile.txt
#2021-08-16_15:45:01
#2021-08-16_15:46:01
#2021-08-16_15:47:01
#...
```

忘れずに、GCEインスタンスを停止します。

### GCEのインスタンススケジューラーの作成

[Compute Engineの「INSTANCE SCHEDULE」タブ](https://console.cloud.google.com/compute/instances?tab=instanceSchedules)を開きます。
上の方にある「+ CREATE SCHEDULE」クリックします。

- "Name"は任意です（ここでは `gce-start-stop-scheduler`）
- "Region"はインスタンスと同じにします
- 上の方の「Use CRON expression」をオンにすると、スケジュールを柔軟に設定できます。
- ここでは、毎日、日本時間の午前1時に起動、午前1時半に停止することにしています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80588/965156bc-93d4-e71f-f933-ed9025b1d10d.png" width="450">

### インスタンスを起動・停止する権限をインスタンススケジューラーを実行するサービスアカウントに付与

[Compute Engineの「INSTANCE SCHEDULE」タブ](https://console.cloud.google.com/compute/instances?tab=instanceSchedules)を開き、上で作成したスケージューラーインスタンス（`gce-start-stop-scheduler`）をクリックし、詳細ページを開きます。

- 「ADD INSTANCES TO SCHEDULE」をクリックし、上で作成したVMインスタンス（`my-test-vm`）を選びます。
- このとき、通常はエラーメッセージが出ます。`service-{projectid}@compute.system.iam.gserviceaccount.com`に必要な権限がない、という意味です。どうやら、このサービスアカウントがVMの起動・停止を行う主体のようです。
- そこで、このサービスアカウントに権限を付与します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80588/f38c7a3d-4c1d-4c86-4133-5406bb7196ce.png" width="600">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80588/6621a3a3-1a7d-4caa-0a2f-e7d09214b245.png" width="600">

[Compute Engineの「INSTANCES」タブ](https://console.cloud.google.com/compute/instances?tab=instances)を開き、上で作成したVMインスタンス（`my-test-vm`）を選択します。

- ページ右の方の「PERMISSIONS」タブを開き、「ADD MEMBER」をクリックします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80588/ba6b466a-d5bf-1726-ae01-83c4b5c8563e.png" width="400">


- New members のところへ、上で権限が必要と言われたサービスアカウント（`service-{projectid}@compute.system.iam.gserviceaccount.com`）を追加します。
- "Role" に、上で作成した `gce-start-stop-role`を加えます。これにより、サービスアカウントはこのVMについて、インスタンスの開始・停止権限を得ることになります。
- 「SAVE」をクリックします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80588/0177724b-0ac9-0537-1ea6-f4db7265ad0c.png">

改めて、[Compute Engineの「INSTANCE SCHEDULE」タブ](https://console.cloud.google.com/compute/instances?tab=instanceSchedules)を開き、上で作成したスケージューラーインスタンス（`gce-start-stop-scheduler`）をクリックし、詳細ページを開きます。
「ADD INSTANCES TO SCHEDULE」をクリックし、上で作成したVMインスタンス（`my-test-vm`）を選びます。
今度は、権限の問題がなくなっているので、追加ができるはずです。
これにより、指定した時刻にVMインスタンスを起動、停止する設定ができました。

### 実験結果

自動起動、停止を設定した時刻まで待ちます（上の例では午前1時半）。
午前1時半直後だとまだ停止していないことがありますので、インスタンスが停止するまで待ちます（5分から15分程度ラグがあるようです）。
停止を確認したら、改めてインスタンスを起動してログインします。


時刻が記載されるファイルを確認すると、次のようになっていました。

```shell
cat ~/testfile.txt
#2021-08-16_15:45:01
#2021-08-16_15:46:01
#2021-08-16_15:47:01
#2021-08-16_15:48:01
#2021-08-16_16:05:01
#2021-08-16_16:06:01
#2021-08-16_16:07:01
#...
#2021-08-16_16:31:01
#2021-08-16_16:32:01
#2021-08-16_16:33:01
#2021-08-16_16:34:01
``` 

- 時刻は世界標準時なので、これに9時間足したものが日本時間です
- 最初の4行は開発時の残りです
- 5行目からは自動起動から停止の間の結果です。標準時の16時は日本時間1時なので、仮想マシンは`01:05`ころに起動し、`01:34`ころに停止したようです。設定した時刻から5分程度ラグがあります
- [ドキュメント](https://cloud.google.com/compute/docs/instances/schedule-instance-start-stop?hl=ja#limitations)では、15分程度の余裕を見て設定することが推奨されています

## ケース２：インスタンス内からの停止

インスタンスの停止に関しては、外から決まった時刻ではなくインスタンスの中で停止したいこともあります。
たとえば、時間のかかる作業を寝る前に起動して、終了後に自動で停止させることができると、朝起きて自分で停止するのに比べて費用と手間を節約できます。
インスタンスの中から `gcloud`コマンドを実行することで、これを実現できます。ただし、インスタンスを実行するサービスアカウントにその権限を付与する必要があります。

- `hostname`はVMの名前になっているので、`my-test-vm`が代入されます。直接VMの名前を書いても大丈夫です。
- `--zone`は適宜変更してください。

```shell
gcloud compute instances stop --zone us-central1-a "$(hostname)"
```

`shutdown`や`poweroff`コマンドでVMを停止することも可能ですが（参考：[OS から VM を停止する](https://cloud.google.com/compute/docs/instances/stop-start-instance)）、`sudo`が必要だったりするので`gcloud`の方が使いやすいと思います。

### インスタンスの起動・停止権限をインスタンスを実行するサービスアカウントに付与

[Compute Engineの「INSTANCES」タブ](https://console.cloud.google.com/compute/instances?tab=instances)を開き、上で作成したVMインスタンス（`my-test-vm`）を選択します。

- ページ右の方の「PERMISSIONS」タブを開き、「ADD MEMBER」をクリックします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80588/61729e65-4ff6-ea40-e63a-0716e42261f1.png" width="400">

- New members のところへ、VMを実行するサービスアカウント（`gce-worker`）を追加します。
- "Role" に、上で作成した `gce-start-stop-role`を加えます。これにより、サービスアカウントはこのVMについて、インスタンスの開始・停止権限を得ることになります。
- 「SAVE」をクリックします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80588/e68f9cd1-248c-e12e-ba3b-92821b00b826.png">


### 実験

VMを起動し、ログインします。
次のシェルスクリプトを作成します。これは、画面上に10から0までを1秒間隔で表示し、その後VMを停止するスクリプトです。

```shell:~/count-and-stop.sh
#!/bin/bash

for i in {10..0}
do
  echo $i
  sleep 1
done

gcloud compute instances stop --zone us-central1-a "$(hostname)"
```

実行可能ファイルにします。

```shell
chmod 700 ~/count-and-stop.sh
```

実行します。すると、10から0まで数えた後VMが停止され、その結果自動的にSSH接続が切断されました。コンソールで確認すると、確かにVMが停止していることが確認できます。

```shell
~/count-and-stop.sh
#10
#9
#8
#7
#6
#5
#4
#3
#2
#1
#0
#Stopping instance(s) my-test-vm...⠶Connection to xxx.xxx.xxx.xxx closed by remote host.
#Connection to xxx.xxx.xxx.xxx closed.
#ERROR: (gcloud.beta.compute.ssh) [/usr/bin/ssh] exited with return code [255].
```

### 備考

この方法を自動起動と組み合わせれば、次のように定期バッチを実装することも可能です。
ただし、2つの時間指定（VMの起動とスクリプトの実行）を矛盾なく設定する必要があるので、管理方法としてはやや煩雑かもしれません。

例：

- 毎日決まった時間にインスタンススケジューラーによってVMを起動
- VM側で、「特定のタスクを実行し、その後VMを停止する」スクリプトを定義し、VM起動の15分後にCronjobで実行