Cloud Functionsを使ってCompute Engineを制御するという話

こんにちはgrasysの福嶌です。 そろそろ競輪祭の時期ですね。それが終わればグランプリなので1年はあっという間だなと言う感じしかしません。

はじめにgrasysでは検証環境があります、ただ気軽に使えるのではなく金額を気にして使わないと怒られてしまいます。
そのためインスタンスタイプを低くして使うことが多いのですが今回は非力ゆえにスペックの低いGCEインスタンスで起きた事象(CPUが100%に張り付いてインスタンスにsshすらできなくなる)の解決策(その場しのぎの対応)について書いていこうかと思います。

事象

検証で同僚1のインスタンスを間借りしようとしたらsshができなく確認したらCPUが100%に張り付いている事象が発生していました。

プロセスを確認するとGCEの以下のディレクトリのログが出力されており以下のディレクトリに出力されてました。

/.config/gcloud/logs/

ドキュメントを見ると以下のような記載

ログファイルには、gcloud compute ツールを使用して送信されたすべてのリクエストとそのレスポンスに関する情報が含まれています。 https://cloud.google.com/compute/docs/troubleshooting/general-tips?hl=ja#viewlogs

結局インスタンスタイプが低すぎて当該のプロセスが原因でCPUが100%に張り付いてしまいインスタンスのリセットを行うと事象は解決しましたが定期的に発生し都度リセットを行うと言う作業をしていました。

解決方法

今回のゴールとして以下を設定しました。 CPUが100%になり張り付いたらそれをトリガーとしてCloud Functions経由でインスタンスの制御を行う。

構成

今回は以下のような構成で作っていこうと思います。

Cloud Pub/Sub

アラートを飛ばす先のPub/Subの設定をします。アラート通知にPub/Subを設定は作ったPub/Subトピックにモニタリングに向けて権限設定を設定する必要があります。
ドキュメントを参考に以下のコマンドを実施

$ gcloud pubsub topics add-iam-policy-binding projects/{{プロジェクト名}}/topics/{{pub/subトピックID}} --role=roles/pubsub.publisher --member=serviceAccount:xxxxx@gcp-sa-monitoring-notification.iam.gserviceaccount.com

Cloud Monitoring

アラートは以下のように設定

対象インスタンス:自分の検証に利用しているインスタンス
Condition: is above
Threshold: 99.9
For: 5 minutes

99.9%以上の高負荷が5分続いた場合アラートを発砲するようにしました。
通知先は念のためメールとPub/Subにします。

Cloud Functions

制御するコードは以下のようなコードを作成しました。今回はgoogle-authgoogle-api-python-clientを利用してAPIを叩きます。

#!/usr/bin/python
#coding:utf-8

import sys
import json
import base64

import google.auth
import googleapiclient.discovery

class Variables:
    name = {{インスタンス名}}
    zone = {{ゾーン名}}}

def reset_instances(compute, project, zone):
    # 対象のVMをリセット
    try:
        compute.instances().reset(project=project, zone=zone, instance=Variables.name).execute()
    except Exception as e:
        print(e)
        sys.exit(1)

    return "success"

def alert2pubsubFunc(event, context):
    if 'data' in event:
        req = base64.b64decode(event['data']).decode('utf-8')
        staus = json.loads(req)['incident']['state']
    else:
        sys.exit(2)

    compute = googleapiclient.discovery.build('compute', 'v1')
    project = google.auth.default()[1]

    # Alert open
    if staus == "open":
        print(reset_instances(compute, project, Variables.zone))

また、Cloud FunctionsのランタイムはPython3.9を利用します。デプロイは以下のコマンドで実施

$ gcloud functions deploy alert2pubsubFunc --trigger-topic {{pub/subトピックID}} --runtime python39 --region asia-northeast1

デプロイエラーが出ると思ったら、Pyparsingが最新で3.0.6ためCloud Functionsでデプロイエラーを起こしてしまうようです。stackoverflowで探したら解決としてはバージョンを2.4.7に指定し解決しました。

テスト

そしたら早速テストをしてみましょう!
発報するためにアラートの閾値を低めに設定しアラートを飛ばしやすく調整を行い。サーバー内で以下のコマンドを実行してCPU負荷を高くしてみます。

$ yes > /dev/null

アラートが12:50に発報され…

モニタリングの方で確認したらインスタンスがリセットされた!

うまく動いてますね!

まとめ

今回はアラートをpub/subに送ってCloud FunctionsのトリガーとしてCompute Engineを制御しました。
またCloud FunctionsをHTTPリクエストにしてアラートではなくCloud Schedulerなどを応用して使えば自動起動や自動停止なども簡単にできそうですね。こちらの記事とか参考になると思います。
上記ができれば無駄な稼働時時間をなくすことができるのでお財布に優しいはず!


現場からは以上です!

  1. 指1本でタイピングしていた 「ど素人」が、世界的人気Webメディアのクラウドインフラを支えるエンジニア ↩︎