근묵자흑
Kubernetes API Server : 코드 레벨에서 이해하는 내부 동작 원리 본문
Kubernetes의 심장부인 kube-apiserver의 내부 구조를 코드 레벨에서 완전 분석해보자. 메인 엔트리 포인트부터 HTTP 요청 처리 파이프라인까지, 실제 소스코드와 함께 상세히 알아본다.
📋 목차
개요
Kubernetes API Server는 클러스터의 모든 상태를 관리하는 핵심 컴포넌트입니다. 이 글에서는 kube-apiserver의 실제 소스코드를 분석하여 다음을 이해해보겠습니다:
- 🚀 애플리케이션 시작부터 HTTP 서버 실행까지의 전체 흐름
- 🔐 인증(Authentication)과 인가(Authorization) 처리 방식
- 🌐 HTTP 요청이 실제 API 핸들러까지 도달하는 과정
- 🎯 요청 라우팅과 필터 체인 메커니즘
프로젝트 구조 및 아키텍처
📁 디렉토리 구조
kubernetes/
├── cmd/kube-apiserver/ # 메인 엔트리 포인트
│ ├── apiserver.go # main() 함수
│ └── app/
│ ├── server.go # 실제 서버 실행 로직
│ ├── options/ # 커맨드라인 옵션 처리
│ └── aggregator.go # API Aggregation 설정
│
└── staging/src/k8s.io/
├── apiserver/ # 핵심 API 서버 라이브러리
│ ├── pkg/
│ │ ├── server/ # GenericAPIServer 구현
│ │ ├── endpoints/ # HTTP 핸들러
│ │ ├── authentication/ # 인증
│ │ ├── authorization/ # 인가
│ │ └── admission/ # Admission Control
│ └── ...
└── api/ # API 리소스 정의
├── core/v1/ # Pod, Service 등
└── apps/v1/ # Deployment 등
🏗️ 서버 체인 아키텍처
kube-apiserver는 3개의 서버가 연결된 체인 구조로 동작합니다:
클라이언트 요청
↓
[Aggregator Server] ← 최상위 (외부 API 프록시)
↓ (delegation)
[KubeAPIServer] ← 중간 (핵심 k8s API)
↓ (delegation)
[API Extensions] ← 최하위 (CRD API)
↓
[404 Handler] ← 매칭 안되면 404
메인 엔트리 포인트 분석
📄 cmd/kube-apiserver/apiserver.go
가장 먼저 살펴볼 파일은 메인 엔트리 포인트입니다:
// APIServer is the main API server and master for the cluster.
// It is responsible for serving the cluster management API.
package main
import (
"os"
_ "time/tzdata" // CronJob에서 시간대 지원을 위해 필요 (빌드시 바이너리에 포함)
"k8s.io/component-base/cli"
_ "k8s.io/component-base/logs/json/register" // JSON 로그 포맷 등록 (init() 함수 실행)
_ "k8s.io/component-base/metrics/prometheus/clientgo" // Prometheus 메트릭 수집을 위한 client-go 플러그인
_ "k8s.io/component-base/metrics/prometheus/version" // 버전 정보 메트릭 등록
"k8s.io/kubernetes/cmd/kube-apiserver/app"
)
func main() {
// 1. API 서버 커맨드 생성 (cobra.Command 반환)
// 여기서 모든 플래그와 설정이 초기화됨
command := app.NewAPIServerCommand()
// 2. 커맨드 실행 (내부적으로 RunE 함수 호출)
// cli.Run은 에러 처리, 로깅 초기화 등을 포함
code := cli.Run(command)
// 3. 프로세스 종료 코드 설정
os.Exit(code)
}
💡 코드 읽기 포인트
- import 블록의
_
(언더스코어): init() 함수만 실행하기 위한 임포트time/tzdata
: CronJob의 시간대 처리logs/json/register
: JSON 로그 포맷 등록metrics/prometheus/*
: 메트릭 수집 플러그인 로드
- cli.Run(): component-base의 표준화된 실행 패턴
- 간결한 main: 실제 로직은 app 패키지에 위임하는 단순한 구조
서버 초기화 및 설정
📄 cmd/kube-apiserver/app/server.go
실제 서버 로직이 시작되는 핵심 파일입니다:
// NewAPIServerCommand는 기본 파라미터를 가진 cobra.Command 객체를 생성합니다
func NewAPIServerCommand() *cobra.Command {
// 1. 서버 실행 옵션 초기화 (플래그, 설정값 등)
s := options.NewServerRunOptions()
// 2. 시그널 핸들러 설정 (SIGTERM, SIGINT 처리)
ctx := genericapiserver.SetupSignalContext()
// 3. Feature Gate 설정 (실험적 기능 활성화/비활성화)
featureGate := s.GenericServerRunOptions.ComponentGlobalsRegistry.
FeatureGateFor(basecompatibility.DefaultKubeComponent)
cmd := &cobra.Command{
Use: "kube-apiserver",
Long: `Kubernetes API 서버의 역할:
- API 객체(Pod, Service, Deployment 등)의 유효성 검사
- 데이터 설정 및 저장
- REST API 제공
- 클러스터의 공유 상태에 대한 프론트엔드 역할`,
SilenceUsage: true, // 에러 발생시 사용법 출력 안함
// 실행 전 준비 작업
PersistentPreRunE: func(*cobra.Command, []string) error {
// 전역 설정 적용
if err := s.GenericServerRunOptions.ComponentGlobalsRegistry.Set(); err != nil {
return err
}
// client-go 경고 메시지 비활성화 (loopback 연결시 자기 자신에게 경고 방지)
rest.SetDefaultWarningHandler(rest.NoWarnings{})
return nil
},
// 메인 실행 함수
RunE: func(cmd *cobra.Command, args []string) error {
// 1. 버전 출력 후 종료 처리
verflag.PrintAndExitIfRequested()
// 2. 로깅 설정 검증 및 적용
if err := logsapi.ValidateAndApply(s.Logs, featureGate); err != nil {
return err
}
// 3. 플래그 값 출력 (디버깅용)
cliflag.PrintFlags(cmd.Flags())
// 4. 옵션 완성 (기본값 설정, 파일 읽기 등)
completedOptions, err := s.Complete(ctx)
if err != nil {
return err
}
// 5. 옵션 유효성 검사
if errs := completedOptions.Validate(); len(errs) != 0 {
return utilerrors.NewAggregate(errs)
}
// 6. 메트릭 추가
featureGate.(featuregate.MutableFeatureGate).AddMetrics()
s.GenericServerRunOptions.ComponentGlobalsRegistry.AddMetrics()
// 7. 실제 서버 실행!
return Run(ctx, completedOptions)
},
}
// 플래그 설정 코드...
return cmd
}
🚀 Run 함수 - 핵심 실행 로직
// Run은 지정된 APIServer를 실행합니다. 이 함수는 절대 종료되지 않아야 합니다.
func Run(ctx context.Context, opts options.CompletedOptions) error {
// 디버깅을 위해 버전 정보 즉시 로깅
klog.Infof("Version: %+v", utilversion.Get())
// Go 런타임 설정 로깅 (성능 튜닝시 중요)
klog.InfoS("Golang settings",
"GOGC", os.Getenv("GOGC"), // 가비지 컬렉션 임계값
"GOMAXPROCS", os.Getenv("GOMAXPROCS"), // 최대 CPU 코어 수
"GOTRACEBACK", os.Getenv("GOTRACEBACK")) // 패닉시 스택트레이스 레벨
// 1. 설정 생성
config, err := NewConfig(opts)
if err != nil {
return err
}
// 2. 설정 완성 (누락된 값 채우기)
completed, err := config.Complete()
if err != nil {
return err
}
// 3. 서버 체인 생성 (3개 서버가 연결됨)
server, err := CreateServerChain(completed)
if err != nil {
return err
}
// 4. 실행 준비 (포트 바인딩, 핸들러 등록 등)
prepared, err := server.PrepareRun()
if err != nil {
return err
}
// 5. 서버 실행 (블로킹)
return prepared.Run(ctx)
}
🔗 CreateServerChain - 3개의 서버 체인
// CreateServerChain은 위임(delegation)을 통해 연결된 API 서버들을 생성합니다.
func CreateServerChain(config CompletedConfig) (*aggregatorapiserver.APIAggregator, error) {
// 404 핸들러 생성
notFoundHandler := notfoundhandler.New(
config.KubeAPIs.ControlPlane.Generic.Serializer,
genericapifilters.NoMuxAndDiscoveryIncompleteKey
)
// 1. API Extensions Server (CRD 처리)
// - CustomResourceDefinition 관리
// - 동적 API 등록
apiExtensionsServer, err := config.ApiExtensions.New(
genericapiserver.NewEmptyDelegateWithCustomHandler(notFoundHandler)
)
if err != nil {
return nil, err
}
// CRD가 활성화되어 있는지 확인
crdAPIEnabled := config.ApiExtensions.GenericConfig.MergedResourceConfig.ResourceEnabled(
apiextensionsv1.SchemeGroupVersion.WithResource("customresourcedefinitions")
)
// 2. KubeAPIServer (핵심 API)
// - core/v1 (Pod, Service, ConfigMap 등)
// - apps/v1 (Deployment, StatefulSet 등)
// - batch/v1 (Job, CronJob 등)
kubeAPIServer, err := config.KubeAPIs.New(apiExtensionsServer.GenericAPIServer)
if err != nil {
return nil, err
}
// 3. Aggregator Server (API 집계)
// - 외부 API 서버 프록시
// - metrics-server, custom metrics API 등
aggregatorServer, err := controlplaneapiserver.CreateAggregatorServer(
config.Aggregator,
kubeAPIServer.ControlPlane.GenericAPIServer,
apiExtensionsServer.Informers.Apiextensions().V1().CustomResourceDefinitions(),
crdAPIEnabled,
apiVersionPriorities
)
if err != nil {
return nil, err
}
return aggregatorServer, nil
}
HTTP 처리 파이프라인
🌐 전체 처리 흐름
HTTP 요청이 kube-apiserver에 도달했을 때의 처리 흐름입니다:
🌐 HTTP 요청
↓
🛡️ PanicRecovery (패닉 잡기)
↓
📋 RequestInfo (요청 정보 파싱)
↓
🔐 Authentication (인증: 누구인가?)
↓
🛡️ Authorization (인가: 권한이 있는가?)
↓
📊 Audit (감사 로깅)
↓
👤 Impersonation (사용자 가장)
↓
🚦 FlowControl (속도 제한)
↓
🎯 Director (라우팅 결정)
├─ gorestful → API 요청 처리
│ ├─ GET /api/v1/pods
│ ├─ POST /apis/apps/v1/deployments
│ └─ DELETE /api/v1/namespaces/default/pods/my-pod
└─ nonGoRestfulMux → 시스템 엔드포인트
├─ GET /metrics
├─ GET /healthz
└─ GET /debug/pprof/heap
🏗️ 필터 체인 구성 원리
// DefaultBuildHandlerChain - 필터 체인을 안쪽부터 바깥쪽으로 구성
func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
handler := apiHandler
// 🔍 주의: 실행 순서의 역순으로 구성됩니다!
// 실제 실행 순서: PanicRecovery → RequestInfo → ... → Authorization → API Handler
// 7. Authorization (인가) - 사용자가 해당 작업을 수행할 권한이 있는가?
handler = genericapifilters.WithAuthorization(handler, c.Authorization.Authorizer, c.Serializer)
// 6. Flow Control (흐름 제어) - 요청 속도 제한
if c.FlowControl != nil {
handler = genericfilters.WithPriorityAndFairness(handler, c.LongRunningFunc,
c.FlowControl, requestWorkEstimator,
c.RequestTimeout/4)
}
// 5. Impersonation (사용자 가장) - 다른 사용자로 가장하기
handler = genericapifilters.WithImpersonation(handler, c.Authorization.Authorizer, c.Serializer)
// 4. Audit (감사) - 보안 및 컴플라이언스를 위한 로깅
handler = genericapifilters.WithAudit(handler, c.AuditBackend,
c.AuditPolicyRuleEvaluator, c.LongRunningFunc)
// 3. Authentication (인증) - 사용자가 누구인가?
handler = genericapifilters.WithAuthentication(handler, c.Authentication.Authenticator,
failedHandler, c.Authentication.APIAudiences,
c.Authentication.RequestHeaderConfig)
// 2. RequestInfo (요청 정보 파싱) - URL 경로를 분석하여 API 정보 추출
handler = genericapifilters.WithRequestInfo(handler, c.RequestInfoResolver)
// 1. PanicRecovery (패닉 복구) - 가장 바깥쪽, 모든 패닉을 잡아서 HTTP 500 반환
handler = genericfilters.WithPanicRecovery(handler, c.RequestInfoResolver)
return handler
}
필터 체인 상세 분석
🔐 인증 필터 (Authentication Filter)
인증 필터는 요청자의 신원을 확인하는 핵심 컴포넌트입니다:
// WithAuthentication은 주어진 요청을 사용자로 인증하려고 시도하는 HTTP 핸들러를 생성합니다.
// 인증된 사용자 정보는 요청의 컨텍스트에 저장됩니다.
func WithAuthentication(handler http.Handler, auth authenticator.Request,
failed http.Handler, apiAuds authenticator.Audiences,
requestHeaderConfig *authenticatorfactory.RequestHeaderConfig) http.Handler {
// 인증이 비활성화된 경우 경고 로그 출력
if auth == nil {
klog.Warning("Authentication is disabled")
return handler
}
// 표준 프록시 헤더 설정 (front proxy용)
standardRequestHeaderConfig := &authenticatorfactory.RequestHeaderConfig{
UsernameHeaders: headerrequest.StaticStringSlice{"X-Remote-User"},
UIDHeaders: headerrequest.StaticStringSlice{"X-Remote-Uid"},
GroupHeaders: headerrequest.StaticStringSlice{"X-Remote-Group"},
ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"X-Remote-Extra-"},
}
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// 🕐 인증 시작 시간 측정 (메트릭용)
authenticationStart := time.Now()
// 🎯 API Audience 설정 (JWT 토큰 검증용)
if len(apiAuds) > 0 {
req = req.WithContext(authenticator.WithAudiences(req.Context(), apiAuds))
}
// 🔐 실제 인증 수행
resp, ok, err := auth.AuthenticateRequest(req)
authenticationFinish := time.Now()
// 📊 메트릭 수집 (인증 성공/실패, 지연시간 등)
defer func() {
metrics(req.Context(), resp, ok, err, apiAuds, authenticationStart, authenticationFinish)
genericapirequest.TrackAuthenticationLatency(req.Context(),
authenticationFinish.Sub(authenticationStart))
}()
// ❌ 인증 실패시 처리
if err != nil || !ok {
if err != nil {
klog.ErrorS(err, "Unable to authenticate the request")
}
failed.ServeHTTP(w, req) // 실패 핸들러 호출 (보통 401 Unauthorized)
return
}
// 🎯 Audience 검증 (JWT 토큰의 aud 클레임 확인)
if !audiencesAreAcceptable(apiAuds, resp.Audiences) {
err = fmt.Errorf("unable to match the audience: %v , accepted: %v",
resp.Audiences, apiAuds)
klog.Error(err)
failed.ServeHTTP(w, req)
return
}
// 🧹 보안 헤더 정리
// 인증 성공 후 Authorization 헤더 제거 (보안상 이유)
req.Header.Del("Authorization")
// 표준 front proxy 헤더들 제거
headerrequest.ClearAuthenticationHeaders(
req.Header,
standardRequestHeaderConfig.UsernameHeaders,
standardRequestHeaderConfig.UIDHeaders,
standardRequestHeaderConfig.GroupHeaders,
standardRequestHeaderConfig.ExtraHeaderPrefixes,
)
// 🛡️ HTTP/2 DoS 공격 완화 (CVE-2023-44487, CVE-2023-39325)
// 익명 사용자의 HTTP/2 연결을 제한
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.UnauthenticatedHTTP2DOSMitigation) &&
req.ProtoMajor == 2 && isAnonymousUser(resp.User) {
// 연결을 이 요청으로만 제한하고 GOAWAY 전송 후 TCP 연결 종료
w.Header().Set("Connection", "close")
}
// ✅ 인증된 사용자 정보를 컨텍스트에 저장
req = req.WithContext(genericapirequest.WithUser(req.Context(), resp.User))
// 다음 핸들러 호출
handler.ServeHTTP(w, req)
})
}
🛡️ 인가 필터 (Authorization Filter)
인증된 사용자가 요청한 작업을 수행할 권한이 있는지 확인합니다:
// WithAuthorization은 인가된 요청만 핸들러로 전달하고, 그렇지 않으면 forbidden 에러를 반환합니다.
func WithAuthorization(handler http.Handler, auth authorizer.Authorizer,
s runtime.NegotiatedSerializer) http.Handler {
if auth == nil {
klog.Warning("Authorization is disabled")
return handler
}
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
authorizationStart := time.Now()
// 🔍 인가 속성 추출 (사용자, 리소스, 동작 등)
attributes, err := GetAuthorizerAttributes(ctx)
if err != nil {
responsewriters.InternalError(w, req, err)
return
}
// 🛡️ 실제 인가 결정 수행
// attributes에는 다음 정보가 포함됨:
// - User: 인증된 사용자 정보
// - Verb: 수행하려는 동작 (get, list, create, update, delete 등)
// - Resource: 대상 리소스 (pods, services, deployments 등)
// - Namespace: 네임스페이스
// - Name: 리소스 이름
authorized, reason, err := auth.Authorize(ctx, attributes)
authorizationFinish := time.Now()
defer func() {
// 📊 인가 메트릭 수집
metrics(ctx, authorized, err, authorizationStart, authorizationFinish)
request.TrackAuthorizationLatency(ctx, authorizationFinish.Sub(authorizationStart))
}()
// ✅ 인가 성공 시
// 💡 RBAC과 같은 인가자는 평가 에러가 있어도 요청을 허용할 수 있으므로
// 에러보다 결정을 먼저 확인합니다.
if authorized == authorizer.DecisionAllow {
// 감사 로그에 허용 결정 기록
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AdvancedAuditing) {
audit.AddAuditAnnotation(ctx, decisionAnnotationKey, decisionAllow)
audit.AddAuditAnnotation(ctx, reasonAnnotationKey, reason)
}
handler.ServeHTTP(w, req)
return
}
// ❌ 인가 실패 시
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AdvancedAuditing) {
audit.AddAuditAnnotation(ctx, decisionAnnotationKey, decisionForbid)
if err != nil {
audit.AddAuditAnnotation(ctx, reasonAnnotationKey, reasonError)
} else {
audit.AddAuditAnnotation(ctx, reasonAnnotationKey, reason)
}
}
// 403 Forbidden 응답 반환
responsewriters.Forbidden(ctx, attributes, w, req, reason, s)
})
}
요청 라우팅 시스템
🎯 Director 패턴
Director는 요청을 적절한 핸들러로 라우팅하는 역할을 담당합니다:
// director는 gorestful과 non-gorestful 핸들러 사이의 라우팅 결정을 담당합니다.
type director struct {
name string
goRestfulContainer *restful.Container // API 요청 처리용
nonGoRestfulMux *mux.PathRecorderMux // 비-API 요청 처리용 (metrics, healthz 등)
}
func (d director) ServeHTTP(w http.ResponseWriter, req *http.Request) {
path := req.URL.Path
// 🔍 등록된 웹서비스들이 이 경로를 처리할 수 있는지 확인
for _, ws := range d.goRestfulContainer.RegisteredWebServices() {
switch {
case ws.RootPath() == "/apis":
// 🎯 /apis 경로 특별 처리
// 정확히 /apis 또는 /apis/인 경우에만 gorestful로 라우팅
// 이는 /apis/* 패턴이 모든 것과 매칭되는 문제를 해결하기 위함
if path == "/apis" || path == "/apis/" {
klog.V(5).Infof("%v: %v %q satisfied by gorestful with webservice %v",
d.name, req.Method, path, ws.RootPath())
// 🚀 gorestful 컨테이너로 디스패치
d.goRestfulContainer.Dispatch(w, req)
return
}
case strings.HasPrefix(path, ws.RootPath()):
// 🎯 경로 프리픽스 매칭
// 정확한 매칭이거나 경계 매칭인지 확인 (부분 매칭 방지)
// 예: /api/v1은 매칭, /api/v123은 매칭 안됨
if len(path) == len(ws.RootPath()) || path[len(ws.RootPath())] == '/' {
klog.V(5).Infof("%v: %v %q satisfied by gorestful with webservice %v",
d.name, req.Method, path, ws.RootPath())
// 🚀 gorestful 컨테이너로 디스패치
d.goRestfulContainer.Dispatch(w, req)
return
}
}
}
// 🎯 매칭되는 웹서비스가 없으면 non-gorestful 핸들러로 라우팅
// 여기서 처리되는 것들:
// - /metrics (Prometheus 메트릭)
// - /healthz (헬스체크)
// - /readyz (준비 상태 체크)
// - /livez (활성 상태 체크)
// - /debug/pprof/* (프로파일링)
// - /version (버전 정보)
klog.V(5).Infof("%v: %v %q satisfied by nonGoRestful", d.name, req.Method, path)
d.nonGoRestfulMux.ServeHTTP(w, req)
}
💡 라우팅 결정 로직
요청이 들어왔을 때 라우팅 결정 과정:
HTTP 요청이 들어오면:
1. 경로가 "/apis" 또는 "/apis/"인가?
└─ YES: gorestful로 라우팅 (API Discovery)
2. 경로가 등록된 웹서비스의 RootPath와 매칭되는가?
(예: /api/v1, /apis/apps/v1, /apis/extensions/v1beta1)
└─ YES: gorestful로 라우팅 (API 요청)
3. 위 조건에 해당하지 않으면:
└─ nonGoRestfulMux로 라우팅 (시스템 엔드포인트)
예시:
- `/api/v1/pods` → gorestful (core API)
- `/apis/apps/v1/deployments` → gorestful (apps API)
- `/metrics` → nonGoRestfulMux (Prometheus)
- `/healthz` → nonGoRestfulMux (헬스체크)
- `/debug/pprof/heap` → nonGoRestfulMux (프로파일링)
🏗️ APIServerHandler 구조
// APIServerHandler는 API 서버에서 사용하는 다양한 http.Handler들을 보유합니다.
type APIServerHandler struct {
// FullHandlerChain은 최종적으로 서비스되는 핸들러입니다.
// 전체 필터 체인을 포함하고 Director를 호출합니다.
FullHandlerChain http.Handler
// 등록된 API들. InstallAPIs가 이를 사용합니다.
GoRestfulContainer *restful.Container
// NonGoRestfulMux는 체인의 마지막 HTTP 핸들러입니다.
// 모든 필터와 API 처리 이후에 실행됩니다.
NonGoRestfulMux *mux.PathRecorderMux
// Director는 fall through와 proxy 케이스를 적절히 처리하기 위해 존재합니다.
Director http.Handler
}
마무리
지금까지 Kubernetes API Server의 내부 구조를 코드 레벨에서 상세히 분석해보았습니다.
🎯 핵심 요약
- 단순한 시작:
main()
함수는 매우 간단하며, 실제 로직은 계층적으로 분리되어 있음 - 서버 체인 아키텍처: Aggregator → KubeAPIServer → APIExtensions 순으로 위임되는 구조
- 필터 체인 패턴: 인증, 인가, 감사 등의 횡단 관심사를 깔끔하게 처리
- 라우팅 시스템: Director 패턴을 통해 API 요청과 시스템 엔드포인트를 효율적으로 분리
- 보안 중심 설계: 각 단계에서 철저한 검증과 로깅을 수행
🚀 다음 단계
이 분석을 바탕으로 다음과 같은 주제들을 더 깊이 탐구할 수 있습니다:
- 스토리지 레이어: etcd와의 상호작용, 캐싱 메커니즘
- Admission Control: 플러그인 체인과 웹훅 시스템
- Watch 메커니즘: 실시간 이벤트 스트리밍 구현
- API Machinery: 스키마 검증과 버전 변환
'k8s' 카테고리의 다른 글
DefaultBuildHandlerChain 깊게 파헤치기 (2) | 2025.07.10 |
---|---|
Kubernetes 모니터링 (0) | 2025.06.22 |
쿠버네티스 파드를 사용하는 주요 오브젝트들 (2) | 2025.06.15 |
커스텀 리소스와 컨트롤러 - CR & CRD (1) | 2025.06.08 |
Kubernetes Admission Controller (1) | 2025.06.01 |