본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 11. 29. · 14 Views
Kubernetes Service 타입 이해하기
쿠버네티스에서 파드 간 통신과 외부 노출을 담당하는 Service의 네 가지 타입을 살펴봅니다. ClusterIP부터 LoadBalancer까지, 각 타입의 특징과 실무 활용법을 이북처럼 쉽게 풀어냅니다.
목차
1. Service란 무엇인가
김개발 씨는 쿠버네티스 클러스터에 애플리케이션을 배포했습니다. 파드는 잘 생성되었는데, 다른 파드에서 이 애플리케이션에 접근하려니 막막해졌습니다.
파드의 IP 주소는 언제든 바뀔 수 있다는데, 어떻게 안정적으로 통신할 수 있을까요?
Service는 쿠버네티스에서 파드에 접근하기 위한 안정적인 엔드포인트를 제공하는 추상화 계층입니다. 마치 회사의 대표 전화번호처럼, 담당자가 바뀌어도 같은 번호로 연락할 수 있게 해줍니다.
Service를 이해하면 파드 간 통신과 외부 노출을 자유자재로 다룰 수 있습니다.
다음 코드를 살펴봅시다.
# Service의 기본 구조를 정의하는 YAML
apiVersion: v1
kind: Service
metadata:
name: my-app-service # Service 이름
labels:
app: my-app
spec:
selector:
app: my-app # 이 라벨을 가진 파드들을 선택
ports:
- protocol: TCP
port: 80 # Service가 노출하는 포트
targetPort: 8080 # 파드의 실제 포트
김개발 씨는 입사 6개월 차 백엔드 개발자입니다. 최근 회사에서 쿠버네티스를 도입하기로 했고, 김개발 씨가 마이그레이션을 담당하게 되었습니다.
첫 번째 애플리케이션을 파드로 배포하는 데는 성공했습니다. 그런데 문제가 생겼습니다.
프론트엔드 파드에서 백엔드 파드로 API를 호출해야 하는데, 백엔드 파드의 IP 주소가 계속 바뀌는 것이었습니다. 파드가 재시작되면 새로운 IP가 할당되고, 스케일 아웃하면 또 다른 IP를 가진 파드들이 생겨났습니다.
선배 개발자 박시니어 씨가 김개발 씨에게 다가왔습니다. "파드 IP를 직접 사용하면 안 돼요.
Service를 만들어야 합니다." 그렇다면 Service란 정확히 무엇일까요? 쉽게 비유하자면, Service는 마치 회사의 대표 전화번호와 같습니다.
고객 상담팀에 전화하려면 담당자 개인 번호가 아닌 대표 번호로 전화합니다. 담당자가 퇴사하거나 자리를 비워도, 대표 번호로 전화하면 다른 상담원이 응대해줍니다.
Service도 마찬가지입니다. 특정 파드의 IP가 아닌 Service의 주소로 요청을 보내면, Service가 알아서 적절한 파드로 연결해줍니다.
Service가 없던 시절을 상상해 봅시다. 개발자들은 파드 IP를 직접 관리해야 했습니다.
파드가 재시작될 때마다 새 IP를 확인하고, 이를 호출하는 모든 곳에서 업데이트해야 했습니다. 스케일 아웃으로 파드가 3개, 5개, 10개로 늘어나면 로드밸런싱도 직접 구현해야 했습니다.
프로덕션 환경에서 이런 방식은 재앙을 불러올 수밖에 없었습니다. 바로 이런 문제를 해결하기 위해 Service가 등장했습니다.
Service를 사용하면 안정적인 DNS 이름과 IP 주소를 얻을 수 있습니다. my-app-service라는 이름으로 Service를 만들면, 같은 네임스페이스 내에서는 그냥 my-app-service로 접근할 수 있습니다.
다른 네임스페이스에서는 my-app-service.namespace.svc.cluster.local로 접근합니다. 위의 YAML 코드를 살펴보겠습니다.
selector 필드가 핵심입니다. app: my-app 라벨을 가진 모든 파드가 이 Service의 백엔드가 됩니다.
port: 80은 Service가 외부에 노출하는 포트이고, targetPort: 8080은 실제 파드가 리스닝하는 포트입니다. 클라이언트는 80번 포트로 요청하지만, 실제로는 파드의 8080번 포트로 전달됩니다.
실무에서는 거의 모든 애플리케이션에 Service를 붙입니다. 웹 서버, API 서버, 데이터베이스, 캐시 서버 등 파드로 배포되는 모든 워크로드에 Service가 필요합니다.
Service 없이 쿠버네티스를 운영한다는 것은 상상하기 어렵습니다. 주의할 점도 있습니다.
selector가 잘못 설정되면 Service가 아무 파드도 찾지 못합니다. 라벨이 정확히 일치하는지 꼼꼼히 확인해야 합니다.
kubectl describe service 명령으로 Endpoints가 제대로 연결되었는지 확인하는 습관을 들이세요. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.
"그래서 파드 IP 대신 Service 이름을 사용하는 거였군요!" 이제 김개발 씨의 프론트엔드 파드는 my-app-service라는 이름으로 백엔드에 안정적으로 접근할 수 있게 되었습니다.
실전 팁
💡 - kubectl get endpoints 명령으로 Service에 연결된 파드 IP 목록을 확인할 수 있습니다
- Service와 파드의 라벨이 정확히 일치하는지 항상 검증하세요
2. ClusterIP로 내부 통신
김개발 씨가 Service를 만들었는데, 외부에서 접근이 안 됩니다. 분명히 Service가 잘 생성되었는데 왜 그럴까요?
박시니어 씨가 웃으며 말합니다. "그건 ClusterIP 타입이라 그래요.
클러스터 내부에서만 접근 가능하거든요."
ClusterIP는 Service의 기본 타입으로, 클러스터 내부에서만 접근 가능한 가상 IP를 할당합니다. 마치 사내 내선 전화처럼, 회사 안에서만 사용할 수 있습니다.
마이크로서비스 간 통신에 가장 많이 사용되는 타입입니다.
다음 코드를 살펴봅시다.
# ClusterIP Service 정의 (기본 타입)
apiVersion: v1
kind: Service
metadata:
name: backend-api
spec:
type: ClusterIP # 생략해도 기본값
selector:
app: backend
ports:
- port: 80
targetPort: 3000
---
# 다른 파드에서 접근하는 방법
# http://backend-api:80 또는
# http://backend-api.default.svc.cluster.local:80
김개발 씨는 이제 Service의 개념을 이해했습니다. 그런데 막상 웹 브라우저에서 Service에 접근하려니 연결이 되지 않았습니다.
분명히 kubectl get service 명령을 치면 IP 주소가 보이는데, 왜 접속이 안 되는 걸까요? 박시니어 씨가 화면을 보더니 바로 원인을 짚어냈습니다.
"이건 ClusterIP 타입이에요. 클러스터 밖에서는 접근할 수 없어요." ClusterIP란 무엇일까요?
쉽게 비유하자면 사내 내선 전화와 같습니다. 회사 내부에서는 간단한 내선 번호(예: 1234)로 동료에게 전화할 수 있지만, 회사 밖에서 그 번호로 전화하면 연결되지 않습니다.
ClusterIP도 마찬가지입니다. 쿠버네티스 클러스터 내부의 파드들끼리만 이 주소로 통신할 수 있습니다.
그렇다면 ClusterIP는 왜 필요할까요? 모든 서비스가 외부에 노출될 필요는 없기 때문입니다.
오히려 대부분의 서비스는 외부에 노출되면 안 됩니다. 데이터베이스 서버, 캐시 서버, 내부 API 서버 등은 오직 같은 클러스터 내의 다른 서비스만 접근해야 합니다.
보안상 이것은 매우 중요합니다. 마이크로서비스 아키텍처를 생각해 봅시다.
사용자 서비스, 주문 서비스, 결제 서비스, 알림 서비스가 있다고 가정합니다. 사용자가 직접 접근하는 것은 API 게이트웨이뿐입니다.
나머지 서비스들은 서로 내부적으로만 통신합니다. 이런 경우 API 게이트웨이만 외부에 노출하고, 나머지는 모두 ClusterIP로 설정하는 것이 올바른 구성입니다.
위의 YAML을 보면 type: ClusterIP라고 명시되어 있습니다. 사실 이 줄은 생략해도 됩니다.
Service를 만들 때 타입을 지정하지 않으면 자동으로 ClusterIP가 됩니다. 쿠버네티스가 기본값으로 가장 안전한 옵션을 선택하는 것입니다.
ClusterIP Service가 생성되면 쿠버네티스는 가상 IP 주소를 할당합니다. 이 IP는 10.96.0.1 같은 형태입니다.
하지만 이 IP를 직접 사용하는 경우는 거의 없습니다. 대신 Service 이름을 DNS처럼 사용합니다.
backend-api라는 Service가 있다면, 같은 네임스페이스의 파드에서는 그냥 **http://backend-api**로 접근합니다. 실무에서 ClusterIP를 사용하는 대표적인 사례를 살펴봅시다.
Redis 캐시 서버를 쿠버네티스에 배포했다고 가정합니다. 웹 애플리케이션 파드에서 Redis에 접근해야 합니다.
이때 Redis Service를 ClusterIP로 만들면, 웹 애플리케이션은 redis:6379로 연결할 수 있습니다. 외부에서 Redis에 직접 접근하는 것은 차단되어 보안도 지켜집니다.
주의할 점이 있습니다. 디버깅을 위해 로컬 컴퓨터에서 ClusterIP Service에 접근하고 싶을 때가 있습니다.
이럴 때는 kubectl port-forward 명령을 사용합니다. kubectl port-forward service/backend-api 8080:80 명령을 실행하면, 로컬의 8080 포트가 Service의 80 포트로 연결됩니다.
개발과 테스트 시 매우 유용한 기능입니다. 김개발 씨는 이제 ClusterIP의 역할을 이해했습니다.
"내부 서비스는 ClusterIP, 외부에 노출할 서비스는 다른 타입을 쓰면 되겠군요!" 박시니어 씨가 고개를 끄덕였습니다. "맞아요, 이제 NodePort를 배워볼 차례예요."
실전 팁
💡 - ClusterIP는 가장 보안이 강한 Service 타입입니다. 외부 노출이 필요 없다면 항상 ClusterIP를 사용하세요
- kubectl port-forward로 로컬에서 ClusterIP Service에 접근하여 디버깅할 수 있습니다
3. NodePort로 외부 노출
김개발 씨가 개발 중인 웹 애플리케이션을 외부에서 테스트하고 싶습니다. 아직 로드밸런서를 설정하기는 이른 것 같고, 간단하게 노출할 방법이 없을까요?
박시니어 씨가 NodePort를 소개해 주었습니다.
NodePort는 클러스터의 모든 노드에서 특정 포트를 열어 외부 트래픽을 받아들이는 Service 타입입니다. 마치 아파트 단지의 모든 동 출입구에 같은 호수의 방이 있는 것처럼, 어느 노드로 들어와도 같은 서비스에 도달합니다.
개발 환경이나 간단한 외부 노출에 적합합니다.
다음 코드를 살펴봅시다.
# NodePort Service 정의
apiVersion: v1
kind: Service
metadata:
name: webapp-nodeport
spec:
type: NodePort
selector:
app: webapp
ports:
- port: 80 # ClusterIP에서 사용할 포트
targetPort: 8080 # 파드의 포트
nodePort: 30080 # 노드에서 열릴 포트 (30000-32767)
---
# 접근 방법: http://<노드IP>:30080
김개발 씨의 웹 애플리케이션이 어느 정도 완성되었습니다. 이제 QA 팀에서 테스트할 수 있도록 외부에서 접근 가능하게 만들어야 합니다.
ClusterIP는 내부 전용이라는 것을 이미 배웠으니, 다른 방법이 필요합니다. 박시니어 씨가 제안했습니다.
"일단 NodePort로 열어보죠. 가장 간단한 외부 노출 방법이에요." NodePort란 무엇일까요?
비유하자면 아파트 단지와 같습니다. 101동, 102동, 103동이 있는데, 각 동의 1층에 모두 편의점 출입구가 있습니다.
어느 동으로 들어가든 같은 편의점에 도착합니다. NodePort도 마찬가지입니다.
클러스터에 노드가 3개 있다면, 3개의 노드 모두에서 같은 포트(예: 30080)가 열립니다. 어떤 노드의 IP로 접근하든 같은 서비스에 연결됩니다.
NodePort를 사용하면 30000번부터 32767번 사이의 포트가 모든 노드에서 열립니다. 이 범위는 쿠버네티스가 기본으로 예약한 범위입니다.
직접 포트를 지정할 수도 있고, 생략하면 쿠버네티스가 자동으로 할당합니다. 위의 YAML에서는 nodePort: 30080으로 명시했습니다.
실제로 트래픽이 어떻게 흐르는지 살펴봅시다. 사용자가 **http://192.168.1.10:30080**으로 요청을 보냅니다.
여기서 192.168.1.10은 노드의 IP입니다. 노드의 30080 포트로 들어온 트래픽은 먼저 kube-proxy가 받습니다.
kube-proxy는 이 트래픽을 Service(ClusterIP)로 전달하고, Service는 다시 적절한 파드로 라우팅합니다. 흥미로운 점은 NodePort Service를 만들면 ClusterIP도 함께 생성된다는 것입니다.
NodePort는 ClusterIP의 확장입니다. 따라서 클러스터 내부에서는 여전히 Service 이름으로 접근할 수 있고, 외부에서는 노드IP:노드포트로 접근할 수 있습니다.
두 가지 접근 방식이 모두 가능한 것입니다. 실무에서 NodePort는 주로 개발 환경이나 온프레미스 환경에서 사용됩니다.
클라우드 환경에서는 보통 LoadBalancer 타입을 사용하지만, 로컬 Kubernetes(minikube, kind 등)나 베어메탈 환경에서는 NodePort가 가장 쉬운 외부 노출 방법입니다. 하지만 프로덕션에서 NodePort를 직접 사용하는 것은 권장되지 않습니다.
몇 가지 단점이 있기 때문입니다. 첫째, 포트 번호가 30000 이상이라 일반적인 웹 서비스 포트(80, 443)를 사용할 수 없습니다.
둘째, 클라이언트가 노드 IP를 직접 알아야 합니다. 노드가 추가되거나 제거되면 클라이언트 설정도 바꿔야 할 수 있습니다.
셋째, 로드밸런싱이 노드 레벨에서 이루어지지 않습니다. 보안 관점에서도 주의가 필요합니다.
NodePort가 열리면 방화벽에서 해당 포트 범위를 허용해야 합니다. 불필요한 노출을 막기 위해 NodePort 사용을 최소화하고, 가능하다면 Ingress나 LoadBalancer를 활용하는 것이 좋습니다.
김개발 씨는 NodePort로 웹 애플리케이션을 열었습니다. QA 팀에게 노드 IP와 포트 번호를 알려주니 바로 테스트를 시작할 수 있었습니다.
"개발 단계에서는 충분하네요. 프로덕션 배포 전에는 더 좋은 방법을 찾아봐야겠어요."
실전 팁
💡 - NodePort 범위(30000-32767)를 변경하려면 kube-apiserver의 --service-node-port-range 옵션을 수정합니다
- 프로덕션에서는 NodePort 앞에 별도의 로드밸런서나 Ingress를 두는 것이 일반적입니다
4. LoadBalancer 서비스
드디어 김개발 씨의 애플리케이션이 프로덕션에 배포될 차례입니다. NodePort로는 한계가 있다고 느꼈습니다.
80번 포트로 깔끔하게 서비스하고 싶은데, 방법이 있을까요? 클라우드 환경이라면 LoadBalancer 타입이 답입니다.
LoadBalancer는 클라우드 프로바이더의 로드밸런서를 자동으로 프로비저닝하는 Service 타입입니다. AWS의 ELB, GCP의 Cloud Load Balancer, Azure의 Azure Load Balancer가 자동으로 생성됩니다.
마치 호텔 프런트 데스크처럼, 외부 요청을 받아 적절한 방(파드)으로 안내해줍니다.
다음 코드를 살펴봅시다.
# LoadBalancer Service 정의
apiVersion: v1
kind: Service
metadata:
name: webapp-lb
annotations:
# AWS ELB 설정 예시
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
spec:
type: LoadBalancer
selector:
app: webapp
ports:
- port: 80 # 외부에 노출되는 포트
targetPort: 8080 # 파드의 포트
---
# 생성 후 EXTERNAL-IP가 할당됨
# kubectl get svc webapp-lb
김개발 씨의 애플리케이션이 드디어 프로덕션에 진입합니다. 회사는 AWS를 사용하고 있고, 사용자들이 **www.example.com**으로 접속하면 이 애플리케이션에 연결되어야 합니다.
NodePort의 30000번대 포트로는 사용자 경험이 좋지 않습니다. 박시니어 씨가 말했습니다.
"프로덕션에서는 LoadBalancer 타입을 쓰세요. 클라우드가 알아서 로드밸런서를 만들어줍니다." LoadBalancer 타입은 클라우드 환경에서 마법처럼 동작합니다.
Service를 생성하면 쿠버네티스가 클라우드 프로바이더의 API를 호출하여 실제 로드밸런서를 만듭니다. AWS에서는 ELB(Elastic Load Balancer)가 생성되고, GCP에서는 Cloud Load Balancer가 생성됩니다.
별도의 콘솔 작업 없이 YAML 파일 하나로 모든 것이 자동화됩니다. 비유하자면 호텔 프런트 데스크와 같습니다.
손님이 호텔에 도착하면 프런트 데스크에서 방을 안내받습니다. 손님은 각 객실의 위치를 알 필요가 없고, 프런트 데스크가 빈 방을 찾아 적절히 배정해줍니다.
LoadBalancer도 마찬가지로 외부 트래픽을 받아서 적절한 파드로 분배합니다. LoadBalancer Service를 만들고 kubectl get svc를 실행하면 처음에는 EXTERNAL-IP가 pending 상태입니다.
클라우드 프로바이더가 로드밸런서를 프로비저닝하는 데 1-2분 정도 걸립니다. 완료되면 실제 외부 IP 주소나 DNS 이름이 할당됩니다.
이제 이 주소로 전 세계 어디서든 서비스에 접근할 수 있습니다. LoadBalancer의 장점은 무엇일까요?
첫째, 80번이나 443번 같은 표준 포트를 사용할 수 있습니다. 사용자는 **http://example.com**으로 접속하면 됩니다.
둘째, 고가용성이 보장됩니다. 클라우드 로드밸런서는 여러 가용 영역에 걸쳐 트래픽을 분산합니다.
노드 하나가 다운되어도 서비스는 계속됩니다. 셋째, 관리가 편합니다.
쿠버네티스가 Service 변경을 감지하여 로드밸런서 설정을 자동으로 업데이트합니다. 위의 YAML에서 annotations 부분을 주목하세요.
클라우드 프로바이더별로 다양한 옵션을 annotation으로 설정할 수 있습니다. AWS에서는 NLB(Network Load Balancer)와 ALB(Application Load Balancer) 중 선택할 수 있고, 내부 전용 로드밸런서로 만들 수도 있습니다.
이런 세부 설정은 각 클라우드의 문서를 참고해야 합니다. 단점도 있습니다.
가장 큰 것은 비용입니다. 각 LoadBalancer Service마다 실제 클라우드 로드밸런서가 생성되므로, 서비스가 많아지면 비용이 급격히 증가합니다.
마이크로서비스가 10개라면 로드밸런서도 10개가 필요할까요? 이런 문제를 해결하기 위해 Ingress가 등장했습니다.
Ingress는 하나의 로드밸런서로 여러 서비스를 라우팅할 수 있습니다. 온프레미스 환경에서는 어떨까요?
기본적으로 LoadBalancer 타입은 클라우드 환경에서만 동작합니다. 하지만 MetalLB같은 솔루션을 설치하면 베어메탈 환경에서도 LoadBalancer를 사용할 수 있습니다.
MetalLB가 가상의 로드밸런서 역할을 수행하여 외부 IP를 할당해줍니다. 김개발 씨는 LoadBalancer Service를 생성했습니다.
잠시 후 EXTERNAL-IP에 AWS ELB의 DNS 이름이 나타났습니다. 이 주소를 Route 53의 도메인에 연결하면 프로덕션 배포가 완료됩니다.
"이게 이렇게 간단하다니, 클라우드 네이티브의 힘이네요."
실전 팁
💡 - 비용 절감을 위해 여러 서비스를 노출해야 한다면 Ingress 사용을 고려하세요
- 클라우드별 annotation을 활용하면 로드밸런서의 상세 설정(SSL, 타임아웃 등)을 제어할 수 있습니다
5. ExternalName 서비스
김개발 씨의 애플리케이션은 외부 API 서비스를 호출해야 합니다. 개발 환경에서는 목업 서버를, 운영 환경에서는 실제 API를 호출하고 싶습니다.
환경마다 코드를 바꿔야 할까요? ExternalName Service가 이 문제를 해결해줍니다.
ExternalName은 클러스터 외부의 서비스에 대한 DNS 별칭을 제공하는 특별한 Service 타입입니다. 마치 전화번호부에서 "본사"를 찾으면 실제 전화번호가 나오는 것처럼, Service 이름이 외부 DNS 이름으로 매핑됩니다.
외부 서비스를 추상화하여 환경별로 다른 엔드포인트를 사용할 수 있게 해줍니다.
다음 코드를 살펴봅시다.
# ExternalName Service 정의
apiVersion: v1
kind: Service
metadata:
name: external-api
namespace: production
spec:
type: ExternalName
externalName: api.external-service.com
---
# 개발 환경에서는 다른 주소 사용
apiVersion: v1
kind: Service
metadata:
name: external-api
namespace: development
spec:
type: ExternalName
externalName: mock-api.internal.local
김개발 씨의 애플리케이션은 결제 처리를 위해 외부 PG사의 API를 호출합니다. 개발할 때는 PG사에서 제공하는 테스트 서버를 사용하고, 운영 환경에서는 실제 API를 호출해야 합니다.
처음에 김개발 씨는 환경 변수로 URL을 관리했습니다. 박시니어 씨가 다른 방법을 제안했습니다.
"ExternalName Service를 사용하면 더 깔끔해요. 코드에서는 항상 같은 이름을 쓰고, 환경별로 Service 설정만 바꾸면 됩니다." ExternalName은 다른 Service 타입과 많이 다릅니다.
파드를 선택하지 않고, ClusterIP도 할당되지 않습니다. 대신 DNS 레벨에서 CNAME 레코드를 만듭니다.
external-api라는 Service 이름으로 DNS 조회를 하면, 쿠버네티스 DNS가 api.external-service.com으로 응답합니다. 비유하자면 전화번호부의 별칭과 같습니다.
"본사"를 찾으면 02-1234-5678이 나오고, "지사"를 찾으면 031-9876-5432가 나옵니다. 실제 전화번호가 바뀌어도 전화번호부만 수정하면 됩니다.
모든 직원이 새 번호를 외울 필요가 없습니다. ExternalName도 마찬가지로, 외부 서비스의 주소가 바뀌어도 애플리케이션 코드를 수정할 필요가 없습니다.
위의 YAML을 보면 production 네임스페이스와 development 네임스페이스에 같은 이름(external-api)의 Service가 있습니다. 하지만 externalName은 다릅니다.
운영 환경의 파드가 **http://external-api**로 요청하면 api.external-service.com으로 연결되고, 개발 환경에서는 mock-api.internal.local로 연결됩니다. 애플리케이션 코드는 환경에 관계없이 **http://external-api**만 사용합니다.
ExternalName의 또 다른 활용 사례는 마이그레이션입니다. 기존에 클러스터 외부에서 운영되던 서비스를 쿠버네티스 내부로 옮긴다고 가정합니다.
처음에는 ExternalName으로 외부 서비스를 가리키다가, 마이그레이션이 완료되면 ClusterIP Service로 바꿉니다. 이 과정에서 다른 서비스들의 코드는 전혀 수정할 필요가 없습니다.
주의할 점도 있습니다. ExternalName은 DNS 레벨에서 동작하므로 포트 매핑이 불가능합니다.
port와 targetPort 설정이 무시됩니다. 외부 서비스가 특정 포트를 사용한다면 클라이언트에서 직접 해당 포트를 지정해야 합니다.
또한 HTTPS 연결 시 인증서 검증에 주의해야 합니다. 실제 접속하는 호스트명이 external-api가 아닌 api.external-service.com이므로 인증서가 이 이름으로 발급되어야 합니다.
ExternalName은 headless Service와 혼동하기 쉽습니다. headless Service는 clusterIP: None으로 설정된 ClusterIP Service로, 파드의 개별 IP를 직접 반환합니다.
반면 ExternalName은 클러스터 외부의 DNS 이름을 반환합니다. 용도가 완전히 다릅니다.
김개발 씨는 ExternalName Service를 적용했습니다. 이제 코드에서 결제 API를 호출할 때 항상 **http://external-api/v1/payment**를 사용합니다.
개발 환경에서는 테스트 서버로, 운영 환경에서는 실제 PG사로 연결됩니다. 환경 설정 파일을 관리하는 것보다 훨씬 깔끔해졌습니다.
실전 팁
💡 - ExternalName은 포트 매핑을 지원하지 않으므로, 외부 서비스의 포트를 코드에서 직접 지정해야 합니다
- HTTPS 사용 시 인증서 호스트명이 externalName과 일치하는지 확인하세요
6. 서비스 디스커버리 원리
김개발 씨는 문득 궁금해졌습니다. 파드에서 **http://my-service**로 요청하면 어떻게 Service를 찾아가는 걸까요?
DNS 서버 설정을 한 적도 없는데 말입니다. 쿠버네티스의 서비스 디스커버리 마법의 비밀을 파헤쳐 봅시다.
서비스 디스커버리는 클라이언트가 서비스의 위치를 자동으로 찾아내는 메커니즘입니다. 쿠버네티스는 CoreDNS와 환경 변수 두 가지 방식으로 서비스 디스커버리를 제공합니다.
마치 114 전화번호 안내처럼, 서비스 이름만 알면 실제 주소를 알려줍니다.
다음 코드를 살펴봅시다.
# CoreDNS가 제공하는 DNS 레코드 형식
# 같은 네임스페이스 내
my-service
# 다른 네임스페이스
my-service.other-namespace
# 완전한 FQDN
my-service.other-namespace.svc.cluster.local
# 파드 내에서 환경 변수 확인
# kubectl exec my-pod -- env | grep SERVICE
MY_SERVICE_SERVICE_HOST=10.96.45.123
MY_SERVICE_SERVICE_PORT=80
김개발 씨가 파드를 띄우고 코드에서 **http://backend-api**로 HTTP 요청을 보냈습니다. 신기하게도 백엔드 Service에 잘 연결됩니다.
분명히 DNS 서버 설정을 한 적이 없는데, 어떻게 backend-api라는 이름이 IP 주소로 변환된 걸까요? 박시니어 씨가 설명해 주었습니다.
"쿠버네티스에는 CoreDNS라는 내장 DNS 서버가 있어요. 모든 파드는 자동으로 이 DNS를 사용하도록 설정됩니다." 쿠버네티스 클러스터를 설치하면 kube-system 네임스페이스에 CoreDNS 파드가 실행됩니다.
이 CoreDNS는 클러스터 내의 모든 Service와 파드에 대한 DNS 레코드를 관리합니다. 새로운 Service가 생성되면 CoreDNS에 자동으로 등록되고, Service가 삭제되면 레코드도 제거됩니다.
파드가 생성되면 쿠버네티스는 파드의 /etc/resolv.conf 파일을 자동으로 구성합니다. 이 파일에 CoreDNS의 ClusterIP가 네임서버로 지정됩니다.
파드 안의 애플리케이션이 DNS 조회를 하면 자동으로 CoreDNS에 질의하게 됩니다. DNS 이름의 형식을 알아봅시다.
my-service.my-namespace.svc.cluster.local이 완전한 형식입니다. 여기서 my-service는 Service 이름, my-namespace는 네임스페이스, svc는 서비스임을 나타내고, cluster.local은 클러스터 도메인입니다.
하지만 같은 네임스페이스 안에서는 그냥 my-service만 써도 됩니다. CoreDNS가 현재 네임스페이스를 자동으로 추가해서 조회하기 때문입니다.
환경 변수 방식도 있습니다. 파드가 생성될 때, 해당 시점에 존재하는 모든 Service에 대한 환경 변수가 주입됩니다.
MY_SERVICE_SERVICE_HOST와 MY_SERVICE_SERVICE_PORT 같은 형식입니다. 하지만 이 방식에는 한계가 있습니다.
파드가 생성된 이후에 만들어진 Service는 환경 변수에 없습니다. 따라서 현대적인 애플리케이션에서는 DNS 방식을 사용하는 것이 권장됩니다.
서비스 디스커버리의 실제 흐름을 따라가 봅시다. 파드 A에서 **http://backend-api:80/users**로 요청을 보냅니다.
첫째, 파드의 DNS 리졸버가 backend-api를 CoreDNS에 질의합니다. 둘째, CoreDNS가 Service의 ClusterIP(예: 10.96.45.123)를 응답합니다.
셋째, 파드 A에서 10.96.45.123:80으로 TCP 연결을 시도합니다. 넷째, kube-proxy가 이 트래픽을 실제 파드 중 하나로 전달합니다.
kube-proxy의 역할도 중요합니다. kube-proxy는 모든 노드에서 실행되며, iptables나 IPVS 규칙을 관리합니다.
ClusterIP로 들어오는 트래픽을 실제 파드 IP로 NAT(Network Address Translation)합니다. 또한 여러 파드가 있을 때 로드밸런싱도 담당합니다.
기본적으로 라운드로빈 방식을 사용합니다. headless Service라는 특별한 경우도 있습니다.
clusterIP: None으로 설정하면 ClusterIP가 할당되지 않고, DNS 조회 시 Service가 아닌 개별 파드의 IP 목록이 반환됩니다. StatefulSet과 함께 사용하면 pod-0.my-service, pod-1.my-service처럼 각 파드에 고유한 DNS 이름을 부여할 수 있습니다.
디버깅 팁을 하나 알려드리겠습니다. Service 연결이 안 될 때 DNS 문제인지 확인하려면 kubectl run 명령으로 임시 파드를 띄워서 nslookup이나 dig 명령을 실행해보세요.
DNS가 정상인데 연결이 안 되면 네트워크 정책이나 파드 상태를 확인해야 합니다. 김개발 씨는 서비스 디스커버리의 동작 원리를 이해하고 나니, 문제가 생겼을 때 어디를 확인해야 할지 알게 되었습니다.
CoreDNS가 살아있는지, kube-proxy 규칙이 제대로 설정되었는지, 순서대로 점검하면 됩니다. "마법이 아니라 시스템이었군요!"
실전 팁
💡 - kubectl run debug --image=busybox -it --rm -- nslookup my-service 명령으로 DNS 동작을 확인할 수 있습니다
- CoreDNS 로그를 확인하려면 kubectl logs -n kube-system -l k8s-app=kube-dns를 사용하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Istio 보안 완벽 가이드
마이크로서비스 환경에서 필수적인 Istio 보안 기능을 실무 중심으로 설명합니다. mTLS부터 인증, 인가까지 단계별로 학습하여 안전한 서비스 메시를 구축할 수 있습니다.
Istio 트래픽 관리 완벽 가이드
Istio의 트래픽 관리 기능을 마스터하는 완벽 가이드입니다. VirtualService와 DestinationRule을 활용한 라우팅부터 트래픽 분할, 헤더 기반 라우팅까지 실무에 필요한 모든 내용을 다룹니다.
Istio 설치와 구성 완벽 가이드
Kubernetes 환경에서 Istio 서비스 메시를 설치하고 구성하는 방법을 초급 개발자도 쉽게 이해할 수 있도록 실무 스토리와 비유로 풀어낸 가이드입니다. istioctl 설치부터 사이드카 주입까지 단계별로 학습합니다.
서비스 메시 완벽 가이드
마이크로서비스 간 통신을 안전하고 효율적으로 관리하는 서비스 메시의 핵심 개념부터 실전 도입까지, 초급 개발자를 위한 완벽한 입문서입니다. Istio와 Linkerd 비교, 사이드카 패턴, 실무 적용 노하우를 담았습니다.
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.