본문으로 건너뛰기
3.4 레이블, 셀렉터, 애너테이션

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!=frontend

set 기반 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 --overwrite

ReplicaSet은 개수가 모자란 것으로 판단해 대체 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/*)에서 출발하면 도구 호환성을 얻는다.