selector로 Pod를 묶고, ports로 노출 규칙을 선언하는 것
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp # 이 라벨을 가진 Pod 모두를 이 Service로 묶음
ports:
- protocol: TCP
port: 80 # Service가 노출하는 포트 (클라이언트 접속 대상)
targetPort: 9376 # Pod 컨테이너가 listen 중인 포트
spec.selectorselector가 있으면 쿠버네티스가 매칭되는 Pod로 EndpointSlice를 자동 생성·갱신spec.ports — port vs targetPortport — Service 자체가 노출하는 포트. 클라이언트가 접속하는 포트 (ClusterIP가 listen)targetPort — Pod 컨테이너가 listen 중인 포트port)와 내부 구현(targetPort)을 분리할 수 있음targetPort는 숫자 대신 컨테이너의 포트 이름을 써도 됨 (Pod의 containers.ports.name 참조)protocol과 appProtocolprotocol (기본 TCP) — TCP / UDP / SCTP 중 하나appProtocol (선택) — 애플리케이션 레벨 프로토콜 힌트 (HTTP, HTTPS, gRPC 등)
name이 반드시 있어야 한다 (단일 포트일 땐 생략 가능)
```yaml
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
selector를 지정하지 않으면 쿠버네티스가 EndpointSlice를 자동 생성하지 않음apiVersion: v1 kind: Service metadata: name: my-service spec: ports: - protocol: TCP port: 80 targetPort: 9376 —
apiVersion: discovery.k8s.io/v1 kind: EndpointSlice metadata: name: my-service-1 labels: kubernetes.io/service-name: my-service # 어떤 Service에 붙는지 지정 addressType: IPv4 ports:
kubernetes.io/service-name 라벨이 Service 이름과 일치해야 연결됨apiVersion: discovery.k8s.io/v1selector가 있으면 쿠버네티스가 매칭 Pod로 EndpointSlice를 자동 생성Endpoints 오브젝트(단일 객체에 모든 백엔드 저장)를 대체하기 위해 도입 — 대규모 클러스터에서 확장성을 확보redis-primary → REDIS_PRIMARY_SERVICE_HOST, REDIS_PRIMARY_SERVICE_PORTREDIS_PRIMARY_PORT, REDIS_PRIMARY_PORT_6379_TCP 등/etc/resolv.conf가 CoreDNS를 가리키도록 kubelet이 설정 → Service 이름 조회 가능<service-name>.<namespace>.svc.<cluster-domain>
cluster.local이면 my-service.my-ns.svc.cluster.localmy-service만 써도 됨
search 도메인이 자동 설정되어 있어 my-service → my-service.<my-ns>.svc.cluster.local 순으로 조회my-service.other-ns처럼 네임스페이스까지 명시_<port-name>._<protocol>.<service> 형태의 SRV 레코드 제공
_http._tcp.my-service.my-ns.svc.cluster.localCoreDNS가 없는 클러스터에서는 DNS 디스커버리 자체가 동작하지 않음 — 사실상 모든 표준 클러스터는 CoreDNS를 기본 탑재.
spec.clusterIP: None으로 지정한 Service — VIP(ClusterIP)를 할당하지 않는 ServiceapiVersion: v1
kind: Service
metadata:
name: my-headless
spec:
clusterIP: None # Headless로 만드는 핵심 필드
selector:
app.kubernetes.io/name: MyApp
ports:
- port: 80
targetPort: 9376
selector 유무에 따른 동작
selector 있음 — 매칭 Pod로 EndpointSlice가 자동 생성, 각 Pod IP에 대한 A 레코드 발행selector 없음 — 사용자가 만든 EndpointSlice의 주소들을 그대로 DNS로 노출 (또는 ExternalName CNAME으로 위임)spec.serviceName이 Headless Service를 가리키면, 각 Pod에 대해 안정적인 DNS 이름이 생성됨
<pod-name>.<service-name>.<namespace>.svc.<cluster-domain>web-0.nginx.default.svc.cluster.local, web-1.nginx.default.svc.cluster.localspec.type 필드로 어디서·어떻게 접근할 수 있는지를 결정ClusterIP(기본) / NodePort / LoadBalancer / ExternalNameLoadBalancer → 내부적으로 NodePort + ClusterIP도 함께 가짐NodePort → 내부적으로 ClusterIP도 함께 가짐spec.type을 생략하면 ClusterIP
spec:
type: ClusterIP
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
spec.clusterIP: None으로 두면 위 Headless Service가 됨 (VIP 미할당)nodePort)를 열어 외부에 노출NodeIP:NodePort → ClusterIP:port → Pod:targetPortClusterIP도 함께 부여됨 (내부 호출도 가능)
spec:
type: NodePort
selector:
app: my-app
ports:
- port: 80 # ClusterIP가 노출하는 포트
targetPort: 8080 # Pod 컨테이너 포트
nodePort: 30007 # 모든 Node에서 열리는 포트 (기본 30000-32767)
nodePort를 생략하면 30000-32767 범위에서 자동 할당NodePort + ClusterIP도 함께 만들어짐 — LB가 모든 Node의 NodePort로 분산status.loadBalancer.ingress에 채워짐
spec:
type: LoadBalancer
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
loadBalancerClass — 기본 클라우드 LB 대신 특정 구현체(Class)를 명시적으로 선택loadBalancerSourceRanges — LB로 들어오는 트래픽을 특정 CIDR로만 제한externalTrafficPolicy — Cluster(기본, Node 간 재라우팅 가능) vs Local(요청 받은 Node의 Pod로만, 클라이언트 IP 보존)allocateLoadBalancerNodePorts — false로 두면 NodePort를 만들지 않음 (LB가 직접 Pod IP로 가는 경우 비용 절감)my-service 이름으로 호출하면 외부 도메인으로 리졸브됨
spec:
type: ExternalName
externalName: my.database.example.com # CNAME 대상
Host 헤더를 그대로 보내므로 상대편 서버가 SNI·Host 매칭에 의존하면 깨질 수 있음| 타입 | 외부 노출 | 부여되는 IP | 주 사용처 | 비고 |
|---|---|---|---|---|
ClusterIP |
❌ (내부 전용) | ClusterIP | 마이크로서비스 간 통신 | 기본값 |
NodePort |
⭕ (Node IP:Port) | ClusterIP + Node:Port | 개발·테스트, 자체 LB 앞단 | 운영 단독 사용은 부적합 |
LoadBalancer |
⭕ (외부 LB IP) | ClusterIP + NodePort + LB IP | 클라우드 환경 외부 노출 | L4, 클라우드 LB 자동 프로비저닝 |
ExternalName |
— | 없음 (CNAME) | 외부 서비스 내부 이름화 | DNS만, 트래픽 통과 X |
kube-proxy가 커널 레벨에서 트래픽을 가로채 실제 Pod IP로 DNATkube-proxy가 모든 Service·EndpointSlice를 watchClusterIP:port로 패킷이 나가면 그 규칙이 매칭되어 살아 있는 Pod IP 중 하나로 DNATkube-proxy --proxy-mode=...) — 같은 클러스터에서 노드마다 다를 수도 있음| 모드 | 데이터플레인 | 특징 | 비고 |
|---|---|---|---|
iptables |
netfilter (iptables) | 기본값, 가장 널리 사용 | Service·Pod 수가 늘면 규칙 수 선형 증가 → 동기화·매칭 비용 ↑ |
ipvs |
IPVS (Linux 커널 LB) | 해시 테이블 기반, 대규모 클러스터에 유리 | RR·LC·SH 등 다양한 LB 알고리즘 선택 가능 |
nftables |
nftables | iptables의 후속, GA로 진입 중 | 매칭 속도·확장성에서 iptables보다 개선 |
userspace |
(deprecated) | 초기 모드, 사용자 공간에서 직접 프록시 | 성능 문제로 더 이상 권장되지 않음 |
iptables 기본, 수천~수만 Service 규모면 ipvs 또는 nftables 검토externalTrafficPolicy(외부에서 들어온 트래픽), internalTrafficPolicy(클러스터 내부에서 들어온 트래픽)externalTrafficPolicyNodePort·LoadBalancer 타입에서 의미 있음Cluster (기본값)
Local
internalTrafficPolicyCluster (기본값) — 클러스터 전체 Pod 중에서 분배Local — 호출하는 Pod와 같은 Node의 Pod만 후보. 같은 Node에 Pod가 없으면 호출 실패spec:
type: LoadBalancer
externalTrafficPolicy: Local # 외부에서 들어온 트래픽은 같은 Node Pod로만
internalTrafficPolicy: Cluster # 내부 호출은 클러스터 전체에서 분배
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
sessionAffinity 사용
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
sessionAffinity: ClientIP # 클라이언트 IP 기반 sticky
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800 # 기본 3시간 (10800초)
None (기본) — affinity 없음, 매번 다른 Pod로 갈 수 있음ClientIP — 같은 클라이언트 IP에서 온 요청은 일정 시간 동안 같은 Pod로 라우팅externalTrafficPolicy: Local처럼 Node 단위 라우팅과 결합되면 affinity 보장이 깨질 수 있음