この記事はGoogle Cloud Platform Advent Calendar 2020の4日目の記事です。
4日目の担当の方がなかなか投稿しないので雑に埋めにきました。
元々はCloud SQLについての記事が投稿されそうでしたが、筆者はRDBMSに関心がないので好きなことを書くことにします。
Network Endpoint Group(NEG)、使っていますか?
筆者はこれができてからUnmanaged Instance Groupではなく、NEGを利用してVMインスタンスをBackendService経由で利用するようになりました。
Unmanaged Instance Groupでも同じことはできますが、それでもNEGを利用しているのは、NEGはVMインスタンスだけではなく他のServerlessなサービスでも利用できて応用が利くからです。
一方で、NEGを利用することでVMインスタンスをGoogle Cloud Load Balancing(GCLB)から利用できますが、Managed Instance Group(MIG)とは異なり、ヘルスチェックに失敗した際にインスタンスを自動で再起動することができません。
この記事では、NEGをもっとproduction ready(?)に使っていくための工夫を紹介します。
Uptime checks
, Notification channels
, PubSub
, Cloud Functions
辺りの単語でやることがわかった方は読む必要がありません。
全てのイベントはPubSubに通ずべし
基本的な考え方として、イベントをPubSubにPublishできたら何をやるにしてもほぼ勝利が確定します。3割くらい嘘かもしれません。
ここでは、Cloud MonitoringのUptime checksを利用してインスタンスを監視し、ヘルスチェックに失敗したらイベントをPubSubに通知し、Cloud Functionsで拾って該当のインスタンスをRESETすることを目指します。
下準備
VMインスタンスにアプリケーションを用意する
Container Optimized OS(COS)に単一のコンテナをデプロイするだけでは、何も工夫しなくても勝手に再起動してくれやがるので、ここではWordpressとMySQLの2つのコンテナをdocker composeで起動することにします。
使用するcomposeの定義は以下のもので試します。
簡便のため、データの永続化は無視します。
めんどくさい人は適当なOSにWEBサーバでもインストールしてsystemctl enableでもしてください。
version: "3"
services:
wordpress:
hostname: wordpress
image: wordpress:5-php7.2-apache
ports:
- 80:80
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: password
WORDPRESS_DB_NAME: wordpress
WORDPRESS_TABLE_PREFIX: wp_
mysql:
hostname: mysql
image: mysql:5
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: password
MYSQL_RANDOM_ROOT_PASSWORD: "yes"
このファイルを、COSのホストOSでwritableな領域に保存します。
ここでは /var/wordpress.yaml
に保存します。
続いて、COSでdocker compose
を利用するために、live restoreを無効にしてdockerを再起動します。
$ sudo sed -i 's/true/false/g' /etc/docker/daemon.json
$ sudo systemctl restart docker
あとはいつも通りです。
$ docker swarm init
...
$ docker stack deploy -c /var/wordpress.yaml wordpress
Creating network wordpress_default
Creating service wordpress_wordpress
Creating service wordpress_mysql
$ docker stack services wordpress
ID NAME MODE REPLICAS IMAGE PORTS
h4tnwbuvamru wordpress_mysql replicated 1/1 mysql:5
ugg0mpp6sqs5 wordpress_wordpress replicated 1/1 wordpress:5-php7.2-apache *:80->80/tcp
$ curl -I localhost/readme.html
HTTP/1.1 200 OK
Date: Wed, 09 Dec 2020 14:26:06 GMT
Server: Apache/2.4.38 (Debian)
Last-Modified: Fri, 26 Jun 2020 13:58:02 GMT
ETag: "1c6e-5a8fd18ee0e80"
Accept-Ranges: bytes
Content-Length: 7278
Vary: Accept-Encoding
Content-Type: text/html
ところで、 /etc/docker/daemon.json
はインスタンスが再起動するたびに元の状態に戻ってしまうので、インスタンスのスタートアップスクリプトで書き換えるようにしてしまいましょう。
具体的にはインスタンスのメタデータ startup-script
に以下のように書き込んでください。
sed -i 's/true/false/g' /etc/docker/daemon.json &&\
docker stack deploy -c /var/wordpress.yaml wordpress
これでインスタンスの準備完了です。
Network Endpoint Groupを作成してインスタンスを紐付ける
ここからはコマンドラインでいきます。
ネットワークなどは適宜置き換えてください。
zoneはインスタンスのzoneと同じものを指定してください。ここではasia-northeast1-b
とします。
NEGを作成する
$ gcloud compute network-endpoint-groups create wordpress \
--zone=asia-northeast1-b \
--subnet=default \
--network-endpoint-type=GCE_VM_IP_PORT \
--default-port=80
Network Endpointを作成する
$ gcloud compute network-endpoint-groups update wordpress \
--zone=asia-northeast1-b \
--add-endpoint=instance=<インスタンス名>,ip=<インスタンス内部IP>,port=80
GCLBを起動する
コマンドラインでやるとすごく長いのでやめます。
ポイントは以下の点です。
- Zonal Network Endpoint Group
wordpress
をBackend Serviceとして利用する - ヘルスチェックのプロトコルをHTTP, Pathを
/readme.html
にする。
ヘルスチェックに成功し、GCLBのIPアドレスでWordpressの初期設定画面が出ることを確認してください。
PubSub Topic, Uptime checks, Notification channelを作成する
インスタンスのヘルスチェックを行うためのUptime checks
Uptime checksに失敗したときに通知を飛ばすNotification channel
通知先となるPubSub Topic
を作成していきます。
PubSub Topic
$ gcloud pubsub topics create notification
Notification Channel
Uptime Checks
必要なIAMを設定する
MonitoringのService AccountがPubSub Topic notification
へメッセージをPublishするために、役割を設定します。
ドキュメントによると
最初の Pub/Sub チャネルを作成すると、Cloud Monitoring は、チャネルが作成されたプロジェクトの Monitoring Notification Service Agent 用のサービス アカウントを作成します。
とあるのですが、一向に出来上がらないです。
しかし、出来上がるのを見たことはあるので、この辺がまだβなのかもしれません。
production readyとは一体...ウゴゴゴ。
Cloud Functionsで自動再起動する
かなりやっつけですがnodejsでコードを書いてきました。
PubSubのメッセージから該当インスタンスのzoneなどを取得してresetをします。
resetされたインスタンスは、startup-script
によって必要なコンテナを起動します。
const Compute = require('@google-cloud/compute');
const compute = new Compute();
exports.resetInstance = async (event, context, callback) => {
const data = JSON.parse(Buffer.from(event.data, 'base64').toString());
const vm = await getVm(data);
try {
await vm.reset();
} catch (err) {
console.error(err);
callback(err);
}
}
async function getVm(data) {
const zone = compute.zone(data.incident.resource.labels.zone);
const vmname = data.incident.resource_display_name;
const vminstance = zone.vm(vmname);
return vminstance;
}
{
"name": "instance-auto-restarter",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"@google-cloud/compute": "^2.4.0"
}
}
あとはこいつをデプロイして、コンテナを止めてUptime checksに失敗させたかったのですが、
自動で作成されるはずのサービスアカウントが作成されず、動作を試せません。
サービスアカウントが作成され次第、覚えていたら追記したいと思います。