데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      쿠버네티스 KEDA External Scaler 개발기

      hbinkim 24.05.28
      1,378 5 4
      DEVOTEE 요약
      KEDA의 External Scaler는 외부 endpoint와 연동이 필요할 때 직접 개발합니다. 외부 소스에서 스케일링 이벤트를 받는 것이 주요 특징이며, 이를 위해 getenvoy/go, protobuf 등의 설치와 설정이 필요합니다. External Scaler의 핵심 함수 구현과 프로젝트 설정 방법 등을 다루며, 예시 코드를 통해 실제 배포 방법과 주의사항도 설명합니다.

      쿠버네티스 KEDA에 관련해서는 LEN님이 이미 작성해주신 소개글도 있고, 한번쯤은 들어보셨을 텐데요.

      KEDA에서 이미 개발된 빌트인 scaler를 편하게 사용하는 방법도 있지만,

      제 경우처럼 필요한 scaler가 외부 endpoint와 연동이 필요하여 external scaler를 직접 개발해야 될 때,

      참고하시면 좋을 내용으로 external scaler 개발기 1편! 개발 초기 단계 내용을 담아봤습니다.

      프로젝트 셋팅부터 개발 및 배포까지 시작합니다!


      External Scaler란

      들어가기에 앞서, External Scaler가 다른 Scaler와 가장 큰 차이점은 바로 scalerAddress! 외부 endpoint로부터 스케일링을 트리거할 이벤트를 받는다에 있습니다.

      triggers:
      - type: external
        metadata:
          scalerAddress: external-scaler-service:8080
          caCert : /path/to/tls/ca.pem
          tlsClientCert: /path/to/tls/cert.pem
          tlsClientKey: /path/to/tls/key.pem
          unsafeSsl: false


      External Scaler 프로젝트 셋팅하기

      External Scaler를 위한 Go 프로젝트 셋팅 방법은 Keda Docs 에 스텝 바이 스텝으로 상세하게 기술되어 있습니다.

      본 글에서 시행착오를 거친 추가 설명과 최신 버전으로 업데이트 하여 반영을 하고 있습니다만, 한번 참고하셔도 좋을 거 같아요~

      개발언어는 KEDA GitHub project에 존재하는 다른 scaler들와 마찬가지로 Golang 으로 선정하였습니다.


      전제 조건


      프로젝트 생성

      $ go mod init example.com/external-scaler/sample
      $ mkdir externalscaler
      $ protoc externalscaler.proto --go_out=plugins=grpc:externalscaler

      위 과정을 거치게 되면 아래 그림과 같이 프로젝트 최초 상태가 구성 됩니다.

      external scaler golang 프로젝트 최초 구조


      샘플 프로젝트 배포

      4년 전 코드긴 하지만 keda core 내 external scaler sample code 가 존재합니다.

      해당 디렉토리에서 main.go, Dockerfile, deploy.yaml 파일을 다운로드 받아서 go 버전과 deprecated 된 함수 수정만 거치면 바로 배포해서 테스트 해볼 수 있습니다.

      # Dockerfile 일부 수정 사항
      FROM golang:1.22 as builder
      WORKDIR /src
      COPY . .
      RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o external-scaler main.go
      
      FROM alpine:latest
      WORKDIR /
      COPY --from=builder /src/external-scaler .
      ENTRYPOINT ["/external-scaler"]
      # deploy.yaml 중 일부 수정 사항
      apiVersion: keda.sh/v1alpha1
      kind: ScaledObject
      metadata:
        name: test-1
        namespace: cloudpc
      spec:
        scaleTargetRef:
          name: test-1
        pollingInterval: 20
        triggers:
          - type: external
            metadata:
              scalerAddress: golang-external-scaler.cloudpc:6000
              longitude: "-65.44281"
              latitude: "19.05693"
      // main.go 중 import 일부 수정 사항
      import (
      	"context"
      	"encoding/json"
      	"fmt"
      	"io"
      	pb "keda/externalscaler"
      	"log"
      	"net"
      	"net/http"
      	"time"
      
      	"google.golang.org/grpc"
      	"google.golang.org/grpc/codes"
      	"google.golang.org/grpc/status"
      )

      배포 전 한가지 또 유의 사항은 샘플 코드에서 scaler가 동작하는 조건이 어제 날짜로 미국 지질조사국 검색 결과 진도 1.0 이상의 지진이 어제 날짜로 2건 초과하여 50km 이내 근접 지역에서 발생한 경우, nginx pod 개수가 늘어납니다.

      따라서 실질적으로 pod 수가 늘어나는 것까지 확인하고 싶으시다면, 해당 홈페이지에서 여러 차례 지진 발생 이력이 있는지 위도 경도를 ScaledObject.yaml 에 함께 반영해주세요.

      결과물은 아래와 같이 확인 할 수 있습니다.

      KEDA Operator 로그

      2024-05-14T04:38:52Z	INFO	Updated HPA according to ScaledObject	{"controller": "scaledobject", "controllerGroup": "keda.sh", "controllerKind": "ScaledObject", "ScaledObject": {"name":"test-1","namespace":"cloudpc"}, "namespace": "cloudpc", "name": "test-1", "reconcileID": "bf93a9f2-3ee4-4fe0-bc1b-9e0736203b15", "HPA.Namespace": "cloudpc", "HPA.Name": "keda-hpa-test-1"}
      2024-05-14T04:38:54Z	ERROR	external_push_scaler	error running internalRun	{"type": "ScaledObject", "namespace": "cloudpc", "name": "test-1", "error": "rpc error: code = Unavailable desc = last connection error: connection error: desc = \"transport: Error while dialing: dial tcp 208.91.112.55:6000: connect: connection refused\""}
      github.com/kedacore/keda/v2/pkg/scalers.(*externalPushScaler).Run.func1
      	/workspace/pkg/scalers/external_scaler.go:249
      github.com/kedacore/keda/v2/pkg/scalers.(*externalPushScaler).Run
      	/workspace/pkg/scalers/external_scaler.go:278
      2024-05-14T04:38:58Z	ERROR	external_push_scaler	error running internalRun	{"type": "ScaledObject", "namespace": "cloudpc", "name": "test-1", "error": "rpc error: code = Unavailable desc = last connection error: connection error: desc = \"transport: Error while dialing: dial tcp 208.91.112.55:6000: connect: connection refused\""}
      github.com/kedacore/keda/v2/pkg/scalers.(*externalPushScaler).Run.func1
      	/workspace/pkg/scalers/external_scaler.go:249
      github.com/kedacore/keda/v2/pkg/scalers.(*externalPushScaler).Run
      	/workspace/pkg/scalers/external_scaler.go:278
      2024-05-14T04:39:16Z	INFO	scaleexecutor	Successfully updated ScaleTarget	{"scaledobject.Name": "test-1", "scaledObject.Namespace": "cloudpc", "scaleTarget.Name": "test-1", "Original Replicas Count": 0, "New Replicas Count": 1}
      $ kubectl get po -n cloudpc | grep scaler    
      golang-external-scaler-78f9dd69f7-5gd5d               1/1     Running     0              24m
      
      $  kubectl get deploy -n cloudpc test-1   
      NAME     READY   UP-TO-DATE   AVAILABLE   AGE
      test-1   1/1     1            1           37m


      External Scaler 개발하기

      이제 프로젝트 셋팅도 마쳤으니 본격적으로 개발로 들어가봐야겠죠!

      main.go 를 보면 알 수 있지만, external scaler를 구현하기 위한 인터페이스가 사전 정의되어 있습니다.

      service ExternalScaler {
          rpc IsActive(ScaledObjectRef) returns (IsActiveResponse) {}
          rpc StreamIsActive(ScaledObjectRef) returns (stream IsActiveResponse) {}
          rpc GetMetricSpec(ScaledObjectRef) returns (GetMetricSpecResponse) {}
          rpc GetMetrics(GetMetricsRequest) returns (GetMetricsResponse) {}
      }

      해당 함수들의 특징을 알기에 앞서, 언제 누가 해당 함수들을 호출 하는지, K8s HPA 와 KEDA operator 등 각 요소들 간의 상관관계를 먼저 알아보겠습니다.


      큰 그림 잡기

      KEDA external scaler 구조도

      Scaled Object 는 KEDA 가 어떤 앱을 어느 시점에 scale 할지 명세를 적은 K8s Custom Resource Definition (CRD) 입니다.

      pollingInterval, minReplicaCount, maxReplicaCount 등 scale 을 트리거 할 세부사항을 정의할 수 있어요.

      KEDA 에서도 K8s HPA 와 외부 소스들 과의 통신을 위해서 사전 정의된 인터페이스가 있는데요.

      우리가 직접 구현할 필요는 없지만, 배경지식 차원에서 Scaler Interface를 먼저 살펴보고 가겠습니다.

      type Scaler interface {
      	GetMetricsAndActivity(ctx context.Context, metricName string) ([]external_metrics.ExternalMetricValue, bool, error)
      	GetMetricSpecForScaling(ctx context.Context) []v2.MetricSpec
      	Close(ctx context.Context) error
      }
      
      type PushScaler interface {
      	Scaler
      	// Run is the only writer to the active channel and must close it once done.
      	Run(ctx context.Context, active chan<- bool)
      }
      • GetMetricSpecForScaling는 새로운 ScaledObject가 생성 되었을 때 이에 대한 HPA object를 생성합니다.

        요청 받은 HPA가 HPA가 scale 할 target value 등을 담아서 리턴하게 되죠.

      • GetMetricsAndActivity에 관해서는 HPA가 KEDA의 keda-operator-metrics-apiserver 를 통해서 해당 함수를 계속 poll 하고 있는데요.

        pollingInterval 마다 불러서 activity 여부가 true 인 경우, metric 을 maxReplicaCount 이내로 scale up 합니다.

        activity 가 false인 경우에는 metric 을 minReplicaCount 이내로 scale down 하게 돼요.

        ExternalScaler Interface의 IsActive와 GetMetricSpec와 관계가 있습니다.

      • Run 의 경우, pollingInterval과 관계 없이 언제 올지 모르는 응답을 받아서 scaling 을 하기 위한 함수로 ExternalScaler Interface의 StreamIsActive와 관련이 깊어요.


      ExternalScaler Interface

      이제 다시 돌아와서 실제로, External Scaler 개발 시 구현이 필요한 External Scaler 인터페이스를 좀 더 살펴볼게요.

      service ExternalScaler {
          rpc IsActive(ScaledObjectRef) returns (IsActiveResponse) {}
          rpc StreamIsActive(ScaledObjectRef) returns (stream IsActiveResponse) {}
          rpc GetMetricSpec(ScaledObjectRef) returns (GetMetricSpecResponse) {}
          rpc GetMetrics(GetMetricsRequest) returns (GetMetricsResponse) {}
      }

      이제 각 함수명이 조금 더 친숙해지셨을까요?~ㅎㅎ

      IsActive

      ScaledObject에 정의된 pollingInterval 마다 KEDA operator 에서 호출하는 함수예요.

      main.go 에 구현된 예제 코드에서는 https://earthquake.usgs.gov/ (외부 소스 엔드포인트)를 쿼리하여, 특정 조건일 때 deployment를 scale 하는 코드 로직을 담고 있어요.

      StreamIsActive

      서버 스트리밍 RPC 인데, keda-operator (client) 가 한번 요청 보낸 후, scaler에서 스트림 형식으로 오랫동안 커넥션을 유지시키며 응답 보내는 형태가 필요할 때 구현하여 사용할 수 있습니다.

      KEDA가 scale을 해야할 필요성이 있든 없든 IsActiveResponse 라는 output을 KEDA에 push 해줘요.

      poolingInterval 과 별개로 설정된 timer에 따라서 외부에 요청을 보낼 수 있거나 async event 를 처리하기 수월한 방법입니다.

      GetMetricSpec

      KEDA에서 일회성으로 호출하는 함수인데요. HPA를 정의할 때 각 MetricSpec 을 정의하기 위해서 호출 됩니다.

      GetMetrics

      ScaledObject에 정의된 pollingInterval 마다 KEDA에서 호출하여, metric 값을 구할 수 있어요.

      결국 이제 4가지 함수들을 필요에 따라서 적절하게 잘 로직 구현하는 일이 남았는데요.

      프로젝트로 진행중인 external scaler가 완성되면 또 다음편으로 찾아오겠습니다.

      읽어주셔서 감사합니다 :D


      참고용 링크 모음

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      hbinkim 님의 최신 블로그

      더보기
      동영상 기고하기