모니터링, 로깅, 및 디버깅

클러스터를 트러블슈팅할 수 있도록 모니터링과 로깅을 설정하거나, 컨테이너화된 애플리케이션을 디버깅한다.

때때로 문제가 발생할 수 있다. 이 가이드는 이러한 상황을 해결하기 위해 작성되었다. 문제 해결에는 다음 두 가지를 참고해 볼 수 있다.

  • 애플리케이션 디버깅하기 - 쿠버네티스에 코드를 배포하였지만 제대로 동작하지 않는 사용자들에게 유용한 가이드이다.
  • 클러스터 디버깅하기 - 쿠버네티스 클러스터에 문제를 겪고 있는 클러스터 관리자 혹은 기분이 나쁜 사람들에게 유용한 가이드이다.

여러분이 현재 사용중인 릴리스에 대한 알려진 이슈들을 다음의 릴리스 페이지에서 확인해 볼 수도 있다.

도움 받기

여러분의 문제가 위에 소개된 어떠한 가이드로도 해결할 수 없다면, 쿠버네티스 커뮤니티로부터 도움을 받을 수 있는 다양한 방법들을 시도해 볼 수 있다.

질문

이 사이트의 문서들은 다양한 질문들에 대한 답변을 제공할 수 있도록 구성되어 있다. 개념은 쿠버네티스의 아키텍처와 각 컴포넌트들이 어떻게 동작하는지에 대해 설명하고, 시작하기는 쿠버네티스를 시작하는 데 유용한 지침들을 제공한다. 태스크는 흔히 사용되는 작업들을 수행하는 방법에 대해 소개하고, 튜토리얼은 실무, 산업 특화 혹은 종단간 개발에 특화된 시나리오를 통해 차근차근 설명한다. 레퍼런스 섹션에서는 쿠버네티스 APIkubectl과 같은 커맨드 라인 인터페이스(CLI)에 대한 상세한 설명을 다룬다.

도와주세요! 내 질문이 다뤄지지 않았어요! 도움이 필요해요!

스택 오버플로우

여러분들이 겪고 있는 문제와 동일한 문제에 대한 도움을 위해 커뮤니티의 다른 사람들이 이미 질문을 올렸을 수 있다. 쿠버네티스 팀은 쿠버네티스 태그가 등록된 글들을 모니터링하고 있다. 발생한 문제에 도움이 되는 기존 질문이 없다면, 해당 질문이 스택 오버플로우에 적합한지새로운 질문을 올리는 방법에 대한 가이드를 읽은 뒤에 새로운 질문을 올리자!

슬랙

쿠버네티스 슬랙의 #kubernetes-users 채널을 통해 쿠버네티스 커뮤니티의 여러 사람들을 접할 수도 있다. 쿠버네티스 슬랙을 사용하기 위해서는 등록이 필요한데, 다음을 통해 채널 초대 요청을 할 수 있다. (누구나 가입할 수 있다). 슬랙 채널은 여러분이 어떠한 질문을 할 수 있도록 언제나 열려있다. 가입하고 나면 여러분의 웹 브라우저나 슬랙 앱을 통해 쿠버네티스 슬랙 에 참여할 수 있다.

쿠버네티스 슬랙에 참여하게 된다면, 다양한 주제의 흥미와 관련된 여러 채널들에 대해 살펴본다. 가령, 쿠버네티스를 처음 접하는 사람이라면 #kubernetes-novice 채널에 가입할 수 있다. 혹은, 만약 당신이 개발자라면 #kubernetes-contributors 채널에 가입할 수 있다.

또한 각 국가 및 사용 언어별 채널들이 여럿 존재한다. 사용하는 언어로 도움을 받거나 정보를 얻기 위해서는 다음의 채널에 참가한다.

국가 / 언어별 슬랙 채널
국가채널
China(중국)#cn-users, #cn-events
Finland(핀란드)#fi-users
France(프랑스)#fr-users, #fr-events
Germany(독일)#de-users, #de-events
India(인도)#in-users, #in-events
Italy(이탈리아)#it-users, #it-events
Japan(일본)#jp-users, #jp-events
Korea(한국)#kr-users
Netherlands(네덜란드)#nl-users
Norway(노르웨이)#norw-users
Poland(폴란드)#pl-users
Russia(러시아)#ru-users
Spain(스페인)#es-users
Sweden(스웨덴)#se-users
Turkey(터키)#tr-users, #tr-events

포럼

공식 쿠버네티스 포럼에 참여하는 것도 추천되는 방법이다. discuss.kubernetes.io.

버그와 기능 추가 요청

만약 여러분이 버그처럼 보이는 것을 발견했거나, 기능 추가 요청을 하기 위해서는 GitHub 이슈 트래킹 시스템을 사용한다.

이슈를 작성하기 전에는, 여러분의 이슈가 기존 이슈에서 이미 다뤄졌는지 검색해 본다.

버그를 보고하는 경우에는, 해당 문제를 어떻게 재현할 수 있는지에 관련된 상세한 정보를 포함한다. 포함되어야 하는 정보들은 다음과 같다.

  • 쿠버네티스 버전: kubectl version
  • 클라우드 프로바이더, OS 배포판, 네트워크 구성, 및 도커 버전
  • 문제를 재현하기 위한 절차

1 - 애플리케이션 트러블슈팅하기

일반적인 컨테이너화된 애플리케이션 이슈를 디버깅한다.

이 문서는 컨테이너화된 애플리케이션의 이슈를 해결하기 위한 자원을 담고 있다. 쿠버네티스 리소스(예: 파드, 서비스, 스테이트풀셋)의 일반적 이슈, 컨테이너 종료 메시지 이해에 대한 조언, 실행 중인 컨테이너를 디버그하는 방법 등을 다룬다.

1.1 - 파드 디버깅하기

이 가이드는 쿠버네티스에 배포되었지만 제대로 동작하지 않는 애플리케이션을 디버깅하는 방법을 소개한다. 이 가이드는 클러스터 디버깅에 대한 것은 아니다. 클러스터 디버깅에 대해서는 이 가이드를 참고한다.

문제 진단하기

트러블슈팅의 첫 단계는 문제를 파악하는 것이다. 무엇이 문제인가? 파드인가, 레플리케이션 컨트롤러인가, 서비스인가?

파드 디버깅하기

파드 디버깅의 첫 번째 단계는 파드를 살펴 보는 것이다. 다음의 명령어를 사용하여 파드의 현재 상태와 최근 이벤트를 점검한다.

kubectl describe pods ${POD_NAME}

파드 내부 컨테이너의 상태를 확인한다. 모두 Running 상태인가? 최근에 재시작 되었는가?

파드의 상태에 따라 디버깅을 계속한다.

파드가 계속 pending 상태인 경우

파드가 Pending 상태로 멈춰 있는 경우는, 노드에 스케줄 될 수 없음을 의미한다. 일반적으로 이것은 어떤 유형의 리소스가 부족하거나 스케줄링을 방해하는 다른 요인 때문이다. 상단의 kubectl describe ... 명령의 결과를 확인하자. 파드를 스케줄 할 수 없는 사유에 대한 스케줄러의 메세지가 있을 것이다. 다음과 같은 사유가 있을 수 있다.

  • 리소스가 부족한 경우: 사용자 클러스터의 CPU 나 메모리가 고갈되었을 수 있다. 이러한 경우, 파드를 삭제하거나, 리소스 요청을 조정하거나, 클러스터에 노드를 추가해야 한다. 컴퓨트 자원 문서에서 더 많은 정보를 확인한다.

  • hostPort를 사용하고 있는 경우: 파드를 hostPort에 바인딩할 때, 파드가 스케줄링될 수 있는 장소 수 제한이 존재한다. 대부분의 경우 hostPort는 불필요하므로, 파드를 노출하기 위해서는 서비스(Service) 오브젝트 사용을 고려해 본다. hostPort가 꼭 필요하다면 클러스터의 노드 수 만큼만 파드를 스케줄링할 수 있다.

파드가 계속 waiting 상태인 경우

파드가 Waiting 상태에서 멈춘 경우는, 파드가 워커 노드에 스케줄링되었지만 해당 노드에서 실행될 수 없음을 의미한다. 다시 말하지만, kubectl describe ... 명령은 유용한 정보를 제공한다. 파드가 Waiting 상태에서 멈추는 가장 흔한 원인은 이미지 풀링(pulling)에 실패했기 때문이다. 다음의 3가지 사항을 확인한다.

  • 이미지 이름이 올바른지 확인한다.
  • 해당 이미지를 저장소에 푸시하였는가?
  • 이미지가 풀 될 수 있는지 확인하기 위해 수동으로 이미지를 풀 해본다. 예를 들어, PC에서 도커를 사용하는 경우, docker pull <image> 명령을 실행한다.

파드가 손상(crashing)되었거나 양호하지 않을(unhealthy) 경우

일단 사용자의 파드가 스케줄 되면, 구동중인 파드 디버그하기에 있는 방법을 사용하여 디버깅을 할 수 있다.

파드가 running 상태이지만 해야 할 일을 하고 있지 않은 경우

파드가 예상과 다르게 동작 중이라면, 파드 상세(예: 로컬 머신에 있는 mypod.yaml 파일)에 에러가 있었는데 파드 생성 시에 에러가 조용히 지나쳐진 경우일 수 있다. 종종 파드 상세의 들여쓰기가 잘못되었거나, 키 이름에 오타가 있어서 해당 키가 무시되는 일이 있을 수 있다. 예를 들어, commandcommnd로 잘못 기재했다면 해당 파드는 생성은 되지만 명시한 명령줄을 실행하지 않을 것이다.

가장 먼저 해야 할 일은 파드를 삭제한 다음, --validate 옵션을 사용하여 다시 만들어 보는 것이다. 예를 들어, kubectl apply --validate -f mypod.yaml 를 실행한다. commandcommnd로 잘못 기재했다면 다음과 같은 에러가 발생할 것이다.

I0805 10:43:25.129850   46757 schema.go:126] unknown field: commnd
I0805 10:43:25.129973   46757 schema.go:129] this may be a false alarm, see https://github.com/kubernetes/kubernetes/issues/6842
pods/mypod

다음으로 확인할 것은 apiserver를 통해 확인한 파드 상세가 사용자가 의도한 파드 상세(예: 로컬 머신에 있는 yaml 파일)와 일치하는지 여부이다. 예를 들어, kubectl get pods/mypod -o yaml > mypod-on-apiserver.yaml 를 실행한 다음, 원본 파드 상세(mypod.yaml)와 apiserver를 통해 확인한 파드 상세(mypod-on-apiserver.yaml)를 수동으로 비교한다. 보통 원본 버전에는 없지만 "apiserver" 버전에는 있는 줄들이 존재한다. 이는 예상대로이다. 하지만, 원본 버전에는 있지만 "apiserver" 버전에는 없는 줄들이 있다면, 이는 원본 파드 상세에 문제가 있을 수도 있음을 의미한다.

레플리케이션컨트롤러 디버깅하기

레플리케이션컨트롤러의 경우에는 매우 직관적이다. 파드 생성이 가능하거나 또는 불가능한 경우 둘 뿐이다. 레플리케이션컨트롤러가 파드를 생성할 수 없다면, 위의 지침을 참고하여 파드를 디버깅한다.

사용자는 kubectl describe rc ${CONTROLLER_NAME} 을 사용하여 레플리케이션 컨트롤러와 관련된 이벤트를 검사할 수도 있다.

서비스 디버깅하기

서비스는 파드 집합에 대한 로드 밸런싱 기능을 제공한다. 일반적인 몇몇 문제들 때문에 서비스가 제대로 동작하지 않을 수 있다. 다음 지침을 이용하여 서비스 문제를 디버깅할 수 있다.

먼저, 서비스를 위한 엔드포인트가 존재하는지 확인한다. 모든 서비스 오브젝트에 대해, apiserver는 endpoints 리소스를 생성하고 사용 가능한(available) 상태로 만든다.

다음 명령을 사용하여 이 리소스를 볼 수 있다.

kubectl get endpoints ${SERVICE_NAME}

엔드포인트의 수가 해당 서비스에 속하는 파드의 수와 일치하는지 확인한다. 예를 들어, 서비스가 레플리카 3개인 nginx 컨테이너를 위한 것이라면, 서비스의 엔드포인트 항목에서 서로 다른 3개의 IP 주소가 확인되어야 한다.

서비스에 엔드포인트가 없는 경우

엔드포인트가 없는 상태라면, 서비스가 사용 중인 레이블을 이용하여 파드 목록을 조회해 본다. 다음과 같은 레이블을 갖는 서비스를 가정한다.

...
spec:
  - selector:
     name: nginx
     type: frontend

다음의 명령을 사용하여,

kubectl get pods --selector=name=nginx,type=frontend

이 셀렉터에 매치되는 파드 목록을 조회할 수 있다. 서비스에 속할 것으로 예상하는 파드가 모두 조회 결과에 있는지 확인한다. 파드의 containerPort가 서비스의 targetPort와 일치하는지 확인한다.

네트워크 트래픽이 포워드되지 않는 경우

서비스 디버깅하기에서 더 많은 정보를 확인한다.

다음 내용

위의 방법 중 어떤 것으로도 문제가 해결되지 않는다면, 서비스 디버깅하기 문서를 참조하여 서비스가 실행 중인지, 서비스에 엔드포인트가 있는지, 파드가 실제로 서빙 중인지 확인한다. 예를 들어, DNS가 실행 중이고, iptables 규칙이 설정되어 있고, kube-proxy가 정상적으로 동작하는 것으로 보이는 상황이라면, 위와 같은 사항을 확인해 볼 수 있다.

트러블슈팅 문서에서 더 많은 정보를 볼 수도 있다.

1.2 - 서비스 디버깅하기

쿠버네티스를 새로 설치할 때 자주 발생하는 문제 중 하나는 서비스가 제대로 작동하지 않는 현상이다. 디플로이먼트(또는 다른 워크로드 컨트롤러)를 통해 파드를 실행하고 서비스를 생성했지만, 해당 서비스에 엑세스하려고 하면 응답이 없는 경우이다. 이 문서를 통해 무엇이 잘못되었는지 파악하는 데 도움이 되기를 바란다.

파드 안에서 명령어 실행

이 페이지의 많은 단계에서, 클러스터 내에 실행 중인 파드가 어떤 것을 보고 있는지 알고 싶을 것이다. 이를 수행하는 가장 간단한 방법은 대화형 busybox 파드를 실행하는 것이다:

kubectl run -it --rm --restart=Never busybox --image=gcr.io/google-containers/busybox sh

사용하려는 파드가 이미 실행 중인 경우, 다음을 사용하여 명령을 실행할 수 있다.

kubectl exec <POD-NAME> -c <CONTAINER-NAME> -- <COMMAND>

설정

연습을 위해, 파드를 몇 개 실행한다. 자신이 관리하는 서비스를 디버깅하는 경우에는 세부 사항을 상황에 맞게 변경하고, 아니면 아래의 과정을 그대로 수행하여 두 번째 데이터 포인트를 얻을 수 있다.

kubectl create deployment hostnames --image=registry.k8s.io/serve_hostname
deployment.apps/hostnames created

kubectl 명령어는 생성되거나 변경된 리소스의 유형과 이름을 출력하여, 여기서 출력된 리소스 유형과 이름은 다음 명령어에 사용할 수 있다.

디플로이먼트의 레플리카를 3개로 확장한다.

kubectl scale deployment hostnames --replicas=3
deployment.apps/hostnames scaled

참고로, 위의 명령들을 실행하여 디플로이먼트를 실행하는 것은 다음과 같은 YAML을 사용하는 것과 동일하다.

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: hostnames
  name: hostnames
spec:
  selector:
    matchLabels:
      app: hostnames
  replicas: 3
  template:
    metadata:
      labels:
        app: hostnames
    spec:
      containers:
      - name: hostnames
        image: registry.k8s.io/serve_hostname

"app" 레이블은 kubectl create deployment명령에 의해 디플로이먼트의 이름으로 자동으로 설정된다.

파드가 실행 중인지 확인할 수 있다.

kubectl get pods -l app=hostnames
NAME                        READY     STATUS    RESTARTS   AGE
hostnames-632524106-bbpiw   1/1       Running   0          2m
hostnames-632524106-ly40y   1/1       Running   0          2m
hostnames-632524106-tlaok   1/1       Running   0          2m

파드가 서빙 중인지도 확인할 수 있다. 파드 IP주소의 목록을 가져온 뒤 이를 직접 테스트할 수 있다.

kubectl get pods -l app=hostnames \
    -o go-template='{{range .items}}{{.status.podIP}}{{"\n"}}{{end}}'
10.244.0.5
10.244.0.6
10.244.0.7

연습에 사용된 컨테이너 예제는 포트 9376에서 HTTP를 통해 호스트이름을 리턴하지만, 본인의 앱을 디버깅하는 경우, 포트 번호를 파드가 수신 대기 중인 포트 번호로 대체하면 된다.

파드 내에서 다음을 실행한다.

for ep in 10.244.0.5:9376 10.244.0.6:9376 10.244.0.7:9376; do
    wget -qO- $ep
done

다음과 같은 결과가 출력될 것이다.

hostnames-632524106-bbpiw
hostnames-632524106-ly40y
hostnames-632524106-tlaok

이 단계에서 예상한 응답을 받지 못한 경우, 파드가 정상적이지 않거나 또는 예상했던 포트에서 수신 대기를 하지 않고 있을 가능성이 있다. 어떤 일이 발생하고 있는지 확인하는 데 kubectl logs가 유용할 수 있으며, 또는 파드에 직접 kubectl exec를 실행하고 이를 통해 디버그을 수행해야 할 수도 있다.

지금까지 모든 것이 계획대로 진행되었다면, 서비스가 작동하지 않는 이유를 분석해 볼 수 있다.

서비스가 존재하는가?

여기까지 따라왔다면 아직 실제로 서비스를 생성하지 않았다는 것을 알아차렸을 것이다. 이는 의도적인 것이다. 이 단계는 때때로 잊어버리지만, 가장 먼저 확인해야 할 단계이다.

존재하지 않는 서비스에 액세스하려고 하면 어떤 일이 발생할까? 이름을 이용하여 이 서비스를 사용하는 다른 파드가 있다면 다음과 같은 결과를 얻을 것이다.

wget -O- hostnames
Resolving hostnames (hostnames)... failed: Name or service not known.
wget: unable to resolve host address 'hostnames'

가장 먼저 확인해야 할 것은 서비스가 실제로 존재하는지 여부이다.

kubectl get svc hostnames
No resources found.
Error from server (NotFound): services "hostnames" not found

이제 서비스를 생성하자. 이전에도 언급했듯이, 이것은 연습을 위한 예시이다. 본인의 서비스의 세부 사항을 여기에 입력할 수도 있다.

kubectl expose deployment hostnames --port=80 --target-port=9376
service/hostnames exposed

다시 조회해 본다.

kubectl get svc hostnames
NAME        TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
hostnames   ClusterIP   10.0.1.175   <none>        80/TCP    5s

이제 서비스가 존재하는 것을 확인할 수 있다.

위와 마찬가지로, 위의 명령들을 실행하여 서비스를 실행하는 것은 다음과 같은 YAML을 사용하는 것과 동일하다.

apiVersion: v1
kind: Service
metadata:
  labels:
    app: hostnames
  name: hostnames
spec:
  selector:
    app: hostnames
  ports:
  - name: default
    protocol: TCP
    port: 80
    targetPort: 9376

환경설정의 전체 범위를 강조하기 위해, 여기에서 생성한 서비스는 파드와 다른 포트 번호를 사용한다. 실제 많은 서비스에서, 이러한 값은 동일할 수 있다.

대상 파드에 영향을 미치는 네트워크 폴리시 인그레스 규칙이 있는가?

hostnames-* 파드로 들어오는 트래픽에 영향을 줄 수 있는 네트워크 폴리시 인그레스 규칙을 배포하는 경우, 이를 검토해야 한다.

자세한 내용은 Network Policies를 참고한다.

서비스가 DNS 이름으로 작동하는가?

클라이언트가 서비스를 사용하는 가장 일반적인 방법 중 하나는 DNS 이름을 통하는 것이다.

동일한 네임스페이스안의 파드 안에서, 다음을 실행한다.

nslookup hostnames
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      hostnames
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local

이것이 실패하면, 파드와 서비스가 다른 네임스페이스에 있는 경우일 수 있다. 네임스페이스를 지정한 이름(namespace-qualified name)을 사용해 본다(다시 말하지만, 파드 안에서 다음을 실행한다).

nslookup hostnames.default
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      hostnames.default
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local

이 방법이 성공했다면, 교차 네임스페이스 이름을 사용하기 위해 앱을 조정하거나, 또는 앱과 서비스를 동일한 네임스페이스에서 실행한다. 여전히 실패한다면, 완전히 지정된 이름(fully-qualified name)을 사용한다.

nslookup hostnames.default.svc.cluster.local
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      hostnames.default.svc.cluster.local
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local

여기서 접미사: "default.svc.cluster.local"에 유의한다. "default"는 작업 중인 네임스페이스이다. "svc"는 서비스임을 나타낸다. "cluster.local"은 클러스터 도메인을 나타내며, 클러스터마다 다를 수 있다.

동일한 작업을 클러스터의 노드에서 시도해 볼 수도 있다.

nslookup hostnames.default.svc.cluster.local 10.0.0.10
Server:         10.0.0.10
Address:        10.0.0.10#53

Name:   hostnames.default.svc.cluster.local
Address: 10.0.1.175

완전히 지정된 이름은 조회가 가능하지만 상대적인 이름은 조회를 할 수 없는 경우, 파드의 /etc/resolv.conf 파일이 올바른지 확인해야 한다. 파드 내에서 다음을 실행한다.

cat /etc/resolv.conf

다음과 같은 내용이 표시될 것이다.

nameserver 10.0.0.10
search default.svc.cluster.local svc.cluster.local cluster.local example.com
options ndots:5

nameserver 라인은 클러스터의 DNS 서비스를 나타내야 한다. 이는 --cluster-dns 플래그와 함께 kubelet으로 전달된다.

search 라인은 서비스 이름을 찾을 수 있는 적절한 접미사가 포함되어야 한다. 위 예시의 경우, ("default.svc.cluster.local" ->) 로컬 네임스페이스의 서비스, ("svc.cluster.local" ->) 모든 네임스페이스의 서비스, 마지막으로 클러스터 ("cluster.local" ->) 클러스터 범위에서 이름을 찾는다. 클러스터의 구성에 따라 그 뒤에 추가 레코드를 기입할 수도 있다(총 6개까지). 클러스터 접미사는 --cluster-domain 플래그와 함께 kubelet에 전달된다. 이 문서에서, 클러스터 접미사는 "cluster.local"로 간주된다. 당신의 클러스터가 다르게 구성되었을 수도 있으며, 이 경우 이전의 모든 명령어에서 이를 변경해야 한다.

options 라인의 ndots 값은 DNS 클라이언트 라이브러리가 모든 탐색 경우의 수를 고려할 수 있을 만큼 충분히 높게 설정되어야 한다. 쿠버네티스는 기본적으로 5로 설정하며, 이는 쿠버네티스가 생성하는 모든 DNS 이름을 포함할 수 있을 만큼 충분히 높다.

DNS 이름으로 동작하는 서비스가 하나라도 있는가?

위의 작업이 계속 실패하면, 서비스에 대해 DNS 조회가 작동하지 않는 것이다. 한 걸음 뒤로 물러나서 어떤 것이 작동하지 않는지 확인할 수 있다. 쿠버네티스 마스터 서비스는 항상 작동해야 한다. 파드 내에서 다음을 실행한다.

nslookup kubernetes.default
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes.default
Address 1: 10.0.0.1 kubernetes.default.svc.cluster.local

이것이 실패하면, 이 문서의 kube-proxy 섹션을 참고하거나 본 문서의 맨 위로 돌아가서 다시 시작한다. 단, 서비스를 디버깅하는 대신, DNS 서비스를 디버그한다.

서비스가 IP로 작동하는가?

DNS가 작동하는 것을 확인했다고 가정하면, 다음 테스트는 서비스가 IP 주소로 작동하는지 확인하는 것이다. 클러스터의 파드에서, 서비스의 IP에 액세스한다(위와 같이 kubectl get을 사용).

for i in $(seq 1 3); do 
    wget -qO- 10.0.1.175:80
done

이렇게 하면 다음과 같은 결과가 나타난다.

hostnames-632524106-bbpiw
hostnames-632524106-ly40y
hostnames-632524106-tlaok

서비스가 작동하는 경우, 올바른 응답을 받았을 것이다. 그렇지 않은 경우, 잘못되어 있을 수 있는 여러가지 사항이 있다. 아래의 내용을 계속 읽어 본다.

서비스가 올바르게 정의되었는가?

몇 차례 강조하지만, 서비스가 정확하게 기재되어 있고 파드의 포트와 일치하는지 두 번, 세 번 확인해야 한다. 아래의 명령을 실행하여 서비스를 다시 조회해 본다.

kubectl get service hostnames -o json
{
    "kind": "Service",
    "apiVersion": "v1",
    "metadata": {
        "name": "hostnames",
        "namespace": "default",
        "uid": "428c8b6c-24bc-11e5-936d-42010af0a9bc",
        "resourceVersion": "347189",
        "creationTimestamp": "2015-07-07T15:24:29Z",
        "labels": {
            "app": "hostnames"
        }
    },
    "spec": {
        "ports": [
            {
                "name": "default",
                "protocol": "TCP",
                "port": 80,
                "targetPort": 9376,
                "nodePort": 0
            }
        ],
        "selector": {
            "app": "hostnames"
        },
        "clusterIP": "10.0.1.175",
        "type": "ClusterIP",
        "sessionAffinity": "None"
    },
    "status": {
        "loadBalancer": {}
    }
}
  • 액세스하려는 서비스 포트가 spec.ports[]에 나열되어 있는가?
  • 파드에 대한 targetPort가 올바른가(일부 파드는 서비스와 다른 포트를 사용한다)?
  • 숫자 포트를 사용하려는 경우, 자료형이 숫자(9376)인가 문자열 "9376"인가?
  • 이름이 부여된 포트를 사용하려는 경우, 파드가 해당 이름의 포트를 노출하는가?
  • 포트의 'protocol'이 파드의 것과 일치하는가?

서비스에 엔드포인트가 있는가?

여기까지 왔다면, 서비스가 올바르게 정의되어 있고 DNS에 의해 해석될 수 있음을 확인한 것이다. 이제 실행 중인 파드가 서비스에 의해 실제로 선택되고 있는지 확인한다.

이전에 파드가 실행 중임을 확인했다. 다음 명령을 통해 다시 확인할 수 있다.

kubectl get pods -l app=hostnames
NAME                        READY     STATUS    RESTARTS   AGE
hostnames-632524106-bbpiw   1/1       Running   0          1h
hostnames-632524106-ly40y   1/1       Running   0          1h
hostnames-632524106-tlaok   1/1       Running   0          1h

-l app=hostnames 인수는 서비스에 구성된 레이블 셀렉터이다.

"AGE" 열은 파드가 실행된 지 약 1시간 되었다고 표시하는 것으로, 이는 파드가 제대로 실행되고 있으며 충돌하지 않음을 의미한다.

"RESTARTS" 열은 파드가 자주 충돌하지 않거나 다시 시작되지 않음을 나타낸다. 잦은 재시작은 간헐적인 연결 문제를 발생할 수 있다. 재시작 횟수가 높으면, 파드를 디버깅하는 방법에 대해 참고한다.

쿠버네티스 시스템 내부에는 모든 서비스의 셀렉터를 평가하고 그 결과를 해당 엔드포인트 오브젝트에 저장하는 제어 루프가 있다.

kubectl get endpoints hostnames

NAME        ENDPOINTS
hostnames   10.244.0.5:9376,10.244.0.6:9376,10.244.0.7:9376

위의 결과를 통해 엔드포인트 컨트롤러가 서비스에 대한 올바른 파드를 찾았음을 알 수 있다. ENDPOINTS 열이 <none>인 경우, 서비스의 spec.selector 필드가 파드의 metadata.labels 값을 실제로 선택하는지 확인해야 한다. 흔한 실수로, kubectl run 명령으로 디플로이먼트도 생성할 수 있었던 1.18 이전 버전에서, 서비스는 app=hostnames 를 이용하여 파드를 선택하지만 디플로이먼트는 run=hostnames 를 이용하여 파드를 선택하는 것과 같은 오타, 또는 기타 오류가 있을 수 있다.

파드가 작동하고 있는가?

여기까지 왔다면, 서비스가 존재하며 이 서비스가 파드를 선택하고 있는 것이다. 파드 자체에 대해서는 이 연습의 시작부분에서 확인했었다. 이제 파드가 실제로 작동하는지 다시 확인해 보자. 위에서 나열된 엔드포인트를 이용하여 서비스 메카니즘을 우회하고 파드에 직접 접근할 수 있다.

파드 내에서 다음을 실행한다.

for ep in 10.244.0.5:9376 10.244.0.6:9376 10.244.0.7:9376; do
    wget -qO- $ep
done

이렇게 하면 다음과 같은 결과가 나타난다.

hostnames-632524106-bbpiw
hostnames-632524106-ly40y
hostnames-632524106-tlaok

엔드포인트 목록의 각 파드가 호스트네임을 반환할 것이라고 예상할 수 있다. 파드가 호스트네임을 반환하지 않는다면 (또는 파드가 정상 동작을 하지 않는다면) 무슨 일이 일어나고 있는지 조사해야 한다.

kube-proxy가 작동하는가?

여기까지 진행했다면, 서비스가 실행 중이고, 엔드포인트가 있으며, 파드가 실제로 서빙하고 있는 것이다. 이 시점에서는, 전체 서비스 프록시 메커니즘이 의심된다. 하나씩 확인하기로 한다.

kube-proxy는 쿠버네티스 서비스의 기본 구현이며 대부분의 클러스터에서 사용하고 있다. kube-proxy는 모든 노드에서 실행되며, 서비스 추상화를 제공하기 위한 메커니즘 일부를 구성하는 프로그램이다. 사용자의 클러스터에서 kube-proxy를 사용하지 않는 경우, 다음 섹션이 적용되지 않으며, 사용 중인 서비스 구현에 대한 내용을 조사해야 할 것이다.

kube-proxy가 실행 중인가?

노드에서 kube-proxy가 실행 중인지 확인한다. 다음의 명령을 노드에서 직접 실행하면, 아래와 같은 결과를 얻을 수 있다.

ps auxw | grep kube-proxy
root  4194  0.4  0.1 101864 17696 ?    Sl Jul04  25:43 /usr/local/bin/kube-proxy --master=https://kubernetes-master --kubeconfig=/var/lib/kube-proxy/kubeconfig --v=2

다음으로, 반드시 되어야 하는 것(예: 마스터와 통신)이 잘 되는지를 확인한다. 이를 수행하려면, 로그를 확인한다. 로그에 액세스하는 방법은 노드의 OS 별로 다르다. 일부 OS에서는 /var/log/kube-proxy.log와 같은 파일이다. 반면 어떤 OS에서는 로그에 액세스하기 위해 journalctl을 사용한다. 다음과 같은 내용이 표시될 것이다.

I1027 22:14:53.995134    5063 server.go:200] Running in resource-only container "/kube-proxy"
I1027 22:14:53.998163    5063 server.go:247] Using iptables Proxier.
I1027 22:14:54.038140    5063 proxier.go:352] Setting endpoints for "kube-system/kube-dns:dns-tcp" to [10.244.1.3:53]
I1027 22:14:54.038164    5063 proxier.go:352] Setting endpoints for "kube-system/kube-dns:dns" to [10.244.1.3:53]
I1027 22:14:54.038209    5063 proxier.go:352] Setting endpoints for "default/kubernetes:https" to [10.240.0.2:443]
I1027 22:14:54.038238    5063 proxier.go:429] Not syncing iptables until Services and Endpoints have been received from master
I1027 22:14:54.040048    5063 proxier.go:294] Adding new service "default/kubernetes:https" at 10.0.0.1:443/TCP
I1027 22:14:54.040154    5063 proxier.go:294] Adding new service "kube-system/kube-dns:dns" at 10.0.0.10:53/UDP
I1027 22:14:54.040223    5063 proxier.go:294] Adding new service "kube-system/kube-dns:dns-tcp" at 10.0.0.10:53/TCP

마스터와 통신할 수 없다는 오류 메시지가 표시되면 노드 구성 및 설치 단계를 다시 확인해야 한다.

kube-proxy가 올바르게 실행되지 않는 이유 중 하나로 필수 conntrack 바이너리를 찾을 수 없는 경우가 있을 수 있다. 클러스터를 설치하는 방법(예, 사전에 준비 없이 쿠버네티스를 설치)에 따라 일부 리눅스 시스템에서 발생할 수 있다. 이 경우, conntrack 패키지를 수동으로 설치(예: 우분투에서 sudo apt install conntrack)한 다음 다시 시도해야 한다.

Kube-proxy는 몇 가지 모드 중 하나로 실행할 수 있다. 위에 나열된 로그에서, Using iptables Proxier 라인은 kube-proxy가 "iptables" 모드로 실행 중임을 나타낸다. 가장 일반적인 다른 모드는 "ipvs"이다.

Iptables 모드

"iptables" 모드일 때, 노드에서 다음 명령을 실행하면 아래와 같은 내용이 표시될 것이다.

iptables-save | grep hostnames
-A KUBE-SEP-57KPRZ3JQVENLNBR -s 10.244.3.6/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-57KPRZ3JQVENLNBR -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.3.6:9376
-A KUBE-SEP-WNBA2IHDGP2BOBGZ -s 10.244.1.7/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-WNBA2IHDGP2BOBGZ -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.1.7:9376
-A KUBE-SEP-X3P2623AGDH6CDF3 -s 10.244.2.3/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-X3P2623AGDH6CDF3 -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.2.3:9376
-A KUBE-SERVICES -d 10.0.1.175/32 -p tcp -m comment --comment "default/hostnames: cluster IP" -m tcp --dport 80 -j KUBE-SVC-NWV5X2332I4OT4T3
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-WNBA2IHDGP2BOBGZ
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-X3P2623AGDH6CDF3
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -j KUBE-SEP-57KPRZ3JQVENLNBR

각 서비스의 포트에 대해, KUBE-SERVICES 내의 하나의 규칙, 그리고 하나의 KUBE-SVC-<hash> 체인이 있어야 한다. 각 파드 엔드포인트에 대해, 해당 KUBE-SVC-<hash>에는 몇 개의 규칙이 있어야 하고, 또한 몇 개의 규칙을 포함하는 하나의 KUBE-SEP-<hash> 체인이 있어야 한다. 정확한 규칙은 사용자의 정확한 설정(노드 포트 및 로드 밸런서 포함)에 따라 다르다.

IPVS 모드

"ipvs" 모드일 때, 노드에서 다음 명령을 실행하면 아래와 같은 내용이 표시될 것이다.

ipvsadm -ln
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
...
TCP  10.0.1.175:80 rr
  -> 10.244.0.5:9376               Masq    1      0          0
  -> 10.244.0.6:9376               Masq    1      0          0
  -> 10.244.0.7:9376               Masq    1      0          0
...

각 서비스의 포트와 모든 NodePort, 외부 IP 및 로드 밸런서 IP에 대해 kube-proxy는 가상 서버를 생성한다. 각 파드 엔드포인트에 대해, kube-proxy는 각 가상 서버에 대응되는 실제 서버를 생성한다. 예제에서, 'hostnames' 서비스(10.0.1.175:80)에는 3개의 엔드포인트(10.244.0.5:9376, 10.244.0.6:9376, 10.244.0.7:9376)가 있다.

kube-proxy가 프록싱을 수행하고 있는가?

위에서 소개한 사례 중 하나를 마주했다고 가정한다면, 노드 중 하나에서 다음을 실행하여 서비스에 IP로 다시 액세스해 본다.

curl 10.0.1.175:80
hostnames-632524106-bbpiw

그래도 실패한다면, kube-proxy 로그에서 다음과 같은 특정 라인을 확인한다.

Setting endpoints for default/hostnames:default to [10.244.0.5:9376 10.244.0.6:9376 10.244.0.7:9376]

이러한 라인이 보이지 않으면, -v 플래그를 4로 설정하여 kube-proxy를 재시작한 다음 로그를 다시 살펴본다.

엣지 케이스: 파드가 서비스 IP를 통해 자신에게 도달하는 데 실패하는 경우

이러한 일이 일어날 것 같지 않을 수도 있지만 실제로 일어나기도 하며, 정상적이라면 제대로 동작해야 한다.

이러한 현상은 네트워크가 '헤어핀' 트래픽(클러스터 내부에서 U턴하는 트래픽)에 대해 제대로 구성되지 않은 경우에 발생할 수 있으며, 이는 주로 kube-proxyiptables 모드에서 실행되고 파드가 브릿지 네트워크에 연결된 경우에 해당할 수 있다. kubelet은 파드가 자신이 속한 서비스 VIP로 접근하는 경우 서비스의 엔드포인트가 이 트래픽을 다시 서비스의 파드로 로드밸런싱하도록 하는 'hairpin-mode[플래그](/docs/reference/command-line-tools-reference/kubelet/)를 노출한다.hairpin-mode플래그는hairpin-veth또는promiscuous-bridge`로 설정되어야 한다.

이 문제를 해결하기 위한 일반적인 단계는 다음과 같다.

  • hairpin-modehairpin-veth 또는 promiscuous-bridge로 설정되어 있는지 확인한다. 아래와 같은 내용이 표시되어야 한다. 아래 예시에서 hairpin-modepromiscuous-bridge로 설정되어 있다.
ps auxw | grep kubelet
root      3392  1.1  0.8 186804 65208 ?        Sl   00:51  11:11 /usr/local/bin/kubelet --enable-debugging-handlers=true --config=/etc/kubernetes/manifests --allow-privileged=True --v=4 --cluster-dns=10.0.0.10 --cluster-domain=cluster.local --configure-cbr0=true --cgroup-root=/ --system-cgroups=/system --hairpin-mode=promiscuous-bridge --runtime-cgroups=/docker-daemon --kubelet-cgroups=/kubelet --babysit-daemons=true --max-pods=110 --serialize-image-pulls=false --outofdisk-transition-frequency=0
  • 실제로 적용되어 있는 hairpin-mode가 무엇인지 확인한다. 이를 확인하려면, kubelet 로그를 확인해야 한다. 로그 액세스는 노드 OS에 따라 다르다. 일부 OS에서는 /var/log/kubelet.log와 같은 파일인 반면 다른 OS에서는 journalctl을 사용하여 로그에 액세스한다. 실제로 적용되어 있는 hairpin 모드는 호환성으로 인해 --hairpin-mode 플래그와 일치하지 않을 수 있으므로 주의한다. kubelet.log에 hairpin 이라는 키워드가 포함된 로그 라인이 있는지 확인한다. 아래와 같이 실행 중인 헤어핀 모드를 나타내는 로그 라인이 있을 것이다.
I0629 00:51:43.648698    3252 kubelet.go:380] Hairpin mode set to "promiscuous-bridge"
  • 실행 중인 hairpin 모드가 hairpin-veth인 경우, kubelet이 노드의 /sys에서 작동할 수 있는 권한이 있는지 확인한다. 모든 것이 제대로 작동한다면, 다음 명령을 실행했을 때 아래와 같이 표시될 것이다.
for intf in /sys/devices/virtual/net/cbr0/brif/*; do cat $intf/hairpin_mode; done
1
1
1
1
  • 실행 중인 hairpin 모드가 promiscuous-bridge인 경우, Kubelet이 노드 안에서 리눅스 브릿지를 조작할 수 있는 권한이 있는지 확인한다. cbr0 브릿지가 사용되었고 제대로 구성되어 있다면, 다음 명령을 실행했을 때 아래와 같이 표시될 것이다.
ifconfig cbr0 |grep PROMISC
UP BROADCAST RUNNING PROMISC MULTICAST  MTU:1460  Metric:1
  • 위의 방법 중 어느 것으로도 해결되지 않는다면, 도움을 요청한다.

도움 요청하기

여기까지 왔다면, 아주 이상한 일이 발생하고 있는 것이다. 서비스가 실행 중이고, 엔드포인트가 있으며, 파드가 실제로 서빙하고 있는 상태이다. DNS가 작동 중이고, kube-proxy가 오작동하는 것은 아닌 것 같다. 그럼에도 서비스가 동작하지 않는 것이다. 우리가 도울 수 있도록, 어떤 일이 일어나고 있는지 커뮤니티에 알려주세요!

Slack 또는 포럼 또는 GitHub 에 문의한다.

다음 내용

자세한 내용은 트러블슈팅하기 개요 문서 를 참고한다.

1.3 - 스테이트풀셋 디버깅하기

이 문서에서는 스테이트풀셋을 디버깅 방법에 대해 설명한다.

시작하기 전에

  • 쿠버네티스 클러스터가 준비되어 있어야 하고, kubectl 커맨드 라인 도구가 클러스터와 통신할 수 있게 사전에 설정되어 있어야 한다.
  • 조사하고자 하는 스테이트풀셋이 사전에 준비되어 있어야 한다.

스테이트풀셋 디버깅하기

레이블이 app.kubernetes.io/name=MyApp으로 지정된 스테이트풀셋 파드를 전부 나열하기 위해서는 다음의 명령을 사용할 수 있다.

kubectl get pods -l app.kubernetes.io/name=MyApp

만약 오랜 시간동안 Unknown이나 Terminating 상태에 있는 파드들을 발견하였다면, 이러한 파드들을 어떻게 다루는지 알아보기 위해 스테이트풀셋 파드 삭제하기를 참고하길 바란다. 스테이트풀셋에 포함된 개별 파드들을 디버깅하기 위해서는 파드 디버그하기 가이드를 참고하길 바란다.

다음 내용

초기화 컨테이너(Init container) 디버그하기를 참고하길 바란다.

1.4 - 파드 실패의 원인 검증하기

이 페이지는 컨테이너 종료 메시지를 읽고 쓰는 방법을 보여준다.

종료 메시지는 컨테이너가 치명적인 이벤트에 대한 정보를, 대시보드나 모니터링 소프트웨어 도구와 같이 쉽게 조회 및 표시할 수 있는 위치에 기록하는 방법을 제공한다. 대부분의 경우에 종료 메시지에 넣는 정보는 일반적으로 쿠버네티스 로그에도 쓰여져야 한다.

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

종료 메시지 읽기 및 쓰기

이 예제에서는, 하나의 컨테이너를 실행하는 파드를 생성한다. 파드의 매니페스트는 컨테이너가 시작될 때 수행하는 명령어를 지정한다.

apiVersion: v1
kind: Pod
metadata:
  name: termination-demo
spec:
  containers:
  - name: termination-demo-container
    image: debian
    command: ["/bin/sh"]
    args: ["-c", "sleep 10 && echo Sleep expired > /dev/termination-log"]
  1. 다음의 YAML 설정 파일에 기반한 파드를 생성한다.

    kubectl apply -f https://k8s.io/examples/debug/termination.yaml
    

    YAML 파일에 있는 commandargs 필드에서 컨테이너가 10초 간 잠든 뒤에 "Sleep expired" 문자열을 /dev/termination-log 파일에 기록하는 것을 확인할 수 있다. 컨테이너는 "Sleep expired" 메시지를 기록한 후에 종료된다.

  2. 파드와 관련된 정보를 출력한다.

    kubectl get pod termination-demo
    

    파드가 더 이상 실행되지 않을 때까지 앞선 명령어를 반복한다.

  3. 파드에 관한 상세 정보를 출력한다.

    kubectl get pod termination-demo --output=yaml
    

    결과는 "Sleep expired" 메시지를 포함한다.

    apiVersion: v1
    kind: Pod
    ...
        lastState:
          terminated:
            containerID: ...
            exitCode: 0
            finishedAt: ...
            message: |
              Sleep expired          
            ...
    
  4. 종료 메시지만을 포함하는 출력 결과를 보기 위해서는 Go 템플릿을 사용한다.

    kubectl get pod termination-demo -o go-template="{{range .status.containerStatuses}}{{.lastState.terminated.message}}{{end}}"
    

여러 컨테이너를 포함하는 파드의 경우, Go 템플릿을 사용하여 컨테이너 이름도 출력할 수 있다. 이렇게 하여, 어떤 컨테이너가 실패하는지 찾을 수 있다.

kubectl get pod multi-container-pod -o go-template='{{range .status.containerStatuses}}{{printf "%s:\n%s\n\n" .name .lastState.terminated.message}}{{end}}'

종료 메시지 사용자 정의하기

쿠버네티스는 컨테이너의 terminationMessagePath 필드에 지정된 종료 메시지 파일에서 종료 메시지를 검색하며, 이 필드의 기본값은 /dev/termination-log 이다. 이 필드를 사용자 정의 함으로써 쿠버네티스가 종료 메시지를 검색할 때 다른 파일을 사용하도록 조정할 수 있다. 쿠버네티스는 지정된 파일의 내용을 사용하여 컨테이너의 성공 및 실패에 대한 상태 메시지를 채운다.

종료 메시지는 assertion failure 메세지처럼 간결한 최종 상태로 생성된다. kubelet은 4096 바이트보다 긴 메시지를 자른다.

모든 컨테이너의 총 메시지 길이는 12KiB로 제한되며, 각 컨테이너에 균등하게 분할된다. 예를 들어, 12개의 컨테이너(initContainers 또는 containers)가 있는 경우 각 컨테이너에는 1024 바이트의 사용 가능한 종료 메시지 공간이 있다.

기본 종료 메시지 경로는 /dev/termination-log이다. 파드가 시작된 후에는 종료 메시지 경로를 설정할 수 없다.

다음의 예제에서 컨테이너는, 쿠버네티스가 조회할 수 있도록 /tmp/my-log 파일에 종료 메시지를 기록한다.

apiVersion: v1
kind: Pod
metadata:
  name: msg-path-demo
spec:
  containers:
  - name: msg-path-demo-container
    image: debian
    terminationMessagePath: "/tmp/my-log"

또한 사용자는 추가적인 사용자 정의를 위해 컨테이너의 terminationMessagePolicy 필드를 설정할 수 있다. 이 필드의 기본 값은 File 이며, 이는 오직 종료 메시지 파일에서만 종료 메시지가 조회되는 것을 의미한다. terminationMessagePolicy 필드의 값을 "FallbackToLogsOnError 으로 설정함으로써, 종료 메시지 파일이 비어 있고 컨테이너가 오류와 함께 종료 되었을 경우 쿠버네티스가 컨테이너 로그 출력의 마지막 청크를 사용하도록 지시할 수 있다. 로그 출력은 2048 바이트나 80 행 중 더 작은 값으로 제한된다.

다음 내용

1.5 - 초기화 컨테이너(Init Containers) 디버그하기

이 페이지는 초기화 컨테이너의 실행과 관련된 문제를 조사하는 방법에 대해 보여준다. 아래 예제의 커맨드 라인은 파드(Pod)를 <pod-name> 으로, 초기화 컨테이너를 <init-container-1><init-container-2> 로 표시한다.

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

버전 확인을 위해서, 다음 커맨드를 실행 kubectl version.

초기화 컨테이너의 상태 체크하기

사용자 파드의 상태를 표시한다.

kubectl get pod <pod-name>

예를 들어, Init:1/2 상태는 두 개의 초기화 컨테이너 중 하나가 성공적으로 완료되었음을 나타낸다.

NAME         READY     STATUS     RESTARTS   AGE
<pod-name>   0/1       Init:1/2   0          7s

상태값과 그 의미에 대한 추가 예제는 파드 상태 이해하기를 참조한다.

초기화 컨테이너에 대한 상세 정보 조회하기

초기화 컨테이너의 실행에 대한 상세 정보를 확인한다.

kubectl describe pod <pod-name>

예를 들어, 2개의 초기화 컨테이너가 있는 파드는 다음과 같이 표시될 수 있다.

Init Containers:
  <init-container-1>:
    Container ID:    ...
    ...
    State:           Terminated
      Reason:        Completed
      Exit Code:     0
      Started:       ...
      Finished:      ...
    Ready:           True
    Restart Count:   0
    ...
  <init-container-2>:
    Container ID:    ...
    ...
    State:           Waiting
      Reason:        CrashLoopBackOff
    Last State:      Terminated
      Reason:        Error
      Exit Code:     1
      Started:       ...
      Finished:      ...
    Ready:           False
    Restart Count:   3
    ...

파드 스펙의 status.initContainerStatuses 필드를 읽어서 프로그래밍 방식으로 초기화 컨테이너의 상태를 조회할 수도 있다.

kubectl get pod nginx --template '{{.status.initContainerStatuses}}'

이 명령은 원시 JSON 방식으로 위와 동일한 정보를 반환한다.

초기화 컨테이너의 로그 조회하기

초기화 컨테이너의 로그를 확인하기 위해 파드의 이름과 초기화 컨테이너의 이름을 같이 전달한다.

kubectl logs <pod-name> -c <init-container-2>

셸 스크립트를 실행하는 초기화 컨테이너는, 초기화 컨테이너가 실행될 때 명령어를 출력한다. 예를 들어, 스크립트의 시작 부분에 set -x 를 추가하고 실행하여 Bash에서 명령어를 출력할 수 있도록 수행할 수 있다.

파드의 상태 이해하기

Init: 으로 시작하는 파드 상태는 초기화 컨테이너의 실행 상태를 요약한다. 아래 표는 초기화 컨테이너를 디버깅하는 동안 사용자가 확인할 수 있는 몇 가지 상태값의 예이다.

상태의미
Init:N/M파드가 M 개의 초기화 컨테이너를 갖고 있으며, 현재까지 N 개가 완료.
Init:Error초기화 컨테이너 실행 실패.
Init:CrashLoopBackOff초기화 컨테이너가 반복적으로 실행 실패.
Pending파드가 아직 초기화 컨테이너를 실행하지 않음.
PodInitializing or Running파드가 이미 초기화 컨테이너 실행을 완료.

1.6 - 동작 중인 파드 디버그

이 페이지는 노드에서 동작 중인(혹은 크래시된) 파드를 디버그하는 방법에 대해 설명한다.

시작하기 전에

  • 여러분의 파드는 이미 스케줄링 되어 동작하고 있을 것이다. 만약 파드가 아직 동작중이지 않다면, 애플리케이션 트러블슈팅을 참고한다.
  • 일부 고급 디버깅 과정에서는 해당 파드가 어떤 노드에서 동작하고 있는지 알아야 하고, 해당 노드에서 쉘 명령어를 실행시킬 수 있어야 한다. kubectl을 사용하는 일반적인 디버깅 과정에서는 이러한 접근 권한이 필요하지 않다.

kubectl describe pod 명령으로 파드 상세사항 가져오기

이 예제에서는 앞의 예제와 비슷하게 두 개의 파드를 생성하기 위해 디플로이먼트를 사용할 것이다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        resources:
          limits:
            memory: "128Mi"
            cpu: "500m"
        ports:
        - containerPort: 80

다음 명령을 실행하여 디플로이먼트를 생성한다.

kubectl apply -f https://k8s.io/examples/application/nginx-with-request.yaml
deployment.apps/nginx-deployment created

다음 명령을 실행하여 파드 상태를 확인한다.

kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-67d4bdd6f5-cx2nz   1/1     Running   0          13s
nginx-deployment-67d4bdd6f5-w6kd7   1/1     Running   0          13s

다음과 같이 kubectl describe pod 명령을 사용하여 각 파드에 대한 더 많은 정보를 가져올 수 있다.

kubectl describe pod nginx-deployment-67d4bdd6f5-w6kd7
Name:         nginx-deployment-67d4bdd6f5-w6kd7
Namespace:    default
Priority:     0
Node:         kube-worker-1/192.168.0.113
Start Time:   Thu, 17 Feb 2022 16:51:01 -0500
Labels:       app=nginx
              pod-template-hash=67d4bdd6f5
Annotations:  <none>
Status:       Running
IP:           10.88.0.3
IPs:
  IP:           10.88.0.3
  IP:           2001:db8::1
Controlled By:  ReplicaSet/nginx-deployment-67d4bdd6f5
Containers:
  nginx:
    Container ID:   containerd://5403af59a2b46ee5a23fb0ae4b1e077f7ca5c5fb7af16e1ab21c00e0e616462a
    Image:          nginx
    Image ID:       docker.io/library/nginx@sha256:2834dc507516af02784808c5f48b7cbe38b8ed5d0f4837f16e78d00deb7e7767
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Thu, 17 Feb 2022 16:51:05 -0500
    Ready:          True
    Restart Count:  0
    Limits:
      cpu:     500m
      memory:  128Mi
    Requests:
      cpu:        500m
      memory:     128Mi
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-bgsgp (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  kube-api-access-bgsgp:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   Guaranteed
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  34s   default-scheduler  Successfully assigned default/nginx-deployment-67d4bdd6f5-w6kd7 to kube-worker-1
  Normal  Pulling    31s   kubelet            Pulling image "nginx"
  Normal  Pulled     30s   kubelet            Successfully pulled image "nginx" in 1.146417389s
  Normal  Created    30s   kubelet            Created container nginx
  Normal  Started    30s   kubelet            Started container nginx

위 예시에서 컨테이너와 파드에 대한 구성 정보(레이블, 리소스 요구사항 등) 및 상태 정보(상태(state), 준비성(readiness), 재시작 횟수, 이벤트 등)를 볼 수 있다.

컨테이너의 상태(state)값은 Waiting, Running, 또는 Terminated 중 하나이다. 각 상태에 따라, 추가 정보가 제공될 것이다. 위 예시에서 Running 상태의 컨테이너에 대해서는 컨테이너의 시작 시각을 시스템이 표시해 주는 것을 볼 수 있다.

Ready 값은 컨테이너의 마지막 준비성 프로브(readiness probe) 통과 여부를 알려 준다. (위 예시에서는 컨테이너에 준비성 프로브가 설정되어 있지 않다. 컨테이너에 준비성 프로브가 설정되어 있지 않으면, 컨테이너는 준비(ready) 상태로 간주된다.)

'재시작 카운트'는 컨테이너가 재시작된 횟수를 보여 준다. 이 정보는 재시작 정책이 'always'로 설정된 컨테이너의 반복적인 강제 종료를 알아차리는 데에 유용하다.

위 예시에서 파드와 연관된 유일한 컨디션(Condition)은 True 또는 False 값을 갖는 Ready 컨디션이며, 이 값이 True라는 것은 파드가 요청을 처리할 수 있으며 모든 동일한 서비스를 묶는 로드 밸런싱 풀에 추가되어야 함을 의미한다.

마지막으로, 파드와 관련된 최근 이벤트 로그가 표시된다. 시스템은 동일한 여러 이벤트를 처음/마지막 발생 시간 및 발생 횟수만 압축적으로 표시한다. "From"은 이벤트 로그를 발생하는 구성 요소를 가리키고, "SubobjectPath"는 참조되는 개체(예: 파드 내 컨테이너)를 나타내며, "Reason" 및 "Message"는 발생한 상황을 알려 준다.

예시: Pending 상태의 파드 디버깅하기

이벤트를 사용하여 감지할 수 있는 일반적인 시나리오는 노드에 할당될 수 없는 파드를 생성하는 경우이다. 예를 들어 파드가 노드에 사용 가능한 리소스보다 더 많은 리소스를 요청하거나, 또는 어떤 노드에도 해당되지 않는 레이블 셀렉터를 명시했을 수 있다. 예를 들어 4개 노드로 구성되며 각 (가상) 머신에 1 CPU가 있는 클러스터가 있는 상황에서, 위 예시 대신 2 레플리카가 아니라 5 레플리카를, 500 밀리코어가 아니라 600 밀리코어를 요청하는 디플로이먼트를 배포했다고 해 보자. 이러한 경우 5개의 파드 중 하나는 스케줄링될 수 없을 것이다. (각 노드에는 fluentd, skydns 등의 클러스터 애드온도 실행되고 있으므로, 만약 1000 밀리코어를 요청했다면 파드가 하나도 스케줄될 수 없었을 것이다.)

kubectl get pods
NAME                                READY     STATUS    RESTARTS   AGE
nginx-deployment-1006230814-6winp   1/1       Running   0          7m
nginx-deployment-1006230814-fmgu3   1/1       Running   0          7m
nginx-deployment-1370807587-6ekbw   1/1       Running   0          1m
nginx-deployment-1370807587-fg172   0/1       Pending   0          1m
nginx-deployment-1370807587-fz9sd   0/1       Pending   0          1m

nginx-deployment-1370807587-fz9sd 파드가 왜 실행되지 않는지를 알아 보려면, pending 상태의 파드에 대해 kubectl describe pod 명령을 실행하고 이벤트(event) 항목을 확인해 볼 수 있다.

kubectl describe pod nginx-deployment-1370807587-fz9sd
  Name:		nginx-deployment-1370807587-fz9sd
  Namespace:	default
  Node:		/
  Labels:		app=nginx,pod-template-hash=1370807587
  Status:		Pending
  IP:
  Controllers:	ReplicaSet/nginx-deployment-1370807587
  Containers:
    nginx:
      Image:	nginx
      Port:	80/TCP
      QoS Tier:
        memory:	Guaranteed
        cpu:	Guaranteed
      Limits:
        cpu:	1
        memory:	128Mi
      Requests:
        cpu:	1
        memory:	128Mi
      Environment Variables:
  Volumes:
    default-token-4bcbi:
      Type:	Secret (a volume populated by a Secret)
      SecretName:	default-token-4bcbi
  Events:
    FirstSeen	LastSeen	Count	From			        SubobjectPath	Type		Reason			    Message
    ---------	--------	-----	----			        -------------	--------	------			    -------
    1m		    48s		    7	    {default-scheduler }			        Warning		FailedScheduling	pod (nginx-deployment-1370807587-fz9sd) failed to fit in any node
  fit failure on node (kubernetes-node-6ta5): Node didn't have enough resource: CPU, requested: 1000, used: 1420, capacity: 2000
  fit failure on node (kubernetes-node-wul5): Node didn't have enough resource: CPU, requested: 1000, used: 1100, capacity: 2000

여기서 스케줄러가 기록한 이벤트를 통해, 파드가 FailedScheduling 사유로 인해 스케줄링되지 않았음을 알 수 있다(다른 이유도 있을 수 있음). 이 메시지를 통해 어떤 노드에도 이 파드를 실행하기 위한 충분한 리소스가 없었음을 알 수 있다.

이 상황을 바로잡으려면, kubectl scale 명령으로 디플로이먼트의 레플리카를 4 이하로 줄일 수 있다. (또는 한 파드를 pending 상태로 두어도 되며, 이렇게 해도 문제는 없다.)

kubectl describe pod 출력의 마지막에 있는 것과 같은 이벤트는 etcd에 기록되어 보존되며 클러스터에 어떤 일이 일어나고 있는지에 대한 높은 차원의 정보를 제공한다. 모든 이벤트의 목록을 보려면 다음 명령을 실행한다.

kubectl get events

그런데 이벤트는 네임스페이스 스코프 객체라는 것을 기억해야 한다. 즉 네임스페이스 스코프 객체에 대한 이벤트(예: my-namespace 네임스페이스의 파드에 어떤 일이 발생했는지)가 궁금하다면, 다음과 같이 커맨드에 네임스페이스를 명시해야 한다.

kubectl get events --namespace=my-namespace

모든 네임스페이스에 대한 이벤트를 보려면, --all-namespaces 인자를 사용할 수 있다.

kubectl describe pod 명령 외에도, kubectl get pod 이상의 정보를 얻는 다른 방법은 kubectl get pod 명령에 출력 형식 플래그 -o yaml 인자를 추가하는 것이다. 이렇게 하면 kubectl describe pod 명령보다 더 많은 정보, 원천적으로는 시스템이 파드에 대해 알고 있는 모든 정보를 YAML 형식으로 볼 수 있다. 여기서 어노테이션(레이블 제한이 없는 키-밸류 메타데이터이며, 쿠버네티스 시스템 구성 요소가 내부적으로 사용함), 재시작 정책, 포트, 볼륨과 같은 정보를 볼 수 있을 것이다.

kubectl get pod nginx-deployment-1006230814-6winp -o yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2022-02-17T21:51:01Z"
  generateName: nginx-deployment-67d4bdd6f5-
  labels:
    app: nginx
    pod-template-hash: 67d4bdd6f5
  name: nginx-deployment-67d4bdd6f5-w6kd7
  namespace: default
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: nginx-deployment-67d4bdd6f5
    uid: 7d41dfd4-84c0-4be4-88ab-cedbe626ad82
  resourceVersion: "1364"
  uid: a6501da1-0447-4262-98eb-c03d4002222e
spec:
  containers:
  - image: nginx
    imagePullPolicy: Always
    name: nginx
    ports:
    - containerPort: 80
      protocol: TCP
    resources:
      limits:
        cpu: 500m
        memory: 128Mi
      requests:
        cpu: 500m
        memory: 128Mi
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-bgsgp
      readOnly: true
  dnsPolicy: ClusterFirst
  enableServiceLinks: true
  nodeName: kube-worker-1
  preemptionPolicy: PreemptLowerPriority
  priority: 0
  restartPolicy: Always
  schedulerName: default-scheduler
  securityContext: {}
  serviceAccount: default
  serviceAccountName: default
  terminationGracePeriodSeconds: 30
  tolerations:
  - effect: NoExecute
    key: node.kubernetes.io/not-ready
    operator: Exists
    tolerationSeconds: 300
  - effect: NoExecute
    key: node.kubernetes.io/unreachable
    operator: Exists
    tolerationSeconds: 300
  volumes:
  - name: kube-api-access-bgsgp
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2022-02-17T21:51:01Z"
    status: "True"
    type: Initialized
  - lastProbeTime: null
    lastTransitionTime: "2022-02-17T21:51:06Z"
    status: "True"
    type: Ready
  - lastProbeTime: null
    lastTransitionTime: "2022-02-17T21:51:06Z"
    status: "True"
    type: ContainersReady
  - lastProbeTime: null
    lastTransitionTime: "2022-02-17T21:51:01Z"
    status: "True"
    type: PodScheduled
  containerStatuses:
  - containerID: containerd://5403af59a2b46ee5a23fb0ae4b1e077f7ca5c5fb7af16e1ab21c00e0e616462a
    image: docker.io/library/nginx:latest
    imageID: docker.io/library/nginx@sha256:2834dc507516af02784808c5f48b7cbe38b8ed5d0f4837f16e78d00deb7e7767
    lastState: {}
    name: nginx
    ready: true
    restartCount: 0
    started: true
    state:
      running:
        startedAt: "2022-02-17T21:51:05Z"
  hostIP: 192.168.0.113
  phase: Running
  podIP: 10.88.0.3
  podIPs:
  - ip: 10.88.0.3
  - ip: 2001:db8::1
  qosClass: Guaranteed
  startTime: "2022-02-17T21:51:01Z"

파드의 로그 확인하기

먼저, 확인하고자 하는 컨테이너의 로그를 확인한다.

kubectl logs ${POD_NAME} ${CONTAINER_NAME}

만약 컨테이너가 이전에 크래시 되었다면, 다음의 명령을 통해 컨테이너의 크래시 로그를 살펴볼 수 있다.

kubectl logs --previous ${POD_NAME} ${CONTAINER_NAME}

exec를 통해 컨테이너 디버깅하기

만약 컨테이너 이미지에 디버깅 도구가 포함되어 있다면, kubectl exec을 통해 특정 컨테이너에서 해당 명령들을 실행할 수 있다. (리눅스나 윈도우 OS를 기반으로 만들어진 이미지에는 대부분 디버깅 도구를 포함하고 있다.)

kubectl exec ${POD_NAME} -c ${CONTAINER_NAME} -- ${CMD} ${ARG1} ${ARG2} ... ${ARGN}

예를 들어, 동작 중인 카산드라 파드의 로그를 살펴보기 위해서는 다음과 같은 명령을 실행할 수 있다.

kubectl exec cassandra -- cat /var/log/cassandra/system.log

kubectl exec-i-t 옵션을 사용해서 터미널에서 접근할 수 있는 쉘을 실행시킬 수도 있다. 예를 들면 다음과 같다.

kubectl exec -it cassandra -- sh

더욱 상세한 내용은 동작중인 컨테이너의 쉘에 접근하기를 참고한다.

임시(ephemeral) 디버그 컨테이너를 사용해서 디버깅하기

기능 상태: Kubernetes v1.25 [stable]

컨테이너가 크래시 됐거나 distroless 이미지처럼 컨테이너 이미지에 디버깅 도구를 포함하고 있지 않아 kubectl exec로는 충분하지 않은 경우에는 임시(Ephemeral) 컨테이너를 사용하는 것이 인터랙티브한 트러블슈팅에 유용하다.

임시 컨테이너를 사용한 디버깅 예시

kubectl debug 명령어를 사용해서 동작 중인 파드에 임시 컨테이너를 추가할 수 있다. 먼저, 다음과 같이 파드를 추가한다.

kubectl run ephemeral-demo --image=registry.k8s.io/pause:3.1 --restart=Never

이 섹션의 예시에서는 디버깅 도구가 포함되지 않은 이미지의 사례를 보여드리기 위해 pause 컨테이너 이미지를 사용했는데, 이 대신 어떠한 이미지를 사용해도 될 것이다.

만약 kubectl exec을 통해 쉘을 생성하려 한다면 다음과 같은 에러를 확인할 수 있을 텐데, 그 이유는 이 이미지에 쉘이 존재하지 않기 때문이다.

kubectl exec -it ephemeral-demo -- sh
OCI runtime exec failed: exec failed: container_linux.go:346: starting container process caused "exec: \"sh\": executable file not found in $PATH": unknown

이 명령어 대신 kubectl debug을 사용해서 디버깅 컨테이너를 생성할 수 있다. 만약 -i/--interactive 인자를 사용한다면, kubectl은 임시 컨테이너의 콘솔에 자동으로 연결할 것이다.

kubectl debug -it ephemeral-demo --image=busybox --target=ephemeral-demo
Defaulting debug container name to debugger-8xzrl.
If you don't see a command prompt, try pressing enter.
/ #

이 명령어는 새로운 busybox 컨테이너를 추가하고 해당 컨테이너로 연결한다. --target 파라미터를 사용하면 다른 컨테이너의 프로세스 네임스페이스를 대상으로 하게 된다. 여기서는 이 옵션이 꼭 필요한데, kubectl run이 생성하는 파드에 대해 프로세스 네임스페이스 공유를 활성화하지 않기 때문이다.

kubectl describe 명령을 사용하면 새롭게 생성된 임시 컨테이너의 상태를 확인할 수 있다.

kubectl describe pod ephemeral-demo
...
Ephemeral Containers:
  debugger-8xzrl:
    Container ID:   docker://b888f9adfd15bd5739fefaa39e1df4dd3c617b9902082b1cfdc29c4028ffb2eb
    Image:          busybox
    Image ID:       docker-pullable://busybox@sha256:1828edd60c5efd34b2bf5dd3282ec0cc04d47b2ff9caa0b6d4f07a21d1c08084
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Wed, 12 Feb 2020 14:25:42 +0100
    Ready:          False
    Restart Count:  0
    Environment:    <none>
    Mounts:         <none>
...

디버깅이 다 끝나면 kubectl delete을 통해 파드를 제거할 수 있다.

kubectl delete pod ephemeral-demo

파드의 복제본을 이용해서 디버깅하기

때때로 파드의 설정 옵션에 따라 특정 상황에서 트러블슈팅을 하기가 어려울 수 있다. 예를 들어, 만일 여러분의 컨테이너 이미지가 쉘을 포함하고 있지 않거나, 여러분의 애플리케이션이 컨테이너 시작에서 크래시가 발생한다면 kubectl exec을 이용해서 컨테이너를 트러블슈팅할 수 없을 수 있다. 이러한 상황에서는 kubectl debug을 사용해서 파드의 복제본을 디버깅을 위한 추가적인 설정 옵션과 함께 생성할 수 있다.

새 컨테이너와 함께 파드의 복제본 생성하기

만일 여러분의 애플리케이션이 동작은 하고 있지만 예상과는 다르게 동작하는 경우, 파드의 복제본에 새로운 컨테이너를 추가함으로써 추가적인 트러블슈팅 도구들을 파드에 함께 추가할 수 있다.

가령, 여러분의 애플리케이션 컨테이너 이미지는 busybox를 기반으로 하고 있는데 여러분은 busybox에는 없는 디버깅 도구를 필요로 한다고 가정해 보자. 이러한 시나리오는 kubectl run 명령을 통해 시뮬레이션 해볼 수 있다.

kubectl run myapp --image=busybox --restart=Never -- sleep 1d

다음의 명령을 실행시켜 디버깅을 위한 새로운 우분투 컨테이너와 함께 myapp-debug이란 이름의 myapp 컨테이너 복제본을 생성할 수 있다.

kubectl debug myapp -it --image=ubuntu --share-processes --copy-to=myapp-debug
Defaulting debug container name to debugger-w7xmf.
If you don't see a command prompt, try pressing enter.
root@myapp-debug:/#

사용이 모두 끝나면, 디버깅에 사용된 파드를 잊지 말고 정리한다.

kubectl delete pod myapp myapp-debug

명령어를 변경하며 파드의 복제본 생성하기

때때로 컨테이너의 명령어를 변경하는 것이 유용한 경우가 있는데, 예를 들면 디버그 플래그를 추가하기 위해서나 애플리케이션이 크래시 되는 경우이다.

다음의 kubectl run 명령을 통해 즉각적으로 크래시가 발생하는 애플리케이션의 사례를 시뮬레이션해 볼 수 있다.

kubectl run --image=busybox myapp -- false

kubectl describe pod myapp 명령을 통해 이 컨테이너에 크래시가 발생하고 있음을 확인할 수 있다.

Containers:
  myapp:
    Image:         busybox
    ...
    Args:
      false
    State:          Waiting
      Reason:       CrashLoopBackOff
    Last State:     Terminated
      Reason:       Error
      Exit Code:    1

이러한 경우에 kubectl debug을 통해 명령어를 지정함으로써 해당 파드의 복제본을 인터랙티브 쉘로 생성할 수 있다.

kubectl debug myapp -it --copy-to=myapp-debug --container=myapp -- sh
If you don't see a command prompt, try pressing enter.
/ #

이제 인터랙티브 쉘에 접근할 수 있으니 파일 시스템 경로를 확인하거나 동작 중인 컨테이너의 명령어를 직접 확인하는 등의 작업이 가능하다.

사용이 모두 끝나면, 디버깅에 사용된 파드들을 잊지 말고 정리한다.

kubectl delete pod myapp myapp-debug

컨테이너 이미지를 변경하며 파드의 복제본 생성하기

특정한 경우에 여러분은 제대로 동작하지 않는 파드의 이미지를 기존 프로덕션 컨테이너 이미지에서 디버깅 빌드나 추가적인 도구를 포함한 이미지로 변경하고 싶을 수 있다.

이 사례를 보여주기 위해 kubectl run 명령을 통해 파드를 생성하였다.

kubectl run myapp --image=busybox --restart=Never -- sleep 1d

여기서는 kubectl debug 명령을 통해 해당 컨테이너 이미지를 ubuntu로 변경하며 복제본을 생성하였다.

kubectl debug myapp --copy-to=myapp-debug --set-image=*=ubuntu

--set-image의 문법은 kubectl set image와 동일하게 container_name=image 형식의 문법을 사용한다. *=ubuntu라는 의미는 모든 컨테이너의 이미지를 ubuntu로 변경하겠다는 의미이다.

사용이 모두 끝나면, 디버깅에 사용된 파드를 잊지 말고 정리한다.

kubectl delete pod myapp myapp-debug

노드의 쉘을 사용해서 디버깅하기

만약 위의 어떠한 방법도 사용할 수 없다면, 파드가 현재 동작 중인 노드를 찾아 해당 노드에서 실행되는 파드를 생성할 수 있다. 다음 kubectl debug 명령을 통해 해당 노드에서 인터랙티브한 쉘을 생성할 수 있다.

kubectl debug node/mynode -it --image=ubuntu
Creating debugging pod node-debugger-mynode-pdx84 with container debugger on node mynode.
If you don't see a command prompt, try pressing enter.
root@ek8s:/#

노드에서 디버깅 세션을 생성할 때 유의해야 할 점은 다음과 같다.

  • kubectl debug는 노드의 이름에 기반해 새로운 파드의 이름을 자동으로 생성한다.
  • 노드의 루트 파일시스템은 /host에 마운트된다.
  • 파드가 특권을 가지고 있지 않더라도, 컨테이너는 호스트 네임스페이스(IPC, 네트워크, PID 네임스페이스)에서 동작한다. 따라서 몇몇 프로세스 정보를 읽어오거나, chroot /host 등의 작업은 수행될 수 없다.
  • 특권을 가진 파드가 필요한 경우에는 직접 생성한다.

사용이 모두 끝나면, 디버깅에 사용된 파드를 잊지 말고 정리한다.

kubectl delete pod node-debugger-mynode-pdx84

1.7 - 동작중인 컨테이너의 셸에 접근하기

이 페이지는 동작중인 컨테이너에 접근하기 위해 kubectl exec을 사용하는 방법에 대해 설명한다.

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

컨테이너의 셸에 접근하기

이 예시에서는 하나의 컨테이너를 가진 파드를 생성할 것이다. 이 컨테이너는 nginx 이미지를 실행한다. 해당 파드에 대한 설정 파일은 다음과 같다.

apiVersion: v1
kind: Pod
metadata:
  name: shell-demo
spec:
  volumes:
  - name: shared-data
    emptyDir: {}
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: shared-data
      mountPath: /usr/share/nginx/html
  hostNetwork: true
  dnsPolicy: Default

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/application/shell-demo.yaml

다음을 통해 컨테이너가 동작하고 있는지 확인할 수 있다.

kubectl get pod shell-demo

동작중인 컨테이너의 셸에 접근한다.

kubectl exec --stdin --tty shell-demo -- /bin/bash

셸에 접근해서 다음처럼 루트 디렉토리를 확인해 볼 수 있다.

# Run this inside the container
ls /

접근한 셸에서 다른 명령어도 한번 실행해 보아라. 다음은 실행해 볼 명령의 예시이다.

# You can run these example commands inside the container
ls /
cat /proc/mounts
cat /proc/1/maps
apt-get update
apt-get install -y tcpdump
tcpdump
apt-get install -y lsof
lsof
apt-get install -y procps
ps aux
ps aux | grep nginx

nginx의 최상단 페이지 작성하기

앞에서 생성한 파드에 대한 설정을 살펴보아라. 파드에는 emptyDir 볼륨이 사용되었고, 이 컨테이너는 해당 볼륨을 /usr/share/nginx/html 경로에 마운트하였다.

접근한 셸 환경에서 /usr/share/nginx/html 디렉터리에 index.html 파일을 생성해 보아라.

# Run this inside the container
echo 'Hello shell demo' > /usr/share/nginx/html/index.html

셸 환경에서 nginx 서버에 GET 요청을 시도해보면 다음과 같다.

# Run this in the shell inside your container
apt-get update
apt-get install curl
curl http://localhost/

출력 결과는 여러분이 index.html 파일에 작성한 텍스트를 출력할 것이다.

Hello shell demo

셸 사용이 모두 끝났다면 exit을 입력해 종료하라.

exit # To quit the shell in the container

컨테이너에서 개별 명령어 실행하기

셸이 아닌 일반적인 커맨드 환경에서 다음처럼 동작중인 컨테이너의 환경 변수를 출력할 수 있다.

kubectl exec shell-demo env

다른 명령어도 한번 실행해 보아라. 다음은 실행해 볼 명령의 예시이다.

kubectl exec shell-demo -- ps aux
kubectl exec shell-demo -- ls /
kubectl exec shell-demo -- cat /proc/1/mounts

파드에 한 개 이상의 컨테이너가 있을 경우 셸에 접근하기

만일 파드에 한 개 이상의 컨테이너가 있을 경우, kubectl exec 명령어에 --container 혹은 -c 옵션을 사용해서 컨테이너를 지정하라. 예를 들어, 여러분이 my-pod라는 이름의 파드가 있다고 가정해 보자. 이 파드에는 main-apphelper-app 이라는 이름의 두 컨테이너가 있다. 다음 명령어는 main-app 컨테이너에 대한 셸에 접근할 것이다.

kubectl exec -i -t my-pod --container main-app -- /bin/bash

다음 내용

2 - 클러스터 트러블슈팅

일반적인 클러스터 이슈를 디버깅한다.

이 문서는 클러스터 트러블슈팅에 대해 설명한다. 사용자가 겪고 있는 문제의 근본 원인으로서 사용자의 애플리케이션을 이미 배제했다고 가정한다. 애플리케이션 디버깅에 대한 팁은 애플리케이션 트러블슈팅 가이드를 참조한다. 자세한 내용은 트러블슈팅 문서를 참조한다.

클러스터 나열하기

클러스터에서 가장 먼저 디버그해야 할 것은 노드가 모두 올바르게 등록되었는지 여부이다.

다음을 실행한다.

kubectl get nodes

그리고 보일 것으로 예상되는 모든 노드가 존재하고 모두 Ready 상태인지 확인한다.

클러스터의 전반적인 상태에 대한 자세한 정보를 얻으려면 다음을 실행할 수 있다.

kubectl cluster-info dump

예제: 다운(down) 상태이거나 통신이 닿지 않는(unreachable) 노드 디버깅하기

때때로 디버깅할 때 노드의 상태를 확인하는 것이 유용할 수 있다(예를 들어, 어떤 노드에서 실행되는 파드가 이상하게 행동하는 것을 발견했거나, 특정 노드에 파드가 스케줄링되지 않는 이유를 알아보기 위해). 파드의 경우와 마찬가지로, kubectl describe nodekubectl get node -o yaml 명령을 사용하여 노드에 대한 상세 정보를 볼 수 있다. 예를 들어, 노드가 다운 상태(네트워크 연결이 끊어졌거나, kubelet이 종료된 후 재시작되지 못했거나 등)라면 아래와 같은 출력이 나올 것이다. 노드가 NotReady 상태라는 것을 나타내는 이벤트(event)와, 더 이상 실행 중이 아닌 파드(NotReady 상태 이후 5분 뒤에 축출되었음)에 주목한다.

kubectl get nodes
NAME                     STATUS       ROLES     AGE     VERSION
kube-worker-1            NotReady     <none>    1h      v1.23.3
kubernetes-node-bols     Ready        <none>    1h      v1.23.3
kubernetes-node-st6x     Ready        <none>    1h      v1.23.3
kubernetes-node-unaj     Ready        <none>    1h      v1.23.3
kubectl describe node kube-worker-1
Name:               kube-worker-1
Roles:              <none>
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=kube-worker-1
                    kubernetes.io/os=linux
Annotations:        kubeadm.alpha.kubernetes.io/cri-socket: /run/containerd/containerd.sock
                    node.alpha.kubernetes.io/ttl: 0
                    volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp:  Thu, 17 Feb 2022 16:46:30 -0500
Taints:             node.kubernetes.io/unreachable:NoExecute
                    node.kubernetes.io/unreachable:NoSchedule
Unschedulable:      false
Lease:
  HolderIdentity:  kube-worker-1
  AcquireTime:     <unset>
  RenewTime:       Thu, 17 Feb 2022 17:13:09 -0500
Conditions:
  Type                 Status    LastHeartbeatTime                 LastTransitionTime                Reason              Message
  ----                 ------    -----------------                 ------------------                ------              -------
  NetworkUnavailable   False     Thu, 17 Feb 2022 17:09:13 -0500   Thu, 17 Feb 2022 17:09:13 -0500   WeaveIsUp           Weave pod has set this
  MemoryPressure       Unknown   Thu, 17 Feb 2022 17:12:40 -0500   Thu, 17 Feb 2022 17:13:52 -0500   NodeStatusUnknown   Kubelet stopped posting node status.
  DiskPressure         Unknown   Thu, 17 Feb 2022 17:12:40 -0500   Thu, 17 Feb 2022 17:13:52 -0500   NodeStatusUnknown   Kubelet stopped posting node status.
  PIDPressure          Unknown   Thu, 17 Feb 2022 17:12:40 -0500   Thu, 17 Feb 2022 17:13:52 -0500   NodeStatusUnknown   Kubelet stopped posting node status.
  Ready                Unknown   Thu, 17 Feb 2022 17:12:40 -0500   Thu, 17 Feb 2022 17:13:52 -0500   NodeStatusUnknown   Kubelet stopped posting node status.
Addresses:
  InternalIP:  192.168.0.113
  Hostname:    kube-worker-1
Capacity:
  cpu:                2
  ephemeral-storage:  15372232Ki
  hugepages-2Mi:      0
  memory:             2025188Ki
  pods:               110
Allocatable:
  cpu:                2
  ephemeral-storage:  14167048988
  hugepages-2Mi:      0
  memory:             1922788Ki
  pods:               110
System Info:
  Machine ID:                 9384e2927f544209b5d7b67474bbf92b
  System UUID:                aa829ca9-73d7-064d-9019-df07404ad448
  Boot ID:                    5a295a03-aaca-4340-af20-1327fa5dab5c
  Kernel Version:             5.13.0-28-generic
  OS Image:                   Ubuntu 21.10
  Operating System:           linux
  Architecture:               amd64
  Container Runtime Version:  containerd://1.5.9
  Kubelet Version:            v1.23.3
  Kube-Proxy Version:         v1.23.3
Non-terminated Pods:          (4 in total)
  Namespace                   Name                                 CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age
  ---------                   ----                                 ------------  ----------  ---------------  -------------  ---
  default                     nginx-deployment-67d4bdd6f5-cx2nz    500m (25%)    500m (25%)  128Mi (6%)       128Mi (6%)     23m
  default                     nginx-deployment-67d4bdd6f5-w6kd7    500m (25%)    500m (25%)  128Mi (6%)       128Mi (6%)     23m
  kube-system                 kube-proxy-dnxbz                     0 (0%)        0 (0%)      0 (0%)           0 (0%)         28m
  kube-system                 weave-net-gjxxp                      100m (5%)     0 (0%)      200Mi (10%)      0 (0%)         28m
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource           Requests     Limits
  --------           --------     ------
  cpu                1100m (55%)  1 (50%)
  memory             456Mi (24%)  256Mi (13%)
  ephemeral-storage  0 (0%)       0 (0%)
  hugepages-2Mi      0 (0%)       0 (0%)
Events:
...
kubectl get node kube-worker-1 -o yaml
apiVersion: v1
kind: Node
metadata:
  annotations:
    kubeadm.alpha.kubernetes.io/cri-socket: /run/containerd/containerd.sock
    node.alpha.kubernetes.io/ttl: "0"
    volumes.kubernetes.io/controller-managed-attach-detach: "true"
  creationTimestamp: "2022-02-17T21:46:30Z"
  labels:
    beta.kubernetes.io/arch: amd64
    beta.kubernetes.io/os: linux
    kubernetes.io/arch: amd64
    kubernetes.io/hostname: kube-worker-1
    kubernetes.io/os: linux
  name: kube-worker-1
  resourceVersion: "4026"
  uid: 98efe7cb-2978-4a0b-842a-1a7bf12c05f8
spec: {}
status:
  addresses:
  - address: 192.168.0.113
    type: InternalIP
  - address: kube-worker-1
    type: Hostname
  allocatable:
    cpu: "2"
    ephemeral-storage: "14167048988"
    hugepages-2Mi: "0"
    memory: 1922788Ki
    pods: "110"
  capacity:
    cpu: "2"
    ephemeral-storage: 15372232Ki
    hugepages-2Mi: "0"
    memory: 2025188Ki
    pods: "110"
  conditions:
  - lastHeartbeatTime: "2022-02-17T22:20:32Z"
    lastTransitionTime: "2022-02-17T22:20:32Z"
    message: Weave pod has set this
    reason: WeaveIsUp
    status: "False"
    type: NetworkUnavailable
  - lastHeartbeatTime: "2022-02-17T22:20:15Z"
    lastTransitionTime: "2022-02-17T22:13:25Z"
    message: kubelet has sufficient memory available
    reason: KubeletHasSufficientMemory
    status: "False"
    type: MemoryPressure
  - lastHeartbeatTime: "2022-02-17T22:20:15Z"
    lastTransitionTime: "2022-02-17T22:13:25Z"
    message: kubelet has no disk pressure
    reason: KubeletHasNoDiskPressure
    status: "False"
    type: DiskPressure
  - lastHeartbeatTime: "2022-02-17T22:20:15Z"
    lastTransitionTime: "2022-02-17T22:13:25Z"
    message: kubelet has sufficient PID available
    reason: KubeletHasSufficientPID
    status: "False"
    type: PIDPressure
  - lastHeartbeatTime: "2022-02-17T22:20:15Z"
    lastTransitionTime: "2022-02-17T22:15:15Z"
    message: kubelet is posting ready status. AppArmor enabled
    reason: KubeletReady
    status: "True"
    type: Ready
  daemonEndpoints:
    kubeletEndpoint:
      Port: 10250
  nodeInfo:
    architecture: amd64
    bootID: 22333234-7a6b-44d4-9ce1-67e31dc7e369
    containerRuntimeVersion: containerd://1.5.9
    kernelVersion: 5.13.0-28-generic
    kubeProxyVersion: v1.23.3
    kubeletVersion: v1.23.3
    machineID: 9384e2927f544209b5d7b67474bbf92b
    operatingSystem: linux
    osImage: Ubuntu 21.10
    systemUUID: aa829ca9-73d7-064d-9019-df07404ad448

로그 보기

현재로서는 클러스터를 더 깊이 파고들려면 관련 머신에서 로그 확인이 필요하다. 관련 로그 파일 위치는 다음과 같다. (systemd 기반 시스템에서는 journalctl을 대신 사용해야 할 수도 있다.)

컨트롤 플레인 노드

  • /var/log/kube-apiserver.log - API 서버, API 제공을 담당
  • /var/log/kube-scheduler.log - 스케줄러, 스케줄 결정을 담당
  • /var/log/kube-controller-manager.log - 레플리케이션 컨트롤러를 담당하는 컨트롤러

워커 노드

  • /var/log/kubelet.log - Kubelet, 노드에서 컨테이너 실행을 담당
  • /var/log/kube-proxy.log - Kube Proxy, 서비스 로드밸런싱을 담당

클러스터 장애 모드

아래에 일부 오류 상황 예시 및 문제를 완화하기 위해 클러스터 설정을 조정하는 방법을 나열한다.

근본 원인

  • VM(들) 종료
  • 클러스터 내 또는 클러스터와 사용자 간의 네트워크 분할
  • 쿠버네티스 소프트웨어의 충돌
  • 데이터 손실 또는 퍼시스턴트 스토리지 사용 불가 (e.g. GCE PD 또는 AWS EBS 볼륨)
  • 운영자 오류, 예를 들면 잘못 구성된 쿠버네티스 소프트웨어 또는 애플리케이션 소프트웨어

특정 시나리오

  • API 서버 VM 종료 또는 API 서버 충돌
    • 다음의 현상을 유발함
      • 새로운 파드, 서비스, 레플리케이션 컨트롤러를 중지, 업데이트 또는 시작할 수 없다.
      • 쿠버네티스 API에 의존하지 않는 기존 파드 및 서비스는 계속 정상적으로 작동할 것이다.
  • API 서버 백업 스토리지 손실
    • 다음의 현상을 유발함
      • API 서버가 구동되지 않을 것이다.
      • kubelet에 도달할 수 없게 되지만, kubelet이 여전히 동일한 파드를 계속 실행하고 동일한 서비스 프록시를 제공할 것이다.
      • API 서버를 재시작하기 전에, 수동으로 복구하거나 API서버 상태를 재생성해야 한다.
  • 지원 서비스 (노드 컨트롤러, 레플리케이션 컨트롤러 매니저, 스케쥴러 등) VM 종료 또는 충돌
    • 현재 그것들은 API 서버와 같은 위치에 있기 때문에 API 서버와 비슷한 상황을 겪을 것이다.
    • 미래에는 이들도 복제본을 가질 것이며 API서버와 별도로 배치될 수도 있다.
    • 지원 서비스들은 상태(persistent state)를 자체적으로 유지하지는 않는다.
  • 개별 노드 (VM 또는 물리적 머신) 종료
    • 다음의 현상을 유발함
      • 해당 노드의 파드가 실행을 중지
  • 네트워크 분할
    • 다음의 현상을 유발함
      • 파티션 A는 파티션 B의 노드가 다운되었다고 생각한다. 파티션 B는 API 서버가 다운되었다고 생각한다. (마스터 VM이 파티션 A에 있다고 가정)
  • Kubelet 소프트웨어 오류
    • 다음의 현상을 유발함
      • 충돌한 kubelet은 노드에서 새 파드를 시작할 수 없다.
      • kubelet이 파드를 삭제할 수도 있고 삭제하지 않을 수도 있다.
      • 노드는 비정상으로 표시된다.
      • 레플리케이션 컨트롤러는 다른 곳에서 새 파드를 시작한다.
  • 클러스터 운영자 오류
    • 다음의 현상을 유발함
      • 파드, 서비스 등의 손실
      • API 서버 백업 저장소 분실
      • API를 읽을 수 없는 사용자
      • 기타

완화

  • 조치: IaaS VM을 위한 IaaS 공급자의 자동 VM 다시 시작 기능을 사용한다.

    • 다음을 완화할 수 있음: API 서버 VM 종료 또는 API 서버 충돌
    • 다음을 완화할 수 있음: 지원 서비스 VM 종료 또는 충돌
  • 조치: API 서버+etcd가 있는 VM에 IaaS 제공자의 안정적인 스토리지(예: GCE PD 또는 AWS EBS 볼륨)를 사용한다.

    • 다음을 완화할 수 있음: API 서버 백업 스토리지 손실
  • 조치: 고가용성 구성을 사용한다.

    • 다음을 완화할 수 있음: 컨트롤 플레인 노드 종료 또는 컨트롤 플레인 구성 요소(스케줄러, API 서버, 컨트롤러 매니저) 충돌
      • 동시에 발생하는 하나 이상의 노드 또는 구성 요소 오류를 허용한다.
    • 다음을 완화할 수 있음: API 서버 백업 스토리지(i.e., etcd의 데이터 디렉터리) 손실
      • 고가용성 etcd 구성을 사용하고 있다고 가정
  • 조치: API 서버 PD/EBS 볼륨의 주기적인 스냅샷

    • 다음을 완화할 수 있음: API 서버 백업 스토리지 손실
    • 다음을 완화할 수 있음: 일부 운영자 오류 사례
    • 다음을 완화할 수 있음: 일부 쿠버네티스 소프트웨어 오류 사례
  • 조치: 파드 앞에 레플리케이션 컨트롤러와 서비스 사용

    • 다음을 완화할 수 있음: 노드 종료
    • 다음을 완화할 수 있음: Kubelet 소프트웨어 오류
  • 조치: 예기치 않은 재시작을 허용하도록 설계된 애플리케이션(컨테이너)

    • 다음을 완화할 수 있음: 노드 종료
    • 다음을 완화할 수 있음: Kubelet 소프트웨어 오류

다음 내용

2.1 - 리소스 메트릭 파이프라인

쿠버네티스에서, 메트릭 API(Metrics API) 는 자동 스케일링 및 비슷한 사용 사례를 지원하기 위한 기본적인 메트릭 집합을 제공한다. 이 API는 노드와 파드의 리소스 사용량 정보를 제공하며, 여기에는 CPU 및 메모리 메트릭이 포함된다. 메트릭 API를 클러스터에 배포하면, 쿠버네티스 API의 클라이언트는 이 정보에 대해 질의할 수 있으며, 질의 권한을 관리하기 위해 쿠버네티스의 접근 제어 메커니즘을 이용할 수 있다.

HorizontalPodAutoscaler(HPA) 및 VerticalPodAutoscaler(VPA)는 사용자의 요구 사항을 만족할 수 있도록 워크로드 레플리카와 리소스를 조정하는 데에 메트릭 API의 데이터를 이용한다.

kubectl top 명령을 이용하여 리소스 메트릭을 볼 수도 있다.

그림 1은 리소스 메트릭 파이프라인의 아키텍처를 나타낸다.

flowchart RL subgraph cluster[클러스터] direction RL S[

] A[Metrics-
Server] subgraph B[노드] direction TB D[cAdvisor] --> C[kubelet] E[컨테이너
런타임] --> D E1[컨테이너
런타임] --> D P[파드 데이터] -.- C end L[API
서버] W[HPA] C ---->|요약
API| A -->|메트릭
API| L --> W end L ---> K[kubectl
top] classDef box fill:#fff,stroke:#000,stroke-width:1px,color:#000; class W,B,P,K,cluster,D,E,E1 box classDef spacewhite fill:#ffffff,stroke:#fff,stroke-width:0px,color:#000 class S spacewhite classDef k8s fill:#326ce5,stroke:#fff,stroke-width:1px,color:#fff; class A,L,C k8s

그림 1. 리소스 메트릭 파이프라인

그림의 오른쪽에서 왼쪽 순으로, 아키텍처 구성 요소는 다음과 같다.

  • cAdvisor: kubelet에 포함된 컨테이너 메트릭을 수집, 집계, 노출하는 데몬

  • kubelet: 컨테이너 리소스 관리를 위한 노드 에이전트. 리소스 메트릭은 kubelet API 엔드포인트 /metrics/resource/stats 를 사용하여 접근 가능하다.

  • 요약 API: /stats 엔드포인트를 통해 사용할 수 있는 노드 별 요약된 정보를 탐색 및 수집할 수 있도록 kubelet이 제공하는 API

  • metrics-server: 각 kubelet으로부터 수집한 리소스 메트릭을 수집 및 집계하는 클러스터 애드온 구성 요소. API 서버는 HPA, VPA 및 kubectl top 명령어가 사용할 수 있도록 메트릭 API를 제공한다. metrics-server는 메트릭 API에 대한 기준 구현(reference implementation) 중 하나이다.

  • 메트릭 API: 워크로드 오토스케일링에 사용되는 CPU 및 메모리 정보로의 접근을 지원하는 쿠버네티스 API. 이를 클러스터에서 사용하려면, 메트릭 API를 제공하는 API 확장(extension) 서버가 필요하다.

메트릭 API

기능 상태: Kubernetes 1.8 [beta]

metrics-server는 메트릭 API에 대한 구현이다. 이 API는 클러스터 내 노드와 파드의 CPU 및 메모리 사용 정보에 접근할 수 있게 해 준다. 이것의 주 역할은 리소스 사용 메트릭을 쿠버네티스 오토스케일러 구성 요소에 제공하는 것이다.

다음은 minikube 노드에 대한 메트릭 API 요청 예시이며 가독성 향상을 위해 jq를 활용한다.

kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes/minikube" | jq '.'

다음은 curl을 이용하여 동일한 API 호출을 하는 명령어다.

curl http://localhost:8080/apis/metrics.k8s.io/v1beta1/nodes/minikube

응답 예시는 다음과 같다.

{
  "kind": "NodeMetrics",
  "apiVersion": "metrics.k8s.io/v1beta1",
  "metadata": {
    "name": "minikube",
    "selfLink": "/apis/metrics.k8s.io/v1beta1/nodes/minikube",
    "creationTimestamp": "2022-01-27T18:48:43Z"
  },
  "timestamp": "2022-01-27T18:48:33Z",
  "window": "30s",
  "usage": {
    "cpu": "487558164n",
    "memory": "732212Ki"
  }
}

다음은 kube-system 네임스페이스 내의 kube-scheduler-minikube 파드에 대한 메트릭 API 요청 예시이며 가독성 향상을 위해 jq를 활용한다.

kubectl get --raw "/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/kube-scheduler-minikube" | jq '.'

다음은 curl을 이용하여 동일한 API 호출을 하는 명령어다.

curl http://localhost:8080/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/kube-scheduler-minikube

응답 예시는 다음과 같다.

{
  "kind": "PodMetrics",
  "apiVersion": "metrics.k8s.io/v1beta1",
  "metadata": {
    "name": "kube-scheduler-minikube",
    "namespace": "kube-system",
    "selfLink": "/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/kube-scheduler-minikube",
    "creationTimestamp": "2022-01-27T19:25:00Z"
  },
  "timestamp": "2022-01-27T19:24:31Z",
  "window": "30s",
  "containers": [
    {
      "name": "kube-scheduler",
      "usage": {
        "cpu": "9559630n",
        "memory": "22244Ki"
      }
    }
  ]
}

메트릭 API는 k8s.io/metrics 저장소에 정의되어 있다. metrics.k8s.io API를 사용하기 위해서는 API 집계(aggregation) 계층을 활성화하고 APIService를 등록해야 한다.

메트릭 API에 대해 더 알아보려면, 리소스 메트릭 API 디자인, metrics-server 저장소리소스 메트릭 API를 참고한다.

리소스 사용량 측정

CPU

CPU는 cpu 단위로 측정된 평균 코어 사용량 형태로 보고된다. 쿠버네티스에서 1 cpu는 클라우드 제공자의 경우 1 vCPU/코어에 해당하고, 베어메탈 인텔 프로세서의 경우 1 하이퍼-스레드에 해당한다.

이 값은 커널(Linux 및 Windows 커널 모두)에서 제공하는 누적 CPU 카운터에 대한 비율을 취하여 얻어진다. CPU 값 계산에 사용된 타임 윈도우는 메트릭 API의 window 필드에 표시된다.

쿠버네티스가 어떻게 CPU 리소스를 할당하고 측정하는지 더 알아보려면, CPU의 의미를 참고한다.

메모리

메모리는 메트릭을 수집하는 순간에 바이트 단위로 측정된 워킹 셋(working set) 형태로 보고된다.

이상적인 환경에서, "워킹 셋"은 메모리가 부족한 상태더라도 해제할 수 없는 사용 중인 메모리의 양이다. 그러나 워킹 셋의 계산 방법은 호스트 OS에 따라 다르며 일반적으로 추정치를 추출하기 위해 휴리스틱을 많이 사용한다.

컨테이너의 워킹 셋에 대한 쿠버네티스 모델은 컨테이너 런타임이 해당 컨테이너와 연결된 익명(anonymous) 메모리를 계산할 것으로 예상한다. 호스트 OS가 항상 페이지를 회수할 수는 없기 때문에, 워킹 셋 메트릭에는 일반적으로 일부 캐시된 (파일 기반) 메모리도 포함된다.

쿠버네티스가 어떻게 메모리 리소스를 할당하고 측정하는지 더 알아보려면, 메모리의 의미를 참고한다.

metrics-server

metrics-server는 kubelet으로부터 리소스 메트릭을 수집하고, 이를 HPA(Horizontal Pod Autoscaler) 및 VPA(Vertical Pod Autoscaler)가 활용할 수 있도록 쿠버네티스 API 서버 내에서 메트릭 API(Metrics API)를 통해 노출한다. kubectl top 명령을 사용하여 이 메트릭을 확인해볼 수도 있다.

metrics-server는 쿠버네티스 API를 사용하여 클러스터의 노드와 파드를 추적한다. metrics-server는 각 노드에 HTTP를 통해 질의하여 메트릭을 수집한다. metrics-server는 또한 파드 메타데이터의 내부적 뷰를 작성하고, 파드 헬스(health)에 대한 캐시를 유지한다. 이렇게 캐시된 파드 헬스 정보는 metrics-server가 제공하는 확장 API(extension API)를 통해 이용할 수 있다.

HPA 질의에 대한 예시에서, 예를 들어 HPA 질의에 대한 경우, metrics-server는 디플로이먼트의 어떤 파드가 레이블 셀렉터 조건을 만족하는지 판별해야 한다.

metrics-server는 각 노드로부터 메트릭을 수집하기 위해 kubelet API를 호출한다. 사용 중인 metrics-server 버전에 따라, 다음의 엔드포인트를 사용한다.

  • v0.6.0 이상: 메트릭 리소스 엔드포인트 /metrics/resource
  • 이전 버전: 요약 API 엔드포인트 /stats/summary

다음 내용

metrics-server에 대한 더 많은 정보는 metrics-server 저장소를 확인한다.

또한 다음을 참고할 수도 있다.

kubelet이 어떻게 노드 메트릭을 제공하는지, 그리고 쿠버네티스 API를 통해 이러한 메트릭에 어떻게 접근하는지 알아보려면, 노드 메트릭 데이터 문서를 참조한다.

2.2 - 리소스 모니터링 도구

애플리케이션을 스케일하여 신뢰할 수 있는 서비스를 제공하려면, 애플리케이션이 배포되었을 때 애플리케이션이 어떻게 동작하는지를 이해해야 한다. 컨테이너, 파드, 서비스, 그리고 전체 클러스터의 특성을 검사하여 쿠버네티스 클러스터 내의 애플리케이션 성능을 검사할 수 있다. 쿠버네티스는 각 레벨에서 애플리케이션의 리소스 사용량에 대한 상세 정보를 제공한다. 이 정보는 애플리케이션의 성능을 평가하고 병목 현상을 제거하여 전체 성능을 향상할 수 있게 해준다.

쿠버네티스에서 애플리케이션 모니터링은 단일 모니터링 솔루션에 의존하지 않는다. 신규 클러스터에서는, 리소스 메트릭 또는 완전한 메트릭 파이프라인으로 모니터링 통계를 수집할 수 있다.

리소스 메트릭 파이프라인

리소스 메트릭 파이프라인은 Horizontal Pod Autoscaler 컨트롤러와 같은 클러스터 구성요소나 kubectl top 유틸리티에 관련되어 있는 메트릭들로 제한된 집합을 제공한다. 이 메트릭은 경량의 단기 인메모리 저장소인 metrics-server에 의해서 수집되며 metrics.k8s.io API를 통해 노출된다.

metrics-server는 클러스터 상의 모든 노드를 발견하고 각 노드의 kubelet에 CPU와 메모리 사용량을 질의한다. Kubelet은 쿠버네티스 마스터와 노드 간의 다리 역할을 하면서 머신에서 구동되는 파드와 컨테이너를 관리한다. Kubelet은 각각의 파드를 해당하는 컨테이너에 매치시키고 컨테이너 런타임 인터페이스를 통해 컨테이너 런타임에서 개별 컨테이너의 사용량 통계를 가져온다. 컨테이너를 구현하기 위해 리눅스 cgroup 및 네임스페이스를 활용하는 컨테이너 런타임을 사용하며, 해당 컨테이너 런타임이 사용 통계치를 퍼블리싱 하지 않는 경우, kubelet은 해당 통계치를 (cAdvisor의 코드 사용하여) 직접 조회 할 수 있다. 이런 통계가 어떻게 도착하든 kubelet은 취합된 파드 리소스 사용량 통계를 metric-server 리소스 메트릭 API를 통해 노출한다. 이 API는 kubelet의 인증이 필요한 읽기 전용 포트 상의 /metrics/resource/v1beta1에서 제공된다.

완전한 메트릭 파이프라인

완전한 메트릭 파이프라인은 보다 풍부한 메트릭에 접근할 수 있도록 해준다. 쿠버네티스는 Horizontal Pod Autoscaler와 같은 메커니즘을 활용해서 이런 메트릭에 대한 반응으로 클러스터의 현재 상태를 기반으로 자동으로 스케일링하거나 클러스터를 조정할 수 있다. 모니터링 파이프라인은 kubelet에서 메트릭을 가져와서 쿠버네티스에 custom.metrics.k8s.ioexternal.metrics.k8s.io API를 구현한 어댑터를 통해 노출한다.

CNCF 프로젝트인 프로메테우스는 기본적으로 쿠버네티스, 노드, 프로메테우스 자체를 모니터링할 수 있다. CNCF 프로젝트가 아닌 완전한 메트릭 파이프라인 프로젝트는 쿠버네티스 문서의 범위가 아니다.

다음 내용

다음과 같은 추가 디버깅 도구에 대해 더 알아본다.

2.3 - 노드 헬스 모니터링하기

노드 문제 감지기(Node Problem Detector) 는 노드의 헬스에 대해 모니터링 및 보고하는 데몬이다. 노드 문제 감지기를 데몬셋(DaemonSet) 혹은 스탠드얼론 데몬(standalone daemon)으로 실행할 수 있다. 노드 문제 감지기는 다양한 데몬으로부터 노드의 문제에 관한 정보를 다양한 데몬으로부터 수집하고, 이러한 컨디션들을 노드컨디션(NodeCondition)이벤트(Event)형태로 API 서버에 보고한다.

노드 문제 감지기 설치 및 사용 방법을 보려면, 노드 문제 감지기 프로젝트 문서를 참조하자.

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

제약 사항

  • 노드 문제 감지기는 파일 기반의 커널 로그만 지원한다. journald 와 같은 로그 도구는 지원하지 않는다.

  • 노드 문제 감지기는 커널 로그 형식을 사용하여 커널 이슈를 보고한다. 커널 로그 형식을 확장하는 방법을 배우려면 기타 로그 형식 지원 추가를 살펴보자.

노드 문제 감지기 활성화하기

일부 클라우드 사업자는 노드 문제 감지기를 애드온 으로서 제공한다. 또한, kubectl을 이용하거나 애드온 파드를 생성하여 노드 문제 감지기를 활성화할 수도 있다.

kubectl를 이용하여 노드 문제 감지기 활성화하기

kubectl은 노드 문제 감지기를 관리하는 가장 유연한 방법이다. 현재 환경에 맞게 조정하거나 사용자 정의 노드 문제를 탐지하기 위해 기본 설정값을 덮어쓸 수 있다. 예를 들면 아래와 같다.

  1. node-problem-detector.yaml와 유사하게 노드 문제 감지기 구성을 생성한다:

    apiVersion: apps/v1
       kind: DaemonSet
       metadata:
         name: node-problem-detector-v0.1
         namespace: kube-system
         labels:
           k8s-app: node-problem-detector
           version: v0.1
           kubernetes.io/cluster-service: "true"
       spec:
         selector:
           matchLabels:
             k8s-app: node-problem-detector  
             version: v0.1
             kubernetes.io/cluster-service: "true"
         template:
           metadata:
             labels:
               k8s-app: node-problem-detector
               version: v0.1
               kubernetes.io/cluster-service: "true"
           spec:
             hostNetwork: true
             containers:
             - name: node-problem-detector
               image: registry.k8s.io/node-problem-detector:v0.1
               securityContext:
                 privileged: true
               resources:
                 limits:
                   cpu: "200m"
                   memory: "100Mi"
                 requests:
                   cpu: "20m"
                   memory: "20Mi"
               volumeMounts:
               - name: log
                 mountPath: /log
                 readOnly: true
             volumes:
             - name: log
               hostPath:
                 path: /var/log/
  2. kubectl을 이용하여 노드 문제 감지기를 시작한다.

    kubectl apply -f https://k8s.io/examples/debug/node-problem-detector.yaml
    

애드온 파드를 이용하여 노드 문제 감지기 활성화하기

만약 커스텀 클러스터 부트스트랩 솔루션을 사용중이고 기본 설정값을 덮어쓸 필요가 없다면, 디플로이먼트를 추가로 자동화하기 위해 애드온 파드를 활용할 수 있다.

node-problem-detector.yaml를 생성하고, 컨트롤 플레인 노드의 애드온 파드의 디렉토리 /etc/kubernetes/addons/node-problem-detector에 설정을 저장한다.

설정 덮어쓰기

노드 문제 감지기를 빌드할 때, 기본 설정이 포함되어 있다.

하지만 컨피그맵(ConfigMap)을 이용해 설정을 덮어쓸 수 있다.

  1. config/ 내의 설정 파일을 변경한다.

  2. node-problem-detector-config 컨피그맵(ConfigMap)을 생성한다.

    kubectl create configmap node-problem-detector-config --from-file=config/
    
  3. 컨피그맵(ConfigMap)을 사용하도록 node-problem-detector.yaml을 변경한다.

    apiVersion: apps/v1
       kind: DaemonSet
       metadata:
         name: node-problem-detector-v0.1
         namespace: kube-system
         labels:
           k8s-app: node-problem-detector
           version: v0.1
           kubernetes.io/cluster-service: "true"
       spec:
         selector:
           matchLabels:
             k8s-app: node-problem-detector  
             version: v0.1
             kubernetes.io/cluster-service: "true"
         template:
           metadata:
             labels:
               k8s-app: node-problem-detector
               version: v0.1
               kubernetes.io/cluster-service: "true"
           spec:
             hostNetwork: true
             containers:
             - name: node-problem-detector
               image: registry.k8s.io/node-problem-detector:v0.1
               securityContext:
                 privileged: true
               resources:
                 limits:
                   cpu: "200m"
                   memory: "100Mi"
                 requests:
                   cpu: "20m"
                   memory: "20Mi"
               volumeMounts:
               - name: log
                 mountPath: /log
                 readOnly: true
               - name: config # config/ 디렉토리를 컨피그맵 볼륨(ConfigMap volume)으로 덮어쓴다
                 mountPath: /config
                 readOnly: true
             volumes:
             - name: log
               hostPath:
                 path: /var/log/
             - name: config # 컨피그맵 볼륨(ConfigMap volume)을 정의한다
               configMap:
                 name: node-problem-detector-config
  4. 새로운 설정 파일을 사용하여 노드 문제 감지기를 재생성한다.

    # 만약 노드 문제 감지기가 동작하고 있다면, 재생성 전 삭제한다
    kubectl delete -f https://k8s.io/examples/debug/node-problem-detector.yaml
    kubectl apply -f https://k8s.io/examples/debug/node-problem-detector-configmap.yaml
    

만약 노드 문제 감지기가 클러스터 애드온으로 실행된 경우, 설정 덮어쓰기가 지원되지 않는다. 애드온 매니저는 컨피그맵(ConfigMap)을 지원하지 않는다.

커널 모니터

커널 모니터는 노드 문제 감지기에서 지원하는 시스템 로그 모니터링 데몬이다. 커널 모니터는 커널 로그를 감시하며, 미리 설정된 규칙에 따라 알려진 커널 이슈를 감지한다.

커널 모니터는 config/kernel-monitor.json에 미리 설정된 규칙 모음과 커널 이슈를 매칭한다. 규칙 리스트는 확장 가능하다. 설정을 덮어쓰기 해 규칙 리스트를 확장할 수 있다.

신규 노드컨디션(NodeConditions) 추가하기

신규 NodeCondition를 지원하려면, config/kernel-monitor.jsonconditions필드 내 조건 정의를 생성해야한다. 예를 들면 아래와 같다.

{
  "type": "NodeConditionType",
  "reason": "CamelCaseDefaultNodeConditionReason",
  "message": "arbitrary default node condition message"
}

신규 문제 감지하기

신규 문제를 감지하려면 config/kernel-monitor.jsonrules필드를 신규 규칙 정의로 확장하면 된다.

{
  "type": "temporary/permanent",
  "condition": "NodeConditionOfPermanentIssue",
  "reason": "CamelCaseShortReason",
  "message": "regexp matching the issue in the kernel log"
}

커널 로그 장치를 위한 경로 설정하기

운영 체제 (OS) 배포판의 커널 로그 경로를 확인한다. 리눅스 커널 로그 장치(log device)는 보통 /dev/kmsg와 같이 표시된다. 하지만, 로그 경로 장소는 OS 배포판마다 상이하다. config/kernel-monitor.jsonlog 필드는 컨테이너 내부의 로그 경로를 나타낸다. log 필드를 노드 문제 감지기가 감시하는 장치 경로와 일치하도록 구성하면 된다.

기타 로그 포맷 지원 추가하기

커널 모니터는 커널 로그의 내부 데이터 구조를 해석하기 위해 Translator 플러그인을 사용한다. 신규 로그 포맷을 사용하기 위해 신규 해석기를 구현할 수 있다.

권장 사항 및 제약 사항

노드 헬스를 모니터링하기 위해 클러스터에 노드 문제 탐지기를 실행할 것을 권장한다. 노드 문제 감지기를 실행할 때, 각 노드에 추가 리소스 오버헤드가 발생할 수 있다. 다음과 같은 이유 때문에 일반적으로는 문제가 없다.

  • 커널 로그는 비교적 천천히 늘어난다.
  • 노드 문제 감지기에는 리소스 제한이 설정되어 있다.
  • 높은 부하가 걸리더라도, 리소스 사용량은 허용 가능한 수준이다. 추가 정보를 위해 노드 문제 감지기의 벤치마크 결과를 살펴보자.

2.4 - crictl로 쿠버네티스 노드 디버깅하기

기능 상태: Kubernetes v1.11 [stable]

crictl은 CRI-호환 컨테이너 런타임에 사용할 수 있는 커맨드라인 인터페이스이다. 쿠버네티스 노드에서 컨테이너 런타임과 애플리케이션을 검사하고 디버그하는 데 사용할 수 있다. crictl과 그 소스는 cri-tools 저장소에서 호스팅한다.

시작하기 전에

crictl은 CRI 런타임이 있는 리눅스 운영체제를 필요로 한다.

crictl 설치하기

cri-tools 릴리스 페이지에서 다양한 아키텍처 별로 압축된 crictl 아카이브(archive)를 다운로드할 수 있다. 설치된 쿠버네티스 버전에 해당하는 버전을 다운로드한다. /usr/local/bin/와 같은 시스템 경로의 위치에 압축을 푼다.

일반적인 사용법

crictl 커맨드에는 여러 하위 커맨드와 런타임 플래그가 있다. 자세한 내용은 crictl help 또는 crictl <subcommand> help를 참조한다.

아래 내용 중 하나를 통해 crictl의 엔드포인트를 설정할 수 있다.

  • --runtime-endpoint--image-endpoint 플래그 설정.
  • CONTAINER_RUNTIME_ENDPOINTIMAGE_SERVICE_ENDPOINT 환경 변수 설정.
  • 구성 파일 /etc/crictl.yaml에 엔드포인트 설정. 다른 파일을 지정하기 위해서는 crictl을 실행할 때 --config=PATH_TO_FILE 플래그를 사용한다.

서버에 연결할 때 구성 파일에서 timeout 또는 debug 값을 명시하거나, --timeout 그리고 --debug 커맨드라인 플래그를 사용하여 타임아웃 값을 지정하고 디버깅을 활성화하거나 비활성화할 수 있다.

현재 구성을 보거나 편집하려면 /etc/crictl.yaml의 내용을 보거나 편집한다. 예를 들어, containerd 컨테이너 런타임 사용 시 구성은 아래와 유사하다.

runtime-endpoint: unix:///var/run/containerd/containerd.sock
image-endpoint: unix:///var/run/containerd/containerd.sock
timeout: 10
debug: true

crictl에 대해 자세히 알고 싶다면, crictl 문서를 참조한다.

crictl 커맨드 예시

아래 예시를 통해 crictl 커맨드와 출력을 확인해보자.

파드 목록 조회

모든 파드의 목록 조회

crictl pods

출력은 다음과 유사하다.

POD ID              CREATED              STATE               NAME                         NAMESPACE           ATTEMPT
926f1b5a1d33a       About a minute ago   Ready               sh-84d7dcf559-4r2gq          default             0
4dccb216c4adb       About a minute ago   Ready               nginx-65899c769f-wv2gp       default             0
a86316e96fa89       17 hours ago         Ready               kube-proxy-gblk4             kube-system         0
919630b8f81f1       17 hours ago         Ready               nvidia-device-plugin-zgbbv   kube-system         0

이름으로 파드의 목록 조회

crictl pods --name nginx-65899c769f-wv2gp

출력은 다음과 유사하다.

POD ID              CREATED             STATE               NAME                     NAMESPACE           ATTEMPT
4dccb216c4adb       2 minutes ago       Ready               nginx-65899c769f-wv2gp   default             0

레이블로 파드의 목록 조회

crictl pods --label run=nginx

출력은 다음과 유사하다.

POD ID              CREATED             STATE               NAME                     NAMESPACE           ATTEMPT
4dccb216c4adb       2 minutes ago       Ready               nginx-65899c769f-wv2gp   default             0

이미지 목록 조회

모든 이미지의 목록 조회

crictl images

출력은 다음과 유사하다.

IMAGE                                     TAG                 IMAGE ID            SIZE
busybox                                   latest              8c811b4aec35f       1.15MB
k8s-gcrio.azureedge.net/hyperkube-amd64   v1.10.3             e179bbfe5d238       665MB
k8s-gcrio.azureedge.net/pause-amd64       3.1                 da86e6ba6ca19       742kB
nginx                                     latest              cd5239a0906a6       109MB

저장소로 이미지 목록 조회

crictl images nginx

출력은 다음과 유사하다.

IMAGE               TAG                 IMAGE ID            SIZE
nginx               latest              cd5239a0906a6       109MB

이미지의 IDs 목록만 조회

crictl images -q

출력은 다음과 유사하다.

sha256:8c811b4aec35f259572d0f79207bc0678df4c736eeec50bc9fec37ed936a472a
sha256:e179bbfe5d238de6069f3b03fccbecc3fb4f2019af741bfff1233c4d7b2970c5
sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e
sha256:cd5239a0906a6ccf0562354852fae04bc5b52d72a2aff9a871ddb6bd57553569

컨테이너 목록 조회

모든 컨테이너 목록 조회

crictl ps -a

출력은 다음과 유사하다.

CONTAINER ID        IMAGE                                                                                                             CREATED             STATE               NAME                       ATTEMPT
1f73f2d81bf98       busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47                                   7 minutes ago       Running             sh                         1
9c5951df22c78       busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47                                   8 minutes ago       Exited              sh                         0
87d3992f84f74       nginx@sha256:d0a8828cccb73397acb0073bf34f4d7d8aa315263f1e7806bf8c55d8ac139d5f                                     8 minutes ago       Running             nginx                      0
1941fb4da154f       k8s-gcrio.azureedge.net/hyperkube-amd64@sha256:00d814b1f7763f4ab5be80c58e98140dfc69df107f253d7fdd714b30a714260a   18 hours ago        Running             kube-proxy                 0

실행 중인 컨테이너 목록 조회

crictl ps

출력은 다음과 유사하다.

CONTAINER ID        IMAGE                                                                                                             CREATED             STATE               NAME                       ATTEMPT
1f73f2d81bf98       busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47                                   6 minutes ago       Running             sh                         1
87d3992f84f74       nginx@sha256:d0a8828cccb73397acb0073bf34f4d7d8aa315263f1e7806bf8c55d8ac139d5f                                     7 minutes ago       Running             nginx                      0
1941fb4da154f       k8s-gcrio.azureedge.net/hyperkube-amd64@sha256:00d814b1f7763f4ab5be80c58e98140dfc69df107f253d7fdd714b30a714260a   17 hours ago        Running             kube-proxy                 0

실행 중인 컨테이너 안에서 명령 실행

crictl exec -i -t 1f73f2d81bf98 ls

출력은 다음과 유사하다.

bin   dev   etc   home  proc  root  sys   tmp   usr   var

컨테이너의 로그 조회

모든 컨테이너의 로그 조회

crictl logs 87d3992f84f74

출력은 다음과 유사하다.

10.240.0.96 - - [06/Jun/2018:02:45:49 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"
10.240.0.96 - - [06/Jun/2018:02:45:50 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"
10.240.0.96 - - [06/Jun/2018:02:45:51 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"

최신 N개 줄의 로그만 조회

crictl logs --tail=1 87d3992f84f74

출력은 다음과 유사하다.

10.240.0.96 - - [06/Jun/2018:02:45:51 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"

파드 샌드박스 실행

crictl을 사용하여 파드 샌드박스를 실행하는 것은 컨테이너 런타임 디버깅에 유용하다. 구동 중인 쿠버네티스 클러스터에서 이러한 샌드박스는 kubelet에 의해서 결국 중지 및 삭제된다.

  1. 다음과 같은 JSON 파일 생성한다.

    {
      "metadata": {
        "name": "nginx-sandbox",
        "namespace": "default",
        "attempt": 1,
        "uid": "hdishd83djaidwnduwk28bcsb"
      },
      "log_directory": "/tmp",
      "linux": {
      }
    }
    
  2. crictl runp 커맨드를 사용하여 JSON을 적용하고 샌드박스를 실행한다.

    crictl runp pod-config.json
    

    결과로 샌드박스의 ID가 반환될 것이다.

컨테이너 생성

crictl을 사용하여 컨테이너를 만드는 것은 컨테이너 런타임 디버깅에 유용하다. 구동 중인 쿠버네티스 클러스터에서 이러한 샌드박스는 kubelet에 의해서 결국 중지 및 삭제된다.

  1. busybox 이미지 가져오기

    crictl pull busybox
    
    Image is up to date for busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47
    
  2. 파드와 컨테이너 구성(config) 생성

    파드 구성:

    {
      "metadata": {
        "name": "busybox-sandbox",
        "namespace": "default",
        "attempt": 1,
        "uid": "aewi4aeThua7ooShohbo1phoj"
      },
      "log_directory": "/tmp",
      "linux": {
      }
    }
    

    컨테이너 구성:

    {
      "metadata": {
        "name": "busybox"
      },
      "image":{
        "image": "busybox"
      },
      "command": [
        "top"
      ],
      "log_path":"busybox.log",
      "linux": {
      }
    }
    
  3. 이전에 생성한 파드의 ID 및 컨테이너 구성 파일과 파드 구성 파일을 커맨드 인자로 전달하여, 컨테이너를 생성한다. 결과로 컨테이너의 ID가 반환될 것이다.

    crictl create f84dd361f8dc51518ed291fbadd6db537b0496536c1d2d6c05ff943ce8c9a54f container-config.json pod-config.json
    
  4. 모든 컨테이너의 목록을 조회하여 새로 생성된 컨테이너의 상태가 Created임을 확인한다.

    crictl ps -a
    

    출력은 다음과 유사하다.

    CONTAINER ID        IMAGE               CREATED             STATE               NAME                ATTEMPT
    3e025dd50a72d       busybox             32 seconds ago      Created             busybox             0
    

컨테이너 시작하기

컨테이너를 시작하기 위해서 컨테이너 ID를 crictl start에 인자로 전달한다.

crictl start 3e025dd50a72d956c4f14881fbb5b1080c9275674e95fb67f965f6478a957d60

출력은 다음과 유사하다.

3e025dd50a72d956c4f14881fbb5b1080c9275674e95fb67f965f6478a957d60

컨테이너의 상태가 Running임을 확인한다.

crictl ps

출력은 다음과 유사하다.

CONTAINER ID   IMAGE    CREATED              STATE    NAME     ATTEMPT
3e025dd50a72d  busybox  About a minute ago   Running  busybox  0

다음 내용

2.5 - 감사(auditing)

쿠버네티스 감사(auditing) 는 클러스터의 작업 순서를 문서화하는 보안 관련 시간별 레코드 세트를 제공한다. 클러스터는 사용자, 쿠버네티스 API를 사용하는 애플리케이션 및 컨트롤 플레인 자체에서 생성된 활동을 감사한다.

감사를 통해 클러스터 관리자는 다음 질문에 답할 수 있다.

  • 무슨 일이 일어났는지?
  • 언제 일어난 일인지?
  • 누가 시작했는지?
  • 어떤 일이 있었는지?
  • 어디서 관찰되었는지?
  • 어디서부터 시작되었는지?
  • 그래서 어디까지 갔는지?

감사 기록은 kube-apiserver 컴포넌트 내에서 수명주기를 시작한다. 실행의 각 단계에서 각 요청은 감사 이벤트를 생성하고, 감사 이벤트는 특정 정책에 따라 사전 처리되고 백엔드에 기록된다. 정책은 기록된 내용을 결정하고 백엔드는 기록을 유지한다. 현재 백엔드 구현에는 로그 파일 및 웹훅이 포함된다.

각 요청들은 연관된 단계(stage) 와 함께 기록될 수 있다. 정의된 단계는 다음과 같다.

  • RequestReceived - 감사 핸들러가 요청을 수신한 직후, 그리고 핸들러 체인으로 위임되기 전에 생성되는 이벤트에 대한 단계이다.
  • ResponseStarted - 응답 헤더는 전송되었지만, 응답 본문(body)은 전송되기 전인 단계이다. 이 단계는 오래 실행되는 요청(예: watch)에 대해서만 생성된다.
  • ResponseComplete - 응답 내용이 완료되었으며, 더 이상 바이트가 전송되지 않을 때의 단계이다.
  • Panic - 패닉이 발생했을 때 생성되는 이벤트이다.

감사 로깅 기능은 감사에 필요한 일부 컨텍스트가 요청마다 저장되기 때문에 API 서버의 메모리 사용량을 증가시킨다. 메모리 소비량은 감사 로깅 구성에 따라 다르다.

감사 정책

감사 정책은 기록해야 하는 이벤트와 포함해야 하는 데이터에 대한 규칙을 정의한다. 감사 정책 오브젝트 구조는 audit.k8s.io API 그룹에 정의되어 있다. 이벤트가 처리되면 규칙 목록과 순서대로 비교된다. 첫번째 일치 규칙은 이벤트의 감사 수준(audit level) 을 설정한다. 정의된 감사 수준은 다음과 같다.

  • None - 이 규칙에 해당되는 이벤트는 로깅하지 않는다.
  • Metadata - 요청 메타데이터(요청하는 사용자, 타임스탬프, 리소스, 동사(verb) 등)는 로깅하지만 요청/응답 본문은 로깅하지 않는다.
  • Request - 이벤트 메타데이터 및 요청 본문을 로깅하지만 응답 본문은 로깅하지 않는다. 리소스 외의 요청에는 적용되지 않는다.
  • RequestResponse - 이벤트 메타데이터 및 요청/응답 본문을 로깅한다. 리소스 외의 요청에는 적용되지 않는다.

--audit-policy-file 플래그를 사용하여 정책이 포함된 파일을 kube-apiserver에 전달할 수 있다. 플래그를 생략하면 이벤트가 기록되지 않는다. 감사 정책 파일에 rules 필드 반드시 가 제공되어야 한다. 규칙이 없는(0개인) 정책은 적절하지 않은(illegal) 것으로 간주된다.

다음은 감사 정책 파일의 예이다.

apiVersion: audit.k8s.io/v1 # 필수사항임.
kind: Policy
# Request Received 단계의 모든 요청에 대해 감사 이벤트를 생성하지 않음.
omitStages:
  - "RequestReceived"
rules:
  # RequestResponse 수준에서 파드 변경 사항 기록
  - level: RequestResponse
    resources:
    - group: ""
      # 리소스 "파드" 가 RBAC 정책과 부합하는 파드의 하위 리소스에 대한
      # 요청과 일치하지 않음.
      resources: ["pods"]
  # 메타데이터 수준에서 "pods/log", "pods/status"를 기록함.
  - level: Metadata
    resources:
    - group: ""
      resources: ["pods/log", "pods/status"]

  # "controller-leader" 라는 컨피그맵에 요청을 기록하지 않음."
  - level: None
    resources:
    - group: ""
      resources: ["configmaps"]
      resourceNames: ["controller-leader"]

  # 엔드포인트 또는 서비스의 "system:kube-proxy"에 의한 감시 요청 기록하지 않음.
  - level: None
    users: ["system:kube-proxy"]
    verbs: ["watch"]
    resources:
    - group: "" # 핵심 API 그룹
      resources: ["endpoints", "services"]

  # 인증된 요청을 특정 리소스가 아닌 URL 경로에 기록하지 않음.
  - level: None
    userGroups: ["system:authenticated"]
    nonResourceURLs:
    - "/api*" # 와일드카드 매칭(wildcard matching).
    - "/version"

  # kube-system에 컨피그맵 변경 사항의 요청 본문을 기록함.
  - level: Request
    resources:
    - group: "" # 핵심 API 그룹
      resources: ["configmaps"]
    # 이 정책은 "kube-system" 네임스페이스의 리소스에만 적용됨.
    # 빈 문자열 "" 은 네임스페이스가 없는 리소스를 선택하는데 사용할 수 있음.
    namespaces: ["kube-system"]

  # 메타데이터 수준에서 다른 모든 네임스페이스의 컨피그맵과 시크릿 변경 사항을 기록함.
  - level: Metadata
    resources:
    - group: "" # 핵심 API 그룹
      resources: ["secrets", "configmaps"]

  # 요청 수준에서 코어 및 확장에 있는 다른 모든 리소스를 기록함.
  - level: Request
    resources:
    - group: "" # 핵심 API 그룹
    - group: "extensions" # 그룹의 버전을 기재하면 안 된다.

  # 메타데이터 수준에서 다른 모든 요청을 기록하기 위한 모든 수집 정책.
  - level: Metadata
    # 이 정책에 해당하는 감시자와 같은 장기 실행 요청은
    # RequestReceived에서 감사 이벤트를 생성하지 않음.
    omitStages:
      - "RequestReceived"

최소 감사 정책 파일을 사용하여 Metadata 수준에서 모든 요청을 기록할 수 있다.

# Log all requests at the Metadata level.
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata

자체 감사 프로필을 만드는 경우 Google Container-Optimized OS에 대한 감사 프로필을 시작점으로 사용할 수 있다. 감사 정책 파일을 생성하는 configure-helper.sh 스크립트를 확인하면 된다. 스크립트로 대부분의 감사 정책 파일을 볼 수 있다.

정의된 필드에 대한 자세한 내용은 Policy configuration reference를 참조할 수도 있다.

감사 백엔드

감사 백엔드는 감사 이벤트를 외부 저장소에 유지한다. 기본적으로 kube-apiserver는 두 가지 백엔드를 제공한다.

  • 이벤트를 파일 시스템에 기록하는 로그 백엔드
  • 이벤트를 외부 HTTP API로 보내는 Webhook 백엔드

모든 경우에 감사 이벤트는 쿠버네티스 API의 audit.k8s.io API 그룹에서 정의한 구조를 따른다.

로그 백엔드

로그 백엔드는 감사이벤트를 JSONlines 형식으로 파일에 기록한다. 다음의 kube-apiserver 플래그를 사용하여 로그 감사 백엔드를 구성할 수 있다.

  • --audit-log-path 는 로그 백엔드가 감사 이벤트를 쓰는 데 사용하는 로그 파일 경로를 지정한다. 이 플래그를 지정하지 않으면 로그 백엔드가 비활성화된다. - 는 표준 출력을 의미한다.
  • --audit-log-maxage 는 오래된 감사 로그 파일을 보관할 최대 일수를 정의한다.
  • --audit-log-maxbackup 은 보관할 감사 로그 파일의 최대 수를 정의한다.
  • --audit-log-maxsize 는 감사 로그 파일이 로테이트 되기 전의 최대 크기(MB)를 정의한다.

클러스터의 컨트롤 플레인이 kube-apiserver를 파드로 실행하는 경우 감사 레코드가 지속되도록 정책 파일 및 로그 파일의 위치에 hostPath 를 마운트 해야한다. 예를 들면

    --audit-policy-file=/etc/kubernetes/audit-policy.yaml \
    --audit-log-path=/var/log/kubernetes/audit/audit.log

그런 다음 볼륨을 마운트 한다.

...
volumeMounts:
  - mountPath: /etc/kubernetes/audit-policy.yaml
    name: audit
    readOnly: true
  - mountPath: /var/log/kubernetes/audit/
    name: audit-log
    readOnly: false

그리고 마지막으로 hostPath 를 구성한다.

...
volumes:
- name: audit
  hostPath:
    path: /etc/kubernetes/audit-policy.yaml
    type: File

- name: audit-log
  hostPath:
    path: /var/log/kubernetes/audit/
    type: DirectoryOrCreate

웹훅 백엔드

웹훅 감사 백엔드는 원격 웹 API로 감사 이벤트를 전송하는데, 이는 인증 수단을 포함하여 쿠버네티스 API의 한 형태로 간주된다. 다음 kube-apiserver 플래그를 사용하여 웹훅 감사 백엔드를 구성할 수 있다.

  • --audit-webhook-config-file 은 웹훅 구성이 있는 파일의 경로를 지정한다. 웹훅 구성은 효과적으로 전문화된 kubeconfig이다.
  • --audit-webhook-initial-backoff 는 첫 번째 실패한 요청 후 다시 시도하기 전에 대기할 시간을 지정한다. 이후 요청은 지수의 백오프로 재시도 된다.

웹훅 구성 파일은 kubeconfig 형식을 사용하여 서비스의 원격 주소와 서비스에 연결하는 데 사용되는 자격 증명을 지정한다.

이벤트 배치

로그 및 웹 훅 백엔드는 모두 배치를 지원한다. 웹훅 사용을 예로 들면, 다음은 사용 가능한 플래그 목록이다. 로그 백엔드에 대해 동일한 플래그를 가져오려면 플래그 이름에 있는 webhooklog 로 바꾼다. 기본적으로 배치 기능은 webhook 에서 활성화되고 log 에서 비활성화된다. 마찬가지로, 기본적으로 쓰로틀링은 webhook 에서는 활성화되고 log 에서는 비활성화된다.

  • --audit-webhook-mode 은 버퍼링 전략을 정의한다. 다음 중 하나이다.
    • batch - 이벤트를 버퍼링하고 비동기식으로 배치한다. 이것이 기본값이다.
    • blocking - 각 개별 이벤트를 처리할 때 API 서버 응답을 차단한다.
    • blocking-strict - blocking과 동일하지만, RequestReceived 단계에서 감사 로깅 중에 오류가 발생하면 kube-apiserver에 대한 전체 요청이 실패한다.

다음 플래그는 batch 모드에서만 사용된다.

  • --audit-webhook-batch-buffer-size 는 배치하기 전에 버퍼링할 이벤트 수를 정의한다. 들어오는 이벤트의 비율이 버퍼를 초과하면 이벤트가 삭제된다.
  • --audit-webhook-batch-max-size 는 한 배치의 최대 이벤트 수를 정의한다.
  • --audit-webhook-batch-max-wait 는 대기열에서 이벤트를 무조건 배치하기 전에 대기할 최대 시간을 정의한다.
  • --audit-webhook-batch-throttle-qps 는 초당 생성되는 최대 평균 배치 수를 정의한다.
  • --audit-webhook-batch-throttle-burst 는 허용된 QPS가 이전에 충분히 활용되지 않은 경우 동시에 생성되는 최대 배치 수를 정의한다.

파라미터 튜닝

파라미터는 API 서버의 로드를 수용할 수 있도록 설정해야 한다.

예를 들어 kube-apiserver가 초당 100건의 요청을 수신하고 각 요청이 ResponseStartedResponseComplete 단계에서만 감사되는 경우 초당 생성되는 ≅200건의 감사 이벤트를 고려해야 한다. 일괄적으로 최대 100개의 이벤트가 있다고 가정할 때 초당 최소 2개의 쿼리 조절 수준을 설정해야 한다. 백엔드가 이벤트를 쓰는 데 최대 5초가 걸릴 수 있다고 가정하면 버퍼크기를 최대 5초의 이벤트를 보유하도록 설정해야 한다. 즉, 10개의 배치 또는 100개의 이벤트이다.

그러나 대부분의 경우 기본 매개 변수만 있으면 충분하며 수동으로 설정할 필요가 없다. kube-apiserver에서 노출된 다음과 같은 프로메테우스 메트릭과 로그에서 감사 하위 시스템의 상태를 모니터링할 수 있다.

  • apiserver_audit_event_total 의 메트릭에는 내보낸 감사 이벤트의 총 수가 포함된다.
  • apiserver_audit_error_total 의 메트릭에는 내보내기 중 오류로 인해 삭제된 총 이벤트 수가 포함된다.

로그 항목 자르기

로그 및 웹훅 백엔드는 모두 로깅되는 이벤트의 크기 제한을 지원한다. 예를 들어 로그 백엔드에 사용할 수 있는 플래그 목록은 다음과 같다.

  • audit-log-truncate-enabled 는 이벤트 및 자르기 배치가 활성화 되었는지 여부를 나타낸다.
  • audit-log-truncate-max-batch-size 는 기본 백엔드로 전송되는 배치의 바이트 단위의 최대 크기이다.
  • audit-log-truncate-max-event-size 는 기본 백엔드로 전송된 감사 이벤트의 바이트 단위의 최대 크기이다.

기본적으로 webhooklog 모두에서 자르기 기능이 비활성화되어 있으므로 이 기능을 활성화 하기 위해 클러스터 관리자는 audit-log-truncate-enabled 또는 audit-webhook-truncate-enabled 를 설정해야 한다.

다음 내용

2.6 - 로컬에서 텔레프레즌스를 이용한 서비스 개발 및 디버깅

쿠버네티스 애플리케이션은 일반적으로 각각 자체 컨테이너에서 실행되는 여러 개의 개별 서비스로 구성된다. 원격 쿠버네티스 클러스터 상에서 이러한 서비스를 개발하고 디버깅하려면 디버깅 도구를 실행하기 위해 동작 중인 컨테이너의 셸(shell)에 접근해야 하기 때문에 번거로울 수 있다.

텔레프레즌스(telepresence)는 원격 쿠버네티스 클러스터로 서비스를 프록시하면서 로컬에서 서비스를 개발 및 디버깅하는 과정을 용이하게 하는 도구이다. 텔레프레즌스를 사용하면 로컬 서비스에 디버거 및 IDE와 같은 사용자 지정 도구를 사용할 수 있고 원격 클러스터에서 실행되는 컨피그맵(ConfigMap), 시크릿(Secret) 및 서비스(Service)에 대한 전체 접근 권한을 서비스에 제공할 수 있다.

이 문서는 텔레프레즌스를 사용하여 원격 클러스터에서 실행 중인 서비스를 로컬로 개발하고 디버그하는 방법을 설명한다.

시작하기 전에

  • 쿠버네티스 클러스터가 설치되어 있어야 한다.
  • kubectl은 클러스터와 통신하도록 구성되어 있어야 한다.
  • 텔레프레즌스가 설치되어 있어야 한다.

로컬 머신을 원격 쿠버네티스 클러스터에 연결

텔레프레즌스를 설치한 후 텔레프레즌스 커넥트(telepresence connect)를 실행하여 데몬을 실행하고 로컬 워크스테이션을 클러스터에 연결한다.

$ telepresence connect
 
Launching Telepresence Daemon
...
Connected to context default (https://<cluster public IP>)

쿠버네티스 구문을 사용하여 서비스에 curl이 가능하다. 예, curl -ik https://kubernetes.default

기존 서비스 개발 또는 디버깅

쿠버네티스에서 애플리케이션을 개발할 때 일반적으로 단일 서비스를 프로그래밍하거나 디버그한다. 서비스를 테스트 및 디버깅하기 위해 다른 서비스에 접근이 필요할 수 있다. 지속적 배포(continuous deployment) 파이프라인을 사용하는 것도 한 가지 옵션이지만, 가장 빠른 배포 파이프라인이라도 프로그래밍 또는 디버그 주기에 지연이 발생할 수 있다.

telepresence intercept $SERVICE_NAME --port $LOCAL_PORT:$REMOTE_PORT 명령을 사용하여 원격 서비스 트래픽을 다시 라우팅하기 위한 "인터셉트(intercept)"를 생성한다.

아래의 각 항목에 대한 설명을 참고한다.

  • $SERVICE_NAME은 로컬 서비스의 이름이다.
  • $LOCAL_PORT는 서비스가 로컬 워크스테이션에서 실행 중인 포트이다.
  • $REMOTE_PORT는 서비스가 클러스터에서 수신하는 포트이다.

이 명령을 실행하면 원격 쿠버네티스 클러스터의 서비스 대신 로컬 서비스에 원격 트래픽을 보내도록 텔레프레즌스에 지시한다. 서비스 소스 코드를 로컬에서 편집하고 저장하여 원격 애플리케이션에 접근할 때 해당 변경 사항이 즉시 적용되는지 확인한다. 디버거 또는 기타 로컬 개발 도구를 사용하여 로컬 서비스를 실행할 수도 있다.

텔레프레즌스는 어떻게 작동하는가?

Telepresence는 원격 클러스터에서 실행 중인 기존 애플리케이션의 컨테이너 옆에 트래픽 에이전트 사이드카(sidecar)를 설치한다. 그런 다음 파드로 들어오는 모든 트래픽 요청을 캡처하고, 이를 원격 클러스터의 애플리케이션에 전달하는 대신, 모든 트래픽(글로벌 인터셉트를 생성하는 경우) 또는 일부 트래픽(개인 인터셉트를 생성하는 경우)을 로컬 개발 환경으로 라우팅한다.

다음 내용

핸즈온 튜토리얼에 관심이 있다면 구글 쿠버네티스 엔진 상의 방명록 애플리케이션을 로컬로 개발하는 과정을 안내하는 이 튜토리얼을 확인한다.

자세한 내용은 텔레프레즌스 웹사이트를 참조한다.

2.7 - 윈도우 디버깅 팁

노드-수준 트러블슈팅

  1. 내 파드가 "Container Creating"에서 멈췄거나 계속해서 다시 시작된다.

    퍼즈(pause) 이미지가 OS 버전과 호환되는지 확인한다. 퍼즈 컨테이너에서 최신 / 추천 퍼즈 이미지 및 추가 정보를 확인한다.

  2. 내 파드의 상태가 ErrImgPull 또는 ImagePullBackOff이다.

    파드가 호환되는 윈도우 노드에 스케줄링되었는지 확인한다.

    각 파드와 호환되는 노드를 찾는 방법에 대한 추가 정보는 이 가이드를 참고한다.

네트워크 트러블슈팅

  1. 내 윈도우 파드에 네트워크 연결이 없다.

    가상 머신을 사용하는 경우, 모든 VM 네트워크 어댑터에 MAC 스푸핑이 활성화되어 있는지 확인한다.

  2. 내 윈도우 파드가 외부 리소스를 ping 할 수 없다.

    윈도우 파드에는 현재 ICMP 프로토콜용으로 프로그래밍된 아웃바운드 규칙이 없다. 그러나 TCP/UDP는 지원된다. 클러스터 외부 리소스에 대한 연결을 시연하려는 경우, ping <IP>를 대응되는 curl <IP>명령으로 대체한다.

    여전히 문제가 발생하는 경우, cni.conf의 네트워크 구성에 특별히 추가 확인이 필요하다. 언제든지 이 정적 파일을 편집할 수 있다. 구성 업데이트는 새로 생성된 모든 쿠버네티스 리소스에 적용된다.

    쿠버네티스 네트워킹 요구 사항(쿠버네티스 모델 참조) 중 하나는 클러스터 통신이 NAT 없이 내부적으로 발생해야 한다는 것이다. 이 요구 사항을 준수하기 위해 아웃바운드 NAT가 발생하지 않도록 하는 모든 통신에 대한 ExceptionList가 있다. 그러나 이것은 쿼리하려는 외부 IP를 ExceptionList에서 제외해야 함도 의미한다. 그래야만 윈도우 파드에서 발생하는 트래픽이 제대로 SNAT 되어 외부에서 응답을 받는다. 이와 관련하여 cni.confExceptionList는 다음과 같아야 한다.

    "ExceptionList": [
                    "10.244.0.0/16",  # 클러스터 서브넷
                    "10.96.0.0/12",   # 서비스 서브넷
                    "10.127.130.0/24" # 관리(호스트) 서브넷
                ]
    
  3. 내 윈도우 노드가 NodePort 서비스에 접근할 수 없다.

    노드 자체에서는 로컬 NodePort 접근이 실패한다. 이것은 알려진 제약사항이다. NodePort 접근은 다른 노드 또는 외부 클라이언트에서는 가능하다.

  4. 컨테이너의 vNIC 및 HNS 엔드포인트가 삭제된다.

    이 문제는 hostname-override 파라미터가 kube-proxy에 전달되지 않은 경우 발생할 수 있다. 이를 해결하려면 사용자는 다음과 같이 hostname을 kube-proxy에 전달해야 한다.

    C:\k\kube-proxy.exe --hostname-override=$(hostname)
    
  5. 내 윈도우 노드가 서비스 IP를 사용하여 내 서비스에 접근할 수 없다.

    이는 윈도우에서 현재 네트워킹 스택의 알려진 제약 사항이다. 그러나 윈도우 파드는 서비스 IP에 접근할 수 있다.

  6. kubelet을 시작할 때 네트워크 어댑터를 찾을 수 없다.

    윈도우 네트워킹 스택에는 쿠버네티스 네트워킹이 작동하기 위한 가상 어댑터가 필요하다. (어드민 셸에서) 다음 명령이 결과를 반환하지 않으면, Kubelet이 작동하는 데 필요한 필수 구성 요소인 가상 네트워크 생성이 실패한 것이다.

    Get-HnsNetwork | ? Name -ieq "cbr0"
    Get-NetAdapter | ? Name -Like "vEthernet (Ethernet*"
    

    호스트 네트워크 어댑터가 "Ethernet"이 아닌 경우, 종종 start.ps1 스크립트의 InterfaceName 파라미터를 수정하는 것이 좋다. 그렇지 않으면 start-kubelet.ps1 스크립트의 출력을 참조하여 가상 네트워크 생성 중에 오류가 있는지 확인한다.

  7. DNS 해석(resolution)이 제대로 작동하지 않는다.

    섹션에서 윈도우에서의 DNS 제한을 확인한다.

  8. kubectl port-forward가 "unable to do port forwarding: wincat not found" 에러와 함께 실패한다.

    이 기능은 퍼즈 인프라 컨테이너 mcr.microsoft.com/oss/kubernetes/pause:3.6wincat.exe를 포함시킴으로써 쿠버네티스 1.15에서 구현되었다. 지원되는 쿠버네티스 버전을 사용하고 있는지 확인한다. 퍼즈 인프라 컨테이너를 직접 빌드하려면 wincat을 포함시켜야 한다.

  9. 내 윈도우 서버 노드가 프록시 뒤에 있기 때문에 쿠버네티스 설치에 실패한다.

    프록시 뒤에 있는 경우, 다음 PowerShell 환경 변수를 정의해야 한다.

    [Environment]::SetEnvironmentVariable("HTTP_PROXY", "http://proxy.example.com:80/", [EnvironmentVariableTarget]::Machine)
    [Environment]::SetEnvironmentVariable("HTTPS_PROXY", "http://proxy.example.com:443/", [EnvironmentVariableTarget]::Machine)
    

플란넬 트러블슈팅

  1. 플란넬(flannel)을 사용하면 클러스터에 다시 조인(join)한 후 노드에 이슈가 발생한다.

    이전에 삭제된 노드가 클러스터에 다시 조인될 때마다, flannelD는 새 파드 서브넷을 노드에 할당하려고 한다. 사용자는 다음 경로에서 이전 파드 서브넷 구성 파일을 제거해야 한다.

    Remove-Item C:\k\SourceVip.json
    Remove-Item C:\k\SourceVipRequest.json
    
  2. flanneld가 "Waiting for the Network to be created" 상태에서 멈춘다.

    이슈에 대한 수많은 보고가 있다. 플란넬 네트워크의 관리 IP가 설정될 때의 타이밍 이슈일 가능성이 높다. 해결 방법은 start.ps1을 다시 시작하거나 다음과 같이 수동으로 다시 시작하는 것이다.

    [Environment]::SetEnvironmentVariable("NODE_NAME", "<Windows_Worker_Hostname>")
    C:\flannel\flanneld.exe --kubeconfig-file=c:\k\config --iface=<Windows_Worker_Node_IP> --ip-masq=1 --kube-subnet-mgr=1
    
  3. /run/flannel/subnet.env 누락으로 인해 윈도우 파드를 시작할 수 없다.

    이것은 플란넬이 제대로 실행되지 않았음을 나타낸다. flanneld.exe를 다시 시작하거나 쿠버네티스 마스터의 /run/flannel/subnet.env에서 윈도우 워커 노드의 C:\run\flannel\subnet.env로 파일을 수동으로 복사할 수 있고, FLANNEL_SUBNET 행을 다른 숫자로 수정한다. 예를 들어, 노드 서브넷 10.244.4.1/24가 필요한 경우 다음과 같이 설정한다.

    FLANNEL_NETWORK=10.244.0.0/16
    FLANNEL_SUBNET=10.244.4.1/24
    FLANNEL_MTU=1500
    FLANNEL_IPMASQ=true
    

추가 조사

이러한 단계로 문제가 해결되지 않으면, 다음을 통해 쿠버네티스의 윈도우 노드에서 윈도우 컨테이너를 실행하는 데 도움을 받을 수 있다.