ICPはコンテナのログを自動的に集めて自動的にElasticsearchに保存します。デフォルトではElasticsearchの保管日数は1日です。エンタープライズ環境では、ログを一定期間保管する要件があったり、既存のログ監視の仕組みがあったりするので、ELKスタックで監視というより既存の仕組みで監視したい場合も多そうです。
最初から/var/log/containers/*.log
を直接保存したり、監視することも考えられると思います。
あるいはLogstashからsyslogに送ることも考えられます。その場合の方法は、@teruq さんの以下の記事が参考になります。
(参考)
IBM Cloud Private(ICP)でコンテナログをsyslogに転送する - logstash差替編
ここでは、Logstashでファイルに保存する方法を試してみます。ICPのバージョンは2.1.0.3で確認しています。
Logstashのファイルプラグイン
LogstashのOutputプラグインでFileプラグインがあり、このプラグインはICP標準のLogstashに入っています。
Logstash Reference » Output plugins » File output plugin
このプラグインを使いますが、どのようなパスに、どのようなフォーマットでログを出力するかは設計・検討する必要があります。
ICPのロギング動作
ICPのデフォルトの仕組みでは、コンテナがHello World!
と標準出力に出すと、Dockerのlogging driverによって/var/log/containers/hogehoge.log
に以下のような形式でログが追記されます。
{"log":"Hello World!","stream":"stdout","time":"2018-07-23T07:16:54.353021511Z"}
これをDaemonSetとして動作しているFilebeatが監視してLogstashに転送し、Logstashのフィルターを通ると、FilebeatやLogstashがフィールドを付加したり削除したりしてElasticsearchに送ります。そのあたりの動作はこちらに書きました。
IBM Cloud Privateのログ収集の仕組みの調査(Logstashまで)
その結果Logstashを出るときには以下の様なデータになります。
{
"offset": 6275,
"pod": {
"ip": "10.1.170.243"
},
"log": "Hello World!",
"kubernetes.pod": "hello-5564fcdfc6-v2n72",
"kubernetes.container_name": "hello",
"input_type": "log",
"source": "/var/log/containers/hello-5564fcdfc6-v2n72_ns3_hello-32ee39e2d8d4dec3e65964b9f579456269bf97e8a4d742ccd65fb906e33363c6.log",
"type": "kube-logs",
"tags": [
"k8s-app",
"beats_input_raw_event"
],
"kubernetes.container_id": "32ee39e2d8d4dec3e65964b9f579456269bf97e8a4d742ccd65fb906e33363c6",
"node": {
"hostname": "172.30.1.224"
},
"@timestamp": "2018-07-23T07:16:54.353Z",
"stream": "stdout",
"kubernetes.namespace": "ns3",
"beat": {
"hostname": "logging-elk-filebeat-ds-ln2gp",
"name": "logging-elk-filebeat-ds-ln2gp",
"version": "5.5.1"
},
"@version": "1",
"time": "2018-07-23T07:16:54.353021511Z"
}
この中で重要そうなフィールドは以下です。
フィールド | 説明 |
---|---|
log | ログの本文 |
[pod][ip] | FilebeatのPodのIPアドレス |
time | logging driverでのロギング時刻 |
[node][hostname] | FilebeatのPodが稼働していたノードのホスト名 |
kubernetes.namespace | Namespace名 |
kubernetes.pod | Pod名 |
kubernetes.container_name | コンテナ名 |
kubernetes.container_id | コンテナID |
なお、ログがjsonの場合はパースされてさらにフィールドが増えますが、もとのjson形式のログ本文はlog
に入ったままです。
このデータを、例えば/log/%{kubernetes.namespace}.log
というファイルに、
%{time}% {kubernetes.pod} {%log}
という形式でを出力するとしたら、
Logstashのoutput設定は、
output {
file {
path => "/log/%{kubernetes.namespace}.log"
codec => line { format => "%{time}% {kubernetes.pod} {%log}" }
}
}
になります。フォーマット等は検討が必要です。
期待値としては次の様なログになります。
2018-05-31T05:49:56.659607745Z hello-758d49f796-g9nm5 Hello World!
ICPの設定変更
ICPの設定を変更してコンテナログをファイルに保存するようにします。
PV/PVCの作成
Logstashがファイルを保存するPVを作成します。
ここではhostPathで作成することにしてディレクトリーを用意します。logstashコンテナ内では1000番のUIDでプロセスが稼働するので、1000番のユーザーがディレクトリーに書き込める必要があります。ここでは777にしてしまいます。
root@myicp01:/export# pwd
/export
root@myicp01:/export# mkdir logstash
root@myicp01:/export# chmod 777 logstash
root@myicp01:/export# ls -l
total 12
drwxrwxrwx 2 root root 4096 Jul 4 10:46 logstash
drwxr-xr-x 15 root root 4096 Jul 4 09:59 MC_jenkins02
drwxr-xr-x 3 root root 4096 Jul 4 07:15 MC_microclimate02
root@myicp01:/export#
PV/PVCを作成します。
apiVersion: v1
kind: PersistentVolume
metadata:
name: logstash-pv
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 4Gi
hostPath:
path: /export/logstash
type: ""
storageClassName: logstash
persistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: logstash-pvc
namespace: kube-system
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 4Gi
storageClassName: logstash
kubectl apply -f logstash-pv.yaml
PVCのマウント
作成したPVCをマウントするようにLogstashのDeploymentの設定を書き換えます。
kubectl edit deploy -n kube-system logging-elk-logstash
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
creationTimestamp: null
generation: 1
labels:
app: logging-elk-elasticsearch
chart: ibm-icplogging-1.0.0
component: logstash
heritage: Tiller
release: logging
role: logstash
name: logging-elk-logstash
selfLink: /apis/extensions/v1beta1/namespaces/kube-system/deployments/logging-elk-logstash
spec:
minReadySeconds: 5
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 3
selector:
matchLabels:
app: logging-elk-elasticsearch
component: logstash
heritage: Tiller
release: logging
role: logstash
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
annotations:
productID: none
productName: Logstash
productVersion: 5.5.1
creationTimestamp: null
labels:
app: logging-elk-elasticsearch
chart: ibm-icplogging
component: logstash
heritage: Tiller
release: logging
role: logstash
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: management
operator: In
values:
- "true"
containers:
- command:
- /bin/bash
- /scripts/entrypoint.sh
env:
- name: LS_JAVA_OPTS
value: -Xmx512m -Xms512m
- name: CFG_BASEDIR
value: /usr/share/logstash
image: ibmcom/logstash:5.5.1
imagePullPolicy: IfNotPresent
name: logstash
ports:
- containerPort: 5044
protocol: TCP
resources:
limits:
memory: 1Gi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /usr/share/logstash/pipeline
name: pipeline-config
- mountPath: /usr/share/logstash-config-x86
name: logstash-config-x86
- mountPath: /usr/share/logstash-config-ppc
name: logstash-config-ppc
- mountPath: /usr/share/logstash/data
name: data
- mountPath: /scripts
name: entrypoint
- mountPath: /log # 追加
name: log # 追加
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
tolerations:
- effect: NoSchedule
key: dedicated
operator: Exists
volumes:
- configMap:
defaultMode: 420
items:
- key: k8s.conf
path: k8s.conf
name: logging-elk-logstash-config
name: pipeline-config
- configMap:
defaultMode: 420
items:
- key: logstash.yml-x86
path: logstash.yml
name: logging-elk-logstash-config
name: logstash-config-x86
- configMap:
defaultMode: 420
items:
- key: logstash.yml-ppc
path: logstash.yml
name: logging-elk-logstash-config
name: logstash-config-ppc
- configMap:
defaultMode: 365
items:
- key: logstash-entrypoint.sh
path: entrypoint.sh
- key: map-config.sh
path: map-config.sh
name: logging-elk-elasticsearch-entrypoint
name: entrypoint
- emptyDir: {}
name: data
- persistentVolumeClaim: # 追加
claimName: logstash-pvc # 追加
name: log # 追加
status: {}
Logstash設定の変更
Logstashの設定はConfigMapにあるのでこれを書き換えます。
kubectl edit cm -n kube-system logging-elk-logstash-config
apiVersion: v1
data:
k8s.conf: |-
input {
beats {
port => 5044
}
}
filter {
if [type] == "kube-logs" {
mutate {
rename => { "message" => "log" }
remove_field => ["host"]
}
date {
match => ["time", "ISO8601"]
}
# Sample:
# tiller-1279707952-sgd08_kube-system_tiller-0c51282d195d1c21307f6a48b9118d690441cda498fc5a2790c043407eab235b.log
# filebeat-j357d_default_filebeat-1a3113e374ad7937a3efa36c4bb42b46b976bcd7cd96223e6b9e6e3df08d802a.log
# appstdoutpod_default_app-stdout-container-01c2e7a7b105d9141825ea3ae5634b580fdd20a5a4ee890cdbe0816ca002623c.log
# unified-router-4047118581-sm913_kube-system_unified-router-ddda8a8cbcb74c45b64a4b18997b4f2928c998a37e45037cd0304eaa56d1634f.log
dissect {
mapping => {
"source" => "/var/log/containers/%{kubernetes.pod}_%{kubernetes.namespace}_%{container_file_ext}"
}
}
dissect {
mapping => {
"container_file_ext" => "%{container}.%{?file_ext}"
}
remove_field => ["host", "container_file_ext"]
}
grok {
"match" => {
"container" => "^%{DATA:kubernetes.container_name}-(?<kubernetes.container_id>[0-9A-Za-z]{64,64})"
}
remove_field => ["container"]
}
}
}
filter {
# Drop empty lines
if [log] =~ /^\s*$/ {
drop { }
}
# Attempt to parse JSON, but ignore failures and pass entries on as-is
json {
source => "log"
skip_on_invalid_json => true
}
}
output {
elasticsearch {
hosts => "elasticsearch:9200"
index => "logstash-%{+YYYY.MM.dd}"
document_type => "%{[@metadata][type]}"
}
# 追加ここから
file {
path => "/log/%{kubernetes.namespace}.log"
codec => line { format => "%{time} %{kubernetes.pod} %{log}" }
}
# 追加ここまで
}
logstash.yml-ppc: |-
# ppc64le-only config. Must not include refs to X-Pack.
config.reload.automatic: true
http.host: "0.0.0.0"
path.config: /usr/share/logstash/pipeline
xpack.monitoring.enabled: false
# end
logstash.yml-x86: |-
# x86-only config. Must include refs to X-Pack.
config.reload.automatic: true
http.host: "0.0.0.0"
path.config: /usr/share/logstash/pipeline
xpack.monitoring.enabled: false
# end
kind: ConfigMap
metadata:
creationTimestamp: null
labels:
app: logging-elk-elasticsearch
chart: ibm-icplogging-1.0.0
component: logstash
heritage: Tiller
release: logging
name: logging-elk-logstash-config
selfLink: /api/v1/namespaces/kube-system/configmaps/logging-elk-logstash-config
設定はしばらく待つと反映されます。
確認
Namespace別にログが出力されていることを確認します。
root@myicp01:/export/logstash# pwd
/export/logstash
root@myicp01:/export/logstash# ls -l
total 3880
-rw-r--r-- 1 ubuntu ubuntu 3950084 Jul 4 14:34 kube-system.log
-rw-r--r-- 1 ubuntu ubuntu 17719 Jul 4 14:33 ns1.log
root@myicp01:/export/logstash# tail -5 ns1.log
2018-07-04T14:33:43.508226495Z sample1-586dbc87fc-mx46s [AUDIT ] CWPKI0803A: SSL certificate created in 1.913 seconds. SSL key file: /opt/ibm/wlp/output/defaultServer/resources/security/key.jks
2018-07-04T14:33:43.876014975Z sample1-586dbc87fc-mx46s [AUDIT ] CWWKT0016I: Web application available (default_host): http://sample1-586dbc87fc-mx46s:9080/Sample1/
2018-07-04T14:33:43.879160256Z sample1-586dbc87fc-mx46s [AUDIT ] CWWKZ0001I: Application Sample1 started in 0.314 seconds.
2018-07-04T14:33:43.916935058Z sample1-586dbc87fc-mx46s [AUDIT ] CWWKF0012I: The server installed the following features: [jsp-2.3, ejbLite-3.2, managedBeans-1.0, servlet-3.1, jsf-2.2, beanValidation-1.1, ssl-1.0, jndi-1.0, appSecurity-2.0, jsonp-1.0, jdbc-4.1, jaxrs-2.0, el-3.0, jaxrsClient-2.0, json-1.0, jpaContainer-2.1, cdi-1.2, distributedMap-1.0, webProfile-7.0, websocket-1.1, jpa-2.1].
2018-07-04T14:33:43.927439222Z sample1-586dbc87fc-mx46s [AUDIT ] CWWKF0011I: The server defaultServer is ready to run a smarter planet.
root@myicp01:/export/logstash#
補足
ここでは、デフォルトのicp-loggingを編集していますが、ICPをバージョンアップした場合に何が起こるかわからないので、おそらく、デフォルトをは別にicp-loggingのHelmチャートをリリースし、そちらをカスタマイズするほうがよいかもしれません。
ログ出力のフォーマットはもう少し真面目に検討した結果、以下がよいと思いました。
# 追加ここから
file {
path => "/log/%{kubernetes.namespace}/%{kubernetes.pod}-%{[node][hostname]}-%{+YYYYMMdd}.log"
codec => line { format => "%{time} %{kubernetes.container_name} %{kubernetes.container_id} %{stream} %{log}" }
}
# 追加ここまで