NLBのアクセスログがCloudWatch Logsに直接転送可能になりました

NLBのアクセスログがCloudWatch Logsに直接転送可能になりました

NLBのログがVended Logsに対応し、CloudWatch LogsやFirehoseへの直接配信が可能に。構造化されたJSONログによるクエリの簡素化や、Live Tailを用いたリアルタイムなログ調査が実現します。
2025.11.24

2025年11月12日、Network Load Balancer (NLB) のアクセスログにアップデートがありました。

これまでNLBのアクセスログといえば「S3に出力して、数分待ってからAthenaで分析」というのが定石でした。今回のアップデートにより、CloudFrontの標準アクセスログv2と同様、CloudWatch Logs、S3、Kinesis Data Firehose への直接配信がサポートされました。

Amazon CloudWatch supports logs for Network Load Balancer access logs | AWS What's New

これにより、CloudWatch Logs の Live Tail を使ったリアルタイムなログ確認や、CloudWatch Logs Insights を用いた高速なクエリ処理、また、JSON形式による構造化ログの利用などが可能になります。

今回はこの新機能(Logs Delivery API)を使って、CloudWatch Logs (JSON/TSV)、S3、Kinesis Data Firehose、4つの配信先にログを配信するテスト環境をCloudFormationで構築し、その挙動と試す機会がありましたので、紹介します。

検証環境

デリバリーソースごとの最大配信数は10個までを設定できる仕様を利用し、以下のフォーマットを同時に出力して検証しました。

  1. CloudWatch Logs (JSON)
    • 推奨設定。構造化ログとして出力されるため、CloudWatch Logs Insights で検索する際に、パースが省略できます。
  2. CloudWatch Logs (Plain)
    • 従来のTSV(タブ区切り)形式です。既存のログ監視基盤を変更せずに移行したい場合に利用します。
  3. Amazon S3 (Plain)
    • 従来のアクセスログ互換形式(GZIP圧縮)です。
  4. Amazon S3 (JSON)
    • 新形式。JSONL(改行区切りJSON)形式で保存されます。
  5. Amazon S3 (W3C)
    • 新形式。W3C Extended Log File Format です。
  6. Amazon S3 (Parquet)
    • 新形式。Apache Parquet 形式で保存されます。Athena でのスキャン料金削減やパフォーマンス向上が期待できます。
  7. Amazon Kinesis Data Firehose
    • Splunk や Datadog などのサードパーティ製品への転送や、データレイク格納の前処理(ETL)に使用します。

構成図

NLBアクセスログ検証構成図

CloudFormationテンプレート

検証に使用したテンプレートです。
このテンプレートをデプロイすると、NLBとテスト用EC2に加え、上記7パターンのログ配信設定が自動的にプロビジョニングされます。

AWS::Logs::Delivery は、並列実行に起因するエラーを回避するため、DependsOn でシーケンシャル処理にしています。

  TSVDelivery:
    Type: AWS::Logs::Delivery
    DependsOn: JSONDelivery
    Properties:
      DeliverySourceName: !Ref DeliverySource
      DeliveryDestinationArn: !GetAtt TSVDeliveryDestination.Arn
  S3DeliveryJSON:
    Type: AWS::Logs::Delivery
    DependsOn: TSVDelivery
    Properties:
      DeliverySourceName: !Ref DeliverySource
      DeliveryDestinationArn: !GetAtt S3DeliveryDestinationJSON.Arn

デプロイタイムライン

検証環境作成テンプレート (クリックして展開)
  • TLSリスナーを使用するため、事前にACMで証明書を作成、パラメータで証明書のARNを指定してください。
  • Default VPC環境での動作を想定しています。

AWSTemplateFormatVersion: '2010-09-09'
Description: 'NLB with all logging destinations (CloudWatch JSON/TSV, S3 x4 formats, Firehose) - Default VPC'

Parameters:
  CertificateArn:
    Type: String
    Description: ARN of the ACM Certificate for HTTPS listener

  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: VPC ID for resources

  Subnet1:
    Type: AWS::EC2::Subnet::Id
    Description: First subnet for NLB

  Subnet2:
    Type: AWS::EC2::Subnet::Id
    Description: Second subnet for NLB

Resources:
  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow HTTP traffic
      VpcId: !Ref VpcId
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0

  EC2Role:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

  EC2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref EC2Role

  TestEC2:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Sub '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64}}'
      InstanceType: t4g.nano
      IamInstanceProfile: !Ref EC2InstanceProfile
      SecurityGroupIds:
        - !Ref EC2SecurityGroup
      UserData:
        Fn::Base64: |
          #!/bin/bash
          yum update -y
          yum install -y httpd
          systemctl start httpd
          systemctl enable httpd
          echo "<h1>NLB Logging Test - $(hostname)</h1>" > /var/www/html/index.html

  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub '${AWS::StackName}-tg'
      Port: 80
      Protocol: TCP
      VpcId: !Ref VpcId
      HealthCheckEnabled: true
      HealthCheckProtocol: TCP
      Targets:
        - Id: !Ref TestEC2
          Port: 80

  NetworkLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Sub '${AWS::StackName}-nlb'
      Type: network
      Scheme: internet-facing
      Subnets:
        - !Ref Subnet1
        - !Ref Subnet2

  TLSListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref TargetGroup
      LoadBalancerArn: !Ref NetworkLoadBalancer
      Port: 443
      Protocol: TLS
      Certificates:
        - CertificateArn: !Ref CertificateArn
      SslPolicy: ELBSecurityPolicy-TLS13-1-2-2021-06

  DeliverySource:
    Type: AWS::Logs::DeliverySource
    Properties:
      Name: !Sub '${AWS::StackName}-source'
      ResourceArn: !Ref NetworkLoadBalancer
      LogType: NLB_ACCESS_LOGS

  NLBLogGroupPolicy:
    Type: AWS::Logs::ResourcePolicy
    Properties:
      PolicyName: !Sub '${AWS::StackName}-policy'
      PolicyDocument: !Sub |
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Sid": "AllowLogDelivery",
              "Effect": "Allow",
              "Principal": {
                "Service": "delivery.logs.amazonaws.com"
              },
              "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
              ],
              "Resource": [
                "${JSONLogGroup.Arn}",
                "${TSVLogGroup.Arn}"
              ],
              "Condition": {
                "StringEquals": {
                  "aws:SourceAccount": "${AWS::AccountId}"
                }
              }
            }
          ]
        }

  # 1. CloudWatch Logs (JSON)
  JSONLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/aws/vendedlogs/nlb/${AWS::StackName}/json'
      RetentionInDays: 3

  JSONDeliveryDestination:
    Type: AWS::Logs::DeliveryDestination
    Properties:
      Name: !Sub '${AWS::StackName}-json'
      OutputFormat: json
      DestinationResourceArn: !GetAtt JSONLogGroup.Arn

  JSONDelivery:
    Type: AWS::Logs::Delivery
    Properties:
      DeliverySourceName: !Ref DeliverySource
      DeliveryDestinationArn: !GetAtt JSONDeliveryDestination.Arn

  # 2. CloudWatch Logs (TSV)
  TSVLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/aws/vendedlogs/nlb/${AWS::StackName}/tsv'
      RetentionInDays: 3

  TSVDeliveryDestination:
    Type: AWS::Logs::DeliveryDestination
    Properties:
      Name: !Sub '${AWS::StackName}-tsv'
      OutputFormat: plain
      DestinationResourceArn: !GetAtt TSVLogGroup.Arn

  TSVDelivery:
    Type: AWS::Logs::Delivery
    DependsOn: JSONDelivery
    Properties:
      DeliverySourceName: !Ref DeliverySource
      DeliveryDestinationArn: !GetAtt TSVDeliveryDestination.Arn

  S3LogBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'nlb-logs-${AWS::StackName}-${AWS::AccountId}'
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      LifecycleConfiguration:
        Rules:
          - Id: DeleteOldLogs
            Status: Enabled
            ExpirationInDays: 7

  S3LogBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3LogBucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: AWSLogDeliveryWrite
            Effect: Allow
            Principal:
              Service: delivery.logs.amazonaws.com
            Action: s3:PutObject
            Resource: !Sub '${S3LogBucket.Arn}/*'
            Condition:
              StringEquals:
                s3:x-amz-acl: bucket-owner-full-control
                aws:SourceAccount: !Ref AWS::AccountId
          - Sid: AWSLogDeliveryAclCheck
            Effect: Allow
            Principal:
              Service: delivery.logs.amazonaws.com
            Action: s3:GetBucketAcl
            Resource: !GetAtt S3LogBucket.Arn
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId

  # 3. S3 (JSON)
  S3DeliveryDestinationJSON:
    Type: AWS::Logs::DeliveryDestination
    DependsOn: S3LogBucketPolicy
    Properties:
      Name: !Sub '${AWS::StackName}-s3-json'
      OutputFormat: json
      DestinationResourceArn: !Sub '${S3LogBucket.Arn}/json'

  S3DeliveryJSON:
    Type: AWS::Logs::Delivery
    DependsOn: TSVDelivery
    Properties:
      DeliverySourceName: !Ref DeliverySource
      DeliveryDestinationArn: !GetAtt S3DeliveryDestinationJSON.Arn

  # 4. S3 (W3C)
  S3DeliveryDestinationW3C:
    Type: AWS::Logs::DeliveryDestination
    DependsOn: S3LogBucketPolicy
    Properties:
      Name: !Sub '${AWS::StackName}-s3-w3c'
      OutputFormat: w3c
      DestinationResourceArn: !Sub '${S3LogBucket.Arn}/w3c'

  S3DeliveryW3C:
    Type: AWS::Logs::Delivery
    DependsOn: S3DeliveryJSON
    Properties:
      DeliverySourceName: !Ref DeliverySource
      DeliveryDestinationArn: !GetAtt S3DeliveryDestinationW3C.Arn

  # 5. S3 (Parquet)
  S3DeliveryDestinationParquet:
    Type: AWS::Logs::DeliveryDestination
    DependsOn: S3LogBucketPolicy
    Properties:
      Name: !Sub '${AWS::StackName}-s3-parquet'
      OutputFormat: parquet
      DestinationResourceArn: !Sub '${S3LogBucket.Arn}/parquet'

  S3DeliveryParquet:
    Type: AWS::Logs::Delivery
    DependsOn: S3DeliveryW3C
    Properties:
      DeliverySourceName: !Ref DeliverySource
      DeliveryDestinationArn: !GetAtt S3DeliveryDestinationParquet.Arn

  # 6. S3 (Plain)
  S3DeliveryDestinationPlain:
    Type: AWS::Logs::DeliveryDestination
    DependsOn: S3LogBucketPolicy
    Properties:
      Name: !Sub '${AWS::StackName}-s3-plain'
      OutputFormat: plain
      DestinationResourceArn: !Sub '${S3LogBucket.Arn}/plain'

  S3DeliveryPlain:
    Type: AWS::Logs::Delivery
    DependsOn: S3DeliveryParquet
    Properties:
      DeliverySourceName: !Ref DeliverySource
      DeliveryDestinationArn: !GetAtt S3DeliveryDestinationPlain.Arn

  # 7. Firehose
  FirehoseRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: firehose.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: FirehoseS3Policy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:GetObject
                  - s3:ListBucket
                Resource:
                  - !GetAtt S3LogBucket.Arn
                  - !Sub '${S3LogBucket.Arn}/*'
              - Effect: Allow
                Action:
                  - logs:PutLogEvents
                Resource: '*'

  FirehoseLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/aws/kinesisfirehose/${AWS::StackName}'
      RetentionInDays: 1

  FirehoseLogStream:
    Type: AWS::Logs::LogStream
    Properties:
      LogGroupName: !Ref FirehoseLogGroup
      LogStreamName: S3Delivery

  DeliveryStream:
    Type: AWS::KinesisFirehose::DeliveryStream
    Properties:
      DeliveryStreamName: !Sub '${AWS::StackName}-firehose'
      DeliveryStreamType: DirectPut
      S3DestinationConfiguration:
        BucketARN: !GetAtt S3LogBucket.Arn
        Prefix: firehose/
        ErrorOutputPrefix: firehose-errors/
        RoleARN: !GetAtt FirehoseRole.Arn
        BufferingHints:
          IntervalInSeconds: 60
          SizeInMBs: 1
        CompressionFormat: GZIP
        CloudWatchLoggingOptions:
          Enabled: true
          LogGroupName: !Ref FirehoseLogGroup
          LogStreamName: !Ref FirehoseLogStream

  FirehoseDeliveryDestination:
    Type: AWS::Logs::DeliveryDestination
    Properties:
      Name: !Sub '${AWS::StackName}-firehose'
      OutputFormat: json
      DestinationResourceArn: !GetAtt DeliveryStream.Arn

  FirehoseDelivery:
    Type: AWS::Logs::Delivery
    DependsOn: S3DeliveryPlain
    Properties:
      DeliverySourceName: !Ref DeliverySource
      DeliveryDestinationArn: !GetAtt FirehoseDeliveryDestination.Arn

Outputs:
  NLBDNSName:
    Description: NLB DNS Name
    Value: !GetAtt NetworkLoadBalancer.DNSName

  TestCommand:
    Description: Command to generate traffic
    Value: !Sub 'curl -k https://${NetworkLoadBalancer.DNSName}'

  JSONLogGroup:
    Description: CloudWatch Logs (JSON)
    Value: !Ref JSONLogGroup

  TSVLogGroup:
    Description: CloudWatch Logs (TSV)
    Value: !Ref TSVLogGroup

  S3Bucket:
    Description: S3 Bucket (json/, w3c/, parquet/, plain/ prefixes)
    Value: !Ref S3LogBucket

  FirehoseStream:
    Description: Kinesis Data Firehose
    Value: !Ref DeliveryStream

動作確認

構築した CloudFormation スタックを使用して、NLB の新しいログ配信機能を検証しました。
デプロイ後、NLB コンソールの「統合」タブより、設定された各ログ配信先(CloudWatch Logs, S3, Firehose)が確認できます。

NLB統合ログ記録

トラフィック生成

検証用クライアントから NLB に対して curl でリクエストを送信し、ログの出力を確認しました。

for i in {1..10}; do curl -k -s https://nlb-test.elb.ap-northeast-1.amazonaws.com && echo " - Request $i"; sleep 1; done

1. CloudWatch Logs への配信

JSON 形式

出力形式を省略(または json 指定)した場合、ログは構造化された JSON 形式で出力されます。
これにより、CloudWatch Logs Insights でクエリする際に複雑な parse コマンドを記述する必要がなくなり、分析の効率が格段に向上します。

{
    "id": "nlb/app/net/nlb-test/xxxxxxxxxxxxxxxx",
    "timestamp": "2025-11-24T08:00:00.123Z",
    "version": "1.0",
    "type": "tcp",
    "listener_arn": "arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:listener/net/nlb-test/xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxx",
    "client_ip": "203.0.113.10",
    "client_port": 54321,
    "target_ip": "10.0.1.50",
    "target_port": 80,
    "connection_status": "success",
    "bytes_received": 150,
    "bytes_sent": 500
}

Plain 形式 (TSV互換)

出力形式を plain、フィールド区切り文字を \t (タブ) と設定することで、従来のスペース区切り(TSV互換)のログとして出力されます。

新機能で出力された TSV ログと、従来の S3 出力(レガシー方式)のログを比較したところ、フィールド数や順序の一致が確認できました。既存のログ解析基盤への影響を最小限に抑えたい場合に有用です。

現在の TSV ログ (フィールド分解)

 1: tls
 2: 2.0
 3: 2025-11-23T18:23:09
 4: net/nlb-auto-tokyo/xxxxxxxxxxxxxxxx
 5: xxxxxxxxxxxxxxxx
 6: 203.0.113.10:57510
 7: 172.31.xx.xx:443
 8: 752
 9: 379
10: 238
...

参考: レガシーログ (フィールド分解)

 1: tls
 2: 2.0
 3: 2020-04-01T08:51:42
 4: net/my-network-loadbalancer/xxxxxxxxxxxxxxxx
 5: xxxxxxxxxxxxxxxx
 6: 72.21.xx.xx:51341
 7: 172.100.xx.xx:443
 ...

2. Amazon S3 への配信

S3 への配信では、バケット内で指定した Prefix ごとに異なるフォーマットで出力確認を行いました。

Plain 形式

従来のアクセスログと同形式のログが、GZIP 圧縮形式で出力されました。

S3 URI
s3://nlb-logs-123456789012/plain/AWSLogs/.../xxxxxxxx.log.gz

ログサンプル

tls 2.0 2025-11-24T07:00:20 net/nlb-test/xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxx ...

JSON 形式

改行区切りの「JSONL」形式で、GZIP 圧縮された状態で保存されます。
各行の JSON 構造は CloudWatch Logs に保存された内容と同等でした。

S3 URI
s3://nlb-logs-123456789012/json/AWSLogs/.../xxxxxxxx.log.gz

W3C 形式

今回の検証設定では、タブ区切りを指定した plain 指定と同じ内容が出力されることを確認しました。

S3 URI
s3://nlb-logs-123456789012/w3c/AWSLogs/.../xxxxxxxx.log.gz

Parquet 形式

Apache Parquet 形式で出力されます。S3 Select を利用してクエリを実行したところ、JSON 設定の内容と同等の構造化データが取得できました。
Athena 等、Parquet 形式をサポートする分析ツールでのコスト削減やパフォーマンス向上が期待できます。

S3 URI
s3://nlb-logs-123456789012/parquet/AWSLogs/.../xxxxxxxx.log.parquet

S3 Select クエリ結果サンプル

{
  "type": "tls",
  "version": "2.0",
  "time": "2025-11-24T07:00:20",
  "elb": "net/nlb-test/xxxxxxxxxxxxxxxx",
  ...
}

3. Kinesis Data Firehose への配信

Firehose はデフォルト設定で利用しました。S3 出力先には、改行区切りの JSON (JSONL) が GZIP 圧縮されたファイルとして保存されていることが確認できました。これにより、Splunk や Datadog 等のサードパーティ監視基盤へのリアルタイムログ転送が容易になります。

S3 URI
s3://nlb-logs-123456789012/firehose/2025/11/24/07/nlb-test-firehose-1-2025-11-24-07-01-04-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.gz

アクセスログの長期保管に加え、保持するログのフィルタリングや、ログ項目の抽出、保存形式の最適化、パーティション管理など ETL 処理が必要な場合、Firehose をご検討ください。

注意点

新方式(Logs Delivery API)を利用する場合、出力先によってログ配信料(Vended Logs 料金)の有無が異なります。

S3 への配信は(Parquet変換を行わない限り)無料ですが、CloudWatch Logs や Firehose へ配信する場合は従量課金が発生する点にご注意ください。

「レガシー設定」のユースケース

「ログはS3にあれば十分、JSONやParquet形式への変換も不要」という場合や、CloudFormationの記述をシンプルに済ませたい場合には、従来の「レガシー設定」も依然として有効な選択肢です。

  NetworkLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      # ...
      LoadBalancerAttributes:
        # 従来の設定(配信料無料)
        - Key: access_logs.s3.enabled
          Value: 'true'
        - Key: access_logs.s3.bucket
          Value: !Ref LogsBucket

まとめ

NLBのログがCloudWatch Logsに来るようになったことで、障害時の初動調査(Live Tailでのエラー確認など)が劇的に楽になる事が期待できます。

平時の利用は S3へのログ出力のみ、任意の出力形式で有効化。
ログのリアルタイム調査などが必要となる期間限定で、CloudWatch Logs の出力を有効にする事で、費用対効果の良い利用ができると思われます。

コストと利便性のバランスを考慮して、要件に合った適切なログ出力先を選択してご活用ください。

この記事をシェアする

FacebookHatena blogX

関連記事