オンプレKubernetesでFluentd+Elasticsearch+Kibanaのログ収集環境を作ってみた

Kubernetesの魅力に取り憑かれ、社内サーバで稼働しているDockerコンテナをオンプレKubernetes環境を作って移行しています。

Dockerコンテナはコンテナが破棄されるとログも消えてしまうため、
DockerComposeでFluentd+Elasticsearch+Kibanaのログ収集、分析環境を用意していました。

今回はオンプレのKubernetesでも同じような事をしたくて、Fluentd+Elasticsearch+Kibanaの環境を作ってみました。
single nodeで構築しているのでシンプルにvolumeはPersistentVolumeを使わずhostPathでノード上のディレクトリに保存するようにしました。

環境

社内サーバにRancherでsingle nodeのKubernetes環境を構築

Docker: v19.03.6
Kubernetes: v1.17.4
elasticsearch: v7.6.1
kibana: v7.6.1
fluentd: v1.10.1

Namespaceを作成

Namespaceを作成します。

$ kubectl create namespace kube-logging
namespace/kube-logging created

Elasticsearch

ログを保持、検索するためにElasticsearchを用意。
今後、PersistentVolumeを使う事も考慮しStatefulSetで作成しました。
Podから接続させるためのServiceをclusterIP: NoneとすることでHeadless Serviceとして動作させます。

elasticsearch.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: elasticsearch
  namespace: kube-logging
spec:
  serviceName: elasticsearch
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      containers:
      - name: elasticsearch
        image: elasticsearch:7.6.1
        env:
        - name: discovery.type
          value: single-node
        resources:
          requests:
            cpu: "100m"
          limits:
            cpu: "500m"
        ports:
        - containerPort: 9200
          name: rest
        - containerPort: 9300
          name: inter-node
        volumeMounts:
          - mountPath: /usr/share/elasticsearch/data
            name: elasticsearchdata
      initContainers:
        - name: fix-permissions
          image: busybox
          command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]
          securityContext:
            privileged: true
          volumeMounts:
            - mountPath: /usr/share/elasticsearch/data
              name: elasticsearchdata
        - name: increase-vm-max-map
          image: busybox
          command: ["sysctl", "-w", "vm.max_map_count=262144"]
          securityContext:
            privileged: true
        - name: increase-fd-ulimit
          image: busybox
          command: ["sh", "-c", "ulimit -n 65536"]
          securityContext:
            privileged: true
      volumes:
        - name: elasticsearchdata
          hostPath:
            path: /var/elasticsearch/data
---
apiVersion: v1
kind: Service
metadata:
  name: elasticsearch
  namespace: kube-logging
spec:
  clusterIP: None
  selector:
    app: elasticsearch
  ports:
  - port: 9200
    name: rest
  - port: 9300
    name: inter-node

マニフェストを適用

$ kubectl apply -f elasticsearch.yml
statefulset.apps/elasticsearch created
service/elasticsearch created

Podが起動しているか確認

$ kubectl get pods -n kube-logging
NAME              READY   STATUS    RESTARTS   AGE
elasticsearch-0   1/1     Running   0          108s

Kibana

ログを可視化、分析するためのUIとしてKibanaを用意。

kibana.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kibana
  namespace: kube-logging
spec:
  selector:
    matchLabels:
      app: kibana
  template:
    metadata:
      labels:
        app: kibana
    spec:
      containers:
      - name: kibana
        image: kibana:7.6.1
        env:
        - name: ELASTICSEARCH_URL
          value: http://elasticsearch:9200
        - name: XPACK_SECURITY_ENABLED
          value: "true"
        ports:
        - containerPort: 5601
---
apiVersion: v1
kind: Service
metadata:
  name: kibana
  namespace: kube-logging
spec:
  selector:
    app: kibana
  type: NodePort
  ports:
  - port: 5601
    targetPort: 5601

マニフェストを適用

$ kubectl apply -f kibana.yml
deployment.apps/kibana created
service/kibana created

Podの起動を確認

$ kubectl get pods -n kube-logging
NAME                      READY   STATUS    RESTARTS   AGE
elasticsearch-0           1/1     Running   0          5m32s
kibana-5d878b7f74-qtss7   1/1     Running   0          113s

サービスの起動、NodePortを確認

$ kubectl get svc -n kube-logging
NAME            TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)             AGE
elasticsearch   ClusterIP   None          <none>        9200/TCP,9300/TCP   4m33s
kibana          NodePort    10.43.95.56   <none>        5601:30839/TCP      53s

SerivceをNordPortで作成しているのでhttp://ノードIP:ノードポートでKibanaを開く事ができる

Fluentd

Podが標準出力したログを収集してElasticsearchに送るためにFluentdを用意。
今回single nodeなのであまり恩恵は受けないが、各Nodeで実行するPodなのでDaemonSetで作成する。

まずFluentdを実行させるためのServiceAccount、ClusterRole、ClusterRoleBindingを作成する。

fluentd-rbac.yml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluentd
  namespace: kube-logging
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: fluentd
rules:
- apiGroups:
  - ""
  resources:
  - pods
  - namespaces
  verbs:
  - get
  - list
  - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: fluentd
roleRef:
  kind: ClusterRole
  name: fluentd
  apiGroup: rbac.authorization.k8s.io
subjects:
  - kind: ServiceAccount
    name: fluentd
    namespace: kube-logging

マニフェストを適用

$ kubectl apply -f fluentd-rbac.yml
serviceaccount/fluentd created
clusterrole.rbac.authorization.k8s.io/fluentd created
clusterrolebinding.rbac.authorization.k8s.io/fluentd created

次にFluentd本体をデプロイ
imageはelasticsearchを使うためfluent/fluentd-kubernetes-daemonset:v1.10.1-debian-elasticsearch7-1.0を指定した

fluentd.yml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: kube-logging
spec:
  selector:
    matchLabels:
      name: fluentd
  template:
    metadata:
      labels:
        name: fluentd
    spec:
      serviceAccount: fluentd
      serviceAccountName: fluentd
      tolerations:
      - key: node-role.kubernetes.io/master
        effect: NoSchedule
      containers:
      - name: fluentd
        image: fluent/fluentd-kubernetes-daemonset:v1.10.1-debian-elasticsearch7-1.0
        env:
          - name:  FLUENT_ELASTICSEARCH_HOST
            value: "elasticsearch.kube-logging.svc.cluster.local"
          - name:  FLUENT_ELASTICSEARCH_PORT
            value: "9200"
          - name: FLUENT_ELASTICSEARCH_SCHEME
            value: "http"
          - name: FLUENTD_SYSTEMD_CONF
            value: disable
        resources:
          limits:
            memory: 512Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers

マニフェストを適用

$ kubectl apply -f fluentd.yml
daemonset.apps/fluentd created

Podの起動を確認

$ kubectl get pods -n kube-logging
NAME                      READY   STATUS    RESTARTS   AGE
elasticsearch-0           1/1     Running   0          10m
fluentd-vzlkg             1/1     Running   0          27s
kibana-5d878b7f74-qtss7   1/1     Running   0          7m2s

Podの起動が確認できたらKibanaにアクセスしCreate index patternでlogstash-*でIndex patternを作成するとDiscoverでログが収集されているのが確認できる。

まとめ

Fluentd+Elasticsearch+Kibanaのログ収集環境をオンプレKubernetesでも比較的シンプルに構築できたと思います。

DockerComposeでコンテナのログをFluentdに収集させる場合はlogging設定にfluentdを指定しないといけませんでしたが
Kubernetes環境では特に指定しなくてもログ収集ができるので楽でした。

さて、次は何をKubernetesに乗せようか。