최근 사내 통합 빌링 시스템의 빌링 데이터 수집 프로그램을 Apache Airflow로 이전하기 위해, Kubernetes 환경에서 Airflow 를 구축하여 운영 해 보는 것을 테스트 해 보고 있습니다. Kubernetes 환경의 경우 안정적인 클러스터 운영을 위해 둘 이상의 노드로 클러스터를 구성하는 것이 일반적입니다. 그러다 보니 테스트 환경을 운영 하거나 작은 규모의 서비스를 운영하기에는 비용 부담이 있을 수도 있습니다. 그렇다고 낮은 사양의 노드로 클러스터를 구성하면, 테스트 할 서비스를 모두 배포하기에 어려움이 있을 수도 있습니다.

그런데 테스트 환경이면 보통 항상 서비스를 켜 두기 보단, 테스트가 필요할 때만 켜는 방식으로 비용 부담을 조금은 줄여 볼 수 있습니다. 이를 매번 수동으로 하기는 번거로우니, 자동으로 필요할 때만 켜지도록 하면 편리할 텐데요, Kubernetes 환경에서는 이 글에서 소개할 KEDA를 활용하여 필요할 때만 자동으로 켜지는 서비스를 배포할 수 있습니다. Airflow 테스트 환경을 구축 사례를 통해 왜 KEDA 를 사용해 보게 되었는지, 어떻게 사용했는지에 대해 정리 해 보고자 합니다.

높은 리소스를 사용하지만, 자주 접속할 일은 없는 Airflow Webserver

Airflow 는 다양한 컴포넌트로 구성되어 있는데요. DAG 스케줄링과 실행 상태를 보여주는 Webserver, DAG로 정의된 각종 작업이 정해진 시간에 실행될 수 있도록 스케줄링 하는 Scheduler, 실제 작업을 스케줄링 하고 실행하는 Executor 와 Worker, Airflow 환경 설정 데이터와 DAG 실행 상태와 기록 등을 저장하는 Metadata Database (보통 PostgreSQL 많이 사용) 등으로 구성 되어 있습니다.

이들 중 Metadata Database 의 경우 기존에 사용중인 외부 DB에 연동하는 형태로 구성할 수 있고, Scheduler, Executor, Worker 는 작업이 정해진 시간에 잘 수행되기 위해 항상 실행 되고 있어야 하지만. Webserver 의 경우 항상 켜져 있을 필요는 없습니다. Airflow 환경 설정을 하거나 DAG 상태를 볼 때만 잠깐 켜도 되고, 꺼져 있어도 DAG 가 실행 되는 것에는 문제가 없습니다.

그런데 이렇게 자주 사용할 일이 없는 Webserver 가 아래 사진처럼 메모리를 많이 사용하고, 또 사용 가능한 리소스가 적은 테스트 용도로 구축한 Kubernetes 클러스터에서 운영하다 보니, Webserver 때문에 다른 Airflow 컴포넌트가 제대로 실행되지 않거나, 정해진 시간에 DAG 가 실행 되다가 메모리가 부족하여 실행에 실패하는 경우도 있고, DAG 를 실행하기 위해 Kubernetes 에서 Webserver 를 종료 시키는 경우가 많았습니다. 그래서 Webserver 를 항상 띄어 놓기 보단, 이 글에서 소개할 KEDA 를 활용해서 필요할 때만 잠깐 띄우는 형태로 구성 하여 DAG 가 실행될 때 문제가 없도록 구성 해 보기로 하였습니다.

KEDA와 KNative

KEDA(Kubernetes Event-driven Autoscaling)는 이벤트 기반으로 Kuibernetes 환경에서 운영중인 컨테이너를 자동 스케일링 하는 Kubernetes 컴포넌트 입니다. HTTP 트래픽, 메시지 큐에서 온 이벤트, 데이터베이스 쿼리 결과 등 다양한 이벤트를 수신 받아 이를 기반으로 컨테이너를 스케일링 할 수 있도록 해 줍니다. 또한 기존에 Kubernetes 환경에 배포한 Service 나 Deployment 를 수정하지 않고 KEDA로 확장하여 사용할 수 있어, 운영중인 Kubernetes 클러스터에 어렵지 않게 도입하여 사용할 수 있습니다.

KEDA와 비슷한 프로젝트로, KNative 프로젝트가 있습니다. KNative 의 경우 Kubernetes 환경에 서비리스를 쉽게 구현할 수 있도록 다양한 도구와 컴포넌트를 제공하는 점에서 KEDA 와는 조금 다르다고 할 수 있습니다. 또한 자체적으로 제공하는 자동 스케일러(KNative Pod Scaler) 등 서버리스 앱 배포에 유용한 기능을 다양하게 제공합니다. 하지만 KNative가 제공하는 강력한 기능을 사용하려면, 기존 Service나 Deployment를 KNative 에서 제공하는 KNative 서비스로 변환해서 사용해야 합니다. 기존 Service 를 그대로 두고 추가적으로 확장하여 사용하고 싶은 경우에는 적합하지 않다고 할 수 있습니다.

자동 스케일링을 적용 하고자 하는 Airflow Webserver 의 경우, 공식 Airflow Helm 차트를 통해 배포된 서비스 입니다. 이 경우 KNative 를 사용 하려면, Helm 차트를 직접 수정해야 KNative를 사용할 수 있는데 이는 복잡한 방법이여서, KEDA 를 사용하여 기존 서비스를 확장하는 형태로 구성하는 것을 시도 하였습니다.

KEDA HTTP Add On

KEDA에는 다양한 유형의 이벤트를 받아서 Service나 Deployment 를 스케일 할 수 있는 Scaler가 있는데요. 이 글에서 구현할 HTTP 트래픽을 기반으로 스케일 되는 서비스를 구현하기 위해, KEDA HTTP Add On 이 제공하는 HTTPScaledObject를 사용할 수 있습니다. 이를 통해 HTTP 트래픽이 있으면 1개 이상의 Pod 를 배포하도록 하고, 트래픽이 많아지면 다수의 Pod를, 그리고 HTTP 트래픽이 없으면 0으로 스케일 하여 Pod를 모두 종료하도록 할 수도 있습니다.

HTTP Add On 의 경우 프로젝트 문서의 설명에 따르면 크게 3가지로 구성되어 있습니다.

  • Operator: HTTPScaledObject와 관련된 이벤트를 수신하여, 스케일러 구성을 생성/수정/삭제 하여 적용 하는 구성요소
  • Intercepter: 외부 HTTP 트래픽을 받아 클러스터에 배포된 애플리케이션에 전달하는 역할 수행
  • Scaler: HTTP 요청 숫자 모니터링 하고 KEDA 에 전달하여, 요청 숫자에 따른 스케일링이 작동하도록 하는 역할 수행

KEDA HTTP Add On 이 배포된 Kubernetes 클러스터 내부 구성
https://github.com/kedacore/http-add-on/blob/main/docs/design.md#architecture-overview

KEDA, KEDA HTTP Add On 설치 및 구성하기

설치하기

KEDA 와 KEDA HTTP Add On 에 대해 알아 보았으니, 이제 직접 Kubernetes 클러스터에 설치하고 구성 해 보겠습니다.

참고: KEDA HTTP Add On 의 경우 아직 베타 단계에 있는 프로젝트 입니다. 테스트 환경에는 괜찮겠지만, 프로덕션 환경에 적용 하기에는 문서화나 제공되는 기능 등이 부족할 수 있으니 참고 하시기 바랍니다.

먼저 Helm 을 사용하여 KEDA 를 설치 합니다. ${NAMESPACE} 의 경우 본인이 KEDA 를 설치할 Kubernetes 네임스페이스로 지정합니다.

참고: 최근 Helm은 대부분의 관리형 Kubernetes 환경이나, Microk8s, Minikube 등 경량 Kubernetes 환경에도 이미 포함되어 있어 바로 Helm 을 사용할 수 있습니다.

helm repo add kedacore https://kedacore.github.io/charts
helm repo update
helm install keda kedacore/keda --namespace ${NAMESPACE} --create-namespace

HTTP Add On 또한 Helm 을 통해 쉽게 설치가 가능합니다.

helm install http-add-on kedacore/keda-add-ons-http --namespace ${NAMESPACE}

구성하기

설치가 다 되었으니, HTTPScaledObject와 Ingress 를 하나씩 만들어서 배포 해 보겠습니다. 먼저 기존 Service 를 자동 스케일링 해줄 HTTPScaledObject를 작성합니다.

kind: HTTPScaledObject
apiVersion: http.keda.sh/v1alpha1
metadata:
    name: airflow-webserver-keda
    namespace: airflow
spec:
    host: my-airflow-on-aks.koreacentral.cloudapp.azure.com
    targetPendingRequests: 100 
    scaleTargetRef:
        deployment: airflow-webserver
        service: airflow-webserver
        port: 8080
    replicas:
        min: 0
        max: 1
  • namespace: 자동 스케일링할 Service/Deployment가 배포된 Namespace로 지정 해 줍니다. 예를 들어, 대상 Service/Deployment 가 airflow Namespace 에 배포되어 있다면, airflow로 지정합니다.
  • host: 외부에서 HTTP 트래픽을 수신할 호스트(주소)를 입력합니다.
  • targetPendingRequests: 스케일링 할 때의 HTTP 요청 수 기준점 입니다. 설정된 숫자보다 많은 요청이 처리/대기 중이면 Replica 수를 늘리고, 적으면 Replica 수를 줄입니다. (기본값: 100)
  • scaleTargetRef: 스케일 대상 Service, Deployment 트래픽 전달 포트 등을 지정합니다. 여기서는 이미 배포된 Service 인 airflow-webserver와 이로 인해 같이 생성된 Deployment인 airflow-webserver 그리고 포트는 8080으로 지정 하였습니다.
  • replicas: 스케일 할 대상의 최소 및 최대 Replica 수를 지정 합니다.

작성을 완료 하였다면, http-scaled-object.yml 등의 적절한 이름으로 저장하고, 클러스터에 배포합니다.

kubectl apply -f http-scaled-object.yml

외부에서 트래픽을 받아 전달할 수 있도록 Ingress 도 배포합니다. Airflow Helm 차트를 사용하면, 간단히 Ingress 관련 설정을 아래처럼 활성화 하여 배포하는 것도 가능하지만, 그렇게 하면 트래픽이 바로 Airflow Webserver 로 가기 때문에 자동 스케일링이 작동하지 않습니다.

...
ingress:
  enabled: false # true 로 변경하고 적용하면 Ingress 자동 배포
  ...
  web:
    annotations: {}
    host: ""
    hosts: []
    ingressClassName: ""
    path: /
    pathType: ImplementationSpecific
    precedingPaths: []
    succeedingPaths: []
    tls:
      enabled: false
      secretName: ""

HTTP 트래픽 모니터링을 위한 HTTP Add On 의 Intercepter 를 거쳐야 Intercepter 에서 모니터링 한 트래픽을 기반으로 자동 스케일링이 가능하므로, 아래처럼 별도로 Ingress 를 작성하여 배포합니다. 아래 예제 Ingress 를 보면, host 는 앞서 작성한 HTTPScaledObject 에서 명시한 호스트로 되어 있는 것을 볼 수 있고, Intercepter 에서 트래픽을 받도록 하기 위해 keda-add-ons-http-interceptor-proxy 서비스가 백엔드로 지정 되어 있는 것을 볼 수 있습니다.

참고: 아래 작성된 YAML 코드는 클러스터에 Nginx Ingress Controller 가 이미 구성되어 있음을 가정하고 작성된 것입니다. 아직 Ingress Controller 가 구성되어 있지 않다면, 이 문서를 참고해서 Nginx Ingress 를 구성할 수 있습니다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: airflow-webserver-keda-ingress
  namespace: airflow
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: nginx
spec:
  rules:
  - host: my-airflow-on-aks.koreacentral.cloudapp.azure.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: keda-add-ons-http-interceptor-proxy
            port:
              number: 8080

마찬가지로, airflow-webserver-keda-ingress.yml 등의 적당한 이름으로 저장해서 클러스터에 배포합니다.

kubectl apply -f airflow-webserver-keda-ingress.yml

테스트

필요한 것을 다 배포 하였으니, 실제로 배포한 URL 에 접속해 보면서 어떻게 작동하는지 살펴보겠습니다.

Ingress 에 Host로 설정한 주소로 접속하면 아래 사진처럼 airflow-webserver-*** Pod 가 새로 생성되는 것을 볼 수 있습니다.

Airflow Webserver 의 경우 리소스 사용량이 크고, 다양한 기능이 있는 웹 애플리케이션 Pod 이다 보니 초기화에 시간이 오래 소요되어 타임아웃 오류가 발생 하기도 합니다.

아래 사진처럼 Pod 가 성공적으로 실행 된 후 부터는, Airflow 웹 콘솔에 정상적으로 접속 가능한 것을 확인할 수 있습니다.

KEDA에서 Deployment의 스케일링을 처리하는 keda-operator Deployment의 Pod인 keda-operator-*** Pod 의 로그에서도 airflow-webserver를 스케일링 한 것을 확인할 수 있습니다.

...
1.6472195814979117e+09	INFO	controller.scaledobject	Initializing Scaling logic according to ScaledObject Specification	{"reconciler group": "keda.sh", "reconciler kind": "ScaledObject", "name": "keda-add-ons-http-interceptor", "namespace": "airflow"}
1.6472195815011077e+09	INFO	controller.scaledobject	Initializing Scaling logic according to ScaledObject Specification	{"reconciler group": "keda.sh", "reconciler kind": "ScaledObject", "name": "airflow-webserver-keda-app", "namespace": "airflow"}
1.647219716826971e+09	INFO	controller.scaledobject	Reconciling ScaledObject	{"reconciler group": "keda.sh", "reconciler kind": "ScaledObject", "name": "keda-add-ons-http-interceptor", "namespace": "airflow"}
1.647223844091361e+09	INFO	scaleexecutor	Successfully updated ScaleTarget	{"scaledobject.Name": "airflow-webserver-keda-app", "scaledObject.Namespace": "airflow", "scaleTarget.Name": "airflow-webserver", "Original Replicas Count": 0, "New Replicas Count": 1}
1.6472238458556235e+09	INFO	controller.scaledobject	Reconciling ScaledObject	{"reconciler group": "keda.sh", "reconciler kind": "ScaledObject", "name": "airflow-webserver-keda-app", "namespace": "airflow"}
1.6472238530737023e+09	INFO	controller.scaledobject	Reconciling ScaledObject	{"reconciler group": "keda.sh", "reconciler kind": "ScaledObject", "name": "keda-add-ons-http-interceptor", "namespace": "airflow"}
1.6472238682097535e+09	INFO	controller.scaledobject	Reconciling ScaledObject	{"reconciler group": "keda.sh", "reconciler kind": "ScaledObject", "name": "keda-add-ons-http-interceptor", "namespace": "airflow"}
1.647223876009515e+09	INFO	controller.scaledobject	Reconciling ScaledObject	{"reconciler group": "keda.sh", "reconciler kind": "ScaledObject", "name": "airflow-webserver-keda-app", "namespace": "airflow"}
1.6472241543949137e+09	INFO	controller.scaledobject	Reconciling ScaledObject	{"reconciler group": "keda.sh", "reconciler kind": "ScaledObject", "name": "keda-add-ons-http-interceptor", "namespace": "airflow"}
1.6472241628582501e+09	INFO	controller.scaledobject	Reconciling ScaledObject	{"reconciler group": "keda.sh", "reconciler kind": "ScaledObject", "name": "airflow-webserver-keda-app", "namespace": "airflow"}
1.6472243830425234e+09	INFO	scaleexecutor	Successfully set ScaleTarget replicas count to ScaledObject minReplicaCount	{"scaledobject.Name": "airflow-webserver-keda-app", "scaledObject.Namespace": "airflow", "scaleTarget.Name": "airflow-webserver", "Original Replicas Count": 1, "New Replicas Count": 0}
1.647224389093124e+09	INFO	controller.scaledobject	Reconciling ScaledObject	{"reconciler group": "keda.sh", "reconciler kind": "ScaledObject", "name": "airflow-webserver-keda-app", "namespace": "airflow"}
...

글을 마치며

지금까지 KEDA와 KEDA Http Add On 을 적용하여, 기존에 배포된 Service 나 Deployment 를 확장하여 HTTP 트래픽이 있을 때만 작동하는 서버리스 형태로 구성 해 보았습니다. Kubernetes 환경에 서버리스 방식으로 작동하도록 애플리케이션을 배포 하고 싶지만, 기존에 배포된 것을 최소한으로 수정하고 확장하여 서버리스 형태로 바꾸고자 할 때 KEDA와 KEDA Http Add On 을 활용하면 어렵지 않게 적용할 수 있습니다. 다만, 참고해야 할 점은 앞에서도 언급 했듯 KEDA Http Add On은 이 글이 작성된 시점에서 아직 베타 단계에 있는 프로젝트라는 점 입니다. 본인의 Kubernetes 클러스터에 적용할 때 이 점을 꼭 참고 하셔야 겠습니다.

참고자료