0. Contents
1. Overview
Service 를 적는 사람은 ① 만 한다 — ②③④ 는 각각 다른 노드의 다른 프로그램이 맡고, 그 중 하나가 빠져도 ① 은 API server 에 그대로 남아 있다
04-x 에서 이어지는 것
- 04-1 은 Pod 가 IP 를 얻어 노드를 넘는 길, 04-2 는 바뀌는 Pod 앞에 이름과 가상 IP 를 세우는 길이었다 — 문제 ③
- 가상 IP 하나가 Pod IP 로 바뀌는 길은 노드 커널까지 04-2 3.2 절에 있다
- 5장은 그 위에 놓인 추상화들을 한 줄로 세운다 — StatefulSet, EndpointSlice, Service 의 네 타입, Headless, ExternalName. 어느 것이 선언이고, 누가 그 선언을 실현하는지를 매번 가른다
5장이 묻는 것
- Service 객체가 있다는 사실과 패킷이 Pod 에 닿는다는 사실 사이에 무엇이 있는가
- StatefulSet 이 고정해 주는 것은 무엇이고, 고정해 주지 않는 것은 무엇인가
- Service 의 백엔드 명단은 누가 쓰고, 명단의 한 줄은 어떤 상태를 거치는가
- 같은 Service API 를 iptables · nftables · eBPF 가 나를 때, 지금 클러스터는 어느 것인지 어떻게 아는가
2. Three Layers — 선언 · 컨트롤러 · 데이터패스
2.1 요청서와 처리 주체
- Service, Ingress, StatefulSet 같은 API 객체는 원하는 상태를 적은 요청서다 — API server 에 저장되는 데이터이고, 그 자체로는 아무 패킷도 움직이지 않는다
- 요청서를 읽고 다른 객체를 쓰는 것이 컨트롤러다 — selector 를 보고 EndpointSlice 를 쓰고, 서수 이름으로 Pod 를 만들고, 외부 IP 를 배정해 status 에 적는다
- 패킷을 실제로 나르는 것이 데이터패스다 — 노드마다 규칙을 쓰는 kube-proxy 와 그 규칙대로 바꾸는 커널, 또는 Cilium 의 eBPF 프로그램
- 세 역할이 한 프로그램에 들어 있지 않다 — 그래서 어느 하나가 없어도 요청서는 그대로 남아 있고, 증상은 그 층의 모양으로 나타난다
선언 하나에 구현이 둘 붙는다 — 주소를 주는 쪽(②③)과 패킷을 나르는 쪽(④)은 다른 Pod · 다른 프로그램이고, 둘 중 무엇이 빠졌는지는 status 와 노드 규칙을 따로 본다
type: LoadBalancer를 적었다고 로드밸런서가 설치되는 것은 아니다 — 클라우드에서는 공급자 컨트롤러가, 직접 만든 클러스터에서는 MetalLB 나 Cilium 의 LB IPAM 이 ② 를 맡는다. 어느 것도 없으면EXTERNAL-IP는<pending>에 머문다<pending>은 Service API 의 고장이 아니다 — ClusterIP 는 그 순간에도 할당되어 안에서는 통한다. 외부 IP 를 배정할 컨트롤러와 주소 풀이 있는지를 본다- CNI 와 CSI 는 Service 나 PVC 같은 리소스가 아니라 플러그인을 끼우는 규격이다 — Cilium 과 Calico 가 CNI 를, 클라우드 디스크 드라이버가 CSI 를 구현한다. Cilium 은 CNI 에 더해 Service 데이터패스와 LoadBalancer IP 배정까지 맡을 수 있다
2.2 객체별 처리 주체
| 선언 (API 객체) | 사용자에게 주는 것 | 실제로 동작시키는 주체 |
|---|---|---|
| Pod · StatefulSet | 프로세스, 구성원의 안정된 이름과 서수 | kubelet 과 워크로드 컨트롤러 |
| Service | 안정된 이름 · 가상 IP · 포트 · 전달 정책 | EndpointSlice 컨트롤러 + 노드의 Service 데이터패스 |
| EndpointSlice | 선택된 백엔드의 주소 · 포트 · 준비 상태 | 컨트롤 플레인, 또는 사용자 컨트롤러 |
| NetworkPolicy | 허용 목록 | 정책을 구현하는 CNI — 04-3 3.1 절 |
| Ingress · Gateway API | HTTP(S) 호스트 · 경로 규칙 | 별도의 Ingress Controller · Gateway Controller |
| PVC · StorageClass | 저장 공간 요청서 | 기존 PV, 또는 동적 프로비저너(흔히 CSI 드라이버) |
- 둘째 열은 API 에 있는 것, 셋째 열은 설치돼 있어야 하는 것이다 — 셋째 열이 비어 있으면 둘째 열은 적혀 있기만 한다
- ClusterIP 만 쓰는 클러스터 안 통신에는 셋째 열의 외부 구현이 필요하지 않다 — kube-proxy 나 그 대체물이면 된다
2.3 실습 환경과 전제
- 클러스터는 04-2 에서 만든 kind 클러스터 cn4 를 그대로 쓴다 — 노드 셋, Cilium 을 평범한 CNI 로, kube-proxy 는 iptables 모드. 만드는 순서는 04-2 2.2 절
- 명령은 모두
kubectl전체 형태로 쓴다. 컨텍스트는 아래 첫 줄의kubectl config use-context kind-cn4로 한 번 고정하고, 이후 명령에는--context를 다시 적지 않는다 - 네임스페이스는 5장 실습 전용으로
netlab을 새로 만든다 — 04-3 의cn4네임스페이스와 섞이지 않는다 - 외부 LoadBalancer 와 Ingress 는 설치하지 않는다 — 아래의 모든 절은 kube-proxy 와 CoreDNS 만으로 관찰할 수 있다
kubectl config use-context kind-cn4 # 새 터미널이면 다시 고정한다
kubectl config current-context # kind-cn4 인지 확인한다
kubectl get nodes -o wide
kubectl -n kube-system get daemonset kube-proxy # 있으면 kube-proxy 가 Service 를 나른다 (7.2)
kubectl get ingressclass # 없는 것이 정상이다
kubectl create namespace netlab
- 컨텍스트를 매번 확인하는 이유는 운영 클러스터에 실습 리소스를 적용하는 실수를 막기 위해서다 — 새 터미널마다
use-context로 kind-cn4 를 다시 고정한다 - 노드 안의 규칙(
iptables-save,nft)을 읽는 명령은 kind 노드 컨테이너에docker exec로 들어가 조회만 한다 — 운영 노드의 방화벽 규칙은 고치지 않는다
3. StatefulSet — 이름과 순서
3.1 Deployment 와의 차이
Deployment 의 안정된 접점은 Service 하나다. StatefulSet 은 구성원 하나하나에 이름을 준다 — 그래도 주소는 고정하지 않는다
| 비교 항목 | Deployment | StatefulSet |
|---|---|---|
| Pod 정체성 | 교체 때 새 무작위 접미사 | 이름-서수 를 유지 |
| 생성 · 삭제 순서 | 복제본 사이 순서 보장 없음 | 기본 OrderedReady 가 순서를 관리 |
| 영속 저장소 | PVC 를 직접 참조 | volumeClaimTemplates 로 복제본별 PVC |
| 네트워크 이름 | Service 의 대표 이름 | Headless Service 로 복제본별 DNS |
| 대표 용도 | 무상태 웹 API · 워커 | DB 구성원, 합의 노드, 고정 식별자가 필요한 구성원 |
- StatefulSet 을 썼다는 사실만으로 데이터 복제나 장애 조치가 생기지 않는다 — PostgreSQL Pod 둘을 만들어도 둘은 서로를 모른다. 복제 프로토콜, 리더 선출, 백업과 복구는 애플리케이션이나 Operator 의 일이다
- StatefulSet 이 책임지는 범위는 구성원의 정체성과 생명주기다 — 이름, 순서, 그 이름에 붙는 PVC 와 DNS
3.2 생성 · 삭제 · 업데이트의 순서
spec:
podManagementPolicy: OrderedReady # 기본값
updateStrategy:
type: RollingUpdate # 기본값
# partition, minReadySeconds 는 필요할 때 따로 설계한다
OrderedReady는 0 번부터 만들고, 앞 Pod 가 Running 이면서 Ready 가 된 뒤 다음을 만든다 — 삭제와 축소는 역순이다RollingUpdate는 높은 서수부터 낮은 서수로 갱신하며 매번 준비 상태를 확인한다Parallel은 생성 · 삭제의 순서 제약을 푼다 — 이름의 안정성은 그대로다- 잘못된 이미지로 업데이트해 새 Pod 가 Ready 가 되지 않으면 롤아웃이 그 서수에서 멈춘다 — 템플릿을 되돌리는 것만으로 끝나지 않는다. 공식 문서는 되돌린 뒤 잘못된 템플릿으로 생긴 Pod 를 직접 삭제하는 복구 경로를 안내한다. 삭제 전에 저장소와 애플리케이션의 일관성을 확인한다
3.3 Headless Service 와 구성원 DNS
spec.serviceName은 구성원의 네트워크 정체성을 관할하는 Service 를 가리킨다 — 보통clusterIP: None인 Headless Service 다- 그 Service 를 통해 구성원 하나하나가 DNS 이름을 얻는다 —
identity-0.identity-headless.netlab.svc.cluster.local
이름의 앞 토막이 Pod 를, 둘째 토막이 Service 를 가리킨다 — Service 이름만 물으면 목록, Pod 이름까지 붙이면 하나
# 03-identity.yaml — 저장소 없이 이름과 DNS 만 본다
apiVersion: v1
kind: Service
metadata:
name: identity-headless
namespace: netlab
spec:
clusterIP: None
selector:
app: identity-demo
ports:
- name: http
port: 80
targetPort: http
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: identity
namespace: netlab
spec:
serviceName: identity-headless
replicas: 2
selector:
matchLabels:
app: identity-demo
template:
metadata:
labels:
app: identity-demo
spec:
containers:
- name: nginx
image: nginx:stable-alpine
ports:
- name: http
containerPort: 80
readinessProbe:
httpGet: {path: /, port: http}
initialDelaySeconds: 1
periodSeconds: 3
kubectl apply -f 03-identity.yaml
kubectl -n netlab rollout status statefulset/identity
kubectl -n netlab get pod -l app=identity-demo -o wide # identity-0, identity-1 과 각자의 Pod IP
kubectl -n netlab get svc identity-headless # CLUSTER-IP 가 None
kubectl -n netlab get endpointslice -l kubernetes.io/service-name=identity-headless
kubectl -n netlab run dns-check --rm -i --restart=Never --image=busybox:1.36 -- \
nslookup identity-0.identity-headless.netlab.svc.cluster.local # A 레코드 하나
kubectl -n netlab run dns-check --rm -i --restart=Never --image=busybox:1.36 -- \
nslookup identity-headless.netlab.svc.cluster.local # A 레코드 둘
nslookup이 답한 주소를get pod -o wide의 Pod IP 와 맞춰 본다 — 같은 값이면 DNS 가 Pod IP 를 그대로 답한 것이다- Pod 를 하나 지우고 다시 묻는다 —
kubectl -n netlab delete pod identity-0뒤 같은 이름이 새 IP 로 돌아온다. 이름은 안정적이고 IP 는 아니다 - 생성 직후 잠깐 응답이 없을 수 있다 — Ready 가 되기 전이거나 DNS 캐시 TTL 안이다
nginx:stable-alpine은 실습용 이동 태그다 — 재현성이 필요한 배포는 digest 로 고정한다
3.4 저장소와 PVC
- StatefulSet 이 영속 데이터를 가지려면 저장 공간을 요청하는 PVC 와 그것을 채울 PV 가 있어야 한다 — 미리 만든 PV 거나, StorageClass 에 연결된 프로비저너(흔히 CSI 드라이버)가 동적으로 만든 것이다. 둘 다 없으면 PVC 는 Pending 이다 — PV 위키
- PVC 는 요청서, StorageClass 는 어떤 종류를 줄지의 설명, CSI 드라이버는 Ceph 나 EBS 같은 저장소와 통신해 볼륨을 만들고 붙이는 구현이다 — 데이터는 그 저장소에 있고, CSI 자체가 디스크는 아니다
- 모든 볼륨에 CSI 가 필요하지는 않다 —
emptyDir는 kubelet 이 주고, 이미 마운트된 NFS 를 Pod 에 넘기는 설계도 있다 - PVC 가 있다고 데이터가 복제되거나 여러 노드에서 동시에 안전하게 쓰이는 것도 아니다
# volumeClaimTemplates 의 모양 — 03-identity.yaml 에 그대로 붙이는 완성본은 아니다
spec:
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
template:
spec:
containers:
- name: application
volumeMounts:
- name: data
mountPath: /var/lib/application
- 템플릿의
data는 구성원마다data-identity-0,data-identity-1이라는 PVC 가 된다 — Pod 가 교체돼도 같은 서수의 PVC 가 다시 붙는다. 3.1 의 그림이 그 연결이다 - 04-3 의 postgres StatefulSet 은
emptyDir였다 — Pod 가 지워지면 데이터도 사라졌다. 실제 상태 저장 서비스는 StorageClass, 백업과 복구, 파일 시스템과 DB 의 일관성을 함께 설계한다
3.5 준비 상태와 DNS 공개 시점
- Headless Service 가 고른 Pod 의 A/AAAA 레코드는 기본적으로 Ready 인 Pod 에 대해서만 공개된다 — probe 위키
publishNotReadyAddresses: true를 켜면 아직 Ready 가 아닌 구성원도 DNS 에 들어간다 — 서로를 찾아야 비로소 준비가 끝나는 합의 노드 같은 멤버십 부트스트랩에 쓴다- 그 설정은 준비 전 구성원에게 일반 사용자 트래픽을 보내도 된다는 뜻이 아니다 — 멤버십 발견용 이름과 사용자 트래픽용 이름을 분리하는 이유다
- DNS 응답을 오래 붙들고 있는 클라이언트는 교체된 구성원의 옛 IP 로 계속 간다 — 캐시와 재해석 정책을 함께 본다
4. EndpointSlice — 백엔드 명단
4.1 Endpoints 에서 EndpointSlice 로
- Service 의 selector 가 어떤 Pod 가 백엔드 후보인지를 고른다 — 컨트롤 플레인이 그 Pod 들의 IP, 포트, 상태를 EndpointSlice 에 적는다
- 클라이언트는 이름으로 가상 IP 를 얻고, 데이터패스는 준비된 endpoint 중 정책에 맞는 곳으로 보낸다 — 컨트롤러가 객체를 갱신하는 일과 TCP 연결이 처리되는 일은 다른 시점의 다른 일이다
큰 클러스터에서 Pod 하나가 바뀔 때 전 노드가 받아야 하는 양을 줄이는 것이 Slice 의 목적이다 — 읽는 쪽이 여러 Slice 를 합쳐야 하는 비용은 그 대가다
| 구분 | 옛 Endpoints | 현행 EndpointSlice |
|---|---|---|
| API | core/v1 Endpoints | discovery.k8s.io/v1 |
| 분할 단위 | Service 당 큰 객체 하나 | Service 당 여러 Slice |
| 상태 정보 | addresses · notReadyAddresses | conditions.ready · serving · terminating |
| 변경 전달 | 전체 객체를 모든 감시자에 | 바뀐 Slice 하나만 |
| 새 구현의 선택 | 호환성 확인용으로만 | 표준 백엔드 발견과 모니터링 대상 |
| 상태 | v1.33 부터 deprecated | v1.21 부터 Stable |
- 책의
kind: Endpoints,kubectl get endpoints,addresses / notReadyAddresses예제는 구조를 이해하는 역사적 자료다 — 새 연동 대상으로 Endpoints 를 고르지 않는다 - 04-2 에서는 EndpointSlice 를 kube-proxy 가 읽는 입력으로만 봤다 — 04-2 3.2 절. 여기서는 그 명단 자체의 모양과 상태를 본다
4.2 레이블을 바꾸면 일어나는 일
- 책은 Deployment 의 Pod 레이블을
app=app에서app=nope로 바꿔, 그 Pod 가 Service 백엔드에서 빠지고 ReplicaSet 이 새 Pod 를 만드는 것을 보여 준다 - 핵심은 Pod 가 종료돼서가 아니라 selector 와 맞지 않게 돼서라는 점이다 — Service 의 selector 와 ReplicaSet 의 selector 가 각자 그 Pod 를 놓친다. 옛 Pod 는 레이블만 바뀐 채 계속 돈다
kubectl -n netlab get svc web -o jsonpath='{.spec.selector}{"\n"}'
kubectl -n netlab get pods -l app=netdemo --show-labels
kubectl -n netlab get endpointslice -l kubernetes.io/service-name=web -o yaml # endpoints 의 주소와 targetRef
kubectl -n netlab get pods -w # 다른 터미널에서 지켜본다
- 운영 중인 서비스에서 디버깅 목적으로 Pod 레이블을 바꾸면 NetworkPolicy, PDB, 다른 Service 의 selector 까지 영향을 받는다 — 무해한 격리 방법으로 일반화하지 않는다
- 보통은 readiness 를 떨어뜨리거나, 별도 진단 복제본을 쓰거나, 트래픽 배제 정책을 먼저 검토한다 — 레이블 변경은 실습 클러스터에서만 한다
4.3 EndpointSlice YAML 읽기
# 구조 설명용 — 적용하는 실습이 아니다. 10.0.0.10 은 설명용 주소다
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: example-manual-1
namespace: netlab
labels:
kubernetes.io/service-name: selectorless-demo # 어느 Service 의 명단인가
endpointslice.kubernetes.io/managed-by: manual-lab # 누가 관리하나
addressType: IPv4
ports:
- name: http
protocol: TCP
port: 8080
endpoints:
- addresses: ["10.0.0.10"]
conditions:
ready: true
serving: true
terminating: false
nodeName: cn4-worker # 위치 정보 — 분산을 보장하는 값은 아니다
zone: zone-a
addressType은 IPv4 와 IPv6 중 하나다 — 이중 스택 Service 는 계열마다 Slice 가 따로 생긴다. 04-2 6.2 절targetRef로 그 주소가 어느 Pod 인지 거슬러 갈 수 있다 —nodeName과zone은 6.3 의 topology 선호에 쓰이는 입력이다- selector 가 있는 Service 에 사용자가 만든 Slice 를 섞으면 컨트롤러와 관리 범위가 충돌한다 — 수동 Slice 는 selector 없는 Service 에만 붙인다. 8.4 끝의 고정 IP 사례가 그 용도다
- 책의
apiVersion: discovery.k8s.io/v1beta1과Topology:출력은 옛 스키마다 — 지금 클러스터의 모양은kubectl explain으로 본다
kubectl api-resources | grep -i endpointslice
kubectl explain endpointslice.endpoints.conditions
kubectl -n netlab get endpointslices -l kubernetes.io/service-name=web -o wide
4.4 ready · serving · terminating
세 조건은 셋이 한 묶음이다 — ready 하나만 보면 '아직 답하는 종료 중 Pod' 와 '아직 준비 안 된 새 Pod' 가 같아 보인다
| 조건 | 뜻 | 읽을 때의 주의 |
|---|---|---|
ready | 지금 트래픽 대상으로 삼아도 좋은가 | publishNotReadyAddresses 가 켜진 Service 에서는 뜻이 달라진다 |
serving | 종료 여부와 별개로 요청에 답할 수 있는가 | 드레이닝 처리가 참고하는 조건 |
terminating | 종료 과정에 들어갔는가 | 준비 상태와 독립적으로 본다 |
nodeName · zone | 위치 | 균등 분산을 보장하는 값이 아니다 |
- 프록시는 보통 종료 중인 endpoint 를 새 연결에서 제외한다 — 모든 endpoint 가 종료 중이면 아직
serving인 곳으로 보낼 수 있다. 롤링 업데이트 중 전부가 동시에 바뀌는 순간을 위한 규칙이다 ready: false는 삭제가 아니다 — 주소는 명단에 남고 조건만 바뀐다. 04-2 에서 본 “주소는 남기고 ready 만 바꾼다” 가 이 조건들이다
5. Service — 네 타입과 포트
5.1 네 타입과 Headless 의 자리
- Service 는 특정 Pod 대신 서는 안정된 접점이다 — selector 가 있으면 같은 네임스페이스에서 레이블이 맞는 Pod 를 골라 EndpointSlice 를 유지한다. selector 가 없으면 명단은 다른 컨트롤러나 사람이 쓴다 — Service 위키
spec.type의 공식 값은 넷이다 — ClusterIP, NodePort, LoadBalancer, ExternalName. Headless 는 다섯 번째 type 이 아니라clusterIP: None이라는 ClusterIP 의 특수한 설정이다
| spec.type | 진입점 | 발견과 전달 | 주로 쓰는 곳 | 절 |
|---|---|---|---|---|
| ClusterIP (기본) | 클러스터 안 가상 IP · DNS | 준비된 백엔드에 L4 전달 | 내부 API, DB 앞 | 5 |
| NodePort | 모든 노드 IP 의 같은 포트 | ClusterIP 위에 노드 포트를 더함 | 노드 직접 접근, 외부 LB 의 하위 수단 | 6 |
| LoadBalancer | 공급자가 준 외부 주소 | 공급자별 L4 LB + Service 데이터패스 | TCP/UDP 서비스의 외부 공개 | — |
| ExternalName | 안의 이름 → 밖의 이름 CNAME | 프록시 없는 DNS 별칭 | 외부 의존성의 이름 추상화 | 8 |
Headless (clusterIP: None) | 개별 Pod 의 DNS | 가상 IP 와 프록시 없음 | StatefulSet, 클라이언트 쪽 선택 | 3 · 8 |
- 위 셋(ClusterIP · NodePort · LoadBalancer)은 쌓인다 — NodePort 는 ClusterIP 를 포함하고, LoadBalancer 는 NodePort 와 ClusterIP 를 포함한다. 바깥 문을 하나 더 열 때마다 안쪽 문은 그대로 남는다
- 아래 둘(ExternalName · Headless)은 가상 IP 가 없다 — 8절에서 한 자리에 모아 본다
5.2 port · targetPort · containerPort
세 칸 중 둘(port · targetPort)은 Service 의 것, 하나(containerPort)는 Pod 의 메모다 — 실제로 문을 여는 것은 셋 중 어느 칸도 아닌 앱이다
apiVersion: v1
kind: Service
metadata:
name: web
namespace: netlab
spec:
# type 을 생략하면 ClusterIP
selector:
app: netdemo
ports:
- name: http
port: 80 # 클라이언트가 Service 에 접속하는 포트
targetPort: http # Pod 가 듣는 포트 — 숫자이거나, Pod ports 의 name
protocol: TCP
containerPort는 Pod spec 의 메모다 — 선언했다고 프로세스가 그 포트를 열지 않고, 선언하지 않았다고 닫히지도 않는다. 04-1 3.3 절- 숫자형
targetPort는containerPort와 일치해야 하는 것이 아니다 — 앱이 실제로 그 포트를 들으면 닿는다 - 이름형
targetPort는 Pod ports 의name과 맞아야 한다 — 이미지가 듣는 포트가 바뀌어도 Pod 쪽 숫자만 고치고 Service 는 그대로 둘 수 있는 것이 이름형의 장점이다
5.3 실습 웹 Deployment
# 06-web.yaml — 6절부터 공통으로 쓴다
apiVersion: apps/v1
kind: Deployment
metadata:
name: netdemo
namespace: netlab
spec:
replicas: 3
selector:
matchLabels:
app: netdemo
template:
metadata:
labels:
app: netdemo
spec:
containers:
- name: web
image: nginx:stable-alpine
ports:
- name: http
containerPort: 80
readinessProbe:
httpGet: {path: /, port: http}
initialDelaySeconds: 1
periodSeconds: 3
---
apiVersion: v1
kind: Service
metadata:
name: web
namespace: netlab
spec:
selector:
app: netdemo
ports:
- name: http
port: 80
targetPort: http
kubectl apply -f 06-web.yaml
kubectl -n netlab rollout status deployment/netdemo
kubectl -n netlab get deployments,pods,services -o wide
kubectl -n netlab get endpointslices -l kubernetes.io/service-name=web # 주소 셋, 전부 ready
- 책의 Go 웹 서버 대신 nginx 를 쓴다 — 응답에 노드와 Pod 이름이 나오지 않는다. 어느 Pod 가 받았는지를 보려면 04-3 의 go-web 이미지와 8080 포트로 바꾼다
- 관찰 대상은 연결 · 백엔드 집합 · DNS · 경로다 — 응답 내용은 보지 않는다
5.4 ClusterIP 와 Service CIDR
spec.type을 생략하면 ClusterIP 다 — Service CIDR 안에서 가상 IP 하나가 할당되고, Service 를 지울 때까지 바뀌지 않는다- 그 주소는 어느 인터페이스에도 붙어 있지 않다 — 노드 커널이 목적지를 읽어 백엔드로 고쳐 쓰는 논리적 진입점이다. 그 과정은 04-2 3.3 절
- Pod CIDR 과 Service CIDR 은 다른 자원에서 관리된다 — Pod CIDR 은 Pod 네트워크 구현의 주소 공간, Service CIDR 은 kube-apiserver 의
--service-cluster-ip-range다. 둘은 겹치면 안 된다 spec.clusterIP로 주소를 직접 지정할 수 있다 — 범위 안이어야 하고 중복이 없어야 한다. 특별한 이유가 없으면 자동 할당에 맡긴다
kubectl -n netlab get svc web -o jsonpath='{.spec.clusterIP}{"\n"}'
kubectl -n netlab run webcheck --rm -i --restart=Never --image=busybox:1.36 -- \
wget -qO- http://web.netlab.svc.cluster.local/ # nginx 의 HTML 이 나오면 끝까지 닿은 것이다
- 실패하면 순서대로 가른다 — 이름이 풀리는가, EndpointSlice 에 ready 주소가 있는가, Pod IP 로 직접 되는가. 04-2 7.1 절
5.5 Service DNS 이름
- 이름은
서비스.네임스페이스.svc.클러스터도메인이다 —cluster.local은 흔한 기본값이지 고정값이 아니다 - 같은 네임스페이스에서는
web만 적어도 search 목록이 채워 준다 — 다른 네임스페이스에서는web.netlab처럼 보충하는 쪽이 안전하다 - 짧은 이름이 search 목록과 ndots 때문에 여러 번 질의되는 일, resolver 가 이름을 채우고 CoreDNS 는 답만 한다는 것은 04-2 4.5 절에 있다
- CoreDNS 는 이름을 주소로 바꿀 뿐이다 — 그 주소로 가는 TCP 연결을 백엔드로 옮기는 것은 노드의 데이터패스다
kubectl -n netlab run dns-check --rm -i --restart=Never --image=busybox:1.36 -- \
nslookup web.netlab.svc.cluster.local
kubectl -n kube-system get svc kube-dns -o wide # CoreDNS 앞의 Service — 그 자체도 ClusterIP 다
5.6 L4 연결과 HTTP 요청
- Service 의 전달은 연결 단위다 — 새 연결이 생길 때 백엔드 하나를 고르고, 그 연결의 뒤 패킷은 conntrack 이 같은 곳으로 보낸다. 04-2 3.6 절
- Service 는 URL 경로나 Host 헤더를 보지 않는다 —
/api와/metrics를 다른 백엔드로 나누는 일은 앱이나 별도 L7 프록시(Ingress Controller, Gateway, 메시)의 몫이다 - 책은 큰 클러스터에 iptables 대신 IPVS 를 권한다 — kube-proxy 의 IPVS 모드는 v1.35 부터 deprecated 다. 새 클러스터의 선택지는 nftables 모드나 요건을 충족하는 eBPF 데이터패스다. 이미 IPVS 인 클러스터를 당장 바꾸라는 뜻은 아니고, 기능 차이와 롤백을 확인한 전환 계획이 필요하다는 뜻이다
6. NodePort — 노드로 들어오는 문
6.1 노드 IP 와 공통 포트
- NodePort Service 는 모든 노드의 IP 에 같은 포트 번호를 연다 — 어느 노드 IP 로 들어와도 그 Service 의 진입점이다
- 그 노드에 백엔드 Pod 가 없어도 기본 정책에서는 다른 노드의 Pod 로 전달된다 — “NodePort 는 Pod 가 있는 노드에서만 된다” 는 생각이 틀리는 이유다
- 기본 포트 범위는 30000–32767 이고, 바꾸는 플래그
--service-node-port-range는 kube-apiserver 의 것이다 — 책에 적힌 kube-controller-manager 의 옵션이 아니다 nodePort를 생략하면 빈 포트가 배정된다 — 직접 지정한 포트가 이미 쓰이거나 범위 밖이면 생성 요청이 실패한다
# 07-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: web-nodeport
namespace: netlab
spec:
type: NodePort
selector:
app: netdemo
ports:
- name: http
protocol: TCP
port: 80
targetPort: http
nodePort: 30080 # 실습값 — 충돌하면 이 줄을 지우고 자동 배정에 맡긴다
kubectl apply -f 07-nodeport.yaml
kubectl -n netlab get svc web-nodeport -o wide # PORT(S) 가 80:30080/TCP, CLUSTER-IP 도 함께 있다
kubectl get nodes -o wide # INTERNAL-IP 열이 노드 주소다
- kind 의 노드는 컨테이너라 노드의 INTERNAL-IP(172.18.0.x)가 호스트에서 바로 라우팅되지 않을 수 있다 — macOS 와 Windows 의 Docker Desktop 이 그렇다.
localhost:30080이 열렸다고 가정하지 않는다 - 노드 안에서 시험하면 확실하다 —
docker exec cn4-worker curl -s http://172.18.0.3:30080/처럼 노드 컨테이너 안에서 다른 노드의 IP 로 부른다. 노드 안에 curl 이 없으면wget -qO-를 쓴다
6.2 externalTrafficPolicy Cluster 와 Local
같은 30080 이 모든 노드에 열린다 — 어느 노드로 들어왔을 때 어디까지 보내 주는가를 정책이 정한다
| 항목 | Cluster (기본) | Local |
|---|---|---|
| 후보 endpoint | 클러스터 전체 | 들어온 노드의 로컬 endpoint 만 |
| 클라이언트 IP | 경로와 구현에 따라 노드 IP 로 바뀜(SNAT) | 보존에 유리 — 외부 LB 의 동작도 함께 확인 |
| 추가 홉 | 생길 수 있음 | Service 전달에서 원격 노드 홉 없음 |
| 로컬 endpoint 가 없는 노드 | 다른 노드로 보냄 | 그 노드로 들어온 요청은 실패 |
| 주의 | 계층별 NAT, 장기 연결의 편향 | 노드별 트래픽 불균등, LB 헬스체크 |
spec:
type: NodePort
externalTrafficPolicy: Local
- Local 을 고르는 이유는 둘이다 — 앱이 클라이언트 IP 를 봐야 하거나, 노드를 한 번 더 건너는 비용을 피하고 싶을 때
- 그 대신 외부 LB 가 Pod 있는 노드만 고르게 헬스체크를 맞춰야 한다 — 그렇지 않으면 Pod 없는 노드로 간 요청이 조용히 버려진다
- 전통적인 iptables 환경에서는
노드IP:NodePort → Service 규칙 → PodIP:targetPort로 바뀌고, Pod 가 다른 노드면 CNI 의 노드 간 경로를 탄다 — nftables 와 Cilium eBPF 는 내부 처리가 다르지만 API 수준의 뜻은 같다
6.3 internalTrafficPolicy 와 trafficDistribution
| 필드 | 값 | 성격 | 로컬에 없으면 |
|---|---|---|---|
externalTrafficPolicy | Cluster · Local | 바깥에서 들어온 트래픽의 후보 | Local: 실패 |
internalTrafficPolicy | Cluster · Local | 안에서 보낸 트래픽의 후보 | Local: 실패 |
trafficDistribution | PreferSameZone · PreferSameNode | 가까운 곳을 선호 | 다른 곳으로 간다 |
spec:
internalTrafficPolicy: Cluster
trafficDistribution: PreferSameZone
- 두 정책(Policy)은 엄격하다 — Local 이면 그 노드에 endpoint 가 없을 때 연결이 실패한다
- 분배(Distribution)는 선호다 — 같은 zone 이나 같은 노드에 endpoint 가 있으면 그쪽을 고르고, 없으면 다른 곳으로 간다.
PreferClose는PreferSameZone의 deprecated 별칭이다 - 둘이 같이 있으면 엄격한 정책이 선호를 이긴다
- 선호가 가능한 이유가 4.3 의
nodeName과zone이다 — 명단에 위치가 적혀 있어 데이터패스가 가까운 것을 고를 수 있다
6.4 장기 연결과 sessionAffinity
- Service 의 분산은 연결 단위다 — HTTP keep-alive 와 gRPC 의 HTTP/2 는 연결 하나에 많은 요청을 싣는다. Pod 셋이 있어도 요청은 한 Pod 에 몰릴 수 있다
sessionAffinity: ClientIP는 같은 클라이언트 IP 의 연결을 일정 시간 같은 백엔드에 붙이는 설정이다 — 요청별 균등 분배가 아니라 그 반대 방향의 설정이다- 요청 단위로 나누려면 자리가 다르다 — gRPC 채널의 로드밸런싱 정책과 Headless DNS(8.3), 또는 서비스 메시의 요청 수준 분산이다. 어느 쪽도 Kubernetes 가 아니라 클라이언트 라이브러리나 프록시의 설정이다
- 100 연결이 넷에 어떻게 나뉘는지의 셈과 conntrack 의 역할은 04-2 3.5 절에 있다
6.5 NodePort 의 자리
- 노드 IP 가 바뀌거나 노드가 늘면 클라이언트나 외부 LB 가 노드 목록을 따라가야 한다 — 노드 주소는 Service 가 숨겨 주는 대상이 아니다
- 방화벽에서 NodePort 범위를 열어 두는 범위도 함께 관리한다
- Pod 사이 통신에 NodePort 는 필요하지 않다 — 안에서는 ClusterIP, 밖으로는 LoadBalancer 나 Gateway · Ingress 가 기본 선택이다
- 노드 주소를 직접 쓰는 방식은 시험, 특수 환경, 외부 LB 의 하위 수단으로 이해한다
7. Datapath — 구현을 판별하는 법
7.1 같은 API 다른 구현
| 구현 | 데이터패스 | 규칙을 읽는 명령 | 상태 |
|---|---|---|---|
| kube-proxy iptables | netfilter nat 규칙 | iptables-save -t nat, conntrack -L | v1.37 기본값 |
| kube-proxy nftables | nftables 규칙 | nft list ruleset | v1.33 Stable, 커널 5.13 이상 |
| kube-proxy ipvs | IPVS 가상 서버 표 | ipvsadm -Ln | v1.35 deprecated |
| Cilium kube-proxy replacement | eBPF 프로그램 · 서비스 맵 | cilium status, cilium service list, Hubble | 별도 전제 조건 |
- kube-proxy 라는 이름 때문에 패킷이 그 프로세스를 지난다고 생각하기 쉽다 — Linux 의 kube-proxy 는 Service 와 EndpointSlice 를 지켜보며 커널 규칙을 설정하고, 패킷은 커널이 나른다. 네 모드의 차이는 04-2 3.4 절
- 모드를 비워 두면 iptables 다 — 문서는 기본값이 nftables 로 바뀔 수 있다고 예고한다. 업그레이드 때 뜻하지 않게 모드가 바뀌지 않도록 설정에 적어 둔다
- nftables 로 옮기기 전에 확인할 것은 NodePort 의 주소 바인딩, localhost 접근, 네트워크 장비와의 호환성이다
7.2 클러스터에서 판별하기
규칙을 읽기 전에 누가 썼는지를 먼저 묻는다 — iptables-save 에 체인이 없다는 사실은 nftables 나 eBPF 환경에서는 아무 증거도 아니다
kubectl -n kube-system get daemonsets # kube-proxy 가 있나, cilium 이 있나
kubectl -n kube-system get configmap kube-proxy -o yaml | grep -A1 '^ mode'
kubectl -n kube-system get pods -l k8s-app=kube-proxy -o wide
kubectl -n kube-system get pods -l k8s-app=cilium -o wide
# Cilium CLI 가 있으면: cilium status | grep KubeProxyReplacement
# kind 노드 안의 규칙 — 조회만 한다
kind get nodes --name cn4
docker exec cn4-worker iptables-save -t nat | grep -c KUBE-SVC # iptables 모드면 체인이 보인다
docker exec cn4-worker nft list ruleset | head # nftables 모드면 여기에 있다
- kube-proxy DaemonSet 이 안 보여도 Service 가 멈췄다고 단정하지 않는다 — 대체 데이터패스가 설치돼 있을 수 있다
KUBE-SVC-*,KUBE-SEP-*체인 이름은 iptables 구현을 이해하는 과거 예제다 — 해시 접미사와 체인 이름을 API 의 계약으로 다루지 않는다. nftables 나 Cilium 환경에서 이 체인이 없는 것은 장애가 아니다- 노드 이미지에
iptables,nft,ip가 없으면 별도 진단 컨테이너나 호스트 도구가 필요하다
7.3 Pod 의 네트워크 네임스페이스와 veth
- 책은
ip netns list,ip netns pids,ps로 pause 컨테이너와 웹 서버 프로세스를 맞춰 본다 — 배우려는 것은 Pod 안의 인터페이스와 노드 쪽 veth, 네트워크 네임스페이스의 연결이다. 그 구조는 04-1 3.1 절과 5.3 절에 있다 ip netns list에 모든 Pod 의 네임스페이스가 이름으로 나온다는 보장은 없다 — 런타임과 CNI 가 만든 네임스페이스가/var/run/netns에 바인드 마운트되지 않을 수 있다- pause 컨테이너는 같은 Pod 의 컨테이너들이 네트워크 네임스페이스를 공유하도록 그 네임스페이스를 붙들고 있는 기반이다 — 모든 애플리케이션 프로세스의 Linux 부모 프로세스라는 설명은 틀렸다. 부모 관계와 네임스페이스 공유는 다른 개념이다
POD=$(kubectl -n netlab get pod -l app=netdemo -o jsonpath='{.items[0].metadata.name}')
kubectl -n netlab exec "$POD" -- cat /proc/net/route # nginx 이미지에는 iproute2 가 없어 ip addr 는 실패할 수 있다
# 노드 쪽에서는 crictl inspectp, nsenter 로 네임스페이스에 들어간다
7.4 conntrack · SNAT · NetworkPolicy
- Service 전달의 기본은 목적지를 Pod IP 로 고쳐 쓰는 DNAT 다 — 응답 패킷은 conntrack 의 항목을 보고 반대 방향으로 되돌려 쓴다
- 출발지를 보존할지 노드 IP 로 바꿀지(SNAT)는 6.2 의 정책과 데이터패스 구성이 정한다 — 패킷을 추적할 때는 클라이언트 → 진입 노드 → 데이터패스 → endpoint → 응답의 양방향을 본다
- NetworkPolicy 는 Service 를 기준으로 자동 허용되는 규칙이 아니다 — 정책은 Pod 레이블 기준이고, CNI 가 구현해야 집행된다. 그래서 DNS 가 답하고 EndpointSlice 가 차 있어도 TCP 연결이 실패할 수 있다 — 04-3 3.5 절
- 정책이 적용되는 위치가 NAT 의 앞인지 뒤인지, 메시의 L7 처리가 끼어 있는지는 구현마다 다르다
8. No Virtual IP — Headless 와 ExternalName
8.1 clusterIP: None 의 뜻
Headless 는 다섯 번째 type 이 아니다 — clusterIP: None 한 줄이 '고르는 일' 을 커널에서 클라이언트로 옮긴다
# 11-headless.yaml — 5.3 의 Pod 셋을 다른 방식으로 부른다
apiVersion: v1
kind: Service
metadata:
name: web-headless
namespace: netlab
spec:
clusterIP: None
selector:
app: netdemo
ports:
- name: http
port: 80
targetPort: http
kubectl apply -f 11-headless.yaml
kubectl -n netlab get svc web web-headless -o wide # 하나는 CLUSTER-IP, 하나는 None
kubectl -n netlab run dns-check --rm -i --restart=Never --image=busybox:1.36 -- \
nslookup web-headless.netlab.svc.cluster.local # Pod IP 셋
kubectl -n netlab get endpointslices -l kubernetes.io/service-name=web-headless -o yaml
clusterIP: None이면 가상 IP 가 할당되지 않고 kube-proxy 도 이 Service 의 규칙을 쓰지 않는다 — selector 가 있으면 DNS 가 준비된 endpoint 의 A/AAAA 레코드를 그대로 답한다- EndpointSlice 는 똑같이 생긴다 — 명단은 있고, 명단을 읽어 규칙으로 바꾸는 쪽만 없다
- DNS 응답의 순서는 분산을 보장하지 않는다 — 첫 주소만 쓰는 클라이언트, 오래 캐시하는 클라이언트는 한 Pod 에만 붙는다. 클라이언트 쪽 분산을 원하면 주소를 다시 읽고 재접속하는 전략을 클라이언트에 설계한다
8.2 StatefulSet 의 Headless 와 Deployment 의 Headless
- Headless Service 는 Deployment 에도 붙는다 —
web-headless가 답하는 것은 Pod IP 목록이다. 백엔드 목록의 발견이 목적이다 - StatefulSet 의
identity-0.identity-headless…는 개별 구성원을 이름으로 찾는다 — 안정된 정체성이 목적이다. 같은clusterIP: None이지만 쓰는 이름이 다르다 - 구성원 DNS 가 있어도 누가 쓰기 리더인지는 정해지지 않는다 — primary 와 replica 를 구별해야 하는 DB 는 애플리케이션이나 Operator 가 리더 상태와 failover 를 관리하고, 클라이언트가 올바른 대상에 붙도록 구성한다
8.3 gRPC 와 클라이언트 선택
- 책이 소개한 “클라이언트에서 백엔드 선택” 은 gRPC 에서 지금도 쓰이는 패턴이다 — Headless DNS 로 주소 목록을 받고 채널의 로드밸런싱 정책이 고른다
- DNS 가 여러 A 레코드를 답한다고 요청이 균등해지지는 않는다 — gRPC 의 DNS resolver 가 여러 주소를 발견하는지, 채널에 어떤 정책(
round_robin등)을 붙였는지, 재해석과 실패 시 재시도를 어떻게 하는지에 따라 결과가 갈린다 - gRPC 채널이 무조건 TCP 연결 하나라는 설명도 틀릴 수 있다 — 구현과 resolver 와 정책에 따라 채널 하나가 여러 연결(서브채널)을 관리한다
- Service 의 L4 분배(연결 단위)와 gRPC 의 요청 단위 분배는 다른 층이다 — ClusterIP 로 gRPC 를 부르면 연결 하나가 한 Pod 에 붙고, 그 안의 모든 RPC 가 거기로 간다
8.4 ExternalName 과 CNAME
ExternalName 에 IP 문자열을 적는 것은 별칭이 아니라 오용이다 — 고정 IP 가 필요하면 selector 없는 Service 와 수동 EndpointSlice 를 쓴다
# 12-externalname.yaml
apiVersion: v1
kind: Service
metadata:
name: remote-db
namespace: netlab
spec:
type: ExternalName
externalName: database.example.com # 설명용 예약 도메인 — 실제 DB 가 있다고 가정하지 않는다
kubectl apply -f 12-externalname.yaml
kubectl -n netlab get svc remote-db -o yaml # clusterIP 가 없다
kubectl -n netlab run dns-check --rm -i --restart=Never --image=busybox:1.36 -- \
nslookup remote-db.netlab.svc.cluster.local # canonical name = database.example.com
- ExternalName 은 안의 Service 이름을 밖의 DNS 이름에 매핑한다 — selector 도 ClusterIP 도 없고, CoreDNS 가 CNAME 을 답한다. 패킷을 밖으로 프록시하거나 NAT 하지 않는다
- 쓰는 자리는 기존 시스템을 옮길 때다 — 앱 설정에 외부 DNS 이름을 직접 넣는 대신 안의 이름을 쓰게 하고, 바깥 주소가 바뀌면 Service 한 줄만 고친다
- 별칭이 풀렸다는 것과 앱이 연결됐다는 것은 다른 사실이다 —
database.example.com이 실제로 응답하는지는 DNS 가 보장하지 않는다 externalName에192.0.2.10같은 IP 문자열을 적는 것은 IP 매핑 방법이 아니다 — 고정 IP 를 붙여야 하면 selector 없는 Service 와 수동 EndpointSlice(4.3) 를 쓴다
8.5 Host 헤더와 TLS 이름
- HTTP 는
Host헤더를, HTTPS 는 SNI 와 인증서의 호스트명 일치를 쓴다 — 클라이언트가remote-db.netlab.svc.cluster.local로 불렀다면 두 값이 그 이름으로 나간다 - 외부 서버는
database.example.com을 기대한다 — CNAME 은 이 차이를 고쳐 주지 않는다. 가상 호스팅 서버는 다른 사이트를 답하거나 거부하고, TLS 는 인증서 검증에서 실패한다 - 그래서 ExternalName 을 모든 프로토콜에 투명한 프록시로 생각하지 않는다 — 클라이언트가 Host 와 SNI 를 바깥 이름으로 바꿔 보내도록 설정할 수 있을 때만 깔끔하게 쓸 수 있다
kubectl -n netlab delete -f 12-externalname.yaml -f 11-headless.yaml -f 07-nodeport.yaml
kubectl -n netlab delete -f 06-web.yaml -f 03-identity.yaml
kubectl delete namespace netlab # 실습을 끝낼 때만
9. Discussion
9.1 어느 층이 비었나
<pending> · 빈 Slice · 규칙 없음 · 연결 거부는 각각 다른 층의 증상이다 — 한 층의 증거로 다른 층을 판단하지 않는다
| 증상 | 비어 있는 층 | 먼저 볼 것 |
|---|---|---|
EXTERNAL-IP <pending> | 컨트롤러 — 외부 IP 를 줄 구현이 없다 | LB 컨트롤러 설치 여부, 주소 풀, Service 의 status |
| 이름은 풀리는데 EndpointSlice 가 비었다 | 컨트롤러 — selector 가 아무 Pod 도 못 골랐다 | Service 의 selector 와 Pod 의 레이블, 같은 네임스페이스인가 |
명단은 찼는데 전부 ready: false | 앱 — probe 가 통과하지 못했다 | Pod 의 conditions, readinessProbe 의 경로와 포트 |
| 명단은 ready 인데 가상 IP 로 연결이 안 된다 | 데이터패스 — 규칙을 쓰는 쪽이 없거나 다른 것이다 | kube-proxy DaemonSet, 모드, Cilium 의 replacement |
| Pod IP 로는 되는데 Service 로는 안 된다 | 데이터패스 또는 정책 | 노드 규칙, NetworkPolicy 의 ingress |
| 연결은 되는데 거부된다 | 앱 — 그 포트를 듣는 프로세스가 없다 | targetPort 와 실제 LISTEN 포트 |
| Headless 인데 한 Pod 에만 간다 | 클라이언트 — 첫 주소만 쓰거나 캐시한다 | DNS 응답 전체, 클라이언트의 재해석 정책 |
- 각 줄의 증거는 그 층에서만 유효하다 —
iptables-save에 체인이 없다는 사실은 nftables 환경에서는 아무것도 말하지 않고, EndpointSlice 가 찼다는 사실은 노드에 규칙이 있다는 뜻이 아니다 - 같은 Service 를 두고 접속점은 넷이다 — 가상 IP, Pod IP, 노드 IP:NodePort, 외부 IP. 어느 주소에서 무엇을 시험했는지 적어야 결과를 비교할 수 있다
- 선언에서 앱까지의 네 층은 LoadBalancer, Ingress, Gateway 에도 그대로 적용된다 — 거기서는 컨트롤러 층이 하나 더 두꺼워진다
핵심 메시지
객체는 요청서다
Service 가 있다는 것과 패킷이 간다는 것은 다른 사실이다. 사이에 컨트롤러와 노드의 규칙이 있고, 빠진 층의 모양으로 증상이 난다
이름은 고정, IP 는 아니다
StatefulSet 은 서수 이름과 그 이름에 붙는 PVC·DNS 를 지킨다. Pod IP 는 교체마다 바뀌고, 복제와 failover 는 앱의 몫이다
명단은 쪼개져 있다
EndpointSlice 는 100 개씩 나눠 전달 비용을 줄인다. 한 줄의 상태는 ready·serving·terminating 셋을 함께 읽어야 한다
문을 여는 것은 앱이다
port 는 Service 의 문, targetPort 는 Pod 의 문, containerPort 는 메모다. 연결이 닿는 기준은 프로세스가 실제로 듣는 포트다
Local 은 거래다
클라이언트 IP 와 홉 하나를 얻고, Pod 없는 노드로 온 요청을 잃는다. 선호(trafficDistribution)는 실패하지 않고, 정책(Policy)은 실패한다
누가 썼는지 먼저 묻는다
iptables·nftables·eBPF 는 같은 Service API 를 나른다. 체인 이름은 구현의 흔적이고, 없다는 사실은 다른 구현에서는 증거가 아니다
고르는 자리가 다르다
ClusterIP 는 커널이 고르고, Headless 는 클라이언트가 고른다. ExternalName 은 고르지 않고 이름만 바꿔 준다 — Host 와 SNI 는 따라가지 않는다
10. References
Service 의 네 type, Headless 가 clusterIP: None 이라는 것, port 와 targetPort 의 관계, sessionAffinity, ExternalName 이 CNAME 이라는 것과 그 주의, NodePort 범위와 --service-node-port-range 가 kube-apiserver 플래그라는 것은 Service 문서에 있다.
"For headless Services, a cluster IP is not allocated, kube-proxy does not handle these Services, and there is no load balancing or proxying done by the platform for them."
[해석] "Headless Service 에는 클러스터 IP 가 할당되지 않고, kube-proxy 가 다루지 않으며, 플랫폼이 해 주는 부하 분산이나 프록시가 없다." — 8.1 과 hl51 그림의 근거다. ExternalName 에 IP 를 적지 말라는 것, Host 헤더와 TLS 의 주의도 같은 문서의 ExternalName 절에 있다. 책의 "--service-node-port-range 는 kube-controller-manager 플래그" 는 kube-apiserver 레퍼런스와 어긋난다.
EndpointSlice 의 세 조건, Slice 당 기본 100 개와 최대 1,000 개, 한 endpoint 가 잠시 두 Slice 에 겹칠 수 있다는 것, Endpoints API 가 v1.33 부터 deprecated 라는 것은 EndpointSlice 문서에 있다.
"The ready condition is a condition that maps to a Pod's Ready condition. ... serving is identical to the ready condition, except it does not account for terminating states."
[해석] "ready 는 Pod 의 Ready 조건에 대응한다. serving 은 종료 중인 상태를 따지지 않는다는 점만 빼면 ready 와 같다." — 4.4 와 cd51 그림의 근거다. 책의 apiVersion discovery.k8s.io/v1beta1 과 Topology 필드는 Stable 승격(v1.21) 뒤의 스키마와 다르다.
StatefulSet 의 서수 이름, OrderedReady 와 Parallel, RollingUpdate 가 높은 서수부터 간다는 것, serviceName 과 구성원 DNS 이름의 형식, 잘못된 업데이트 뒤의 수동 삭제 복구는 StatefulSet 문서에 있다.
"Each Pod in a StatefulSet derives its hostname from the name of the StatefulSet and the ordinal of the Pod. The pattern for the constructed hostname is $(statefulset name)-$(ordinal)."
[해석] "StatefulSet 의 각 Pod 는 StatefulSet 이름과 서수로 호스트명을 얻는다 — $(statefulset name)-$(ordinal)." — 3.1 과 hd51 그림의 첫 토막의 근거다. 구성원 DNS 가 Headless Service 를 거쳐 $(podname).$(service).$(namespace).svc.$(cluster domain) 이 된다는 것도 같은 문서에 있다.
kube-proxy 가 데이터 경로에 있지 않다는 것, 모드별 상태(iptables 기본, nftables Stable v1.33 과 커널 5.13 요구, IPVS deprecated v1.35), externalTrafficPolicy 와 internalTrafficPolicy 의 Local 이 실패할 수 있다는 것, trafficDistribution 의 값과 PreferClose 가 별칭이라는 것은 Virtual IPs 문서에 있다.
"If there are no local endpoints, packets sent to the node are dropped, so you can rely on the correct client IP in any packet processing rules you might apply a packet that make it through to the endpoint."
[해석] "로컬 endpoint 가 없으면 그 노드로 온 패킷은 버려진다 — 그래서 endpoint 까지 온 패킷의 클라이언트 IP 는 믿을 수 있다." — 6.2 와 np51 그림의 Local 쪽 근거다. 책의 "대규모에서는 IPVS" 는 deprecated 이후 적용하지 않는다.
Service 와 Pod 의 DNS 이름 형식, Headless Service 가 A/AAAA 레코드를 Pod IP 로 답한다는 것, publishNotReadyAddresses 가 준비 전 Pod 를 DNS 에 넣는다는 것은 DNS 문서에 있다. pause 가 네트워크 네임스페이스를 붙드는 샌드박스라는 것은 CRI 와 Pod 문서에 있다.
"For a headless Service, this resolves to the set of IPs of all of the Pods selected by the Service."
[해석] "Headless Service 의 이름은 그 Service 가 고른 모든 Pod 의 IP 집합으로 풀린다." — 3.3 과 8.1 의 근거다. 책의 "pause 가 모든 앱 프로세스의 부모 프로세스" 는 네임스페이스 공유와 프로세스 부모 관계를 섞은 서술이다.
