Kubernetes에서 Go 인터페이스 캡슐화 배우기
Olivia Novak
Dev Intern · Leapcell

인터페이스를 사용하여 입력 매개변수 세부 정보 숨기기
메서드의 입력 매개변수가 구조체인 경우 내부 호출은 입력에 대한 너무 많은 세부 정보를 드러냅니다. 이러한 경우 입력을 인터페이스로 암묵적으로 변환하여 내부 구현에서 필요한 메서드만 보도록 할 수 있습니다.
type Kubelet struct{} func (kl *Kubelet) HandlePodAdditions(pods []*Pod) { for _, pod := range pods { fmt.Printf("create pods : %s\n", pod.Status) } } func (kl *Kubelet) Run(updates <-chan Pod) { fmt.Println(" run kubelet") go kl.syncLoop(updates, kl) } func (kl *Kubelet) syncLoop(updates <-chan Pod, handler SyncHandler) { for { select { case pod := <-updates: handler.HandlePodAdditions([]*Pod{&pod}) } } } type SyncHandler interface { HandlePodAdditions(pods []*Pod) }
여기서 Kubelet 자체에 여러 메서드가 있음을 알 수 있습니다.
syncLoop
: 상태 동기화를 위한 루프Run
: 수신 대기 루프를 시작하는 데 사용HandlePodAdditions
: Pod 추가 처리를 위한 로직
syncLoop
는 실제로 kubelet의 다른 메서드에 대해 알 필요가 없으므로 SyncHandler
인터페이스를 정의하고 kubelet이 이 인터페이스를 구현하도록 한 다음 kubelet을 SyncHandler
로 syncLoop
에 인수로 전달합니다. 이렇게 하면 kubelet이 SyncHandler
로 형식 캐스팅됩니다.
이 변환 후에는 kubelet의 다른 메서드가 더 이상 입력 매개변수에 표시되지 않으므로 코딩 중에 syncLoop
내부의 로직에 더 집중할 수 있습니다.
그러나 이 접근 방식은 몇 가지 문제를 일으킬 수도 있습니다. 초기 추상화는 첫 번째 요구 사항 세트에 충분할 수 있지만 요구 사항이 증가하고 반복됨에 따라 인터페이스에 래핑되지 않은 kubelet의 다른 메서드를 사용해야 하는 경우 kubelet을 명시적으로 전달하거나 인터페이스에 추가해야 하며 둘 다 코딩 노력을 증가시키고 원래 캡슐화를 깨뜨립니다.
계층화된 캡슐화 및 숨김은 설계의 궁극적인 목표입니다. 즉, 코드의 각 부분이 자신이 관리해야 하는 것에만 집중할 수 있도록 하는 것입니다.
더 쉬운 모의 테스트를 위한 인터페이스 캡슐화
인터페이스를 통한 추상화를 통해 테스트 중에 신경 쓰지 않는 부분에 대한 모의 구조체를 직접 인스턴스화할 수 있습니다.
type OrderAPI interface { GetOrderId() string } type realOrderImpl struct{} func (r *realOrderImpl) GetOrderId() string { return "" } type mockOrderImpl struct{} func (m *mockOrderImpl) GetOrderId() string { return "mock" }
여기서 테스트 중에 GetOrderId
가 올바르게 작동하는지 여부에 신경 쓰지 않으면 OrderAPI
를 mockOrderImpl
로 직접 초기화할 수 있으며 모의의 로직은 필요한 만큼 복잡하게 만들 수 있습니다.
func TestGetOrderId(t *testing.T) { orderAPI := &mockOrderImpl{} // 주문 ID를 가져와야 하지만 테스트의 초점이 아닌 경우 모의 구조체로 초기화하십시오. fmt.Println(orderAPI.GetOrderId()) }
gomonkey
는 테스트 주입에도 사용할 수 있습니다. 따라서 기존 코드가 인터페이스를 통해 캡슐화되지 않은 경우에도 모의를 달성할 수 있으며 이 방법이 훨씬 더 강력합니다.
patches := gomonkey.ApplyFunc(GetOrder, func(orderId string) Order { return Order{ OrderId: orderId, OrderState: delivering, } }) return func() { patches.Reset() }
gomonkey
를 사용하면 함수 반환 값을 직접 설정할 수 있으므로 인터페이스 추상화는 구조체에서 인스턴스화된 콘텐츠만 처리할 수 있으므로 더 유연한 모의가 가능합니다.
여러 기본 구현을 위한 인터페이스 캡슐화
iptables
및 ipvs
와 같은 구현은 모든 네트워크 설정이 Service와 Endpoint를 모두 처리해야 하기 때문에 인터페이스 추상화를 통해 이루어집니다. 따라서 ServiceHandler
및 EndpointSliceHandler
를 추상화했습니다.
// ServiceHandler는 서비스 객체 변경에 대한 알림을 받는 데 사용되는 추상 인터페이스입니다. type ServiceHandler interface { // OnServiceAdd는 새 서비스 객체가 생성된 것으로 관찰될 때 호출됩니다. OnServiceAdd(service *v1.Service) // OnServiceUpdate는 기존 서비스 객체가 수정된 것으로 관찰될 때 호출됩니다. OnServiceUpdate(oldService, service *v1.Service) // OnServiceDelete는 기존 서비스 객체가 삭제된 것으로 관찰될 때 호출됩니다. OnServiceDelete(service *v1.Service) // OnServiceSynced는 모든 초기 이벤트 핸들러가 호출되고 상태가 로컬 캐시에 완전히 전파된 후 한 번 호출됩니다. OnServiceSynced() } // EndpointSliceHandler는 엔드포인트 슬라이스 객체 변경에 대한 알림을 받는 데 사용되는 추상 인터페이스입니다. type EndpointSliceHandler interface { // OnEndpointSliceAdd는 새 엔드포인트 슬라이스 객체가 생성된 것으로 관찰될 때 호출됩니다. OnEndpointSliceAdd(endpointSlice *discoveryv1.EndpointSlice) // OnEndpointSliceUpdate는 기존 엔드포인트 슬라이스 객체가 수정된 것으로 관찰될 때 호출됩니다. OnEndpointSliceUpdate(oldEndpointSlice, newEndpointSlice *discoveryv1.EndpointSlice) // OnEndpointSliceDelete는 기존 엔드포인트 슬라이스 객체가 삭제된 것으로 관찰될 때 호출됩니다. OnEndpointSliceDelete(endpointSlice *discoveryv1.EndpointSlice) // OnEndpointSlicesSynced는 모든 초기 이벤트 핸들러가 호출되고 상태가 로컬 캐시에 완전히 전파된 후 한 번 호출됩니다. OnEndpointSlicesSynced() }
그런 다음 Provider
를 통해 주입할 수 있습니다.
type Provider interface { config.EndpointSliceHandler config.ServiceHandler }
이것은 또한 제가 컴포넌트 작업을 할 때 가장 많이 사용하는 코딩 기술입니다. 유사한 작업을 추상화하여 기본 구현을 교체한 후에도 상위 레이어 코드를 변경할 필요가 없습니다.
예외 처리 캡슐화
goroutine을 시작한 후 예외를 캡처하지 않으면 예외가 발생하여 goroutine이 직접 패닉됩니다. 그러나 매번 전역 복구 로직을 작성하는 것은 매우 우아하지 않으므로 캡슐화된 HandleCrash
메서드를 사용할 수 있습니다.
package runtime var ( ReallyCrash = true ) // 기본 전역 패닉 핸들러 var PanicHandlers = []func(interface{}){logPanic} // 외부에서 추가 사용자 지정 패닉 핸들러를 전달할 수 있습니다. func HandleCrash(additionalHandlers ...func(interface{})) { if r := recover(); r != nil { for _, fn := range PanicHandlers { fn(r) } for _, fn := range additionalHandlers { fn(r) } if ReallyCrash { panic(r) } } }
이는 내부 예외 처리와 추가 핸들러의 외부 주입을 모두 지원합니다. 충돌하지 않으려면 필요에 따라 로직을 수정할 수 있습니다.
package runtime func Go(fn func()) { go func() { defer HandleCrash() fn() }() }
goroutine을 시작할 때 패닉 처리를 추가하는 것을 잊지 않도록 Go
메서드를 사용할 수 있습니다.
WaitGroup 캡슐화
import "sync" type Group struct { wg sync.WaitGroup } func (g *Group) Wait() { g.wg.Wait() } func (g *Group) Start(f func()) { g.wg.Add(1) go func() { defer g.wg.Done() f() }() }
여기서 가장 중요한 부분은 Add
및 Done
을 내부적으로 캡슐화하는 Start
메서드입니다. 몇 줄의 코드일 뿐이지만 waitgroup을 사용할 때마다 카운터를 늘리거나 완료하는 것을 잊지 않도록 보장합니다.
세마포로에서 트리거된 로직 캡슐화
type BoundedFrequencyRunner struct { sync.Mutex // 활성 트리거 run chan struct{} // 타이머 제한 timer *time.Timer // 실행할 실제 로직 fn func() } func NewBoundedFrequencyRunner(fn func()) *BoundedFrequencyRunner { return &BoundedFrequencyRunner{ run: make(chan struct{}, 1), fn: fn, timer: time.NewTimer(0), } } // Run은 실행을 트리거합니다. 여기에는 하나의 신호만 쓸 수 있으며 추가 신호는 차단 없이 삭제됩니다. 필요에 따라 큐 크기를 늘릴 수 있습니다. func (b *BoundedFrequencyRunner) Run() { select { case b.run <- struct{}{}: fmt.Println("Signal written successfully") default: fmt.Println("Signal already triggered once, discarding") } } func (b *BoundedFrequencyRunner) Loop() { b.timer.Reset(time.Second * 1) for { select { case <-b.run: fmt.Println("Run signal triggered") b.tryRun() case <-b.timer.C: fmt.Println("Timer triggered execution") b.tryRun() } } } func (b *BoundedFrequencyRunner) tryRun() { b.Lock() defer b.Unlock() // 속도 제한과 같은 로직을 여기에 추가할 수 있습니다. b.timer.Reset(time.Second * 1) b.fn() }
Leapcell은 Go 프로젝트 호스팅을 위한 최고의 선택입니다.
Leapcell은 웹 호스팅, 비동기 작업 및 Redis를 위한 차세대 서버리스 플랫폼입니다.
다국어 지원
- Node.js, Python, Go 또는 Rust로 개발하십시오.
무제한 프로젝트를 무료로 배포
- 사용량에 대해서만 지불하십시오. 요청도 없고 요금도 없습니다.
탁월한 비용 효율성
- 유휴 요금 없이 사용량에 따라 지불합니다.
- 예: $25는 평균 응답 시간이 60ms인 694만 건의 요청을 지원합니다.
간소화된 개발자 경험
- 손쉬운 설정을 위한 직관적인 UI.
- 완전 자동화된 CI/CD 파이프라인 및 GitOps 통합.
- 실행 가능한 통찰력을 위한 실시간 메트릭 및 로깅.
손쉬운 확장성 및 고성능
- 고 동시성을 쉽게 처리하기 위한 자동 확장.
- 운영 오버헤드가 전혀 없습니다. 빌드에만 집중하십시오.
Documentation에서 더 많은 정보를 알아보십시오!
X에서 팔로우하세요: @LeapcellHQ