TIL

Kubernetes 구축 시 서비스명 해결 문제와 NodePort 해결책

하얀잔디 2025. 9. 13. 15:41

상황 개요

Kubernetes 환경을 구축하면서 게이트웨이 서버에서 내부 서비스들에 접근하려고 했는데, 서비스명 기반 라우팅이 작동하지 않는 문제가 발생했습니다. NodePort를 활용한 포트 기반 접근으로 해결한 과정을 공유합니다.

 

 

환경 정보

  • 플랫폼: Kubernetes
  • 구성: 게이트웨이 서버 + Kubernetes 클러스터
  • 목표: 게이트웨이에서 클러스터 내부 서비스로 트래픽 라우팅

문제 상황

Kubernetes 클러스터 구축 후 게이트웨이 서버에서 내부 서비스들을 서비스명으로 접근할 수 없었습니다.

# 게이트웨이에서 시도했던 것들
curl http://my-service:8080        # 실패
curl http://my-service.default:8080 # 실패  
nslookup my-service                # 실패

하지만 클러스터 내부 Pod들끼리는 서비스명 기반 통신이 정상적으로 작동했습니다.

원인 분석

문제의 핵심

게이트웨이 서버가 Kubernetes 클러스터 외부에 위치해서 클러스터 내부 DNS(CoreDNS)를 사용할 수 없었던 것이 원인이었습니다.

진단 과정

1단계: 서비스명 해결 테스트

# 게이트웨이에서
nslookup my-service                    # 실패
dig my-service.default.svc.cluster.local # 실패

# 클러스터 내부 Pod에서
kubectl exec -it pod-name -- nslookup my-service  # 성공

2단계: 서비스 상태 확인

# 서비스 목록 및 타입 확인
kubectl get svc
kubectl get svc my-service -o yaml

# 엔드포인트 확인
kubectl get endpoints my-service

3단계: 현재 서비스 타입 확인

kubectl describe svc my-service
# Type: ClusterIP (클러스터 내부에서만 접근 가능)

4단계: NodePort로 변경 후 테스트

# 게이트웨이에서 노드IP + NodePort로 접근
curl http://192.168.1.101:30080  # 성공!

해결 방법

Kubernetes의 NodePort 서비스 타입을 활용해 클러스터 외부에서 포트를 통해 직접 접근할 수 있도록 설정했습니다.

서비스 타입 변경

기존 ClusterIP 서비스

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: ClusterIP  # 클러스터 내부에서만 접근 가능
  ports:
  - port: 8080
    targetPort: 8080
  selector:
    app: my-app

NodePort로 변경

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: NodePort  # 노드 포트를 통해 외부 접근 가능
  ports:
  - port: 8080
    targetPort: 8080
    nodePort: 30080  # 모든 노드의 이 포트로 접근 가능
  selector:
    app: my-app

여러 서비스 NodePort 설정 예시

# API 서비스
apiVersion: v1
kind: Service
metadata:
  name: api-service
spec:
  type: NodePort
  ports:
  - port: 8080
    targetPort: 8080
    nodePort: 30081
  selector:
    app: api-app
---
# Frontend 서비스  
apiVersion: v1
kind: Service
metadata:
  name: frontend-service
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 30082
  selector:
    app: frontend-app

게이트웨이 Nginx 설정

# /etc/nginx/sites-available/k8s-services
upstream api_backend {
    server 192.168.1.101:30081;  # 노드1 IP + NodePort
    server 192.168.1.102:30081;  # 노드2 IP + NodePort  
    server 192.168.1.103:30081;  # 노드3 IP + NodePort
    least_conn;
}

upstream frontend_backend {
    server 192.168.1.101:30082;
    server 192.168.1.102:30082;
    server 192.168.1.103:30082;
    least_conn;
}

# API 라우팅
server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://api_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

# Frontend 라우팅
server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://frontend_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
    }
}

작동 원리

기존 시도했던 방식 (실패)

게이트웨이 → 서비스명(my-service) → ❌ DNS 해결 실패

실제 해결 방식 (성공)

게이트웨이 → Nginx → 노드IP:NodePort → kube-proxy → Pod
  1. 게이트웨이: 클라이언트 요청 수신
  2. Nginx: upstream 설정에 따라 로드밸런싱
  3. NodePort: 모든 노드에서 동일한 포트로 서비스 접근 가능
  4. kube-proxy: 노드 내에서 해당 Pod으로 트래픽 전달
  5. Pod: 실제 애플리케이션에서 요청 처리

kubectl을 활용한 운영

서비스 상태 모니터링

# 서비스 목록과 NodePort 확인
kubectl get svc -o wide

# 특정 서비스의 엔드포인트 확인  
kubectl get endpoints my-service

# 서비스 상세 정보
kubectl describe svc my-service

동적 NodePort 정보 수집

#!/bin/bash
# get-nodeports.sh
# NodePort 정보를 가져와서 Nginx upstream 설정 생성

SERVICES=("api-service" "frontend-service")
NODES=$(kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}')

for svc in "${SERVICES[@]}"; do
    NODEPORT=$(kubectl get svc $svc -o jsonpath='{.spec.ports[0].nodePort}')
    echo "upstream ${svc//-/_}_backend {"
    for node in $NODES; do
        echo "    server $node:$NODEPORT;"
    done
    echo "    least_conn;"
    echo "}"
    echo
done

헬스체크 스크립트

#!/bin/bash
# health-check.sh
# NodePort 서비스 헬스체크

check_service() {
    local service=$1
    local nodeport=$2
    local nodes=$(kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}')
    
    for node in $nodes; do
        if curl -f -s http://$node:$nodeport/health > /dev/null; then
            echo "✓ $service on $node:$nodeport is healthy"
        else
            echo "✗ $service on $node:$nodeport is unhealthy"
        fi
    done
}

check_service "api-service" "30081"
check_service "frontend-service" "30082"

추가 고려사항

보안 측면

# NodePort 범위 확인 (기본: 30000-32767)
kubectl cluster-info dump | grep service-node-port-range

# 특정 노드에서만 접근하도록 방화벽 설정
ufw allow from 192.168.1.0/24 to any port 30080:30090

LoadBalancer 타입 대안 (클라우드 환경)

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: LoadBalancer  # 클라우드 LB 자동 프로비저닝
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: my-app

Ingress Controller 활용 방안

# 추후 개선 시 고려할 수 있는 Ingress 설정
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
spec:
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 8080

장점과 한계

NodePort 방식의 장점

  • 간단한 설정: 추가 컴포넌트 없이 외부 접근 가능
  • 안정성: 클러스터 DNS 의존성 제거
  • 투명성: 기존 게이트웨이 설정 재활용 가능
  • 디버깅: 문제 발생 시 원인 파악이 쉬움

한계점

  • 포트 관리: NodePort 범위 내에서 포트 할당 필요
  • 보안: 모든 노드에서 포트가 열림
  • 확장성: 노드 추가/제거 시 설정 업데이트 필요

결론

Kubernetes 환경에서 게이트웨이가 클러스터 외부에 있을 때 서비스명 기반 접근이 불가능한 문제를 NodePort 서비스 타입으로 해결할 수 있었습니다.

핵심 포인트

  • 클러스터 경계 이해: 내부 DNS vs 외부 접근의 차이
  • 서비스 타입 선택: ClusterIP → NodePort 변경
  • 포트 기반 라우팅: Nginx upstream을 통한 로드밸런싱
  • 운영 관리: kubectl을 활용한 모니터링과 자동화

처음에는 서비스명이 왜 안 되나 싶었는데, Kubernetes의 네트워크 구조를 이해하고 나니 NodePort가 이런 상황에 딱 맞는 해결책이었네요.