LoginSignup
0
0

More than 1 year has passed since last update.

AWS環境でAPCUを利用しDataTransfer料金を下げる

Posted at

前提

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機能を利用し、タグ指定でオートスケールインスタンス全台に対してクリア処理を行う

構成図

APCU操作フロー図-ページ1のコピー.jpg

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0