前提
EC2 + RDS + ElastiCacheを利用した一般的なサービス環境
webサーバはLaravel on Dockerで、オートスケールを利用し動的に台数が変化する
目的
マルチAZ構成にしている場合、EC2(AZ-A)とRDS/ElastiCache(AZ-C)の別AZ間の通信には同一VPCであっても通信料金がかかる
そのためAPCUを利用し、アクセス頻度が高いデータはローカルキャッシュから読み込むことで通信費用を抑える
問題点
・APCUはプロセス間でキャッシュデータを共有しないので、php-fpm(nginxからのリクエスト処理)経由で作られたデータとartisanコマンドで操作可能なデータは異なる
・あくまでローカルキャッシュなのでキャッシュクリアを行いたい場合は全webサーバに対してクリア処理を行う必要がある
解決法
・nginxコンテナからlocalhost向けでリクエストを送り、php-fpmプロセスに対して処理を行う
・AWSSystemManagerのRunCommand機能を利用し、タグ指定でオートスケールインスタンス全台に対してクリア処理を行う
構成図
SSM実行に関連する情報
実行元サーバ情報
php処理
SDKを利用
https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-ssm-2014-11-06.html#sendcommand
AWS::createClient('ssm')->sendCommand([
'Targets' => [
[
'Key' => 'tag:aws:autoscaling:groupName',
'Values' => ['hogehoge']
]
],
'Parameters' => [
'commands' => [
"docker exec -t nginxコンテナ curl 'localhost/apcu.php'",
],
],
'DocumentName' => 'AWS-RunShellScript',
'ServiceRoleArn' => 'arn:aws:iam::123456789:role/ssm-runcommand-service-role',
'NotificationConfig' => [
'NotificationArn' => 'arn:aws:sns:ap-northeast-1:123456789:ssm-runcommand-topic',
'NotificationEvents' => ['All'],
'NotificationType' => 'Command',
],
]);
インスタンスロール
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"ssm:SendCommand",
"iam:PassRole",
"sns:Publish"
],
"Resource": "*"
}
]
}
webサーバ
インスタンスロール
・AmazonEC2RoleforSSM
進捗通知Lambda
SSMエージェントが起動されてなかったり、権限の問題でタグ検索がうまくいかなかったりで、ターゲット数が0の場合でもRunCommandは成功扱いになってしまうので、一度Lambdaを通して成否チェックを行いslackに結果を通知する
import boto3
import json
def post_slack(message):
send_data = {
"text": message,
}
send_text = json.dumps(send_data)
request = urllib.request.Request(
'https://hooks.slack.com/HOGEHOGE',
data = send_text.encode('utf-8'),
method = "POST"
)
with urllib.request.urlopen(request) as response:
response_body = response.read().decode('utf-8')
def lambda_handler(event, context):
message = json.loads(event['Records'][0]['Sns']['Message']);
status = message['status'];
if status == 'InProgress':
# 実行中は何もしない
return;
# 実行台数0台でも成功になってしまうので細かく精査
result = boto3.client('ssm').list_commands(
CommandId=message['commandId']
);
targetCount = result['Commands'][0]['TargetCount'];
targetParam = result['Commands'][0]['Targets'][0];
sendMessage = '<!here> \nkey: ' + targetParam['Key'] + '\nvalue: ' + ', '.join(map(str, targetParam['Values']));
if status == 'Success':
if targetParam['Key'] == 'tag:aws:autoscaling:groupName':
asData = boto3.client('autoscaling').describe_auto_scaling_groups(
AutoScalingGroupNames=[
targetParam['Values'][0]
]
);
if targetCount == asData['AutoScalingGroups'][0]['DesiredCapacity']:
sendMessage += '\nSSMのRunCommand実行に成功しました';
else:
sendMessage += '\nSSMのRunCommand実行台数とAS希望台数が合致しません、指定が正しいか確認してください';
else:
if targetCount == 0:
sendMessage += '\nSSMのRunCommand実行の対象インスタンスが0台です、指定が正しいか確認してください';
else:
sendMessage += '\nSSMのRunCommand実行に成功しました';
else:
sendMessage += '\nSSMのRunCommand実行に失敗しました';
post_slack(sendMessage);
実行ロール
・AutoScalingReadOnlyAccess
・AmazonSSMReadOnlyAccess