EC2インスタンスに自動再起動を設定する

インフラを担当している渡瀬です。

歯が痛くて仕事にならないので虫歯だと思って歯医者に行ったら、TCH (Tooth Contacting Habit、「上下歯列接触癖」)と診断を受けました。

歯と歯が接触する時間は一般的に一日に17分だそうですが、TCHの人は歯と歯が接触している時間が長すぎるという癖だそうです。

下を向くと歯と歯が接触するため、デスクワークやスマホ操作が長い人に多いそうです。

背景

最近、EC2インスタンスがシステムステータスのチェックにフェイルするケースに遭遇しました。

当該サーバはautoscaling groupに所属していたため、自動的にインスタンスの追加と削除が実行されましたが、autoscaling groupに所属せずに動作しているEC2インスタンスもあるため、自動復旧する設定を投入しました。

VMWare vSphereやGCPであれば、ハードウェア障害は、ライブマイグレーション(GCP)で自動対応されますが、AWSにはライブマイグレーションの機能がないため、自力で対応する必要があります。

EC2のインスタンスチェックの種類

AWSのドキュメントを見てみました: インスタンスのステータスチェック

システムステータスチェック

基本的にハードウェア障害

システムステータスチェックの失敗の原因となる問題の例を次に示します。 - ネットワーク接続の喪失 - システム電源の喪失 - 物理ホストのソフトウェアの問題 - ネットワーク到達可能性に影響する、物理ホスト上のハードウェアの問題

引用元: インスタンスのステータスチェック

このケースではこちらでできることは基本的にはない認識です。

インスタンスステータスチェック

ユーザー側で対処する必要のある障害

ユーザーが関与して修復する必要のある問題が検出されます。 インスタンスステータスチェックが失敗した場合は通常、自分自身で (たとえば、インスタンスを再起動する、インスタンス設定を変更するなどによって) 問題に対処する必要があります。 インスタンスステータスチェックの失敗の原因となる問題の例を次に示します。 - 失敗したシステムステータスチェック - 正しくないネットワークまたは起動設定 - メモリの枯渇 - 破損したファイルシステム - 互換性のないカーネル

引用元: インスタンスのステータスチェック

インスタンスステータスチェックの失敗時は、必ずしも自動再起動することが望ましいわけではないのですが、今回のケースでは、自動再起動を設定することを選択しました。

実際の設定

CloudFormation で設定しました

システムステータス対応

AutoRecoveryの設定をするテンプレートです

AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  InstanceId:
    Type: AWS::EC2::Instance::Id
    Description: InstanceId
Resources:
  AutoRecoverySystemAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Sub "system-failed-autorecovery-${InstanceId}"
      Namespace: AWS/EC2
      MetricName: StatusCheckFailed_System
      Statistic: Minimum
      Period: 60
      EvaluationPeriods: 2
      ComparisonOperator: GreaterThanThreshold
      Threshold: 0
      AlarmActions:
        - !Sub "arn:aws:automate:${AWS::Region}:ec2:recover"
      Dimensions:
        - Name: InstanceId
          Value: !Ref InstanceId

インスタンスステータス対応

自動再起動するテンプレートです

AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  InstanceId:
    Type: AWS::EC2::Instance::Id
    Description: InstanceId
Resources:
  AutoRecoveryInstanceAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Sub "instance-failed-autoreboot-${InstanceId}"
      Namespace: AWS/EC2
      MetricName: StatusCheckFailed_Instance
      Statistic: Minimum
      Period: 60
      EvaluationPeriods: 3
      ComparisonOperator: GreaterThanThreshold
      Threshold: 0
      AlarmActions:
        - !Sub "arn:aws:swf:${AWS::Region}:${AWS::AccountId}:action/actions/AWS_EC2.InstanceId.Reboot/1.0"
      Dimensions:
        - Name: InstanceId
          Value: !Ref InstanceId

設定されていないインスタンスを抽出する

コマンドは zsh / bash で動作確認しました

システムステータスのAutoRecovery

システムステータスのAutoRecovery設定済みのインスタンスの一覧

region=ap-northeast-1
aws cloudwatch describe-alarms --region $region \
  | jq -r '
  .MetricAlarms[]
  |select(.MetricName == "StatusCheckFailed_System")
  |select(.AlarmActions[] | . == "arn:aws:automate:'$region':ec2:recover")
  |.Dimensions[]
  |select(.Name == "InstanceId").Value'

AutoScalingに属しない status:running のインスタンスのうち、AutoRecovery が適用されていないインスタンスの一覧の抽出コマンド

region=ap-northeast-1
aws ec2 describe-instances --region $region \
  --filters 'Name=instance-state-name,Values=running' \
  --query 'Reservations[*].Instances[*].InstanceId' \
  --output text \
  | grep -vxFf <(echo i-xxxxxxxxxxxxxxxxx; \
    aws autoscaling describe-auto-scaling-instances --region $region \
    --query 'AutoScalingInstances[*].InstanceId' | jq -r '.[]') \
  | grep -vxFf <(echo i-xxxxxxxxxxxxxxxxx; \
    aws cloudwatch describe-alarms --region $region \
    | jq -r '
      .MetricAlarms[]
      |select(.MetricName == "StatusCheckFailed_System")
      |select(.AlarmActions[] | . == "arn:aws:automate:'$region':ec2:recover")
      |.Dimensions[]
      |select(.Name == "InstanceId").Value' \
  )

(途中の i-xxxxxxxxxxxxxxxxx は、grep によるフィルタ時に、フィルタ条件が空になり、全件マッチするのを防ぐための条件です)

インスタンスステータスの自動再起動

インスタンスステータスフェイル時に再起動する設定がされているインスタンスの一覧

region=ap-northeast-1
account_id=$(aws sts get-caller-identity --query Account --output text)
aws cloudwatch describe-alarms --region $region | \
  jq -r '
    .MetricAlarms[]
    |select(.MetricName == "StatusCheckFailed_Instance")
    |select(.AlarmActions[] | . == "arn:aws:swf:'$region':'$account_id':action/actions/AWS_EC2.InstanceId.Reboot/1.0")
    |.Dimensions[]
    |select(.Name == "InstanceId").Value'

AutoScalingに属しない status:running のインスタンスのうち、再起動が設定されていないインスタンスの一覧の抽出コマンド

region=ap-northeast-1
account_id=$(aws sts get-caller-identity --query Account --output text)
aws ec2 describe-instances --region $region \
  --filters 'Name=instance-state-name,Values=running' \
  --query 'Reservations[*].Instances[*].InstanceId' \
  --output text \
  | grep -vxFf <(echo i-xxxxxxxxxxxxxxxxx; \
    aws autoscaling describe-auto-scaling-instances --region $region \
    --query 'AutoScalingInstances[*].InstanceId' | jq -r '.[]') \
  | grep -vxFf <(echo i-xxxxxxxxxxxxxxxxx; \
    aws cloudwatch describe-alarms --region $region \
    | jq -r '
      .MetricAlarms[]
      |select(.MetricName == "StatusCheckFailed_Instance")
      |select(.AlarmActions[] | . == "arn:aws:swf:'$region':'$account_id':action/actions/AWS_EC2.InstanceId.Reboot/1.0")
      |.Dimensions[]
      |select(.Name == "InstanceId").Value')

(途中の i-xxxxxxxxxxxxxxxxx は、grep によるフィルタ時に、フィルタ条件が空になり、全件マッチするのを防ぐための条件です)

終わりに

仕組みに任せれることは任せてしまえると楽ですね