こちらのつづき。
エラーログ?アイツはもう消した!
1 時間ごとにファイルを生成して S3 へ転送するということは、インスタンスが上手いタイミングでスケールイン対象となった場合に、場合によっては最大で直近 1 時間のログが消失してしまう可能性があるということだ。
たとえば、何らかのエラーでステータスチェックに失敗してインスタンス廃棄となった際に、ちょうどエラーが起こった時間帯のログが消えてしまうということが起こりうる。
これを防ぐためには、インスタンス廃棄の直前にログが溜まったバッファを flush して S3 へ転送しておく必要がある。
Fluentd のバッファを Flush
Fluentd におけるバッファの Flush 方法は HTTP RPC (API) を使う方法と UNIX signal を使う方法が存在する。Linux の場合はシグナルを使ったほうがシンプルだろう。
Auto Scaling のライフサイクルフック
また、Auto Scaling においてインスタンス廃棄前に任意の処理を挟み込む仕組みとして、ライフサイクルフック が存在する。
これをつかって Lambda 関数を起動したり、SNS 通知、SQS にエンキューといった処理を行うことができる。
要するに
ライフサイクルフックでキックされる Lambda を使って、廃棄対象のインスタンスで Fluentd のマスタープロセスにシグナル (SIGUSR1
) を送ってやれば良い。
AWS Systems Manager Run Command を使った外部からの任意コマンド実行
シグナルを送るなら、Lambda から SSH でやるか、と思いがち。
しかし、そんな面倒くさいことしなくてもいいんです。そう、AWS Systems Manager (以下、SSM) のRun Command ならね。
SSM とは、EC2 やオンプレミスのサーバー郡をいい感じに一元管理できる素敵なサービス。 そのうちの 1 機能に Run Command があり、SSM エージェント経由で特定のサーバーにおいて外部から SSH 接続なしで任意のコマンドを実行できるのだ。
SSM エージェントの準備
最近の Amazon Linux 2 ならば、デフォルトでインストールされているはず。 ない場合は、こちらを参考にインストール。
また、このエージェントをまともに動作させるためには、AmazonEC2RoleforSSM
という SSM 用のポリシーをアタッチした IAM ロールか IAM ユーザーが必要になる。
EC2 の場合はインスタンスに IAM ロールを割り当てるのが良いだろう。
こちらの記事で作成したような Auto Scaling 用の起動テンプレートの場合は、高度な詳細 - IAM インスタンスプロフィール
(ひどい翻訳w) に設定項目があるのでロールを指定。
Auto Scaling ライフサイクルフック用の Lambda 関数実装
Lambda 関数から SSM Run Command をキックしてインスタンス内でコマンド 1 つ実行すれば終了。
というと簡単なのだが、ログファイルがでかくて S3 への転送に時間かかる場合もあるじゃん?(執筆時点で) Lambda 関数の最大実行時間は 5 分じゃん?
=> AWS Step Functions を使って S3 転送が確実に終わるであろう適当な時間だけ待ちましょう。
※ちなみに、執筆直後に 最大 15 分まで延長された模様……
コード全体
hiraro/asg-lifecycle-hook-for-fluentd
シグナル送信による Fluentd バッファのフラッシュ
kill
コマンドで直接送ってもいいけど、systemctl kill -s SIGUSR1 td-agent
でやれば簡単。
import boto3 ssm = boto3.client('ssm') response = ssm.send_command( InstanceIds=["EC2InstanceId"], DocumentName="AWS-RunShellScript", TimeoutSeconds=300, Parameters={ "commands": [ "systemctl kill -s SIGUSR1 td-agent" ], "executionTimeout": ["300"] }, )
complete-lifecycle-action
タイムアウト期間が終了する前に終了した場合、次の状態に移るには、complete-lifecycle-action コマンド、または CompleteLifecycleAction オペレーションを使用します。
フック処理が終わった後に、AutoScaling へ明示的に処理終了したことを伝えてあげる必要がある。 これをやらないと、フック処理のタイムアウト (デフォルト値: 1 時間) までインスタンスがターミネートされずに無駄に課金されてしまう。
import boto3 client = boto3.client('autoscaling') response = client.complete_lifecycle_action( LifecycleHookName="LifecycleHookName", AutoScalingGroupName="AutoScalingGroupName", LifecycleActionToken="LifecycleActionToken", InstanceId="EC2InstanceId", LifecycleActionResult='ABANDON' )
Auto Scaling ライフサイクル設定
ちまちま設定。
EC2 - Auto Scaling グループ - ライフサイクルフック
にてフック作成。
CloudWatch イベント - ルール
にてルール作成。
ちなみに、ここで設定している Lambda 関数では、Step Function をキックするだけ。
import boto3 def lambda_handler(event, context): client = boto3.client('stepfunctions') response = client.start_execution( stateMachineArn='STEP_FUNC_ARN', input=json.dumps(event["detail"]) )
引数の event
には、下記のようなオブジェクトが渡されてきた。
{ "account": "xxxxxxxxxxxxxxxx", "detail": { "AutoScalingGroupName": "xxxxxxxxxxxxxxxxxxx", "EC2InstanceId": "i-xxxxxxxxxxxxxxxx, "LifecycleActionToken": "xxxxxxxxx-xxxxx-xxxx-xxxx-xxxxxxxxxxxx", "LifecycleHookName": "on-terminate", "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING" }, "detail-type": "EC2 Instance-terminate Lifecycle Action", "id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxx", "region": "ap-northeast-1", "resources": [ "arn:aws:autoscaling:ap-northeast-1: xxxxxxxx:autoScalingGroup:xxxxxxxxxxxxxxxxxxxxxxxxxxx:autoScalingGroupName/TEST-wordpress-asg" ], "source": "aws.autoscaling", "time": "2018-10-10T09: 25: 04Z", "version": "0" }
動作確認
実際に負荷をかけてスケールアウトからのスケールインさせて確認しても良いが、ダルいので、オートスケーリンググループの下記を編集し、スケールインを強制的に発生させて確認したほうが楽。
- 希望するキャパシティ
- 最小
- 最大
Step Function が成功して、S3 に該当インスタンスのログが保存されていれば成功。
おわりに
ライフサイクルフックを使用して Fluentd のバッファを Flush する、その他の方法
参考: Amazon EC2 Auto Scaling ライフサイクルフック – Amazon EC2 Auto Scaling (日本語)
冒頭でも少し触れたとおり、スケールイン時には Lambda を起動するだけでなく、SQS にライフサイクルイベントをエンキューすることもできる。
よって、オートスケーリンググループ内の各インスタンス内で、この SQS キューのポーリング、イベントのハンドリングを行うプログラムを動作させれば、同様の事ができるはず。
ログ転送できたけど、どうすんの?
Amazon S3 Select なんてつかってみるのも、また一興。