선언적 오브젝트 구성 관리하기

선언적 오브젝트 구성 관리하기

쿠버네티스 오브젝트는 여러 오브젝트 구성 파일을 디렉토리에 저장하고 kubectl apply를 사용해 그 오브젝트들을 재귀적으로 생성·업데이트함으로써 만들고, 업데이트하고, 삭제할 수 있어요. 이 방법은 변경 사항을 오브젝트 구성 파일로 병합하지 않고 라이브 오브젝트에 대한 쓰기를 유지합니다. kubectl diffapply가 어떤 변경을 만들지 미리 볼 수 있게 해줘요.

출처: 문서

본문

시작하기 전에

kubectl을 설치해요.

트레이드오프

kubectl 도구는 세 가지 종류의 오브젝트 관리를 지원해요:

  • 명령형 명령(Imperative commands)
  • 명령형 오브젝트 구성(Imperative object configuration)
  • 선언형 오브젝트 구성(Declarative object configuration)

각 관리 방식의 장단점에 대한 논의는 쿠버네티스 오브젝트 관리를 참고하세요.

개요

선언형 오브젝트 구성은 쿠버네티스 오브젝트 정의와 구성에 대한 확실한 이해가 필요해요. 아직 읽지 않았다면 다음 문서를 읽고 완료하세요:

다음은 이 문서에서 사용하는 용어의 정의입니다:

  • 오브젝트 구성 파일 / 구성 파일: 쿠버네티스 오브젝트의 구성을 정의하는 파일. 이 주제는 구성 파일을 kubectl apply에 전달하는 방법을 보여줘요. 구성 파일은 보통 Git 같은 소스 제어에 저장됩니다.
  • 라이브 오브젝트 구성 / 라이브 구성: 쿠버네티스 클러스터가 관찰하는 오브젝트의 라이브 구성 값. 이 값들은 쿠버네티스 클러스터 스토리지, 보통 etcd에 유지됩니다.
  • 선언형 구성 작성자 / 선언형 작성자: 라이브 오브젝트를 업데이트하는 사람이나 소프트웨어 구성 요소. 이 주제에서 언급하는 라이브 작성자는 오브젝트 구성 파일을 변경하고 kubectl apply를 실행해 변경 사항을 씁니다.

오브젝트 생성 방법

지정된 디렉토리의 구성 파일로 정의된, 이미 존재하는 오브젝트를 제외한 모든 오브젝트를 만들려면 kubectl apply를 사용해요:

kubectl apply -f <directory>

이 명령은 각 오브젝트에 kubectl.kubernetes.io/last-applied-configuration: '{...}' 어노테이션을 설정해요. 이 어노테이션은 오브젝트를 만드는 데 사용된 오브젝트 구성 파일의 내용을 담고 있습니다.

디렉토리를 재귀적으로 처리하려면 -R 플래그를 추가해요.

오브젝트 구성 파일의 예시는 다음과 같아요:

kubectl diff를 실행해 생성될 오브젝트를 출력해요:

kubectl diff -f https://k8s.io/examples/application/simple_deployment.yaml

diff서버 사이드 dry-run을 사용하며, 이는 kube-apiserver에서 활성화되어 있어야 해요.

diff가 dry-run 모드로 서버 사이드 apply 요청을 수행하므로 PATCH, CREATE, UPDATE 권한을 부여해야 해요. 자세한 내용은 Dry-Run 인가를 참고하세요.

kubectl apply로 오브젝트를 만들어요:

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

kubectl get으로 라이브 구성을 출력해요:

kubectl get -f https://k8s.io/examples/application/simple_deployment.yaml -o yaml

출력은 kubectl.kubernetes.io/last-applied-configuration 어노테이션이 라이브 구성에 기록되었고 구성 파일과 일치함을 보여줍니다:

kind: Deployment
metadata:
  annotations:
    # ...
    # This is the json representation of simple_deployment.yaml
    # It was written by kubectl apply when the object was created
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment",
      "metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
      "spec":{"minReadySeconds":5,"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
      "spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx",
      "ports":[{"containerPort":80}]}]}}}}
  # ...
spec:
  # ...
  minReadySeconds: 5
  selector:
    matchLabels:
      # ...
      app: nginx
  template:
    metadata:
      # ...
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.14.2
        # ...
        name: nginx
        ports:
        - containerPort: 80
        # ...
      # ...
    # ...
  # ...

오브젝트 업데이트 방법

디렉토리에 정의된 모든 오브젝트를, 이미 존재하더라도 업데이트하려면 kubectl apply를 사용할 수 있어요. 이 접근 방식은 다음을 달성합니다:

  1. 라이브 구성에서 구성 파일에 나타나는 필드를 설정한다.
  2. 라이브 구성에서 구성 파일에서 제거된 필드를 지운다.
kubectl diff -f <directory>
kubectl apply -f <directory>

디렉토리를 재귀적으로 처리하려면 -R 플래그를 추가해요.

예시 구성 파일은 다음과 같아요:

kubectl apply로 오브젝트를 만들어요:

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

설명을 위해 위 명령은 디렉토리 대신 단일 구성 파일을 참조합니다.

kubectl get으로 라이브 구성을 출력해요:

kubectl get -f https://k8s.io/examples/application/simple_deployment.yaml -o yaml

출력은 kubectl.kubernetes.io/last-applied-configuration 어노테이션이 라이브 구성에 기록되었고 구성 파일과 일치함을 보여줍니다:

kind: Deployment
metadata:
  annotations:
    # ...
    # This is the json representation of simple_deployment.yaml
    # It was written by kubectl apply when the object was created
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment",
      "metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
      "spec":{"minReadySeconds":5,"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
      "spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx",
      "ports":[{"containerPort":80}]}]}}}}
  # ...
spec:
  # ...
  minReadySeconds: 5
  selector:
    matchLabels:
      # ...
      app: nginx
  template:
    metadata:
      # ...
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.14.2
        # ...
        name: nginx
        ports:
        - containerPort: 80
        # ...
      # ...
    # ...
  # ...

kubectl scale을 사용해 라이브 구성의 replicas 필드를 직접 업데이트해요. 이는 kubectl apply를 사용하지 않습니다:

kubectl scale deployment/nginx-deployment --replicas=2

kubectl get으로 라이브 구성을 출력해요:

kubectl get deployment nginx-deployment -o yaml

출력은 replicas 필드가 2로 설정되었고 last-applied-configuration 어노테이션에 replicas 필드가 없음을 보여줍니다:

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    # ...
    # note that the annotation does not contain replicas
    # because it was not updated through apply
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment",
      "metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
      "spec":{"minReadySeconds":5,"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
      "spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx",
      "ports":[{"containerPort":80}]}]}}}}
  # ...
spec:
  replicas: 2 # written by scale
  # ...
  minReadySeconds: 5
  selector:
    matchLabels:
      # ...
      app: nginx
  template:
    metadata:
      # ...
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.14.2
        # ...
        name: nginx
        ports:
        - containerPort: 80
      # ...

simple_deployment.yaml 구성 파일을 업데이트해 이미지를 nginx:1.14.2에서 nginx:1.16.1로 바꾸고 minReadySeconds 필드를 삭제해요:

구성 파일에 적용한 변경을 반영해요:

kubectl diff -f https://k8s.io/examples/application/update_deployment.yaml
kubectl apply -f https://k8s.io/examples/application/update_deployment.yaml

kubectl get으로 라이브 구성을 출력해요:

kubectl get -f https://k8s.io/examples/application/update_deployment.yaml -o yaml

출력은 라이브 구성에 다음 변경이 있음을 보여줍니다:

  • replicas 필드는 kubectl scale이 설정한 값 2를 유지한다. 구성 파일에서 생략되었기 때문에 가능하다.
  • image 필드가 nginx:1.14.2에서 nginx:1.16.1로 업데이트되었다.
  • last-applied-configuration 어노테이션이 새 이미지로 업데이트되었다.
  • minReadySeconds 필드가 지워졌다.
  • last-applied-configuration 어노테이션에 더 이상 minReadySeconds 필드가 없다.
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    # ...
    # The annotation contains the updated image to nginx 1.16.1,
    # but does not contain the updated replicas to 2
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment",
      "metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
      "spec":{"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
      "spec":{"containers":[{"image":"nginx:1.16.1","name":"nginx",
      "ports":[{"containerPort":80}]}]}}}}
    # ...
spec:
  replicas: 2 # Set by `kubectl scale`.  Ignored by `kubectl apply`.
  # minReadySeconds cleared by `kubectl apply`
  # ...
  selector:
    matchLabels:
      # ...
      app: nginx
  template:
    metadata:
      # ...
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.16.1 # Set by `kubectl apply`
        # ...
        name: nginx
        ports:
        - containerPort: 80
        # ...
      # ...
    # ...
  # ...

kubectl apply를 명령형 오브젝트 구성 명령인 create·replace와 섞는 것은 지원되지 않아요. createreplacekubectl apply가 업데이트를 계산하는 데 사용하는 kubectl.kubernetes.io/last-applied-configuration을 유지하지 않기 때문입니다.

오브젝트 삭제 방법

kubectl apply로 관리되는 오브젝트를 삭제하는 두 가지 접근 방식이 있어요.

권장: kubectl delete -f <filename>

명령형 명령으로 오브젝트를 수동 삭제하는 것이 권장되는 접근 방식이에요. 무엇을 삭제하는지 더 명시적이고 사용자가 의도치 않게 무언가를 삭제할 가능성이 낮기 때문입니다:

kubectl delete -f <filename>

대안: kubectl apply -f <directory> --prune

kubectl delete의 대안으로, 로컬 파일시스템의 디렉토리에서 매니페스트가 제거된 뒤 삭제할 오브젝트를 식별하는 데 kubectl apply를 사용할 수 있어요.

쿠버네티스 v1.37에서 kubectl apply에는 두 가지 프루닝 모드가 있습니다:

  • Allowlist 기반 프루닝: 이 모드는 kubectl v1.5부터 존재했지만 설계상의 사용성·정확성·성능 문제로 여전히 알파 상태예요. ApplySet 기반 모드가 이를 대체하도록 설계되었습니다.
  • ApplySet 기반 프루닝: _apply set_은 kubectl이 apply 작업 전반에 걸쳐 집합 구성원을 정확·효율적으로 추적하는 데 사용할 수 있는 서버 사이드 오브젝트(기본적으로 Secret)예요. 이 모드는 allowlist 기반 프루닝의 대체물로 kubectl v1.27에서 알파로 도입되었습니다.

allow list 모드에서 kubectl apply와 함께 --prune을 사용할 때는 주의해야 해요. 어떤 오브젝트가 프루닝되는지는 --prune-allowlist, --selector, --namespace 플래그의 값에 의존하며, 범위 내 오브젝트의 동적 발견에 의존합니다. 특히 호출 사이에 플래그 값이 변경되면 오브젝트가 예기치 않게 삭제되거나 유지될 수 있습니다.

allowlist 기반 프루닝을 사용하려면 kubectl apply 호출에 다음 플래그를 추가해요:

  • --prune: 현재 호출에 전달된 집합에 없는 이전에 적용된 오브젝트를 삭제한다.
  • --prune-allowlist: 프루닝을 고려할 group-version-kind(GVK) 목록. 이 플래그는 선택 사항이지만 강력히 권장된다. 기본값이 네임스페이스 범위와 클러스터 범위 유형의 부분 목록이라 예상 밖의 결과를 낳을 수 있기 때문이다.
  • --selector/-l: 프루닝할 오브젝트 집합을 제한하는 라벨 선택자. 선택 사항이지만 강력히 권장된다.
  • --all: --selector/-l 대신 사용해 allowlist 유형의 이전에 적용된 모든 오브젝트를 명시적으로 선택한다.

Allowlist 기반 프루닝은 주어진 라벨(있으면)과 일치하는 allowlist GVK의 모든 오브젝트를 API 서버에 질의하고, 반환된 라이브 오브젝트 구성을 오브젝트 매니페스트 파일과 대조하려 시도해요. 오브젝트가 질의와 일치하고, 디렉토리에 매니페스트가 없고, kubectl.kubernetes.io/last-applied-configuration 어노테이션이 있다면 삭제됩니다.

kubectl apply -f <directory> --prune -l <labels> --prune-allowlist=<gvk-list>

Apply with prune은 오브젝트 매니페스트를 담고 있는 루트 디렉토리에 대해서만 실행해야 해요. 하위 디렉토리에 대해 실행하면, 이전에 적용되었고 주어진 라벨(있으면)을 가졌으며 하위 디렉토리에 나타나지 않는 오브젝트가 의도치 않게 삭제될 수 있습니다.

kubectl apply --prune --applyset은 알파 상태이며, 이후 릴리스에서 하위 호환되지 않는 변경이 도입될 수 있어요.

ApplySet 기반 프루닝을 사용하려면 KUBECTL_APPLYSET=true 환경 변수를 설정하고 kubectl apply 호출에 다음 플래그를 추가해요:

  • --prune: 현재 호출에 전달된 집합에 없는 이전에 적용된 오브젝트를 삭제한다.
  • --applyset: kubectl이 apply 작업 전반에 걸쳐 집합 구성원을 정확·효율적으로 추적하는 데 사용할 수 있는 오브젝트 이름.
KUBECTL_APPLYSET=true kubectl apply -f <directory> --prune --applyset=<name>

기본적으로 사용되는 ApplySet 부모 오브젝트의 유형은 Secret이에요. 하지만 --applyset=configmaps/<name> 형식으로 ConfigMap도 사용할 수 있습니다. Secret이나 ConfigMap을 사용할 때 kubectl은 오브젝트가 이미 없으면 만들어요.

ApplySet 부모 오브젝트로 커스텀 리소스를 사용하는 것도 가능해요. 이를 활성화하려면 사용하려는 리소스를 정의하는 Custom Resource Definition(CRD)에 applyset.kubernetes.io/is-parent-type: true 라벨을 붙이세요. 그런 다음 ApplySet 부모로 사용할 오브젝트를 만듭니다(kubectl은 커스텀 리소스에 대해 자동으로 만들지 않아요). 마지막으로 applyset 플래그에서 그 오브젝트를 --applyset=<resource>.<group>/<name>(예: widgets.custom.example.com/widget-name)으로 참조합니다.

ApplySet 기반 프루닝에서 kubectl은 각 오브젝트가 서버로 보내지기 전에 집합의 각 오브젝트에 applyset.kubernetes.io/part-of=<parentID> 라벨을 추가해요. 성능상의 이유로 집합이 포함하는 리소스 유형·네임스페이스 목록도 수집해 라이브 부모 오브젝트의 어노테이션에 추가합니다. 마지막으로 apply 작업 끝에 applyset.kubernetes.io/part-of=<parentID> 라벨로 정의된 집합에 속하는, 해당 유형의 오브젝트를 해당 네임스페이스(또는 해당하는 경우 클러스터 범위)에서 API 서버에 질의합니다.

주의 사항과 제한:

  • 각 오브젝트는 최대 하나의 집합의 구성원일 수 있다.
  • 기본 Secret을 포함한 네임스페이스 범위 부모를 사용할 때는 --namespace 플래그가 필요하다. 즉 여러 네임스페이스에 걸친 ApplySet은 클러스터 범위의 커스텀 리소스를 부모 오브젝트로 사용해야 한다.
  • 여러 디렉토리와 함께 ApplySet 기반 프루닝을 안전하게 사용하려면 각각에 고유한 ApplySet 이름을 사용해라.

오브젝트 보기 방법

kubectl get-o yaml과 함께 사용해 라이브 오브젝트의 구성을 볼 수 있어요:

kubectl get -f <filename|url> -o yaml

apply가 차이를 계산하고 변경을 병합하는 방법

*패치(patch)*는 오브젝트 전체가 아니라 오브젝트의 특정 필드로 범위가 한정된 업데이트 작업이에요. 이렇게 하면 오브젝트를 먼저 읽지 않고 오브젝트의 특정 필드 집합만 업데이트할 수 있습니다.

kubectl apply가 오브젝트의 라이브 구성을 업데이트할 때 API 서버로 패치 요청을 보내 그렇게 해요. 패치는 라이브 오브젝트 구성의 특정 필드로 범위가 한정된 업데이트를 정의합니다. kubectl apply 명령은 구성 파일, 라이브 구성, 그리고 라이브 구성에 저장된 last-applied-configuration 어노테이션을 사용해 이 패치 요청을 계산해요.

병합 패치 계산

kubectl apply 명령은 구성 파일의 내용을 kubectl.kubernetes.io/last-applied-configuration 어노테이션에 씁니다. 이는 구성 파일에서 제거되어 라이브 구성에서 지워져야 하는 필드를 식별하는 데 사용됩니다. 삭제되거나 설정될 필드를 계산하는 데 사용되는 단계는 다음과 같아요:

  1. 삭제할 필드를 계산한다. 이는 last-applied-configuration에 있고 구성 파일에 없는 필드다.
  2. 추가하거나 설정할 필드를 계산한다. 이는 구성 파일에 있고 값이 라이브 구성과 일치하지 않는 필드다.

예시를 들어볼게요. 이것이 Deployment 오브젝트의 구성 파일이라고 가정해요:

또한 같은 Deployment 오브젝트의 라이브 구성을 이렇게 가정해요:

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    # ...
    # note that the annotation does not contain replicas
    # because it was not updated through apply
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment",
      "metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
      "spec":{"minReadySeconds":5,"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
      "spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx",
      "ports":[{"containerPort":80}]}]}}}}
  # ...
spec:
  replicas: 2 # written by scale
  # ...
  minReadySeconds: 5
  selector:
    matchLabels:
      # ...
      app: nginx
  template:
    metadata:
      # ...
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.14.2
        # ...
        name: nginx
        ports:
        - containerPort: 80
      # ...

kubectl apply가 수행할 병합 계산은 다음과 같아요:

  1. last-applied-configuration에서 값을 읽고 구성 파일의 값과 비교해 삭제할 필드를 계산한다. 로컬 오브젝트 구성 파일에서 명시적으로 null로 설정된 필드는 last-applied-configuration에 나타나는지 여부와 관계없이 지운다. 이 예시에서 minReadySecondslast-applied-configuration 어노테이션에 나타나지만 구성 파일에는 없다. 동작: 라이브 구성에서 minReadySeconds를 지운다.
  2. 구성 파일에서 값을 읽고 라이브 구성의 값과 비교해 설정할 필드를 계산한다. 이 예시에서 구성 파일의 image 값은 라이브 구성의 값과 일치하지 않는다. 동작: 라이브 구성에서 image 값을 설정한다.
  3. last-applied-configuration 어노테이션을 구성 파일의 값과 일치하도록 설정한다.
  4. 1, 2, 3의 결과를 API 서버로 보낼 단일 패치 요청으로 병합한다.

다음은 병합 결과로 만들어지는 라이브 구성입니다:

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    # ...
    # The annotation contains the updated image to nginx 1.16.1,
    # but does not contain the updated replicas to 2
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment",
      "metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
      "spec":{"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
      "spec":{"containers":[{"image":"nginx:1.16.1","name":"nginx",
      "ports":[{"containerPort":80}]}]}}}}
    # ...
spec:
  selector:
    matchLabels:
      # ...
      app: nginx
  replicas: 2 # Set by `kubectl scale`.  Ignored by `kubectl apply`.
  # minReadySeconds cleared by `kubectl apply`
  # ...
  template:
    metadata:
      # ...
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.16.1 # Set by `kubectl apply`
        # ...
        name: nginx
        ports:
        - containerPort: 80
        # ...
      # ...
    # ...
  # ...

서로 다른 유형의 필드가 병합되는 방법

구성 파일의 특정 필드가 라이브 구성과 병합되는 방식은 필드의 유형에 따라 달라져요. 여러 유형의 필드가 있습니다:

  • 프리미티브(primitive): string, integer, boolean 유형의 필드. 예를 들어 imagereplicas는 프리미티브 필드다. 동작: 교체한다.

  • 맵(map), *오브젝트(object)*라고도 함: 맵 유형 또는 하위 필드를 포함하는 복합 유형의 필드. 예를 들어 labels, annotations, spec, metadata는 모두 맵이다. 동작: 요소나 하위 필드를 병합한다.

  • 리스트(list): 프리미티브 유형이나 맵이 될 수 있는 항목의 목록을 포함하는 필드. 예를 들어 containers, ports, args는 리스트다. 동작: 다양하다.

kubectl apply가 맵이나 리스트 필드를 업데이트할 때 보통 전체 필드를 교체하지 않고 개별 하위 요소를 업데이트해요. 예를 들어 Deployment의 spec을 병합할 때 전체 spec을 교체하지 않습니다. 대신 replicas 같은 spec의 하위 필드를 비교하고 병합합니다.

프리미티브 필드에 대한 변경 병합

프리미티브 필드는 교체되거나 지워집니다.

-는 값을 사용하지 않으므로 "해당 없음"을 나타내는 데 쓰입니다.

오브젝트 구성 파일의 필드 라이브 오브젝트 구성의 필드 last-applied-configuration의 필드 동작
있음 있음 - 라이브를 구성 파일 값으로 설정한다.
있음 없음 - 라이브를 로컬 구성으로 설정한다.
없음 - 있음 라이브 구성에서 지운다.
없음 - 없음 아무것도 하지 않는다. 라이브 값을 유지한다.

맵 필드에 대한 변경 병합

맵을 나타내는 필드는 맵의 각 하위 필드나 요소를 비교해 병합됩니다:

-는 값을 사용하지 않으므로 "해당 없음"을 나타내는 데 쓰입니다.

오브젝트 구성 파일의 키 라이브 오브젝트 구성의 키 last-applied-configuration의 필드 동작
있음 있음 - 하위 필드 값을 비교한다.
있음 없음 - 라이브를 로컬 구성으로 설정한다.
없음 - 있음 라이브 구성에서 삭제한다.
없음 - 없음 아무것도 하지 않는다. 라이브 값을 유지한다.

list 유형의 필드에 대한 변경 병합

리스트에 대한 변경 병합은 세 가지 전략 중 하나를 사용해요:

  • 모든 요소가 프리미티브면 리스트를 교체한다.
  • 복합 요소의 리스트에서 개별 요소를 병합한다.
  • 프리미티브 요소의 리스트를 병합한다.

전략 선택은 필드별로 이뤄집니다.

모든 요소가 프리미티브면 리스트 교체

리스트를 프리미티브 필드처럼 취급한다. 전체 리스트를 교체하거나 삭제한다. 이러면 순서가 보존됩니다.

예시: kubectl apply를 사용해 Pod의 Container args 필드를 업데이트한다. 이렇게 하면 라이브 구성의 args 값이 구성 파일의 값으로 설정됩니다. 이전에 라이브 구성에 추가했던 args 요소는 손실됩니다. 구성 파일에 정의된 args 요소의 순서는 라이브 구성에 유지됩니다.

# last-applied-configuration value
    args: ["a", "b"]

# configuration file value
    args: ["a", "c"]

# live configuration
    args: ["a", "b", "d"]

# result after merge
    args: ["a", "c"]

설명: 병합이 구성 파일 값을 새 리스트 값으로 사용했습니다.

복합 요소 리스트의 개별 요소 병합

리스트를 맵처럼 취급하고, 각 요소의 특정 필드를 키로 취급한다. 개별 요소를 추가·삭제·업데이트한다. 이러면 순서가 보존되지 않습니다.

이 병합 전략은 각 필드에 patchMergeKey라는 특별한 태그를 사용해요. patchMergeKey는 쿠버네티스 소스 코드의 각 필드에 정의되어 있습니다: types.go. 맵 리스트를 병합할 때 주어진 요소에 대해 patchMergeKey로 지정된 필드는 그 요소의 맵 키처럼 사용됩니다.

예시: kubectl apply를 사용해 PodSpec의 containers 필드를 업데이트한다. 이렇게 하면 각 요소가 name으로 키가 지정된 맵인 것처럼 리스트가 병합됩니다.

# last-applied-configuration value
    containers:
    - name: nginx
      image: nginx:1.16
    - name: nginx-helper-a # key: nginx-helper-a; will be deleted in result
      image: helper:1.3
    - name: nginx-helper-b # key: nginx-helper-b; will be retained
      image: helper:1.3

# configuration file value
    containers:
    - name: nginx
      image: nginx:1.16
    - name: nginx-helper-b
      image: helper:1.3
    - name: nginx-helper-c # key: nginx-helper-c; will be added in result
      image: helper:1.3

# live configuration
    containers:
    - name: nginx
      image: nginx:1.16
    - name: nginx-helper-a
      image: helper:1.3
    - name: nginx-helper-b
      image: helper:1.3
      args: ["run"] # Field will be retained
    - name: nginx-helper-d # key: nginx-helper-d; will be retained
      image: helper:1.3

# result after merge
    containers:
    - name: nginx
      image: nginx:1.16
      # Element nginx-helper-a was deleted
    - name: nginx-helper-b
      image: helper:1.3
      args: ["run"] # Field was retained
    - name: nginx-helper-c # Element was added
      image: helper:1.3
    - name: nginx-helper-d # Element was ignored
      image: helper:1.3

설명:

  • "nginx-helper-a"라는 컨테이너는 구성 파일에 "nginx-helper-a" 컨테이너가 나타나지 않았으므로 삭제되었다.
  • "nginx-helper-b"라는 컨테이너는 라이브 구성의 args 변경을 유지했다. kubectl apply는 라이브 구성의 "nginx-helper-b"가 구성 파일의 "nginx-helper-b"와 같다는 것을, 필드 값이 다르더라도(구성 파일에 args가 없음) 식별할 수 있었다. patchMergeKey 필드 값(이름)이 둘 다 같았기 때문이다.
  • "nginx-helper-c"라는 컨테이너는 라이브 구성에 그 이름의 컨테이너가 없었지만 구성 파일에는 있었으므로 추가되었다.
  • "nginx-helper-d"라는 컨테이너는 last-applied-configuration에 그 이름의 요소가 없었으므로 유지되었다.

프리미티브 요소 리스트 병합

쿠버네티스 1.5부터 프리미티브 요소 리스트의 병합은 지원되지 않아요.

주어진 필드에 어떤 전략이 선택되는지는 types.gopatchStrategy 태그가 제어합니다. list 유형의 필드에 patchStrategy가 지정되지 않으면 리스트가 교체됩니다.

기본값 필드 값

API 서버는 오브젝트가 생성될 때 지정되지 않은 경우 라이브 구성의 특정 필드를 기본값으로 설정해요.

다음은 Deployment의 구성 파일입니다. 파일은 strategy를 지정하지 않습니다:

kubectl apply로 오브젝트를 만들어요:

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

kubectl get으로 라이브 구성을 출력해요:

kubectl get -f https://k8s.io/examples/application/simple_deployment.yaml -o yaml

출력은 API 서버가 라이브 구성의 여러 필드를 기본값으로 설정했음을 보여줍니다. 이 필드들은 구성 파일에 지정되지 않았어요.

apiVersion: apps/v1
kind: Deployment
# ...
spec:
  selector:
    matchLabels:
      app: nginx
  minReadySeconds: 5
  replicas: 1 # defaulted by apiserver
  strategy:
    rollingUpdate: # defaulted by apiserver - derived from strategy.type
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate # defaulted by apiserver
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.14.2
        imagePullPolicy: IfNotPresent # defaulted by apiserver
        name: nginx
        ports:
        - containerPort: 80
          protocol: TCP # defaulted by apiserver
        resources: {} # defaulted by apiserver
        terminationMessagePath: /dev/termination-log # defaulted by apiserver
      dnsPolicy: ClusterFirst # defaulted by apiserver
      restartPolicy: Always # defaulted by apiserver
      securityContext: {} # defaulted by apiserver
      terminationGracePeriodSeconds: 30 # defaulted by apiserver
# ...

패치 요청에서 기본값이 설정된 필드는 패치 요청의 일부로 명시적으로 지워지지 않는 한 다시 기본값이 설정되지 않아요. 이는 다른 필드의 값에 기반해 기본값이 설정되는 필드에 예상치 못한 동작을 일으킬 수 있습니다. 다른 필드가 나중에 변경되면, 그로부터 기본값이 설정된 값은 명시적으로 지우지 않는 한 업데이트되지 않을 거예요.

이런 이유로 서버가 기본값을 설정하는 특정 필드는 원하는 값이 서버 기본값과 일치하더라도 구성 파일에 명시적으로 정의하는 것이 권장됩니다. 그러면 서버가 다시 기본값을 설정하지 않을 충돌 값을 더 쉽게 알아볼 수 있습니다.

예시:

# last-applied-configuration
spec:
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

# configuration file
spec:
  strategy:
    type: Recreate # updated value
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

# live configuration
spec:
  strategy:
    type: RollingUpdate # defaulted value
    rollingUpdate: # defaulted value derived from type
      maxSurge : 1
      maxUnavailable: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

# result after merge - ERROR!
spec:
  strategy:
    type: Recreate # updated value: incompatible with rollingUpdate
    rollingUpdate: # defaulted value: incompatible with "type: Recreate"
      maxSurge : 1
      maxUnavailable: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

설명:

  1. 사용자가 strategy.type을 정의하지 않고 Deployment를 만든다.
  2. 서버가 strategy.typeRollingUpdate로 기본값을 설정하고 strategy.rollingUpdate 값을 기본값으로 설정한다.
  3. 사용자가 strategy.typeRecreate로 변경한다. strategy.rollingUpdate 값은 서버가 지워지길 기대하는데도 기본값으로 설정된 값을 유지한다. strategy.rollingUpdate 값이 처음에 구성 파일에 정의되었다면 삭제해야 한다는 것이 더 명확했을 것이다.
  4. strategy.rollingUpdate가 지워지지 않아 apply가 실패한다. strategy.rollingupdate 필드는 strategy.typeRecreate일 때 정의할 수 없다.

권장 사항: 다음 필드는 오브젝트 구성 파일에 명시적으로 정의해야 해요:

  • Deployment, StatefulSet, Job, DaemonSet, ReplicaSet, ReplicationController 같은 워크로드의 선택자와 PodTemplate 라벨
  • Deployment 롤아웃 전략

서버 기본값 필드나 다른 작성자가 설정한 필드 지우기

구성 파일에 나타나지 않는 필드는 값을 null로 설정한 뒤 구성 파일을 적용해 지울 수 있어요. 서버가 기본값을 설정하는 필드의 경우 이는 값을 다시 기본값으로 설정하는 것을 트리거합니다.

구성 파일과 직접 명령형 작성자 사이에서 필드 소유권 변경하기

개별 오브젝트 필드를 변경하는 데는 다음 방법만 사용해야 해요:

  • kubectl apply를 사용한다.
  • 구성 파일을 수정하지 않고 라이브 구성에 직접 쓴다. 예를 들어 kubectl scale을 사용한다.

소유자를 직접 명령형 작성자에서 구성 파일로 변경하기

필드를 구성 파일에 추가한다. 그 필드에 대해 kubectl apply를 거치지 않는 라이브 구성에 대한 직접 업데이트를 중단한다.

소유자를 구성 파일에서 직접 명령형 작성자로 변경하기

쿠버네티스 1.5부터 필드 소유권을 구성 파일에서 명령형 작성자로 변경하는 것은 수동 단계가 필요해요:

  • 구성 파일에서 필드를 제거한다.
  • 라이브 오브젝트의 kubectl.kubernetes.io/last-applied-configuration 어노테이션에서 필드를 제거한다.

관리 방법 변경하기

쿠버네티스 오브젝트는 한 번에 오직 한 가지 방법으로만 관리해야 해요. 한 방법에서 다른 방법으로 전환하는 것은 가능하지만 수동 프로세스입니다.

선언형 관리와 함께 명령형 삭제를 사용하는 것은 괜찮아요.

명령형 명령 관리에서 선언형 오브젝트 구성으로 마이그레이션

명령형 명령 관리에서 선언형 오브젝트 구성으로 마이그레이션하는 것에는 여러 수동 단계가 포함됩니다:

  1. 라이브 오브젝트를 로컬 구성 파일로 내보낸다:

    kubectl get <kind>/<name> -o yaml > <kind>_<name>.yaml
    
  2. 구성 파일에서 status 필드를 수동으로 제거한다.

    이 단계는 선택 사항이다. kubectl apply는 구성 파일에 있어도 status 필드를 업데이트하지 않기 때문이다.

  3. 오브젝트에 kubectl.kubernetes.io/last-applied-configuration 어노테이션을 설정한다:

    kubectl replace --save-config -f <kind>_<name>.yaml
    
  4. 프로세스를 변경해 오브젝트 관리에 kubectl apply만 사용한다.

명령형 오브젝트 구성에서 선언형 오브젝트 구성으로 마이그레이션

  1. 오브젝트에 kubectl.kubernetes.io/last-applied-configuration 어노테이션을 설정한다:

    kubectl replace --save-config -f <kind>_<name>.yaml
    
  2. 프로세스를 변경해 오브젝트 관리에 kubectl apply만 사용한다.

컨트롤러 선택자와 PodTemplate 라벨 정의하기

컨트롤러의 선택자를 업데이트하는 것은 강력히 권장되지 않아요.

권장되는 접근 방식은 컨트롤러 선택자만이 사용하며 다른 의미론적 의미가 없는 단일 불변 PodTemplate 라벨을 정의하는 것입니다.

예시:

selector:
  matchLabels:
      controller-selector: "apps/v1/deployment/nginx"
template:
  metadata:
    labels:
      controller-selector: "apps/v1/deployment/nginx"

더 알아보기 (Learn more)