콘텐츠로 건너뛰기

Kubernetes docs 정리 — Workload

태그:

Intro

Kubernetes docs 정리‘에서는 Kubernetes docs를 읽으며 필요한 부분들에 대해 스스로가 이해할 수 있는 단어로 유연하게 의역하여 정리한다. 이번 포스팅에서 정리할 섹션은 Kubernetes Workload 부분이다.

Workload

workload는 Kubernetes에서 돌아가는 어플리케이션을 말한다. workload의 가장 작은 단위는 Pod이다. Pod를 기본으로하여 더욱 복잡한 컴포넌트들을 만들어낼 수 있다. Pod는 클러스터에서 운용되는 container들의 묶음을 나타낸다.

Pod는 라이프사이클이 존재한다. 예를 들어 Pod가 클러스터에서 돌아가고 있는 도중에 node에 문제가 발생하면 해당 node에서 운용되고 있던 Pod들은 모두 실패하게된다. 이와 같은 상황에서 Pod의 라이프사이클은 끝났기 때문에 어플리케이션을 사용하기 위해서는 Pod를 다시 생성해야한다.

이러한 과정은 상당히 번거롭고 관리가 불편하기 때문에 Pod을 직접 사용하는 것이 아니라 Pod을 관리해주는 또다른 workload resource들을 사용할 수 있다. 이러한 종류의 resource들은 controller의 형태로 구성되있으며 Pod의 숫자와 종류를 유지하도록 관리한다.

Kubernetes에서는 기본적으로 다음과 같은 workload resource들을 제공한다:

  • Deployment, ReplicaSet: Deployment는 stateless한 workload를 관리하는데 적합하다. Deployment가 관리하는 Pod는 각자의 식별자가 없기 때문에 언제든지 다른 Pod로 교체될 수 있다.
  • StatefulSet: StatefulSet은 Pod가 stateful한 경우에 적합하다. 예를 들어 workload가 어떤 데이터를 볼륨에 영구적으로 보관하거나 해야하는 경우가 있을 수 있다. StatefulSet이 관리하는 Pod는 각각 식별자가 존재하고 같은 식별자의 Pod일 경우에는 상태가 유지된다.
  • DaemonSet: DaemonSet은 node에 한정된 기능을 제공하기 위한 workload를 관리하는데 적합하다. DaemonSet을 이용하여 클러스터의 오퍼레이션을 정의할 수도 있다. 클러스터에 node가 추가되는 경우 만약 해당 node가 DaemonSet이 관리하여야하는 node인 경우 Pod를 스케쥴링하여 해당 node에 workload가 추가된다.
  • Job, CronJob: 완료라는 상태가 있고 중간에 멈출 수 있는 workload를 관리하는데 적합하다.

Pods

Pod는 Kubernetes 클러스터 내에 배포할 수 있는 가장 작은 단위이다. Pod는 하나 이상의 container로 구성된다. 그리고 container들은 저장소와 네트워크 리소스는 공유한다. 어플리케이션뿐만아니라 Pod가 시작하기 전에 실행할 수 있는 init container를 명시할 수 있고 디버깅 용도의 ephemeral container를 주입할 수도 있다.

What is pod?

Pod에서 공유되는 컨텍스들은 Linux의 namespace, cgroup 등이 공유된다. Docker의 컨셉으로 보자면 Pod 안에 존재하는 container들은 namespace와 file system volume을 공유하는 Docker container들로 볼 수 있다.

Working with Pods

Kubernetes를 사용하면서 우리가 직접적으로 Pod를 생성하는 일은 거의 없다. 왜냐하면 Pod는 기본적으로 임시적이고 언제든지 버려질 수 있는 개체로 디자인되었기 때문이다. 보통 controller에 의해서 Pod가 생성되는데 새로 생성된 pod는 클러스터에 존재하는 node에 스케쥴링된다. 그리고 Pod는 자신의 작업이 끝나거나, 삭제되거나 혹은 리소스가 부족하여 evicted되는 경우에 삭제된다.

Pods and controllers

보통 workload resource를 통해서 여러 개의 Pod을 만들고 관리한다. controller를 통해서 Pod를 업데이트하거나 Pod가 실패했을 때 다시 복구시켜준다. 예를 들어 node에 장애가 발생하여 Pod이 죽었을 때 controller는 Pod이 중단된 것을 파악하고 다시 Pod을 띄운다.

Pod templates

workload resource에 대한 controller는 pod template을 보고 해당 스펙에 맞는 Pod을 생성하고 관리한다. workload resource가 pod template을 변경하는 경우 기존에 떠있는 Pod에는 직접적인 영향은 없다. 그렇지만 workload resource는 pod template이 바뀌었다는 것을 감지하고 해당 스펙에 맞는 Pod를 클러스터 내에 만들게 된다. 또한 각각의 workload resource는 pod template이 바뀌었을 때 어떻게 핸들링할 것인지에 대한 것을 정의하게 되는데 update strategy에 따라서 결정된다.

Pod update and replacement

위에서 언급했듯이 workload resource의 pod template이 변경되면 기존의 Pod을 변경시키는 것이 아니라 새로운 Pod를 만들게된다. Kubernetes는 Pod을 직접적으로 관리하는 것을 막지는 않는다. Pod의 특정 필드를 수정할 수 있지만 제약이 따른다. 예를 들어 Pod의 대부분의 메타데이터는 immutable하다. 그렇기 때문에 namespace, name, uid와 같은 필드들은 한 번 생성이되면 중간에 변경할 수 없다.

Resource sharing and communication

Pod는 자신의 container 사이에서 데이터와 네트워크 리소스를 공유할 수 있다.

Storage in Pods

Pod에 공유할 수 있는 volume을 선언할 수 있고 Pod 안에 있는 모든 컨테이너들이 해당 volume에 접근하여 데이터를 읽고 쓸 수 있다.

Pod networking

Pod는 자신의 고유한 IP 주소를 할당받는다. Pod 내부에 있는 컨테이너들은 네트워크 namespace, IP 주소, 포트를 공유한다. Pod 내부에 있는 컨테이너들은 서로를 localhost 를 통해서 통신할 수 있다. 그렇지만 만약 컨테이너가 Pod 외부에 있는 서비스와 통신하려고 한다면 Pod의 공유하는 자원, 예를 들어 포트를 다른 컨테이너와 어떻게 나눠서 사용할지 명시해야한다.

ReplicaSet

ReplicaSet의 목적은 항상 특정 정해진 숫자만큼의 Pod를 클러스터에 올려놓도록 하는 것이다.

How a ReplicaSet works

ReplicaSet은 자신이 관리해야하는 Pod를 어떻게 찾을지에대한 selector와 몇 개의 레플리카를 유지해야하는지에 대한 정보를 가지고 있으며 레플리카 갯수를 유지하기 위해 Pod을 새로 생성할 때 어떠한 스펙으로 Pod을 만들어야하는지에 대한 정보를 가지고 있다.

ReplicaSet이 관리하는 Pod들은 Pod의 metadata.ownerReferences필드를 통해 ReplicaSet과 연결되어있다. metadata.ownerReferences 필드는 현재 이 리소스가 누구에게 관리되고 있는지를 나타낸다. ReplicaSet을 통해 생성된 Pod들은 모두 자신을 관리하는 ReplicaSet의 ID 정보가 ownerReferences 필드에 들어있다. 이 필드를 통해 ReplicaSet은 자신이 관리하는 Pod들의 상태를 알 수 있고 이를 통해 자신이 어떻게 동작 해야하는지를 알고 있다.

ReplicaSet은 자신이 관리해야하는 Pod들을 selector를 통해서 알게된다. 만약 클러스터에 ReplicaSet의 selector와 일치하지만 OwnerReference 정보가 없거나 OwnerReference 정보가 Controller가 아니라면 해당 ReplicaSet가 관리하게 된다.

When to use a ReplicaSet

위에서 살펴보았듯이 ReplicaSet은 자신의 스펙에 맞는 그리고 지정된 레플리카 갯수만큼 Pod을 유지하는 목적을 가진다. 그런데 ReplicaSet 상위 개념으로 Deployment가 존재하고 Deployment는 ReplicaSet을 관리하며 Pod을 특정 갯수만큼 유지시켜줄뿐만 아니라 다른 유용한 기능들도 제공하게된다. 그렇기때문에 Kubernetes에서는 ReplicaSet을 직접 사용하기보다 ReplicaSet을 사용하기를 권장하고 있다.

만약에 커스텀하게 Pod들을 업데이트하는 방식을 원하거나 Pod들의 스펙이 변경되지 않기를 원하는 경우 ReplicaSet을 사용해볼 수 있는데 거의 직접 사용할 일은 없을 것이다.

Deployment

Deployment는 ReplicaSet를 이용한 상위 레벨의 Kubernetes Resource이고 선언적으로 원하는 스펙을 명시함으로써 ReplicaSet와 Pod을 업데이트할 수 있다.

Use Case

다음과 같은 상황에서 우리는 Deployment를 사용할 수 있다.

  • ReplicaSet을 rollout하기 위해 Deployment를 생성하는 경우: Deployment를 통해서 ReplicaSet을 생성하고 ReplicaSet은 Pod을 생성한다.
  • Pod들을 새로운 스펙으로 변경하고 싶은 경우: Deployment의 PodTemplateSpec의 변경을 통해서 Pod의 스펙을 변경할 수 있다. 이 때 Deployment는 새로운 스펙의 ReplicaSet을 생성하게되고 해당 ReplicaSet을 통해 다시 Pod들이 생성되도록 한다. 생성된 ReplicaSet은 Deployment의 revision을 업데이트시킨다.
  • 이전 revision으로 Deployment를 롤백하고 싶은 경우: 만약 현재 버전의 Deployment가 불안정하다면 이전 버전으로 Deployment를 롤백할 수 있다.

Updating a Deployment

Deployment의 rollout은 Deployment의 Pod template이 바뀌었을 때만 발생한다. 예를 들어 .spec.template 필드의 label이나 컨테이너 이미지가 바뀌는 경우 rollout이 진행되지만 레플리카 갯수를 바꾼 경우 rollout은 진행되지 않는다.

또한 Deployment는 rollout 과정 중에 얼마 만큼의 Pod들이 unavailable 상태를 가질 수 있을지 설정 가능하다. 이 비율의 기본값은 레플리카 중 최대 25% 만큼의 Pod들이 rollout 과정 중에 unavailable의 상태를 가질 수 있다. 얼마만큼의 unavailable 상태를 가질지 설정할 수 있을뿐만 아니라 gracefulShutdownPeriods 등으로 인해 동시간대에 Deployment가 관리하는 레플리카보다 더 많은 숫자의 레플리카가 클러스터에 뜰 수 있는데 이 비율도 설정할 수 있다. 기본값은 desired 레플리카 125% 만큼의 레플리카를 rollout 과정 중에 동시에 떠있을 수 있도록 설정되어있다.

Deployment rollout 과정 중 이러한 특징을 관찰할 수 있는데 새로운 PodTemplateSpec으로 rollout이 시작되면 이전 버전 Pod들이 한번에 unavailable 상태가 되면서 업데이트되는 것이 아니라 먼저 하나의 최신 버전의 Pod이 새로 생성된다. 그리고 최대 unavailable 한 상태를 가질 수 있는 Pod 비율만큼 이전 버전의 Pod가 내려간다. 그리고 max surge 비율만큼 다시 최신 버전의 Pod가 rollout된다. 이와 같은 과정이 반복되면서 최신 PodTemplateSpec의 Deployment로 업데이트된다.

아래 예시에서는 3개의 레플리카가 존재한다. 그렇기 때문에 동시간에 최소 2개 Pod가 available 한 상태를 가져야하며 최대 4개까지의 레플리카들이 존재할 수 있다. 이 조건을 만족시키며 Deployment의 rollout 과정이 진행된다.

 Name:                   nginx-deployment
 Namespace:              default
 CreationTimestamp:      Thu, 30 Nov 2017 10:56:25 +0000
 Labels:                 app=nginx
 Annotations:            deployment.kubernetes.io/revision=2
 Selector:               app=nginx
 Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
 StrategyType:           RollingUpdate
 MinReadySeconds:        0
 RollingUpdateStrategy:  25% max unavailable, 25% max surge
 Pod Template:
   Labels:  app=nginx
    Containers:
     nginx:
       Image:        nginx:1.16.1
       Port:         80/TCP
       Environment:  
       Mounts:       
     Volumes:        
   Conditions:
     Type           Status  Reason
     ----           ------  ------
     Available      True    MinimumReplicasAvailable
     Progressing    True    NewReplicaSetAvailable
   OldReplicaSets:  
   NewReplicaSet:   nginx-deployment-1564180365 (3/3 replicas created)
   Events:
     Type    Reason             Age   From                   Message
     ----    ------             ----  ----                   -------
     Normal  ScalingReplicaSet  2m    deployment-controller  Scaled up replica set nginx-deployment-2035384211 to 3
     Normal  ScalingReplicaSet  24s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 1
     Normal  ScalingReplicaSet  22s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 2
     Normal  ScalingReplicaSet  22s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 2
     Normal  ScalingReplicaSet  19s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 1
     Normal  ScalingReplicaSet  19s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 3
     Normal  ScalingReplicaSet  14s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 0

Rollover (aka multiple updates in-flight)

Deployment가 새로운 PodTemplateSpec으로 업데이트될 때마다 Deployment controller는 이것을 발견하고 새로운 ReplicaSet을 생성하고 desired 레플리카 숫자만큼 Pod를 생성한다. 그리고 controller는 ReplicaSet들 중 .spec.selector 와는 일치하지만 .spec.template 과는 일치하지 않는 것을 scale down하면서 이전 버전의 Pod들을 줄인다. 최종적으로 이전 버전의 ReplicaSet의 레플리카 갯수는 0개가 된다.

그렇다면 만약에 Deployment가 rollout 과정 중에 다시 한번 업데이트가 된다면 어떻게 될까? Deployment는 가장 최신 버전의 .spec.template 에 해당하는 ReplicaSet을 생성한다. 그리고 이전까지 떠있던 ReplicaSet은이제 old RelicaSet 리스트에 들어가서 scale down 과정을 같이 진행하게 된다.

예를 들어 레플리카 5개의 nginx:1.14.2 를 rollout 하다가 아직 3개의 레플리카만이 up인 상태에서 다시 nginx:1.16.1 버전의 nginx를 rollout 하면, nginx:1.14.2 를 관리하던 ReplicaSet의 레플리카가 5개 모두 up이 되는 것을 기다리는 것이 아니라 곧바로 scale down을 시작하고 최신버전의 ReplicaSet만이 scale up을 진행한다.

Label selector updates

Kubernetes는 Deployment의 Pod label selector를 업데이트하는 것을 권장하지 않는다. 그렇기 때문에 label selector는 사전에 충분히 고려를 하여 설계를 해야한다.

Kubernetes API version apps/v1 부터는 Deployment의 label selector가 한 번 생성이되고 나서는 immutable한 상태가되기 때문에 이후에 Deployment를 삭제하지않고서는 업데이트하는 것이 불가능하다.

중요한 것은 Deployment의 label이나 PodTemplateSpec의 Pod label은 얼마든지 변경이 가능하다.

Rolling Back a Deployment

새로 rollout한 Deployment가 rollout 과정에서 crash가 나는 등 불안정하다면 이전 버전으로 rollback을 할 수 있다. Deployment의 revision은 Deployment가 rollout될 때마다 생성된다. 다시말하면 PodTemplateSpec(.spec.template)이 업데이트 될 때마다 revision이 생성되고 이것을 통해 특정 revision으로 롤백할 수 있다. PodTemplateSpec와 상관없는 Deployment의 업데이트라면 revision이 생성되지 않는다. 예를 들어 Deployment의 레플리카 숫자를 바꾼 것이라면 revision이 생성되지 않는다.

StatefulSet

StatefulSet은 stateful한 어플리케이션을 관리하는데 적합한 Kubernetes workload 이다. Deployment와 비교했을 때 PodTemplateSpec을 통해서 동일한 스펙의 Pod를 관리한다는 유지한다는 공통점은 있지만 StatefulSet은 각각의 Pod에 대해서 ID를 부여하여 관리하게된다. 그렇기 때문에 스펙은 동일할지라도 레플리카와 다르게 각각은 고유하다는 특성을 가진다.

Workload 에 volume을 사용해야한다면 StatefulSet이 해결책 중 하나이다. 각각의 고유한 Pod는 운영 중에 잠깐 다운될 수 있지만 StatefulSet이 해당 ID의 Pod을 새로 생성하고 해당 ID에 매칭되는 volume을 다시 마운트하여 다시 동작할 수 있도록 만들어준다.

Use case

StatefulSet은 다음과 같은 특성이 필요하다면 활용해볼 수 있다.

  • 고유한 네트워크 식별자
  • 영구 저장소
  • 순서가 중요한 배포나 스케일링
  • 순서가 중요한 롤링 업데이트

위와 같이 각 Pod 별로 구분이 필요없다거나 순서가 중요하지 않다거나 저장소가 필요하지 않다면은 stateless한 어플케이션을 위한 Deployment를 사용할 수 있다.

Limitations

  • Pod에 마운트하려는 저장소의 종류는 PersistentVolume Provisioner가 제공할 수 있는 StorageClass이어야한다.
  • StatefulSet을 삭제하거나 스케일링하거나 한다고 해서 volume이 삭제되지 않는다. Pod가 잠깐 다운이 되더라도 관련 데이터는 유실이 되지 않도록 하기 위해서 이러한 결정을 내렸다.
  • StatefulSet의 Pod들이 네트워크 ID를 가지기 위해서는 Headless Service가 필요하고 이것은 관리자가 별도로 만들어주어야한다.
  • StatefulSet이 삭제될 때 관리하고 있던 Pod들의 종료에 대해서 어떠한 것도 보장하지 않는다. 순서가 보장되고 graceful하게 Pod들이 종료되도록하기 위해서는 StatefulSet이 관리하는 Pod을 먼저 0으로 scale down하고 삭제해야한다.
  • StatefulSet의 default Pod Management Policy인 OrderedReady로 rolling update를 할 때 잘못된 설정으로 Pod 배포된다면 수동으로 고쳐주어야하는 경우가 존재한다.

Pod Identity

StatefulSet Pod은 고유한 ID가 있다. 이 ID는 ordinal index, stable network identity, stable storage로 구성된다.

Ordinal Index

N개의 레플리카가 존재하는 StatefulSet에 대해서 각각의 Pod는 0부터 시작하여 N-1까지 순차적으로 증가하는 인덱스를 부여받는다.

Stable Network ID

StatefulSet의 Pod들은 StatefulSet의 name과 Pod의 인덱스를 이용하여 hostname을 부여받는다. $(statefulset name)-$(ordinal) 의 형태를 띄며 Headless Service를 이용하여 Pod에게 도메인을 부여할 수 있다.

DNS 설정에 따라서 새로 생성된 Pod의 도메인을 바로 찾을 수 없을 수도 있다. 이러한 문제는 클라이언트가 Pod에 요청을 보낸 시점에 DNS에는 도메인이 등록되었지만 아직 Pod가 생성이 안된 경우 발생할 수도 있고 그 결과 Negative caching으로 인해 실제로 Pod가 정상적으로 동작하지만 잠시동안 도메인을 찾지 못할 수도 있다.

생성된 Pod의 도메인을 찾고싶다면 다음과 같은 옵션이 있다.

  • DNS을 통해 찾는 것이 아니라 직접 Kubernetes API를 통해 도메인을 조회
  • Kubernetes DNS provider caching 시간을 줄이기

Forced Rollback

StatefulSet의 podTemplate을 업데이트하는 과정에서 새로 배포된 Pod이 Running 상태나 Ready 상태가 되지 못하는 경우 StatefulSet은 rollout 과정을 중단하게된다.

이러한 경우가 발생한다면 해당 이슈때문에 podTemplate을 다시 제대로 동작하도록 수정하더라도 실패한 Pod이 다시 자동으로 재배포되지 않는다. 직접 실패한 Pod을 삭제해주어야 StatefulSet은 올바른 podTemplate을 통해 다시 Pod을 생성하게된다.

DaemonSet

DaemonSet은 클러스터를 구성하는 전체 혹은 특정 node마다 Pod을 띄울 수 있게 해준다. 클러스터에 새로운 node가 추가되면 DaemonSet이 관리하는 Pod이 추가된 node에도 배포된다. 클러스터에 node가 삭제되면 해당 DaemonSet Pod은 GC된다.

다음과 같은 상황에서 DaemonSet을 활용해볼 수 있다.

  • 클러스터 각 node에 storage daemon을 띄워 관리한다.
  • 클러스터 각 node에 log collection daemon을 띄워 관리한다.
  • 클러스터 각 node를 모니터링하는 daemon을 띄워 관리한다.

Garbage Collection

Kubernetes garbage collector의 역할은 현재 자신의 owner가 존재하지 않는 object들을 삭제하는 것이다.

Owners and dependents

Kubernetes object들 중 몇몇은 다른 object들의 owner이다. 예를 들어 ReplicaSet은 자신이 관리하는 Pod들의 owner이다. 이렇게 관리를 받고 있는 object들을 owner object의 dependents라고 부른다.

모든 dependent object는 metadata.ownerReferences 필드를 가지고 있으며 해당 필드에는 자신의 owner object를 가리키는 값이 들어간다. Kubernetes는 ownerReference 값을 자동으로 입력해주기도 하는데 ReplicaSet을 클러스터에 생성하는 경우 Kubernetes는 ReplicaSet이 관리하는 Pod의 ownerReference 필드 값을 입력해준다. 이는 ReplicationController, StatefulSet, DaemonSet, Deployment, Job도 마찬가지이다.

CRD를 만드는 경우 직접 ownerReference 필드에 값을 입력함으로써 owner와 depdencent의 관계를 만들 수도 있다.

Controlling how the garbage colltor deletes dependents

어떤 object를 삭제할 때 자신의 dependent들을 같이 삭제할지를 결정할 수 있다. 같이 삭제하는 동작을 cascading deletion이라고 부르며 background, foreground cascading deletion이 존재한다.

Foreground cascading deletion

foreground cascading deletion 과정에서 root object는 먼저 “deletion in progress” 상태가 된다. 이 과정에서 root object는 다음과 같은 특성을 가진다.

  • REST API를 통해 여전히 조회할 수 있다.
  • root object의 deletionTimestamp가 찍혀있다.
  • root object의 metadata.finalizers 에 “foregroundDeletion” 값이 포함되어있다.

“deletion in progress” 상태에서 garbage collector는 root object의 dependent들을 모두 삭제한다. 모두 삭제한 후에 root object를 삭제한다. 한 가지 주목해야할 부분은 ownerReference.blockOwnerDeletion=true 인 dependent들에 대해서만 owner object가 삭제될 때 블럭된다. 해당 값이 true가 아닌 dependent들에 대해서는 삭제 과정 중 블럭되지 않는다.

Background cascading deletion

background cascading deletion 과정에서 Kubernetes는 owner object를 곧바로 지우고 GC가 백그라운드에서 관련 depdendent들을 삭제한다.

Leave a comment