Kubernetes Node

Node Management

노드의 3가지 주요 컴포넌트는 다음과 같다.


API 서버에 노드를 등록하는 두 가지 방버이 존재하며 컨트롤 플레인은 신규 노드가 유효한지 확인한다.


노드 네임은 반드시 Unique 해야 한다.


Self-registration of Nodes


Node authorization mode, NodeRestriction admission plugin이 활성화 되면 kubelet은 자신의 노드 리소스에 대한 생성/수정 만 가능하도록 인가된다.

The Node authorizer는 kubelet이 API를 수행할 수 있도록 허가해주며 다음 권한을 포함한다.



Node Status

노드의 상태는 다음 정보를 포함한다.



Heartbeats

Kubernetes 노드에 의해 전송되는 Heartbeats는 클러스터가 노드가 가용한지 결정하는데 도움을 주며 장애가 감지되었을 때 어떤 조치를 할 수 있도록 도와준다.

Heartbeats는 두 가지 형태가 존재한다.

노드의 .status를 업데이트하는 것에 비해 Lease는 가벼운 리소스이다. Heartbeats를 위해 Leases를 사용하는 것은 규모가 큰 클러스터에서 성능에 성능에 영향을 줄일 수 있다.

Kubelet은 노드의 .status를 생성하고 업데이트를 담당하며 관련된 Lease들의 업데이트를 담당한다.

The kubelet is responsible for creating and updating the .status of Nodes, and for updating their related Leases.

$ kubectl get lease -n kube-node-lease
NAME                                                      HOLDER                                                    AGE
fargate-ip-10-20-10-162.ap-northeast-2.compute.internal   fargate-ip-10-20-10-162.ap-northeast-2.compute.internal   118d
fargate-ip-10-20-10-38.ap-northeast-2.compute.internal    fargate-ip-10-20-10-38.ap-northeast-2.compute.internal    16h
fargate-ip-10-20-15-189.ap-northeast-2.compute.internal   fargate-ip-10-20-15-189.ap-northeast-2.compute.internal   118d
fargate-ip-10-20-15-226.ap-northeast-2.compute.internal   fargate-ip-10-20-15-226.ap-northeast-2.compute.internal   118d
ip-10-20-10-112.ap-northeast-2.compute.internal           ip-10-20-10-112.ap-northeast-2.compute.internal           11d
ip-10-20-10-208.ap-northeast-2.compute.internal           ip-10-20-10-208.ap-northeast-2.compute.internal           6d2h
ip-10-20-10-51.ap-northeast-2.compute.internal            ip-10-20-10-51.ap-northeast-2.compute.internal            173d
ip-10-20-10-62.ap-northeast-2.compute.internal            ip-10-20-10-62.ap-northeast-2.compute.internal            73d
ip-10-20-11-100.ap-northeast-2.compute.internal           ip-10-20-11-100.ap-northeast-2.compute.internal           73d
ip-10-20-11-132.ap-northeast-2.compute.internal           ip-10-20-11-132.ap-northeast-2.compute.internal           2d1h
ip-10-20-11-135.ap-northeast-2.compute.internal           ip-10-20-11-135.ap-northeast-2.compute.internal           33d
ip-10-20-11-32.ap-northeast-2.compute.internal            ip-10-20-11-32.ap-northeast-2.compute.internal            11d
ip-10-20-11-9.ap-northeast-2.compute.internal             ip-10-20-11-9.ap-northeast-2.compute.internal             73d
ip-10-20-20-79.ap-northeast-2.compute.internal            ip-10-20-20-79.ap-northeast-2.compute.internal            25h

Lease 객체를 Describe 하면 spec에 Renew Time이 존재하며 정상적인 노드의 경우 10초 간격으로 업데이트 된다.

kd lease ip-10-20-20-79.ap-northeast-2.compute.internal -n kube-node-lease
Name:         ip-10-20-20-79.ap-northeast-2.compute.internal
Namespace:    kube-node-lease
Labels:       <none>
Annotations:  <none>
API Version:  coordination.k8s.io/v1
Kind:         Lease
Metadata:
  Creation Timestamp:  2023-04-30T07:17:09Z
  Owner References:
    API Version:     v1
    Kind:            Node
    Name:            ip-10-20-20-79.ap-northeast-2.compute.internal
    UID:             e42b805e-d7e4-4682-b907-6716e628d1bc
  Resource Version:  93730810
  UID:               77736a12-626c-43da-bf71-9c616bfbf121
Spec:
  Holder Identity:         ip-10-20-20-79.ap-northeast-2.compute.internal
  Lease Duration Seconds:  40
  Renew Time:              2023-05-01T08:31:18.434194Z
Events:                    <none>

모든 Kubelet heartbeat는 Lease 객체에 대한 업데이트 요청이며 Lease객체의 spec.renewTime 필드를 업데이트한다. Kubernetes control plane은 이 time stamp를 이용하여 노드의 가용성을 결정한다.

Under the hood, every kubelet heartbeat is an update request to this Lease object, updating the spec.renewTime field for the Lease. The Kubernetes control plane uses the time stamp of this field to determine the availability of this Node



Reserve Compute Resources

파드는 기본적으로 노드에서 사용 가능한 모든 리소스 용량을 소비할 수 있다. 일반적으로 노드는 OS 및 쿠버네티스를 위한 몇 가지 System Daemon을 실행하기 때문에 문제가 될 수 있다. System Daemon 프로세스들에 대해서 리소스를 별도로 설정하지 않는다면 파드와 System Daemon들은 리소스 경쟁을 하게 되며 노드에서 리소스 부족 문제가 발생할 수 있다.
Kubelet에서는 Node Allocatable 이라고 하는 기능을 제공하며 System Daemon들에 대한 리소스 예약을 도와준다.

쿠버네티스 노드에서 Allocatable의 정의는 파드가 사용가능한 리소스의 양으로 정의하며 아래 describe node를 통해 확인 가능하다.

$ kubectl describe node ip-10-20-11-44.ap-northeast-2.compute.internal
......생략
Allocatable:
  cpu:                        1930m
  ephemeral-storage:          76224326324
  hugepages-1Gi:              0
  hugepages-2Mi:              0
  memory:                     7220180Ki
  pods:                       29
  vpc.amazonaws.com/pod-eni:  9

Example

이 시나리오에서 Allocatable 값은 CPUs: 14.5 CPUs, Memory: 28.5Gi, Storage: 88Gi 가 된다. Scheduler는 이를 통해 이 노드에서 실행 중인 모든 파드의 요청 메모리가 28.5Gi를 넘을 수 없다는 것을 알 수 있다. 또한 kubelet은 전체적인 파드의 메모리 사용량이 28.5Gi를 넘어서면 eviction을 하게 된다.

만약 kube-reserved, system-reserved 설정이 되어있지 않고 시스템 데몬이 예약값을 초과하면 kubelet은 31.5Gi 메모리, 90Gi 스토리지 용량을 넘을 때 마다 파드를 eviction한다.



Kube-controller-manager(Node controller)

노드 컨트롤러는 Kubernetes Control Plane의 요소이며 노드를 다양한 측면에서 관리한다.

  1. 노드 컨트롤러는 CIDR assignment 설정이 된 경우 노드가 등록될 때 노드에 CIDR block을 할당한다.
  2. 노드 컨트롤러는 CSP사의 available machines 리스트의 최신정보로 내부 노드 리스트를 유지한다.
  3. 노드 컨트롤러는 노드의 Health 상태를 모니터링 한다.
    • 노드가 Unreachable 상태가 되면 노드의 .status의 Ready 상태를 업데이트한다. 이런 경우 노드 컨트롤러는 Ready상태를 Unknown 상태로 변경한다.

      In the case that a node becomes unreachable, updating the Ready condition in the Node’s .status field. In this case the node controller sets the Ready condition to Unknown.

    • 노드가 Unreachable 상태로 남아있으면 해당 노드의 Pod들에 대해서 API-initiated eviction을 시작한다. 기본적으로 노드 컨트롤러는 노드가 Unknown 상태로 마크되고 5분의 시간 뒤에 첫 번째 eviction 요청을 진행한다.

      If a node remains unreachable: triggering API-initiated eviction for all of the Pods on the unreachable node. By default, the node controller waits 5 minutes between marking the node as Unknown and submitting the first eviction request.

      • API-initiated Eviction은 Eviction API를 이용해 graceful pod termination을 시작하는 eviction object를 만드는 프로세스다.
      • API-initiated evictions respect your configured PodDisruptionBudgets and terminationGracePeriodSeconds
      • API 서버에 의해 eviction이 허용되면 Pod가 삭제되는 자세한 과정은 문서를 참고한다.
        • Pod리소스에 deletion timestamp 추가되고 grace period가 설정된다. Kubelet은 종료될 파드를 인지하고 gracefully shut down을 시작한다. Kubelet이 파드를 종료하는 동안 컨트롤플레인은 Endpoint와 EndpointSlice 객체를 제거한다. 그 결과 컨트롤러들은 해당 파드가 유효한 객체가 아님을 알게 된다. grace period가 지나면 kubelet은 파드를 강제로 죵료한다. Kubelet은 API서버에 파드가 제거 됨을 알린다.

기본적으로 노드 컨트롤러는 노드의 상태를 매 5초(–node-monitor-period 설정) 마다 체크한다.

k8s-node-status

다음은 전체적인 Node health check에 대한 flow이며 아래는 참고사항이다.


Cloud-controller-manager(Node controller)

노드 컨트롤러는 클라우드 Infrastructure에서 새로운 서버가 생성될 때 노드 객체를 업데이트 하는 역할을 한다. 노드 컨트롤러는 CSP에서 실행되는 호스트 정보를 가져오며 다음과 같은 역할을 수행한다.

  1. Cloud provider의 API로 부터 얻은 서버 고유 ID를 노드 객체에 업데이트한다.
  2. Annotating and labelling the Node object with cloud-specific information, such as the region the node is deployed into and the resources (CPU, memory, etc) that it has available.
  3. Obtain the node’s hostname and network addresses.
  4. Verifying the node’s health. In case a node becomes unresponsive, this controller checks with your cloud provider’s API to see if the server has been deactivated / deleted / terminated. If the node has been deleted from the cloud, the controller deletes the Node object from your Kubernetes cluster.

Some cloud provider implementations split this into a node controller and a separate node lifecycle controller.



Graceful node shutdown

Kubelet은 노드의 shutdown을 감지하고 노드에서 실행 중인 파드의 종료를 시도한다. Kubelet은 노드가 shutdown 되는동안 파드가 pod termination process를 따르도록 한다.
GracefulNodeShutdown는 feature gate를 통해 제어할 수 있다. feature gate란 쿠버네티스 기능을 설명하는 key=value 쌍이다. --feature-gates=...,GracefulNodeShutdown=true

Default설정으로 shutdownGracePeriod와 shutdownGracePeriodCriticalPods는 모두 0로 설정되어있으며 graceful node shutdown 기능은 활성화 되어있지 않다. 활성화 시키기 위해서는 kubelet 설정을 적절히 변경하고 0값을 수정해야 한다.

만약 shutdownGracePeriod 값이 30이고 shutdownGracePeriodCriticalPods 값이 10이면 kubelet은 노드 종료를 30초 까지 지연시키며 처음 20초 동안은 일반 파드를 종료하는데 마지막 10초는 Critical 파드를 종료하는데 할당한다.

Critical 파드를 지정하기 위해서는 아래와 같이 PriorityClass 설정 및 파드의 Spec에 PriorityClass를 지정하여 사용 가능하다.

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for XYZ service pods only."


apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  priorityClassName: high-priority

kubelet config YAML

shutdownGracePeriodByPodPriority:
  - priority: 100000
    shutdownGracePeriodSeconds: 10
  - priority: 10000
    shutdownGracePeriodSeconds: 180
  - priority: 1000
    shutdownGracePeriodSeconds: 120
  - priority: 0
    shutdownGracePeriodSeconds: 60



Non Graceful node shutdown

노드가 shutdown 되었지만 kubelet의 node shutdown manager가 감지하지 못했을 때 StatefulSet의 파드는 terminating 상태로 stuck 되어 다른 노드로 이동이 되지 못 할 수 있다. Shutdown 된 노드의 kubelet이 파드를 지울 수 없어 동일이름으로 다른 노드에 파드를 만들 수 없기 때문이다. 만약 볼륨을 사용하는 파드가 있다면 종료되는 노드에서 VolumeAttachment가 삭제되지 않으며 새로운 노드에 볼륨이 Attach되지 않는다.

위 와 같은 상황을 완화하기 위해, 사용자가 node.kubernetes.io/out-of-service taint를 NoExecute 또는 NoSchedule 값으로 추가하여 노드의 서비스 불가상태를 표시 할 수 있다. kube-controller-manager에 NodeOutOfServiceVolumeDetach feature gate가 활성화 되어있고 노드에 out-of-service taint가 되어있다면 해당 노드의 파드는 toleration이 없는 경우 강제 삭제되며 종료되는 파드에 대한 볼륨 해제 작업도 즉시 수행된다.

During a non-graceful shutdown, Pods are terminated in the two phases:

  1. Force delete the Pods that do not have matching out-of-service tolerations.
  2. Immediately perform detach volume operation for such pods.



Communication between Nodes and the Control Plane

Node to Control Plane

노드(또는 노드에서 실행 중인 파드)에서의 모든 API 사용은 API서버에서 종료된다. 노드는 유효한 client credential과 함께 API서버에 안전한 연결을 위해 public root certificate로 provision되어야 한다. Client 인증서를 통해 kubelet의 자격증명을 사용하는 것은 좋은 방법이다.

Control Plane to Node

API서버에서 노드로는 두 가지 통신 경로가 있으며 하나는 API서버에서 kubelet 프로세스이며 두 번째는 API 서버에서 API서버의 프록시 기능을 이용해 노드, 파드 또는 서비스에 이르는 것이다.

API server to kubelet

위와 같은 연결은 kubelet의 HTTPS endpoint에서 종료되며 기본적으로 API서버는 kubelet이 제공 인증서(serving certificate)를 확인하지 않는다. 이는 중간자 공격의 연결을 만드는 것이며 신뢰할 수 없는 public network에서 실행되기 때문에 안전하지 않다.
이 연결을 검증하기 위해서는 –kubelet-certificate-authority flag를 사용하여 API서버에 kubelet의 serving certificate가 유효한지 확인하는데 root certificate bundle을 제공한다.


📚 References