3.4 레이블, 셀렉터, 애너테이션
name은 오브젝트 하나를 가리킨다. 그런데 Kubernetes에서 필요한 질문은 대개 “web 애플리케이션에 속한 모든 Pod"처럼 무리를 향한다. 이 질문에 답하는 장치가 label과 selector다. label은 오브젝트에 붙이는 key-value 꼬리표이고, selector는 그 꼬리표로 무리를 골라내는 질의다. Service가 트래픽을 보낼 Pod를 찾는 것도, Deployment가 자기 Pod를 세는 것도 전부 이 질의로 이루어진다.
label 문법과 권장 키
label key는 선택적 prefix와 name 두 부분으로 나뉜다. prefix는 DNS subdomain 형식으로 253자 이내, name은 63자 이내이며 영숫자로 시작하고 끝나야 하고 중간에 -, _, .을 허용한다. value도 63자 이내로 같은 문자 규칙을 따른다(빈 값 허용). kubernetes.io/와 k8s.io/ prefix는 시스템 컴포넌트용으로 예약되어 있다.
key 이름은 팀마다 제각각이 되기 쉬워서, 공식 문서가 권장 키 묶음을 정의해 두었다. Helm을 비롯한 여러 도구가 이 키를 인식하므로 특별한 이유가 없으면 여기서 출발한다.
| 키 | 뜻 | 예시 값 |
|---|---|---|
app.kubernetes.io/name | 애플리케이션 이름 | postgresql |
app.kubernetes.io/instance | 설치 인스턴스 식별자 | pg-main |
app.kubernetes.io/version | 애플리케이션 버전 | “16.4” |
app.kubernetes.io/component | 아키텍처 안의 구성 요소 | database |
app.kubernetes.io/part-of | 상위 시스템 | billing |
app.kubernetes.io/managed-by | 관리 도구 | helm |
name과 instance의 구분에 주의한다. 같은 postgresql을 한 클러스터에 두 벌 설치하면 name은 둘 다 postgresql이고 instance만 다르다.
두 종류의 selector
equality 기반 selector는 =(같음)과 !=(다름) 두 연산으로 이루어진다. 쉼표로 이으면 AND다.
kubectl get pods -l app=web
kubectl get pods -l app=web,tier!=frontendset 기반 selector는 집합 연산을 쓴다. in, notin, 그리고 key의 존재 여부다.
kubectl get pods -l 'env in (dev,stage)'
kubectl get pods -l 'tier notin (frontend)'
kubectl get pods -l release # release 키가 존재하는 것
kubectl get pods -l '!release' # release 키가 없는 것지원 범위는 리소스마다 다르다. Service의 spec.selector는 단순 map이라 equality 조건의 AND만 표현한다. Deployment, ReplicaSet, Job, DaemonSet은 matchLabels(equality)와 matchExpressions(set 기반)를 함께 지원한다.
selector:
matchLabels:
app: web
matchExpressions:
- key: tier
operator: In
values: [frontend, gateway]한 가지 제약이 실무에서 자주 걸린다. apps/v1의 Deployment는 selector가 생성 후 불변이다. selector를 바꾸고 싶다는 요구는 사실상 새 Deployment를 만들라는 뜻이 된다.
selector가 만드는 느슨한 결합
Service는 자신이 트래픽을 보낼 Pod 목록을 보유하지 않는다. spec.selector와 label이 일치하는 Pod가 그 순간의 대상 전부다. 새 Pod가 그 label을 달고 뜨면 자동으로 편입되고, label이 사라지면 조용히 이탈한다. Deployment도 마찬가지로 selector로 자기 ReplicaSet과 Pod를 셀 뿐, 개별 Pod의 이름을 기억하지 않는다.
flowchart TD
SVC["Service<br/>selector: app=web"] --> P1["Pod A<br/>app=web"]
SVC --> P2["Pod B<br/>app=web"]
DEP["Deployment<br/>selector: app=web"] -.-> P1
DEP -.-> P2
P3["Pod C<br/>app=batch"]
이 결합 방식에서 운영 기법 하나가 나온다. 문제가 의심되는 Pod의 label을 바꾸면 그 Pod는 Service와 ReplicaSet 양쪽에서 동시에 이탈한다.
kubectl label pod web-7c9d6bf8c5-2xkqv app=web-debug --overwriteReplicaSet은 개수가 모자란 것으로 판단해 대체 Pod를 새로 만들고, 트래픽은 정상 Pod로만 흐른다. 분리된 Pod는 트래픽을 받지 않은 채 살아 있으므로 프로세스 상태와 로그를 여유 있게 조사한 뒤 지우면 된다. Deployment가 pod-template-hash label로 ReplicaSet 세대를 구분하는 구조는 Part IV. 워크로드에서 다룬다.
annotation: 식별하지 않는 메타데이터
annotation도 key-value라는 점은 label과 같지만, selector로 조회할 수 없다. 식별에 쓰지 않는 메타데이터를 담는 자리다. 실제로 들어가는 내용은 이런 것들이다.
- 도구가 남기는 상태: kubectl의
last-applied-configuration, ingress controller나 서비스 메시의 동작 옵션 - 빌드와 릴리스 정보: git commit hash, 빌드 시각, 파이프라인 실행 번호
- 사람을 위한 정보: 담당팀 연락 채널, 관련 문서 URL
문자 규칙이 label보다 느슨해서 JSON 덩어리 같은 큰 값도 들어간다. 다만 한 오브젝트의 annotation 총량은 256KiB로 제한된다.
label과 annotation의 구분 기준은 하나다. 이 값으로 오브젝트를 골라낼 일이 있는가. 있으면 label, 없으면 annotation이다. 골라낼 일 없는 정보를 label에 몰아넣으면 63자 제한과 문자 규칙에 부딪히고, 반대로 selector에 쓸 값을 annotation에 두면 어떤 controller도 그 값으로 오브젝트를 찾지 못한다.
정리
- label은 식별용 key-value, annotation은 비식별 메타데이터다. 골라낼 일이 있는 값만 label에 둔다.
- selector에는 equality 기반과 set 기반 두 문법이 있고, Service는 map 형태의 equality만 지원한다.
- Service와 Deployment는 Pod 이름이 아니라 label 질의로 대상을 정한다. label 하나로 Pod를 편입하고 분리하는 운영 기법이 여기서 나온다.
- 키 이름은 권장 키(
app.kubernetes.io/*)에서 출발하면 도구 호환성을 얻는다.