[☸️K8s] 쿠버네티스 리소스와 GVK 체계
리소스와 오브젝트의 차이, GVK(Group/Version/Kind)가 API 확장성과 하위 호환성을 어떻게 보장하는지, 그리고 Namespace-scoped와 Cluster-scoped 리소스 분류를 정리합니다.
“쿠버네티스에서 리소스가 뭐야?” “Pod랑 Deployment는 뭐가 달라?” 처음 들으면 다 같은 말 같지만, 정확히 구분하지 못하면 GVK 체계나 RBAC을 이해할 때 계속 헷갈린다. 1주차 핵심 개념인 리소스·오브젝트·GVK를 한 번 제대로 정리하자!
What this post covers
- 리소스(Resource)와 오브젝트(Object)의 정확한 구분
kubectl api-resources출력의 각 컬럼 의미- GVK(Group/Version/Kind) 체계와 API 확장성
- GVK와 GVR(Group/Version/Resource)의 매핑 관계
- Namespace-scoped vs Cluster-scoped 리소스 분류 기준
- 기능 기준 분류와 학습 로드맵
1. 리소스(Resource)란 무엇인가?
1.1 API Object로서의 리소스
쿠버네티스에서 리소스(Resource)는 쿠버네티스 API가 노출하는 특정 종류의 오브젝트를 표현하는 엔드포인트다.
쿠버네티스 공식 문서는 리소스를 다음과 같이 정의한다.
“A resource is an endpoint in the Kubernetes API that stores a collection of API objects of a certain kind.” — Kubernetes Documentation, API Overview
이것을 구체적으로 풀면, 쿠버네티스 API Server는 HTTP REST API 서버이며, 각 리소스는 그 API의 특정 경로(path)에 대응한다.
1
2
3
GET /api/v1/namespaces/default/pods → Pod 리소스 목록 조회
POST /api/v1/namespaces/default/pods → Pod 오브젝트 생성
GET /apis/apps/v1/namespaces/default/deployments → Deployment 리소스 목록 조회
즉, 리소스라는 개념은 “쿠버네티스 API의 URL 경로 상에 존재하는 엔드포인트”를 의미한다.
kubectl이 실행하는 모든 명령은 결국 이 HTTP 엔드포인트에 대한 요청으로 변환된다.
리소스는 “클러스터 안에 있는 물건”이라기보다, 그 물건을 다루기 위해 API Server가 제공하는 공식 API 경로에 가깝다.
이 구조는 운영체제의 시스템 콜 인터페이스와 동일한 역할을 한다.
OS에서 프로세스는 커널 기능에 직접 접근하지 못하고 반드시 시스템 콜이라는 정의된 인터페이스를 통해서만 접근한다. 쿠버네티스에서도 클러스터의 모든 상태 변경은 API Server라는 단일 인터페이스를 통해서만 가능하다. 어떤 컴포넌트도 etcd를 직접 읽거나 쓰지 않는다.
1.2 리소스(타입)와 오브젝트(인스턴스)의 구분
리소스와 오브젝트는 자주 혼용되지만 정확히 구분해야 한다.
| 개념 | 의미 | 예시 |
|---|---|---|
| 리소스(Resource) | 타입 또는 API 엔드포인트 | Pod, Deployment, Service, /api/v1/pods |
| 오브젝트(Object) | 리소스의 구체적인 인스턴스 | default Namespace의 nginx-pod |
프로그래밍 언어에 비유하면 리소스는 클래스(class) 정의, 오브젝트는 클래스의 인스턴스에 해당한다.
1
2
3
4
리소스(타입) → Pod
오브젝트(인스턴스) → nginx-pod (default 네임스페이스)
→ api-server-pod (kube-system 네임스페이스)
→ worker-pod (production 네임스페이스)
kubectl get pods는 Pod 리소스 엔드포인트에 GET 요청을 보내서 현재 존재하는 Pod 오브젝트 목록을 받아오는 것이다.
kubectl apply -f pod.yaml은 YAML에 정의된 스펙으로 새로운 Pod 오브젝트를 생성하도록 API Server에 요청하는 것이다.
kubectl get pods에서pods는 리소스이고, 출력되는 각 행은 실제 Pod 오브젝트다.
모든 쿠버네티스 오브젝트는 공통 메타데이터 구조를 가진다.
1
2
3
4
5
6
7
8
9
apiVersion: apps/v1 # 어떤 API 그룹의 어떤 버전인지
kind: Deployment # 어떤 리소스 타입인지
metadata:
name: nginx # 오브젝트 식별자
namespace: default # 소속 네임스페이스 (Namespace-scoped 리소스의 경우)
uid: 1a2b3c4d-... # API Server가 부여하는 전역 고유 ID
resourceVersion: "12345" # etcd 내 버전 번호 (낙관적 동시성 제어에 사용)
spec: ... # 사용자가 선언한 목표 상태
status: ... # 시스템이 관찰한 현재 상태
uid와 resourceVersion은 쿠버네티스 내부 동시성 제어에 사용된다.
특히 resourceVersion은 etcd의 revision 번호를 반영하며, Controller가 Watch할 때 “어느 시점 이후의 변경만 받을지”를 지정하는 커서 역할을 한다. 이 필드 덕분에 Watch가 중단됐다가 재연결될 때 누락 없이 이벤트를 이어받을 수 있다.
1.3 kubectl api-resources로 보는 전체 리소스 목록
클러스터에 등록된 모든 리소스 타입은 다음 명령으로 확인할 수 있다.
1
kubectl api-resources
출력 결과는 다음과 같은 형태다.
1
2
3
4
5
6
7
NAME SHORTNAMES APIVERSION NAMESPACED KIND
pods po v1 true Pod
deployments deploy apps/v1 true Deployment
nodes no v1 false Node
persistentvolumes pv v1 false PersistentVolume
namespaces ns v1 false Namespace
clusterroles rbac.authorization.k8s.io/v1 false ClusterRole
각 컬럼이 의미하는 바는 다음과 같다.
| 컬럼 | 의미 |
|---|---|
| NAME | API 엔드포인트 경로에서 사용되는 복수형 이름. kubectl get pods에서 pods가 이것이다. |
| SHORTNAMES | kubectl에서 사용할 수 있는 단축 이름. po, deploy, svc 등이 여기에 해당한다. |
| APIVERSION | 이 리소스가 속한 API 그룹과 버전. v1은 core 그룹, apps/v1은 apps 그룹의 v1 버전이다. |
| NAMESPACED | true이면 Namespace-scoped, false이면 Cluster-scoped 리소스다. |
| KIND | YAML의 kind 필드에 들어가는 값. Pod, Deployment, Node 등 단수형 파스칼케이스 이름이다. |
kubectl api-resources가 중요한 이유는 이것이 단순한 목록 출력이 아니라, API Server에 등록된 리소스 타입의 스키마 정보를 동적으로 조회하는 것이기 때문이다. 쿠버네티스는 CRD(CustomResourceDefinition)를 통해 새로운 리소스 타입을 런타임에 추가할 수 있는데, 새로 등록된 CRD도 이 명령의 결과에 즉시 나타난다. 이 확장 메커니즘은 5주차에서 자세히 다룬다.
2. GVK: Group / Version / Kind 체계
2.1 왜 GVK가 필요한가?
쿠버네티스는 처음부터 지금의 규모를 가진 시스템이 아니었다.
초기에는 Pod, Service, ReplicationController 같은 소수의 리소스만 존재했고, 이것들은 단일 API 경로(/api/v1/)에 모두 담겨 있었다.
시스템이 성장하면서 문제가 생겼다.
리소스 종류가 급격히 늘어났고, 각 리소스의 스펙도 계속 변경해야 했다. 특정 리소스의 스펙을 바꾸면 그 리소스를 사용하는 모든 사용자의 YAML이 즉시 깨질 수 있다. 동시에 쿠버네티스 외부 프로젝트들도 자신만의 리소스 타입을 클러스터에 추가하고 싶어했다.
이 세 가지 문제, 즉 리소스의 분류 관리, 하위 호환성 보장, 외부 확장 지원을 해결하기 위해 설계된 것이 GVK 체계다.
GVK는 “이 YAML이 정확히 어떤 API 타입을 말하는가?”에 대한 쿠버네티스의 답이다.
2.2 Group
API Group은 관련된 리소스들을 논리적으로 묶는 단위다.
쿠버네티스의 API Group은 두 종류로 나뉜다.
| 구분 | API 경로 | YAML 표현 | 예시 |
|---|---|---|---|
| Core Group | /api/v1 | apiVersion: v1 | Pod, Service, ConfigMap |
| Named Group | /apis/<group>/<version> | apiVersion: apps/v1 | Deployment, Job, Ingress |
Core Group (Legacy Group)은 쿠버네티스 초기부터 존재했던 가장 기본적인 리소스들의 그룹이다. 그룹명이 없으며 API 경로는 /api/v1이다. YAML에서는 apiVersion: v1으로 표현한다.
1
2
3
4
5
6
7
8
9
# Core Group 리소스 예시
apiVersion: v1
kind: Pod
apiVersion: v1
kind: Service
apiVersion: v1
kind: ConfigMap
Named Group은 기능 도메인 단위로 명시적인 이름을 가진 그룹이다. API 경로는 /apis/<group>/<version> 형태다.
1
2
3
4
5
6
7
8
9
10
11
12
# Named Group 리소스 예시
apiVersion: apps/v1
kind: Deployment # apps 그룹
apiVersion: batch/v1
kind: Job # batch 그룹
apiVersion: networking.k8s.io/v1
kind: Ingress # networking.k8s.io 그룹
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole # rbac.authorization.k8s.io 그룹
그룹명은 도메인 이름 형태를 따른다. 쿠버네티스 내장 그룹은 *.k8s.io 형태를 사용하고, 외부 프로젝트가 CRD로 추가하는 그룹은 자신의 도메인을 사용한다. 예를 들어 Prometheus Operator는 monitoring.coreos.com, Cert-manager는 cert-manager.io 그룹을 사용한다. 이 네이밍 방식은 DNS 도메인과 동일한 원리로 이름 충돌을 방지한다.
2.3 Version
Version은 동일한 리소스의 스펙이 시간이 지남에 따라 변경될 수 있다는 현실을 수용하는 메커니즘이다.
쿠버네티스의 API 버전은 세 단계의 성숙도를 가진다.
1
v1alpha1 → v1beta1 → v1
| 단계 | 특징 | 사용 권장 |
|---|---|---|
alpha (v1alpha1, …) | 실험적 기능. 기본 비활성화. 다음 릴리스에서 예고 없이 변경/제거 가능 | ❌ 프로덕션 사용 금지 |
beta (v1beta1, …) | 기능 검증 완료. 기본 활성화. GA 전 세부 변경 가능성 있음 | ⚠️ 주의해서 사용 |
GA (v1, v2, …) | 안정화된 버전. 하위 호환성 보장. 스펙 변경 없이 유지 | ✅ 프로덕션 사용 권장 |
하나의 리소스가 동시에 여러 버전으로 제공될 수 있다.
API Server는 내부적으로 특정 저장 버전(storage version)을 사용하고, 클라이언트가 요청한 버전에 맞게 자동으로 변환한다. 이 변환 작업은 API Server 내부의 변환 로직이 처리하며, CRD에서는 필요에 따라 Conversion Webhook을 사용할 수 있다.
덕분에 사용자가 v1beta1으로 작성한 YAML은 내부에서 v1으로 변환되어 저장되고, 다시 v1beta1으로 읽어올 수 있다.
2.4 Kind
Kind는 리소스의 타입 이름이다.
YAML의 kind 필드에 들어가는 값이며 항상 단수형 파스칼케이스(PascalCase)를 사용한다.
Kind는 Group + Version이라는 맥락 안에서만 의미가 있다. 같은 이름의 Kind가 서로 다른 Group에 존재할 수 있기 때문이다. 예를 들어 Ingress는 쿠버네티스 1.18 이전에는 extensions/v1beta1 그룹에 있었고, 이후 networking.k8s.io/v1으로 이동했다. 두 Group의 Ingress는 같은 이름이지만 서로 다른 리소스다.
1
2
3
4
5
6
7
# 이전 (deprecated)
apiVersion: extensions/v1beta1
kind: Ingress
# 현재 (stable)
apiVersion: networking.k8s.io/v1
kind: Ingress
이 때문에 쿠버네티스 내부에서 리소스를 식별할 때는 Kind 단독이 아닌 Group + Version + Kind 세 가지를 조합한 GVK를 완전한 식별자로 사용한다.
kind: Ingress만 보고 같은 리소스라고 판단하면 안 된다.apiVersion까지 함께 봐야 정확한 API 타입을 알 수 있다.
2.5 GVK와 GVR — API 경로와의 매핑
쿠버네티스 API에는 GVK와 구분되는 개념으로 GVR(Group / Version / Resource)이 있다.
| 구분 | 사용 위치 | 마지막 요소 |
|---|---|---|
| GVK | YAML 작성, 코드에서 오브젝트 타입 참조 | Kind |
| GVR | HTTP API 엔드포인트 경로 구성 | 복수형 소문자 Resource |
URL에는 Kind 대신 복수형 소문자 리소스 이름(Resource)이 쓰인다.
1
2
3
4
GVK: Group=apps, Version=v1, Kind=Deployment
GVR: Group=apps, Version=v1, Resource=deployments
→ API 경로: /apis/apps/v1/namespaces/default/deployments
kubectl이 YAML을 받으면 apiVersion과 kind 필드로 GVK를 파악한다.
그다음 API Server에 등록된 스키마 정보를 조회하여 해당 GVK에 대응하는 GVR을 찾고, 최종 HTTP 요청 경로를 결정한다. 이 매핑 정보는 API Server의 Discovery API(/apis)를 통해 제공된다.
2.6 YAML apiVersion / kind와 GVK의 대응
실제 YAML 작성 시 GVK의 세 요소가 어떻게 표현되는지 정리하면 다음과 같다.
1
2
apiVersion: apps/v1 # Group=apps, Version=v1
kind: Deployment # Kind=Deployment
1
2
apiVersion: v1 # Group=core(없음), Version=v1
kind: Pod # Kind=Pod
1
2
apiVersion: batch/v1 # Group=batch, Version=v1
kind: CronJob # Kind=CronJob
Core Group의 경우 그룹명이 없으므로 apiVersion에 버전만 표기한다.
Named Group은 그룹/버전 형태로 표기한다. kind는 항상 GVK의 Kind 값을 그대로 사용한다.
3. 리소스 분류 지도
3.1 두 가지 기준으로 보는 리소스 분류
쿠버네티스 리소스는 두 가지 독립적인 기준으로 분류할 수 있다.
| 기준 | 질문 | 왜 중요한가 |
|---|---|---|
| 스코프(Scope) | 특정 Namespace에 속하는가, 클러스터 전체에 속하는가? | 가시성과 접근 제어 범위를 결정 |
| 기능(Function) | 워크로드, 네트워크, 스토리지 중 어떤 역할인가? | 학습 순서와 운영 관점을 결정 |
두 기준은 독립적이다.
워크로드 리소스 중에도 Namespace-scoped인 것과 Cluster-scoped인 것이 섞여 있다. 분류 기준을 혼동하면 나중에 접근 제어와 RBAC을 이해할 때 혼란이 생기므로 처음부터 구분해서 이해해야 한다.
3.2 스코프 기준 분류: Namespace-scoped vs Cluster-scoped
Namespace-scoped 리소스
Namespace-scoped 리소스는 반드시 하나의 Namespace에 속한다.
오브젝트 생성 시 Namespace를 지정해야 하며, 지정하지 않으면 default Namespace에 생성된다. API 경로에 Namespace가 포함된다.
1
2
/api/v1/namespaces/{namespace}/pods
/apis/apps/v1/namespaces/{namespace}/deployments
주요 Namespace-scoped 리소스는 다음과 같다.
1
2
3
4
5
6
7
8
9
10
워크로드 Pod, ReplicaSet, Deployment, StatefulSet,
DaemonSet, Job, CronJob
네트워크 Service, Ingress, EndpointSlice
스토리지 PersistentVolumeClaim (PVC)
설정·보안 ConfigMap, Secret, ServiceAccount
RBAC Role, RoleBinding
Cluster-scoped 리소스
Cluster-scoped 리소스는 어떤 Namespace에도 속하지 않는다.
클러스터 전체가 스코프이므로 소속 Namespace라는 개념 자체가 없다. API 경로에 Namespace 세그먼트가 없다.
1
2
3
/api/v1/nodes
/api/v1/persistentvolumes
/api/v1/namespaces
주요 Cluster-scoped 리소스는 다음과 같다.
1
2
3
4
5
6
7
8
9
인프라 Node, Namespace 자체
스토리지 PersistentVolume (PV), StorageClass
RBAC ClusterRole, ClusterRoleBinding
런타임 RuntimeClass
네트워크 IngressClass
Namespace 자체가 Cluster-scoped인 이유
Namespace는 Cluster-scoped 리소스다.
이것은 순환 의존 문제를 피하기 위한 설계다. Namespace-scoped 리소스는 Namespace에 속하는데, 만약 Namespace 자신도 어떤 Namespace에 속한다면 그 상위 Namespace는 또 어디에 속해야 하느냐는 무한 재귀 문제가 생긴다.
Namespace가 Cluster-scoped이기 때문에 이 재귀가 클러스터 수준에서 종료된다.
kubectl 동작 차이
스코프 구분은 kubectl 명령의 동작에 직접 영향을 준다.
1
2
3
4
5
6
7
8
9
10
11
12
# default Namespace의 Pod만 조회
kubectl get pods
# 모든 Namespace의 Pod 조회 (-A = --all-namespaces)
kubectl get pods -A
# Namespace-scoped 리소스는 -n으로 Namespace 지정 가능
kubectl get pods -n kube-system
# Cluster-scoped 리소스는 -n 옵션 자체가 의미 없음
kubectl get nodes # 올바른 사용
kubectl get nodes -n default # Namespace 개념이 없으므로 무의미
kubectl get pods가 전체 Pod를 보여주지 않는 이유가 여기 있다.
Pod는 Namespace-scoped 리소스이므로 kubectl은 기본적으로 현재 컨텍스트의 Namespace만 조회한다. -A 플래그는 모든 Namespace에 걸쳐 조회하라는 의미이며, 이때 API 경로가 달라진다.
1
2
3
4
5
# -n default
GET /api/v1/namespaces/default/pods
# -A (--all-namespaces)
GET /api/v1/pods ← Namespace 세그먼트 없는 경로로 전체 조회
5주차 RBAC 연결 포인트
스코프 구분은 5주차 RBAC에서 Role과 ClusterRole의 차이로 직접 이어진다.
Role은 Namespace-scoped 리소스다.
특정 Namespace 안에서만 권한을 부여할 수 있다. ClusterRole은 Cluster-scoped 리소스로, 클러스터 전체 범위의 권한을 정의한다.
1
2
3
Role → Namespace-scoped → 특정 Namespace 내 리소스에 대한 권한
ClusterRole → Cluster-scoped → 클러스터 전체 리소스에 대한 권한
(Node, PV 같은 Cluster-scoped 리소스 포함)
Cluster-scoped 리소스인 Node나 PersistentVolume에 대한 권한은 Role로는 부여할 수 없다.
이 리소스들은 Namespace에 속하지 않기 때문에 Namespace 범위의 Role이 적용되지 않는다. 반드시 ClusterRole을 사용해야 한다. 이 제약이 스코프 구분에서 직접 파생된다.
RBAC을 볼 때는 먼저 대상 리소스가 Namespace-scoped인지 Cluster-scoped인지 확인해야 한다. 이 구분이
Role과ClusterRole선택을 결정한다.
3.3 기능 기준 분류: 학습 로드맵
스코프와 독립적으로, 리소스가 수행하는 역할에 따른 기능 분류는 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
워크로드 리소스
├─ Pod, ReplicaSet, Deployment
├─ StatefulSet, DaemonSet
└─ Job, CronJob
→ 2주차: 컨테이너 실행과 배포 관리
네트워크 리소스
├─ Service (ClusterIP, NodePort, LoadBalancer)
├─ Ingress, IngressClass
└─ EndpointSlice
→ 3주차: 클러스터 내외부 트래픽 라우팅
스토리지 리소스
├─ PersistentVolume (PV)
├─ PersistentVolumeClaim (PVC)
└─ StorageClass
→ 4주차: 컨테이너의 영속적 데이터 관리
설정·보안 리소스
├─ ConfigMap, Secret
├─ ServiceAccount
└─ Role, ClusterRole, RoleBinding, ClusterRoleBinding
→ 5주차: 애플리케이션 설정 주입과 접근 제어
클러스터 리소스
├─ Node, Namespace
└─ RuntimeClass
→ 0주차 아키텍처, 2주차 CRI와 연결
각 기능 범주는 대응하는 인터페이스와 페어링된다.
| 기능 범주 | 대응 인터페이스 | 주차 |
|---|---|---|
| 워크로드 | CRI (Container Runtime Interface) | 2주차 |
| 네트워크 | CNI (Container Network Interface) | 3주차 |
| 스토리지 | CSI (Container Storage Interface) | 4주차 |
| 설정·보안 | 대응 독립 인터페이스 없음 | 5주차 |
설정·보안 리소스가 대응 인터페이스 없이 별도 주차로 분리된 이유는 커리큘럼 특징에서 설명한 것과 같다.
ConfigMap, Secret, RBAC은 독립적인 표준 인터페이스를 갖지 않지만, 실제 워크로드 운영에서 반드시 필요한 구성 요소이므로 독립 주차로 다룬다.
핵심 정리 🎯
- 리소스(Resource)는 API 엔드포인트(타입), 오브젝트(Object)는 그 인스턴스다.
- GVK는 Group + Version + Kind로 리소스를 완전히 식별한다. Kind만으로는 부족하다.
- GVR은 GVK에 대응하는 API 경로를 만든다. Kind 대신 복수형 소문자 Resource를 사용한다.
- Namespace-scoped 리소스는 Namespace에 속하고, Cluster-scoped 리소스는 클러스터 전체에 속한다.
- Namespace 자체는 Cluster-scoped다. (순환 의존 방지)
- Role은 Namespace-scoped 권한만, ClusterRole은 Cluster-scoped 리소스 권한도 줄 수 있다.
Reading flow
- Previous:
[☸️K8s] 쿠버네티스를 공부하는 이유와 아키텍처 한눈에 보기—_posts/k8s/2026-05-15-K8s(1).md - Next:
... - Series:
/series/
