Kubernetes에서 Windows 컨테이너 실행 가이드

Kubernetes에서 Windows 컨테이너 실행 가이드 (Guide for Running Windows Containers in Kubernetes)

이 페이지는 Kubernetes로 Windows 컨테이너를 실행하기 위해 따라 할 수 있는 몇 가지 단계를 안내해요. 또한 Kubernetes 안에서 Windows에 특화된 몇 가지 기능도 소개해요.

Kubernetes에서 서비스와 워크로드를 만들고 배포하는 방식은 Linux와 Windows 컨테이너에서 거의 동일하다는 점을 기억하는 게 중요해요. 클러스터와 상호작용하는 kubectl 명령도 똑같아요. 이 페이지의 예시들은 Windows 컨테이너 경험을 빠르게 시작할 수 있도록 돕기 위해 준비된 거예요.

목표

Windows 노드에서 Windows 컨테이너를 실행하는 예시 배포를 구성해요.

시작하기 전에

이미 Windows Server를 실행하는 워커 노드가 포함된 Kubernetes 클러스터에 접근할 수 있어야 해요.

시작하기: Windows 워크로드 배포

아래 예시 YAML 파일은 Windows 컨테이너 안에서 실행되는 간단한 웹 서버 애플리케이션을 배포해요.

win-webserver.yaml이라는 매니페스트를 아래 내용으로 만드세요.

---
apiVersion: v1
kind: Service
metadata:
  name: win-webserver
  labels:
    app: win-webserver
spec:
  ports:
    # the port that this service should serve on
    - 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

참고:

포트 매핑도 지원되지만, 이 예시에서는 단순함을 위해 컨테이너의 80 포트를 Service에 직접 노출해요.

  1. 모든 노드가 정상인지 확인해요.
kubectl get nodes
  1. 서비스를 배포하고 pod 갱신을 지켜봐요.
kubectl apply -f win-webserver.yaml
kubectl get pods -o wide -w

서비스가 올바르게 배포되면 두 Pod 모두 Ready로 표시돼요. watch 명령에서 나가려면 Ctrl+C를 눌러요.

  1. 배포가 성공했는지 확인해요. 검증 방법을 볼게요.
    • Linux 컨트롤 플레인 노드에서 여러 pod 목록을 보려면 kubectl get pods를 사용해요.
    • 네트워크를 통한 노드-투-pod 통신: Linux 컨트롤 플레인 노드에서 pod IP의 80 포트를 curl로 확인해 웹 서버 응답을 확인해요.
    • Pod-투-pod 통신: kubectl exec로 pod 사이에서 ping을 해 봐요(Windows 노드가 둘 이상이면 호스트 간에도).
    • Service-투-pod 통신: Linux 컨트롤 플레인 노드와 개별 pod에서 가상 서비스 IP(kubectl get services에서 확인)를 curl로 확인해요.
    • Service 디스커버리: Kubernetes 기본 DNS 접미사를 쓰는 서비스 이름을 curl로 확인해요.
    • 인바운드 연결: Linux 컨트롤 플레인 노드 또는 클러스터 밖의 머신에서 NodePort를 curl로 확인해요.
    • 아웃바운드 연결: kubectl exec로 pod 안에서 외부 IP를 curl로 확인해요.

참고:

Windows 컨테이너 호스트는 현재 Windows 네트워킹 스택의 플랫폼 한계 때문에 자체적으로 스케줄링된 서비스의 IP에 접근할 수 없어요. Windows pod만 서비스 IP에 접근할 수 있어요.

관측성

워크로드에서 로그 수집하기

로그는 관측성의 중요한 요소예요. 로그를 통해 워크로드의 운영 측면에 대한 통찰을 얻고 문제 해결의 핵심 재료가 돼요. Windows 컨테이너와 그 안의 워크로드는 Linux 컨테이너와 다르게 동작하기 때문에, 사용자들은 로그를 수집하는 데 어려움을 겪었고 운영 가시성도 제한됐어요. 예를 들어 Windows 워크로드는 보통 ETW(Event Tracing for Windows)로 로그하거나 애플리케이션 이벤트 로그에 항목을 남기도록 구성돼요. Microsoft가 만든 오픈 소스 도구인 LogMonitor는 Windows 컨테이너 안에서 구성된 로그 소스를 모니터링하는 권장 방법이에요. LogMonitor는 이벤트 로그, ETW 공급자, 커스텀 애플리케이션 로그를 모니터링해서 STDOUT로 파이프해 kubectl logs <pod>로 소비할 수 있게 해 줘요.

LogMonitor GitHub 페이지의 지침에 따라 바이너리와 구성 파일을 모든 컨테이너에 복사하고, LogMonitor가 로그를 STDOUT로 보내도록 필요한 엔트리포인트를 추가하세요.

컨테이너 사용자 구성

구성 가능한 컨테이너 사용자 이름 사용

Windows 컨테이너는 이미지 기본값과 다른 사용자 이름으로 엔트리포인트와 프로세스를 실행하도록 구성할 수 있어요. 자세한 내용은 여기에서 알아보세요.

GMSA(Group Managed Service Accounts)로 워크로드 ID 관리

Windows 컨테이너 워크로드는 GMSA(Group Managed Service Accounts)를 사용하도록 구성할 수 있어요. GMSA는 Active Directory 계정의 특정 유형으로, 자동 암호 관리를 제공하고 서비스 사용자 이름(SPN) 관리를 단순화하며 여러 서버에 걸쳐 관리를 다른 관리자에게 위임할 수 있게 해 줘요. GMSA로 구성된 컨테이너는 GMSA에 구성된 ID를 지니고 외부 Active Directory 도메인 리소스에 접근할 수 있어요. Windows 컨테이너에 GMSA를 구성하고 사용하는 방법은 여기에서 알아보세요.

테인트(Taints)와 톨러레이션(Tolerations)

Linux와 Windows 워크로드를 각각의 OS에 맞는 노드에 스케줄링하려면 테인트와 노드 셀렉터를 조합해서 써야 해요. 권장 접근 방식은 아래에 있는데, 주요 목표 중 하나는 기존 Linux 워크로드의 호환성을 깨지 않도록 하는 거예요.

각 Pod에 .spec.os.name을 설정해서 해당 Pod의 컨테이너가 대상으로 하는 운영 체제를 나타낼 수 있어요(그리고 그래야 합니다). Linux 컨테이너를 실행하는 Pod에는 .spec.os.namelinux로, Windows 컨테이너를 실행하는 Pod에는 windows로 설정해요.

참고:

1.24보다 오래된 Kubernetes 버전을 실행 중이라면 .spec.pod.os에 값을 설정할 수 있도록 IdentifyPodOS 기능 게이트를 활성화해야 할 수도 있어요.

스케줄러는 Pod를 노드에 배치할 때 .spec.os.name 값을 사용하지 않아요. 클러스터의 컨트롤 플레인이 pod를 올바른 운영 체제를 실행하는 노드에 배치하도록 pod를 노드에 할당하는 일반적인 메커니즘을 사용해야 해요.

.spec.os.name 값은 Windows pod의 스케줄링에는 영향을 미치지 않아요. 따라서 Windows pod가 적절한 Windows 노드에 배치되도록 여전히 테인트와 톨러레이션(또는 노드 셀렉터)이 필요해요.

OS별 워크로드가 적절한 컨테이너 호스트에 배치되도록 보장

테인트와 톨러레이션을 사용하면 Windows 컨테이너가 적절한 호스트에 스케줄링되도록 보장할 수 있어요. Kubernetes 1.36을 실행하는 모든 Kubernetes 노드에는 다음과 같은 기본 레이블이 있어요.

  • kubernetes.io/os = [windows|linux]
  • kubernetes.io/arch = [amd64|arm64|...]

Pod 스펙에 "kubernetes.io/os": windows 같은 nodeSelector를 지정하지 않으면, 해당 Pod는 Windows든 Linux든 어떤 호스트에든 스케줄링될 수 있어요. Windows 컨테이너는 Windows에서만, Linux 컨테이너는 Linux에서만 실행될 수 있으므로 이는 문제가 될 수 있어요. Kubernetes 1.36의 모범 사례는 nodeSelector를 사용하는 거예요.

하지만 많은 경우 사용자는 Linux 컨테이너용으로 이미 만들어진 대규모 배포와 커뮤니티 Helm 차트 같은 기성 설정, operator 같은 프로그래밍 방식의 Pod 생성 사례를 갖고 있어요. 그런 상황에서는 모든 Pod와 Pod 템플릿에 nodeSelector 필드를 추가하는 구성 변경을 망설일 수도 있어요. 대안은 테인트를 사용하는 거예요. kubelet은 등록 중에 테인트를 설정할 수 있기 때문에, Windows에서만 실행할 때 자동으로 테인트를 추가하도록 쉽게 수정할 수 있어요.

예를 들어 --register-with-taints='os=windows:NoSchedule'처럼요.

모든 Windows 노드에 테인트를 추가하면 그 노드들에는 아무것도 스케줄링되지 않아요(기존 Linux Pod도 포함). Windows pod가 Windows 노드에 스케줄링되려면 nodeSelector와 Windows를 선택하는 적절한 매칭 톨러레이션이 둘 다 필요해요.

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

같은 클러스터에서 여러 Windows 버전 처리

각 pod가 사용하는 Windows Server 버전은 노드의 버전과 일치해야 해요. 같은 클러스터에서 여러 Windows Server 버전을 쓰려면 추가 노드 레이블과 nodeSelector 필드를 설정해야 해요.

Kubernetes는 이를 단순화하기 위해 node.kubernetes.io/windows-build 레이블을 자동으로 추가해요.

이 레이블은 호환성을 위해 일치해야 하는 Windows 메이저, 마이너, 빌드 번호를 반영해요. 각 Windows Server 버전에 사용되는 값은 다음과 같아요.

제품 이름 버전
Windows Server 2022 10.0.20348
Windows Server 2025 10.0.26100

RuntimeClass로 단순화

RuntimeClass를 사용하면 테인트와 톨러레이션을 사용하는 과정을 단순화할 수 있어요. 클러스터 관리자는 이러한 테인트와 톨러레이션을 캡슐화하는 RuntimeClass 객체를 만들 수 있어요.

  1. 이 파일을 runtimeClasses.yml로 저장해요. Windows OS, 아키텍처, 버전에 대한 적절한 nodeSelector가 포함돼 있어요.
---
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: windows-2019
handler: example-container-runtime-handler
scheduling:
  nodeSelector:
    kubernetes.io/os: 'windows'
    kubernetes.io/arch: 'amd64'
    node.kubernetes.io/windows-build: '10.0.20348'
  tolerations:
  - effect: NoSchedule
    key: os
    operator: Equal
    value: "windows"
  1. 클러스터 관리자로 kubectl create -f runtimeClasses.yml을 실행해요.
  2. Pod 스펙에 적절하게 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