CloudWatch エージェントが journald に対応したので Amazon Linux 2023 で試してみた

CloudWatch エージェントが journald に対応したので Amazon Linux 2023 で試してみた

CloudWatch エージェントが systemd ジャーナルの直接収集に対応しました。/var/log/messages を持たない Amazon Linux 2023 で、systemd ユニット・カーネル・監査・Docker の 4 種類のログを、ファイルへ退避せずに CloudWatch Logs へ集約し、Logs Insights でクエリできるところまで試しました。
2026.08.29

はじめに

2026 年 8 月 28 日、CloudWatch エージェントが systemd ジャーナル(journald)からのログ収集に対応しました。

https://aws.amazon.com/about-aws/whats-new/2026/08/amazon-cloudwatch-agent-journald/

項目 以前 今回
journald ログの収集方法 journald をファイルへエクスポートする追加設定が必要 エージェントが journald を直接読み取り
CloudWatch エージェント向けのファイル退避 ジャーナルをファイルへ書き出す必要あり 不要
構造化メタデータ ファイル経由では失われる場合あり ユニット名・優先度・プロセス情報を保持して送信
絞り込み なし ユニット・優先度・フィールドマッチ・正規表現フィルタ

Amazon Linux 2023 の標準構成には /var/log/messages がなく、rsyslog も入っていません。OS が出すログは journald に集まるため、エージェントの設定はファイルを指定する files を使わず、journald セクションだけで構成しました。systemd ユニット・カーネルメッセージ・監査イベント・Docker の 4 種類のログを、別々のロググループへ収集しました。CloudWatch Logs に届いたイベントの形式と、Logs Insights でのクエリまで確認しました。

検証内容

検証環境

項目
OS Amazon Linux 2023.12.20260817
カーネル 6.18.41-94.142.amzn2023.x86_64
systemd 252
インスタンス t3.micro
リージョン ap-northeast-1
CloudWatch エージェント 1.300072.0b1766

インバウンドは開けず、操作はすべて SSM 経由で行いました。サブネットはパブリックサブネットで、インスタンスにパブリック IP を付与し、アウトバウンドは許可しています。SSM、S3、Amazon Linux のリポジトリ、CloudWatch Logs への通信が必要です。

journald 対応はエージェント 1.300070.0 以降の機能です。2026 年 8 月 29 日時点で、Amazon Linux 2023 の amazonlinux リポジトリから入るのは 1.300069.1 でした。そのため取得元を S3 の最新 RPM(https://amazoncloudwatch-agent.s3.amazonaws.com/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm)に切り替えました。

https://github.com/aws/amazon-cloudwatch-agent/blob/main/RELEASE_NOTES

最小権限とデプロイ

EC2 と IAM ロールは CloudFormation の Express モードで作成しました。インスタンスロールは SSM 用のマネージドポリシーに加えて、CloudWatch Logs の権限を 4 アクションだけに絞っています。設定ファイルで retention_in_days を指定するため、logs:PutRetentionPolicy も含めました。保持期間は検証用に 1 日としています。

テンプレートのうち、インスタンスロール部分は次のとおりです。

  InstanceRole:
    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
      Policies:
        - PolicyName: journald-logs-minimal
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                  - logs:PutRetentionPolicy
                Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:journald-*
aws cloudformation create-stack \
  --stack-name journald-demo \
  --template-body file://journald-demo.yaml \
  --capabilities CAPABILITY_IAM \
  --deployment-config Mode=EXPRESS \
  --parameters ParameterKey=VpcId,ParameterValue=vpc-xxxxxxxxxxxxxxxxx \
               ParameterKey=SubnetId,ParameterValue=subnet-xxxxxxxxxxxxxxxxx

journald 設定と送信形式

収集対象は次の 4 エントリで、それぞれ別のロググループへ送ります。

{
  "agent": {
    "run_as_user": "root"
  },
  "logs": {
    "logs_collected": {
      "journald": {
        "collect_list": [
          {
            "log_group_name": "journald-units",
            "log_stream_name": "{instance_id}",
            "units": ["sshd", "systemd-logind", "chronyd", "amazon-ssm-agent"],
            "retention_in_days": 1
          },
          {
            "log_group_name": "journald-kernel",
            "log_stream_name": "{instance_id}",
            "matches": [{"_TRANSPORT": "kernel"}],
            "retention_in_days": 1
          },
          {
            "log_group_name": "journald-audit",
            "log_stream_name": "{instance_id}",
            "matches": [{"_TRANSPORT": "audit"}],
            "priority": "debug",
            "retention_in_days": 1
          },
          {
            "log_group_name": "journald-docker",
            "log_stream_name": "{instance_id}",
            "units": ["docker"],
            "retention_in_days": 1
          }
        ]
      }
    }
  }
}

適用は次のコマンドで行いました。

sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -s -c file:/opt/aws/amazon-cloudwatch-agent/etc/cw-journald.json

CloudWatch Logs に届いた systemd ユニットのログを 1 イベント分取得しました。

{
    "events": [
        {
            "timestamp": 1787982970748,
            "message": "{\"body\":{\"MESSAGE\":\"pam_unix(sudo:session): session closed for user root\",\"PRIORITY\":\"6\",\"SYSLOG_FACILITY\":\"10\",\"SYSLOG_IDENTIFIER\":\"sudo\",\"_CMDLINE\":\"sudo docker run --rm public.ecr.aws/docker/library/alpine:3.20 true\",\"_COMM\":\"sudo\",\"_EXE\":\"/usr/bin/sudo\",\"_HOSTNAME\":\"ip-10-0-x-x.ap-northeast-1.compute.internal\",\"_PID\":\"29152\",\"_SYSTEMD_UNIT\":\"amazon-ssm-agent.service\",\"_TRANSPORT\":\"syslog\",\"_UID\":\"0\",\"__CURSOR\":\"s=xxxxxxxx;i=e09;b=xxxxxxxx;m=2155d6a2;t=65a2939d5ff89;x=xxxxxxxx\"}}",
            "ingestionTime": 1787982974133
        }
    ]
}

イベントは 1 件あたり、journald の全フィールドを body 配下に収めた構造化 JSON として届きます。MESSAGE 本文だけの平文ではありません。上記は一部のフィールドを省いた抜粋です(以降に載せるイベントの JSON も同様です)。

sshd のログイン成功イベントの body も確認しました。

{
 "body": {
  "MESSAGE": "Accepted publickey for ec2-user from 127.0.0.1 port 54478 ssh2: ED25519 SHA256:xxxxxxxx",
  "PRIORITY": "6",
  "SYSLOG_FACILITY": "10",
  "SYSLOG_IDENTIFIER": "sshd-session",
  "SYSLOG_PID": "29125",
  "_COMM": "sshd-session",
  "_EXE": "/usr/libexec/openssh/sshd-session",
  "_HOSTNAME": "ip-10-0-x-x.ap-northeast-1.compute.internal",
  "_PID": "29125",
  "_SELINUX_CONTEXT": "system_u:system_r:sshd_t:s0-s0:c0.c1023",
  "_SYSTEMD_CGROUP": "/system.slice/sshd.service",
  "_SYSTEMD_UNIT": "sshd.service",
  "_TRANSPORT": "syslog",
  "_UID": "0"
 }
}

設定で指定したのは sshd ですが、SYSLOG_IDENTIFIER は sshd-session で、_SYSTEMD_UNIT は sshd.service でした。ユニット単位の指定は、識別子ではなくユニット名で効きます。

接続元が 127.0.0.1 なのは、今回使った Session Manager のシェルセッションでは sshd のログが出なかったためです。インスタンス内から使い捨て鍵で ssh ec2-user@localhost を実行してログを発生させました。

なお fetch-config は、適用時に etc/amazon-cloudwatch-agent.json を削除し、amazon-cloudwatch-agent.d 配下の既存ファイルも置き換えます。したがって、ここで示した手順では既存のエージェント設定が上書きされる点に注意してください。

エージェントが内部で起動する journalctl
journalctl --utc --output=json --follow --priority debug _TRANSPORT=audit
journalctl --utc --output=json --follow --priority info _TRANSPORT=kernel --after-cursor s=xxxxxxxx;i=9be;...
journalctl --utc --output=json --follow --unit docker --priority info --after-cursor s=xxxxxxxx;i=9b2;...
journalctl --utc --output=json --follow --unit sshd --unit systemd-logind --unit chronyd --unit amazon-ssm-agent --priority info --after-cursor s=xxxxxxxx;i=cb2;...

collect_list の 1 エントリが journalctl 1 プロセスに対応します。設定に書いたユニット名・フィールド指定・優先度が、そのままコマンドライン引数になっていました。

既にカーソルがある collect_list のエントリには --after-cursor が付き、過去のログは再収集されません。

カーネルと監査ログ

カーネルメッセージは matches で _TRANSPORT を kernel に指定して収集しました。

{
    "events": [
        {
            "timestamp": 1787982734525,
            "message": "{\"body\":{\"MESSAGE\":\"veth97da191 (unregistering): left allmulticast mode\",\"PRIORITY\":\"6\",\"SYSLOG_FACILITY\":\"0\",\"SYSLOG_IDENTIFIER\":\"kernel\",\"_BOOT_ID\":\"xxxxxxxx\",\"_HOSTNAME\":\"ip-10-0-x-x.ap-northeast-1.compute.internal\",\"_TRANSPORT\":\"kernel\",\"__CURSOR\":\"s=xxxxxxxx;i=9bc;b=xxxxxxxx;m=13415a86;t=65a292bc1836e;x=xxxxxxxx\"}}",
            "ingestionTime": 1787982739614
        }
    ]
}

カーネルメッセージにはユニット名が付かないため、units では指定できません。検証でログを収集した 20 分間を対象にした journalctl --unit kernel は「-- No entries --」を返しました。一方、journalctl _TRANSPORT=kernel は 639 行を返しました。

Amazon Linux 2023 では auditd と systemd-journald-audit.socket が既定でともに active なため、監査イベントは journald にも届いています。ログを収集した 20 分間では 911 件のエントリが対象になりました。ところが、この 911 件のうち PRIORITY フィールドを持つものは 0 件でした。ドキュメントには、priority の既定値は info(debug を除外)と記載されています。エージェントは journalctl --priority info を付けて起動するため、監査エントリは既定では収集対象から外れます。

https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Agent-Configuration-File-Details.html

既定の優先度では監査イベントを収集できず、journald-audit のロググループ自体も作成されませんでした。ほかの 3 つのロググループは作成されました。priority に debug を指定すると 911 件すべてが対象になり、エージェントはロググループも作成しました。

{
    "events": [
        {
            "timestamp": 1787983006400,
            "message": "{\"body\":{\"AUDIT_FIELD_EXE\":\"/usr/lib/systemd/systemd\",\"AUDIT_FIELD_RES\":\"success\",\"AUDIT_FIELD_UNIT\":\"user@0\",\"MESSAGE\":\"SERVICE_STOP pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=user@0 comm=\\\"systemd\\\" exe=\\\"/usr/lib/systemd/systemd\\\" hostname=? addr=? terminal=? res=success'\",\"SYSLOG_FACILITY\":\"4\",\"SYSLOG_IDENTIFIER\":\"audit\",\"_AUDIT_ID\":\"1433\",\"_AUDIT_TYPE\":\"1131\",\"_AUDIT_TYPE_NAME\":\"SERVICE_STOP\",\"_HOSTNAME\":\"ip-10-0-x-x.ap-northeast-1.compute.internal\",\"_PID\":\"1\",\"_TRANSPORT\":\"audit\",\"_UID\":\"0\"}}",
            "ingestionTime": 1787983009138
        }
    ]
}

監査イベントは _AUDIT_TYPE_NAME と AUDIT_FIELD_* を保ったまま届きました。

Docker のログ

systemd 管理下のアプリケーションの例として、Docker のログを専用のロググループへ分離しました。

{
    "events": [
        {
            "timestamp": 1787982718503,
            "message": "{\"body\":{\"MESSAGE\":\"time=\\\"2026-08-29T05:51:58.502767587Z\\\" level=info msg=\\\"API listen on /run/docker.sock\\\"\",\"PRIORITY\":\"6\",\"SYSLOG_FACILITY\":\"3\",\"SYSLOG_IDENTIFIER\":\"dockerd\",\"_CMDLINE\":\"/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --default-ulimit nofile=32768:65536\",\"_COMM\":\"dockerd\",\"_EXE\":\"/usr/bin/dockerd\",\"_HOSTNAME\":\"ip-10-0-x-x.ap-northeast-1.compute.internal\",\"_PID\":\"7416\",\"_SYSTEMD_UNIT\":\"docker.service\",\"_TRANSPORT\":\"stdout\",\"_UID\":\"0\"}}",
            "ingestionTime": 1787982724803
        }
    ]
}

ユニットに docker を指定すると、dockerd のログが収集されました。同じロググループには、systemd 自身が出す docker.service 関連のメッセージ(CODE_FILE が src/core/job.c であるエントリ)も含まれます。journalctl -u docker と同じ範囲が対象になります。

Logs Insights クエリ

送信形式が JSON なので、body 配下のフィールドはそのまま discovered field になり、parse は不要でした。以下のクエリはいずれも直近 1 時間を対象にしています。まず systemd ユニットのロググループで、ユニット別に数えます(recordsMatched 111)。

stats count(*) as cnt by body._SYSTEMD_UNIT | sort cnt desc
body._SYSTEMD_UNIT cnt
amazon-ssm-agent.service 95
sshd.service 10
systemd-logind.service 6

次に 4 つのロググループを横断して、優先度で切り分けます(logGroupsScanned 4、recordsMatched 366)。

stats count(*) as cnt by @log, body.PRIORITY | sort @log, body.PRIORITY
@log body.PRIORITY cnt
123456789012:journald-audit (空) 186
123456789012:journald-docker 6 2
123456789012:journald-kernel 5 5
123456789012:journald-kernel 6 62
123456789012:journald-units 5 34
123456789012:journald-units 6 77

監査イベントは種別ごとに数えます(recordsMatched 176)。

stats count(*) as cnt by body._AUDIT_TYPE_NAME | sort cnt desc | limit 8
body._AUDIT_TYPE_NAME cnt
BPF 74
USER_START 12
USER_ACCT 11
CRYPTO_KEY_USER 10
CRED_DISP 10
SERVICE_STOP 9
USER_END 9
SERVICE_START 7

sshd の不正ユーザー接続だけを抜き出します(recordsMatched 2)。

filter body._SYSTEMD_UNIT = "sshd.service" and body.MESSAGE like /Invalid user/
| fields @timestamp, body.MESSAGE | sort @timestamp desc
@timestamp body.MESSAGE
2026-08-29 05:56:09.986 Invalid user nosuchuser from 127.0.0.1 port 54494
2026-08-29 05:52:10.066 Invalid user nosuchuser from 127.0.0.1 port 47170

まとめ

CloudWatch エージェントの journald 対応により、Amazon Linux 2023 の systemd ユニット・カーネル・監査・Docker のログを CloudWatch Logs へ収集できました。ジャーナルをファイルへ書き出す設定は不要でした。各イベントは journald のフィールドを保持した構造化 JSON として送信されます。ユニット名や監査イベント種別を使って、Logs Insights からそのまま集計・検索できます。

CloudWatch エージェントへ渡すためだけに rsyslog を追加したり、ジャーナルをファイルへ退避して tail したりしている構成は、journald セクションによって簡素化できます。特に、OS や systemd 管理下のサービスのログをまとめて収集したい場合は、移行を検討してみてください。

付録: UserData

SSM で事後設定せずに同じ構成を作る UserData です。エージェントの導入から journald 設定の適用まで、起動時に完了します。

#!/bin/bash
set -xeuo pipefail

# systemd 管理下のアプリ例として Docker を導入
dnf install -y docker
systemctl enable --now docker

# AL2023 の dnf リポジトリ版は journald 非対応バージョンのため S3 の最新 RPM を使う
cd /tmp
curl -sS -O https://amazoncloudwatch-agent.s3.amazonaws.com/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm
dnf install -y ./amazon-cloudwatch-agent.rpm

cat > /opt/aws/amazon-cloudwatch-agent/etc/cw-journald.json <<'CONFIG'
{
  "agent": { "run_as_user": "root" },
  "logs": {
    "logs_collected": {
      "journald": {
        "collect_list": [
          {
            "log_group_name": "journald-userdata-units",
            "log_stream_name": "{instance_id}",
            "units": ["sshd", "systemd-logind", "chronyd", "amazon-ssm-agent"],
            "retention_in_days": 1
          },
          {
            "log_group_name": "journald-userdata-kernel",
            "log_stream_name": "{instance_id}",
            "matches": [{"_TRANSPORT": "kernel"}],
            "retention_in_days": 1
          },
          {
            "log_group_name": "journald-userdata-audit",
            "log_stream_name": "{instance_id}",
            "matches": [{"_TRANSPORT": "audit"}],
            "priority": "debug",
            "retention_in_days": 1
          },
          {
            "log_group_name": "journald-userdata-docker",
            "log_stream_name": "{instance_id}",
            "units": ["docker"],
            "retention_in_days": 1
          }
        ]
      }
    }
  }
}
CONFIG

/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
  -a fetch-config -m ec2 -s \
  -c file:/opt/aws/amazon-cloudwatch-agent/etc/cw-journald.json

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事