ReplicaSet

ReplicaSet

ReplicaSet의 목적은 주어진 시점에 안정적인 복제 파드(replica Pod) 집합을 실행 상태로 유지하는 것이에요. 그래서 종종 지정된 수의 동일한 파드 가용성을 보장하는 데 사용돼요.

출처: 문서

본문

ReplicaSet이 작동하는 방식

ReplicaSet은 몇 가지 필드로 정의돼요. 획득할 수 있는 파드를 식별하는 방법을 지정하는 셀렉터, 유지해야 하는 파드의 수를 나타내는 replicas 수, 그리고 replicas 기준을 충족시키기 위해 생성해야 하는 새 파드의 데이터를 지정하는 파드 템플릿이 그것이에요. ReplicaSet은 그런 다음 원하는 수에 도달하기 위해 필요에 따라 파드를 생성·삭제함으로써 그 목적을 수행해요. ReplicaSet이 새 파드를 만들 필요가 있을 때는 자신의 파드 템플릿을 사용해요.

ReplicaSet은 파드의 metadata.ownerReferences 필드를 통해 파드에 연결돼요. 이 필드는 현재 객체가 어떤 리소스에 소유되는지 지정해요. ReplicaSet이 획득한 모든 파드의 ownerReferences 필드에는 소유 ReplicaSet의 식별 정보가 들어 있어요. 바로 이 연결을 통해 ReplicaSet은 자신이 유지하는 파드의 상태를 알고 그에 따라 계획을 세워요.

ReplicaSet은 자신의 셀렉터를 사용해 획득할 새 파드를 식별해요. OwnerReference가 없거나 OwnerReference가 컨트롤러가 아니면서 ReplicaSet의 셀렉터와 일치하는 파드가 있다면, 그 파드는 즉시 해당 ReplicaSet이 획득해요.

ReplicaSet을 언제 사용할까

ReplicaSet은 주어진 시점에 지정된 수의 파드 복제본이 실행되고 있음을 보장해요. 하지만 Deployment는 ReplicaSet을 관리하고 파드에 선언적 업데이트를 제공하며 다른 유용한 기능도 많이 제공하는 더 높은 수준의 개념이에요. 따라서 커스텀 업데이트 오케스트레이션이 필요하거나 업데이트가 전혀 필요하지 않은 경우가 아니라면, ReplicaSet을 직접 사용하기보다 Deployment를 사용하는 것을 권장해요.

이것은 사실상 ReplicaSet 객체를 직접 조작할 필요가 없을 수도 있다는 뜻이에요. 대신 Deployment를 사용하고 spec 섹션에 애플리케이션을 정의하세요.

예시 (Example)

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # modify replicas according to your case
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: us-docker.pkg.dev/google-samples/containers/gke/gb-frontend:v5

이 매니페스트를 frontend.yaml로 저장하고 Kubernetes 클러스터에 제출하면 정의된 ReplicaSet과 그것이 관리하는 파드를 만들어요.

kubectl apply -f https://kubernetes.io/examples/controllers/frontend.yaml

그런 다음 배포된 현재 ReplicaSet을 확인할 수 있어요.

kubectl get rs

자신이 만든 frontend를 볼 수 있어요.

NAME       DESIRED   CURRENT   READY   AGE
frontend   3         3         3       6s

ReplicaSet의 상태도 확인할 수 있어요.

kubectl describe rs/frontend

다음과 비슷한 출력을 보게 돼요.

Name:         frontend
Namespace:    default
Selector:     tier=frontend
Labels:       app=guestbook
              tier=frontend
Annotations:  <none>
Replicas:     3 current / 3 desired
Pods Status:  3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  tier=frontend
  Containers:
   php-redis:
    Image:        us-docker.pkg.dev/google-samples/containers/gke/gb-frontend:v5
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From                   Message
  ----    ------            ----  ----                   -------
  Normal  SuccessfulCreate  13s   replicaset-controller  Created pod: frontend-gbgfx
  Normal  SuccessfulCreate  13s   replicaset-controller  Created pod: frontend-rwz57
  Normal  SuccessfulCreate  13s   replicaset-controller  Created pod: frontend-wkl7w

마지막으로 생성된 파드를 확인할 수 있어요.

kubectl get pods

다음과 비슷한 파드 정보를 보게 돼요.

NAME             READY   STATUS    RESTARTS   AGE
frontend-gbgfx   1/1     Running   0          10m
frontend-rwz57   1/1     Running   0          10m
frontend-wkl7w   1/1     Running   0          10m

이 파드들의 owner reference가 frontend ReplicaSet으로 설정되었는지도 확인할 수 있어요. 그러려면 실행 중인 파드 중 하나의 yaml을 가져와요.

kubectl get pods frontend-gbgfx -o yaml

출력은 metadata의 ownerReferences 필드에 frontend ReplicaSet의 정보가 설정된, 이와 비슷하게 보일 거예요.

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2024-02-28T22:30:44Z"
  generateName: frontend-
  labels:
    tier: frontend
  name: frontend-gbgfx
  namespace: default
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: frontend
    uid: e129deca-f864-481b-bb16-b27abfd92292
...

비템플릿 파드 획득

베어 파드(bare Pod)를 아무 문제 없이 만들 수는 있지만, 베어 파드가 ReplicaSet 중 하나의 셀렉터와 일치하는 라벨을 갖지 않도록 하는 것을 강력히 권장해요. 이유는 ReplicaSet이 템플릿이 지정한 파드만 소유하는 데 제한되지 않기 때문이에요. 앞선 섹션에서 언급한 방식으로 다른 파드를 획득할 수도 있어요.

앞선 frontend ReplicaSet 예시와 다음 매니페스트에 지정된 파드들을 가정해 봐요.

apiVersion: v1
kind: Pod
metadata:
  name: pod1
  labels:
    tier: frontend
spec:
  containers:
  - name: hello1
    image: gcr.io/google-samples/hello-app:2.0

---

apiVersion: v1
kind: Pod
metadata:
  name: pod2
  labels:
    tier: frontend
spec:
  containers:
  - name: hello2
    image: gcr.io/google-samples/hello-app:1.0

이 파드들은 소유자 참조로 컨트롤러(또는 어떤 객체)가 없고 frontend ReplicaSet의 셀렉터와 일치하므로, 즉시 그것이 획득해요.

frontend ReplicaSet이 배포되어 초기 파드 복제본을 설정해 replica 수 요구 사항을 충족한 후에 파드를 만든다고 가정해 봐요.

kubectl apply -f https://kubernetes.io/examples/pods/pod-rs.yaml

새 파드들은 ReplicaSet이 획득한 다음, ReplicaSet이 원하는 수를 초과하므로 즉시 종료돼요.

파드를 가져오면:

kubectl get pods

출력은 새 파드들이 이미 종료되었거나 종료되는 과정에 있음을 보여줘요.

NAME             READY   STATUS        RESTARTS   AGE
frontend-b2zdv   1/1     Running       0          10m
frontend-vcmts   1/1     Running       0          10m
frontend-wtsmm   1/1     Running       0          10m
pod1             0/1     Terminating   0          1s
pod2             0/1     Terminating   0          1s

먼저 파드를 만들고:

kubectl apply -f https://kubernetes.io/examples/pods/pod-rs.yaml

그런 다음 ReplicaSet을 만든다면:

kubectl apply -f https://kubernetes.io/examples/controllers/frontend.yaml

ReplicaSet이 파드들을 획득하고, 새 파드 수와 원래 파드가 원하는 수와 일치할 때까지 자신의 spec에 따라서만 새 파드를 만들었음을 볼 수 있어요. 파드를 가져오면:

kubectl get pods

출력에서 다음이 드러나요.

NAME             READY   STATUS    RESTARTS   AGE
frontend-hmmj2   1/1     Running   0          9s
pod1             1/1     Running   0          36s
pod2             1/1     Running   0          36s

이런 식으로 ReplicaSet은 동질적이지 않은(non-homogeneous) 파드 집합을 소유할 수 있어요.

ReplicaSet 매니페스트 작성

다른 모든 Kubernetes API 객체와 마찬가지로 ReplicaSet은 apiVersion, kind, metadata 필드가 필요해요. ReplicaSet의 경우 kind는 항상 ReplicaSet이에요.

컨트롤 플레인이 ReplicaSet을 위해 새 파드를 만들 때, ReplicaSet의 .metadata.name이 그 파드 이름의 일부 기반이 돼요. ReplicaSet의 이름은 유효한 DNS 서브도메인 값이어야 하지만, 이것은 파드 호스트 이름에 예상치 못한 결과를 만들 수 있어요. 최고의 호환성을 위해 이름은 DNS 라벨에 대한 더 제한적인 규칙을 따라야 해요.

ReplicaSet은 또한 .spec 섹션이 필요해요.

파드 템플릿 (Pod Template)

.spec.template파드 템플릿이며, 라벨이 있어야 해요. frontend.yaml 예시에서 tier: frontend라는 라벨 하나가 있었어요. 다른 컨트롤러의 셀렉터와 겹치지 않도록 주의해야 해요. 그렇지 않으면 그들이 이 파드를 입양하려 할 거예요.

템플릿의 재시작 정책 필드인 .spec.template.spec.restartPolicy의 경우 허용되는 값은 Always뿐이며, 그것이 기본값이에요.

파드 셀렉터 (Pod Selector)

.spec.selector 필드는 라벨 셀렉터예요. 앞서 논의했듯이 이것은 획득할 잠재적 파드를 식별하는 데 사용되는 라벨이에요. frontend.yaml 예시에서 셀렉터는:

matchLabels:
  tier: frontend

ReplicaSet에서 .spec.template.metadata.labelsspec.selector와 일치해야 해요. 그렇지 않으면 API가 거부해요.

복제본 (Replicas)

.spec.replicas를 설정해 동시에 실행해야 하는 파드 수를 지정할 수 있어요. ReplicaSet은 이 수와 일치하도록 파드를 생성/삭제해요.

.spec.replicas를 지정하지 않으면 기본값은 1이에요.

ReplicaSet 사용하기

ReplicaSet과 그 파드 삭제

ReplicaSet과 모든 파드를 삭제하려면 kubectl delete를 사용해요. 가비지 컬렉터가 기본적으로 모든 종속 파드를 자동으로 삭제해요.

REST API나 client-go 라이브러리를 사용할 때는 -d 옵션에서 propagationPolicyBackground 또는 Foreground로 설정해야 해요. 예를 들어:

kubectl proxy --port=8080
curl -X DELETE  'localhost:8080/apis/apps/v1/namespaces/default/replicasets/frontend' \
  -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Foreground"}' \
  -H "Content-Type: application/json"

ReplicaSet만 삭제

kubectl delete--cascade=orphan 옵션과 함께 사용해 어떤 파드에도 영향을 주지 않고 ReplicaSet을 삭제할 수 있어요. REST API나 client-go 라이브러리를 사용할 때는 propagationPolicyOrphan으로 설정해야 해요. 예를 들어:

kubectl proxy --port=8080
curl -X DELETE  'localhost:8080/apis/apps/v1/namespaces/default/replicasets/frontend' \
  -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Orphan"}' \
  -H "Content-Type: application/json"

원본이 삭제되면 그것을 대체할 새 ReplicaSet을 만들 수 있어요. 옛 selector와 새 .spec.selector가 같기만 하면 새 ReplicaSet이 기존 파드들을 입양할 거예요. 다만 기존 파드를 새롭고 다른 파드 템플릿과 일치시키려는 노력은 하지 않아요. 파드를 통제된 방식으로 새 spec으로 업데이트하려면 Deployment를 사용해요. ReplicaSet은 롤링 업데이트를 직접 지원하지 않기 때문이에요.

파드 종료 (Terminating Pods)

DeploymentReplicaSetTerminatingReplicas 기능 게이트를 API 서버kube-controller-manager에서 설정해 이 기능을 활성화할 수 있어요.

삭제나 축소로 인해 terminating이 되는 파드는 종료되는 데 오래 걸릴 수 있고, 그 기간 동안 추가 리소스를 소비할 수 있어요. 그 결과 모든 파드의 총 수는 일시적으로 .spec.replicas를 초과할 수 있어요. terminating 파드는 ReplicaSet의 .status.terminatingReplicas 필드로 추적할 수 있어요.

ReplicaSet에서 파드 격리

파드의 라벨을 변경해 ReplicaSet에서 제거할 수 있어요. 이 기법은 디버깅이나 데이터 복구 등을 위해 파드를 서비스에서 제거하는 데 사용할 수 있어요. 이렇게 제거된 파드는 (replicas 수를 동시에 변경하지 않는다고 가정하면) 자동으로 대체돼요.

ReplicaSet 확장

.spec.replicas 필드를 업데이트하는 것만으로 ReplicaSet을 쉽게 확장하거나 축소할 수 있어요. ReplicaSet 컨트롤러는 일치하는 라벨 셀렉터를 가진 원하는 수의 파드가 가용하고 작동하는지 보장해요.

축소할 때 ReplicaSet 컨트롤러는 다음 일반 알고리즘에 따라 축소할 파드를 우선순위로 정하기 위해 가용한 파드를 정렬해 어떤 파드를 삭제할지 선택해요.

  • Pending(그리고 스케줄 불가능한) 파드를 먼저 축소
  • controller.kubernetes.io/pod-deletion-cost 어노테이션이 설정되어 있으면 값이 더 낮은 파드가 먼저 옴
  • 복제본이 더 많은 노드의 파드가 복제본이 더 적은 노드의 파드보다 먼저 옴
  • 파드의 생성 시간이 다르면 더 최근에 생성된 파드가 더 오래된 파드보다 먼저 옴(생성 시간은 정수 로그 스케일로 버킷화됨)

위 모두가 일치하면 선택은 무작위예요.

파드 삭제 비용 (Pod deletion cost)

controller.kubernetes.io/pod-deletion-cost 어노테이션을 사용해 ReplicaSet을 축소할 때 어떤 파드를 먼저 제거할지에 대한 선호를 설정할 수 있어요.

이 어노테이션은 파드에 설정해야 하며, 범위는 [-2147483648, 2147483647]이에요. 같은 ReplicaSet에 속한 다른 파드와 비교한 파드 삭제 비용을 나타내요. 삭제 비용이 낮은 파드가 높은 파드보다 먼저 삭제되는 것이 선호돼요.

이 어노테이션을 설정하지 않은 파드의 암시적 값은 0이며, 음수 값도 허용돼요. 잘못된 값은 API 서버가 거부해요.

이 기능은 베타이며 기본적으로 활성화돼 있어요. kube-apiserver와 kube-controller-manager 모두에서 PodDeletionCost 기능 게이트로 비활성화할 수 있어요.

이것은 최선 노력(best-effort) 기준으로 존중되므로 파드 삭제 순서에 어떤 보장도 제공하지 않아요. 사용자는 메트릭 값을 기반으로 업데이트하는 것처럼 어노테이션을 자주 업데이트하지 않아야 해요. 그렇게 하면 apiserver에 상당한 수의 파드 업데이트가 생성되거든요.

예시 사용 사례

애플리케이션의 서로 다른 파드는 서로 다른 활용 수준을 가질 수 있어요. 축소 시 애플리케이션은 활용도가 낮은 파드를 먼저 제거하는 것을 선호할 수 있어요. 파드를 자주 업데이트하지 않기 위해 애플리케이션은 축소를 실행하기 전에 controller.kubernetes.io/pod-deletion-cost를 한 번 업데이트해야 해요(어노테이션을 파드 활용 수준에 비례하는 값으로 설정). 이것은 애플리케이션 자체가 축소를 제어할 때 동작해요. 예를 들어 Spark 배포의 driver 파드가 그렇죠.

Horizontal Pod Autoscaler 대상으로서의 ReplicaSet

ReplicaSet은 Horizontal Pod Autoscaler(HPA)의 대상이 될 수도 있어요. 즉 ReplicaSet은 HPA로 자동 확장될 수 있어요. 다음은 앞선 예시에서 만든 ReplicaSet을 대상으로 하는 HPA 예시예요.

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: frontend-scaler
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: ReplicaSet
    name: frontend
  minReplicas: 3
  maxReplicas: 10
  targetCPUUtilizationPercentage: 50

이 매니페스트를 hpa-rs.yaml로 저장하고 Kubernetes 클러스터에 제출하면, 복제된 파드의 CPU 사용량에 따라 대상 ReplicaSet을 자동 확장하는 정의된 HPA를 만들어야 해요.

kubectl apply -f https://k8s.io/examples/controllers/hpa-rs.yaml

또는 kubectl autoscale 명령어로 같은 일을 할 수 있어요 (그리고 더 쉬워요!).

kubectl autoscale rs frontend --max=10 --min=3 --cpu=50%

ReplicaSet의 대안

Deployment (권장)

Deployment는 ReplicaSet을 소유하고, 선언적이고 서버 측의 롤링 업데이트로 ReplicaSet과 그 파드를 업데이트할 수 있는 객체예요. ReplicaSet은 독립적으로 사용될 수 있지만, 오늘날에는 주로 Deployment가 파드 생성·삭제·업데이트를 오케스트레이션하는 메커니즘으로 사용해요. Deployment를 사용하면 Deployment가 만든 ReplicaSet을 관리할 걱정을 하지 않아도 돼요. Deployment가 자신의 ReplicaSet을 소유하고 관리해요. 따라서 ReplicaSet이 필요할 때는 Deployment를 사용하는 것이 권장돼요.

베어 파드 (Bare Pods)

사용자가 직접 만든 파드와 달리, ReplicaSet은 노드 실패나 커널 업그레이드 같은 파괴적인 노드 유지보수 등 어떤 이유로 삭제되거나 종료된 파드를 대체해요. 그래서 애플리케이션이 단일 파드만 필요하더라도 ReplicaSet을 사용할 것을 권장해요. 이것을 프로세스 감독자와 비슷하게 생각하면 돼요. 단, 단일 노드의 개별 프로세스 대신 여러 노드에 걸친 여러 파드를 감독할 뿐이에요. ReplicaSet은 로컬 컨테이너 재시작을 kubelet 같은 노드의 어떤 에이전트에 위임해요.

Job

스스로 종료될 것으로 기대되는 파드(즉 배치 작업)에는 ReplicaSet 대신 Job을 사용해요.

DaemonSet

머신 모니터링이나 머신 로깅 같은 머신 수준 기능을 제공하는 파드에는 ReplicaSet 대신 DaemonSet을 사용해요. 이런 파드는 머신 수명에 묶인 수명을 가져요. 다른 파드가 시작되기 전에 그 머신에서 파드가 실행 중이어야 하고, 머신이 재부팅/종료될 준비가 되면 종료되어도 안전해요.

ReplicationController

ReplicaSet은 ReplicationController의 후속이에요. 둘은 같은 목적을 제공하고 비슷하게 동작하지만, ReplicationController는 라벨 사용자 가이드에 설명된 집합 기반(set-based) 셀렉터 요구 사항을 지원하지 않아요. 그래서 ReplicaSet이 ReplicationController보다 선호돼요.

다음 단계

더 알아보기 (Learn more)