이전 MLOps Toy 프로젝트를 진행할 때 자원 문제로 인해 EFK 및 Prometheus를 적용하지 못하였다.
이에 간단한 Flask의 log를 시각화하는 EFK를 적용해보고자 한다.
EFK란?
EFK stack은 Elasticsearch, Fluentd 그리고 Kibana 세개의 플랫폼 조합을 뜻하며, 클러스터 환경에서 로그를 수집, 검색 그리고 시각화를 가능하게 한다. 이번 포스팅에서는 각 플랫폼의 작동 원리나 자세한 기능까지 얘기하지 않고, 간단한 튜토리얼을 통해 쉽게 설치하는 내용을 담았다. 위의 그림을 보면 알 수 있듯이, 각 클러스터에 fluentd가 daemonset으로 log를 수집한다. elasticsearch는 fluentd가 수집한 로그를 저장하고, 요청에 따라 검색을 한다. 마지막으로 유저가 용이하게 사용할 수 있도록 kibana로 시각화 한다.
Tutorial
튜토리얼을 위한 minikube 설치 그리고 EFK 설치 뿐만 아니라 설치된 EFK 플랫폼으로 서비스되고있는 Flask의 로그를 시각화하는 방법을 보여준다. 아래 모든 내용에 사용된 코드는 byeongjokim/EFK-Example (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를 가진 deployment와 LoadBalancer로 이루어진 service를 배포했으며, 8088 포트를 통해 접근 가능하다. 위 yaml 파일을 보면 알 수 있듯이 namespace는 real을 사용하였다. 다음은 Flask API를 배포하는 스크립트이다.
$ kubectl create namespace real
$ kubectl apply -f k8s.yaml
위 사진을 보면, flask-example 이름의 service가 배포된 것을 확인할 수 있다. 외부에서 이 service에 접근하게 하기 위해서는 port-forward가 필요하다. 아래는 port forward를 위한 스크립트와 결과이다.
$ kubectl port-forward svc/flask-example -n real 8088:8088 --address=0.0.0.0 &
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 확인이 가능하다.
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를 살펴보면 다음과 같다.
elasticsearch, kibana, fluentd 모두 배포 된 것을 확인할 수 있다.
Log 확인
이제 배포된 kibana에 다시 접근한 후 Discover를 클릭하면, index pattern을 생성하는 창이 뜬다. 모든 log를 검색하기 위해 *를 입력하였으며, setting은 기본 값으로 설정하였다.
그 후 배포된 flask app(flask-example)을 검색해보면 위와 같이 관련 모든 Log에 접근할 수 있다.
Reference
- [AWS] EFK 구축 : 네이버 블로그 (naver.com)
- Kubernetes 에서 EFK (ElasticSearch/FluentBit/Kibana) stack 설치하기 | by Jung-taek Lim | Medium
'MLOps' 카테고리의 다른 글
Knowledge Distillation을 이용한 딥러닝 모델 경량화 파이프라인 구축 (0) | 2021.07.17 |
---|---|
Deep Learning GPU 성능 최적화 전략 (0) | 2021.05.16 |
TorchScript? torch.jit.script 에러 유형 및 해결 방법 (0) | 2021.05.01 |
[PyTorch] .detach().cpu().numpy()와 .cpu().data.numpy() ? (0) | 2021.04.28 |
NVIDIA APEX가 빠른 이유 (ft. FP16 vs FP32) (0) | 2021.04.22 |