はじめに
こちらの記事でPower Systems Virtual Server(以下PowerVS)へのOpenShift 4.7の導入後作業としてinfraノードの構成を実施しました。infraノードのElasticsearchは短期的なログ保存を目的としています。長期的にログを保存するために外部ElasticsearchやFluentdをサポートする外部ログ集計ソリューションへログを転送することができますが、この記事ではテキストファイルへエクスポートします。
OpenShift Logging Elasticsearch インスタンスは、短期 (約 7 日間) の保存について最適化され、テストされています。長期間ログを保持する必要がある場合は、データをサードパーティーのストレージシステムに移動することが推奨されます。
ログを他のログアグリゲーターに送信するには、OpenShift Container Platform ログ転送 API を使用します。この API を使用すると、コンテナー、インフラストラクチャーおよび監査ログをクラスター内外の特定のエンドポイントに送信できます。
1. Elasticsearch保存データのエクスポート
1.1. Elasticsearch接続用ルート作成
bastionノードからElasticsearchのログストアに対して、curlで接続できるようにルートを作成します。
oc project openshift-logging
vi route.yaml
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: elasticsearch
namespace: openshift-logging
spec:
host:
to:
kind: Service
name: elasticsearch
tls:
termination: reencrypt
destinationCACertificate: |
### vi終了
oc extract secret/elasticsearch --to=. --keys=admin-ca
cat ./admin-ca | sed -e "s/^/ /" >> route.yaml
oc apply -f route.yaml
oc get route
### 標準出力↓
NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD
elasticsearch elasticsearch-openshift-logging.apps.ocp.powervs elasticsearch <all> reencrypt None
kibana kibana-openshift-logging.apps.ocp.powervs kibana <all> reencrypt/Redirect None
1.2. インデックス一覧の取得
OpenShiftユーザーのトークンでElasticsearchに接続してインデックス一覧を取得します。
ここではユーザーとしてkubeadminを使用しています。
# 準備
oc login -u kubeadmin -p <kubeadminパスワード>
token=$(oc whoami -t)
domain=elasticsearch-openshift-logging.apps.ocp.powervs
header1="Authorization: Bearer ${token}"
# インデックス一覧取得
curl -sk -H "${header1}" https://${domain}/_cat/indices
### 標準出力↓
green open audit-000003 ltFqYg6jT9qZ_wV6QRGIuA 3 1 0 0 1.5kb 783b
green open app-000077 WGatHRvEQrSrEuUeNyEwyg 3 1 0 0 1.5kb 783b
green open app-000076 yZ4NX2HVRHymJBSCWs4J7Q 3 1 0 0 1.5kb 783b
green open app-000084 H-vMl6moTV6b70kF-79-Og 3 1 0 0 1.5kb 783b
green open infra-000007 ZOD5e6BwQdSVJrGYxCSRFQ 3 1 3391227 0 3.8gb 1.9gb
green open infra-000013 WUTnjBHMTSupphIesJf0JA 3 1 2620386 0 3.1gb 1.5gb
green open infra-000003 NqJ0DCXMSMWD7IUrApM_Zg 3 1 3304724 0 3.9gb 1.9gb
green open app-000093 olJZao2VS5OqmM3KpRINKQ 3 1 0 0 1.5kb 783b
green open infra-000014 i9KXTBGOQRO2vYfulIXmcw 3 1 1227321 0 1.5gb 776.5mb
green open app-000085 1m-sM80PQaa5ipZtvWY-PA 3 1 0 0 1.5kb 783b
green open infra-000005 hjyO2OJBTcKwozQzF0279A 3 1 3396051 0 3.8gb 1.9gb
green open audit-000005 _EmhHSEpQKSEL6tyVCKw1w 3 1 0 0 1.5kb 783b
green open .security pBEUFDweSnqDNL7icj7g2A 1 1 5 0 58.3kb 29.1kb
green open app-000095 b6l0rD6hTnKGWgF7L1GB4A 3 1 32 0 576.8kb 266.9kb
・・・
1.3. JSON形式でのエクスポート
Elasticsearchでは一度に取得できる件数が「10,000」に制限されています。
■「10,000」件以下の場合のJSON形式でのエクスポート例
# 準備
oc login -u kubeadmin -p <kubeadminパスワード>
token=$(oc whoami -t)
domain=elasticsearch-openshift-logging.apps.ocp.powervs
header1="Authorization: Bearer ${token}"
# 件数確認(32件)
curl -sk -H "${header1}" https://${domain}/_cat/count/app-000095
### 標準出力↓
1621402132 14:28:52 32
# JSON形式でのエクスポート
curl -sk -H "${header1}" https://${domain}/app-000095/_search?size=32 > app-000095.json
■「10,000」件より多い場合のJSON形式でのエクスポート例
# 準備
oc login -u kubeadmin -p <kubeadminパスワード>
token=$(oc whoami -t)
domain=elasticsearch-openshift-logging.apps.ocp.powervs
header1="Authorization: Bearer ${token}"
header2="Content-Type: application/json"
# 最初の10,000件とscroll_idを取得
curl -sk -H "${header1}" -H "${header2}" -d @test.json \
https://${domain}/infra-000007/_search?scroll=1m > infra-000007.json
scroll_id=`jq -r "._scroll_id" infra-000007.json | tr -d '\n'`
# 下記(1)~(3)を取得件数が『0』なるまで繰り返す
# (1) 次の10,000件を取得
curl -sk -H "${header1}" -H "${header2}" -d "{\"scroll\":\"1m\",\"scroll_id\":\"${scroll_id}\"}" \
https://${domain}/_search/scroll > temp.json
# (2) 件数確認
jq -r ".hits.hits | length" temp.json
# (3) 0件でない場合に実行
cat temp.json >> infra-000007.json
{
"size": "10000",
"_source": [
"kubernetes.container_name",
"kubernetes.namespace_name",
"kubernetes.pod_name",
"hostname",
"message",
"@timestamp"
]
}
■JSON出力例(1件)
{
"took" : 84,
"timed_out" : false,
"_shards" : {
"total" : 3,
"successful" : 3,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 32,
"max_score" : 1.0,
"hits" : [
{
"_index" : "app-000095",
"_type" : "_doc",
"_id" : "YzBhMGZhZDctZWRiNS00YmM3LThlNDUtOTFkNzJjZDhiMTM4",
"_score" : 1.0,
"_source" : {
"docker" : {
"container_id" : "30d33fb8920008f1e48c09b7b0fd2c4c51bfcb30240dc8e93f429faba7882e53"
},
"kubernetes" : {
"container_name" : "open-liberty",
"namespace_name" : "open-liberty",
"pod_name" : "open-liberty-0",
"container_image" : "docker.io/library/open-liberty:21.0.0.4-full-java11-openj9",
"container_image_id" : "docker.io/library/open-liberty@sha256:3b4da7bbf3a9298ffff3fbc9c311c65221192d10529c3a83312e9243c710e3c4",
"pod_id" : "bfad4941-c019-44a0-98e3-a8343dac2c08",
"host" : "worker-0",
"master_url" : "https://kubernetes.default.svc",
"namespace_id" : "fec969a8-4f62-4ef4-b82f-6c07919a3cf5",
"flat_labels" : [
"app=open-liberty",
"controller-revision-hash=open-liberty-56dd67f46b",
"statefulset_kubernetes_io/pod-name=open-liberty-0"
]
},
"message" : "[AUDIT ] CWWKE0085I: The server defaultServer is stopping because the JVM is exiting.",
"level" : "unknown",
"hostname" : "worker-0",
"pipeline_metadata" : {
"collector" : {
"ipaddr4" : "192.168.25.113",
"inputname" : "fluent-plugin-systemd",
"name" : "fluentd",
"received_at" : "2021-05-20T14:56:40.664956+00:00",
"version" : "1.7.4 1.6.0"
}
},
"@timestamp" : "2021-05-20T14:56:39.647267+00:00",
"viaq_msg_id" : "YzBhMGZhZDctZWRiNS00YmM3LThlNDUtOTFkNzJjZDhiMTM4"
}
}
]
}
}
1.4. JSON形式からCSV形式への変換
前項でエクスポートした「app-000095.json」をCSV形式に変換します。ダブルクオーテーションを除去し、message内のコンマはシャープに置換しています。
cat app-000095.json | jq -r '.hits.hits[] |
[ ._source."@timestamp",
._source.hostname,
._source.kubernetes.namespace_name,
._source.kubernetes.container_name,
._source.kubernetes.pod_name,
._source.message] | @csv' | sed -e 's/\"//g' -e 's/,/#/6g' > app-000095.csv
2021-05-20T14:56:39.647267+00:00,worker-0,open-liberty,open-liberty,open-liberty-0,[AUDIT ] CWWKE0085I: The server defaultServer is stopping because the JVM is exiting.
1.5. CSV形式ファイルの分割
「app-000095.csv」は複数のノードやネームスペースのログを集約したものなので分割します。
awk -F, '{out=$2"_"$3"_"$4".csv"; print $1","$5","$6 >> out}' app-000095.csv
ls -l
### 標準出力↓
-rw-r--r--. 1 root root 1400 5月 21 00:07 worker-0_open-liberty_open-liberty.csv
-rw-r--r--. 1 root root 1134 5月 21 00:07 worker-1_open-liberty_open-liberty.csv
2021-05-20T14:56:39.647267+00:00,open-liberty-0,[AUDIT ] CWWKE0085I: The server defaultServer is stopping because the JVM is exiting.
1.6. シェルスクリプトによる一括処理
一連の処理をシェルスクリプトとして実行します。前項ではインデックスの全件をエクスポートしましたが、ここでは日付「2021-05-20」を範囲指定して件数を取得し、0件以上だった場合にCSVファイルに出力しています。
mkdir /tmp/output
./export.sh
### 標準出力↓
[2021/05/21 20:25:33] JSON取得: infra-000013, 2620386
[2021/05/21 20:36:57] JSON取得: infra-000014, 2605663
[2021/05/21 20:46:52] JSON取得: app-000095, 32
[2021/05/21 20:46:52] JSON取得: infra-000015, 1579811
[2021/05/21 20:52:02] JSON取得: infra-000012, 1035194
[2021/05/21 20:55:13] CSV変換: /tmp/app-000095.json
[2021/05/21 20:55:13] CSV変換: /tmp/infra-000012.json
[2021/05/21 20:56:12] CSV変換: /tmp/infra-000013.json
[2021/05/21 20:58:43] CSV変換: /tmp/infra-000014.json
[2021/05/21 21:01:12] CSV変換: /tmp/infra-000015.json
[2021/05/21 21:02:42] CSVファイル分割: /tmp/app-000095.csv
[2021/05/21 21:02:42] CSVファイル分割: /tmp/infra-000012.csv
[2021/05/21 21:02:55] CSVファイル分割: /tmp/infra-000013.csv
[2021/05/21 21:03:27] CSVファイル分割: /tmp/infra-000014.csv
[2021/05/21 21:03:59] CSVファイル分割: /tmp/infra-000015.csv
[2021/05/21 21:04:19] CSVファイルソート: /tmp/infra-0__.tmp
[2021/05/21 21:04:19] CSVファイルソート: /tmp/infra-0_openshift-image-registry_node-ca.tmp
・・・
CSVファイルの行数が各インデックスの検索件数と一致しています。
wc -l /tmp/*.csv
### 標準出力↓
32 /tmp/app-000095.csv
1035194 /tmp/infra-000012.csv
2620386 /tmp/infra-000013.csv
2605663 /tmp/infra-000014.csv
1579811 /tmp/infra-000015.csv
7841086 合計
token=$(oc whoami -t)
header1="Authorization: Bearer ${token}"
header2="Content-Type: application/json"
domain=elasticsearch-openshift-logging.apps.ocp.powervs
# インデックス一覧取得
curl -sk -H "${header1}" https://${domain}/_cat/indices | egrep "app-|infra-" | awk '{print $3}' > /tmp/indices.lst
cat /tmp/indices.lst | while read index
do
# インデックスの件数取得
#count=`curl -sk -H "${header1}" https://${domain}/_cat/count/${index} | awk '{print $3}'`
count=`curl -sk -H "${header1}" -H "${header2}" https://${domain}/${index}/_count -d @count.json | jq .count`
if [ ${count}"X" = "X" ]; then
count = -1
fi
# JSON取得
if [ ${count} -gt 0 -a ${count} -le 10000 ]; then
# レコード数10000以下
echo [`date "+%Y/%m/%d %H:%M:%S"`] "JSON取得: ${index}, ${count}"
curl -sk -H "${header1}" https://${domain}/${index}/_search?size=${count} > /tmp/${index}.json
elif [ ${count} -gt 10000 ]; then
# レコード数10000より大きい
echo [`date "+%Y/%m/%d %H:%M:%S"`] "JSON取得: ${index}, ${count}"
curl -sk -H "${header1}" -H "${header2}" -d @export.json \
https://${domain}/${index}/_search?scroll=1m > /tmp/${index}.json
scroll_id=`jq -r "._scroll_id" /tmp/${index}.json | tr -d '\n'`
length=-1
while [ ${length} -ne 0 ]
do
curl -sk -H "${header1}" -H "${header2}" -d "{\"scroll\":\"1m\",\"scroll_id\":\"${scroll_id}\"}" \
https://${domain}/_search/scroll > /tmp/temp.json
length=`jq -r ".hits.hits | length" /tmp/temp.json`
cat /tmp/temp.json >> /tmp/${index}.json
rm -f /tmp/temp.json
done
# スクロールID削除
curl -sk -H "${header1}" -H "${header2}" -XDELETE -d "{ \"scroll_id\": \"${scroll_id}\"}" \
https://${domain}/_search/scroll > /dev/null
fi
done
# JSONからCSVへの変換
ls -l /tmp/*.json | grep -v "temp.json" | awk '{print $9}' | while read fname
do
echo [`date "+%Y/%m/%d %H:%M:%S"`] "CSV変換: ${fname}"
bname=`basename ${fname} | awk -F. '{print $1}'`
cat ${fname} | jq -r '.hits.hits[] |
[ ._source."@timestamp",
._source.hostname,
._source.kubernetes.namespace_name,
._source.kubernetes.container_name,
._source.kubernetes.pod_name,
._source.message] | @csv' | sed -e 's/\"//g' -e 's/,/#/6g' > /tmp/${bname}.csv
done
# CSVファイルをコンテナ単位に分割
ls -l /tmp/*.csv | awk '{print $9}' | while read fname
do
echo [`date "+%Y/%m/%d %H:%M:%S"`] "CSVファイル分割: ${fname}"
awk -F, '{out="/tmp/"$2"_"$3"_"$4".tmp"; print $1","$5","$6 >> out}' ${fname}
done
# コンテナ単位に分割したCSVファイルを時系列でソート
ls -l /tmp/*.tmp | awk '{print $9}' | while read fname
do
echo [`date "+%Y/%m/%d %H:%M:%S"`] "CSVファイルソート: ${fname}"
bname=`basename ${fname} | sed -e 's/\.tmp$//g'`
sort ${fname} > /tmp/output/${bname}.csv
rm -f ${fname}
done
{
"query": {
"range": {
"@timestamp": {
"lt": "2021-05-21",
"gte": "2021-05-20"
}
}
}
}
{
"size": "10000",
"_source": [
"kubernetes.container_name",
"kubernetes.namespace_name",
"kubernetes.pod_name",
"hostname",
"message",
"@timestamp"
],
"query": {
"range": {
"@timestamp": {
"lt": "2021-05-21",
"gte": "2021-05-20"
}
}
}
}