clientcmd를 사용한 일관된 API 서버 접근
Source: Kubernetes Blog
만약 Kubernetes API용 명령줄 클라이언트를 개발하고 싶다면—특히 kubectl 플러그인으로 사용할 수 있는 경우—사용자에게 kubectl과 친숙하게 느껴지도록 만드는 방법이 궁금할 수 있습니다.
kubectl options 출력물을 한눈에 보면 압도적일 수 있습니다:
“정말로 저 옵션들을 모두 구현해야 하나요?”
걱정하지 마세요! Kubernetes 프로젝트에서는 대부분의 복잡한 작업을 대신해 주는 두 개의 라이브러리를 이미 제공하고 있습니다:
- clientcmd – kubectl 스타일의 명령줄 인수를 파싱하고
restclient.Config를 구축합니다. - cli‑runtime –
clientcmd위에 추가 기능을 제공합니다.
이 글에서는 clientcmd 라이브러리에 초점을 맞춥니다.
일반 철학
clientcmd는 client‑go의 일부이므로 궁극적인 목적은 API 서버에 요청을 보낼 수 있는 restclient.Config를 생성하는 것입니다. 이는 kubectl과 동일한 의미 체계를 따릅니다:
| 소스 | 설명 |
|---|---|
~/.kube/config (또는 동등한 파일) | 기본 설정 |
KUBECONFIG 환경 변수 | 내용이 병합되는 하나 이상의 파일 |
| 명령줄 인수 | 위의 모든 것을 재정의 |
clientcmd는 자동으로 --kubeconfig 플래그를 추가하지 않습니다; “플래그 바인딩” 섹션에서 추가하는 방법을 확인할 수 있습니다.
사용 가능한 기능
clientcmd는 프로그램이 다음을 처리하도록 합니다:
KUBECONFIG를 통한 kubeconfig 선택- 컨텍스트 선택
- 네임스페이스 선택
- 클라이언트 인증서 및 개인 키
- 사용자 가장(임퍼시네이션)
- HTTP 기본 인증(사용자 이름/비밀번호)
구성 병합
KUBECONFIG는 파일들의 콜론으로 구분된 목록을 포함할 수 있으며, 내용이 병합됩니다.- 맵 기반 설정 – 첫 번째 정의가 적용되고, 이후 정의는 무시됩니다.
- 맵이 아닌 설정 – 마지막 정의가 적용됩니다.
KUBECONFIG에 지정된 존재하지 않는 파일은 경고만 발생시킵니다.- 사용자가 경로를 명시적으로 제공할 경우(예:
--kubeconfig), 해당 파일은 존재해야 합니다. KUBECONFIG가 설정되지 않은 경우,~/.kube/config가 사용됩니다(존재한다면).
전체 프로세스
일반적인 사용 패턴은 clientcmd 패키지 문서에 요약되어 있습니다:
// 1️⃣ 로딩 규칙 구축 (기본값은 KUBECONFIG 또는 ~/.kube/config)
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
// – 필요에 따라 여기서 순서/파일을 사용자 정의할 수 있습니다
// 2️⃣ 오버라이드 준비 (플래그에서 나중에 채워짐)
configOverrides := &clientcmd.ConfigOverrides{}
// 3️⃣ 지연 로딩 클라이언트 구성 생성
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
loadingRules,
configOverrides,
)
// 4️⃣ 최종 REST 구성 해결
config, err := kubeConfig.ClientConfig()
if err != nil {
// 오류 처리
}
// 5️⃣ 클라이언트 구축 (동적 클라이언트를 사용하는 예시)
client, err := dynamic.NewForConfig(config)
if err != nil {
// 오류 처리
}
이 글에서는 위 번호가 매겨진 주석에 해당하는 여섯 단계를 차례대로 살펴보겠습니다:
- 로드 규칙 구성
- 오버라이드 구성
- 플래그 집합 구축
- 플래그 바인딩
- 병합된 구성 구축
- API 클라이언트 얻기
1️⃣ Configure the Loading Rules
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
KUBECONFIG가 설정되어 있으면 사용하고, 그렇지 않으면~/.kube/config를 사용합니다.- 기본 파일이 사용될 경우, 매우 오래된 기본값(
~/.kube/.kubeconfig)에서 설정을 마이그레이션할 수 있습니다.
표준이 아닌 순서나 추가 파일이 필요하면 사용자 정의 ClientConfigLoadingRules를 만들 수 있지만, 대부분의 경우 기본값으로 충분합니다.
2️⃣ Configure the Overrides
clientcmd.ConfigOverrides는 kubeconfig 파일에서 로드된 내용에 덮어쓰기할 값을 저장하는 구조체입니다. 이 가이드에서는 오버라이드를 명령줄 플래그(pflag 라이브러리, Go의 flag 패키지를 대체하는 라이브러리)를 통해 채웁니다.
configOverrides := &clientcmd.ConfigOverrides{}
보통 여기서 직접 값을 설정하지 않으며, 다음 단계에서 구조체 필드를 플래그에 바인딩하게 됩니다.
3️⃣ 플래그 집합 만들기
플래그는 명령줄 인수를 나타냅니다 (예: --namespace 또는 -n). clientcmd는 각각 FlagInfo 구조체로 감싸진 세 그룹의 플래그를 제공합니다:
| 플래그 그룹 | 일반적인 인수 |
|---|---|
| 인증 | --certificate-authority, --token, --as, --username, --password |
| 클러스터 | --server, --certificate-authority, --insecure-skip-tls-verify, --proxy-url, --tls-server-name, --compress |
| 컨텍스트 | --context, --cluster, --user, --namespace |
추천 집합에는 세 그룹 모두와 --timeout 플래그가 포함됩니다.
추천 플래그 생성자
// No prefix → flags like --context, --namespace, etc.
flags := clientcmd.RecommendedConfigOverrideFlags("")
// With a prefix → flags like --from-context, --from-namespace, etc.
flags := clientcmd.RecommendedConfigOverrideFlags("from-")
--timeout플래그는 기본값이0입니다.--namespace플래그는 짧은 별칭-n도 가집니다.
주의: 접두사는 짧은 이름에 영향을 주지 않습니다. 여러 접두사가 붙은 플래그 집합을 만들 경우, -n 별칭을 유지할 수 있는 것은 하나뿐입니다. 다른 집합에서는 짧은 이름을 비워 주세요:
kflags := clientcmd.RecommendedConfigOverrideFlags(prefix)
kflags.ContextOverrideFlags.Namespace.ShortName = "" // removes -n
4️⃣ 플래그 바인딩
이제 플래그 정의를 ConfigOverrides 구조체에 연결하여 명령줄에 입력된 값이 오버라이드에 채워지도록 합니다.
// Assume you have a pflag.FlagSet called fs
fs := pflag.NewFlagSet("myclient", pflag.ExitOnError)
// Build the flag structs (choose a prefix if you like)
flags := clientcmd.RecommendedConfigOverrideFlags("")
// Register the flags
flags.BindFlags(fs) // registers the flags
flags.BindFlagSet(fs, configOverrides) // populates configOverrides when parsed
kubectl과 동일하게 --kubeconfig 플래그도 필요하다면 직접 추가합니다:
fs.StringVar(&configOverrides.ClusterInfo.KubeConfigPath, "kubeconfig", "", "Path to the kubeconfig file")
마지막으로 명령줄을 파싱합니다:
if err := fs.Parse(os.Args[1:]); err != nil {
// handle parse error
}
5️⃣ 병합된 구성 만들기
로드 규칙과 오버라이드가 준비되면, 지연 로딩 클라이언트 구성을 생성하고 최종 rest.Config를 해결합니다:
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
loadingRules,
configOverrides,
)
restConfig, err := kubeConfig.ClientConfig()
if err != nil {
// handle error (e.g., missing kubeconfig, invalid overrides)
}
restConfig에는 이제 다음으로부터 병합된 설정이 포함됩니다:
KUBECONFIG(또는 기본 파일)로 지정된 파일들- 플래그를 통해 제공된 오버라이드
6️⃣ API 클라이언트 얻기
rest.Config를 사용하여 필요한 Kubernetes 클라이언트를 인스턴스화합니다. 다음은 몇 가지 일반적인 예시입니다:
// CoreV1 client
coreClient, err := kubernetes.NewForConfig(restConfig)
// Dynamic client (works with any resource)
dynClient, err := dynamic.NewForConfig(restConfig)
// Discovery client
discClient, err := discovery.NewDiscoveryClientForConfig(restConfig)
여기서부터는 kubectl이 하는 것처럼 클러스터와 상호작용할 수 있습니다.
TL;DR – 6단계 체크리스트
| 단계 | 코드 스니펫 |
|---|---|
| 1️⃣ Loading rules | loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() |
| 2️⃣ Overrides | configOverrides := &clientcmd.ConfigOverrides{} |
| 3️⃣ Flags | flags := clientcmd.RecommendedConfigOverrideFlags("") |
| 4️⃣ Bind | flags.BindFlags(fs); flags.BindFlagSet(fs, configOverrides) |
| 5️⃣ Merge | kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides); restConfig, _ := kubeConfig.ClientConfig() |
| 6️⃣ Client | client, _ := kubernetes.NewForConfig(restConfig) |
이 단계들을 따르면 kubectl과 동일한 외관과 동작을 Go CLI에 적용할 수 있으며, 모든 플래그를 직접 구현할 필요가 없습니다. 즐거운 코딩 되세요!
플래그 비활성화
플래그를 완전히 비활성화하려면, 해당 플래그의 긴 이름을 비워두세요:
kflags.ContextOverrideFlags.Namespace.LongName = ""
플래그 바인딩 (대안)
플래그 집합이 정의되면, clientcmd.BindOverrideFlags를 사용하여 명령줄 인수를 오버라이드에 바인딩할 수 있습니다. 이는 Go의 표준 flag 패키지 대신 pflag FlagSet이 필요합니다.
--kubeconfig도 바인딩하려면, 로딩 규칙에서 ExplicitPath를 바인딩하여 지금 수행하십시오:
flags.StringVarP(&loadingRules.ExplicitPath,
"kubeconfig", "", "", "absolute path(s) to the kubeconfig file(s)")
병합된 구성 빌드 (대안)
병합된 구성을 만들기 위해 두 가지 함수가 제공됩니다:
clientcmd.NewInteractiveDeferredLoadingClientConfigclientcmd.NewNonInteractiveDeferredLoadingClientConfig
interactive 버전은 제공된 리더를 사용해 인증 정보를 대화형으로 물어볼 수 있는 반면, non‑interactive 버전은 호출자가 제공한 정보만 사용합니다. 두 함수 모두 “지연(deferred)” 방식이며, 명령줄 인수를 파싱하기 이전에 호출할 수 있습니다; 실제로 구성이 생성될 때까지 파싱된 플래그 값이 구성에 반영됩니다.
API 클라이언트 얻기 (대안)
병합된 구성은 ClientConfig 인스턴스로 반환됩니다. ClientConfig() 메서드를 호출하면 해당 인스턴스에서 API 클라이언트를 얻을 수 있습니다.
구성을 찾을 수 없는 경우(예: KUBECONFIG가 비어 있거나 존재하지 않는 파일을 가리키고, ~/.kube/config가 없으며, 명령줄 오버라이드가 제공되지 않은 경우) 기본 설정은 KUBERNETES_MASTER를 언급하는 모호한 오류를 반환합니다. 이 레거시 동작은 kubectl의 --local 및 --dry-run 플래그에 대해서만 유지됩니다.
Tip: clientcmd.IsEmptyConfig(err) 로 “빈 구성” 오류를 감지하고 사용자에게 더 명확한 메시지를 표시하세요.
Namespace() 메서드도 유용합니다—사용해야 할 네임스페이스를 반환하고, 사용자가 --namespace 플래그를 통해 네임스페이스를 오버라이드했는지 여부를 알려줍니다.
전체 예제
다음은 완전하고 실행 가능한 예제입니다:
package main
import (
"context"
"fmt"
"os"
"github.com/spf13/pflag"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
func main() {
// Loading rules – no configuration yet
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
// Overrides and flag (command‑line argument) setup
configOverrides := &clientcmd.ConfigOverrides{}
flags := pflag.NewFlagSet("clientcmddemo", pflag.ExitOnError)
// Bind the standard override flags
clientcmd.BindOverrideFlags(configOverrides, flags,
clientcmd.RecommendedConfigOverrideFlags(""))
// Bind the --kubeconfig flag
flags.StringVarP(&loadingRules.ExplicitPath,
"kubeconfig", "", "", "absolute path(s) to the kubeconfig file(s)")
// Parse the command‑line arguments
flags.Parse(os.Args)
// Construct the client configuration (non‑interactive)
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
loadingRules, configOverrides)
// Get a REST config for the client‑go libraries
config, err := kubeConfig.ClientConfig()
if err != nil {
if clientcmd.IsEmptyConfig(err) {
panic("Please provide a configuration pointing to the Kubernetes API server")
}
panic(err)
}
// Build the typed clientset
client, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err)
}
// Determine which namespace to use
namespace, overridden, err := kubeConfig.Namespace()
if err != nil {
panic(err)
}
fmt.Printf("Chosen namespace: %s; overridden: %t\n", namespace, overridden)
// Use the client – list all nodes
nodeList, err := client.CoreV1().Nodes().List(context.TODO(), v1.ListOptions{})
if err != nil {
panic(err)
}
for _, node := range nodeList.Items {
fmt.Println(node.Name)
}
}
코딩을 즐기세요, 그리고 친숙한 사용 패턴으로 도구를 만드는 데 관심을 가져 주셔서 감사합니다!