ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Kubernetes] 구성 - 파드, 컨테이너 구성 및 리소스 관리
    공부/데이터 2025. 2. 3. 23:52

    파드 label

    파드 label은 쿠버네티스에서 파드 및 다른 리소스를 조직하고 관리하는 데 사용되는 핵심 개념입니다. label은 키-값 쌍으로 리소스에 연결되며, label selector를 사용하여 다른 워크로드 리소스에서 분류하고 선택하는 데 활용됩니다.

    레이블 없는 상태의 파드
    app, rel(릴리즈 명) 으로 파드에 레이블링

     

    apiVersion: v1
    kind: Pod
    metadata:
      name: my-app-pod
      labels:
        app: my-app
        env: production
        version: 1.0
        tier: frontend
    spec:
      containers:
      - name: my-app-container
        image: nginx:latest
        ports:
        - containerPort: 80
    • metadata.labels 섹션에 키-값 쌍 형태로 레이블을 정의합니다.
    • 위 예시에서는 app, env, version, tier 4개의 레이블을 사용했습니다.
    • 각 레이블은 고유한 키와 값을 가지며, 이를 통해 파드를 분류하고 선택할 수 있습니다.

    명령어

    • kubectl label pods <pod-name> <key>=<value>: 파드에 레이블 추가
    • kubectl get pods --show-labels: 파드 목록과 함께 레이블 표시
    • kubectl label pods <pod-name> <key>-: 파드에서 레이블 제거

    label 활용 예시

    kubectl get pods -l "app=my-app, env=production"

    또는

    apiVersion: v1
    kind: Service
    metadata:
      name: my-app-service
    spec:
      selector:
        app: my-app
        tier: frontend
      ports:
      - protocol: TCP
        port: 80
        targetPort: 80

    Init 컨테이너

    Init 컨테이너는 파드 내의 애플리케이션 컨테이너가 실행되기 전에 실행되는 특수한 컨테이너입니다. Init 컨테이너는 애플리케이션 이미지에 없는 유틸리티나 설정 스크립트를 포함할 수 있습니다.

    Init 컨테이너는 파드 스펙의 containers 배열(애플리케이션 컨테이너를 설명하는 부분)과 함께 지정할 수 있습니다.

    이해

    파드는 내부에 여러 개의 컨테이너를 두어 앱을 실행할 수 있지만 앱 컨테이너가 시작되기 전에 실행되는 하나 이상의 init 컨테이너를 가질 수도 있습니다.

    Init 컨테이너는 다음과 같은 점을 제외하고는 일반 컨테이너와 완전히 동일합니다.

    • Init 컨테이너는 항상 완료될 때까지 실행됩니다.
    • 각 init 컨테이너는 다음 컨테이너가 시작되기 전에 성공적으로 완료되어야 합니다.
    • 파드의 init 컨테이너가 실패하면 kubelet은 성공할 때까지 해당 init 컨테이너를 반복해서 다시 시작합니다. 그러나 파드의 restartPolicy가 Never이고 해당 파드의 시작 중에 init 컨테이너가 실패하면 Kubernetes는 전체 파드를 실패한 것으로 간주합니다.
    • 파드에 init 컨테이너를 지정하려면 파드 스펙에 initContainers 필드를 container 항목의 배열로 추가합니다 (앱 containers 필드와 내용과 유사).
    • init 컨테이너의 상태는 status.initContainerStatuses 필드에 컨테이너 상태 배열로 반환됩니다 (status.containerStatuses 필드와 유사).

    일반 컨테이너와의 차이점

    Init 컨테이너는 리소스 제한, 볼륨, 보안 설정 등 앱 컨테이너의 모든 필드와 기능을 지원합니다. 그러나 Init 컨테이너의 리소스 요청 및 제한은 아래에서 얘기할 컨테이너 내의 리소스 공유에 설명된 대로 다르게 처리됩니다.

    일반적인 Init 컨테이너(즉, 사이드카 컨테이너 제외)는 lifecycle, livenessProbe, readinessProbe 또는 startupProbe 필드를 지원하지 않습니다. Init 컨테이너는 파드가 준비되기 전에 완료될 때까지 실행되어야 합니다. 반면, 사이드카 컨테이너는 파드의 수명 동안 계속 실행되며 일부 프로브를 지원합니다.

    파드에 여러 개의 Init 컨테이너를 지정하면 kubelet은 각 Init 컨테이너를 순차적으로 실행합니다. 각 Init 컨테이너는 다음 컨테이너가 실행되기 전에 성공해야 합니다. 모든 Init 컨테이너가 완료되면 kubelet은 파드의 애플리케이션 컨테이너를 초기화하고 정상적으로 실행합니다.

    사이드카 컨테이너와의 차이점

    Init 컨테이너는 주 애플리케이션 컨테이너가 시작되기 전에 실행되고 작업을 완료합니다. 사이드카 컨테이너와 달리 Init 컨테이너는 주 컨테이너와 함께 계속 실행되지 않습니다.

    Init 컨테이너는 순차적으로 실행되며 완료될 때까지 실행되고, 모든 Init 컨테이너가 성공적으로 완료될 때까지 주 컨테이너는 시작되지 않습니다.

    Init 컨테이너는 lifecycle, livenessProbe, readinessProbe 또는 startupProbe를 지원하지 않는 반면, 사이드카 컨테이너는 수명 주기를 제어하기 위해 이러한 모든 프로브를 지원합니다.

    Init 컨테이너는 주 애플리케이션 컨테이너와 동일한 리소스(CPU, 메모리, 네트워크)를 공유하지만, 직접 상호 작용하지는 않습니다. 그러나 데이터 교환을 위해 공유 볼륨을 사용할 수 있습니다.

    사용

    Init 컨테이너는 앱 컨테이너와 별도의 이미지를 사용하기 때문에 시작 관련 코드에 다음과 같은 몇 가지 장점이 있습니다.

    • 유틸리티 및 맞춤형 코드 포함: Init 컨테이너는 앱 이미지에 없는 설정에 필요한 유틸리티나 맞춤형 코드를 포함할 수 있습니다. 예를 들어, 설정 중에 sed, awk, python 또는 dig와 같은 도구를 사용하기 위해 다른 이미지에서 이미지를 FROM할 필요가 없습니다.
    • 독립적인 이미지 빌드 및 배포: 애플리케이션 이미지 빌더와 배포자 역할은 단일 앱 이미지를 공동으로 빌드할 필요 없이 독립적으로 작업할 수 있습니다.
    • 파일 시스템 접근 권한 분리: Init 컨테이너는 동일한 파드의 앱 컨테이너와 다른 파일 시스템 뷰로 실행될 수 있습니다. 결과적으로 앱 컨테이너가 접근할 수 없는 Secrets에 접근 권한을 부여할 수 있습니다.
    • 시작 전 조건 충족: Init 컨테이너는 모든 앱 컨테이너가 시작되기 전에 완료될 때까지 실행되므로, 일련의 사전 조건이 충족될 때까지 앱 컨테이너 시작을 차단하거나 지연시키는 메커니즘을 제공합니다. 사전 조건이 충족되면 Pod의 모든 앱 컨테이너가 병렬로 시작될 수 있습니다.
    • 보안 강화: Init 컨테이너는 앱 컨테이너 이미지를 덜 안전하게 만들 수 있는 유틸리티나 맞춤형 코드를 안전하게 실행할 수 있습니다. 불필요한 도구를 분리함으로써 앱 컨테이너 이미지의 공격 표면을 제한할 수 있습니다.

    예시

    • 다음과 같은 쉘 명령을 사용하여 Service가 생성될 때까지 기다립니다
    for i in {1..100}; do sleep 1; if nslookup myservice; then exit 0; fi; done; exit 1
    • 이 파드를 다음과 같은 명령어를 사용하여 Downward API로부터 원격 서버에 등록합니다.
    curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'
    • 다음과 같은 명령어를 사용하여 앱 컨테이너를 시작하기 전에 잠시 기다립니다.
    sleep 60
    • Git 저장소를 볼륨에 복제합니다.
    • 값을 구성 파일에 넣고 템플릿 도구를 실행하여 주 앱 컨테이너에 대한 구성 파일을 동적으로 생성합니다. 예를 들어, POD_IP 값을 구성 파일에 넣고 Jinja를 사용하여 주 앱 구성 파일을 생성합니다.

    사용 중인 init 컨테이너

    이 예제는 두 개의 init 컨테이너를 가진 간단한 파드를 정의합니다. 첫 번째는 myservice를 기다리고 두 번째는 mydb를 기다립니다. 두 init 컨테이너가 모두 완료되면 파드는 spec 섹션의 앱 컨테이너를 실행합니다.

    apiVersion: v1
    kind: Pod
    metadata:
      name: myapp-pod
      labels:
        app.kubernetes.io/name: MyApp
    spec:
      containers:
      - name: myapp-container
        image: busybox:1.28
        command: ['sh', '-c', 'echo The app is running! && sleep 3600']
      initContainers:
      - name: init-myservice
        image: busybox:1.28
        command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]
      - name: init-mydb
        image: busybox:1.28
        command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for mydb; sleep 2; done"]

    해당 파드를 배포하고 아래 명령어를 통해 상태를 확인할 수 있습니다.

    $ kubectl get -f myapp.yaml
    NAME        READY     STATUS     RESTARTS   AGE
    myapp-pod   0/1       Init:0/2   0          6m
    
    $ kubectl describe -f myapp.yaml
    Name:          myapp-pod
    Namespace:     default
    [...]
    Labels:        app.kubernetes.io/name=MyApp
    Status:        Pending
    [...]
    Init Containers:
      init-myservice:
    [...]
        State:         Running
    [...]
      init-mydb:
    [...]
        State:         Waiting
          Reason:      PodInitializing
        Ready:         False
    [...]
    Containers:
      myapp-container:
    [...]
        State:         Waiting
          Reason:      PodInitializing
        Ready:         False
    [...]
    Events:
      FirstSeen    LastSeen    Count    From                      SubObjectPath                           Type          Reason        Message
      ---------    --------    -----    ----                      -------------                           --------      ------        -------
      16s          16s         1        {default-scheduler }                                              Normal        Scheduled     Successfully assigned myapp-pod to 172.17.4.201
      16s          16s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Pulling       pulling image "busybox"
      13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Pulled        Successfully pulled image "busybox"
      13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Created       Created container init-myservice
      13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Started       Started container init-myservice

    더 자세하게 파드의 컨테이너의 로그를 확인하려면 다음 명령어를 입력합니다.

    $ kubectl logs myapp-pod -c init-myservice # Inspect the first init container
    $ kubectl logs myapp-pod -c init-mydb      # Inspect the second init container

    이 시점에서 해당 init 컨테이너들은 mydbmyservice라는 이름의 서비스를 찾기 위해 대기합니다.

    다음은 이러한 서비스를 나타나게 하기 위해 사용할 수 있는 구성입니다.

    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: myservice
    spec:
      ports:
      - protocol: TCP
        port: 80
        targetPort: 9376
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: mydb
    spec:
      ports:
      - protocol: TCP
        port: 80
        targetPort: 9377

    위의 서비스를 배포하면 해당 init 컨테이너들이 완료되고 myapp-pod Pod가 실행 중(Running) 상태로 이동하는 것을 볼 수 있습니다.

    자세한 동작

    파드 시작 시, kubelet은 네트워킹과 스토리지가 준비될 때까지 init 컨테이너 실행을 지연시킵니다. 그런 다음 kubelet은 파드 스펙에 나타나는 순서대로 파드의 init 컨테이너를 실행합니다.

    각 init 컨테이너는 다음 컨테이너가 시작되기 전에 성공적으로 종료되어야 합니다. 런타임으로 인해 컨테이너가 시작에 실패하거나 실패 상태로 종료되면 파드의 restartPolicy에 따라 다시 시도됩니다. 그러나 파드의 restartPolicy가 Always로 설정된 경우 init 컨테이너는 restartPolicy OnFailure를 사용합니다.

    모든 init 컨테이너가 성공할 때까지 파드는 Ready 상태가 될 수 없습니다. init 컨테이너의 포트는 서비스에 집계되지 않습니다. 초기화 중인 파드는 Pending 상태이지만 Initialized 조건은 false로 설정되어야 합니다.

    • Pending 상태에는 컨테이너 이미지 다운로드, 네트워크 설정, 볼륨 마운트, Init 컨테이너 실행 등이 포함
    • Initialized 조건은 파드의 초기화 과정이 완료되었는지 여부임
    • Initialized 조건은 Init 컨테이너 실행이 시작되기 전에는 false로 설정되고 모든 Init 컨테이너가 성공적으로 완료된 후에 true로 변경됨
      • 쿠버네티스 시스템이 자동으로 관리
    • 파드가 Pending 상태로 계속 남아있고 Initialized 조건이 false인 경우 Init 컨테이너에 문제가 발생했을 가능성이 높음

    파드가 다시 시작되거나 재시작되면 모든 init 컨테이너가 다시 실행되어야 합니다.

    init 컨테이너 스펙의 변경은 컨테이너 이미지 필드로 제한됩니다. init 컨테이너의 image 필드를 직접 변경해도 파드가 다시 시작되거나 재생성되지 않습니다. 파드가 아직 시작되지 않은 경우 해당 변경 사항은 파드 부팅 방식에 영향을 줄 수 있습니다.

    파드 템플릿의 경우 일반적으로 init 컨테이너의 모든 필드를 변경할 수 있습니다. 해당 변경의 영향은 파드 템플릿이 사용되는 위치에 따라 달라집니다.

    Init 컨테이너는 재시작, 재시도 또는 재실행될 수 있으므로 init 컨테이너 코드는 멱등성을 가져야 합니다. 특히 emptyDir 볼륨에 쓰는 코드는 출력 파일이 이미 존재할 가능성에 대비해야 합니다.

    Init 컨테이너는 앱 컨테이너의 모든 필드를 갖습니다. 그러나 Kubernetes는 init 컨테이너가 완료와 별도로 준비 상태를 정의할 수 없기 때문에 readinessProbe 사용을 금지합니다. 이는 유효성 검사 중에 적용됩니다.

    파드의 activeDeadlineSeconds를 사용하여 init 컨테이너가 영원히 실패하는 것을 방지합니다. 활성 기한에는 init 컨테이너가 포함됩니다. 그러나 activeDeadlineSeconds는 initContainer가 완료된 후에도 영향을 미치기 때문에 팀이 애플리케이션을 Job으로 배포하는 경우에만 사용하는 것이 좋습니다. 올바르게 실행 중인 파드는 설정한 activeDeadlineSeconds에 의해 종료될 수 있습니다.

    파드의 각 앱 및 init 컨테이너의 이름은 고유해야 합니다. 다른 컨테이너와 이름을 공유하는 컨테이너에 대해서는 유효성 검사 오류가 발생합니다.

    컨테이너 내 리소스 공유

    Init, 사이드카, 앱 컨테이너의 실행 순서를 고려할 때, 리소스 사용에 다음과 같은 규칙이 적용됩니다.

    1. 모든 Init 컨테이너에 정의된 특정 리소스 request 또는 limit 중 가장 높은 값이 초기화의 request/limit이 됩니다. 만약 어떤 리소스에 대한 limit이 지정되지 않았다면 이는 가장 높은 limit으로 간주됩니다.
    2. 파드의 request/limit은 다음 중 더 높은 값입니다.
      • 해당 리소스에 대한 모든 앱 컨테이너 request/limit의 합
      • 해당 리소스에 대한 초기화 request/limit
    3. 스케줄링은 request/limit을 기반으로 수행됩니다. 즉, Init 컨테이너는 파드 수명 동안 사용되지 않는 초기화를 위한 리소스를 예약할 수 있습니다.
    4. 파드의 QoS 티어는 Init 컨테이너와 앱 컨테이너 모두에 대한 QoS 티어입니다.
    5. 할당량 및 제한은 Pod 요청 및 제한을 기준으로 적용됩니다.

    멀티 컨테이너 패턴(사이드카, 엠버서더, 어댑터)

    멀티 컨테이너 패턴은 하나의 파드 안에 여러 개의 컨테이너를 함께 실행하는 방식을 의미합니다. 각 컨테이너는 특정 역할을 담당하며 서로 협력하여 애플리케이션의 기능을 완성합니다. 컨테이너는 서로 독립적으로 배포, 업데이트 및 확장될 수 있어 유연성을 제공합니다.

    종류

    사이드카 패턴 (Sidecar Pattern)

    메인 컨테이너의 기능을 확장하거나 향상시키는 컨테이너를 추가하는 패턴입니다. 메인 컨테이너와 함께 파드 내에서 실행되며, 밀접하게 상호작용합니다.

    예시

    • 로그 수집기
      • 메인 컨테이너의 로그를 수집하여 중앙 로그 시스템으로 전송
    • 웹 페이지 생성
      • 메인 컨테이너의 데이터를 기반으로 웹 페이지를 생성하여 제공
    • 보안 에이전트
      • 메인 컨테이너의 보안을 강화하는 역할을 수행
    • Composer의 gcsfuse
      • Composer에서 Google Cloud Storage (GCS)를 파일 시스템처럼 마운트하여 사용할 수 있도록 하는 역할

    엠버서더 패턴 (Ambassador Pattern)

    메인 컨테이너의 네트워크 통신을 대리하는 컨테이너를 추가하는 패턴입니다. 메인 컨테이너는 엠버서더 컨테이너를 통해 외부 서비스와 통신하며, 엠버서더 컨테이너는 네트워크 관련 복잡한 작업을 처리합니다.

    예시

    • 프록시
      • 메인 컨테이너의 트래픽을 관리하고 보안 기능을 제공
    • 서비스 디스커버리
      • 외부 서비스의 위치 정보를 관리하고 메인 컨테이너에 제공

    어댑터 패턴 (Adapter Pattern)

    메인 컨테이너의 인터페이스를 다른 시스템에서 사용할 수 있도록 변환하는 컨테이너를 추가하는 패턴입니다. 메인 컨테이너는 어댑터 컨테이너를 통해 다양한 형식의 데이터를 주고받을 수 있습니다.

    예시

    • 데이터 형식 변환
      • 메인 컨테이너의 데이터를 다른 시스템에서 사용하는 형식으로 변환
    • 프로토콜 변환
      • 메인 컨테이너가 사용하는 프로토콜을 다른 시스템에서 사용하는 프로토콜로 변환

    멀티 컨테이너 패턴의 장점

    • 모듈화: 애플리케이션을 작은 단위의 컨테이너로 분리하여 개발 및 유지보수 효율성을 높입니다.
    • 재사용성: 컨테이너를 재사용하여 개발 생산성을 향상시킵니다.
    • 확장성: 각 컨테이너를 독립적으로 확장하여 애플리케이션의 성능을 향상시킵니다.
    • 유연성: 다양한 기능을 가진 컨테이너를 조합하여 애플리케이션을 구성할 수 있습니다.

    파드 어피니티(Affinity)와 파드 안티 어피니티(Anti-affinity)

    파드 어피니티와 안티 어피니티는 쿠버네티스에서 파드의 배치 전략을 세밀하게 조정하는 데 사용되는 강력한 메커니즘입니다. 파드들을 특정 노드에 모아두거나 분산시켜 배치함으로써 애플리케이션의 성능, 안정성, 가용성을 향상시키는 데 기여합니다.

    파드 어피니티

    파드 어피니티는 특정 파드와 동일한 노드에 파드를 배치하도록 Kubernetes에 지시하는 규칙입니다. 이는 파드 간의 의존성을 고려하여 파드의 배치 전략을 최적화하는 데 유용합니다.

    예시

    데이터베이스 서버와 애플리케이션 서버는 밀접하게 상호작용하므로 동일한 노드에 배치하는 것이 좋습니다. 이를 통해 네트워크 지연 시간을 최소화하고 성능을 향상시킬 수 있습니다. 또 다른 예로 캐시 서버와 애플리케이션 서버 역시 동일한 노드에 배치하여 캐시 접근 속도를 높일 수 있습니다.

    파드 안티 어피니티

    파드 안티 어피니티는 특정 파드와 다른 노드에 파드를 배치하도록 Kubernetes에 지시하는 규칙입니다. 이는 파드의 고가용성을 확보하고 장애 발생 시 영향을 최소화하는 데 유용합니다.

    예시

    동일한 기능을 수행하는 여러 개의 파드를 서로 다른 노드에 분산시켜 배치함으로써 특정 노드에 장애가 발생해도 애플리케이션의 가용성을 유지할 수 있습니다. 스테이트풀셋은 각 파드에 고유한 ID를 부여하고 안정적인 스토리지를 제공하는 데 사용됩니다. 스테이트풀셋 파드를 안티 어피니티를 사용하여 분산 배치함으로써 데이터의 안정성을 높일 수 있습니다.

    선호 규칙

    어피니티를 사용할 때, 무조건 적용하는 것과 weight에 따라 적용할 수도 있는 옵션이 있습니다.

    requiredDuringSchedulingIgnoredDuringExecution

    이 설정을 사용하면 파드가 특정 조건을 만족하는 파드가 있는 노드에만 배치되도록 강제합니다. 만약 조건을 만족하는 노드가 없다면 파드는 Pending 상태로 남습니다. 예를 들면, 데이터베이스(DB) 파드와 애플리케이션(APP) 파드를 무조건 동일한 노드에 배치해야 하는 경우에 사용할 수 있습니다.

    affinity:
      podAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
                - key: app
                  operator: In
                  values:
                    - my-db # my-db 레이블을 가진 파드와 동일 노드에 배치
            topologyKey: kubernetes.io/hostname # 노드 단위로 어피니티 적용

    위 YAML 파일은 app=my-db 레이블을 가진 파드가 실행 중인 노드에만 현재 파드를 배치하도록 지시합니다. 만약 app=my-db 레이블을 가진 파드가 실행 중인 노드가 없다면 현재 파드는 Pending 상태로 남습니다.

    preferredDuringSchedulingIgnoredDuringExecution

    이 설정을 사용하면 파드가 특정 조건을 만족하는 파드가 있는 노드에 배치되는 것을 선호하지만 필수는 아닙니다. 조건을 만족하는 노드가 없다면 파드는 다른 노드에 배치될 수 있습니다. 예를 들면, 애플리케이션(APP) 파드를 캐시 서버(Cache) 파드와 가능한 한 동일한 노드에 배치하고 싶지만 필수는 아닐 때 사용합니다.

    affinity:
      podAffinity:
        preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 50 # 가중치 50 (0~100)
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                  - key: cache
                    operator: In
                    values:
                      - my-cache # my-cache 레이블을 가진 파드와 동일 노드에 배치 선호
              topologyKey: kubernetes.io/hostname # 노드 단위로 어피니티 적용

    위 YAML 파일은 cache=my-cache 레이블을 가진 파드가 실행 중인 노드에 현재 파드를 배치하는 것을 선호합니다. 가중치(weight)는 선호도를 나타내며, 0~100 사이의 값을 가질 수 있습니다. 값이 높을수록 선호도가 높아집니다. 만약 cache=my-cache 레이블을 가진 파드가 실행 중인 노드가 없더라도 현재 파드는 다른 노드에 배치될 수 있습니다.

    파드 어피니티와 안티 어피니티 활용 시 고려사항

    • 레이블(Label) 활용: 파드 어피니티와 안티 어피니티는 파드의 레이블을 기반으로 동작합니다. 따라서 파드에 적절한 레이블을 부여하는 것이 중요합니다.
    • 토폴로지 키(Topology Key): 토폴로지 키는 파드가 배치될 노드의 범위를 지정하는 데 사용됩니다. 예를 들어, topology.kubernetes.io/zone을 사용하면 파드를 동일한 Zone에 배치하거나 다른 Zone에 배치하도록 설정할 수 있습니다.
    • 가중치(Weight): 선호 어피니티 또는 안티 어피니티의 경우 가중치를 사용하여 선호도를 조절할 수 있습니다.

    사용 예시

    DB와 APP 동일 노드 배치 (파드 어피니티)

    apiVersion: v1
    kind: Pod
    metadata:
      name: my-app
    spec:
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: app
                    operator: In
                    values:
                      - my-db # my-db 레이블을 가진 파드와 동일 노드에 배치
              topologyKey: kubernetes.io/hostname # 노드 단위로 어피니티 적용
      containers:
        - name: my-app-container
          image: my-app-image

    동일 기능 파드 분산 배치 (파드 안티 어피니티)

    apiVersion: v1
    kind: Deployment
    metadata:
      name: my-app-deployment
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: my-app
      template:
        metadata:
          labels:
            app: my-app
        spec:
          affinity:
            podAntiAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
                - labelSelector:
                    matchExpressions:
                      - key: app
                        operator: In
                        values:
                          - my-app # my-app 레이블을 가진 파드와 다른 노드에 배치
                  topologyKey: kubernetes.io/hostname # 노드 단위로 안티 어피니티 적용
          containers:
            - name: my-app-container
              image: my-app-image

    weight을 활용한 파드 어피니티/안티 어피니티 활용

    apiVersion: v1
    kind: Pod
    metadata:
      name: my-app
    spec:
      affinity:
        podAffinity:
          preferredDuringSchedulingIgnoredDuringExecution: # 선호 어피니티 (가중치 50)
            - weight: 50
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - key: app
                      operator: In
                      values:
                        - my-db
                topologyKey: kubernetes.io/hostname
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution: # 선호 안티 어피니티 (가중치 100)
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - key: app
                      operator: In
                      values:
                        - my-app
                topologyKey: kubernetes.io/hostname
      containers:
        - name: my-app-container
          image: my-app-image
    • labelSelector: 파드를 선택하는 조건을 정의합니다.
    • matchExpressions: 레이블 선택 조건을 구체적으로 지정합니다.
    • key: 레이블의 키를 나타냅니다.
    • operator: 레이블 선택 연산자 (In, NotIn, Exists, DoesNotExist 등)를 나타냅니다.
    • values: 레이블 값의 목록을 나타냅니다.
    • topologyKey: 어피니티/안티 어피니티를 적용할 토폴로지 범위를 지정합니다. (kubernetes.io/hostname: 노드 단위, topology.kubernetes.io/zone: Zone 단위 등)
    • weight: 선호 어피니티/안티 어피니티의 가중치를 나타냅니다. (값이 높을수록 선호도가 높아짐)

    토폴로지 분배 제약 조건(Topology Spread Constraints)

    토폴로지 분배 제약 조건(Topology Spread Constraints)은 쿠버네티스에서 파드의 배치를 제어하는 강력한 메커니즘입니다. 파드들을 노드, 영역(Zone), 지역(Region)과 같은 다양한 토폴로지 도메인에 균등하게 분산시켜 배치함으로써 특정 노드나 영역에 장애가 발생해도 애플리케이션의 가용성을 유지할 수 있습니다. 또한 파드를 여러 토폴로지 도메인에 균등하게 분산시켜 배치함으로써 트래픽을 분산시키고 로드 밸런싱 효과를 얻을 수 있습니다. 마지막으로 파드를 특정 토폴로지 도메인에 집중시키지 않고 분산시켜 배치함으로써 클러스터 전체의 자원을 효율적으로 활용할 수 있습니다.

    사용 방법

    토폴로지 분배 제약 조건은 파드 스펙(Pod Specification)의 topologySpreadConstraints 필드에 정의합니다. topologySpreadConstraints는 다음과 같은 속성들을 가집니다.

    • maxSkew: 토폴로지 도메인 간의 최대 파드 수 차이를 나타냅니다. 예를 들어, maxSkew: 1은 어떤 토폴로지 도메인에서도 파드 수 차이가 1을 넘지 않도록 제한합니다.
    • topologyKey: 토폴로지 도메인을 구분하는 데 사용되는 레이블 키를 지정합니다. 예를 들어, topology.kubernetes.io/zone은 영역 단위로 파드를 분산시키고, kubernetes.io/hostname은 노드 단위로 파드를 분산시킵니다.
      • kubernetes.io/hostname: 노드 단위로 파드를 분산
      • topology.kubernetes.io/zone: 영역(Zone) 단위로 파드를 분산
      • topology.kubernetes.io/region: 지역(Region) 단위로 파드를 분산
      • 사용자 정의 label 키: 사용자가 정의한 label 키를 기준으로 파드를 분산할 수 있음
        • 예를 들어, 특정 하드웨어 속성을 가진 노드들을 그룹으로 묶어 해당 그룹 내에서 파드를 분산시키도록 설정할 수 있음
    • whenUnsatisfied: 제약 조건을 만족하지 못할 경우 파드를 어떻게 처리할지 결정합니다.
      • DoNotSchedule
        • 제약 조건을 만족하지 못할 경우 파드 배치를 보류합니다.
        • 파드는 Pending 상태로 남으며, 조건을 만족하는 노드가 나타날 때까지 대기합니다.
        • 고가용성을 최우선으로 고려하는 경우에 유용합니다.
      • ScheduleAnyway
        • 제약 조건을 만족하지 못하더라도 파드를 배치합니다.
        • 파드는 가능한 한 균등하게 분산되도록 배치되지만, 제약 조건을 완전히 만족하지 못할 수 있습니다.
        • 리소스 활용 효율성을 우선시하는 경우에 유용합니다.
    • DoNotSchedule은 파드 배치를 보류하고, ScheduleAnyway는 제약 조건을 무시하고 파드를 배치합니다.
    • labelSelector: 제약 조건을 적용할 파드를 선택하는 데 사용되는 레이블 셀렉터입니다.

    예시

    노드 단위 분산

    apiVersion: apps/v1
    kind: Deployment
    spec:
      replicas: 6
      template:
        spec:
          topologySpreadConstraints:
            - maxSkew: 1
              topologyKey: kubernetes.io/hostname
              whenUnsatisfied: DoNotSchedule
              labelSelector:
                matchLabels:
                  app: my-app
          containers:
            - name: my-app-container
              image: my-app-image

    위 예시는 app: my-app 레이블을 가진 파드를 노드 단위로 분산시켜 배치하며, 어떤 노드에서도 파드 수 차이가 1을 넘지 않도록 제한합니다. 만약 제약 조건을 만족하는 노드가 없다면 파드 배치를 보류합니다.

    region 단위 분산

    apiVersion: apps/v1
    kind: Deployment
    spec:
      replicas: 9
      template:
        spec:
          topologySpreadConstraints:
            - maxSkew: 2
              topologyKey: topology.kubernetes.io/zone
              whenUnsatisfied: ScheduleAnyway
              labelSelector:
                matchLabels:
                  app: my-app
          containers:
            - name: my-app-container
              image: my-app-image

    위 예시는 app: my-app 레이블을 가진 파드를 영역 단위로 분산시켜 배치하며, 어떤 영역에서도 파드 수 차이가 2를 넘지 않도록 제한합니다. 만약 제약 조건을 만족하는 영역이 없다면 제약 조건을 무시하고 파드를 배치합니다.

    레퍼런스

    https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/

    https://kubernetes.io/docs/concepts/workloads/pods/init-containers/

    댓글