본문 바로가기

Kubernetes

Kubernetes EFK

EFK는 elasticsearch, fluentd, kibana를 묶어 표현한 단어

각 노드의 파드들이 가지는 로그를 취합해 검색하여 시각화하는 EFK의 대한 예제

 

우선 namespace를 정의

#elasticsearch-namespace.yaml

kind: Namespace
apiVersion: v1
metadata:
  name: kube-logging

역시 원하는데로 설정하여 사용하여도 좋으나 이후 나올 코드들의 네임 스페이스도 변경해야 함

 

elasticsearch의 경우 persistentvolume에 로그를 저장하여 사용하기 때문에 persistentvolume을 정의

#persistentvolume.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: data
  namespace: kube-logging
spec:
  capacity:
    storage: 5Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: data
  hostPath:
    path: /mnt/disk/vol1
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - node1

 

위의 persistentvolume과 연결할 storageclass 정의

#storageclass.yaml

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: data
  namespace: kube-logging
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

 

실행 후 터미널에서 아래 명령어를 실행해 권한을 변경

chmod 777 /mnt/disk/vol1

뒤의 /mnt/disk/vol1 은 pv에서 설정한 루트

 

elasticsearch의 특성 상 종료되더라도 로그기록을 유지한채로 다시 시작되어야 하기 때문에 statefulset을 사용

#elasticsearch-statefulset.yaml

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: es-cluster
  namespace: kube-logging
spec:
  serviceName: elasticsearch-svc
  replicas: 1
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      containers:
      - name: elasticsearch-svc
        image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0
        resources:
            limits:
              cpu: 1000m
            requests:
              cpu: 100m
        ports:
        - containerPort: 9200
          name: rest
          protocol: TCP
        - containerPort: 9300
          name: inter-node
          protocol: TCP
        volumeMounts:
        - name: data
          mountPath: /usr/share/elasticsearch/data
        env:
          - name: cluster.name
            value: k8s-logs
          - name: node.name
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: discovery.seed_hosts
            value: "es-cluster-0.elasticsearch"
          - name: cluster.initial_master_nodes
            value: "es-cluster-0"
          - name: ES_JAVA_OPTS
            value: "-Xms512m -Xmx512m"
      initContainers:
      - name: fix-permissions
        image: busybox
        command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]
        securityContext:
          privileged: true
        volumeMounts:
        - name: data
          mountPath: /usr/share/elasticsearch/data
      - 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
  volumeClaimTemplates:
  - metadata:
      name: data
      namespace: kube-logging
      labels:
        app: elasticsearch
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: data
      resources:
        requests:
          storage: 5Gi

 

elasticsearch의 service 정의

kind: Service
apiVersion: v1
metadata:
  name: elasticsearch-svc
  namespace: kube-logging
  labels:
    app: elasticsearch
spec:
  #type: ClusterIP
  selector:
    app: elasticsearch
  clusterIP: None
  ports:
    - port: 9200
      name: rest
    - port: 9300
      name: inter-node

헤드리스 서비스로 구성하였지만 연결 문제가 존재할 경우 clusterIP: None을 주석처리 하고 type: ClusterIP의 주석처리를 해제하여 내부 IP를 할당해 사용

하지만 스테이트풀셋은 헤드리스 서비스를 기본으로 사용하기 때문에 가급적 유지하길 바람

 

위의 사항까지 잘 구현했다면 pv와 pvc의 정보를 확인하여 status가 bound 상태인지 확인

만약 바운드상태가 아니라면 kubectl delete -f <yaml file> #ex: kubectl delete -f elasticsearch-statefulset.yaml의 명령어를 적용해 지우고 네임 스페이스와 엘라스틱서치부터 구현한 후 다시 시도

 

이제 각 노드에 배포해 로그를 가져올 fluentd를 정의

하나의 yaml에 serviceaccount, clusterrole, clusterrolebinding, daemonset을 모두 정의하였으니 잘 살펴야 함

#위에서 부터 serviceaccount, clusterrole, clusterrolebinding, daemonset 정의

apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluentd
  namespace: kube-logging
  labels:
    app: fluentd
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: fluentd
  labels:
    app: 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: kube-logging
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: kube-logging
  labels:
    app: fluentd
spec:
  selector:
    matchLabels:
      app: fluentd
  template:
    metadata:
      labels:
        app: fluentd
    spec:
      serviceAccount: fluentd
      serviceAccountName: fluentd
      tolerations:
      - key: node-role.kubernetes.io/master
        effect: NoSchedule
      containers:
      - name: fluentd
        image: fluent/fluentd-kubernetes-daemonset:v1.4.2-debian-elasticsearch-1.1
        env:
          - name:  FLUENT_ELASTICSEARCH_HOST
            value: "elasticsearch-svc.kube-logging.svc.cluster.local" # elasticsearch의 svc name과 네임스페이스를 잘 맞춰서 적용
          - 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
#      - name: fluentd-extra-config
#        configMap: # 로그 레코드를 처리하는 방법을 지정한 구성 파일 매핑
#          name: "fluentd-hands-on-config"
        env:
          - name:  FLUENT_ELASTICSEARCH_HOST
            value: "elasticsearch-svc.kube-logging.svc.cluster.local" # elasticsearch의 svc name과 네임스페이스를 잘 맞춰서 적용

따로 떼어놓은 부분은 사소한 실수로 연결 오류를 만들 수 있으니 주의해서 보길 바람

 

해당 fluentd yaml로 모든 로그를 가져와 검색이 가능하지만 만약 특정 필터를 적용해 선택적으로 로그를 가져오고 싶다면 주석처리한 volume부분을 해제하고 아래의 configmap.yaml을 적용

apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentd-hands-on-config
  namespace: kube-system
data: # 플루언트 구성이 data 요소에 들어감
  fluentd-hands-on.conf: |

    <match kubernetes.**istio**> # kubernetes로 시작하고 태그 이름 중에 istio라는 문자열이 있는지 찾음
      @type rewrite_tag_filter # 태그 이름을 변경한 후 플루언티드 라우팅 엔진으로 로그 레코드를 재전송
      <rule>
        key log # 로그 레코드에서 log 필드를 찾도록 key 필드를 log로 설정
        pattern ^(.*)$ # 모든 문자열과 일치
        tag istio.${tag} # 태그 앞에 istio 문자열을 붙임
      </rule>
    </match>

    <match kubernetes.**hands-on**>
      @type rewrite_tag_filter
      <rule>
        key log
        pattern ^(.*)$
        tag spring-boot.${tag} # spring-boot 태그 붙임
      </rule>
    </match>

    <match spring-boot.**>
      @type rewrite_tag_filter
      <rule> # log 필드가 타임스탬프로 시작하는지 확인
        key log
        pattern /^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}.*/
        tag parse.${tag}
      </rule>
      <rule> # log 필드가 타임스탬프로 시작하지 않으면 스택 추적 로그 레코드로 처리
        key log
        pattern /^.*/
        tag check.exception.${tag}
      </rule>
    </match>

    <match check.exception.spring-boot.**>
      @type detect_exceptions # 여러 줄로 된 예외를 스택 추적을 포함하는 한 줄의 로그 레코드로 취합하고자 사용
      languages java
      remove_tag_prefix check # 무한 루프를 제거하기 위해 check 접두어 제거
      message log
      multiline_flush_interval 5
    </match>

    <filter parse.spring-boot.**> # 로그 레코드르 재전송하지 않으며, 로그 레코드 태그와 일치하는 구성 파일의 다음 요소로 전달
      @type parser
      key_name log
      time_key time
      time_format %Y-%m-%d %H:%M:%S.%N
      reserve_data true
      # Sample log message: 2019-08-10 11:27:20.497 DEBUG [product-composite,677435df95da585a22d6d6a62db84082,59f32cef360398a4,true] 1 --- [or-http-epoll-1] s.m.u.h.GlobalControllerExceptionHandler : Returning HTTP status: 404 NOT_FOUND for path: /product-composite/13, message: Product Id: 13 not found 
      # <time> <spring.level> <log> 등을 추출
      format /^(?<time>\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3})\s+(?<spring.level>[^\s]+)\s+(\[(?<spring.service>[^,]*),(?<spring.trace>[^,]*),(?<spring.span>[^,]*),[^\]]*\])\s+(?<spring.pid>\d+)\s+---\s+\[\s*(?<spring.thread>[^\]]+)\]\s+(?<spring.class>[^\s]+)\s*:\s+(?<log>.*)$/
    </filter>

주석이 달린 부분을 잘 확인하여 수정 후 사용 요망

 

시각화를 위한 kibana의 deployment와 service정의

apiVersion: v1
kind: Service
metadata:
  name: kibana
  namespace: kube-logging
  labels:
    app: kibana
spec:
  ports:
  - port: 5601
  selector:
    app: kibana
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kibana
  namespace: kube-logging
  labels:
    app: kibana
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kibana
  template:
    metadata:
      labels:
        app: kibana
    spec:
      containers:
      - name: kibana
        image: docker.elastic.co/kibana/kibana:7.2.0
        resources:
          limits:
            cpu: 1000m
          requests:
            cpu: 100m
        env:
          - name: ELASTICSEARCH_HOSTS
            value: 'http://elasticsearch-svc:9200'
        ports:
        - containerPort: 5601

만약 연결문제로 위에서 nodeport로 진행할 경우 해당부분을 http://<CLUSTER-IP>:<node ports>로 진행

 

진행이 끝났다면 port-forward을 통해 연결

이때 사용되는 파드는 kibana의 파드이므로 이름을 알아와야 함

kubectl port-forward -n kube-logging kibana-675bc55bbb-fm6nk 5601:5601

여기서 5601은 키바나의 서비스에서 정의한 port번호

 

http://localhost:5601/을 통해 키바나에 접속가능

use Elasticsearch data -> connect to youre elasticsearch index

정상적으로 설정되었다면 그림처럼 로그들이 나오겠지만 로그가 안나온다면 volume과의 연결이 안되었을 가능성이 큼

 

인덱스 이름을 정해주고 다음으로 넘어가면 time filter를 설정

 

memory오류가 나타난다면 아래의 yaml 파일을 적용

apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: metrics-server
  name: metrics-server
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    k8s-app: metrics-server
    rbac.authorization.k8s.io/aggregate-to-admin: "true"
    rbac.authorization.k8s.io/aggregate-to-edit: "true"
    rbac.authorization.k8s.io/aggregate-to-view: "true"
  name: system:aggregated-metrics-reader
rules:
- apiGroups:
  - metrics.k8s.io
  resources:
  - pods
  - nodes
  verbs:
  - get
  - list
  - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    k8s-app: metrics-server
  name: system:metrics-server
rules:
- apiGroups:
  - ""
  resources:
  - pods
  - nodes
  - nodes/stats
  - namespaces
  - configmaps
  verbs:
  - get
  - list
  - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    k8s-app: metrics-server
  name: metrics-server-auth-reader
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: extension-apiserver-authentication-reader
subjects:
- kind: ServiceAccount
  name: metrics-server
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    k8s-app: metrics-server
  name: metrics-server:system:auth-delegator
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:auth-delegator
subjects:
- kind: ServiceAccount
  name: metrics-server
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    k8s-app: metrics-server
  name: system:metrics-server
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:metrics-server
subjects:
- kind: ServiceAccount
  name: metrics-server
  namespace: kube-system
---
apiVersion: v1
kind: Service
metadata:
  labels:
    k8s-app: metrics-server
  name: metrics-server
  namespace: kube-system
spec:
  ports:
  - name: https
    port: 443
    protocol: TCP
    targetPort: https
  selector:
    k8s-app: metrics-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    k8s-app: metrics-server
  name: metrics-server
  namespace: kube-system
spec:
  selector:
    matchLabels:
      k8s-app: metrics-server
  strategy:
    rollingUpdate:
      maxUnavailable: 0
  template:
    metadata:
      labels:
        k8s-app: metrics-server
    spec:
      containers:
      - args:
        - --cert-dir=/tmp
        - --secure-port=4443
        - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
        - --kubelet-use-node-status-port
        - --metric-resolution=15s
        - --kubelet-insecure-tls=true ## 추가부분
        - --kubelet-preferred-address-types=InternalIP ## 추가부분
        image: k8s.gcr.io/metrics-server/metrics-server:v0.5.2
        imagePullPolicy: IfNotPresent
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /livez
            port: https
            scheme: HTTPS
          periodSeconds: 10
        name: metrics-server
        ports:
        - containerPort: 4443
          name: https
          protocol: TCP
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /readyz
            port: https
            scheme: HTTPS
          initialDelaySeconds: 20
          periodSeconds: 10
        resources:
          requests:
            cpu: 100m
            memory: 200Mi
        securityContext:
          readOnlyRootFilesystem: true
          runAsNonRoot: true
          runAsUser: 1000
        volumeMounts:
        - mountPath: /tmp
          name: tmp-dir
      nodeSelector:
        kubernetes.io/os: linux
      hostNetwork: true
      priorityClassName: system-cluster-critical
      serviceAccountName: metrics-server
      volumes:
      - emptyDir: {}
        name: tmp-dir
---
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
  labels:
    k8s-app: metrics-server
  name: v1beta1.metrics.k8s.io
spec:
  group: metrics.k8s.io
  groupPriorityMinimum: 100
  insecureSkipTLSVerify: true
  service:
    name: metrics-server
    namespace: kube-system
  version: v1beta1
  versionPriority: 100

137줄과 138줄의 코드를 추가하여 적용하는 것으로 해결 가능

'Kubernetes' 카테고리의 다른 글

Kubesphere Tower Federation  (0) 2024.05.23
Kubefed  (0) 2024.05.23
Kubernetes Prometheus  (0) 2024.05.23
Kubesphere Federation  (0) 2024.05.23
Kubekey를 사용한 Kubernetes, Kubesphere 배포  (0) 2024.05.23