7.5.2 데몬셋 동작 방식

데몬셋(DaemonSet)이 클러스터의 각 노드에 파드를 하나씩 실행시켜주는 편리한 도구라는 것은 이제 잘 알겠습니다. 그렇다면 데몬셋은 구체적으로 어떤 원리로 이러한 마법 같은 일을 수행하는 걸까요? 단순히 “모든 노드에 배포해줘!” 라고 외치기만 하면 되는 걸까요? 물론 쿠버네티스는 사용자가 선언한 상태를 달성하기 위해 많은 부분을 자동화해주지만, 그 이면에는 정교한 메커니즘이 숨어 있습니다.

데몬셋 컨트롤러는 쿠버네티스 API 서버를 지속적으로 감시하면서, 데몬셋 명세에 정의된 조건과 클러스터의 현재 노드 상태를 비교합니다. 그리고 이 둘 사이에 불일치가 발생하면, 즉 어떤 노드에 데몬셋 파드가 실행되어야 하는데 없거나, 실행되어서는 안 되는데 있는 경우, 이를 해결하기 위한 조치를 취합니다. 마치 꼼꼼한 관리인이 각 방마다 필요한 물품이 제대로 비치되어 있는지 끊임없이 확인하고 조율하는 것과 같습니다.

이제 데몬셋이 어떻게 특정 노드를 선택하고, 노드의 변화에 따라 파드를 자동으로 관리하는지 그 핵심 동작 방식을 자세히 살펴보겠습니다.

7.5.2.1 노드 셀렉터 또는 어피니티/톨러레이션 활용

기본적으로 데몬셋은 클러스터 내의 모든 사용 가능한 노드에 파드를 하나씩 배포하려고 시도합니다. 하지만 실제 운영 환경에서는 모든 노드에 동일한 데몬셋 파드를 실행하는 것이 아니라, 특정 조건을 만족하는 노드에만 데몬셋 파드를 실행시키고 싶을 때가 많습니다. 예를 들어, GPU가 장착된 노드에만 GPU 드라이버 관련 데몬을 실행하거나, 특정 스토리지 타입에 연결된 노드에만 해당 스토리지 에이전트를 실행하는 경우가 있을 수 있습니다.

이러한 요구사항을 충족시키기 위해 데몬셋은 쿠버네티스의 강력한 노드 선택 메커니즘을 활용합니다. 이 메커니즘들은 데몬셋 파드가 어떤 노드에 스케줄링될 수 있는지, 또는 스케줄링되어서는 안 되는지를 결정하는 규칙을 정의합니다. 주요하게 사용되는 방법들은 다음과 같습니다.

  • 노드 셀렉터 (Node Selector):가장 간단하면서도 직접적인 노드 선택 방법입니다. 노드에는 ‘키-값’ 쌍으로 이루어진 레이블(label)을 붙일 수 있습니다 (예: disktype: ssd, gpu: nvidia-tesla-v100). 데몬셋의 파드 템플릿 명세 (spec.template.spec) 안에 nodeSelector 필드를 정의하고, 여기에 원하는 ‘키-값’ 레이블을 지정하면, 해당 레이블을 가진 노드에만 데몬셋 파드가 스케줄링됩니다.
    클립보드에 복사
    만약 어떤 노드도 지정된 nodeSelector와 일치하는 레이블을 가지고 있지 않다면, 해당 데몬셋의 파드는 어떤 노드에도 배포되지 않습니다.
  • 노드 어피니티 (Node Affinity) / 안티-어피니티 (Node Anti-Affinity):노드 셀렉터보다 훨씬 더 유연하고 표현력이 풍부한 노드 선택 방식입니다. nodeSelector가 단순히 ‘이 레이블을 가진 노드에만’이라는 조건만 표현할 수 있다면, 노드 어피니티는 “이러이러한 조건을 선호한다(preferred)” 또는 “반드시 만족해야 한다(required)” 와 같이 더 복잡한 규칙을 정의할 수 있습니다. 또한, In, NotIn, Exists, DoesNotExist, Gt (보다 큼), Lt (보다 작음)와 같은 다양한 연산자를 사용할 수 있습니다.
    • requiredDuringSchedulingIgnoredDuringExecution: 스케줄링 시에는 반드시 조건을 만족해야 파드가 해당 노드에 배포될 수 있습니다. 하지만 일단 배포된 후에는 노드의 레이블이 변경되어 조건에 맞지 않게 되더라도 파드가 계속 실행됩니다 (쫓겨나지 않음).
    • preferredDuringSchedulingIgnoredDuringExecution: 스케줄링 시에 조건을 만족하는 노드를 ‘선호’하지만, 만족하는 노드가 없더라도 다른 노드에 배포될 수 있습니다. 가중치(weight)를 부여하여 선호도를 조절할 수 있습니다.

    노드 안티-어피니티는 반대로 특정 조건을 가진 노드를 피하도록 설정하는 것입니다. 데몬셋에서는 주로 requiredDuringSchedulingIgnoredDuringExecution 어피니티가 많이 사용되어 특정 노드 풀에만 데몬셋 파드를 정확히 위치시키는 데 활용됩니다.

  • 테인트(Taint)와 톨러레이션(Toleration):어피니티가 파드가 특정 노드를 ‘좋아하게’ 만드는 것이라면, 테인트와 톨러레이션은 반대 개념으로 작동합니다. 노드에 테인트(Taint)를 설정하면, 해당 테인트를 용인(Tolerate)하는 톨러레이션을 가진 파드만 그 노드에 스케줄링될 수 있도록 제한합니다. 테인트는 ‘이 노드에는 아무나 들어오지 마!’라고 표시하는 것과 같고, 톨러레이션은 ‘나는 그 표시를 무시하고 들어갈 수 있어!’라고 허가를 받는 것과 같습니다.

    데몬셋 컨트롤러는 특정 테인트(예: node.kubernetes.io/unschedulablenode.kubernetes.io/not-ready 등 스케줄링과 관련된 기본 테인트)를 자동으로 용인하도록 설정되어 있어, 일반적으로 스케줄링이 불가능한 상태의 노드에도 데몬셋 파드를 배포할 수 있습니다 (단, NoExecute 효과의 테인트는 파드를 축출시킬 수 있으므로 주의).

    하지만 사용자가 직접 노드에 커스텀 테인트를 적용하고, 데몬셋 파드 템플릿에 해당 테인트를 용인하는 톨러레이션을 명시적으로 추가하면, 해당 톨러레이션을 가진 데몬셋 파드만 그 테인트된 노드에 배포되도록 제어할 수 있습니다. 이는 특정 역할을 수행하는 전용 노드 그룹에만 데몬셋 파드를 실행시키고 싶을 때 유용합니다. 예를 들어, 마스터 노드에는 보통 일반 워크로드가 스케줄링되지 않도록 테인트가 걸려 있지만, 마스터 노드에도 특정 데몬셋(예: 네트워크 플러그인 에이전트)이 실행되어야 할 경우, 해당 데몬셋은 마스터 노드의 테인트를 용인하는 톨러레이션을 가져야 합니다.

이러한 노드 선택 메커니즘들을 조합하여 사용하면, 데몬셋 파드가 정확히 원하는 노드들에만 배포되도록 매우 정교하게 제어할 수 있습니다. 데몬셋 컨트롤러는 이러한 규칙들을 고려하여 어떤 노드가 데몬셋 파드를 실행해야 하는 ‘적격한(eligible)’ 노드인지 판단하고, 그에 따라 파드를 생성하거나 삭제합니다.

7.5.2.2 노드 추가/삭제 시 자동 파드 관리

데몬셋의 가장 큰 장점 중 하나는 클러스터의 노드 구성 변경에 따라 파드를 자동으로 관리해준다는 것입니다. 이는 클러스터 관리자의 수동 개입 없이도 항상 원하는 상태를 유지할 수 있도록 해주는 핵심 기능입니다.

  • 새로운 노드 추가 시:클러스터에 새로운 워커 노드가 추가되고, 이 노드가 데몬셋 명세에 정의된 노드 셀렉터, 어피니티/안티-어피니티, 그리고 테인트/톨러레이션 규칙에 부합하는 ‘적격한’ 노드라고 판단되면, 데몬셋 컨트롤러는 즉시 해당 새로운 노드에 데몬셋 파드의 복제본을 자동으로 생성하여 스케줄링합니다.이 과정은 매우 신속하게 이루어지므로, 새로운 노드가 클러스터에 참여하자마자 필요한 시스템 수준의 에이전트(로그 수집기, 모니터링 에이전트 등)가 즉시 실행되어 해당 노드를 지원할 수 있게 됩니다. 이는 클러스터 확장에 따른 운영 부담을 크게 줄여줍니다.
  • 기존 노드 삭제 또는 부적격 상태 변경 시:반대로, 클러스터에서 기존 노드가 제거되거나, 노드의 레이블 변경 또는 테인트 추가 등으로 인해 더 이상 데몬셋 파드를 실행하기에 ‘적격하지 않은’ 상태가 되면, 데몬셋 컨트롤러는 해당 노드에서 실행 중이던 데몬셋 파드를 자동으로 종료시키고 삭제합니다.이를 통해 불필요한 리소스 낭비를 막고, 항상 데몬셋의 의도에 맞는 노드에만 파드가 실행되도록 보장합니다. 예를 들어, 특정 노드가 유지보수를 위해 일시적으로 스케줄링 불가능(unschedulable) 상태가 되더라도 (단, NoExecute 테인트가 없다면), 이미 실행 중인 데몬셋 파드는 계속 실행될 수 있습니다 (데몬셋 컨트롤러가 특정 테인트를 자동으로 용인하기 때문). 하지만 노드가 완전히 클러스터에서 제거되면 해당 파드도 깔끔하게 정리됩니다.
  • 데몬셋 업데이트 전략:데몬셋 자체의 파드 템플릿(예: 컨테이너 이미지 버전 변경)이 업데이트될 때도, 데몬셋은 파드들을 새로운 버전으로 교체해야 합니다. 이때 사용되는 업데이트 전략은 spec.updateStrategy.type 필드를 통해 설정할 수 있으며, 주로 두 가지 방식이 사용됩니다.
    • RollingUpdate (기본값): 디플로이먼트의 롤링 업데이트와 유사하게, 기존 버전의 파드를 하나씩 또는 지정된 수만큼 삭제하고 새로운 버전의 파드를 생성하는 방식으로 점진적인 업데이트를 수행합니다. spec.updateStrategy.rollingUpdate.maxUnavailable (또는 이전 버전의 maxSurge 와 유사한 개념이었던 maxMisscheduled 등, 버전에 따라 필드명 상이할 수 있음) 필드를 통해 동시에 업데이트될 수 있는 파드의 수를 제어할 수 있습니다. 이를 통해 서비스 중단 없이 안전하게 데몬셋 파드를 업데이트할 수 있습니다.
    • OnDelete: 이 전략을 사용하면, 데몬셋 템플릿이 변경되더라도 기존 파드들은 자동으로 업데이트되지 않습니다. 사용자가 수동으로 기존 데몬셋 파드를 삭제해야만 새로운 템플릿에 따라 새 파드가 생성됩니다. 이는 매우 민감한 시스템 데몬을 업데이트할 때, 각 노드별로 상황을 확인하며 점진적으로 수동 업데이트를 진행하고 싶을 경우에 사용할 수 있습니다.

이처럼 데몬셋 컨트롤러는 마치 부지런한 정원사처럼, 클러스터라는 정원의 모든 나무(노드)에 필요한 영양제(파드)가 제대로 공급되고 있는지, 그리고 불필요한 잡초가 자라지는 않는지 끊임없이 살피고 관리합니다. 이러한 자동화된 관리 기능 덕분에 우리는 클러스터의 규모가 커지거나 노드 구성이 자주 변경되더라도 일관된 방식으로 필요한 시스템 수준의 에이전트들을 안정적으로 운영할 수 있게 됩니다. 이는 클라우드 네이티브 환경의 동적인 특성에 매우 잘 부합하는 특징이라고 할 수 있습니다.