Fluend Elasticsearch plugin 문제 해결 - index가 계속해서 rollover될 때(무한 rollover)

잘 동작하는 줄 알았더니 하나 버그가 있었다. log가 적히지 않는 index를 계속해서 rollover하는 문제였다.

가령 다음과 같은 index가 있다고 하자.

park--2023.12.08-000001

park으로 시작하는 prefix를 가지는 data는 park이라는 하나의 pod에 대한 log를 기록한다고 하자. 하루마다 index의 suffix가 날짜로 바뀌고 이전 data들은 3개의 index로 rollover되도록 설정했다면

시간이 지나 rollover가 되어 3개의 rollover index를 만들어 질 것이다 12-08일이 지나면 12-09 index가 새로 만드어지고 12-08은 더 이상 rollover가 넘어가지 않아야 한다.

park--2023.12.08-000001
park--2023.12.08-000002
park--2023.12.08-000003
park--2023.12.09-000001

그러나, 12-09일이 지나갔는데, 12-08 index가 아직도 rollover되는 문제가 발생했다.

park--2023.12.08-000001
park--2023.12.08-000002
park--2023.12.08-000003
park--2023.12.08-000004
park--2023.12.08-000005
park--2023.12.08-000006
park--2023.12.09-000001
park--2023.12.09-000002
park--2023.12.09-000003
park--2023.12.10-000001

2012.12.08은 이제 더이상 log가 쓰이고 있지 않은 index임에도 불구하고 계속해서 rollover가 되고 있었다. 이는 결국 elasticsearch의 모든 shard를 다 소모하는 문제를 발생시킨다. 즉, 용량이 초과되어 문제를 발생시키는 것이 아니라 너무 많은 index로 인해 감당이 안되는 것이다.

이는 사실 굉장히 간단한 문제였는데, 이전 chapter에서도 말했듯이 rollover는 index-pattern이 아니라 rollover_alias과 index의 alias가 동일하기만 하면 계속 발생한다.

park--2023.12.08-000001rollover_aliaspark--2023.12.08였고, park--2023.12.09-000001rollover_aliaspark--2023.12.09였다. 따라서, rollover_alias가 서로 다름으로 현재 log가 쓰이고 있는 index가 park--2023.12.10-000001일지라도 park--2023.12.08는 계속 rollover되는 것이다. 더불어 park--2023.12.09도 log가 안쓰이는 index라도 계속해서 rollover될 것이다.

잘 확인해보면 매일마다 그 날의 rollover_alias이름를 가진 index-template가 만들어지고 있다는 것도 알 수 있다. 또한, 그 날의 index들도 alias가 그 날에 딱 매칭되는 alias를 가지고 있을 것이다.

즉 그림으로 정리하면 다음과 같다.

|-----park--2023.12.08-----|
|park--2023.12.08-000001   |
|park--2023.12.08-000002   |
|park--2023.12.08-000003   |
|park--2023.12.08-000004   | ---> log 쓰기 끝
|park--2023.12.08-000005   |
|park--2023.12.08-000006   |
|--------------------------|

|-----park--2023.12.09-----|
|park--2023.12.09-000001   |
|park--2023.12.09-000002   | ---> log 쓰기 끝
|park--2023.12.09-000003   | 
|--------------------------|

|-----park--2023.12.10-----|
|park--2023.12.10-000001   |---> log 쓰는 중 (is_write_index: true)
|                          |
|                          |
|--------------------------|

park--2023.12.10에서 is_write_indextrue이므로 주도권이 있고, 데이터가 계속 write되므로 여기서는 rollover가 발생해야한다. 그러나 문제는 park--2023.12.08park--2023.12.09이 계속해서 rollover이 되고 있다는 것이다. 다음날(12-11일)이 되면 다음과 같아진다.

|-----park--2023.12.08-----|
|park--2023.12.08-000001   |
|park--2023.12.08-000002   |
|park--2023.12.08-000003   |
|park--2023.12.08-000004   | ---> log 쓰기 끝
|park--2023.12.08-000005   |
|park--2023.12.08-000006   |
|park--2023.12.08-000007   |
|park--2023.12.08-000008   |
|park--2023.12.08-000009   |
|--------------------------|

|-----park--2023.12.09-----|
|park--2023.12.09-000001   |
|park--2023.12.09-000002   | ---> log 쓰기 끝
|park--2023.12.09-000003   |
|park--2023.12.09-000004   |
|park--2023.12.09-000005   |
|park--2023.12.09-000006   | 
|--------------------------|

|-----park--2023.12.10-----|
|park--2023.12.10-000001   |---> log 쓰기 끝
|park--2023.12.10-000002   |
|park--2023.12.10-000003   |
|--------------------------|

|-----park--2023.12.11-----|
|park--2023.12.11-000001   |---> log 쓰는 중 (is_write_index: true)
|                          |
|                          |
|--------------------------|

이런 일이 발생하는 이유는 park라는 index-prefix를 가진 data끼리 하나의 data뭉치처럼 관리되는 것이 아니라, 서로 다른 것처럼 관리되기 때문이다. 왜냐하면 각자의 alias가 다르고 각자의 index-template가 다르고 각자의 rollover_alais가 다르기 때문이다.

|-----park--2023.12.08-----|
|park--2023.12.08-000001   |
|park--2023.12.08-000002   |
|park--2023.12.08-000003   | ---> index-template: park--2023.12.08 
|park--2023.12.08-000004   | ---> alias: park--2023.12.08
|park--2023.12.08-000005   | ---> rollover_alias: park--2023.12.08
|park--2023.12.08-000006   |
|park--2023.12.08-000007   |
|park--2023.12.08-000008   |
|park--2023.12.08-000009   |
|--------------------------|

|-----park--2023.12.09-----|
|park--2023.12.09-000001   |
|park--2023.12.09-000002   | ---> index-template: park--2023.12.09 
|park--2023.12.09-000003   | ---> alias: park--2023.12.09
|park--2023.12.09-000004   | ---> rollover_alias: park--2023.12.09
|park--2023.12.09-000005   |
|park--2023.12.09-000006   | 
|--------------------------|

|-----park--2023.12.10-----|
|park--2023.12.10-000001   | ---> index-template: park--2023.12.10 
|park--2023.12.10-000002   | ---> alias: park--2023.12.10
|park--2023.12.10-000003   | ---> rollover_alias: park--2023.12.10
|--------------------------|

|-----park--2023.12.11-----|
|park--2023.12.11-000001   |---> log 쓰는 중 (is_write_index: true)
|                          |
|                          |
|--------------------------|

참고로 자신의 index가 어떤 alias를 가지고 있고 연결된 index-templaterollover_alias가 무엇인지는 지는 다음을 통해서 알 수 있다.

GET park--2023.12.08-000001/

응답을 잘보면 다음의 값이 있다.

{
  "park--2023.12.08-000001" : {
    "aliases" : {
      "park--2023.12.08" : {
        "is_write_index" : true
      }
    },
    ...
    "settings" : {
      "index" : {
        "lifecycle" : {
          "name" : "retention-2d",
          "rollover_alias" : "park--2023.12.08"
        },
...
}

park--2023.12.08-000001aliaspark--2023.12.08이고 rollover_aliaspark--2023.12.08이므로 서로 동일하여 rollover가 문제없이 동작했던 것이다. 그러나 이는 곳 날짜가 지나가서 park--2023.12.09-000001alias가 다르므로 park--2023.12.08 prefix를 가진 index에는 더이상 데이터가 쓰이고 있지 않아도 반복적으로 rollover가 동작하고 있던 것이다.

따라서 이 문제를 해결하기 위해서는 하나의 index prefix를 가지는 park 에 관해서는 같은 alias, rollover_alais를 두도록 하고, index의 alias도 rollover_alias와 동일하게 해주어야 한다는 것이다. 그림으로 표현하면 다음과 같다.

|-----park--2023.12.08-----|
|park--2023.12.08-000001   |
|park--2023.12.08-000002   |
|park--2023.12.08-000003   | ---> index-template: park
|park--2023.12.08-000004   | ---> alias: park
|park--2023.12.08-000005   | ---> rollover_alias: park
|park--2023.12.08-000006   |
|park--2023.12.08-000007   |
|park--2023.12.08-000008   |
|park--2023.12.08-000009   |
|--------------------------|

|-----park--2023.12.09-----|
|park--2023.12.09-000001   |
|park--2023.12.09-000002   | ---> index-template: park
|park--2023.12.09-000003   | ---> alias: park
|park--2023.12.09-000004   | ---> rollover_alias: park
|park--2023.12.09-000005   |
|park--2023.12.09-000006   | 
|--------------------------|

|-----park--2023.12.10-----|
|park--2023.12.10-000001   | ---> index-template: park
|park--2023.12.10-000002   | ---> alias: park
|park--2023.12.10-000003   | ---> rollover_alias: park
|--------------------------|

|-----park--2023.12.11-----|
|park--2023.12.11-000001   |---> log 쓰는 중 (is_write_index: true)
|                          |
|                          |
|--------------------------|

park으로 시작하는 index를 가지는 것들은 하나의 data set으로 생각해서 이들은 index-template를 같도록 하여, aliasrollover모두 같은 값을 쓰도록 하는 것이다. 이렇게 되면 이전의 무의미하게 아무런 log도 추가되지 않고 있던 index들이 rollover되는 일이 없어진다.

아래와 같이 될 것이다.

|-----park--2023.12.08-----|
|park--2023.12.08-000001   | ---> index-template: park
|park--2023.12.08-000002   | ---> alias: park
|park--2023.12.08-000003   |  ---> rollover_alias: park
|--------------------------|

|-----park--2023.12.09-----|
|park--2023.12.09-000001   | ---> index-template: park
|park--2023.12.09-000002   | ---> alias: park
|park--2023.12.09-000003   | ---> rollover_alias: park 
|--------------------------|

|-----park--2023.12.10-----|
|park--2023.12.10-000001   | ---> index-template: park
|park--2023.12.10-000002   | ---> alias: park
|park--2023.12.10-000003   | ---> rollover_alias: park
|--------------------------|

|-----park--2023.12.11-----|
|park--2023.12.11-000001   |---> log 쓰는 중 (is_write_index: true)
|                          |
|                          |
|--------------------------|

daemonset의 configmap에 index-template를 다음과 같이 변경했다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentd-config
  namespace: fluentd
data:
  index_template.json: |-
    {
      "index_patterns": [
        "park*"
      ],
      "settings": {
          "index": {
              "number_of_replicas": "1",
              "lifecycle": {
                "name": "retention-2d",
                "rollover_alias": "park"
              }
          }
      }
    }
...

park index pattern을 가지는 index에 대해서는 park rollover_alias를 지정하고 retention-2d를 적용하도록 하는 것이다.

다음으로 해주어야 할 것은 각 indexaliasrollover_alias와 동일한 park로 변경해야한다. 문제는 fluentd elasticsearch plugin에서 자동으로 설정되는 index의 alias를 설정해주고 있다는 것이다.

도대체 어디서 park--2023.12.08, park--2023.12.09, park--2023.12.10 이렇게 각 index에 대한 alias를 날짜별로 설정해주고 있는 것일까?? 바로 elasticsearch plugin의 logstash_format true이다. 이 값을 설정해주면 rollover_alias를 자동으로 index의 날짜별로 넣어준다.

그런데, 이 값이 없으면 timestampindex_name에 관해서 따로 설정해주어야 한다. 기가막힌 plugin이다.

그래서 fluentd elasticsearch plugin의 option값으로 logstash_format true를 없애주고, 다음의 option을 추가해주도록 하자.

index_name park
...
include_timestamp true

index_name은 지정한 index name을 만들어준다. 여기서는 park이라는 index를 만들어준다. 또한, 데이터 분석을 위한 timestamp가 필요하므로 include_timestamptrue로 설정해주면 logstash에서 제공해주는 timestamp처럼 값을 제공해준다.

이렇게 설정해주면 park index가 만들어지는데, index template에 의해서 rollover가 적용된다. 따라서 실제 이름은 날짜와 번호가 붙는다. 가령 park--2023.12.08-000001와 같이 된다. 그러나 이 모든 index에 대한 aliaspark이 된다. 아니 이 내용이 docs에 없는게 말이되는거냐

최종적으로 daemonset은 다음과 같이 된다.

  • fluentd-elasticsearch-deamonset.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: fluentd
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentd-config
  namespace: fluentd
data:
  index_template.json: |-
    {
      "index_patterns": [
        "${INDEX_PREFIX}"
      ],
      "settings": {
          "index": {
              "number_of_replicas": "1",
              "lifecycle": {
                "name": "${ILM_POLICY_ID}",
                "rollover_alias": "${INDEX_PREFIX}"
              }
          }
      }
    }
  fluent.conf: |- 
    
    <system>
      @log_level debug
    </system>
    
    <source>  
      @type tail
      @id in_tail_container_logs
      path "/var/log/pods/platform*/*/*.log, /var/log/pods/application*/*/*.log"
      exclude_path ["/var/log/pods/kube-system*",
                    "/var/log/pods/elastic*",
                    "/var/log/pods/fluentd*"]
      pos_file "/var/log/fluentd-containers.log.pos"
      read_from_head true
      tag "**"
      format json
      time_format %Y-%m-%dT%H:%M:%S.%NZ
      <parse>
        time_format %Y-%m-%dT%H:%M:%S.%NZ
        @type json
        time_type string
      </parse>
    </source>
    
    <filter **>
      @type record_transformer
      <record>
        hostname "#{Socket.gethostname}"
        tag ${tag}
      </record>
    </filter>
    <match **>
      @type elasticsearch
      host ${ELASTICSEARCH_HOST_IP}
      port ${ELASTICSEARCH_HOST_REST_PORT}
      index_name ${INDEX_PREFIX}
      type_name fluentd
      application_name ${APP_NAME}
      template_name ${INDEX_PREFIX}
      template_file /fluentd/etc/index_template.json
      enable_ilm true
      include_timestamp true
      ilm_policy_id ${ILM_POLICY_ID}
      ilm_policy {"policy":{"phases":{"hot":{"min_age":"0ms","actions":{"rollover":{"max_age":"6h","max_size":"3gb"}}},"delete":{"min_age":"2d","actions":{"delete":{}}}}}}
      # ilm_policy_overwrite false
    </match>
    
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluentd
  namespace: fluentd

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: fluentd
rules:
- apiGroups:
  - ""
  resources:
  - pods
  - namespaces
  verbs:
  - get
  - list
  - watch

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: fluentd
roleRef:
  kind: ClusterRole
  name: fluentd
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
  name: fluentd
  namespace: fluentd
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: fluentd
  labels:
    k8s-app: fluentd-logging
    version: v1
spec:
  selector:
    matchLabels:
      k8s-app: fluentd-logging
      version: v1
  template:
    metadata:
      labels:
        k8s-app: fluentd-logging
        version: v1
    spec:
      serviceAccount: fluentd
      serviceAccountName: fluentd
      tolerations:
      - key: node-role.kubernetes.io/control-plane
        effect: NoSchedule
      - key: node-role.kubernetes.io/master
        effect: NoSchedule
      containers:
      - name: fluentd
        image: fluentd-kubernetes-daemonset:v1-debian-elasticsearch
        env:
          - name: K8S_NODE_NAME
            valueFrom:
              fieldRef:
                fieldPath: spec.nodeName
          - name:  FLUENT_ELASTICSEARCH_HOST
            value: "YOUR_HOST_IP"
          - name:  FLUENT_ELASTICSEARCH_PORT
            value: "30920"
          - name: FLUENT_ELASTICSEARCH_SCHEME
            value: "http"
          # Option to configure elasticsearch plugin with self signed certs
          # ================================================================
          - name: FLUENT_ELASTICSEARCH_SSL_VERIFY
            value: "true"
          # Option to configure elasticsearch plugin with tls
          # ================================================================
          - name: FLUENT_ELASTICSEARCH_SSL_VERSION
            value: "TLSv1_2"
          # X-Pack Authentication
          # =====================
          - name: FLUENT_ELASTICSEARCH_USER
            value: "elastic"
          - name: FLUENT_ELASTICSEARCH_PASSWORD
            value: "changeme"
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: dockercontainerlogdirectory2
          mountPath: /var/lib/docker/containers
          readOnly: true
        # When actual pod logs in /var/log/pods, the following lines should be used.
        - name: dockercontainerlogdirectory
          mountPath: /var/log/pods
          readOnly: true
        - name: config
          mountPath: /fluentd/etc/fluent.conf
          subPath: fluent.conf
        - name: config
          mountPath: /fluentd/etc/index_template.json
          subPath: index_template.json
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      # When actual pod logs in /var/lib/docker/containers, the following lines should be used.
      # - name: dockercontainerlogdirectory
      #   hostPath:
      #     path: /var/lib/docker/containers
      # When actual pod logs in /var/log/pods, the following lines should be used.
      - name: dockercontainerlogdirectory
        hostPath:
          path: /var/log/pods
      - name: dockercontainerlogdirectory2
        hostPath:
          path: /var/lib/docker/containers
      - name: config
        configMap:
          name: fluentd-config

참고로 환경변수 값들은 각자가 원하는 값들을 넣도록 하자.

이전과 다른 것을 비교해보면 index_template.json파일에서 index_patternslifecycle을 직접 선택한다는 것이다. 여기서 ${ILM_POLICY_ID}ilm_policy_id와 같은 값이어야 한다. 또한, rollover_alias${INDEX_PREFIX}와 동일해야한다.

elasticsearch plugin에서 달라진건 logstash_formatlogstash_prefix가 사라지고, index_nameinclude_timestamp가 추가되었다는 것이다.

park이라는 alias 이제 잘 만들어졌는 지 확인해보면

GET park--2023.12.11-000001/

응답을 잘보면 다음의 값이 있다.

{
  "park--2023.12.11-000001" : {
    "aliases" : {
      "park" : {
        "is_write_index" : true
      }
    },
    ...
    "settings" : {
      "index" : {
        "lifecycle" : {
          "name" : "retention-2d",
          "rollover_alias" : "park"
        },
...
}

park이라는 alias, rollover_alias를 갖게된다.

다음 날이 되어서 새로 park--2023.12.12-000001이 만들어지면 park alias를 가져야한다.

```sh
GET park--2023.12.11-000001/

응답을 잘보면 다음의 값이 있다.

{
  "park--2023.12.11-000001" : {
    "aliases" : {
      "park" : {
        "is_write_index" : true
      }
    },
    ...
    "settings" : {
      "index" : {
        "lifecycle" : {
          "name" : "retention-2d",
          "rollover_alias" : "park"
        },
...
}

이것이 마지막 fluentd elasticsearch daemonset이기를 바란다. 참고로 사용한 elasticsearch + kibana는 다음과 같다.

  • ek.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: elastic
---
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: elastic
  name: elasticsearch-config
  labels:
    app: elasticsearch
    #role: data
data:
  elasticsearch.yml: |-
    cluster.name: ${CLUSTER_NAME}	# 클러스터 이름
    #node.name: ${NODE_NAME}		# 노드 이름
    #discovery.seed_hosts: ${NODE_LIST}			# 노드 리스트
    #cluster.initial_master_nodes: ${MASTER_NODES}	# 마스터 노드
    network.host: 0.0.0.0			# 외부 접근
    #node:					# 노드 정보 옵션
    #  master: false
    #  data: true
    #  ingest: false
    #xpack.security.enabled: true		# X pack의 경우 보안설정
    #xpack.monitoring.collection.enabled: true
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: elasticsearch-pv
  labels:
    name: elasticsearch
spec:
  storageClassName: ""
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  claimRef:
    namespace: elastic
    name: elasticsearch-pvc
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: /tmp/es
    type: DirectoryOrCreate
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: elasticsearch-pvc
  namespace: elastic
spec:
  storageClassName: ""
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: elasticsearch
  namespace: elastic
  labels:
    app: elasticsearch
spec:
  replicas: 1
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      containers:
      - name: elasticsearch
        image: elasticsearch:7.17.14
        env:
        - name: CLUSTER_NAME
          value: elastic-cluster
        - name: discovery.type
          value: single-node
        ports:
        - containerPort: 9200
        - containerPort: 9300
        volumeMounts:
        - name: config
          mountPath: /usr/share/elasticsearch/config/elasticsearch.yml
          readOnly: true
          subPath: elasticsearch.yml
        - name: elasticsearch-persistent-storage
          mountPath: /usr/share/elasticsearch/data
        resources:
          requests:
            memory: 8Gi
            cpu: 8
          limits:
            memory: 32Gi
      initContainers:
      - name: fix-permissions
        image: busybox:elasticsearch
        command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]
        securityContext:
          privileged: true
        volumeMounts:
        - name: elasticsearch-persistent-storage
          mountPath: /usr/share/elasticsearch/data
      volumes:
      - name: config
        configMap:
          name: elasticsearch-config
      - name: elasticsearch-persistent-storage
        persistentVolumeClaim:
          claimName: elasticsearch-pvc
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: elasticsearch
  name: elasticsearch-svc
  namespace: elastic
spec:
  ports:
  - name: elasticsearch-rest
    nodePort: 30920
    port: 9200
    protocol: TCP
    targetPort: 9200
  - name: elasticsearch-nodecom
    nodePort: 30930
    port: 9300
    protocol: TCP
    targetPort: 9300
  selector:
    app: elasticsearch
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kibana
  namespace: elastic
  labels:
    app: kibana
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kibana
  template:
    metadata:
      labels:
        app: kibana
    spec:
      containers:
      - name: kibana
        image: kibana:7.17.14
        env:
        - name: SERVER_NAME
          value: kibana.kubenetes.example.com
        - name: ELASTICSEARCH_HOSTS
          value: http://elasticsearch-svc:9200
        ports:
        - containerPort: 5601
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: kibana
  name: kibana-svc
  namespace: elastic
spec:
  ports:
  - nodePort: 30561
    port: 5601
    protocol: TCP
    targetPort: 5601
  selector:
    app: kibana
  type: NodePort

0개의 댓글

Powered by GraphCDN, the GraphQL CDN