Spark on Kubernetes 에서 Prometheus 로 metric 유입하기 (Kubernetes 에 Prometheus 설치 포함)

Jung-taek Lim
17 min readDec 29, 2020

--

역시나 디테일은 다른 문서들에게 맡기고 “방법” 에 집중해서 정리하고자 한다.

일단 k8s 클러스터에 Prometheus 설치부터 하자.

kubectl create namespace prometheushelm install prometheus stable/prometheus-operator -n prometheus

설치가 되었다면, k8s 에 Prometheus 관련 여러 개의 서비스들이 등록되어 있을 것이다.

kubectl get svc -n prometheusNAME                                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
prometheus-prometheus-oper-alertmanager ClusterIP 10.43.133.105 <none> 9093/TCP 71s
prometheus-prometheus-oper-prometheus ClusterIP 10.43.50.232 <none> 9090/TCP 71s
prometheus-kube-state-metrics ClusterIP 10.43.23.65 <none> 8080/TCP 71s
prometheus-prometheus-oper-operator ClusterIP 10.43.107.212 <none> 8080/TCP,443/TCP 71s
prometheus-prometheus-node-exporter ClusterIP 10.43.66.204 <none> 9100/TCP 71s
prometheus-grafana ClusterIP 10.43.254.135 <none> 80/TCP 71s
alertmanager-operated ClusterIP None <none> 9093/TCP,9094/TCP,9094/UDP 60s
prometheus-operated ClusterIP None <none> 9090/TCP 50s

“prometheus-prometheus-oper-prometheus” 이 서비스를 port-forwarding 하고, 브라우저를 통해 열어 보면 Prometheus 가 정상적으로 실행 중임을 확인할 수 있다.

kubectl port-forward -n prometheus service/prometheus-prometheus-oper-prometheus 9090

Grafana 도 한 세트로 설치가 된다. “prometheus-grafana” 를 port-forwarding 하고 브라우저를 통해 열어 보자.

kubectl port-forward -n prometheus service/prometheus-grafana 9091:80

기본적으로 id 는 admin, password는 prom-operator 로 설정되어 있다. 어떤 chart/operator 를 설치하냐에 따라 password 가 제각각이다. 구글링으로 한 번 더 확인하자.

기본적으로 설치되어 있는 Prometheus 가 data source 로 등록되어 있다. k8s 클러스터를 모니터링 할 수 있는 dashboard 들도 등록되어 있으니 Dashboard -> Manage 에서 둘러보자.

Prometheus 는 설치가 되었으니 Spark 에서 metrics 를 넣어 보자.

Prometheus 는 기본적으로 push 가 아닌 pull 방식으로 동작한다. 다른 말로 하면, Spark 에서 metric 정보를 노출해 주고, prometheus 에서 해당 노출 경로로 접근해서 데이터를 가져가게 된다. (push gateway 를 설치하고 여기로 push 하는 방안도 있다고 한다. 공식 문서에 따르면 일부 유즈 케이스를 제외하면 추천하지 않는 듯)

Spark 의 Prometheus 공식 지원은 두 가지로 이루어진다.

  1. Prometheus Metric Sink 추가
  2. UI 를 통한 Prometheus 전용 executor metrics 노출 켜기

1번은 Spark 이 Dropwizard 를 활용하여 노출하는 metric 을 Prometheus 가 가져갈 수 있는 형태로 노출하는 것이다. pull 방식이므로 여타 time-series DB 에 연동하는 방법과는 다르게 전용 Servlet 을 생성하고 UI 에 붙여 준다.

설정 방법은 다음과 같다.

  1. metrics.properties 파일 수정

템플릿 파일에도 설정이 주석처리 되어 있을 것이다. 어려울 게 없다.

*.sink.prometheusServlet.class=org.apache.spark.metrics.sink.PrometheusServlet
*.sink.prometheusServlet.path=/metrics/prometheus

2. 해당 파일을 Spark docker image 에 포함 vs 어플리케이션 제출 시에 파일 포함시키고 설정에서 해당 파일을 metric 설정 파일로 사용하도록 변경하기

모든 어플리케이션을 Prometheus 로 연동할 예정이고, 무엇보다 쉽게 가고 싶으면 전자로 가자. 설명하겠지만 (그리고 실제로 이렇게 설정해 두었지만) 후자가 좀 귀찮게 되어 있다. 대신 설정 파일 내용 변경하고 이미지만 build/push 하는 거니까 설명은 생략.

(SO 에 따르면) 후자의 가장 쉬운 방법은 --files 에 설정이 완료된 metrics.properties 파일을 추가하고 --conf spark.metrics.conf=./metrics.properties 로 추가된 파일을 사용하도록 설정하는 것이다. Scala 로 해보진 않았지만, PySpark 3.1.0 SNAPSHOT, k8s cluster mode 기준으로 이 방법은 동작하지 않았다. SparkContext 를 초기화할 때 메트릭 설정이 먼저 발생하고 (파일 없음 오류 발생) 나중에 files 에 있는 파일을 로드하는 것으로 보인다.

여러 방안들이 더 있겠지만, 기 설정해 둔 NFS 를 여기에 활용할 수 있다. 디렉토리를 하나 더 만들어서 (spark-resources 라고 하자) NFS 설정을 해 주고, static provisioning 방법으로 PV/PVC 등록을 해 주자.

> PV

vim nfs-pv-spark-resources.yamlapiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-spark-resources
labels:
name: nfs-pv-spark-resources
spec:
capacity:
storage: 1Gi
volumeMode: Filesystem
accessModes:
- ReadOnlyMany
persistentVolumeReclaimPolicy: Recycle
storageClassName: nfs
mountOptions:
- hard
- nfsvers=4.1
nfs:
path: /nfs/spark-resources
server: 192.168.1.60
kubectl apply -f nfs-pv-spark-resources.yaml

> PVC

vim nfs-pvc-spark-resources.yamlapiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc-spark-resources
spec:
storageClassName: nfs
accessModes:
- ReadOnlyMany
resources:
requests:
storage: 1Gi
selector:
matchLabels:
name: nfs-pv-spark-resources
kubectl apply -f nfs-pvc-spark-resources.yaml

NFS 에 설정한 /nfs/spark-resources 디렉토리에 설정이 완료된 metrics.properties 파일을 넣고, 어플리케이션 제출에 아래 설정을 추가하자.

--conf spark.kubernetes.driver.volumes.persistentVolumeClaim.res.options.claimName="nfs-pvc-spark-resources" \
--conf spark.kubernetes.driver.volumes.persistentVolumeClaim.res.mount.path="/home/spark/resources" \
--conf spark.kubernetes.executor.volumes.persistentVolumeClaim.res.options.claimName="nfs-pvc-spark-resources" \
--conf spark.kubernetes.executor.volumes.persistentVolumeClaim.res.mount.path="/home/spark/resources" \
--conf spark.metrics.conf=/home/spark/resources/metrics.properties \

이제 어플리케이션을 제출하고, Spark UI 를 port forwarding 하는 등의 방법으로 접근해서 /metrics/prometheus 경로를 열어 보자. 페이지가 열리면서 아래와 비슷한 metric 정보들이 출력되면 설정이 잘 된 것이다.

metrics_spark_bf62f5b6f14d4170b4ef7b58673b5dd5_driver_BlockManager_disk_diskSpaceUsed_MB_Number{type="gauges"} 0
metrics_spark_bf62f5b6f14d4170b4ef7b58673b5dd5_driver_BlockManager_disk_diskSpaceUsed_MB_Value{type="gauges"} 0
metrics_spark_bf62f5b6f14d4170b4ef7b58673b5dd5_driver_BlockManager_memory_maxMem_MB_Number{type="gauges"} 827
metrics_spark_bf62f5b6f14d4170b4ef7b58673b5dd5_driver_BlockManager_memory_maxMem_MB_Value{type="gauges"} 827
metrics_spark_bf62f5b6f14d4170b4ef7b58673b5dd5_driver_BlockManager_memory_maxOffHeapMem_MB_Number{type="gauges"} 0
...

이제 이 정보를 Prometheus 가 가져가도록 설정해야 한다.

기본 설치 시에 이미 pod monitor 와 service monitor 를 모니터링하도록 설정되어 있다.

kubectl get prometheus -ANAMESPACE    NAME                                    VERSION   REPLICAS   AGE
prometheus prometheus-prometheus-oper-prometheus v2.18.2 1 4h35m
kubectl describe prometheus -n prometheus prometheus-prometheus-oper-prometheus...Pod Monitor Namespace Selector:
Pod Monitor Selector:
Match Labels:
Release: prometheus
...Service Monitor Namespace Selector:
Service Monitor Selector:
Match Labels:
Release: prometheus

pod 을 감시하거나 service 를 감시하도록 monitor 를 등록해 준다. Spark 이 생성하는 service 들은 label 이 없으므로 (3.1.0 기준) service monitor 를 사용하는 경우 label 을 수동으로 등록하거나 별도 수정을 통해 자동으로 label 을 등록하게 해야 한다. 반대로, Spark 이 생성하는 pod 들은 몇 개의 label 을 자동 등록하므로 pod monitor 를 사용하면 별도 labeling 없이 pod monitor 에서 인식시킬 수 있다.

> service monitor

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
labels:
release: prometheus
name: spark-servicemonitor
spec:
endpoints:
- interval: 5s
port: spark-ui
path: /metrics/prometheus/
- interval: 5s
port: spark-ui
path: /metrics/executors/prometheus/
namespaceSelector:
any: true
selector:
matchLabels:
spark-role: driver

spark-ui 에 해당하는 service 에 “spark-role=driver” 라는 label 을 등록해 줘야 인식한다. 제한적으로 metric 을 유입시키고 싶을 땐 이 쪽도 방법이 되겠다.

> pod monitor

apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
labels:
release: prometheus
name: spark-podmonitor
spec:
podMetricsEndpoints:
- interval: 5s
port: spark-ui
path: /metrics/prometheus/
- interval: 5s
port: spark-ui
path: /metrics/executors/prometheus/
namespaceSelector:
any: true
selector:
matchLabels:
spark-role: driver

cluster mode 로 실행한다면 driver pod 은 “spark-role=driver” 을 label 로 갖고 있다. port 도 입력해 줘야 되는데, service 에서 사용하는 alias 를 사용할 수 있는 것으로 보인다.

위에서 path 가 두 가지인데 “/metrics/executors/prometheus” 는 UI 에서 prometheus 전용 executor metric 을 노출할 때 기본 path 로 쓰는 것이다. 사용하지 않으면 제외해도 된다. (하지만 현실적으로 두 path 모두를 사용하고 싶을 것이다. 이유는 아래에 다시 언급)

이 방법의 장점은 기존 노출되던 metric 을 100% 활용할 수 있다는 것이다. (당연한 얘기로 들리겠지만 2번이 다른 접근 방법을 취하기 때문에 2번 대비 충분한 장점이다.) Structured Streaming 도 많진 않지만 별도 metric 을 옵션을 통해 노출하고 있어서 오래 실행될 쿼리를 모니터링하기엔 이 쪽이 좋다.

이 방법의 단점은 크게 두 가지이다.

  1. Prometheus Sink (Servlet) 이 UI 에 의존성이 있다.

다른 말로 하면, UI 를 옵션으로 끄면 Prometheus Sink 도 노출되지 않을 것이다. 그리고 standalone 이 아닌 경우에는 driver 의 Spark UI 외에는 executor 에 UI 가 실행될 게 없다. 무슨 얘기냐면 executor 들의 metric 을 가져올 방법이 없다는 이야기 이다. push gateway 를 활용하거나, executor 도 UI 를 실행하도록 Spark 커뮤니티에 요청하는 수밖에…

2. metric key 가 엄청나게 길다.

이건 tag 를 지원하지 않는 time-series DB 들을 지원하기 위해 key 에 모든 식별 가능한 정보를 붙이다 보니 어쩔 수 없는 현상인데, tag 를 지원하는 Prometheus 입장에서는 엄청난 마이너스라 할 수 있겠다. 예를 들어 Grafana 등에서 key 로 조회할 때 key 뒷 부분이 다 잘려서 자동완성이 의미가 없어진다.

metrics_spark_bf62f5b6f14d4170b4ef7b58673b5dd5_driver_spark_streaming_7e5879b7_76d5_444c_8a63_f8fdc7491c07_inputRate_total_Number

이게 하나의 metric key 다. Structured Streaming 의 metric 은 app 식별자에 query 식별자까지 붙어서 엄청나게 길다. Prometheus 에서 relabeling 을 통해 조정이 가능한 걸로 보이는데 상세하게 보진 않았다. regex 로 하는 것 같은데 손이 엄청 갈 것 같은…

이제 UI 에서 prometheus 전용 executor metric 을 노출하는 방안을 살펴보자.

--conf spark.ui.prometheus.enabled=true

끝이다. 위에서 언급했듯이 기본 경로는 “/metrics/executors/prometheus” 이다. Spark UI 페이지를 열고 위의 경로를 붙여서 열어보자. “/metrics/prometheus” 의 페이지 내용과 비교하면 metric key 가 상당히 간결하다는 것을 알 수 있다. (tag 로 빼 두었기 때문)

기본적으로는 위의 pod monitor/service monitor 를 설정하고 “/metrics/executor/prometheus” path 를 포함시켜야 동작한다. 이미 첫 번째 방법을 소개할 때 두 번째 방법까지 처리해 둔 것이다.

여기에 더해, Spark 은 annotation 을 붙여서 Prometheus 가 알아서 가져가도록 설정할 수도 있다. (pod monitor/service monitor 를 사용하지 않는 방법)

--conf spark.kubernetes.driver.annotation.prometheus.io/scrape=true  \
--conf spark.kubernetes.driver.annotation.prometheus.io/path=/metrics/executors/prometheus/ \
--conf spark.kubernetes.driver.annotation.prometheus.io/port=4040 \

그럼 pod monitor/service monitor 는 왜 필요한가? 설정을 보면 port 한 개 path 한 개로 제한적임을 알 수 있다. 이 방법이 tag 도 지원하고 하니 이것만 쓰면 되는 거 아닌가? 왜 path 한 개만 지원하는 게 제한인가? 이 방법은 Spark REST API 에서 넘겨 주는 executor 정보에 있는 metric 만을 커버하며, Dropwizard 로 등록되는 metric 을 전혀 커버하지 않는다. 최대한 많은 metric 을 수집하고 싶다면 결국 둘 다 써야 된다. (정확하게는, 둘 다 써도 executor 측의 일부 metric 은 현재로써는 수집할 수 없다. 정말 필요하다면 push gateway 를 써야 될 듯?)

위의 PR 댓글을 따라가면서 읽어보면 Prometheus operator 에는 기능이 의도적으로 빠져 있다. 댓글 중에 values.yaml 을 직접 수정해서 annotation 을 동작하게 만드는 방안이 달려 있긴 하다.

이렇게 등록하고 나서 Prometheus UI -> Status -> Service Discovery 를 확인해서 pod monitor 에 path 별로 discovery 가 각각 존재하는지, pod 리스트에 있는 Spark driver pod 들이 UP 상태인지 (동작을 멈춘 driver pod 이라면 DOWN 이 정상일 것이다) 확인하면 끝이다.

References

--

--

Responses (1)