23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
쿠버네티스 KEDA에 관련해서는 LEN님이 이미 작성해주신 소개글도 있고, 한번쯤은 들어보셨을 텐데요.
KEDA에서 이미 개발된 빌트인 scaler를 편하게 사용하는 방법도 있지만,
제 경우처럼 필요한 scaler가 외부 endpoint와 연동이 필요하여 external scaler를 직접 개발해야 될 때,
참고하시면 좋을 내용으로 external scaler 개발기 1편! 개발 초기 단계 내용을 담아봤습니다.
프로젝트 셋팅부터 개발 및 배포까지 시작합니다!
들어가기에 앞서, 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: falseExternal Scaler를 위한 Go 프로젝트 셋팅 방법은 Keda Docs 에 스텝 바이 스텝으로 상세하게 기술되어 있습니다.
본 글에서 시행착오를 거친 추가 설명과 최신 버전으로 업데이트 하여 반영을 하고 있습니다만, 한번 참고하셔도 좋을 거 같아요~
개발언어는 KEDA GitHub project에 존재하는 다른 scaler들와 마찬가지로 Golang 으로 선정하였습니다.
go 설치 : https://go.dev/doc/install
protobuf 설치 : https://github.com/protocolbuffers/protobuf/releases
proto 파일 다운로드 : https://github.com/kedacore/keda/blob/main/pkg/scalers/externalscaler/externalscaler.proto
protoc-gen-go 설치 : go install github.com/golang/protobuf/protoc-gen-go@v1.5.4
프로젝트 생성 :
$ go mod init example.com/external-scaler/sample
$ mkdir externalscaler
$ protoc externalscaler.proto --go_out=plugins=grpc:externalscaler위 과정을 거치게 되면 아래 그림과 같이 프로젝트 최초 상태가 구성 됩니다.
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 에 함께 반영해주세요.
결과물은 아래와 같이 확인 할 수 있습니다.
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이제 프로젝트 셋팅도 마쳤으니 본격적으로 개발로 들어가봐야겠죠!
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 등 각 요소들 간의 상관관계를 먼저 알아보겠습니다.
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와 관련이 깊어요.
이제 다시 돌아와서 실제로, 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) {}
}이제 각 함수명이 조금 더 친숙해지셨을까요?~ㅎㅎ
ScaledObject에 정의된 pollingInterval 마다 KEDA operator 에서 호출하는 함수예요.
main.go 에 구현된 예제 코드에서는 https://earthquake.usgs.gov/ (외부 소스 엔드포인트)를 쿼리하여, 특정 조건일 때 deployment를 scale 하는 코드 로직을 담고 있어요.
서버 스트리밍 RPC 인데, keda-operator (client) 가 한번 요청 보낸 후, scaler에서 스트림 형식으로 오랫동안 커넥션을 유지시키며 응답 보내는 형태가 필요할 때 구현하여 사용할 수 있습니다.
KEDA가 scale을 해야할 필요성이 있든 없든 IsActiveResponse 라는 output을 KEDA에 push 해줘요.
poolingInterval 과 별개로 설정된 timer에 따라서 외부에 요청을 보낼 수 있거나 async event 를 처리하기 수월한 방법입니다.
KEDA에서 일회성으로 호출하는 함수인데요. HPA를 정의할 때 각 MetricSpec 을 정의하기 위해서 호출 됩니다.
ScaledObject에 정의된 pollingInterval 마다 KEDA에서 호출하여, metric 값을 구할 수 있어요.
결국 이제 4가지 함수들을 필요에 따라서 적절하게 잘 로직 구현하는 일이 남았는데요.
프로젝트로 진행중인 external scaler가 완성되면 또 다음편으로 찾아오겠습니다.
읽어주셔서 감사합니다 :D
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.