1. Service가 필요한 이유
Deployment는 제어 루프를 통해 Pod를 자유롭게 종료하고 새로 생성하며 원하는 개수를 유지한다. 그런데 이 동작에는 한 가지 문제가 따른다.
각 Pod는 자신만의 IP 주소를 가지는데, Pod가 종료되고 새로 생성될 때마다 이 IP 주소가 바뀐다. Pod는 일회용이며 언제든 교체될 수 있는 존재이므로, Pod의 IP를 직접 가리키는 방식은 사용할 수 없다.
예를 들어 프론트엔드 Pod가 백엔드 Pod로 요청을 보내는 상황을 생각해 보자.
- 현재 백엔드 Pod의 IP는
10.1.0.5이다. - 프론트엔드가
10.1.0.5로 요청을 보내도록 설정한다. - 백엔드 Pod가 종료되고, 제어 루프가 새 Pod를 생성한다. 새 IP는
10.1.0.9이다. - 프론트엔드는 여전히
10.1.0.5로 요청을 보내므로 연결에 실패한다.
이 문제를 해결하기 위한 리소스가 Service이다.
2. Service란 무엇인가
Service는 클러스터에서 실행 중인 하나 이상의 Pod 집합에 대해 변하지 않는 안정적인 접근 지점을 제공하는 리소스이다.
비유하자면, 자주 자리를 옮기는 담당자들이 백엔드 Pod라면, Service는 회사의 대표 전화번호에 해당한다. 담당자가 바뀌거나 자리를 옮겨도, 외부에서는 항상 대표 번호로 연락하면 누군가 받아서 처리한다.
Service를 생성하면 다음 두 가지가 제공된다.
- 고정된 가상 IP (ClusterIP): 뒤에 있는 Pod가 종료되고 생성되어도 변하지 않는다.
- 고정된 DNS 이름: 예를 들어
backend와 같은 이름이다. IP보다 다루기 편하므로 일반적으로 이름으로 접근한다.
이제 프론트엔드는 Pod의 IP가 아니라 backend라는 Service 이름으로 요청을 보낸다. 뒤에서 Pod가 몇 번이나 종료되고 생성되든 프론트엔드는 이를 신경 쓸 필요가 없다.
3. Service가 Pod를 찾는 방법
Service는 레이블 셀렉터(label selector)를 사용하여 대상 Pod를 찾는다. 이는 Deployment가 selector.matchLabels로 자신이 관리할 Pod를 식별하는 방식과 동일하다.
Service에 "app: backend 레이블이 붙은 Pod로 요청을 전달하라"고 설정해 두면, Service는 해당 레이블을 가진 Pod들을 자동으로 추적한다. 새 Pod가 생성되면 자동으로 대상 목록에 추가하고, 종료된 Pod는 목록에서 제거한다.
전체 흐름은 다음과 같이 연결된다.
Deployment가 (app: backend 레이블이 붙은) Pod를 생성하고 유지
↓
Service가 (app: backend 레이블이 붙은) Pod를 찾아서
↓
들어온 요청을 해당 Pod들에게 나누어 전달
이를 그림으로 나타내면 다음과 같다.
여기서 요청을 여러 Pod에 나누어 전달하는 것이 로드 밸런싱(load balancing)이다. Service 뒤에 Pod가 세 개 있으면, 들어온 요청을 세 Pod에 고르게 분산하여 특정 Pod에 부하가 집중되는 것을 방지한다.
4. 매니페스트 예시
app: backend 레이블을 가진 Pod 앞에 Service를 배치하는 예시이다.
apiVersion: v1
kind: Service
metadata:
name: backend
spec:
selector:
app: backend
ports:
- port: 80
targetPort: 8080
각 필드의 의미는 다음과 같다.
spec.selector: 요청을 전달할 대상 Pod를 식별하는 기준이다.app: backend레이블을 가진 Pod가 대상이 된다. Deployment의 셀렉터와 동일한 개념이다.spec.ports.port: Service가 요청을 받는 포트이다.spec.ports.targetPort: 실제 Pod의 컨테이너가 요청을 수신하는 포트이다.
이 예시에서 Service는 80번 포트로 요청을 받아 Pod의 8080번 포트로 전달한다. 다른 Pod에서는 http://backend 주소로 요청을 보내면 이 Service에 도달하며, Service가 실행 중인 Pod 하나를 선택하여 요청을 넘긴다.
5. Service의 종류
Service에는 여러 타입이 있으며, 이는 "누가 해당 Service에 접근할 수 있는가"의 차이이다. 대표적인 세 가지는 다음과 같다.
- ClusterIP: 기본값이다. 클러스터 내부에서만 접근할 수 있다. 위의 프론트엔드에서 백엔드로 요청을 보내는 예시가 여기에 해당하며, 외부 인터넷에서는 접근할 수 없다.
- NodePort: 각 노드의 특정 포트를 열어 클러스터 외부에서도 접근할 수 있게 한다. 구성이 단순하지만 실무에서 직접 사용하기에는 투박하다.
- LoadBalancer: 클라우드 제공자(AWS, GCP 등)의 로드 밸런서와 연결하여 외부에 노출한다. 클라우드 환경에서 서비스를 외부에 공개할 때 주로 사용한다.
처음에는 ClusterIP가 기본값이며 내부 통신용이고, 외부에 노출하려면 다른 타입이 필요하다는 정도로 이해하면 충분하다.
정리
Pod는 종료되고 생성될 때마다 IP 주소가 바뀌므로 직접 가리킬 수 없다. Service는 변하지 않는 고정 IP와 DNS 이름을 제공하여 이 문제를 해결한다.
이 글에서 다룬 핵심 내용을 요약하면 다음과 같다.
- 안정적인 접근 지점: Service는 뒤에 있는 Pod가 교체되어도 변하지 않는 고정 IP와 DNS 이름을 제공한다.
- 레이블 셀렉터: Service는 레이블을 기준으로 대상 Pod를 자동으로 추적한다. Pod가 생성되거나 종료되면 대상 목록이 자동으로 갱신된다.
- 로드 밸런싱: Service는 들어온 요청을 실행 중인 여러 Pod에 분산하여 전달한다.
- 타입: ClusterIP는 내부 통신용 기본값이며, 외부 노출에는 NodePort 또는 LoadBalancer를 사용한다.
일반적으로 Deployment로 Pod를 실행하고, 그 앞에 Service를 배치하여 안정적으로 접근하는 구성을 사용한다.