【ログが混ざる!?】CloudWatchモニタリングスクリプトのインスタンスIDキャッシュに気をつけようの話

2017.10.31

EC2のメモリやディスク容量のモニタリング、皆さんどうされていますでしょうか?

この用途には、Amazonから提供されているCloudWatchモニタリングスクリプトが利用でき、私も非常に便利に利用していたのですが、先日、この設定をしたAMIから別インスタンスを作成した際に、インスタンスIDのキャッシュにハマったので、その経緯と解決策をお話します。

ほな、いってみよ。

CloudWatchモニタリングスクリプトとは?

CloudWatchの標準機能では、CPU利用率やディスクIO等の取得が可能ですが、メモリ使用量やディスク容量のモニタリングには、独自のカスタムメトリクスを利用する必要があります。

それには、Amazon標準で用意されているAmazon CloudWatch Monitoring Scripts for Linuxを利用することができます。

公式ドキュメント:Amazon EC2 Linux インスタンスのメモリとディスクのメトリクスのモニタリング

はっきり言って導入と利用方法は、むっちゃ簡単。素晴らしい。

これを利用することで、メモリ使用量とディスク使用量(ここでは/varディレクトリ配下)を、下記コマンド一発で、CloudWatchに連携できます。

./mon-put-instance-data.pl --mem-util --mem-used-incl-cache-buff --mem-used --mem-avail --disk-space-util --disk-path=/var

後はこれを適当なシェルにして、cronで1分毎に自動起動するようにしておけば、CloudWatchアラームからのSNS連携によるメール通知など、なんでもできるので非常に便利。

aws cli、cloudwatchのput-metric-dataなどを利用して、独自のシェルで実装する方法もありますが、各種コマンドの結果を編集したり、インスタンスIDやメトリクス名を設定したり、手間がそれなりにかかるので、これらの値の取得が標準スクリプトとして用意されているのはありがたいです。

インスタンスIDが重複して、ログが混ざってしまった

先日、このモニタリングスクリプトを設定したインスタンスからAMIを作成し、そこから別インスタンスを起動しました。起動は普通に成功しているのに、なぜかいつまでたってもCloudWatchログが新インスタンスから転送されてこない。

instanc-id-not

既存のインスタンスのログはあるのに、新インスタンスのログが来ない。awslogsのログやステータスを確認しても正常に動作している。

「なんでやろ〜」

とあれこれ見ているうちに、既存インスタンスのログストリームに新インスタンスのログが混ざっていることを見つけた次第です。

インスタンスIDが同一になった原因

公式ドキュメント(Amazon EC2 Linux インスタンスのメモリとディスクのメトリクスのモニタリング)の下の方に記載がありました。

トラブルシューティング

CloudWatchClient.pm モジュールは、インスタンスのメタデータをローカルでキャッシュします。モニタリングスクリプトを実行しているインスタンスから AMI を作成すると、キャッシュ TTL (デフォルト: 6 時間、Auto Scaling グループでは 24 時間) 以内にこの AMI から起動したすべてのインスタンスは、元のインスタンスのインスタンス ID を使用してメトリクスを出力します。キャッシュ TTL 期間が経過した後は、スクリプトは新しいデータを取得し、モニタリングスクリプトは現在のインスタンスのインスタンス ID を使用します。これをすぐに修正するには、次のコマンドを使用してキャッシュされたデータを削除します。

引用:Amazon EC2 Linux インスタンスのメモリとディスクのメトリクスのモニタリング - Amazon Elastic Compute Cloud

このように、スクリプトの中でインスタンスIDをキャッシュしており、これは6時間有効とのことなので、インスタンスIDが変わったときなどは、以下のコマンドでキャッシュをクリアする必要があります。

rm /var/tmp/aws-mon/instance-id

perlスクリプトの中の該当箇所を抜粋しています。ファイルのタイムスタンプと比較して、インスタンスIDをキャッシュしてます。

our $meta_data_ttl = 21600; # 6 hours
our $max_meta_data_ttl = 86400; # 1 day

sub read_meta_data
{
  my $resource = shift;
  my $default_ttl = shift;
  
  my $location = $ENV{'AWS_EC2CW_META_DATA'};
  if (!defined($location) || length($location) == 0) { 	 
    $location = $meta_data_loc if ($meta_data_loc); 	 
  }
  my $meta_data_ttl = $ENV{'AWS_EC2CW_META_DATA_TTL'};
  $meta_data_ttl = $default_ttl if (!defined($meta_data_ttl));
  
  my $data_value;
  if ($location)
  {
    my $filename = $location.$resource;
    if (-d $filename) {
      $data_value = `/bin/ls $filename`;
      chomp($data_value);
    } elsif (-e $filename) {
      my $updated = (stat($filename))[9];
      my $file_age = time() - $updated;
      if ($file_age < $meta_data_ttl)
      {
        open MDATA, "$filename";
        while(my $line = <MDATA>) {
          $data_value .= $line;
        }
        close MDATA;
        chomp $data_value;
      }
    }
  }
  
  return $data_value;
}

自前でシェルを作っている場合なんかは、恐らくインスタンスIDは都度取得して書いている人がほとんどだと思うのでこういう問題にはならなさそうですが、便利なツールを利用するときはドキュメントは良く読んでから使うようにしましょう、という教訓でした。

解決策

解決策として、上記モニタリングスクリプトのキャッシュ期間を0に変更するなどの方法もありますが、あまりお作法的に良い方法とも思えません。

EC2のユーザーデータで、上記で紹介したキャッシュファイルの削除コマンドを仕込んでおくのが、良いかと思います。

参考:Linux インスタンスでの起動時のコマンドの実行 - Amazon Elastic Compute Cloud

以上、同様のことでハマる人が出た時の参考になればと思います。

それでは、今日はこのへんで。濱田でした。