るいすのブログ

オタクエンジニアの雑記

Firebase hosting で超簡単に障害時に自動でメンテナンスに振る


TL;DR

  • メンテナンス情報を Firebase Cloud Fire Store から取得する
  • uptimerobot の Webhook を使う
  • Firebase functions で uptimerobot からの webhook を受け取る
  • Firebase functions で Firebase Cloud Fire Store を書き換える

Firebase Cloud Fire Store を使う

firestore.js

import Firebase from 'firebase'
import 'firebase/firestore'

let config = {}

if (process.env.STAGE === 'staging' || process.env.STAGE === 'develop' || process.env.STAGE === 'local') {
  config = {
    apiKey: '',
    authDomain: 'easyuploader-web-stg.firebaseapp.com',
    databaseURL: 'https://easyuploader-web-stg.firebaseio.com',
    projectId: 'easyuploader-web-stg',
    storageBucket: 'easyuploader-web-stg.appspot.com',
    messagingSenderId: '1029528151776',
    appId: '1:1029528151776:web:ba68efd8003542a10fd285'
  }
}

if (process.env.STAGE === 'production') {
  config = {
    apiKey: '',
    authDomain: 'easyuploader-web.firebaseapp.com',
    databaseURL: 'https://easyuploader-web.firebaseio.com',
    projectId: 'easyuploader-web',
    storageBucket: 'easyuploader-web.appspot.com',
    messagingSenderId: '940122739680',
    appId: '1:940122739680:web:8b6d89ef3d3ac20a841185'
  }
}

if (process.env.STAGE === 'free') {
  config = {
    apiKey: '',
    authDomain: 'easyuploader-free.firebaseapp.com',
    databaseURL: 'https://easyuploader-free.firebaseio.com',
    projectId: 'easyuploader-free',
    storageBucket: 'easyuploader-free.appspot.com',
    messagingSenderId: '816447004806',
    appId: '1:816447004806:web:4b3ca4d405e8cfa66bb107',
    measurementId: 'G-JGTVDZE5MY'
  }
}

const firebaseApp = Firebase.initializeApp(config, 'exercise-vue')
const firestore = firebaseApp.firestore()

export default firestore

App.vue

<script>
import firestore from './firestore'
const initRef = firestore.collection('init')

export default {
  name: 'EasyUploader',
  created () {
    initRef.get().then(querySnapshot => {
      querySnapshot.forEach(doc => {
        const initObj = doc.data()
        this.initObj.isMaintenance = initObj.maintenance
        this.initObj.message = '【お知らせ】' + initObj.message
      })
    })
  }
}
</script>

uptimerobot webhook

uptimerbot は無料で使える死活監視 SaaS で、アラートに webhook が使える。

uptimerobot.com

そして、指定した URL にクエリパラメータとして下記の情報が付与される

*monitorID* (the ID of the monitor)
*monitorURL* (the URL of the monitor)
*monitorFriendlyName* (the friendly name of the monitor)
*alertType* (1: down, 2: up, 3: SSL expiry notification)
*alertTypeFriendlyName* (Down or Up)
*alertDetails* (any info regarding the alert -if exists-)
*alertDuration* (in seconds and only for up events)
*alertDateTime* (in Unix timestamp)
*monitorAlertContacts* (the alert contacts associated with the alert in the format of 457;2;john@doe.com -alertContactID;alertContactType, alertContactValue)
*sslExpiryDate* (only for SSL expiry notifications)
*sslExpiryDaysLeft* (only for SSL expiry notifications)

Firebase Functions

個人開発の雑書きですが、やりたいことはなんとなく分かると思います。

const functions = require('firebase-functions')

const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)

const firestore = admin.firestore()

// eslint-disable-next-line consistent-return
exports.uptimerobot = functions.https.onRequest((req, res) => {
  const project = process.env.GCLOUD_PROJECT
  const alertType = req.query.alertType
  const monitorID = req.query.monitorID

  console.log(`project: ${project}`)
  console.log(`alertType: ${alertType}`)
  console.log(`monitorID: ${monitorID}`)

  const docGetFileProblem = {
    maintenance: false,
    message: '現在ファイルの参照ができない問題が発生しています'
  }
  const docGetFileSolved = {
    maintenance: false,
    message: ''
  }
  const docBasicFunctionProblem = {
    maintenance: true,
    message: '現在アップロードすることができません'
  }
  const docBasicFunctionSolved = {
    maintenance: false,
    message: ''
  }
  const docUploadProblem = {
    maintenance: true,
    message: ''
  }
  const docUploadSolved = {
    maintenance: false,
    message: ''
  }

  let docID
  // staging
  if (project === 'easyuploader-web-stg') {
    docID = 'RA0EBeoUw0jZwxV9mGPR'
  }
  // free
  if (project === 'easyuploader-free') {
    docID = 'hwWuR9Q5Gs7KJ9MKRrkr'
  }
  // production
  if (project === 'easyuploader-web') {
    docID = 'NKqRdtrdiNBJSrYmQT5b'
  }

  const initRef = firestore.collection('init').doc(docID)

  // down
  if (alertType === '1') {
    // ファイル参照
    if (monitorID === '784159295') {
      return initRef.update(docGetFileProblem)
        // eslint-disable-next-line promise/always-return
        .then(() => {
          res.status(200).send('ok')
        })
        .catch((error) => {
          console.error('Error updating document: ', error)
        })
    }
    // 基本機能
    if (monitorID === '783009626') {
      return initRef.update(docBasicFunctionProblem)
        // eslint-disable-next-line promise/always-return
        .then(() => {
          res.status(200).send('ok')
        })
        .catch((error) => {
          console.error('Error updating document: ', error)
        })
    }
    // アップロード
    if (monitorID === '784152180') {
      return initRef.update(docUploadProblem)
        // eslint-disable-next-line promise/always-return
        .then(() => {
          res.status(200).send('ok')
        })
        .catch((error) => {
          console.error('Error updating document: ', error)
        })
    }
  }

  // up
  if (alertType === '2') {
    // ファイル参照
    if (monitorID === '784159295') {
      return initRef.update(docGetFileSolved)
        // eslint-disable-next-line promise/always-return
        .then(() => {
          res.status(200).send('ok')
        })
        .catch((error) => {
          console.error('Error updating document: ', error)
        })
    }
    // 基本機能
    if (monitorID === '783009626') {
      return initRef.update(docBasicFunctionSolved)
        // eslint-disable-next-line promise/always-return
        .then(() => {
          res.status(200).send('ok')
        })
        .catch((error) => {
          console.error('Error updating document: ', error)
        })
    }
    // アップロード
    if (monitorID === '784152180') {
      return initRef.update(docUploadSolved)
        // eslint-disable-next-line promise/always-return
        .then(() => {
          res.status(200).send('ok')
        })
        .catch((error) => {
          console.error('Error updating document: ', error)
        })
    }
  }
})