はじめに
OpenShift上の Pod は、再作成されると Pod のログまで消失してしまいます。これでは運用に堪えないので、ログを外部に転送することを検討することにしました。
OpenShift上にLokiStackを構築してもいいのですが、検証環境は非力なスペックなので、syslogサーバーに転送することにしました。
検証環境は下記記事で作ったものです。
システム構成
-
ClusterLogForwarder
- OpenShift では、ClusterLogForwarder リソースを使用してログを転送します
- openshift-logging という namespace に作成します
-
rsyslog
- 今回は bastionサーバーに構築します
-
/var/log/openshift/<namespace name>/<pod name>_<container name>.log
という命名規則でログを出力することを目指します
構築手順
下記、公式手順を参考にしながら進めていきます。
今回は、ログをクラスタ外のみ転送し、クラスタの内部ログストアに格納しないため、内部ログストアとなる LokiStack は不要なので、 Loki Operator のインストール、 LokiStack の作成は不要です。
OpenShift側の設定
- openshift-logging namespace の作成
oc create namespace openshift-logging
- OperatorGroup の作成
01_OperatorGroup_openshift-logging.yaml
apiVersion: operators.coreos.com/v1 kind: OperatorGroup metadata: name: cluster-logging namespace: openshift-logging spec: upgradeStrategy: Default
oc apply -f 01_OperatorGroup_openshift-logging.yaml
- Subscription の作成
02_subscription_cluster-logging.yaml
apiVersion: operators.coreos.com/v1alpha1 kind: Subscription metadata: name: cluster-logging namespace: openshift-logging spec: channel: stable-6.2 installPlanApproval: Automatic name: cluster-logging source: redhat-operators sourceNamespace: openshift-marketplace
なお、 channel の候補は以下で確認できます。oc apply -f 02_subscription_cluster-logging.yaml
実行例$ oc get packagemanifests cluster-logging -n openshift-marketplace -o jsonpath='{.status.channels[*].name}' stable-6.1 stable-6.2
- インストール状況の確認
以下を確認します。# Operatorのインストール状況確認 oc get csv -n openshift-logging # Podの状態確認 oc get pods -n openshift-logging # CRD(Custom Resource Definition)の確認 oc get crd | grep logging
実行例$ oc get csv -n openshift-logging NAME DISPLAY VERSION REPLACES PHASE cluster-logging.v6.2.2 Red Hat OpenShift Logging 6.2.2 cluster-logging.v6.2.1 Succeeded metallb-operator.v4.18.0-202505081435 MetalLB Operator 4.18.0-202505081435 Succeeded openstack-operator.v0.3.0 OpenStack 0.3.0 Succeeded $ oc get pods -n openshift-logging NAME READY STATUS RESTARTS AGE cluster-logging-operator-b5d867f4c-mbcss 1/1 Running 0 2m17s $ oc get crd | grep logging logfilemetricexporters.logging.openshift.io 2025-05-31T17:07:39Z loggings.telemetry.openstack.org 2025-05-31T16:06:09Z
- Servcice Accountの作成
ログコレクターが使用するサービスアカウントを作成し、コレクターがログを収集して転送できるように、サービスアカウントに必要な権限を割り当てます。ここでは、コレクターにインフラストラクチャーログとアプリケーションログの両方を収集する権限を付与します。oc create sa logging-collector -n openshift-logging oc adm policy add-cluster-role-to-user logging-collector-logs-writer -z logging-collector -n openshift-logging oc adm policy add-cluster-role-to-user collect-application-logs -z logging-collector -n openshift-logging oc adm policy add-cluster-role-to-user collect-infrastructure-logs -z logging-collector -n openshift-logging
- ClusterLogForwarder の作成
ClusterLogging が稼動したら、ClusterLogForwarderを作成します。04_clusterlogforwarder.yamlapiVersion: observability.openshift.io/v1 kind: ClusterLogForwarder metadata: name: instance namespace: openshift-logging spec: serviceAccount: name: logging-collector outputs: - name: syslog-output type: syslog syslog: url: udp://172.18.0.253:514 facility: local0 severity: informational tag: "openshift" rfc: RFC5424 pipelines: - name: application-logs inputRefs: - application outputRefs: - syslog-output
rsyslog側の設定
- rsyslogの設定ファイルの編集
/etc/rsyslog.conf は最終的には以下のようになりました。試行錯誤の過程は後述します。--- /etc/rsyslog.conf.default 2025-05-07 22:14:04.000000000 +0900 +++ /etc/rsyslog.conf 2025-06-01 02:45:21.909801001 +0900 @@ -29,14 +29,19 @@ # Provides UDP syslog reception # for parameters see http://www.rsyslog.com/doc/imudp.html -#module(load="imudp") # needs to be done just once -#input(type="imudp" port="514") +module(load="imudp") # needs to be done just once +input(type="imudp" port="514") # Provides TCP syslog reception # for parameters see http://www.rsyslog.com/doc/imtcp.html #module(load="imtcp") # needs to be done just once #input(type="imtcp" port="514") +#### TEMPLATES #### +template(name="DynaPodFile" type="string" string="/var/log/openshift/%$.extracted_hostname%/%$.extracted_namespace_name%/%$.extracted_pod_name%_%$.extracted_container_name%.log") +$template OpenShiftLogFormat,"%TIMESTAMP% %HOSTNAME% %.extracted_namespace_name% %.extracted_pod_name% %.extracted_container_name% | %.extracted_message%\n" + #### RULES #### # Log all kernel messages to the console. @@ -79,3 +84,33 @@ # # Remote Logging (we use TCP for reliable delivery) # # remote_host is: name/ip, e.g. 192.168.0.1, port optional e.g. 10514 #Target="remote_host" Port="XXX" Protocol="tcp") + +if $syslogfacility-text == 'local0' then { + # Pod名を抽出(正規表現を使用) + if re_match($msg, '.*"pod_name":"([^"]+)".*') then { + set $.extracted_hostname = re_extract($msg, '"hostname":"([^"]+)"', 0, 1, "unknown"); + set $.extracted_namespace_name = re_extract($msg, '"namespace_name":"([^"]+)"', 0, 1, "unknown"); + set $.extracted_pod_name = re_extract($msg, '"pod_name":"([^"]+)"', 0, 1, "unknown"); + set $.extracted_container_name = re_extract($msg, '"container_name":"([^"]+)"', 0, 1, "unknown"); + #set $.extracted_message = re_extract($msg, '"message":"([^"]+)"', 0, 1, "unknown"); + + # messageの開始位置から、次の","msg_idまでを抽出 + set $.extracted_message = re_extract($msg, '"message":"(.*?)","msg_id"', 0, 1, ""); + + # 見つからない場合 + if ($.extracted_message == "") then { + set $.extracted_message = "Message extraction failed"; + } + + # Pod名が取得できた場合 + if ($.extracted_pod_name != "unknown") then { + local0.* ?DynaPodFile;OpenShiftLogFormat + stop + } + } +}
- ログディレクトリの作成
sudo mkdir -p /var/log/openshift
- ログローテーション設定
/etc/logrotate.d/openshift
/var/log/openshift/*/*.log { daily rotate 30 compress delaycompress missingok create 0644 syslog syslog postrotate systemctl reload rsyslog endscript }
- ファイアウォール設定
sudo firewall-cmd --permanent --add-port=514/udp sudo firewall-cmd --permanent --add-port=514/tcp sudo firewall-cmd --reload
- SELinux設定
sudo setsebool -P nis_enabled 1 sudo semanage port -a -t syslogd_port_t -p udp 514 sudo semanage port -a -t syslogd_port_t -p tcp 514
- rsyslogサービスの再起動
sudo rsyslogd -N1 -f /etc/rsyslog.conf sudo systemctl restart rsyslog
実行例$ sudo rsyslogd -N1 -f /etc/rsyslog.conf rsyslogd: version 8.2412.0-2.el9, config validation run (level 1), master config /etc/rsyslog.conf rsyslogd: End of config validation run. Bye.
- 出力されたログの確認
実行例
$ cd /var/log/openshift $ sudo tree . . └── crc ├── baremetal-operator-system │ ├── baremetal-operator-ironic-8677964577-lbsjj_ironic-httpd.log │ ├── baremetal-operator-ironic-8677964577-lbsjj_ironic.log │ └── baremetal-operator-ironic-8677964577-lbsjj_mariadb.log ├── cert-manager │ └── cert-manager-cainjector-645bd4478d-24x9q_cert-manager-cainjector.log ├── cert-manager-operator │ └── cert-manager-operator-controller-manager-698c6d7767-qr7k9_cert-manager-operator.log ├── hostpath-provisioner │ ├── csi-hostpathplugin-fp6dj_csi-provisioner.log │ └── csi-hostpathplugin-fp6dj_hostpath-provisioner.log ├── metallb-system │ ├── controller-764dff778f-sp6pf_controller.log │ ├── frr-k8s-jwx4m_controller.log │ ├── frr-k8s-jwx4m_frr.log │ ├── metallb-operator-controller-manager-564755966-6q58k_manager.log │ └── speaker-w2pbh_speaker.log ├── openstack │ ├── ceilometer-0_ceilometer-central-agent.log │ ├── ceilometer-0_ceilometer-notification-agent.log │ ├── ceilometer-0_proxy-httpd.log │ ├── cinder-api-0_cinder-api-log.log │ ├── cinder-api-0_cinder-api.log (snip) └── openstack-operators ├── barbican-operator-controller-manager-655f8448d8-59bdm_manager.log ├── cinder-operator-controller-manager-645dc58df8-4x97r_manager.log ├── glance-operator-controller-manager-669856f45d-s4f8p_manager.log (snip)
May 31 05:39:02 crc baremetal-operator-system baremetal-operator-ironic-8677964577-lbsjj ironic-httpd | 127.0.0.1 - - [31/May/2025:05:39:01 +0000] \"GET / HTTP/1.1\" 200 474 \"-\" \"curl/7.76.1\" May 31 05:39:02 crc baremetal-operator-system baremetal-operator-ironic-8677964577-lbsjj ironic-httpd | [Sat May 31 05:39:01.617894 2025] [ssl:debug] [pid 146:tid 149] ssl_engine_io.c(1150): [client 127.0.0.1:50322] AH02001: Connection closed to child 193 with standard shutdown (server 192.168.122.10:6385)
rsyslog.conf のチューニング
Step1. そのまま出力
まずは local0 で受け取ったものを /var/log/openshift/openshift.log に出力してみます。
$template OpenShiftLogFormat,"%TIMESTAMP% %HOSTNAME% %syslogtag %msg%\n"
local0.* /var/log/openshift/openshift.log;OpenShiftLogFormat
以下のようなログが出力されました。
May 31 17:29:40 crc openstack_memcached-0_memcached[63591bb1-0722-4505-9616-fb5fd5dbdb26] {"@timestamp":"2025-05-31T17:29:40.425489219Z","app_name":"openstack_memcached-0_memcached","facility":"local0","hostname":"crc","kubernetes":{"annotations":{"k8s.ovn.org/pod-networks":"{\"default\":{\"ip_addresses\":[\"10.217.0.101/23\"],\"mac_address\":\"0a:58:0a:d9:00:65\",\"gateway_ips\":[\"10.217.0.1\"],\"routes\":[{\"dest\":\"10.217.0.0/22\",\"nextHop\":\"10.217.0.1\"},{\"dest\":\"10.217.4.0/23\",\"nextHop\":\"10.217.0.1\"},{\"dest\":\"169.254.0.5/32\",\"nextHop\":\"10.217.0.1\"},{\"dest\":\"100.64.0.0/16\",\"nextHop\":\"10.217.0.1\"}],\"ip_address\":\"10.217.0.101/23\",\"gateway_ip\":\"10.217.0.1\",\"role\":\"primary\"}}","k8s.v1.cni.cncf.io/network-status":"[{\n \"name\": \"ovn-kubernetes\",\n \"interface\": \"eth0\",\n \"ips\": [\n \"10.217.0.101\"\n ],\n \"mac\": \"0a:58:0a:d9:00:65\",\n \"default\": true,\n \"dns\": {}\n}]","openshift.io/scc":"anyuid"},"container_id":"cri-o://b19aa1c9e60596b9ad86d6649ba2bafc3827bf49e6eca9265bb660af35058479","container_image":"quay.io/podified-antelope-centos9/openstack-memcached@sha256:83b7d261083257b382f78e0a1190dd2c8d2e46c8d14dabd0ebb81c8c9be9fb4a","container_image_id":"quay.io/podified-antelope-centos9/openstack-memcached@sha256:83b7d261083257b382f78e0a1190dd2c8d2e46c8d14dabd0ebb81c8c9be9fb4a","container_iostream":"stderr","container_name":"memcached","labels":{"app":"memcached","apps_kubernetes_io_pod-index":"0","controller-revision-hash":"memcached-8b564785b","cr":"memcached","memcached_name":"memcached","memcached_namespace":"openstack","memcached_uid":"4d668ca9-c6b3-4bbc-af33-eef0da3c8d74","owner":"infra-operator","service":"memcached","statefulset_kubernetes_io_pod-name":"memcached-0"},"namespace_id":"69c7a12e-a245-4481-b3d9-fd9dccd6f001","namespace_labels":{"kubernetes_io_metadata_name":"openstack","pod-security_kubernetes_io_enforce":"privileged","security_openshift_io_scc_podSecurityLabelSync":"false"},"namespace_name":"openstack","pod_id":"63591bb1-0722-4505-9616-fb5fd5dbdb26","pod_ip":"10.217.0.101","pod_name":"memcached-0","pod_owner":"StatefulSet/memcached"},"level":"default","log_source":"container","log_type":"application","message":"<43 new auto-negotiating client connection","msg_id":"container","openshift":{"cluster_id":"6df651c0-eb51-42db-a77c-8a60f65389bc","sequence":1748712580660089637},"proc_id":"63591bb1-0722-4505-9616-fb5fd5dbdb26","severity":"informational","timestamp":"2025-05-31T17:29:40.425489219Z"}
ログを整形すると以下のような構造になっています。
{
"@timestamp": "2025-05-31T17:29:40.425489219Z",
"app_name": "openstack_memcached-0_memcached",
"facility": "local0",
"hostname": "crc",
"kubernetes": {
"annotations": {
"k8s.ovn.org/pod-networks": "{\"default\":{\"ip_addresses\":[\"10.217.0.101/23\"],\"mac_address\":\"0a:58:0a:d9:00:65\",\"gateway_ips\":[\"10.217.0.1\"],\"routes\":[{\"dest\":\"10.217.0.0/22\",\"nextHop\":\"10.217.0.1\"},{\"dest\":\"10.217.4.0/23\",\"nextHop\":\"10.217.0.1\"},{\"dest\":\"169.254.0.5/32\",\"nextHop\":\"10.217.0.1\"},{\"dest\":\"100.64.0.0/16\",\"nextHop\":\"10.217.0.1\"}],\"ip_address\":\"10.217.0.101/23\",\"gateway_ip\":\"10.217.0.1\",\"role\":\"primary\"}}",
"k8s.v1.cni.cncf.io/network-status": "[{\n \"name\": \"ovn-kubernetes\",\n \"interface\": \"eth0\",\n \"ips\": [\n \"10.217.0.101\"\n ],\n \"mac\": \"0a:58:0a:d9:00:65\",\n \"default\": true,\n \"dns\": {}\n}]",
"openshift.io/scc": "anyuid"
},
"container_id": "cri-o://b19aa1c9e60596b9ad86d6649ba2bafc3827bf49e6eca9265bb660af35058479",
"container_image": "quay.io/podified-antelope-centos9/openstack-memcached@sha256:83b7d261083257b382f78e0a1190dd2c8d2e46c8d14dabd0ebb81c8c9be9fb4a",
"container_image_id": "quay.io/podified-antelope-centos9/openstack-memcached@sha256:83b7d261083257b382f78e0a1190dd2c8d2e46c8d14dabd0ebb81c8c9be9fb4a",
"container_iostream": "stderr",
"container_name": "memcached",
"labels": {
"app": "memcached",
"apps_kubernetes_io_pod-index": "0",
"controller-revision-hash": "memcached-8b564785b",
"cr": "memcached",
"memcached_name": "memcached",
"memcached_namespace": "openstack",
"memcached_uid": "4d668ca9-c6b3-4bbc-af33-eef0da3c8d74",
"owner": "infra-operator",
"service": "memcached",
"statefulset_kubernetes_io_pod-name": "memcached-0"
},
"namespace_id": "69c7a12e-a245-4481-b3d9-fd9dccd6f001",
"namespace_labels": {
"kubernetes_io_metadata_name": "openstack",
"pod-security_kubernetes_io_enforce": "privileged",
"security_openshift_io_scc_podSecurityLabelSync": "false"
},
"namespace_name": "openstack",
"pod_id": "63591bb1-0722-4505-9616-fb5fd5dbdb26",
"pod_ip": "10.217.0.101",
"pod_name": "memcached-0",
"pod_owner": "StatefulSet/memcached"
},
"level": "default",
"log_source": "container",
"log_type": "application",
"message": "<43 new auto-negotiating client connection",
"msg_id": "container",
"openshift": {
"cluster_id": "6df651c0-eb51-42db-a77c-8a60f65389bc",
"sequence": 1748712580660089637
},
"proc_id": "63591bb1-0722-4505-9616-fb5fd5dbdb26",
"severity": "informational",
"timestamp": "2025-05-31T17:29:40.425489219Z"
}
今回は ELK などに送信したい訳ではないので、人間が見るにはかなり冗長です。JSONデータを parse して、message だけ出力するようにしてみます。また、namespace_name、container_name が kubernetes の中にあるので、その値も抽出する必要があります。
Step2. rsyslog の mmjsonparse を使ってみる
rsyslog.conf を以下のようにしてみました。
module(load="mmjsonparse")
template(name="OpenShiftLogFormat" type="string" string="%TIMESTAMP% %HOSTNAME% %syslogtag% [%parsesuccess%] %msg%\n")
if $syslogtag contains "fluentd" then {
# JSONを解析
action(type="mmjsonparse")
action(type="omfile" File="/var/log/openshift/openshift.log" template="OpenShiftLogFormat")
}
残念ながら、 %parsesuccess
の値が FALSE になってしまい、 mmjsonparse では parse することはできませんでした。
Step3. 正規表現で必要なパラメータを抽出する
rsyslog.conf を以下のようにしてみました。
#### TEMPLATES ####
template(name="DynaPodFile" type="string" string="/var/log/openshift/%$.extracted_hostname%/%$.extracted_namespace_name%/%$.extracted_pod_name%_%$.extracted_container_name%.log")
$template OpenShiftLogFormat,"%TIMESTAMP% %HOSTNAME% %.extracted_namespace_name% %.extracted_pod_name% %.extracted_container_name% | %.extracted_message%\n"
if $syslogtag contains "fluentd" then {
# Pod名を抽出(正規表現を使用)
if re_match($msg, '.*"pod_name":"([^"]+)".*') then {
set $.extracted_hostname = re_extract($msg, '"hostname":"([^"]+)"', 0, 1, "unknown");
set $.extracted_namespace_name = re_extract($msg, '"namespace_name":"([^"]+)"', 0, 1, "unknown");
set $.extracted_pod_name = re_extract($msg, '"pod_name":"([^"]+)"', 0, 1, "unknown");
set $.extracted_container_name = re_extract($msg, '"container_name":"([^"]+)"', 0, 1, "unknown");
# messageの開始位置から、次の","msg_idまでを抽出
set $.extracted_message = re_extract($msg, '"message":"(.*?)","msg_id"', 0, 1, "");
# 見つからない場合
if ($.extracted_message == "") then {
set $.extracted_message = "Message extraction failed";
}
# Pod名が取得できた場合
if ($.extracted_pod_name != "unknown") then {
local0.* ?DynaPodFile;OpenShiftLogFormat
stop
}
}
}
message
の内容に「"」が含まれていると、namespace_name
などのように parse すると途中で切れてしまうことが分かったので、message
の次の msg_id
が現れるまでを抽出することにしました。