【Golang】AWS Lmbdaで特定サイトのHTTPS接続を監視、アラート通知をLINEで行ってみた【AWS】

2月 12, 2022

どもども、最近頼むカクテルがシャンディガフで固定化されつつあるsaisaiです。飲み屋全店置いて。


さて、僕は普段副業で構築した数サイトを今も顧客の要求に応じて保守しているのですが、とくにサイト接続監視などはしておりませんでした。


あるときお客様のとあるトラブルでAWSアカウントが停止しサイトに接続できなくなるという事件が発生したのですが、当時の僕はお客様に相談されるまでその事態に気づきませんでした。


個人的な仕事とはいえ流石にまずいなと思ったのでサイトに接続できなくなるとLINE経由でアラートが飛んでくる機能をAWS Lambdaで実装したので記事にしようと思います。


とはいえ、世の中には優良な無料監視サービスが存在しますので要件に合わせてこちらの記事の内容を参考にしていただければと思います。


また、GolangとLINE Messaging APIの準備、lambrollのインストールとLambdaに紐づけるIAMロールの準備はすでに完了しているものとします。LINE Messaging APIの設定についてはこちらを参照し、チャンネルの開設及びチャンネルシークレット、チャンネルトークン、ユーザIDの取得まで済ませていてください。lambrollについては過去のこちらの記事を参照してください。

必要ファイル

今回実装する機能に登場するファイルたちは以下の通りです。

・check-connection.go
サイトへの接続を行い200以外のレスポンス、あるいはエラーレスポンスだった場合にLINEで通知します。今回はGolangで実装します。
・url-list.txt
監視対象サイトのURLを記載します。
・.env
LINE Messaging APIの使用に必要なクレデンシャルを記載します。
・function.json
今回はlambrollを使用してコードをLambdaにデプロイするのでこちらの設定ファイルを生成します。

各種設定ファイルの準備

監視するコードを書く前にコードから必要な値を呼び出せるようにした準備をしていきます。今回必要な値は

LINE Messaging APIチャンネルシークレット
LINE Messaging APIチャンネルトークン
LINE Messaging APIユーザID
・監視対象URL

です。LINE APIを叩く際に使用するクレデンシャルをハードコーディングするのはあまり良くないため.envファイルを用意しgodotenvで呼び出すようにします。.envは下記3行を記載してください。

CHANNEL_SECRET= "チャンネルシークレット"
CHANNEL_TOKEN= "チャンネルトークン"
USER_ID= "APIユーザID"

続いて監視対象URLも別ファイルで管理しておきましょう。url-list.txtというファイルを作成し中にURLを記載しておきます。

https://graff-it-i.com/aaa/ ※こちらはエラーパターン検証のためにあえて存在しないパスを指定しています
https://example.com/

これで必要情報は揃いました。コードを書いていきましょう。

サイト接続を監視するコード

今回はGolangで実装します。まずは全体をお見せしましょう。今回はcheck-connection.goというファイルを作成しました。

package main

import (
  "fmt"
	"log"
  "net/http"
	"os"
	"bufio"

	"github.com/line/line-bot-sdk-go/linebot"
	"github.com/joho/godotenv"
	"github.com/aws/aws-lambda-go/lambda"
)

func defineUrl() {
    filename := "url-list.txt"

    fp, err := os.Open(filename)
    if err != nil {
    }
    defer fp.Close()

    scanner := bufio.NewScanner(fp)

    for scanner.Scan() {
        checkStatusCode(scanner.Text())
    }

    if err = scanner.Err(); err != nil {
    }
}

func checkStatusCode(url string ){

    req1, err := http.NewRequest("GET", url, nil)
	req2, err := http.NewRequest("GET", url, nil)

    if err != nil {
        log.Fatal(err)
    }

    req1.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36")
	req2.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36")
	res1, err := http.DefaultClient.Do(req1)
	res2, err := http.DefaultClient.Do(req2)

    if err != nil {
		msg := fmt.Sprintf("%s につながりません、確認してください!", url)
		postLineMessage(msg)        
        log.Fatal(err)
    }

	status := fmt.Sprintf("%s windows=%v Mac=%v", url, res1.StatusCode, res2.StatusCode)
	fmt.Println(status)

    if res1.StatusCode != 200 || res2.StatusCode != 200 {
		fmt.Println(res1.StatusCode, res2.StatusCode)
		msg := fmt.Sprintf("%s 200以外のステータスコードです、確認してください!", url)
		postLineMessage(msg)
	}

}

func postLineMessage( result string ) {
	err := godotenv.Load(".env")
	bot, err := linebot.New(os.Getenv("CHANNEL_SECRET"), os.Getenv("CHANNEL_TOKEN"))
	if err != nil {
		fmt.Println(err)
	}
	if _, err := bot.PushMessage(os.Getenv("USER_ID"), linebot.NewTextMessage(result)).Do(); err != nil {
		fmt.Println(err)
	}
}

func main() {
	lambda.Start(defineUrl)
}

まずは下記の部分です。こちらはurl-list.txtから読み出したURLを引数としてcheckStatusCodeを呼び出しています。url-list.txtに複数URLが記載されている場合、その分だけfor文で各URLを対象にcheckStatusCodeを呼び出すことができます。

func defineUrl() {
    filename := "url-list.txt"

    fp, err := os.Open(filename)
    if err != nil {
    }
    defer fp.Close()

    scanner := bufio.NewScanner(fp)

    for scanner.Scan() {
        checkStatusCode(scanner.Text())
    }

    if err = scanner.Err(); err != nil {
    }
}

次に実際に引数に取ったURLを対象にユーザーエージェントを設定してHTTPSに接続しステータスコードを取得します。また、そのステータスコードが200だった場合はログの出力のみ、200以外、あるいはそもそもエラーだった場合は接続できない可能性が高いのでLINE Messaging APIを使用してLINE通知します。

func checkStatusCode(url string ){

    req1, err := http.NewRequest("GET", url, nil)
	req2, err := http.NewRequest("GET", url, nil)

    if err != nil {
        log.Fatal(err)
    }

    req1.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36")
	req2.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36")
	res1, err := http.DefaultClient.Do(req1)
	res2, err := http.DefaultClient.Do(req2)

    if err != nil {
		msg := fmt.Sprintf("%s につながりません、確認してください!", url)
		postLineMessage(msg)        
        log.Fatal(err)
    }

	status := fmt.Sprintf("%s windows=%v Mac=%v", url, res1.StatusCode, res2.StatusCode)
	fmt.Println(status)

    if res1.StatusCode != 200 || res2.StatusCode != 200 {
		fmt.Println(res1.StatusCode, res2.StatusCode)
		msg := fmt.Sprintf("%s 200以外のステータスコードです、確認してください!", url)
		postLineMessage(msg)
	}

}

最後にLINE Messaging APIを使用しLINEメッセージを送信します。APIを使用するためのクレデンシャルは先ほど用意した.envからgodotenvを使用して呼び出します。

func postLineMessage( result string ) {
	err := godotenv.Load(".env")
	bot, err := linebot.New(os.Getenv("CHANNEL_SECRET"), os.Getenv("CHANNEL_TOKEN"))
	if err != nil {
		fmt.Println(err)
	}
	if _, err := bot.PushMessage(os.Getenv("USER_ID"), linebot.NewTextMessage(result)).Do(); err != nil {
		fmt.Println(err)
	}
}

これでコードは完成です。Lambdaで実行できるようコンパイルしておきましょう。

GOARCH=amd64 GOOS=linux go build check-connection.go

ここまで完了したらLambdaにデプロイしましょう。

lambrollでデプロイ

まずはlambrollを使用する最初の準備を行います。登録しているAWSクレデンシャルのうち特定のプロファイルのものを使用する場合はそちらも指定してください。

$ lambroll init --function-name=https-connection-alert --profile="使用するプロファイル名"

これでfunction.jsonが生成されたはずなので中身を下記のように変更していきます。

{
  "FunctionName": "https-connection-alert",
  "Handler": "check-connection",
  "MemorySize": 128,
  "Role": "用意したIAMロールのARN",
  "Runtime": "go1.x",
  "Timeout": 3
}

それではデプロイしましょう。

$ lambroll deploy --region "ap-northeast-1" --profile="使用するプロファイル名" 

これでLambdaにコードがデプロイされました。テストしてみましょう。エラーコードのレスポンスを通知するLINEメッセージを受信できれば成功です。


あとはurl-list.txtの内容を実際に監視したいURLに変更し、上記コマンドで再デプロイしてご利用ください!

最後に

今回は特定サイトのhttps接続確認を行い異常時LINE通知するというコードを作成してみました。lambrollはやっぱり便利ですね!


冒頭にも述べた通り監視ツールというのは世の中に色々ありますので、メール通知やslack通知であれば作成するまでもなく接続監視を実現することも可能でしょう。


僕の場合は個人的な仕事ということもありLINE通知したかったのでAWS Lambdaで実装しましたが、様々なソリューションを検討してみてください!


最後まで読んでいただきありがとうございました!


↓オススメ教材

AWS,Golang

Posted by CY