쿠버네티스 네트워킹, 왜 이렇게 헷갈릴까?

|MSA & Architecture|12분 읽기

또 접속이 안 된다

배포는 성공했는데 브라우저에서는 시간 초과 에러만 뜬다. kubectl로 파드는 정상 동작 중이라고 나오는데 왜 접근할 수 없는지 모르겠다. 쿠버네티스 네트워킹을 처음 접했을 때 누구나 겪는 일입니다.

몇 년 전 처음 k8s를 만졌을 때, 단순히 파드 IP로 접근하려고 했던 기억이 납니다. 당연히 안 되더라고요. 그때는 왜 이렇게 복잡한지 이해할 수 없었는데, 지금 돌이켜보면 파드, 서비스, 노드라는 세 계층이 각각 다른 네트워크 규칙을 가지고 있다는 걸 몰랐던 게 문제였습니다.

파드는 어떻게 IP를 받을까

쿠버네티스에서 IP는 컨테이너가 아닌 파드 단위로 할당됩니다. 하나의 파드 안에 여러 컨테이너가 있다면, 그 컨테이너들은 모두 같은 IP를 공유하죠.

단일 노드에서는 이게 간단합니다:

엔티티 IP 주소 네트워크
내 노트북 192.168.1.10 홈 LAN
쿠버네티스 노드 192.168.1.2 홈 LAN
Pod A 10.244.0.2 클러스터 내부
Pod B 10.244.0.3 클러스터 내부

쿠버네티스가 시작되면 내부 네트워크(보통 10.244.0.0/16)를 만들고, 여기서 파드들이 IP를 할당받습니다. 같은 노드의 파드끼리는 바로 통신이 가능하고요.

다만 파드 IP는 언제든 바뀔 수 있다는 점이 중요합니다. 파드가 재시작되거나 업데이트되면 새로운 IP를 받게 되니까, 설정에 파드 IP를 직접 쓰면 안 됩니다.

멀티 노드에서 생기는 문제

노드가 하나 더 추가되는 순간 상황이 복잡해집니다. 각 노드가 독립적으로 10.244.0.0/16 네트워크를 만들면, 서로 다른 노드에 있는 파드가 같은 IP를 가질 수 있거든요.

이런 문제를 해결하기 위해 쿠버네티스는 다음 요구사항을 제시합니다:

  1. 모든 파드는 NAT 없이 다른 모든 파드에 접근 가능해야 함
  2. 모든 노드는 모든 파드에 접근 가능해야 함
  3. 모든 파드는 모든 노드에 접근 가능해야 함

쿠버네티스는 이 문제의 해결책은 제시하지 않습니다. 대신 CNI(Container Network Interface) 플러그인이 이를 담당하죠.

CNI 플러그인들

클러스터 설치 시점에 CNI를 하나 선택하면, 해당 플러그인이 노드별 서브넷 할당, 라우팅 설정, 노드 간 통신 등을 모두 처리합니다.

주요 CNI 옵션들:

CNI 특징
Calico 범용성 좋음, 강력한 네트워크 정책 지원
Flannel 단순함, '그냥 작동하는' 클래식한 선택
Cilium eBPF 기반, 고성능, 뛰어난 관측성
Weave Net 멀티클라우드 설정이 쉬움

관리형 쿠버네티스(EKS, GKE, AKS)는 기본 CNI를 제공하지만 교체도 가능합니다. 최근엔 Cilium으로 갈아타는 팀들이 늘고 있더라고요.

CNI가 설치되면 각 노드는 고유한 서브넷을 할당받습니다. 노드1은 10.244.0.0/24, 노드2는 10.244.1.0/24 이런 식으로요. 그러면 모든 파드가 서로 통신할 수 있게 됩니다.

서비스가 필요한 이유

파드는 언제든 사라지고 새로 생깁니다. IP도 바뀌고요. 그럼 프론트엔드가 백엔드를 어떻게 안정적으로 호출할까요?

**서비스(Service)**가 바로 이 문제의 해답입니다. 서비스는:

  • 레이블 셀렉터로 파드 그룹을 선택
  • 자체적인 안정적 IP와 DNS 이름 보유
  • 매칭되는 파드들에 트래픽 로드밸런싱

일상적으로 쓰게 될 서비스 타입은 세 가지입니다.

ClusterIP: 기본 내부 서비스

클러스터 내부에서만 접근 가능한 서비스입니다. 가장 많이 쓰는 타입이죠.

apiVersion: v1
kind: Service
metadata:
  name: backend
spec:
  type: ClusterIP  # 생략해도 됨 (기본값)
  ports:
    - port: 80          # 서비스가 노출하는 포트
      targetPort: 8080  # 파드가 실제 받는 포트
  selector:
    app: backend        # app=backend 레이블 파드 선택

적용하면:

kubectl apply -f backend-service.yaml
kubectl get svc backend
# NAME      TYPE        CLUSTER-IP    PORT(S)   AGE
# backend   ClusterIP   10.100.20.4   80/TCP    5s

이제 클러스터의 다른 파드에서 이렇게 호출할 수 있습니다:

curl http://backend              # DNS로 해결
curl http://10.100.20.4         # IP로도 가능하지만 이름 추천

실제로는 이런 구조를 많이 씁니다:

  • 프론트엔드 파드 → 백엔드 ClusterIP 서비스
  • 백엔드 파드 → Redis ClusterIP 서비스, MySQL ClusterIP 서비스

각 계층이 독립적으로 스케일링되고 재배포되어도, 서비스가 안정적인 엔드포인트를 제공하니까요.

디버깅 팁: 연결이 안 되면 kubectl exec로 파드에 들어가서 nslookup backend, curl -v http://backend 순서로 확인해보세요. DNS는 되는데 curl이 안 되면 셀렉터 문제일 가능성이 높습니다.

NodePort: 외부에서 접근 가능하게

모든 노드의 같은 포트를 열어서 외부 접근을 허용하는 서비스입니다.

apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  type: NodePort
  ports:
    - port: 80          # 서비스 포트 (내부)
      targetPort: 80    # 파드 포트
      nodePort: 30008   # 노드 포트 (외부, 30000-32767)
  selector:
    app: myapp

NodePort 서비스는 세 개 포트가 관련됩니다:

  • nodePort: 외부에서 접근하는 포트 (30008)
  • port: 클러스터 내부 서비스 포트 (80)
  • targetPort: 파드가 실제 받는 포트 (80)

nodePort를 생략하면 30000-32767 범위에서 자동 할당됩니다.

kubectl apply -f nodeport-service.yaml
# 외부에서 접근
curl http://192.168.1.2:30008
curl http://192.168.1.3:30008   # 모든 노드에서 같은 포트

여러 파드가 있으면 자동으로 로드밸런싱되고, 파드가 여러 노드에 분산되어 있어도 어느 노드로 접근하든 상관없습니다.

다만 NodePort는 프로덕션 공개 서비스로는 부적절합니다. 임의의 고번호 포트를 노출하고 TLS나 호스트 기반 라우팅을 제공하지 않거든요.

LoadBalancer: 프로덕션급 외부 노출

클라우드 제공업체의 실제 로드밸런서를 프로비저닝하는 서비스입니다.

apiVersion: v1
kind: Service
metadata:
  name: voting-app
spec:
  type: LoadBalancer
  ports:
    - port: 80
      targetPort: 80
  selector:
    app: voting-app

적용하면:

kubectl get svc voting-app
# NAME         TYPE           CLUSTER-IP    EXTERNAL-IP     PORT(S)        AGE
# voting-app   LoadBalancer   10.100.55.9   34.102.77.14    80:31842/TCP   45s

이제 34.102.77.14로 DNS를 설정하면 서비스가 공개됩니다.

주의할 점: LoadBalancer 타입을 지원하지 않는 환경(VirtualBox, 홈 랩 등)에서는 EXTERNAL-IP가 계속 <pending> 상태로 남습니다. 대신 NodePort처럼 동작하죠.

베어메탈에서 LoadBalancer 기능을 원한다면 MetalLB를 설치하면 됩니다.

어떤 타입을 언제 쓸까

판단 기준은 간단합니다:

  1. 클러스터 내부에서만 호출되는 서비스 → ClusterIP
  2. 외부 접근 필요 + 클라우드 환경 → LoadBalancer (보통 Ingress와 함께)
  3. 외부 접근 필요 + 베어메탈/데모 → NodePort (또는 MetalLB)
타입 용도 외부 접근 프로덕션 적합성
ClusterIP 내부 서비스 X ⭐⭐⭐
NodePort 개발/데모 O
LoadBalancer 공개 서비스 O ⭐⭐⭐

자주 만나는 문제들

몇 가지 흔한 문제와 해결 방법입니다:

EXTERNAL-IP가 계속 <pending>: 클라우드 컨트롤러가 없는 환경에서 LoadBalancer 타입 사용. NodePort로 바꾸거나 MetalLB 설치.

NodePort로 "Connection refused": 셀렉터가 파드와 매칭되지 않음. kubectl get endpoints <svc>로 ENDPOINTS 확인.

DNS는 되는데 curl이 안 됨: 매칭 파드는 있지만 Ready 상태가 아님. readinessProbe 설정 점검.

노드 간 파드 통신 실패: CNI 문제. kubectl get pods -n kube-system에서 CNI 파드들이 Running 상태인지 확인.

정리하며

쿠버네티스 네트워킹은 처음엔 복잡해 보이지만, 결국 파드 네트워킹 + 서비스 추상화로 요약할 수 있습니다. CNI가 파드 간 통신을 책임지고, 서비스가 안정적인 엔드포인트를 제공하는 구조죠.

내부 통신엔 ClusterIP, 외부 노출엔 상황에 따라 NodePort나 LoadBalancer. 이 기본기만 확실히 해도 일상적인 네트워킹 문제 대부분은 해결할 수 있습니다.

생각해보니 몇 년 전 그 시간 초과 에러도, 결국 서비스를 제대로 안 만들어서였더라고요.

#kubernetes#네트워킹#클러스터#devops#인프라