ALBのアクセスログにアベイアビリティゾーン情報を追加してみた

はじめに

AWSチームのすずきです。

ELB (Application Load Balancer:以下ALB) アクセスログは、接続元となるクライアント(ブラウザ等)と、接続先のターゲット(httpd等が稼働するEC2)のIPアドレスを確認することが可能です。

今回、アクセスログに含まれない ALBノードのIPアドレス、アベイアビリティゾーン単位での解析を実施する必要が生じたため、 アクセスログのファイル名よりALBノードのIPアドレス情報を取得し、ネットワークインターフェイス情報からアベイアビリティゾーンを求める機会がありましたので紹介します。

アクセスログ仕様

ALBのアクセスログの仕様は、2019年9月時点の公式ドキュメントを参考にしました。

ログファイル

ログファイル名に含まれる「ip-address」より、ALBノードのIPアドレスを求めます。

フィールド 説明
bucket S3 バケットの名前。
プレフィックス バケットのプレフィックス (論理階層)
aws-account-id 所有者の AWS アカウント ID。
リージョン ロードバランサーおよび S3 バケットのリージョン。
yyyy/mm/dd ログが配信された日付。
load-balancer-id ロードバランサーのリソース ID
end-time ログ作成の間隔が終了した日時
ip-address リクエストを処理したロードバランサーノードの IP アドレス。内部ロードバランサーの場合、プライベート IP アドレスです。
random-string システムによって生成されたランダム文字列。

ログファイル名サンプル

bucket[/prefix]/AWSLogs/aws-account-id/elasticloadbalancing/region/yyyy/mm/dd/aws-account-id_elasticloadbalancing_region_load-balancer-id_end-time_ip-address_random-string.log.gz

アクセスログファイル

フィールド 説明
type リクエストまたは接続のタイプ。
timestamp ロードバランサーがクライアントに対してレスポンスを生成した時刻
elb ロードバランサーのリソース ID。
client:port リクエストを送信したクライアントの IP アドレスとポート。
target:port このリクエストを処理したターゲットの IP アドレスとポート。
request_processing_time ロードバランサーがリクエストを受け取った時点からターゲットに送信するまでの合計経過時間
target_processing_time ロードバランサーがターゲットにリクエストを送信した時点から、そのターゲットが応答ヘッダーの送信を開始した時点までの合計経過時間
response_processing_time ロードバランサーがターゲットから応答ヘッダーを受け取った時点から、クライアントへの応答の送信を開始した時点までの合計経過時間
elb_status_code ロードバランサーからの応答のステータスコード。
target_status_code ターゲットから応答のステータスコード。
received_bytes クライアント (リクエスタ) から受け取ったリクエストのサイズ
sent_bytes クライアント (リクエスタ) に返される応答のサイズ
request クライアントからのリクエスト
user_agent リクエスト元のクライアントを特定する User-Agent 文字列
ssl_cipher SSL 暗号
ssl_protocol SSL プロトコル
target_group_arn ターゲットグループの Amazon リソースネーム
trace_id X-Amzn-Trace-Id ヘッダーのコンテンツ
domain_name TLS ハンドシェイク中にクライアントから提供される SNI ドメイン
chosen_cert_arn クライアントに提示される証明書の ARN
matched_rule_priority リクエストに一致したルールの優先度の値
request_creation_time ロードバランサーがクライアントからリクエストを受け取った時刻
actions_executed リクエストの処理時に実行されるアクション
redirect_url HTTP レスポンスのロケーションヘッダーのリダイレクトターゲットの URL
error_reason エラー理由コード

CLI

CLIを利用して、ALBノードのIPアドレスがアクセスログファイル名に含まれる事、 ネットワークインターフェイス情報より、アベイアビリティゾーン、プライベートIP情報が取得出来る事を確認しました。

hosts

  • ALBのDNS名より、正引きのIPアドレスを確認します。
$ host public-xx.ap-northeast-1.elb.amazonaws.com
public-xxxx.ap-northeast-1.elb.amazonaws.com has address 13.113.xx.63
public-xxxx.ap-northeast-1.elb.amazonaws.com has address 54.199.xx.110

アクセスログファイル

  • ALBのログ出力先のS3、ログファイル名にALBのIPアドレスが含まれる事を確認します。
$ aws s3 ls s3://aa/AWSLogs/xx/elasticloadbalancing/ap-northeast-1/2019/09/07/ | awk '{print $4}'
xx_elasticloadbalancing_ap-northeast-1_app.pub.yy_20190907T0745Z_13.113.xx.63_zzz.log.gz
xx_elasticloadbalancing_ap-northeast-1_app.pub.yy_20190907T0745Z_54.199.xx.110_zzz.log.gz

ノードのIPアドレス

  • アクセスログのキー(ファイル名)を「_」区切りの配列とし、末尾から2つ目の要素からALBノードのIPアドレスが抽出出来る事を確認します。
$  aws s3 ls s3://aa/pub/AWSLogs/xxx/elasticloadbalancing/ap-northeast-1/2019/09/07/ | awk '{print $4}' |  awk -F '_' '{print $(NF-1)}'
13.113.xx.63
54.199.xx.110
ノードのアベイアビリティゾーン
  • EC2のAPI「describe-network-interfaces」を利用し、ALBノードのIPアドレスに一致するネットワークインターフェスの情報を確認します。
$ aws ec2 describe-network-interfaces --filters 'Name=addresses.association.public-ip,Values=13.113.xx.63' --query 'NetworkInterfaces[*].[NetworkInterfaceId,AvailabilityZone,PrivateIpAddress,Association.PublicIp]'
[
    [
        "eni-xxx",
        "ap-northeast-1a",
        "172.31.xx.223",
        "13.113.xx.63"
    ]
]
  • 「Internal」のALBの場合、フィルタの対象を「private-ip-address」とします。
AvailabilityZone
$ aws ec2 describe-network-interfaces --filters 'Name=addresses.private-ip-address,Values=172.31.xx.29' --query 'NetworkInterfaces[*].[NetworkInterfaceId,AvailabilityZone,PrivateIpAddress]'
[
    [
        "eni-xxx",
        "ap-northeast-1a",
        "172.31.xx.29"
    ]
]

Python(Boto3)

Lambda (Python) で実装する前提で、Boto3を利用してアベイアビリティゾーンを求めてみました。


    ec2 = boto3.client('ec2')
    
    s3key='prefix/AWSLogs/xx_elasticloadbalancing_ap-northeast-1_app.pub.yy_20190907T0745Z_13.113.xx.63_zzz.log.gz'

    # elb ip
    elb_listener_ip = s3key.split('_')[-2]

    # public elb
    responce = ec2.describe_network_interfaces(
        Filters=[{'Name':'association.public-ip','Values':[elb_listener_ip]}]
    )

    # internal elb
    if not responce["NetworkInterfaces"]:
        responce = ec2.describe_network_interfaces(
        Filters=[{'Name':'private-ip-address','Values':[elb_listener_ip]}]
    ) 

    availabilityzone='none'
    privateipaddress='none'

    if responce["NetworkInterfaces"]:
        if responce["NetworkInterfaces"][0]:
            if responce["NetworkInterfaces"][0]["AvailabilityZone"]:
                availabilityzone = responce["NetworkInterfaces"][0]["AvailabilityZone"]
            if responce["NetworkInterfaces"][0]["PrivateIpAddress"]:
                privateipaddress = responce["NetworkInterfaces"][0]["PrivateIpAddress"]

    print('AvailabilityZone: ' + availabilityzone)
    print('PrivateIpAddress: '+ privateipaddress)


実行結果

AvailabilityZone: ap-northeast-1a
PrivateIpAddress: 172.31.xx.223

ALBのアクセスログのファイル名(キー)より、ノードのIPアドレスとアベイアビリティゾーンを確認できました。

まとめ

アクセスログに含まれないALBノードの情報をLambda(Python)を利用して取得できた事で、ログ項目に追加できる目処がつきました。

アベイアビリティゾーン別の解析以外にも、ALBを利用するシステムで問題が発生し、クライアントやターゲットのログとALBのログの突き合わせが必要となった場合、 ALBノードのIPアドレス情報が役立つと考えられます。

ログカラムを追加したアクセスログ、Kinesis Firehose などを利用してS3に保存する事で、Amazon Athena で解析する事が可能です。 アクセスログの追加したALBノードのIPアドレス、アベイアビリティゾーンに対応した解析の手順は追って紹介させて頂きたいと思います。

ELBとCloudFrontのアクセスログをサーバレスに集約させてみた

尚、今回紹介した方法では、ALBノードのスケールインや交換などがあった場合、削除されたノードのネットワークインターフェイス情報は取得できないため、 古いアクセスログでは結果が得られない可能性があります。

また、ALBのアクセスログファイル名に含まれる「_」、区切り文字の扱いについてはドキュメント上は明文化されておらず、今後変化する可能性もあります。 アベイアビリティゾーンやIPアドレス情報の欠落が許容できない用途には、今回の手法は適さない可能性がありますのでご留意ください。