CloudWatch エージェントが journald に対応したので Amazon Linux 2023 で試してみた
はじめに
2026 年 8 月 28 日、CloudWatch エージェントが systemd ジャーナル(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)に切り替えました。
最小権限とデプロイ
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 を付けて起動するため、監査エントリは既定では収集対象から外れます。
既定の優先度では監査イベントを収集できず、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







