Kubernetes 연결 문제 해결: exec와 port-forward 효과적으로 사용하기

Kubernetes 연결 및 내부 애플리케이션 문제를 자신 있게 해결하세요. 이 가이드는 `kubectl exec`를 사용하여 컨테이너 내부에서 명령을 실행하고 `kubectl port-forward`를 사용하여 로컬 머신에서 서비스에 안전하게 액세스하는 실용적인 예제를 제공합니다. 네트워크 문제를 진단하고, 구성을 검사하며, 클러스터 내 애플리케이션 동작에 대한 깊은 통찰력을 얻는 방법을 알아보세요.

Kubernetes 연결 문제 해결: exec와 port-forward 효과적으로 사용하기

Kubernetes 서비스가 시간 초과되면 일반적으로 문제를 해결하기 전에 한 가지 명확한 질문에 답해야 합니다: 연결이 어디에서 중단되는가? kubectl exec를 사용하면 클러스터 내부, 애플리케이션 가까이에서 테스트할 수 있습니다. kubectl port-forward를 사용하면 Ingress, LoadBalancer, 방화벽 규칙 또는 DNS 레코드를 변경하지 않고도 하나의 Pod 또는 서비스를 노트북으로 가져올 수 있습니다.

이 두 명령을 함께 사용하면 추측을 피할 수 있습니다. 앱이 수신 대기 중인지, 클러스터 DNS가 작동하는지, Service가 올바른 Pod를 가리키는지, 문제가 Kubernetes 네트워킹에 있는지 애플리케이션 자체에 있는지 테스트할 수 있습니다.

kubectl exec 이해하기

kubectl exec 명령을 사용하면 Pod 내에서 실행 중인 컨테이너 내부에서 명령을 실행할 수 있습니다. 이는 로그를 검사하고, 구성을 확인하며, 애플리케이션이 있는 곳에서 직접 진단 도구를 실행하는 데 매우 유용합니다.

프로덕션에서는 신중하게 사용하세요. 실제 워크로드 컨테이너 내부에서 명령을 실행하는 것입니다. 무해한 env, cat, curl 또는 ss는 정상입니다. 라이브 Pod 내부에 패키지를 설치하거나, 파일을 변경하거나, 비용이 많이 드는 진단을 실행하면 원래 문제를 숨기거나 새로운 문제를 만들 수 있습니다.

기본 구문

kubectl exec의 기본 구문은 다음과 같습니다:

kubectl exec <pod-name> -- <command> [args...]
  • <pod-name>: 명령을 실행하려는 Pod의 이름입니다.
  • --: 이 구분자는 중요합니다. kubectl 플래그와 컨테이너 내부에서 실행하려는 명령을 구분합니다.
  • <command>: 컨테이너 내에서 실행할 명령입니다 (예: ls, cat, ping).
  • [args...]: 명령에 대한 인수입니다.

대화형 셸 액세스

kubectl exec의 가장 일반적인 용도 중 하나는 컨테이너 내부에서 대화형 셸(예: bash 또는 sh)을 얻는 것입니다. 이를 통해 컨테이너의 파일 시스템을 탐색하고 여러 명령을 실행할 수 있습니다.

대화형 셸을 얻으려면:

kubectl exec -it <pod-name> -- /bin/bash
  • -i (또는 --stdin): 연결되지 않은 경우에도 stdin을 열어 둡니다.
  • -t (또는 --tty): 의사-TTY를 할당하며, 이는 대화형 셸 세션에 필요합니다.

예: my-app-pod라는 Pod에서 bash 셸에 액세스:

kubectl exec -it my-app-pod -- /bin/bash

내부에 들어가면 표준 Linux 명령을 사용할 수 있습니다. 셸을 종료하려면 exit를 입력하거나 Ctrl+D를 누르세요. /bin/bash가 없으면 /bin/sh를 시도하세요. 많은 소형 이미지에는 Bash가 포함되어 있지 않습니다.

단일 명령 실행

대화형 셸 없이 단일 명령을 실행할 수도 있습니다. 이는 빠른 확인이나 스크립팅에 유용합니다.

예: my-app-pod/app 디렉토리에서 파일 확인:

kubectl exec my-app-pod -- ls /app

예: 구성 파일 config.yaml의 내용 보기:

kubectl exec my-app-pod -- cat /etc/my-app/config.yaml

Pod 내 컨테이너 지정

Pod에 여러 컨테이너가 있는 경우 -c 플래그를 사용하여 명령을 실행할 컨테이너를 지정해야 합니다.

kubectl exec <pod-name> -c <container-name> -- <command>

예: multi-container-podsidecar-container에서 env 실행:

kubectl exec multi-container-pod -c sidecar-container -- env

kubectl port-forward 이해하기

kubectl port-forward 명령을 사용하면 로컬 머신에서 Kubernetes 클러스터의 특정 Pod 또는 서비스로 안전한 터널을 설정할 수 있습니다. 이는 외부에 노출되지 않은 애플리케이션을 디버깅하거나, 데이터베이스에 액세스하거나, 내부 API를 테스트하는 데 매우 유용합니다.

이는 프로덕션 트래픽 경로가 아닙니다. Kubernetes API 연결을 통한 디버깅 터널입니다. API 서버 연결이 끊어지면 로컬 터널도 끊어집니다.

기본 구문

일반 구문은 다음과 같습니다:

kubectl port-forward <pod-name> <local-port>:<remote-port>
  • <pod-name>: 연결하려는 Pod의 이름입니다.
  • <local-port>: 연결을 수신할 로컬 머신의 포트입니다.
  • <remote-port>: 전달된 트래픽을 수신할 Pod의 포트입니다.

예: 로컬 포트 8080을 my-app-pod의 포트 80으로 전달:

kubectl port-forward my-app-pod 8080:80

이 명령이 실행되면 웹 브라우저에서 http://localhost:8080으로 이동하거나 로컬 머신에서 curl과 같은 도구를 사용하여 애플리케이션에 액세스할 수 있습니다.

서비스로 전달

특정 Pod 대신 Kubernetes Service로 트래픽을 전달할 수도 있습니다. kubectl이 해당 서비스를 지원하는 Pod를 자동으로 선택합니다.

kubectl port-forward service/<service-name> <local-port>:<service-port>

예: 로컬 포트 3000을 my-service 서비스의 포트 80으로 전달:

kubectl port-forward service/my-service 3000:80

서비스로 전달할 때 kubectl이 하나의 백업 Pod를 선택한다는 점을 기억하세요. 복제본 하나만 손상된 경우 서비스 수준 전달이 이를 놓칠 수 있습니다. 예를 들어, 세 개의 Pod가 있는 Deployment에 두 개의 정상 Pod와 하나의 잘못된 구성이 있는 Pod가 있을 수 있습니다. service/my-service로 전달하면 정상 Pod를 선택하여 서비스가 정상으로 보일 수 있습니다. 복제본별 문제가 의심되는 경우 정확한 Pod 이름으로 전달하세요.

Deployment 또는 StatefulSet으로 전달

마찬가지로 Deployment 또는 StatefulSet으로 전달할 수 있습니다. kubectl이 지정된 리소스에서 관리하는 Pod 중 하나를 선택합니다.

kubectl port-forward deployment/<deployment-name> <local-port>:<container-port>
kubectl port-forward statefulset/<statefulset-name> <local-port>:<container-port>

특정 주소에 바인딩

기본적으로 port-forwardlocalhost에 바인딩됩니다. --address 플래그를 사용하여 다른 로컬 주소를 지정할 수 있습니다.

kubectl port-forward --address 127.0.0.1 <pod-name> <local-port>:<remote-port>

여러 포트 전달

kubectl port-forward는 여러 포트를 동시에 전달할 수 있습니다.

kubectl port-forward my-app-pod 8080:80 9090:90

이 명령은 로컬 포트 8080을 Pod 포트 80으로, 로컬 포트 9090을 Pod 포트 90으로 전달합니다.

일반적인 문제 해결 시나리오 및 솔루션

시나리오 1: 애플리케이션이 응답하지 않지만 Pod는 정상으로 보입니다.

  • 문제: Pod가 실행 중이지만 서비스에 대한 요청이 실패하거나 시간 초과됩니다. 애플리케이션에 내부 구성 문제가 있거나 중단되었을 수 있습니다.
  • kubectl exec를 사용한 솔루션:
    1. Pod에 대화형 셸을 얻습니다: kubectl exec -it <pod-name> -- /bin/bash
    2. 셸 내부에서 애플리케이션 로그를 확인합니다 (예: tail -f /var/log/myapp.log).
    3. 애플리케이션의 내부 구성 파일을 확인합니다.
    4. Pod 내부에서 다른 서비스로의 네트워크 연결을 ping 또는 curl(설치된 경우)을 사용하여 확인합니다.
  • kubectl port-forward를 사용한 솔루션:
    1. 애플리케이션의 수신 포트로 포트를 전달합니다: kubectl port-forward <pod-name> 8080:<app-port>
    2. http://localhost:8080을 통해 로컬에서 애플리케이션에 액세스해 봅니다. 이는 문제가 Kubernetes 서비스 검색 또는 인그레스에 있는지, 아니면 애플리케이션 자체가 응답하지 않는지 확인하는 데 도움이 됩니다.

포트 포워딩이 작동하지만 일반 서비스 URL이 실패하면 Service 선택기와 엔드포인트를 검사하세요:

kubectl get service <service-name> -n <namespace> -o yaml
kubectl get endpoints <service-name> -n <namespace>
kubectl get pods -n <namespace> --show-labels

매우 일반적인 실패는 레이블 불일치입니다. Pod는 정상이고 서비스는 존재하지만 선택기가 Pod 레이블과 일치하지 않아 Service에 엔드포인트가 없습니다.

시나리오 2: Pod에서 실행 중인 데이터베이스를 디버깅해야 합니다.

  • 문제: 로컬 데이터베이스 클라이언트를 Kubernetes Pod 내부에서 실행 중인 데이터베이스에 연결하여 데이터를 검사하거나 쿼리를 실행해야 합니다.
  • kubectl port-forward를 사용한 솔루션:
    1. 데이터베이스를 실행 중인 Pod와 해당 포트를 식별합니다 (예: mysql-pod, 포트 3306).
    2. 로컬 포트를 데이터베이스 포트로 전달합니다: kubectl port-forward mysql-pod 3306:3306
    3. 적절한 데이터베이스 자격 증명을 사용하여 로컬 데이터베이스 클라이언트를 localhost:3306에 연결하도록 구성합니다.

시나리오 3: Pod 내 DNS 확인 문제 진단.

  • 문제: Pod 내부의 애플리케이션이 서비스 이름으로 다른 서비스에 도달할 수 없어 DNS 문제를 암시합니다.
  • kubectl exec를 사용한 솔루션:
    1. Pod에 대화형 셸을 얻습니다: kubectl exec -it <pod-name> -- /bin/bash
    2. 셸 내부에서 알려진 서비스 이름을 확인해 봅니다: nslookup <service-name>.<namespace>.svc.cluster.local 또는 dig <service-name>.<namespace>.svc.cluster.local.
    3. /etc/resolv.conf의 내용을 확인하여 Pod 내부의 클러스터 DNS 구성이 올바른지 확인합니다.

이미지에 nslookup, dig 또는 curl이 포함되지 않은 경우 동일한 네임스페이스에서 임시 디버그 Pod를 사용하세요:

kubectl run net-debug -n <namespace> --rm -it --image=curlimages/curl -- sh

거기서 애플리케이션이 사용하는 동일한 서비스 이름을 테스트하세요. 이는 "내 애플리케이션 이미지에 도구가 없음"과 "클러스터 DNS가 손상됨"을 구분하는 데 도움이 됩니다.

시나리오 4: 포트 포워딩이 연결된 후 즉시 종료됩니다.

  • 문제: kubectl port-forward가 전달 메시지를 출력하지만 브라우저를 열거나 curl을 실행하면 연결이 종료됩니다.
  • 가능한 원인: 대상 프로세스가 원격 포트에서 수신 대기하지 않거나, 애플리케이션이 컨테이너 내부에서 127.0.0.1에만 바인딩되거나, 터널이 열려 있는 동안 Pod가 다시 시작됩니다.
  • 확인:
    kubectl exec <pod-name> -n <namespace> -- ss -lntp
    kubectl get pod <pod-name> -n <namespace> -w
    kubectl logs <pod-name> -n <namespace> --tail=100
    

프로세스가 포트 8080에서 수신 대기 중이지만 80으로 전달하는 경우 터널 자체는 정상입니다. 대상이 잘못된 것입니다. 테스트 중에 Pod가 다시 시작되면 네트워킹을 추적하기 전에 다시 시작 원인을 해결하세요.

시나리오 5: 서비스가 한 Pod에서는 작동하지만 다른 Pod에서는 작동하지 않습니다.

  • 문제: 백엔드가 Redis에 도달할 수 있지만 다른 네임스페이스의 작업자 Pod는 도달할 수 없습니다.
  • exec로 확인:
    kubectl exec <source-pod> -n <source-namespace> -- curl -v http://<service>.<target-namespace>.svc.cluster.local:<port>
    kubectl exec <source-pod> -n <source-namespace> -- cat /etc/resolv.conf
    

DNS가 확인되지만 TCP가 실패하면 NetworkPolicy, 서비스 엔드포인트 및 대상 애플리케이션이 해당 네임스페이스의 트래픽을 수락하는지 확인하세요. DNS가 확인되지 않으면 앱을 비난하기 전에 정규화된 서비스 이름을 테스트하세요.

간단한 연결 사다리

요청이 실패하면 가장 가까운 곳에서 가장 먼 곳으로 테스트하세요:

  1. Pod 내부에서 로컬 앱 프로세스를 테스트:
    kubectl exec <pod> -n <namespace> -- curl -v http://127.0.0.1:<port>/health
    
  2. 동일한 네임스페이스의 다른 Pod에서 Service를 테스트:
    kubectl run curl-test -n <namespace> --rm -it --image=curlimages/curl -- curl -v http://<service>:<port>/health
    
  3. 노트북에서 포트 포워딩을 통해 테스트:
    kubectl port-forward service/<service> -n <namespace> 8080:<service-port>
    curl -v http://localhost:8080/health
    
  4. 이러한 단계가 통과한 후에만 Ingress, 로드 밸런서, DNS 및 방화벽 규칙으로 이동하세요.

이 순서는 각 단계가 하나의 계층을 증명하므로 시간을 절약합니다. Pod 내부의 localhost가 실패하면 Ingress는 관련이 없습니다. Pod-로컬이 성공하지만 Service 액세스가 실패하면 앱은 아마 정상이며 Kubernetes 서비스 검색에 주의가 필요합니다.

한 가지 더 습관이 도움이 됩니다: 디버깅하는 동안 네임스페이스를 명시적으로 유지하세요. 많은 혼란스러운 세션은 기본 네임스페이스에서 kubectl exec를 실행하는 반면 문제가 있는 워크로드는 다른 곳에 있을 때 발생합니다. 세션의 컨텍스트에서 네임스페이스를 설정하거나 모든 명령에 -n <namespace>를 추가하세요. 추가 입력은 잘못된 Pod를 테스트하는 것보다 저렴합니다.

또한 실패를 증명한 정확한 명령을 저장하세요. 다음 당직자는 컨텍스트를 기억에서 재구성하지 않고 다시 실행할 수 있습니다.

모범 사례 및 팁

  • port-forward를 계속 실행: kubectl port-forward는 포그라운드에서 실행됩니다. 터미널 창을 열어 두어야 합니다. 백그라운드에서 실행하려면 nohup 또는 screen/tmux와 같은 도구를 사용할 수 있습니다.
  • 디버깅 시 특정 Pod 사용: 서비스로 전달하는 것이 편리하지만 특정 인스턴스의 문제를 정확히 찾아내려면 이름을 사용하여 특정 Pod로 전달하는 것이 더 효과적인 경우가 많습니다.
  • 보안: 노출하는 포트에 주의하세요. 꼭 필요한 경우가 아니면 민감한 포트를 전달하지 말고 로컬 머신이 보호되는지 확인하세요.
  • 리소스 사용: kubectl exec는 리소스를 소비할 수 있습니다. 특히 프로덕션 클러스터에서는 신중하게 사용하세요.
  • 권한: kubectl 컨텍스트에 Pod에서 명령을 실행하거나 포트를 전달하는 데 필요한 권한이 있는지 확인하세요.

문제를 해결한 후 기록할 사항

좋은 디버깅은 흔적을 남깁니다. 실패한 URL, 소스 Pod, 대상 서비스, 네임스페이스, 문제를 재현한 정확한 명령 및 실패한 계층을 캡처하세요. "연결 문제"는 다음 번에 도움이 되기에는 너무 모호합니다. "레이블 이름 변경 후 Service 선택기가 Pod와 일치하지 않음"은 수정 가능한 패턴입니다.