[k8s 스터디] #09 Service
k8s는 컨테이너 기반 구조이기 때문에 파드가 여러 노드에서 생성되고 삭제되며, 그에 따라 파드의 IP도 자주 변경된다.
이처럼 동적으로 변하는 IP를 일일이 추적해서 통신하는 것은 현실적으로 어렵기 때문에,
이를 해결하기 위한 중간 계층의 네트워크 접근 지점이 필요하다.
바로 그 역할을 하는 것이 k8s의 Service 리소스이다.
Service는 파드들의 고정된 접근 지점(가상 IP, DNS 이름)을 제공하고,
내부 로드밸런싱을 통해 파드 간 통신과 외부 노출까지 효과적으로 처리해준다.
이번 포스팅에서는 다음과 같은 질문을 통해 Service의 개념과 동작 원리를 실습을 기반으로 이해하고자 한다
- 왜 Kubernetes에서는 Service 리소스가 꼭 필요한가?
- Service는 어떤 방식으로 파드 집합을 식별하고 트래픽을 전달하는가?
- ClusterIP, NodePort, LoadBalancer는 어떤 차이가 있는가?
- Service는 내부에서 어떻게 호출되고, 외부에서는 어떤 방식으로 접근하는가?
- 파드에서 ports 정의 없이도 targetPort로 트래픽을 수신할 수 있는 이유는 무엇인가?
Service
Pod 집합에 안정적인 네트워크 접근 지점을 제공하는 추상 리소스
- DNS 이름 부여
- 고정 IP 제공
- 내부 클러스터 IP(ClusterIP) 할당
- 부하 부산
- 연결된 여러 Pod에 트래픽 분산 (Round Robin)
- 내부의 파드끼리 통신하기 위한 것 외부에서는 이걸로 못들어옴
- 연결된 여러 Pod에 트래픽 분산 (Round Robin)
- 외부 접근
- 필요 시 외부에서 접근 가능(NodePort, LoadBalancer)
- NodePort의 경우 외부에서 노드들의 퍼블릭 주소로만 요청하면 그걸 Kube-proxy가 알아서 서비스에 맞는 파드로 포워딩 해줌
- LoadBalancer의 경우는 EKS 같이 클라우드 환경에서 사용하는 타입
### ▶ Service
apiVersion: v1 # Kubernetes core API 그룹의 버전 v1 사용
kind: Service # 이 YAML은 Service 리소스를 정의함
metadata:
namespace: anotherclass-123 # 이 서비스가 속한 네임스페이스
name: api-tester-1231 # 서비스 이름 (DNS 이름 등으로 사용됨)
labels: # 서비스 리소스 자체에 대한 메타데이터
part-of: k8s-anotherclass # 프로젝트 그룹 라벨
component: backend-server # 백엔드 역할임을 나타냄
name: api-tester # 서비스 식별용 이름
instance: api-tester-1231 # 서비스 인스턴스 구분값
version: 1.0.0 # 버전 정보
managed-by: dashboard # 이 리소스가 생성된 관리 도구 (예: 웹 대시보드)
spec:
selector: # 이 서비스가 연결할 대상 Pod을 결정하는 라벨 셀렉터
part-of: k8s-anotherclass # 아래의 라벨과 일치하는 Pod을 선택함
component: backend-server
name: api-tester
instance: api-tester-1231
ports:
- port: 80 # 클러스터 내부에서 이 서비스에 접근할 때 사용하는 포트 (가상포트)
targetPort: http # 실제 Pod 내부 컨테이너의 포트 이름 또는 번호 (예: containerPort: 8080)
nodePort: 31231 # 클러스터 외부에서 접근 가능한 고정 포트 (Node의 물리 포트)
type: NodePort # 외부 사용자도 Node IP + nodePort 조합으로 접근 가능하게 함
nodePort 가 31231이니까
외부에선 A노드 ip 1.1.1.1 이라 하면 1.1.1.1:31231로 접근하면 Kube-Proxy가 서비스로 요청 프록시 해줌
만약 클라이언트가 클러스터 내부에 있다면 https://api-tester-1231:80로 접근 가능
실시간 확인 명령어
목적 | 명령어 |
서비스 목록 확인 | kubectl get svc |
서비스 상세 보기 | kubectl describe svc <이름> |
엔드포인트 보기 | kubectl get endpoints <이름> |
연결된 Pod 보기 | kubectl get pods -l app=myapp |
실습
서비스 확인
서비스 상세 보기
엔드포인트 보기
연결된 Pod 보기
▶ 1. Pod 내부에서 Service 명으로 API 호출
// Version API 호출
curl http://api-tester-1231:80/version
kubectl exec -n anotherclass-123 -it api-tester-1231-74465f56d6-dldd5 -- /bin/sh
파드 내부에서는 name으로 요청을 보낼 수 있음
▶ 2. Deployment에서 Pod의 ports 전체 삭제, Service targetPort를 http -> 8080으로 수정
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: anotherclass-123
name: api-tester-1231
spec:
template:
spec:
nodeSelector:
kubernetes.io/hostname: k8s-master
containers:
- name: api-tester-1231
ports: // 삭제
- name: http // 삭제
containerPort: 8080 // 삭제
---
apiVersion: v1
kind: Service
metadata:
namespace: anotherclass-123
name: api-tester-1231
spec:
ports:
- port: 80
targetPort: http -> 8080 // 변경
nodePort: 31231
type: NodePort
2. 그리고 다시 Pod 내부에서 Service 명으로 API 호출
curl http://api-tester-1231:80/version
이런 경우는 서비스가 http로 타켓 포트를 사용했음 Deployment의 name: http를 통해서
근데 이걸 지우면
디플로이먼트의 포트는 none이 됨 하지만 이렇다고 해서 8080 요청을 못받는게 아님
백엔드가 8080을 리슨하고 있으면 그게 받음
그래도 잘 동작함
마무리
질문에 대한 답
왜 Kubernetes에서는 Service 리소스가 꼭 필요한가?
파드는 재시작되거나 노드가 바뀌면 IP가 바뀌므로, 고정된 네트워크 접근 지점이 필요하기 때문이다.
Service는 어떤 방식으로 파드 집합을 식별하고 트래픽을 전달하는가?
Service는 selector로 라벨이 일치하는 파드들을 찾아 연결하고, 내부 로드밸런싱을 통해 트래픽을 분산시킨다.
ClusterIP, NodePort, LoadBalancer는 어떤 차이가 있는가?
ClusterIP는 내부 접근 전용, NodePort는 노드 IP + 포트로 외부 접근 가능, LoadBalancer는 클라우드에서 외부 LB를 생성해 직접 접근을 지원한다.
Service는 내부에서 어떻게 호출되고, 외부에서는 어떤 방식으로 접근하는가?
내부에서는 서비스 이름과 포트로 DNS처럼 호출하고, 외부에서는 Node IP와 nodePort 조합으로 접근한다.
파드에서 ports 정의 없이도 targetPort로 트래픽을 수신할 수 있는 이유는 무엇인가?
컨테이너가 실제 해당 포트를 리슨하고 있기만 하면, Service는 포트 이름 없이도 트래픽을 정상적으로 전달할 수 있기 때문이다.
Service 리소스가 파드의 IP 변경 문제를 해결하는 방식을 알게 되었다.
쿠버네티스는 컨테이너 방식의 장점은 장점대로 활용하고,
단점은 보완하며 정말 편리하게 만들어 진 것 같다고 느꼈다.