본문 바로가기

Infrastructure/Kubernetes

[Windows] 컨테이너 스케줄링

Windows 컨테이너 스케줄링

쿠버네티스 공식문서를 확인하며 Windows 컨테이너 스케줄링에 대해서 기억해야 하는 부분을 기록한다.


목표

  • Windows 노드에서 Windows 컨테이너를 실행하는 예시 디플로이먼트를 구성한다.
  • 쿠버네티스의 Windows 관련 기능을 강조한다.

시작하기 전에

  • 컨트롤 플레인과 Windows 서버로 운영되는 워커 노드를 포함하는 쿠버네티스 클러스터를 생성한다.
  • 쿠버네티스에서 서비스와 워크로드를 생성하고 배포하는 것은 리눅스나 Windows 컨테이너 모두 비슷한 방식이라는 것이 중요하다.
  • kubectl 커맨드로 클러스터에 접속하는 것은 동일하다.

Windows 컨테이너 배포하기

  • 아래는 Windows 컨테이너 안에서 웹서버 애플리케이션을 배포하는 YAML 예시이다.

      apiVersion: v1
      kind: Service
      metadata:
        name: win-webserver
        labels:
          app: win-webserver
      spec:
        ports:
          # 이 서비스가 서비스를 제공할 포트
          - port: 80
            targetPort: 80
        selector:
          app: win-webserver
        type: NodePort
      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        labels:
          app: win-webserver
        name: win-webserver
      spec:
        replicas: 2
        selector:
          matchLabels:
            app: win-webserver
        template:
          metadata:
            labels:
              app: win-webserver
            name: win-webserver
          spec:
           containers:
            - name: windowswebserver
              image: mcr.microsoft.com/windows/servercore:ltsc2019
              command:
              - powershell.exe
              - -command
              - "<#code used from https://gist.github.com/19WAS85/5424431#> ; $$listener = New-Object System.Net.HttpListener ; $$listener.Prefixes.Add('http://*:80/') ; $$listener.Start() ; $$callerCounts = @{} ; Write-Host('Listening at http://*:80/') ; while ($$listener.IsListening) { ;$$context = $$listener.GetContext() ;$$requestUrl = $$context.Request.Url ;$$clientIP = $$context.Request.RemoteEndPoint.Address ;$$response = $$context.Response ;Write-Host '' ;Write-Host('> {0}' -f $$requestUrl) ;  ;$$count = 1 ;$$k=$$callerCounts.Get_Item($$clientIP) ;if ($$k -ne $$null) { $$count += $$k } ;$$callerCounts.Set_Item($$clientIP, $$count) ;$$ip=(Get-NetAdapter | Get-NetIpAddress); $$header='<html><body><H1>Windows Container Web Server</H1>' ;$$callerCountsString='' ;$$callerCounts.Keys | % { $$callerCountsString+='<p>IP {0} callerCount {1} ' -f $$ip[1].IPAddress,$$callerCounts.Item($$_) } ;$$footer='</body></html>' ;$$content='{0}{1}{2}' -f $$header,$$callerCountsString,$$footer ;Write-Output $$content ;$$buffer = [System.Text.Encoding]::UTF8.GetBytes($$content) ;$$response.ContentLength64 = $$buffer.Length ;$$response.OutputStream.Write($$buffer, 0, $$buffer.Length) ;$$response.Close() ;$$responseStatus = $$response.StatusCode ;Write-Host('< {0}' -f $$responseStatus)  } ; "
           nodeSelector:
            kubernetes.io/os: windows
  • kubectl get nodes커맨드를 실행하여 노드의 상태를 확인할 수 있다.

  • kubectl apply -f win-webserver.yaml 커맨드를 실행하여 파드를 실행할 수 있다.

  • kubectl get pods -o wide -w 커맨드를 실행하여 파드의 갱신 상태를 확인할 수 있다.

  • 아래의 순서대로 디플로이먼트가 성공적인지 확인할 수 있다.

    • 리눅스 컨트롤 플레인 노드에서 나열된 두 파드가 존재하는지 확인하려면, kubectl get pods를 사용한다.
    • 네트워크를 통한 노드에서 파드로의 통신이 되는지 확인하려면, 리눅스 컨트롤 플레인 노드에서 curl을 파드 IP 주소의 80 포트로 실행하여 웹 서버 응답을 확인한다.
    • 파드 간 통신이 되는지 확인하려면, docker execkubectl exec를 이용해 파드 간에 핑(ping)한다.
    • 서비스에서 파드로의 통신이 되는지 확인하려면, 리눅스 컨트롤 플레인 노드와 독립 파드에서 curl을 가상 서비스 IP 주소로 실행한다. 여기서 가상 서비스 IP 주소는 kubectl get services 커맨드를 통해 확인할 수 있다.
    • 서비스 검색(discovery)이 되는지 확인하려면, 쿠버네티스 기본 DNS 접미사와 서비스 이름으로 curl을 실행한다.
    • 인바운드 연결이 되는지 확인하려면, 클러스터 외부 장비나 리눅스 컨트롤 플레인 노드에서 NodePort로 curl을 실행한다.
    • 아웃바운드 연결이 되는지 확인하려면, kubectl exec를 이용해서 파드에서 외부 IP 주소로 curl을 실행한다.
  • Windows 컨테이너 호스트는 현재 Windows 네트워킹 스택의 플랫폼 제한으로 인해, 그 안에서 스케줄링하는 서비스의 IP 주소로 접근할 수 없다. Windows 파드만 서비스 IP 주소로 접근할 수 있다.


가시성

워크로드에서 로그 캡쳐하기

  • 로그는 가시성이 중요한 요소이기 때문에 사용자가 워크로드의 운영측면을 파악할 수 있도록 하며 문제 해결의 핵심 요소로 작용한다.
  • Windows 컨테이너, 그리고 Windows 컨테이너 내의 워크로드는 리눅스 컨테이너와는 다르게 동작하기 때문에, 사용자가 로그를 수집하기 어려웠고 이로 인해 운영 가시성이 제한되어 왔다.
  • Windows 워크로드는 일반적으로 ETW(Event Tracing for Windows)에 로그인하거나 애플리케이션 이벤트 로그에 항목을 푸시하도록 구성한다.
  • Microsoft의 오픈 소스 도구인 LogMonitor는 Windows 컨테이너 안에 구성된 로그 소스를 모니터링하기 위해 권장되는 방법이다.
  • LogMonitor는 이벤트 로그, ETW 공급자 그리고 사용자 정의 애플리케이션 로그 모니터링을 지원하고 kubectl logs <pod>에 의한 사용을 위해 STDOUT으로 파이프한다.

[정리]

Windows 컨테이너의 경우 리눅스 컨테이너와 다르기 때문에 로그 수집이 어렵다. 이러한 문제를 해결하기 위해 Microsoft에서 오픈 소스로 제공하는 LogMonitor을 컨테이너 안에 구성해야 한다.


컨테이너 사용자 구성하기

설정 가능한 컨테이너 username 사용하기

  • Windows 컨테이너는 이미지 기본값과는 다르게 username으로 엔트리포인트와 프로세스를 실행하도록 설정할 수 있다.

그룹 매니지드 서비스 어카운트(GMSA)를 이용하여 워크로드 신원 관리하기

  • Windows 컨테이너 워크로드는 그룹 매니지드 서비스 어카운트(GMSA, Group Managed Service Account)를 이용하여 구성할 수 있다.
  • 그룹 매니지드 서비스 어카운트는 액티브 디렉터리 어카운트의 특정한 종류로 자동 암호 관리 기능, 단순화된 서비스 주체 이름(SPN, Simplified service principal name), 여러 서버의 다른 관리자에게 관리를 위임하는 기능을 제공한다.
  • GMSA로 구성한 컨테이너는 GMSA로 구성된 신원을 들고 있는 동안 외부 액티브 디렉터리 도메인 리소스를 접근할 수 있다.

테인트(Taint)와 톨러레이션(Toleration)

  • 사용자는 리눅스와 Windows 워크로드를 적절한 노드에 스케줄링되도록 하기 위해 테인트와 노드 셀렉터(nodeSelector)의 조합을 이용해야 한다.
  • 권장되는 방식들의 주요 목표 중에 하나는 이러한 방식들이 기존 리눅스 워크로드와 호환되어야 한다는 것이다.
  • IdentifyPodOS 기능 게이트가 활성화되어 있으면, 파드의 컨테이너가 어떤 운영 체제용인지를 파드의 .spec.os.name에 설정할 수 있다.
  • 리눅스 컨테이너를 실행하는 파드에는 .spec.os.namelinux로 설정한다. Windows 컨테이너를 실행하는 파드에는 .spec.os.namewindows로 설정해야 한다.
  • 스케줄러는 파드를 노드에 할당할 때, .spec.os.name 필드의 값을 사용하지 않는다.
  • 컨트롤 플레인이 파드를 적절한 운영 체제가 실행되고 있는 노드에 배치하도록 하려면, 파드를 노드에 할당하는 일반적인 쿠버네티스 메커니즘을 사용해야 한다.
  • .spec.os.name 필드는 Windows 파드의 스케줄링에는 영향을 미치지 않기 때문에, Windows 파드가 적절한 Windows 노드에 할당되도록 하려면 테인트, 톨러레이션 및 노드 셀렉터가 여전히 필요하다.

특정 OS 워크로드를 적절한 컨테이너 호스트에서 처리하도록 보장하기

  • 사용자는 테인트와 톨러레이션을 이용하여 Windows 컨테이너가 적절한 호스트에서 스케줄링 되는 것을 보장할 수 있다.

  • 모든 쿠버네티스 노드는 아래와 같은 기본 레이블을 가지고 있다.

  • 파드 사양에 노드 셀렉터를 "kubernetes.io/os": windows와 같이 지정하지 않았다면, 그 파드는 리눅스나 Windows, 아무 호스트에나 스케줄링될 수 있다.

  • Windows 컨테이너는 Windows에서만 운영될 수 있고 리눅스 컨테이너는 리눅스에서만 실행되도록 해야 하며, 이를 위해 가장 좋은 방법은 노드 셀렉터를 사용하는 것이다.

  • 많은 경우 사용자는 이미 존재하는 대량의 리눅스 컨테이너용 디플로이먼트를 가지고 있을 분만 아니라, 헬름(Helm) 차트 커뮤니티 같은 상용 구성의 에코시스템이나, 오퍼레이터같은 프로그래밍 방식의 파드 생성를 알고 있기 때문에 노드 셀렉터를 추가하는 작업은 망설여질 수 있다.

  • 이러한 경우에 테인트를 사용하는 것이 추천된다. kubelet은 등록하는 동안 테인트를 설정할 수 있기 때문에, Windows에서만 운영할 때에 자동으로 테인트를 추가하기 쉽다.

  • --register-with-taints='os=windows:NoSchedule'와 같은 커맨드를 사용하면 모든 Windows 노드에 테인트를 추가하여 아무 것도 해당 노드에 스케줄링되지 않게 될 것이다.

  • Windows 파드가 Windows 노드에 스케줄링되도록 하려면, Windows 노드가 선택되도록 하기 위한 노드 셀렉터 및 적합하게 일치하는 톨러레이션이 모두 필요하다. 아래는 예시이다.

      nodeSelector:
          kubernetes.io/os: windows
          node.kubernetes.io/windows-build: '10.0.17763'
      tolerations:
          - key: "os"
            operator: "Equal"
            value: "windows"
            effect: "NoSchedule"

동일 클러스터에서 여러 Windows 버전을 조작하는 방법

  • 파드에서 사용하는 Windows 서버 버전은 노드의 Windows 서버 버전과 일치해야 한다. 만약 동일한 클러스터에서 여러 Windows 서버 버전을 사용하려면, 추가로 노드 레이블과 nodeSelectors를 설정해야 한다.
  • 쿠버네티스는 이러한 설정을 단순화하기 위해 새로운 레이블인 node.kubernetes.io/windows-buil를 자동으로 추가한다. 이 레이블은 호환성을 위해 일치시켜야 하는 Windows major/minor 및 빌드 번호를 나타내며 아래는 각 윈도우 서버 버전에 대해 현재 사용하고 있는 빌드 버전이다.
제품 이름 빌드 번호
Windows Server 2019 10.0.17763
Windows Server, 버전 20H2 10.0.19042
Windows Server 2022 10.0.20348

RuntimeClass로 단순화

  • 런타임클래스를 사용해서 테인트와 톨러레이션을 사용하는 프로세스를 간소화할 수 있다.

  • 클러스터 관리자는 이 테인트와 톨러레이션을 캡슐화하는 데 사용되는 RuntimeClass 오브젝트를 생성할 수 있다.

  • 아래의 runtimeClasses.yml 파일은 Windows OS, 아키텍처 및 버전에 적합한 nodeSelector가 포함되어 있는 예시이다.

      apiVersion: node.k8s.io/v1
      kind: RuntimeClass
      metadata:
        name: windows-2019
      handler: 'docker'
      scheduling:
        nodeSelector:
          kubernetes.io/os: 'windows'
          kubernetes.io/arch: 'amd64'
          node.kubernetes.io/windows-build: '10.0.17763'
        tolerations:
        - effect: NoSchedule
          key: os
          operator: Equal
          value: "windows"
  • 클러스터 관리자로 kubectl create -f runtimeClasses.yml을 실행해서 사용한다. 파드 사양에 적합한 runtimeClassName: windows-2019를 추가한다. 아래는 예시이다.

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: iis-2019
        labels:
          app: iis-2019
      spec:
        replicas: 1
        template:
          metadata:
            name: iis-2019
            labels:
              app: iis-2019
          spec:
            runtimeClassName: windows-2019
            containers:
            - name: iis
              image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
              resources:
                limits:
                  cpu: 1
                  memory: 800Mi
                requests:
                  cpu: .1
                  memory: 300Mi
              ports:
                - containerPort: 80
       selector:
          matchLabels:
            app: iis-2019
      ---
      apiVersion: v1
      kind: Service
      metadata:
        name: iis
      spec:
        type: LoadBalancer
        ports:
        - protocol: TCP
          port: 80
        selector:
          app: iis-2019

[정리]

Windows 컨테이너와 리눅스 컨테이너를 같이 사용하는 경우에 적절한 노드에 스케줄링되도록 하는 것이 매우 중요하다. 이럴 때 주로 사용되는 것이 노드 셀렉터다.

하지만 노드 셀렉터의 경우 이미 헬름(Helm)이나 기타 커뮤니티에서 사용되는 예시가 있기 때문에 노드 셀렉터를 추가하는 것은 쉽지 않다. 이럴 때 사용이 추천되는 것이 테인트와 톨러레이션이다.

클러스터 관리자는 RuntimeClass를 사용하여 테인트와 톨러레이션을 적용을 간소화할 수 있고, 캡슐화 또한 할 수 있다.


참고 자료