Dev / App

KEDA를 활용하여 방문자가 있을 때만 작동하는 서비스 배포

KEDA를 통해 HTTP 트래픽이 있을 때만 작동하는 서버리스 형태로 구성하는 방법

14 min read
KEDA를 활용하여 방문자가 있을 때만 작동하는 서비스 배포

최근 사내 통합 빌링 시스템의 빌링 데이터 수집 프로그램을 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가지로 구성되어 있습니다.

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

작성을 완료 하였다면, 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 클러스터에 적용할 때 이 점을 꼭 참고 하셔야 겠습니다.

참고자료

Share This Post

Check out these related posts

개발팀의 애자일 도입 이야기2

개발팀의 애자일 도입 이야기 1

Lambda@Edge 고급 로깅 제어 기능