본문 바로가기

MLOps

Kubernetes에 EFK 설치 및 튜토리얼

728x90
이전 MLOps Toy 프로젝트를 진행할 때 자원 문제로 인해 EFK 및 Prometheus를 적용하지 못하였다.
이에 간단한 Flask의 log를 시각화하는 EFK를 적용해보고자 한다.

EFK란?

https://medium.com/avmconsulting-blog/how-to-deploy-an-efk-stack-to-kubernetes-ebc1b539d063

EFK stack은 Elasticsearch, Fluentd 그리고 Kibana 세개의 플랫폼 조합을 뜻하며, 클러스터 환경에서 로그를 수집, 검색 그리고 시각화를 가능하게 한다. 이번 포스팅에서는 각 플랫폼의 작동 원리나 자세한 기능까지 얘기하지 않고, 간단한 튜토리얼을 통해 쉽게 설치하는 내용을 담았다. 위의 그림을 보면 알 수 있듯이, 각 클러스터에 fluentd가 daemonset으로 log를 수집한다. elasticsearch는 fluentd가 수집한 로그를 저장하고, 요청에 따라 검색을 한다. 마지막으로 유저가 용이하게 사용할 수 있도록 kibana로 시각화 한다.

 

Kibana는 무엇인가요?

Kibana는 Elastic Stack을 기반으로 구축된 무료 오픈 소스 프론트엔드 애플리케이션으로, Elasticsearch에서 색인된 데이터를 검색하고 시각화하는 기능을 제공합니다. Elastic Stack(이전에는 Elasticsearch, Lo

www.elastic.co

 

Fluentd | Open Source Data Collector

"Logs are streams, not files. I love that Fluentd puts this concept front-and-center, with a developer-friendly approach for distributed systems logging." Adam Wiggins, Heroku co-founder

www.fluentd.org

 

Elasticsearch는 무엇인가요?

Elasticsearch는 텍스트, 숫자, 위치 기반 정보, 정형 및 비정형 데이터 등 모든 유형의 데이터를 위한 무료 검색 및 분석 엔진으로 분산형 및 개방형을 특징으로 합니다. Elasticsearch는 Apache Lucene을 기

www.elastic.co

 

Tutorial

튜토리얼을 위한 minikube 설치 그리고 EFK 설치 뿐만 아니라 설치된 EFK 플랫폼으로 서비스되고있는 Flask의 로그를 시각화하는 방법을 보여준다. 아래 모든 내용에 사용된 코드는 byeongjokim/EFK-Example (github.com) 에서 볼 수 있다.

 

byeongjokim/EFK-Example

Contribute to byeongjokim/EFK-Example development by creating an account on GitHub.

github.com

Minikube 설치

Docker 설치

$ sudo apt-get update
$ sudo apt-get install docker.io -y
$ sudo usermod -aG docker $USER && newgrp docker

 

minikube 설치

$ curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
$ sudo install minikube-linux-amd64 /usr/local/bin/minikube

 

 

minikube 시작 및 kubectl 사용

$ minikube start --cpus 4 --memory 8192

$ ln -s $(which minikube) /usr/local/bin/kubectl

Flask 배포

from flask import Flask
from flask import request, jsonify

app = Flask(__name__)

@app.route("/")
def main():
    message = request.args.get("message")
    return_json = {"message": message}
    return jsonify(return_json)

@app.route("/test")
def test():
    message = request.args.get("message")
    return_json = {"message": message}
    return jsonify(return_json)

if __name__=='__main__':
    app.run(host="0.0.0.0", port=8080)

 

배포할 Flask app은 정말 간단하게 만들었다. /?message=<message> 를 요청하면 {"message": <message>}를 return 하도록 하였다. 또한 관련 Dockerfile과 kubernetes에 올릴 때 사용하는 yaml 파일은 다음과 같다.

 

Dockerfile

FROM python:3.8-slim
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
EXPOSE 8080
ENTRYPOINT ["python"]
CMD ["run.py"]

 

k8s.yaml

apiVersion: v1
kind: Service
metadata:
  name: flask-example
  namespace: real
spec:
  selector:
    app: flask-example
  ports:
  - name: http
    protocol: TCP
    port: 8088
    targetPort: 8080
  type: LoadBalancer

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-example
  namespace: real
spec:
  selector:
    matchLabels:
      app: flask-example
  replicas: 2
  template:
    metadata:
      labels:
        app: flask-example
    spec:
      containers:
      - name: flask-example
        image: byeongjokim/flask-example
        ports:
        - containerPort: 8080

 

2개의 replica를 가진 deploymentLoadBalancer로 이루어진 service를 배포했으며, 8088 포트를 통해 접근 가능하다. 위 yaml 파일을 보면 알 수 있듯이 namespace는 real을 사용하였다. 다음은 Flask API를 배포하는 스크립트이다.

 

$ kubectl create namespace real
$ kubectl apply -f k8s.yaml

 

배포된 Pods, Service

위 사진을 보면, flask-example 이름의 service가 배포된 것을 확인할 수 있다. 외부에서 이 service에 접근하게 하기 위해서는 port-forward가 필요하다. 아래는 port forward를 위한 스크립트와 결과이다.

 

$ kubectl port-forward svc/flask-example -n real 8088:8088 --address=0.0.0.0 &

 

Flask API

EFK 배포

EFK는 elastic namespace에 배포하였다. 각 yaml 파일을 통해 배포가 가능하다.

$ kubectl apply -f elasticsearch.yaml
# kubectl port-forward svc/elasticsearch-svc -n elastic 9200:9200 --address=0.0.0.0 &

$ kubectl apply -f kibana.yaml
$ kubectl port-forward svc/kibana-svc -n elastic 5601:5601 --address=0.0.0.0 &

$ kubectl apply -f fluentd.yaml

 

elasticsearch.yaml

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: elastic/elasticsearch:6.4.0
        env:
        - name: discovery.type
          value: single-node
        ports:
        - containerPort: 9200
        - containerPort: 9300
---
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

 

1개의 Deployment와 NodePort의 Service로 구성되어 있으며 9200 포트와 9300 포트를 사용한다. kibana와 fluentd는 9200 포트를 통해 elasticsearch에 접근 가능하게 된다.

 

kibana.yaml

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: elastic/kibana:6.4.0
        env:
        - name: SERVER_NAME
          value: kibana.kubenetes.example.com
        - name: ELASTICSEARCH_URL
          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

 

5601을 포트를 통해 kibana Web UI에 접근 가능하다. 즉 5601에 접근 가능하도록 서버/공유기 세팅 등이 필요하다.

$ kubectl port-forward svc/kibana-svc -n elastic 5601:5601 --address=0.0.0.0 &

 

<아이피주소>:5601에 접근해보면 다음과 같은 Web UI 확인이 가능하다.

Kibana Web UI

 

fluentd.yaml

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluentd
  namespace: elastic

---
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: elastic
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: elastic
  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/master
        effect: NoSchedule
      containers:
      - name: fluentd
        image: fluent/fluentd-kubernetes-daemonset:v1-debian-elasticsearch
        env:
          - name:  FLUENT_ELASTICSEARCH_HOST
            value: "elasticsearch-svc"
          - name:  FLUENT_ELASTICSEARCH_PORT
            value: "9200"
          - name: FLUENT_ELASTICSEARCH_SCHEME
            value: "http"
        resources:
          limits:
            memory: 200Mi
          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

 

마지막으로 fluentd 배포가 남았다. fluentd는 모든 node에 생성되어야 모든 log 수집이 가능하다. 따라서 Daemonset으로 배포되어야한다. 

 

EFK를 모두 배포한 후, elastic namespace의 resource를 살펴보면 다음과 같다.

elastic namespace의 resources

elasticsearch, kibana, fluentd 모두 배포 된 것을 확인할 수 있다.

Log 확인

이제 배포된 kibana에 다시 접근한 후 Discover를 클릭하면, index pattern을 생성하는 창이 뜬다. 모든 log를 검색하기 위해 *를 입력하였으며, setting은 기본 값으로 설정하였다.

flask-example의 Log

그 후 배포된 flask app(flask-example)을 검색해보면 위와 같이 관련 모든 Log에 접근할 수 있다. 

Reference

- [AWS] EFK 구축 : 네이버 블로그 (naver.com)

- Kubernetes 에서 EFK (ElasticSearch/FluentBit/Kibana) stack 설치하기 | by Jung-taek Lim | Medium

728x90