본문 바로가기

프로그래밍/kubernetes

CKA 시험 관련 잡다한 정리(4)

아래의 yaml 파일에서 정의한 형식으로 Pod을 생성한다고 가정해보자.

apiVersion: v1
kind: Pod
metadata:
  name: random-number-generator
spec:
  containers:
  - image: alpine
    name: alpine
    command: ["/bin/sh", "-c"]
    args: ["shuf -i 0-100 -n 1 >> /opt/number.out;"]
    volumeMounts:
    - mountPath: /opt
      name: data-volume
  volumes:
  - name: data-volume
    hostPath:
      path: /data
      type: Directory

이 Pod은 0부터 100까지 무작위로 숫자를 1개 뽑아서 그것을 /opt/number.out 파일에 기록하게 된다. 그러나 Pod이 죽으면 /opt/number.out 파일로 날라가버리기 때문에 이를 관리하기 위해서 volume을 만들어서 관리한다. spec.volumes에서 volume에 대한 정의를 하면서 volume의 이름과 디렉토리(여기서 말하는 디렉토리는 Pod이 서비스되는 Node의 디렉토리를 의미한다)를 정의하고 spec.containers.volumeMounts에서 container의 디렉토리와 volume을 만들때 사용한 volume 이름을 같이 설정하는 방식으로 volume을 연결하게 된다. 위의 yaml 파일의 예를 들어서 보자면 다음의 관계가 성립된다.

 

Node의 /data 디렉토리 = container의 /opt 디렉토리

 

그러나 이러한 방법의 문제점은 Node의 디렉토리를 사용하는데 문제점이 있다. 예를 들어 Node가 2(Node01, Node02)대가 있는 상황에서 위의 설정을 이용해 각 Node에 Pod이 만들어졌다고 가정해보자. 사용자가 Node01에서 실행중인 Pod을 들어와서 파일을 volume에 기록하게 되면 해당 파일은 Node01에 생기게 된다. 그러나 Pod은 어느 Node를 접근하든간에 내가 만든 파일은 접근가능해야 하기 때문에 Node02에서 실행중인 Pod을 들어가게 되면 Node01에서 만든 파일을 억세스할수 없게 된다. 이러한 문제점을 해결하기 위해 Volume을 Node와는 별개의 외부 저장소를 이용하게 되는 방법을 사용한다. 이러한 저장소는 가깝게는 NFS를 사용하는 방법이 있고, kubernetes를 cloud 환경에서 서비스 할 경우 cloud에서 제공하는 파일시스템 서비스를 이용하는 방법도 있다.

 

그러나 이렇게 Volume으로 어떤 것을 쓰겠다는 것을 구체적으로 명시하게 될 경우 해당 Pod을 자기 부서만 사용한다고 가정할 경우엔 별 문제가 없으나 동일한 Pod을 다른 부서에서 구성하고자 할 경우엔 해당 Volume 까지도 같이 구현해야 하는 문제가 있다(이해하기 쉽게 예시를 들면 A 부서에서 NFS를 Volume으로 사용하는 Pod을 만들어서 사용하고 있어쓴데 동일한 기능을 사용하는 Pod을 AWS ELS를 사용하는 B 부서에서 사용하려 할 경우 B 부서는 AWS EBS로 Volume을 구현할수 있는데도 불구하고 NFS를 강제로 구축해서 사용해야 한다. 물론 Pod의 yaml 파일을 수정할 수 있겠지만 그것은 결국 Pod이 운영되는 환경에 따라 거기에 맞는 Volume으로 일일이 수정해야 하는 문제가 생기게 된다) 

 

이러한 문제점을 해결하기 위해 PV(Persistent Volume)이 kubernetes에서 생기게 되었다. PV는 kubernetes cluster 사용자가 어플리케이션을 배포할때 해당 어플리케이션에서 사용될 수 있도록 관리자가 구성해놓은 클러스터 전체 스토리지 볼륨 Pool이다. kubernetes cluster 사용자는 PVC(Persistent Volume Claim)을 이용하여 이렇게 만들어져 있는 Pool에서 스토리지를 선택할 수 있다. PV는 다음과 같은 구조의 yaml 파일로 생성할 수 있다.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-vol1
spec:
  accessModes
  - ReadWriteOnce
  capacity:
    storage: 1Gi
  hostPath:
    path: /tmp/data

accessModes 항목에 들어갈수 있는 값으로는 3가지 종류가 있다

ReadWriteOnce : 1:1 마운트만 가능, 읽기, 쓰기 가능

ReadOnlyMany: 1:N 마운트 가능, 읽기 전용

ReadWriteMany: 1:N 마운트 가능, 읽기, 쓰기 가능

Volume이  어떤 storage solution(ex: AWS EBS, NFS, Azure Disk, Azure File 등)을 사용하는지에 따라 solution이 지원가능한 1:1 또는 N 마운트 부분과 읽기, 쓰기 기능에 맞춰서 accessModes를 설정해야 한다.

capaity.storage는 Volume 크기를 설정하는 것으로 여기서는 1Gi byte를 설정했다

hostPath.path는 Host에서 Volume이 사용하는 경로를 설정한다.

만약 storage solution을 별도로 사용하는 것이 있으면 hostPath 항목을 사용하는게 아니라 storage solution에 따른 항목 설정을 해준다(ex: 만약 AWS EBS를 사용중이라면 hostPath.path를 사용하는게 아니라 awsElasticBlockStore 항목의 volumeID 항목과 fsType 항목을 설정해야 한다)

 

kubernetes 관리자가 PV를 만들고 kubernetes 사용자가 storage를 이용하기 위해 PVC(Persistent Volume Claim)을 만든다. 이 관계는 이렇게 보면 된다. 관리자는 kubernetes에 여러 다양한 종류의 PV를 만들어서 설정해둔다. 위에서 봤듯이 PV에는 읽기/쓰기 모드와 용량이 기록되어 있다. 그리고 pod을 만들어서 배포하는 kubernetes 사용자가 Pod이 운영되는데 필요한 storage에 대한 정의(어떠한 읽기/쓰기 모드를 사용하는지, 용량이 얼마나 필요한지 등)을 PVC로 만든다. 그러면 kubernetes는 PVC를 만족하는 PV를 찾아서 이를 연결해준다. 만약 PVC를 만족하는 PV를 찾지 못하면 PVC는 PVC를 만족하는 PV를 찾을때까지 대기(Pending) 상태에 들어가게 된다. PVC는 아래의 yaml 구조의 형태로 만들게 된다.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  accessModes
  - ReadWriteOnce
  resources:
    requests:
      storage: 500Mi

위에서 PV를 다룰때 설정하지 않은 항목이 있는데 spec.persistentVolumeReclaimPolicy 항목이다. 이 항목은 PVC가 삭제될때 해당 PVC와 연결되어 있는 PV는 어떤 행동을 취하는가에 대한 정의를 하게 된다. 여기에는 3가지 값중 하나가 설정된다

Retain : 스토리지에 저장되어 있는 데이터는 PVC가 삭제되어도 그대로 유지시키고(수동으로 데이터를 삭제해야 한다는 뜻) persistentVolumeReclaimPolicy을 설정하지 않을 경우 이 값이 기본값으로 적용된다.

Delete : PVC를 삭제하면 PV도 삭제한다.

Recycle : PVC를 삭제하면 PV에 저장되어 있는 데이터만 자동으로 삭제하고 PV는 삭제하지 않는다. 그러나 이 설정값은 deprecated 되어 있는 상태임

 

이렇게 PV와 PVC를 만들면 다음과 같이 Pod에서 사용될 수 있다.

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: myfrontend
    image: nginx
    volumeMounts:
    - mountPath: "/var/www/html"
      name: mypd
  volumes:
  - name: mypd
    persistentVolumeClaim:
      claimName: myclaim

 

PV를 만들려면 사전에 외부 스토리지를 정의해야 했다(ex: AWS EBS를 사용하는 PV를 만들려면 사전에 AWS EBS에 스토리지를 만들고 그것을 PV에 설정하는 방법으로 만들다보니..) 이렇게 정의를 먼저 해야 하다보니 이 부분이 불편한 점이 있어서 PVC를 만들때 PVC가 요청하는 조건에 맞는 Volume이 없으면 자동으로 PV와 외부 스토리지를 같이 만들어서 PVC가 사용하게 해주는 Dynamic Provisioning 기능을 제공해준다. 이 외부스토리지를 만들때 Storage Class(SC)를 참고해서 만들게 된다. 즉 SC에 있는 정보를 이용해서 외부 스토리지를 만들게 되는 것이다. 아래의 yaml 파일은 AWS EBS를 사용하는 SC이다.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: slow
provisioner: kubernetes.io/aws-ebs
parameters:
  type: io1
  iopsPerGB: "10"
  fsType: ext4

metadata.name 항목에 SC 이름을 설정해주고 provisioner에는 스토리지별로 지정되어 있는 provisioner를 넣어준다(이것은 kubernetes storage class document 를 보면 정리되어 있으니 참고) parameters 항목에서는 해당 provisioner를 이용하여 스토리지를 만들때 사용되어야 할 파라미터 항목들을 넣어둔다(이것도 kubernetes storage class 문서에 정리되어 있음)

이렇게 SC를 만들었으면 이 SC를 사용하는 PVC를 만든다.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: awsebspvc
spec:
  storageClassName: slow
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

spec.storageClassName 항목에 SC 이름을 넣어준다. 이렇게 설정해주면 SC와 PVC에 입력한 정보들을 기반으로 PV를 자동으로 만든뒤 만들어진 PV와 PVC를 연결해준다

 

Dynamic Provisioning을 통해 만들어진 Volume의 Reclaim Policy는 Delete로 설정되기 때문에 PVC를 지우면 PV도 지워지면서 외부 스토리지(여기선 AWS EBS) Volume 또한 같이 삭제된다. 

 

CKS 시험에 비추어 개인적인 사견을 첨언하자면 AWS나 GCE를 이용할 수 있는 환경은 아니기 때문에 아마 이를 이용해서 Storage Class를 만드는 문제는 나오지않을것이다. 대신 Local Volume을 이용해서 만드는건 나올 수 있기 때문에 이걸 만드는 yaml 파일을 아래에 써둔다

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

강의 동영상에서도 Local Volume 기반으로 Storage Class를 만드는 문제가 나왔었다. 여기서 알아두어야 할 항목으로 volumeBindingMode 항목이 있다. 이 항목은 만들어지는 Volume을 사용하는 시점과 Dynamic Provisioning 의 시작 시점을 제어하게 된다. Default로는 Immediate 로 PVC가 생성이 되는 시점에 바로 Dynamic Provisioning을 통해 Volume을 만들고 이를 이용하는 PV를 만들게 된다. 그러나 구성 환경으로 인해 Schdule 되지 않은 Pod이 발생할 수 있다. 그래서 이러한 문제를 해결하기 위해 WaitForFirstConsumer 로 설정하는 상황도 있다. 이걸로 설정하면 Pod이 Scheduling 될때 까지 Dynamic Provisioning. 작업과 PV 생성 작업, PV와 PVC를 연결하는 작업을 지연시킨다(즉 Pod이 Node에 배포되고 난 뒤에 이러한 작업들이 진행된다는 뜻)


Kubernetes Cluster를 구성하는 Node 들은 network와 연결된 1개 이상의 network interface를 가지고 있다.

 

Master Node에 있는 각각의 구성요소가 통신할때 사용하게 되는 port는

kube-apiserver는 6443, kubelet은 10250, kube-scheduler는 10251, kube-controller-manager는 10252 port를 사용한다

ETCD의 경우 2379 port를 사용한다. 그러나 Master Node를 여러대 두면서 ETCD를 Cluster로 구성하게 되면 ETCD 서로간의 통신을 해야 하는데 이러한 용도로 사용할때는 2380 port를 사용한다.

 

Worker Node에 있는 각각의 구성요소가 통신할때 사용하게 되는 port는

kubelet은 10250, Service를 통해 외부에서 Worker Node 로 접속할때 사용되는 Port는 30000-32767 Port를 사용한다.

 

이러한 port 들에 대한 정보는 Installing kubeadm 문서를 보면 확인 가능하다

 

kubernetes에는 자체 DNS Server(CoreDNS)가 설치되어 있다. 이 DNS 서버의 역할은 Pod과 Pod을 중간에 연결해주는 Service에 대해 Service 이름과 Service IP를 매핑시켜주는 역할을 하고 있다. 아래의 그림으로 설명을 대신한다.

KubeDNS(CoreDNS)의 역할

이렇게 Pod이 다른 Pod과 연결되어 있는 Service를 이용해서 통신하려 할때 연결하고자 하는 서비스 이름을 도메인 이름으로 삼아서 통신할 수 있게 되며 이 과정에서 CoreDNS가 Service이름과 Service IP를 연결해주는 DNS 서버 역할을 해준다. 그러나 이것은 이 통신과 관련된 모든 Resource가 같은 namespace 안에 존재할때 가능한 방법이다. 만약 모든 Resource들이 서로 다른 namespace에 존재한다면 그땐 어떻게 될까? 아래 그림을 보자.

서로 다른 Namespace에 있는 Resource들을 DNS가 매핑한 내용

DNS가 보관하는 내용은 Hostname, Namespace, Type, Root 그리고 이 4개를 조합했을때 매핑되는 IP Address를 보관하게 된다. 예를 들어 위에서는 Resource들이 모두 같은 Namespace에 있었기 때문에 생략이 가능했지만 만약 서로 다른 Namespace에 존재하는 Resource들이라면 이 모두를 다 밝혀줘야 한다. 조합은 Hostname.Namespace.Type.Root 의 조합으로 이루어진다. 그림을 보면 10-244-2-5.apps.pod.cluster.local 로 조합이 되어 있는데 이것은 apps Namespace에 있는 web Pod을 가리키게 된다. 그러나 Service와 Pod의 결합관계를 고려하면 이것은 web-service.apps.svc.cluster.local 을 사용해도 동일한 결과를 가져오게 된다(가르키는 Resource 자체는 다르지만 Service와 그 Service와 연결되는 Pod을 생각해보면 최종 동작 결과는 동일한 결과를 가져온다)

 

Kubernetes에서 Resource를 가리키는 DNS 서버가 필요한 이유는 이렇다. 우리가 Pod을 생성할때 기존의 Network 게념에서 놓고보면 Pod에서 실행중인 container의 /etc/hosts 파일에 다른 Pod의 ip 주소를 Pod 이름과 같이 넣어야 할 것이다. 그러나 이러한 방법은 Pod이 1~2개 생기는 정도에 일단 Pod이 만들어지면 Pod 이 불변한다는 전제하에 가능한 방법이지, 여러개의 Pod을 동시다발적으로 만들어야 하며 한 종류의 Pod이 생성되어야 할 갯수가 동적으로 상황에 따라 변해가는 환경에서는 이러한 방법을 사용할 수 없다. 그래서 이러한 Pod 의 이름(hostname)과 매핑되는 IP 주소를 보관하는 DNS 서버를 kubernetes 안에 별도로 두고 각각의 Pod에서는 /etc/resolv.conf 파일에 이 DNS 서버를 추가함으로써 이러한 상황에 적절하게 대비하게끔 한 것이다. 

 

Kubernetes 1.12 이전 버전에서는 이러한 역할을 kube-dns가 했지만 1.12 부터는 CoreDNS가 이 역할을 대신하게 되었다. CodeDNS는 kube-namespace에 있는 Pod의 형태로 설치되어진다. Pod이 2개가 생성되며(Deployment 형태로 생성되어져서 Pod이 2개가 만들어짐)

CoreDNS Pod이 만들어지면 이 Pod과 연결되는 ClusterIP Service가 만들어지는데 kubectl get svc -n kube-system 을 통해 검색되는 kube-dns Service 이다. 그러면 각 Pod들의 /etc/resolv.conf 파일에 DNS 서버를 추가할때 이 Service의 IP를 추가해 줌으로써 CoreDNS을 접근할수 있게 해준다.

 

CoreDNS의 설정파일은 CodeDNS Pod을 배포한 Deployment의 상세(describe)를 보면 그 파일 이름과 위치(/etc/coredns/Corefile)를 알 수 있다(kubectl describe deployment coredns -n kube-system) 이 환경 설정 파일에 대한 내용은 configmap 으로 저장되기 때문에 환경설정 파일의 내용을 볼려면 configmap 의 상세내용을 확인하면 알 수 있다(deployment codedns의 상세 내용에 coredns란 이름의 configmap을 사용하는 것을 확인할 수 있음, kubectl describe configmap coredns -n kube-system)

 

위에서도 설명했듯이 이론적으로는 별다른 설정만 없으면 Pod으로도 가능하다고 했는데 실제 문제를 풀면서 확인해본바로는 Pod으로 direct로 연결하는게 아니라 연결하고자 하는 Pod과 연결되어 있는 Service를 연결하는 방식으로 진행된다(위의 그림을 예시로 빌어 얘기하자면 test Pod과 web Pod을 직접 연결하는게 아니라 web Pod과 연결되어 있는 Service인 web-service를 test Pod이 연결하는 방식) 그래서 Pod과 Pod 간의 연결 관련 문제를 풀때는 연결 목적지가 되는 Pod이 속한 namespace에 연결 목적지인 Pod과 연계되는 Service가 있는지 그걸 찾아야 한다. From-To Pod이 모두 동일한 Namespace에 있어도 연결 대상의 Pod과 연계되는 Service를 연결하는 방법으로 풀어야 한다(이걸 알아내는 방법은 연결 대상이 되는 Pod이 가지고 있는 label을 먼저 알아낸뒤 Service의 상세(describe)를 조회해서 상세내용중 Selector 항목에서 보여주는 label과 일치하는지 알아내면 된다(pod의 label을 보는 방법은 kubectl get pod --show-labels 를 실행하면 Pod이 가지고 있는 label들을 보여준다)


Ingress 강좌의 연습문제를 풀때 metadata.annotaions 항목에 하위 항목으로 nginx.ingress.kubernetes.io/rewrite-target 를 넣고 그 값을 / 를 널어줘야 정상동작 했던 상황이 있었다. yaml 로 표현하면 아래와 같다

apiVersion: networking.k8s.io/v1 
kind: Ingress
metadata:
  name: ingress-pay
  namespace: critical-space
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /

강좌에서 만든 Service를 그대로 예시로 해서 설명하도록 하겠다.  watch-app pod과 연결된 watch-svc service가 있다고 가정해보자.  http://watch-svc:port 번호/ 로 브라우저나 curl로 호출하면 응답이 올것이다. 그러나 여기에 ingress가 끼어있어서 ingress로 호출하게 되면 아래와 같은 현상이 발생한다.

 

http://ingress-service:port 번호/watch -> http://watch-svc:port 번호/watch

 

우리가 의도한 것은 http://ingress-service:port 번호/watch 를 호출하면 http://watch-svc:port 번호/ 즉 watch-app pod의 container ROOT 경로를 호출하기를 원한 것인데 그게 아니라 container root의 /watch를 호출하게 되는 상황이 되어버리기 때문에 Ingress 를 만든 의도와는 다르게 호출되는 상황이 발생한다. 그래서 ingress를 이용하여 호출할때 ingress의 path에 따라 연결된 Service의 ROOT 경로를 호출하게 할려면 redirection을 할 필요가 발생하게 되는 것이다. nginx.ingress.kubernetes.io/rewrite-target은 ingress 경로를 호출할때 ingress와 연결된 Service의 어떤 경로로 redirect를 해야하는지를 설정하는 부분이 되는것이다. 이 항목에 값을 /로 설정하면 ingress와 연결된 Service의 Root 경로로 redirect 하라는 의미가 된다. 만약 위와 같이 설정된 상태에서 log 경로를 호출하게 되면 다음과 같이 하면 된다

 

http:// ingress-service:port 번호/watch/log -> http://watch-svc:port 번호/log

 

이렇게 redirect를 어디로 해줘야 하는지에 대한 생각을 한 후 거기에 맞는 값을 설정하면 된다.

 

이 부분을 공부한뒤 생각은 해봤는데..이거는 웬지 CKA 시험에 나오지는 않을듯 싶다. 이게 나올려면 nginx-ingress-controller를 설치해야 가능한건데 시험보는 상황에서는 nginx-ingress-controller 관련 설치 페이지를 볼 수도 없고 저 annotation 에 대한 내용도 nginx-ingress-controller 페이지를 가야 있기 때문에 저렇게까지는 안나올듯 싶다. 시험문제에 대한 Pod에 대한 설계를 할때 ingress에 /watch 패턴을 설정했으면 Pod의 watch를 가게끔 구조를 그렇게 만들었을것 같다. 이 부분은 시험을 볼때 ingress에 대한 문제가 나오면 업데이트 하도록 하겠다.


Master Node를 2대 이상으로 구성해서 장애에 대비하고자 할 경우에는 고려해야 할 사항이 있다. 먼저 kubernetes에 처리 요청을 할 때 그것을 가장 먼저 받는 kube-apiserver가 있다. 어느 한쪽 Master Node의 kube-apiserver에서 처리하게끔 집중시켜버리면 부하가 발생되기 때문에 kube-apiserver 앞단에는 nginx나 haproxy 같은 load balancer를 두어서 여러 Master Node에 존재하는 kube-apiserver 중 한 곳으로 연결되게끔 분기처리를 시켜준다.(Acrive-Active 방식)

그리고 고려해야 할 부분이 kube-controller-manager가 있다. kube-controller-manager의 역할은 Pod의 상태를 감시하고 문제가 발생할 경우 문제의 Pod을 삭제하고 재생성하는 역할인데 Master Node가 여러개 있을 경우 각각의 Master Node에 있는 kube-controller-manager가 Pod을 감시하면서 문제를 해결하려 하기 때문에 필요 이상의 Pod이 생기는 상황이 발 생할 수 있다. 이를 막기 위해서는 여러 Master Node에 있는 kube-controller-manager중 대표격으로 1개만 동작하다가 동작중인 kube-controller-manager에게 문제가 생기면 대기중인 kube-controller-manager가 대표로 설정되어서 동작하는 방식으로 진행한다(Active-Standby 방식) 이를 위해서 kube-controller-manager를 실행할때 다음의 옵션을 걸어둔다

kube-controller-manager --leader-elect true
                        --leader-elect-lease-duration 15s
                        --leader elect-renew-deadline 10s
                        --leader-elect-retry-period 2s

--leader-elect 옵션은 여러 kube-controller-manager 중에서 대표격인 리더를 뽑는 작업을 한다는 설정이고, --leader-elect-lease-durationleader로 최종 확정 될때까지의 대기 시간을 설정하는 것이다. API 문서 내용을 보고 동작방식을 해석해보자면 여러 kube-controller-manager 중 1개를 어떤 로직을 거쳐 leader(B)로 선출한 뒤 문제가 발생한 기존 leader(A)가 복귀해서 일을 할 수 있을지를 기달려주는 시간을 가진 후  그 시간을 기달려도 복구가 안되면 새로 leader로 뽑은 것(B)이 최종 leader가 되는 것이다. 이 옵션은 그런 기달려주는 시간을 설정하는 것이다.

이해를 돕기 위해 실생활에 비유해서 설명하겠다. 예를 들어 어느 학급에서 일하던 반장(A)이 교통사고가 나서 정상적으로 반장 역할을 수행할 수 없게 되었다. 그래서 학급생들이 새로운 반장을 뽑기 위해 투표를 해서 새 반장(B)이 선출되었다. 그러나 선출된 반장이 바로 반장을 수행하는게 아니라 기존 반장이 회복해서 다시 일을 할수 있을지를 15일 정도 기달려보고 만약 기존 반장이 회복되어서 정상적으로 반장 역할을 수행할 수 있으면 기존 반장(A)이 계속 역할을 수행하는 것이고 15일을 기달려도 반장이 회복이 되질 않아서 반장 역할을 할 수 없으면 새 반장(B)이 반장 역할을 담당하게 된다. 여기서 예시로 얘기했던 기달리는 시간인 15일을 설정하는 것이 바로 이 옵션인것이다.

--leader-elect-renew-deadline 옵션은 문제가 발생된 기존 leader가 문제가 발생한 시점부터 정상수행을 할 수 있다고 알려줘야 하는 기한을 설정하는 것이다. 이해를 돕기 위해 위에서 들었던 예시에 비추어 설명하겠다. 학급 내규에 반장이 사고로 인해 정상적인 역할을 수행할 수 없는 상황에서 다시 회복되었을 경우 사고가 난 일시부터 10일 이내에 알려줘야 한다는 규정이 있다. 그러면 반장은 병원에서 치료 받으면서 10일 안에는 회복되어 다시 반장직을 수행할 수 있다고 학급 임원들에게 알려줘야 한다. 바로 이 10일 이란 개념이 이 옵션인 것이다. 즉 문제가 발생한 leader는 이 옵션에서 설정한 시간 내에 문제점이 해결되었다고 알려줘야 한다는 것이다(물론 문제가 해결된 상태여야 한 것은 당연한 것이고..) 그래서 이 옵션값은 --leader-lease-duration 에 설정한 값보다 작거나 같아야한다.

마지막으로 --leader-elect-retry-period는 leader가 아닌 kube-controller-manager가 leader를 뽑아야하는 상황인지 체크하는 작업의 interval을 설정해주는 것이다. leader가 아닌 kube-controller-manager는 현재 leader를 뽑아야 하는 상황인지를 주기적으로 체크해서 leader를 뽑아야 하는 상황이면 leader를 뽑는 로직을 수행하게 된다(이게 영문 API 설명을 한글로 번역해보면 이런 내용이 아닌데 Deep dive into Kubernetes Simple Leader Election 글을 보고 leader를 뽑는 과정을 보면서 해석하게 되었다.

(미리 밝혀두지만 leader를 뽑는 과정에 대한 이해 없이 그냥 kubernetes kube-controller-manager 문서만 보고 해석한지라 정확하다고 말하기는 어렵다. 각자 내가 설명한 내용에 대한 검증을 진행하길 권한다)


kubeadm install 실습시 weavenet network plugin을 설치하는 문제가 나왔는데 kubernetes 최신 버전인 1.20 문서에서는 이것과 관련된 내용이 없었다. 원래 Creating a cluster with kubeadm 문서의 Installing a Pod network add-on 항목에 각 네트워크 플러그인 별로 설치관련 내용이 있었는데 그게 없었다. 확인해보니 그 내용이 1.17까지는 그 내용이 있는데 1.18부터는 그 내용이 없어졌다. 그래서 그 부분을 봐야 한다면 Creating a cluster with kubeadm 문서의 버전을 1.17로 낮추고 봐야 한다.

 

kubeadm 설치시 마지막에 kubectl 실행관련 내용으로 설정해야 할 다음의 내용이 나오게 되는데 이거 빼먹지 말자

이걸 해야 kubectl을 실행시킬수 있다.

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

현재 root 계정으로 실행중인 상황이면(이걸 확인하는 방법은 id 라고 입력하면 나온다) 추가로 이것도 마저 해준다

export KUBECONFIG=/etc/kubernetes/admin.conf

Troubleshooting은 크게는 4가지 영역으로 나눠서 볼 수 있다.

Application Failure, Worker Node Failure, ControlPlane(Master Node) Failure, Networking

 

Application Failure는 kubernetes에서 하나의 Application이 어떠한 구조로 이루어져 있는지 볼 필요가 있다.

구조적으로 보면 사용자가 Application을 이용할때 다음의 순서로 흐름이 전개된다고 보면 된다. 예를 들어 Web Application과 DB 이렇게 2개로 구성되어져 있는 Application의 경우

 

사용자 -> Web Service(Node Port, Load Balancer) -> Web Application Pod -> DB Service(ClusterIP) -> DB Pod

 

만약 Applcation 오류가 발생했을 경우 위의 흐름에 따라 체크해보면 된다

1. Web Service와 Web Application Pod 간의 연결

이것은 Web Service의 상세내용(kubectl describe service websvc)에서 selector 항목의 값(name=webapp)이 Web Application Pod을 생성하는 yaml(kubectl get pod webapp -o yaml > webapp.yaml)의 metadata.labels 항목 값(name=webapp)과 비교해서 맞는지를 확인한다. 즉 service를 이용하도록 허용하는 Pod을 정의해주는 selector 항목과 해당 서비스를 이용하도록 구성이 된 Pod의 이름 서로 맞는지를 확인하는 것이다.

이 부분과 관련해서 연습문제를 풀어보면 NodePort로 되어 있는 Service에서 Port 연결이 잘못되어서 이걸 복구하는 문제가 있었다. 근데 Port 연결이란게 Service와 Pod의 Port 연결뿐만 아니라 외부로 노출되는 Port 번호를 잘못기입해서 오류가 나는 상황을 고치는 것이어서 NodePort 로 오픈되는 Port도 문제에서 제시한 Port 번호로 오픈된 것이 맞는지도 확인한다

 

2. Web Application Pod 자체의 문제

먼저 kubectl get pod webapp 을 실행해서 현재의 상태를 확인한다. restart 된 횟수가 있는지도 확인해본다.

그 다음 상세내용을 확인해서 pod을 생성하는 과정에서 문제가 있는지를 확인해본다

그리고  Web Application Pod의 log를 봐야 한다.(kubectl logs webapp) 만약 tail -f 같이 계속 관찰하고 싶으면 -f 옵션을 걸어서 에러가 발생하는 시점의 log 내용을 볼 수 있고, 또는 --previous 옵션을 걸어서 이전에 실행했던 Pod의 log를 볼 수도 있다.

이 과정에서 Web Application Pod이 DB Service를 맞게 호출하고 있는 상황인지도 확인할 수 있다. 만약 호출을 잘못하고 있는 상황이면 error가 발생했을테니까..그럴 경우 DB URL을 ConfigMap이나 Secrets를 이용해서 환경변수에 설정을 했을 가능성이 있기 때문에 Pod을 생성하는 yaml(kubectl get pod webapp -o yaml > webapp.yaml)을 보고 ConfigMap과 Secrets 중 어떤것을 썼는지 확인한 뒤 그 값을 수정하면 되겠다.

이 부분과 관련되어서 연습문제를 풀었는데 환경변수를 ConfigMap 이나 Secrets를 사용해서 값을 대입한 것이 아니라 그냥 하드코딩 해서 환경변수 값을 넣었다. 환경변수 값이 입력이 안되었거나 엉뚱한 값이 들어있거나 또는 호출해야 할 Service 이름이 잘못되었거나 하는 식이었다

 

3. DB Service와 DB Pod 간의 연결관계 문제

이것은 1번에서 언급했던 내용과 같아서 1번을 본다

이 부분과 관련되어서 연습문제를 풀었을땐 Port 번호를 잘못입력했거나 해당 Service와 연결되는 Pod을 결정짓는 Selector 이름을 잘못넣은 경우가 있었다.

 

4. DB Pod 자체의 문제

이것은 2번에서 언급한 내용과 같아서 2번을 본다

이것과 관련된 연습문제는 DB Pod에 환경변수로 들어가 있는 DB root 계정의 패스워드를 잘못 입력해서 그것을 고치는 문제가 나왔었다.

 

Application 오류 관련 문제는 개발자라면 풀기는 쉬웠다. 나의 경우는 python으로 만들어진 App이었으나 어떤 언어를 사용하든 간에 DB를 연결하기 위해서는 연결하고자 하는 DB의 주소와 연결에 사용하는 Port번호, DB 계정과 패스워드를 사용해야 하기 때문에 이것을 염두해두고 문제를 풀어나가면 쉽게 풀 수 있는 것들이었다. 다만 다이렉트로 연결하는게 아니라 네트워크 연결 관련 정보가 DB Pod과 연결되어 있는 Service에 있기 때문제 DB 주소를 Service 이름으로 넣는거 외에는 차이가 없다.


Control Plane Failure는 먼저 각 Node 들의 현재 상태(kubectl get nodes)들을 체크하고 Pod의 상태(kubectl get pods)들을 체크하고 kube-system namespace에서 운영되는 Pod의 상태(kubectl get pods -n kube-system)들을 체크한다.

 

또한 Control Plane에서 실행중인 구성요소들(ex: kube-apiserver, kube-controller-manager, kube-scheduler, kubelet, kube-proxy)의 실행 상태(service kube-apiserver status, service kube-controller-manager status, service kube-scheduler status, service kubelet status, service kube-proxy status)를 체크한다. kubelet과 kube-proxy의 경우 Worker Node에서 실행되기 때문에 Worker Node 에서 살펴본다.

 

Control Plane에서 실행중인 구성요소들의 log를 살펴본다(kubectl logs kube-apiserver-master -n kube-system) log는 OS쪽에서도 남기기 때문에 sudo journalctl -u kube-apiserver 를 실행시켜 확인해본다.


연습문제로 나왔던 것은 다음의 상황들이 발생한 것에 대한 해결이었다.

1. Deployment를 통해 생성된 Pod의 상태가 Pending(대기 상태)인 것을 해결하는 문제

생성된 Pod의 상세내용을 봐도 에러가 나올만한 상황이 없다. 그러면 현재 Control Plane에서 실행중인 Kubernetes 구성요소들이 정상동작 하는지 확인을 해야 한다(kubectl get pod -n kube-system) 실행해보니 kube-scheduler-controlplane의 상태가 CrashLoopBackOff 로 나타나고 있음을 확인할 수 있다. kube-scheduler는 생성된 Pod을 Worker Node에 배포하는 역할을 하기 때문에 이것에 문제가 생기면 Pod이 배포되질 않는다. 그러면 kube-scheduler-controlplane Pod에 어떤 이유로 문제가 생겼는지 상세내용을 본다(kubectl describe pod kube-scheduler-controlplane -n kube-system) Events 의 Message 를 보게 되면 다음과 같은 Message를 볼 수 있었다.

 

Error: failed to start container "kube-scheduler": Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"kube-schedulerrrr\": executable file not found in $PATH": unknown

 

대강의 내용을 보면 kube-schedulerrrr 파일을 실행하려는데 파일을 찾지 못했다고 나온다. kube-scheduler 를 실행하는데 사용되는 파일은 kube-schedulerrrr 이 아닌 kube-scheduler 라 생각하고 이 kube-scheduler-controlplane을 생성하는데 사용되는 yaml을 찾아야 한다. kubernetes 구성요소 Pod은 Static Pod이기 때문에 /etc/kubsernetes/manifests 디렉토리에 가면 이런 Static Pod을 생성하는 yaml을 찾을 수 있다.(StaticPod 생성 관련 설정파일이 어느 위치에 저장되는지를 확인할려면 kubelet의 config 파일에서 staticPodPath 항목을 찾아야 한다. kubelet은 Worker Node에서 실행되기 때문에 ssh node01 이런 식으로 Worker Node에 접속한 뒤 ps -aux | grep kubelet을 실행해보면 kubelet 실행 프로세스를 확인할 수 있다. kubelet 실행시 각종 option이 사용되는 것을 볼 수 있는데 kubelet 환경설정 파일을 지정하는 option은 --config option이다. 이 옵션값에 사용된 파일을 열어서 staticPodPath 항목이 있는데 그 항목에 설정된 값이 StaticPod을 생성하는데 사용할 yaml 파일들이 있는 위치가 되겠다)

 

이 디렉토리에 가서 파일이 머가 있는지 알아보면 kube-scheduler.yaml 파일이 있는것을 알 수 있다. 이 yaml 파일을 열어보자. 이 파일을 열어서 spec.containers.command 항목을 보면 현재 kube-schedulerrrr 로 되어 있음을 알 수 있다. 이를 kube-scheduler로 수정한뒤 저장하고 vi 에디터를 나온다. 그러면 kube-scheduler-controlplane pod을 다시 재생성하게 되고 아까 Deployment를 통해 배포되었던 Pending 상태였던 Pod의 상태가 Running 상태로 바뀌게 된다.

 

다음 문제는 Deployment의 replicas를 재조정해서 Pod을 더 만들도록 하게 했는데도 불구하고 Pod의 갯수에 변함이 없어서 이러한 상황을 해결하는 문제였다. Pod 을 지정된 갯수로 Pod의 갯수를 유지시키는 책임을 하는 것은 kube-controller-manager이다. 그래서 kube-system namespace에 있는 pod의 상태를 알아보면(kubectl get pods -n kube-system) kube-controller-manager-controlplane Pod의 상태가 CrashLoopBackOff 임을 알 수 있다. 아까 첫번째 문제를 해결했던 방법대로 kube-controller-manager-controlplane의 상세 내용을 보자(kubectl describe pod kube-controller-manager-controlplane -n kube-system) 그러면 Event 항목의 Message에 다음과 같은 Message가 있는 것을 확인할 수 있다.

 

Error: Error response from daemon: Conflict. The container name "/k8s_kube-controller-manager_kube-controller-manager-controlplane_kube-system_ec1bce416cb9f46a8376103e7be46187_0" is already in use by container "ae2e667eb5dd5241b9ed12a47d6f88726eb0d6fb2cafea46bbb84f228487e548". You have to remove (or rename) that container to be able to reuse that name.

 

근데 이 메시지만으로는 알기 어려웠다. 왜냐면 저 메시지만 놓고 보면 해당 container가 다른 곳에 사용중이어서 이를 삭제하거나 rename 한 후 다시 사용해야 한다는 의미였다. 그래서 kube-controller-manager-controlplane Pod의 log를 살펴봤다. log에 다음과 같은 내용이 있었다

 

I0322 09:36:50.147239 1 serving.go:331] Generated self-signed cert in-memory stat /etc/kubernetes/controller-manager-XXXX.conf: no such file or directory

 

/etc/kuberntes/controller-manager-XXXX.conf 란 파일이 없다는 뜻이다. 실제 이 부분을 체크해보면 저런 파일이 존재하지 않는다. 즉 존재하지 않는 파일을 사용하는 부분을 찾으면 된다. kube-controller-manager 또한 StaticPod 이기 때문에 /etc/kubernetes/manifests 디렉토리에서 kube-controller-manager Pod을 생성하는 yaml 파일(kube-controller-manager.yaml)을 찾는다. 그리고 이 파일을 열어보면 spec.containers.command 항목에서 kube-controller-manager 를 실행시킬때 사용하는 option에서 kubeconfig 옵션에 /etc/kubernetes/controller-manager-XXXX.conf 가 사용되는 것을 확인할 수 있다. 이것을 올바른 값(/etc/kubernetes/controller-manager.conf)로 수정한 뒤 저장하고 yaml 파일을 나오면 kube-controller-manager-controlplane Pod을 재생성한뒤 replicas 항목에 설정한 값대로 Pod의 갯수를 맞추는 것을 확인할 수 있다.

 

deployment를 통해 Pod을 3개를 만들도록 되어 있는데 3개 모두 만들어져 있지 않아서 이것을 해결하는 문제는 위에서 언급했던것과 마찬가지로 kube-controller-manager와 연관이 있다. 실제로 kube-system namespace의 pod을 조회해보면 kube-controller-manager-controlplane Pod에 문제가 발생했음을 확인할 수 있었다. 먼저 kube-controller-manager-controlplane Pod의 상세내용을 확인했으나 딱히 이렇다할만한 에러 메시지를 발견하지 못했다. Pod의 상세에서 발견을 못했으니 이젠 log를 봐야 할것 같아 log를 확인했다(kubectl logs kube-controller-manager-controlplane -n kube-system) log를 열어 확인해보니 다음과 같은 에러 메시지가 있었다

 

unable to load client CA file "/etc/kubernetes/pki/ca.crt": open /etc/kubernetes/pki/ca.crt: no such file or directory

 

이번에도 경로 문제라고 생각해서 확인해봤는데 /etc/kubernetes/pki/ca.crt 파일이 존재하고 있었다. 존재하고 있는데도 불구하고 no such file or directory 라고 나와서 일단은 이 Pod을 생성하는 파일인 /etc/kubernetes/manifests/kube-controller-manager.yaml 파일을 열어서 그 내용을 보았다. spec.containers.command 항목에서 저 파일을 사용하는 부분에 있어서 딱히 문제점이랄것을 발견할 수 없어서 이 문제는 풀지를 못했었는데 해답을 보고 알게 되었다. kubernetes에서는 volume을 이용해서 경로를 지정할 수 있기 때문에 /etc/kubernetes/pki 디렉토리가 volume 상으로 실제 어떤 디렉토리와 연결되었는지 확인할 필요가 있었다. 그래서 kube-controller-manager.yaml 파일에서 /etc/kubernetes/pki 디렉토리를 volume으로 설정한 부분을 찾았다. yaml 파일에서 spec.containers.volumeMounts 항목을 살펴보니 다음과 같은 내용이 있었다

 

- mountPath: /etc/kubernetes/pki
  name: k8s-certs
  readOnly: true

 

즉 우리가 사용했던 /etc/kubernetes/pki 디렉토리는 OS의 실제 디렉토리가 아니라 kubernetes의 volume mount path 였던 것이다. 이 volume mount path의 이름으로 k8s-certs를 사용했으니 이 k8s-certs가 실제 OS의 어떤 물리 디렉토리랑 연결되었는지 확인해 보면 된다. spec.volumes 항목에는 volume 자체에 대한 정의를 할 수 있는데 이름이 k8s-certs 인 것을 찾아보면..

 

- hostPath:
    path: /etc/kubernetes/WRONG-PKI-DIRECTORY
    type: DirectoryOrCreate
  name: k8s-certs

 

이렇게 정의되어 있던 것을 찾을수 있다. 실제 물리 경로는 /etc/kuberntes/WRONG-PKI-DIRECTORY 인 곳을 /etc/kubernetes/pki 경로와 연결을 시도하고 있는것이 k8s-certs 였던 것이다. OS 상에서는 /etc/kubernetes/WRONG-PKI-DIRECTORY 란 디렉토리가 없기 때문에 에러 메시지 상으로 no such file or directory 에러가 발생했던것이다. /etc/kubernetes/WRONG-PKI-DIRECTORY 대신 ca.crt 파일이 있는 /etc/kubernetes/pki 디렉토리로 수정 저장해서 kube-controller-manager-controlplane Pod을 재생성하게끔 해서 이 문제를 풀었다. 이 문제를 통해 경로 관련 문제가 발생했을 경우 2가지를 체크해야 한다는 것을 알았다. 

 

1. 그 경로가 실제 물리적인 디렉토리 경로로 존재하는지 확인

2. 물리적인 디렉토리 경로로 존재하지 않을 경우 volume에 관련 경로 정의가 있는지 확인


Worker Node Failure에 대한 해결 방법도 Control Plane Failure 해결방법과 그리 차이가 없다. Node 상태를 체크해서 Node 상태가 NotReady로 나올경우 대상 Worker Node의 상세(kubectl describe nodes node01)를 보고 분석해야 한다. 상세 내용을 보면 Node의 상태를 다양한 관점에서 체크해서 알려주게 되는데 Conditions 항목을 보면 다음의 항목들에 대한 결과를 알려주고 있다(동영상에서 사용되는 kubernetes가 구버전이어서 그런지 1.20과는 항목을 다르게 하고 있어서 1.20 기준으로 적어 놓는다)

- NetworkUnavailable : kubernetes network plugin이 정상동작 하지 않을 경우 Status가 true로 나온다(항목으로 물어보는게 Unavailable 이다. 즉 정상동작 하지 않는가 를 물어보는 것이기 때문에 정상이면 false로 나오는게 맞다)

- MemoryPressure : 메모리가 부족한지를 물어보는것이므로 false로 나와야 부족하지 않다는 것을 뜻하게 된다

- DiskPressure : 디스크의 잔여용량이 부족한지를 물어보는것이므로 false로 나와야 부족하지 않다는 것을 뜻하게 된다

- PIDPressure : 너무 많은 프로세스가 실행중이어서 PID를 부여하는데 문제가 있는지를 물어보는 것이므로 fasle로 나와야 문제가 없다는 것을 의미한다.

- Ready : 전반적인 상태 점검 결과를 의미하는 것으로 true로 나와야 문제가 없다는 것을 뜻한다

만약 Worker Node와 Master Node간의 통신이 원할하지 않은 상황이어서 체크 결과를 받을수 없을때는 Status 항목 결과값이 Unknown으로 나오게 된다.

 

이러한 상태 결과값을 보고 문제가 발생했을 경우 top 을 이용해서 CPU, 메모리 사용율을 점검해보거나 df -h 명령을 통해 디스크 사용율 및 잔여량을 점검해볼수 있다.

 

kubelet의 현재 상태를 점검해 볼수 있고(systemctl status kubelet, sudo journalctl -u kubelet)

kubelet이 사용하는 인증서의 만료 기간을 점검해서 확인해볼 수 있다(openssl x509 -in /var/lib/kubelet/pki/worker-1.crt -text)


Worker Node 문제 해결 관련 연습 문제는 다음과 같았다.

 

1. kubectl get nodes 를 해본 결과 Worker Node인 node01의 상태가 Not Ready였다. 그래서 kubectl describe nodes node01을 실행시켜본 결과 위에서 설명했던 점검항목들의 결과값들이 unknown으로 나오는 것이었다. 이것은 kublet을 통해서 master node로의 전달이 원활하지 않아 발생하는 것이기 때문에 ssh node01을 실행하여 node01에 접속한후 systemctl status kubelet 을 해본 결과 kubelet service가 실행중이지 않았다. 그래서 systemctl start kublet 을 실행하여 kubelet을 실행시킨뒤 Master Node로 돌아와서 Node01 상태를 다시 체크하여 Ready로 되었음을 확인했다

 

2. 이번 문제도 Worker Node인 node01의 상태가 Not Ready였으며 kubectl describe nodes node01을 실행시켜본 결과 점검항목들의 결과값들이 unknown으로 나오는 것이었다. node01의 kubelet에 문제가 발생한것 같아보여 systemctl status kubelet을 실행시켜본 결과 실행이 되질 않아 반복적으로 계속 실행을 시키고 있는 것임을 확인했다(Active: activating (auto-restart) (Result: exit-code) since Mon 2021-03-22 16:10:01 UTC; 2s ago) 그래서 journalctl -u kubelet을 실행시켜 kubelet 실행시 관련 log를 살펴보니 다음과 같은 내용이 있었다.

 

unable to load client CA file /etc/kubernetes/pki/WRONG-CA-FILE.crt: open /etc/kubernetes/pki/WRONG-CA-FILE.crt: no such file or directory

 

kubelet 환경설정파일에서 있지도 않은 파일인 /etc/kubernetes/pki/WRONG-CA-FILE.crt 를 설정해서 발생하는 오류였다. 이걸 고칠려면 kubelet 설정 파일을 수정해야 한다. 그럼 어떤 파일이 kubelet 설정 파일일까?

 

systemctl status kubelet을 실행하면 Drop-In 항목을 보면 kubelet이 service로 실행할때 필요한 환경변수 값들이 저장되어 있는 파일을 읽어서 실행하도록 되어 있다. 그 값을 살펴보면 /etc/systemd/system/kubelet.service.d/10-kubeadm.conf 인것임을 알수 있다. 이 파일을 열어보면 Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml" 내용이 있는데 /var/lib/kubelet/config.yaml 이 파일이 kubelet 관련 설정 파일이다.

이 파일을 열어보면 authentication.x509.clientCAFile 항목의 값으로 /etc/kubernetes/pki/WRONG-CA-FILE.crt 가 들어가 있음을 확인할 수 있다. 이것을 /etc/kubernetes/pki/ca.crt로 수정한 뒤 파일을 저장하고 다음의 명령들을 차례대로 실행한다

 

systemctl daemon-reload

systemctl restart kubelet

 

그리고나서 systemctl status kubelet 을 실행하면 실행중 상태라고 나오며 Master Node로 가서 Worker Node의 상태를 확인해보면 Ready로 나오는 것을 확인할 수 있다.

 

3. 이 문제는 결국 풀지를 못했었는데 kubelet 실행 자체가 정상적으로 나오고 있어서 무엇을 봐야 하는지 알 수가 없었다. 그래서 풀이를 보고 알게 되었는데..

systemctl status kubelet -l 을 실행시켜서 log를 보면 다음의 내용이 있었다

 

error: Get "https://172.17.0.19:6553/apis/coordination.k8s.io/v1/namespaces/kube-node-lease/leases/node01?timeout=10s": dial tcp 172.17.0.19:6553: connect: connection refused

 

log가 중간에 짤려서 표현된 것을 내가 놓치고 말았던 것이다(방향키 -> 를 눌르면 log 나머지 부분을 볼 수 있었는데...ㅠㅠ)

이거는 kubelet이 172.17.0.19:6553으로 통신을 하지 못해서 발생한 것이다. 그러면 172.17.0.19:6553을 호출하는 것이 맞는 것인지를 확인하는 것인데 Master Node에서 kubectl cluster-info 를 실행하면 다음과 같은 내용을 보여준다

 

Kubernetes master is running at https://172.17.0.19:6443

KubeDNS is running at https://172.17.0.19:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

 

172.17.0.19:6443으로 통신했어야 하는거였다. 즉 잘못된 포트번호를 호출하는 것이었다. 이 문제를 해결하기 위해 위에서 살펴봤던 파일인 /etc/systemd/system/kubelet.service.d/10-kubeadm.conf 내용을 다시 한번 볼 필요가 있다

이 파일 내용을 살펴보면 다음과 같은 내용이 있다

 

Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"

 

여기서 kubeconfig 옵션에서 사용된 /etc/kubernetes/kubelet.conf  파일을 열어서 그 내용을 살펴보면 yaml 구조로 되어 있는 내용이 보이게 되는데 여기서 clusters.cluster.server 항목의 값이 https://172.17.0.19:6553 으로 되어 있음을 확인할 수 있다. 즉 master node에서 kubectl cluster-info 로 확인한 주소인 https://172.17.0.19:6443 과는 포트번호가 다른 것이었다. 6553 포트를 사용하는 것을 6443으로 바꾸고 파일을 저장한뒤 위에서와 같이 마찬가지로 systemctl daemon-reload 명령과 systemctl restart kubelet 명령을 차례대로 실행한뒤 Master Node로 돌아가서 kubectl get nodes 로 node01 상태를 확인해보면 Ready로 되어 있음을 확인할 수 있다.

 

이 문제들을 풀면서 환경설정 파일을 2개를 접하게 되는데 그것은 --kubeconfig 옵션을 사용해서 설정하게 되는 /etc/kubernetes/kubelet.conf 파일과 --config 옵션을 사용해서 설정하게 되는 /var/lib/kubelet/config.yaml 파일이다. --config 옵션으로 설정하게 되는 /var/lib/kubelet/config.yaml은 kubelet 의 환경설정 파일이고 --kubeconfig 옵션을 사용해서 설정하게 되는 /etc/kubernetes/kubelet.conf 파일은 Master Node의 kube-apiserver를 접속하기 위해 필요한 환경설정 파일인 것이다.


Network 관련 문제 해결에는 3가지 종류의 문제가 나왔다.

1. Calico, Flannel, Weavenet 같은 네트워크 통신 플러그인 설치가 되질 않은 경우

이것은 단순하다. 설치하면 되니까..Kubernetes 문서 최신 버전에는 이 설치 관련 내용이 없는데 이럴 경우 문서 버전을 낮춰서 찾아보면 나온다. 또는 Weavenet의 경우는 상관없는 주제를 다루는 문서에서 설치하는 방법에 대한 언급이 있어서 그걸 참고해도 된다.

 

2. KubeProxy에서 문제가 발생한것을 해결하는 문제였다. KubeProxy는 모든 Node에 생성되는 Pod 이기 때문에 DaemonSet 인데 KubeProxy Pod의 log를 살펴보면 /var/lib/kube-proxy/configuration.conf 이 없다고 나온다. KubeProxy DaemonSet에 대해 describe 명령을 이용해서 상세내용을 살펴보면 container의 command 항목에서 문제의 파일을 옵션값으로 사용하는 명령어를 볼 수 있다. 이거는 container 안에서 저 경로를 찾는 것이기 때문에 container 안에 물리적으로 저 경로가 있거나 또는 volume을 통해 다른 경로로 연결할 수도 있으므로 이 daemonset 을 생성하는 yaml 을 추출(kubectl get daemonset kube-proxy -n kube-system -o yaml > kube-proxy.yaml)해서 저 경로가 어떻게 설정되어 있는지 살펴볼 필요가 있다. yaml 파일을 살펴보면 spec.template.spec.containers.volumeMounts 항목에 /var/lib/kubeproxy 를 kube-proxy란 이름의 volume에 mount 한 것을 확인할 수 있다. 그러면 Pod의 volume 설정에 kube-proxy 란 이름의 volume 을 찾으면 된다. 이걸 찾아보면 kube-proxy 란 ConfigMap에 volume을 매핑한 것을 알 수 있다. 이 ConfigMap을 살펴보면 내부적으로 2개의 파일(ConfigMap은 파일로도 구성될 수 있는데 파일로 구성할 경우 파일명이 key가 된다)로 구성되어 있는데 하나는 config.conf 란 파일이고 또 하나는 kubeconfig.conf 란 파일이었다. 그러나 config.conf 파일과 kubeconfig.conf 파일의 내용을 살펴보면 kube-proxy와 관련된 내용은 config.conf 였었다. 그래서 /var/lib/kube-proxy/configuration.conf 대신 /var/lib/kube-proxy/config.conf 로 설정해주면 이 문제가 해결된다.

 

3. Web Application 영역에는 문제가 없는데 DB를 접근하지 못할 경우 DB Pod과 이와 연결된 Service 설정에 문제가 없고 Web Application 에서 DB Service를 호출하는방법에도 문제가 없다면 Service 이름을 가지고 network 연결을 하는 방식인 DNS에서 문제가 생겼을 가능성이 있다. DNS의 경우는 kube-system namespace에서 coredns 라는 Deployment와 이 Deployment와 연결된 kube-dns 라는 Service 이 둘로 기능이 구현된다. 그래서 이 Deployment와 Service 간의 연결에 문제가 있는지 확인해보는 것이 좋다. 여기서는 이 둘이 제대로 연결되어 있질 않아서 kube-dns Service 에서 endpoint가 나타나질 않았다. kube-dns Service의 selector를 Deployment에 맞춰서 설정해주면 문제가 해결된다.