ELB ヘルスチェックのリクエストヘッダーの確認と、任意のエラーコードでヘルスチェックを失敗させたい

ncコマンドでできるヘルスチェックのリクエストヘッダーの出力と、任意のステータスコードでヘルスチェック失敗のさせ方です。
2021.04.20

ターゲットグループのヘルスチェックについて調べる機会がありました。確認、検証した内容をまとめます。

この記事で学べること

  • ヘルスチェックのリクエストヘッダーの確認方法
  • 任意のエラーコードでヘルスチェックのエラーを出力方法

ヘルスチェックについて

ターゲットグループに登録されたターゲット(インスタンスや、コンテナ)に対して定期的にリクエストを送信し、期待した応答があるかどうかでターゲットの正常性を確認する動作です。

ヘルスチェックのリクエストヘッダー確認

ヘルスチェックのリクエストヘッダーを出力し確認する機会がありました。

ncコマンドで確認しました。ターゲットグループのヘルスチェックパスは/healthでポートは8080を指定しています。

ターゲットグループ設定値

項目
ターゲットの種類 インスタンス
プロトコル:ポート HTTP:8080
プロトコルバージョン HTTP1
プロトコル HTTP
パス /health
ポート traffic-port

出力結果です。求めていたものです。

$ nc -l 8080
GET /health HTTP/1.1
Host: 10.0.17.184:8080
Connection: close
User-Agent: ELB-HealthChecker/2.0
Accept-Encoding: gzip, compressed

簡単にリクエストヘッダーを出力できました。ヘッダーの説明は下記リンクをご確認ください。

ncコマンドがあればサクッと確認できました。思いついた中では一番手っ取り早い方法でした。

Pythonはどう?

Python 2.xは標準でインストールされていることが多いのでサクッと確認できるのではないか?と思ったので試しました。

SimpleHTTPServerを使ったワンライナーのコマンドではリクエストヘッダーの取得はできませんでした。ncコマンドと同等の手軽さを求めていたのでワンライナーにこだわってしまいました。

$ python -V
Python 2.7.18

$ python -m SimpleHTTPServer 8080
Serving HTTP on 0.0.0.0 port 8080 ...
10.0.2.180 - - [20/Apr/2021 02:44:32] code 404, message File not found
10.0.2.180 - - [20/Apr/2021 02:44:32] "GET /health HTTP/1.1" 404 -

ワンライナーのHTTPサーバ探したらいろいろな方法が見つかり面白かったです。ご興味があればこちらもどうぞ。

ヘルスチェックのステータスを変えたい

任意のステータスコードでヘルスチェックをエラーにして確認したいことがありました。

ncコマンドであればヘルスチェックパスを/に設定すると簡単にできそうなので試してみました。

ステータスコードを200を返してヘルスチェックが正常の判定になるか確認します。

while true; do ( echo "HTTP/1.1 200 Ok"; echo; echo "I'm OK" ) | nc -l 8080; done

実行結果はヘルスチェックのリクエストのたびに下記の出力が繰り返されます。単発のnc -l 8080コマンドと同じ出力結果です。

GET / HTTP/1.1
Host: 10.0.17.184:8080
Connection: close
User-Agent: ELB-HealthChecker/2.0
Accept-Encoding: gzip, compressed

healthyを確認できました。ループしておけばヘルスチェックのテストに利用できることがわかりました。

ELBトラブルシュートのドキュメントを参考にステータスコードを460に指定しました。ステータスの内容が不明だったので適当にI don't knowを入れました

while true; do ( echo "HTTP/1.1 460 I don't know..."; echo; echo "I'm OK" ) | nc -l 8080; done

unhealthyになり、エラーコード460と確認できます。ステータスコードの数字を書き換えるだけで任意のエラーを起こせることがわかりました。

ヘルスチェックのパスを変更したい

ncコマンドで/health宛てのヘルスチェックを試したいときにパスの変更方法がわかりませんでした。

GoのWEBフレームワークEchoを使い、ビルドした実行ファイル一発でWEBサーバを起動して確認することにしました。

WEBサーバの準備

レスポンスヘッダーに設定するステータスコードを各々指定しています。便利なことにステータスコードは定数が用意されていました。

  • /healthにアクセスすると200 Okが返る
  • /health2は、405 Method not allowed
  • /health3は、501 Not implemented

main.go

package main

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

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
)

// Health check status
type StatusResponse struct {
	Status int `json:"status"`
}

func main() {
	// Echo instance
	e := echo.New()

	// Middleware
	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	// Routes
	e.GET("/", Home)
	e.GET("/health", HealthCheck)
	e.GET("/health2", HealthCheck2)
	e.GET("/health3", HealthCheck3)

	// Start server
	e.Logger.Fatal(e.Start(":8080"))
}

// Handler
func Home(c echo.Context) error {
	return c.String(http.StatusOK, "Hello Abashiri")
}

func HealthCheck(c echo.Context) error {
	headers := fmt.Sprintf("%v", c.Request().Header)
	log.Println("RequestHearders:", headers)

	r := &StatusResponse{
		Status: http.StatusOK,
	}
	return c.JSON(http.StatusOK, r)

}

func HealthCheck2(c echo.Context) error {
	statusCode := http.StatusMethodNotAllowed
	r := &StatusResponse{
		Status: statusCode,
	}
	return c.JSON(statusCode, r)

}

func HealthCheck3(c echo.Context) error {
	statusCode := http.StatusNotImplemented
	r := &StatusResponse{
		Status: statusCode,
	}
	return c.JSON(statusCode, r)

}

Arm(Graviton2)インスタンスで実行するためクロスコンパイルします。

$ go version
go version go1.16.3 darwin/amd64

$ GOOS=linux GOARCH=arm64 go build -o responseCode main.go

EC2インスタンスへ実行ファイルをコピーして実行すると簡単にWEBサーバが起動しました。不要になったら実行ファイル削除するだけで済み後片付けも楽です。

$ ./responseCode

   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.2.2
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on [::]:8080

/healthの結果

ヘルスチェックパスを/healthに変更しました。当然のようにhealthyを確認できます。

WEBサーバのログ

{"time":"2021-04-20T04:52:29.304548416Z","id":"","remote_ip":"10.0.2.180","host":"10.0.17.184:8080","method":"GET","uri":"/","user_agent":"ELB-HealthChecker/2.0","status":200,"error":"","latency":22531,"latency_human":"22.531µs","bytes_in":0,"bytes_out":14}

/health2の結果

ヘルスチェックパスを/health2に変更しました。サーバのログからもリクエストしたパスを読み取れます。想定どおりunhealthyで任意のエラーを確認できました。

{"time":"2021-04-20T04:53:49.382389472Z","id":"","remote_ip":"10.0.2.180","host":"10.0.17.184:8080","method":"GET","uri":"/health2","user_agent":"ELB-HealthChecker/2.0","status":405,"error":"","latency":29325,"latency_human":"29.325µs","bytes_in":0,"bytes_out":15}

/health3の結果

同上です。

{"time":"2021-04-20T05:47:22.594833734Z","id":"","remote_ip":"10.0.2.180","host":"10.0.17.184:8080","method":"GET","uri":"/health3","user_agent":"ELB-HealthChecker/2.0","status":501,"error":"","latency":29235,"latency_human":"29.235µs","bytes_in":0,"bytes_out":15}

おわりに

リクエストヘッダーと、レスポンスのステータスコードと向き合いました。ncコマンドが便利。後半はなにに役立つかわからない検証だったのですが調べているうちに新しいことを学びました。

ターゲットグループに有効なターゲットが存在せず、すべてヘルスチェックに失敗するとフェイルオープンの動作すること。

If all targets fail health checks at the same time in all enabled Availability Zones, the load balancer fails open. The effect of the fail open is to allow traffic to all targets in all enabled Availability Zones, regardless of their health status.

Health checks for your target groups - Elastic Load Balancing

「あー、認識誤ってたわ」と思ったらブログにまとまってました。なんでもあるので驚くばかりです、同じ会社なのですけど。

参考