はじめに
EC2の「ステータスチェック」といえば、これまではインスタンス自体やホスト側の障害を見るものでした。
Webサーバーのプロセスが固まっている、Dockerデーモンが落ちているといった「アプリケーションの中身」の異常は、EC2側のチェックでは検知できず、自前でヘルスチェックの仕組みを作る必要がありました。
今回、この隙間を埋める新しいステータスチェックが追加されたので、実際にインスタンスを立てて正常時・異常時の両方の挙動を確認してみました。
Application status checksとは
アプリケーションが動いているポートとパス(例: /health)に対して定期的にHTTPリクエストを送り、期待したステータスコードが返ってくるかどうかを60秒間隔で確認する機能です。
チェック用のリクエストは、自分のVPC内に自動で作られる「マネージドENI(AWSが管理するネットワークインターフェース)」から送られるため、インターネットを経由しません。
チェックは「連続◯回失敗したらimpaired」「連続◯回成功したらok」というしきい値(デフォルトはどちらも2回)で判定され、この結果はAuto Scalingグループのヘルスチェックとしてもそのまま使えます。
既存のインスタンス/システムステータスチェックと並行して動くので、今まで自前で作っていたアプリケーションレベルの死活監視をAWS側に寄せられるのが一番のメリットかと思います。
やってみた
手順1: Terraformでテスト用インスタンスを用意する
nginxをアプリケーション代わりに動かすインスタンスと、ポート80をVPC内からだけ許可するセキュリティグループを作ります。
main.tfの中身
mkdir -p appstatus-tf && cd appstatus-tf
cat > main.tf <<'EOF'
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
data "aws_vpc" "default" {
default = true
}
data "aws_subnets" "default" {
filter {
name = "vpc-id"
values = [data.aws_vpc.default.id]
}
}
data "aws_ssm_parameter" "al2023_arm64" {
name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
}
resource "aws_security_group" "appstatus_verify" {
name = "appstatus-verify-sg"
description = "temp sg for application status check verification"
vpc_id = data.aws_vpc.default.id
}
resource "aws_vpc_security_group_egress_rule" "all" {
security_group_id = aws_security_group.appstatus_verify.id
cidr_ipv4 = "0.0.0.0/0"
ip_protocol = "-1"
}
resource "aws_instance" "appstatus_verify" {
ami = data.aws_ssm_parameter.al2023_arm64.value
instance_type = "t4g.micro"
subnet_id = sort(data.aws_subnets.default.ids)[0]
vpc_security_group_ids = [aws_security_group.appstatus_verify.id]
user_data = <<-USERDATA
#!/bin/bash
dnf install -y nginx
systemctl enable --now nginx
USERDATA
tags = {
Name = "appstatus-verify"
}
}
output "instance_id" {
value = aws_instance.appstatus_verify.id
}
output "security_group_id" {
value = aws_security_group.appstatus_verify.id
}
EOF
ヘルスチェック用のインバウンドルールだけは、あとの手順4で「Terraformの変更として」消したいので別ファイルに分けておきます。
cat > ingress.tf <<'EOF'
resource "aws_vpc_security_group_ingress_rule" "http" {
security_group_id = aws_security_group.appstatus_verify.id
cidr_ipv4 = data.aws_vpc.default.cidr_block
from_port = 80
to_port = 80
ip_protocol = "tcp"
}
EOF
terraform init -input=false
terraform apply -auto-approve
INSTANCE_ID=$(terraform output -raw instance_id)
SG_ID=$(terraform output -raw security_group_id)
echo "INSTANCE_ID=$INSTANCE_ID / SG_ID=$SG_ID"
AMIをAWS公式ドキュメントにあるami = "resolve:ssm:/aws/service/..."構文で直接指定すると、state上は実際に解決されたAMI IDが保存されるため、次のterraform applyのたびに設定値との差分でインスタンスが再作成されてしまいます。
そのため、data "aws_ssm_parameter"経由で値を取ることで安定します。
また、aws_subnetsのidsは実はList型なのですが、裏側のAWS API(DescribeSubnets)自体がレスポンス順序を保証していないため、tolist(...)[0]のままだと再適用時にsubnet_idが変わってインスタンスが再作成されることがありました。sort()で固定しています。
手順2: Application status checkを作成する
Get Started手順に沿って、HTTPのポート80・パス/(デフォルトの/にnginxが200を返すのでそのままでOK)・ステータスコード200を条件にチェックを作ります。
CHECK_ID=$(aws ec2 create-application-status-check \
--protocol http --port 80 --status-code-matcher 200 \
--initialization-grace-period-seconds 30 \
--query 'ApplicationStatusCheck.ApplicationStatusCheckId' --output text)
echo "CHECK_ID=$CHECK_ID"
手順3: インスタンスに関連付けて結果を確認する
aws ec2 associate-application-status-check \
--application-status-check-id $CHECK_ID --instance-ids $INSTANCE_ID
関連付け直後はinitializingで、しばらくするとokに変わります。okになるまでその場でポーリングします。
while true; do
STATUS=$(aws ec2 describe-application-status \
--query "ApplicationStatuses.Instances[?InstanceId=='$INSTANCE_ID'].ApplicationStatus.Status" \
--output text)
echo "$(date +%T) $STATUS"
[ "$STATUS" = "ok" ] && break
sleep 15
done
aws ec2 describe-application-status \
--query "ApplicationStatuses.Instances[?InstanceId=='$INSTANCE_ID']"
[
{
"InstanceId": "i-0b2b14188f940ab8e",
"ApplicationStatus": {
"Status": "ok",
"Details": [
{
"ApplicationStatusCheckId": "asc-f216389f",
"Status": "passed",
"Reason": { "Code": "ResponseCodeMatched", "StatusCode": 200, "Protocol": "HTTP" }
}
]
}
}
]
手順4: 障害を疑似的に起こして検知できるか確認する
ingress.tfを削除してterraform applyし、Terraformの変更としてインバウンドルールを取り除きます。
FailureThresholdのデフォルト2回どおり、impairedになるまで約2分かかります。
rm ingress.tf
terraform apply -auto-approve
while true; do
STATUS=$(aws ec2 describe-application-status \
--query "ApplicationStatuses.Instances[?InstanceId=='$INSTANCE_ID'].ApplicationStatus.Status" \
--output text)
echo "$(date +%T) $STATUS"
[ "$STATUS" = "impaired" ] && break
sleep 15
done
aws ec2 describe-application-status \
--query "ApplicationStatuses.Instances[?InstanceId=='$INSTANCE_ID']"
[
{
"InstanceId": "i-0b2b14188f940ab8e",
"ApplicationStatus": {
"Status": "impaired",
"Details": [
{ "Status": "failed", "Reason": { "Code": "ConnectionTimeout", "StatusCode": 0 } }
]
}
}
]
CLIの結果からは impaired となっていますが、AWS マネージメントコンソールの「アプリケーションステータス」は チェックに合格しました のままとなっているのは何なんでしょうね。「ステータスチェック」は 3/4 のチェックに合格しました となっていますので、確実にアプリケーションステータスは失敗しているはずなのですが、ちょっと今のところ謎です。
手順5: 後片付け
# まずチェックをインスタンスから外す
aws ec2 disassociate-application-status-check --application-status-check-id $CHECK_ID --instance-ids $INSTANCE_ID
# その後チェック自体を削除
aws ec2 delete-application-status-check --application-status-check-id $CHECK_ID
# マネージドENIが外れるまで数分〜十数分かかることがあるので、外れ次第destroyする
until terraform destroy -auto-approve; do
echo "$(date +%T) マネージドENIの解放待ち..."
sleep 30
done
echo "cleanup done"
料金
マネージドENI 1個・1アベイラビリティゾーンあたり時間0.01ドルに加え、チェックのメトリクスには通常のCloudWatch料金がかかります。
チェックそのものやHTTPリクエスト回数に対する追加課金はありません。


