1 - 컨테이너 및 파드 메모리 리소스 할당

이 페이지는 메모리 요청량 과 메모리 상한 을 컨테이너에 어떻게 지정하는지 보여준다. 컨테이너는 요청량 만큼의 메모리 확보가 보장되나 상한보다 더 많은 메모리는 사용할 수 없다.

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

버전 확인을 위해서, 다음 커맨드를 실행 kubectl version.

클러스터의 각 노드에 최소 300 MiB 메모리가 있어야 한다.

이 페이지의 몇 가지 단계를 수행하기 위해서는 클러스터 내 metrics-server 서비스 실행이 필요하다. 이미 실행 중인 metrics-server가 있다면 다음 단계를 건너뛸 수 있다.

Minikube를 사용 중이라면, 다음 명령어를 실행해 metric-server를 활성화할 수 있다.

minikube addons enable metrics-server

metric-server가 실행 중인지 확인하거나 다른 제공자의 리소스 메트릭 API (metrics.k8s.io)를 확인하기 위해 다음의 명령어를 실행한다.

kubectl get apiservices

리소스 메트릭 API를 사용할 수 있다면 출력에 metrics.k8s.io에 대한 참조가 포함되어 있다.

NAME      
v1beta1.metrics.k8s.io

네임스페이스 생성

이 예제에서 생성할 자원과 클러스터 내 나머지를 분리하기 위해 네임스페이스를 생성한다.

kubectl create namespace mem-example

메모리 요청량 및 상한을 지정

컨테이너에 메모리 요청량을 지정하기 위해서는 컨테이너의 리소스 매니페스트에 resources:requests 필드를 포함한다. 리소스 상한을 지정하기 위해서는 resources:limits 필드를 포함한다.

이 예제에서 하나의 컨테이너를 가진 파드를 생성한다. 생성된 컨테이너는 100 MiB 메모리 요청량과 200 MiB 메모리 상한을 갖는다. 이 것이 파드 구성 파일이다.

apiVersion: v1
kind: Pod
metadata:
  name: memory-demo
  namespace: mem-example
spec:
  containers:
  - name: memory-demo-ctr
    image: polinux/stress
    resources:
      requests:
        memory: "100Mi"
      limits:
        memory: "200Mi"
    command: ["stress"]
    args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]

구성 파일 내 args 섹션은 컨테이너가 시작될 때 아규먼트를 제공한다. "--vm-bytes", "150M" 아규먼트는 컨테이너가 150 MiB 할당을 시도 하도록 한다.

파드 생성:

kubectl apply -f https://k8s.io/examples/pods/resource/memory-request-limit.yaml --namespace=mem-example

파드 컨테이너가 실행 중인지 확인:

kubectl get pod memory-demo --namespace=mem-example

파드에 대한 자세한 정보 보기:

kubectl get pod memory-demo --output=yaml --namespace=mem-example

출력은 파드 내 하나의 컨테이너에 100MiB 메모리 요청량과 200 MiB 메모리 상한이 있는 것을 보여준다.

...
resources:
  requests:
    memory: 100Mi
  limits:
    memory: 200Mi
...

kubectl top을 실행하여 파드 메트릭 가져오기:

kubectl top pod memory-demo --namespace=mem-example

출력은 파드가 약 150 MiB 해당하는 약 162,900,000 바이트 메모리를 사용하는 것을 보여준다. 이는 파드의 100 MiB 요청 보다 많으나 파드의 200 MiB 상한보다는 적다.

NAME                        CPU(cores)   MEMORY(bytes)
memory-demo                 <something>  162856960

파드 삭제:

kubectl delete pod memory-demo --namespace=mem-example

컨테이너의 메모리 상한을 초과

노드 내 메모리가 충분하다면 컨테이너는 지정한 요청량보다 많은 메모리를 사용 할 수 있다. 그러나 컨테이너는 지정한 메모리 상한보다 많은 메모리를 사용할 수 없다. 만약 컨테이너가 지정한 메모리 상한보다 많은 메모리를 할당하면 해당 컨테이너는 종료 대상 후보가 된다. 만약 컨테이너가 지속적으로 지정된 상한보다 많은 메모리를 사용한다면, 해당 컨테이너는 종료된다. 만약 종료된 컨테이너가 재실행 가능하다면 다른 런타임 실패와 마찬가지로 kubelet에 의해 재실행된다.

이 예제에서는 상한보다 많은 메모리를 할당하려는 파드를 생성한다. 이 것은 50 MiB 메모리 요청량과 100 MiB 메모리 상한을 갖는 하나의 컨테이너를 갖는 파드의 구성 파일이다.

apiVersion: v1
kind: Pod
metadata:
  name: memory-demo-2
  namespace: mem-example
spec:
  containers:
  - name: memory-demo-2-ctr
    image: polinux/stress
    resources:
      requests:
        memory: "50Mi"
      limits:
        memory: "100Mi"
    command: ["stress"]
    args: ["--vm", "1", "--vm-bytes", "250M", "--vm-hang", "1"]

구성 파일의 args 섹션에서 컨테이너가 100 MiB 상한을 훨씬 초과하는 250 MiB의 메모리를 할당하려는 것을 볼 수 있다.

파드 생성:

kubectl apply -f https://k8s.io/examples/pods/resource/memory-request-limit-2.yaml --namespace=mem-example

파드에 대한 자세한 정보 보기:

kubectl get pod memory-demo-2 --namespace=mem-example

이 시점에 컨테이너가 실행되거나 종료되었을 수 있다. 컨테이너가 종료될 때까지 이전의 명령을 반복한다.

NAME            READY     STATUS      RESTARTS   AGE
memory-demo-2   0/1       OOMKilled   1          24s

컨테이너 상태의 상세 상태 보기:

kubectl get pod memory-demo-2 --output=yaml --namespace=mem-example

컨테이너가 메모리 부족 (OOM) 으로 종료되었음이 출력된다.

lastState:
   terminated:
     containerID: 65183c1877aaec2e8427bc95609cc52677a454b56fcb24340dbd22917c23b10f
     exitCode: 137
     finishedAt: 2017-06-20T20:52:19Z
     reason: OOMKilled
     startedAt: null

이 예제에서 컨테이너는 재실행 가능하여 kubelet에 의해 재실행된다. 컨테이너가 종료되었다 재실행되는 것을 보기 위해 다음 명령을 몇 번 반복한다.

kubectl get pod memory-demo-2 --namespace=mem-example

출력은 컨테이너의 종료, 재실행, 재종료, 재실행 등을 보여준다.

kubectl get pod memory-demo-2 --namespace=mem-example
NAME            READY     STATUS      RESTARTS   AGE
memory-demo-2   0/1       OOMKilled   1          37s

kubectl get pod memory-demo-2 --namespace=mem-example
NAME            READY     STATUS    RESTARTS   AGE
memory-demo-2   1/1       Running   2          40s

파드 내역에 대한 상세 정보 보기:

kubectl describe pod memory-demo-2 --namespace=mem-example

컨테이너가 반복적으로 시작하고 실패 하는 출력을 보여준다.

... Normal  Created   Created container with id 66a3a20aa7980e61be4922780bf9d24d1a1d8b7395c09861225b0eba1b1f8511
... Warning BackOff   Back-off restarting failed container

클러스터 노드에 대한 자세한 정보 보기:

kubectl describe nodes

출력에는 컨테이너가 메모리 부족으로 종료된 기록이 포함된다.

Warning OOMKilling Memory cgroup out of memory: Kill process 4481 (stress) score 1994 or sacrifice child

파드 삭제:

kubectl delete pod memory-demo-2 --namespace=mem-example

노드에 비해 너무 큰 메모리 요청량의 지정

메모리 요청량과 상한은 컨테이너와 관련있지만, 파드가 가지는 메모리 요청량과 상한으로 이해하면 유용하다. 파드의 메모리 요청량은 파드 내 모든 컨테이너의 메모리 요청량의 합이다. 마찬가지로 파드의 메모리 상한은 파드 내 모든 컨테이너의 메모리 상한의 합이다.

파드는 요청량을 기반하여 스케줄링된다. 노드에 파드의 메모리 요청량을 충족하기에 충분한 메모리가 있는 경우에만 파드가 노드에서 스케줄링된다.

이 예제에서는 메모리 요청량이 너무 커 클러스터 내 모든 노드의 용량을 초과하는 파드를 생성한다. 다음은 클러스터 내 모든 노드의 용량을 초과할 수 있는 1000 GiB 메모리 요청을 포함하는 컨테이너를 갖는 파드의 구성 파일이다.

apiVersion: v1
kind: Pod
metadata:
  name: memory-demo-3
  namespace: mem-example
spec:
  containers:
  - name: memory-demo-3-ctr
    image: polinux/stress
    resources:
      requests:
        memory: "1000Gi"
      limits:
        memory: "1000Gi"
    command: ["stress"]
    args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]

파드 생성:

kubectl apply -f https://k8s.io/examples/pods/resource/memory-request-limit-3.yaml --namespace=mem-example

파드 상태 보기:

kubectl get pod memory-demo-3 --namespace=mem-example

파드 상태가 PENDING 상태임이 출력된다. 즉 파드는 어떤 노드에서도 실행되도록 스케줄 되지 않고 PENDING가 계속 지속된다.

kubectl get pod memory-demo-3 --namespace=mem-example
NAME            READY     STATUS    RESTARTS   AGE
memory-demo-3   0/1       Pending   0          25s

이벤트를 포함한 파드 상세 정보 보기:

kubectl describe pod memory-demo-3 --namespace=mem-example

출력은 노드 내 메모리가 부족하여 파드가 스케줄링될 수 없음을 보여준다.

Events:
  ...  Reason            Message
       ------            -------
  ...  FailedScheduling  No nodes are available that match all of the following predicates:: Insufficient memory (3).

메모리 단위

메모리 리소스는 byte 단위로 측정된다. 다음 접미사 중 하나로 정수 또는 고정 소수점으로 메모리를 표시할 수 있다. E, P, T, G, M, K, Ei, Pi, Ti, Gi, Mi, Ki. 예를 들어 다음은 거의 유사한 값을 나타낸다.

128974848, 129e6, 129M, 123Mi

파드 삭제:

kubectl delete pod memory-demo-3 --namespace=mem-example

메모리 상한을 지정하지 않으면

컨테이너에 메모리 상한을 지정하지 않으면 다음 중 하나가 적용된다.

  • 컨테이너가 사용할 수 있는 메모리 상한은 없다. 컨테이너가 실행 중인 노드에서 사용 가능한 모든 메모리를 사용하여 OOM Killer가 실행될 수 있다. 또한 메모리 부족으로 인한 종료 시 메모리 상한이 없는 컨테이너가 종료될 가능성이 크다.

  • 기본 메모리 상한을 갖는 네임스페이스 내에서 실행중인 컨테이너는 자동으로 기본 메모리 상한이 할당된다. 클러스터 관리자들은 LimitRange를 사용해 메모리 상한의 기본 값을 지정 가능하다.

메모리 요청량과 상한 동기부여

클러스터에서 실행되는 컨테이너에 메모리 요청량과 상한을 구성하여 클러스터 내 노드들의 메모리 리소스를 효율적으로 사용할 수 있게 할 수 있다. 파드의 메모리 요청량을 적게 유지하여 파드가 높은 확률로 스케줄링 될 수 있도록 한다. 메모리 상한이 메모리 요청량보다 크면 다음 두 가지가 수행된다.

  • 가용한 메모리가 있는 경우 파드가 이를 사용할 수 있는 버스트(burst) 활동을 할 수 있다.
  • 파드가 버스트 중 사용 가능한 메모리 양이 적절히 제한된다.

정리

네임스페이스를 지운다. 이 작업을 통해 네임스페이스 내 생성했던 모든 파드들은 삭제된다.

kubectl delete namespace mem-example

다음 내용

앱 개발자들을 위한

클러스터 관리자들을 위한

2 - 윈도우 파드 및 컨테이너에서 RunAsUserName 구성

기능 상태: Kubernetes v1.18 [stable]

이 페이지에서는 윈도우 노드에서 실행될 파드 및 컨테이너에 runAsUserName 설정을 사용하는 방법을 소개한다. 이는 리눅스 관련 runAsUser 설정과 거의 동일하여, 컨테이너의 기본값과 다른 username으로 애플리케이션을 실행할 수 있다.

시작하기 전에

쿠버네티스 클러스터가 있어야 하며 클러스터와 통신하도록 kubectl 명령줄 도구를 구성해야 한다. 클러스터에는 윈도우 워커 노드가 있어야 하고, 해당 노드에서 윈도우 워크로드를 실행하는 컨테이너의 파드가 스케쥴 된다.

파드의 username 설정

파드의 컨테이너 프로세스를 실행할 username을 지정하려면 파드 명세에 securityContext 필드 (PodSecurityContext) 를 포함시키고, 그 안에 runAsUserName 필드를 포함하는 windowsOptions (WindowsSecurityContextOptions) 필드를 추가한다.

파드에 지정하는 윈도우 보안 컨텍스트 옵션은 파드의 모든 컨테이너 및 초기화 컨테이너에 적용된다.

다음은 runAsUserName 필드가 설정된 윈도우 파드의 구성 파일이다.

apiVersion: v1
kind: Pod
metadata:
  name: run-as-username-pod-demo
spec:
  securityContext:
    windowsOptions:
      runAsUserName: "ContainerUser"
  containers:
  - name: run-as-username-demo
    image: mcr.microsoft.com/windows/servercore:ltsc2019
    command: ["ping", "-t", "localhost"]
  nodeSelector:
    kubernetes.io/os: windows

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/windows/run-as-username-pod.yaml

파드의 컨테이너가 실행 중인지 확인한다.

kubectl get pod run-as-username-pod-demo

실행 중인 컨테이너의 셸에 접근한다.

kubectl exec -it run-as-username-pod-demo -- powershell

셸이 올바른 username인 사용자로 실행 중인지 확인한다.

echo $env:USERNAME

결과는 다음과 같다.

ContainerUser

컨테이너의 username 설정

컨테이너의 프로세스를 실행할 username을 지정하려면, 컨테이너 매니페스트에 securityContext 필드 (SecurityContext) 를 포함시키고 그 안에 runAsUserName 필드를 포함하는 windowsOptions (WindowsSecurityContextOptions) 필드를 추가한다.

컨테이너에 지정하는 윈도우 보안 컨텍스트 옵션은 해당 개별 컨테이너에만 적용되며 파드 수준에서 지정한 설정을 재정의한다.

다음은 한 개의 컨테이너에 runAsUserName 필드가 파드 수준 및 컨테이너 수준에서 설정되는 파드의 구성 파일이다.

apiVersion: v1
kind: Pod
metadata:
  name: run-as-username-container-demo
spec:
  securityContext:
    windowsOptions:
      runAsUserName: "ContainerUser"
  containers:
  - name: run-as-username-demo
    image: mcr.microsoft.com/windows/servercore:ltsc2019
    command: ["ping", "-t", "localhost"]
    securityContext:
        windowsOptions:
            runAsUserName: "ContainerAdministrator"
  nodeSelector:
    kubernetes.io/os: windows

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/windows/run-as-username-container.yaml

파드의 컨테이너가 실행 중인지 확인한다.

kubectl get pod run-as-username-container-demo

실행 중인 컨테이너의 셸에 접근한다.

kubectl exec -it run-as-username-container-demo -- powershell

셸이 사용자에게 올바른 username(컨테이너 수준에서 설정된 사용자)을 실행 중인지 확인한다.

echo $env:USERNAME

결과는 다음과 같다.

ContainerAdministrator

윈도우 username 제약사항

이 기능을 사용하려면 runAsUserName 필드에 설정된 값이 유효한 username이어야 한다. 형식은 DOMAIN\USER 여야하고, 여기서 DOMAIN\은 선택 사항이다. 윈도우 username은 대소문자를 구분하지 않는다. 또한 DOMAINUSER 와 관련된 몇 가지 제약사항이 있다.

  • runAsUserName 필드는 비워 둘 수 없으며 제어 문자를 포함할 수 없다. (ASCII 값: 0x00-0x1F, 0x7F)
  • DOMAIN은 NetBios 이름 또는 DNS 이름이어야 하며 각각 고유한 제한이 있다.
    • NetBios 이름: 최대 15 자, .(마침표)으로 시작할 수 없으며 다음 문자를 포함할 수 없다. \ / : * ? " < > |
    • DNS 이름: 최대 255 자로 영숫자, 마침표(.), 대시(-)로만 구성되며, 마침표 또는 대시로 시작하거나 끝날 수 없다.
  • USER는 최대 20자이며, 오직 마침표나 공백들로는 구성할 수 없고, 다음 문자는 포함할 수 없다. " / \ [ ] : ; | = , + * ? < > @.

runAsUserName 필드에 허용되는 값의 예 : ContainerAdministrator,ContainerUser, NT AUTHORITY\NETWORK SERVICE, NT AUTHORITY\LOCAL SERVICE.

이러한 제약사항에 대한 자세한 내용은 여기여기를 확인한다.

다음 내용

3 - 윈도우 파드와 컨테이너용 GMSA 구성

기능 상태: Kubernetes v1.18 [stable]

이 페이지는 윈도우 노드에서 실행되는 파드와 컨테이너용으로 그룹 관리 서비스 어카운트(Group Managed Service Accounts, GMSA)를 구성하는 방법을 소개한다. 그룹 관리 서비스 어카운트는 자동 암호 관리, 단순화된 서비스 사용자 이름(service principal name, SPN) 관리, 여러 서버에 걸쳐 다른 관리자에게 관리를 위임하는 기능을 제공하는 특정한 유형의 액티브 디렉터리(Active Directory) 계정이다.

쿠버네티스에서 GMSA 자격 증명 사양은 쿠버네티스 클러스터 전체 범위에서 사용자 정의 리소스(Custom Resources)로 구성된다. 윈도우 파드 및 파드 내의 개별 컨테이너들은 다른 윈도우 서비스와 상호 작용할 때 도메인 기반 기능(예: Kerberos 인증)에 GMSA를 사용하도록 구성할 수 있다.

시작하기 전에

쿠버네티스 클러스터가 있어야 하며 클러스터와 통신하도록 kubectl 커맨드라인 툴을 구성해야 한다. 클러스터에는 윈도우 워커 노드가 있어야 한다. 이 섹션에서는 각 클러스터에 대해 한 번씩 필요한 일련의 초기 단계를 다룬다.

GMSACredentialSpec CRD 설치

GMSA 자격 증명 사양 리소스에 대한 커스텀리소스데피니션(CustomResourceDefinition, CRD)을 클러스터에서 구성하여 사용자 정의 리소스 유형 GMSACredentialSpec을 정의해야 한다. GMSA CRD YAML을 다운로드하고 gmsa-crd.yaml로 저장한다. 다음, kubectl apply -f gmsa-crd.yaml 로 CRD를 설치한다.

GMSA 사용자를 검증하기 위해 웹훅 설치

쿠버네티스 클러스터에서 두 개의 웹훅을 구성하여 파드 또는 컨테이너 수준에서 GMSA 자격 증명 사양 참조를 채우고 검증한다.

  1. 변형(mutating) 웹훅은 (파드 사양의 이름별로) GMSA에 대한 참조를 파드 사양 내 JSON 형식의 전체 자격 증명 사양으로 확장한다.

  2. 검증(validating) 웹훅은 GMSA에 대한 모든 참조가 파드 서비스 어카운트에서 사용하도록 승인되었는지 확인한다.

위의 웹훅 및 관련 오브젝트를 설치하려면 다음 단계가 필요하다.

  1. 인증서 키 쌍 생성 (웹훅 컨테이너가 클러스터와 통신할 수 있도록 하는데 사용됨)

  2. 위의 인증서로 시크릿을 설치

  3. 핵심 웹훅 로직에 대한 디플로이먼트(deployment)를 생성

  4. 디플로이먼트를 참조하여 검증 및 변경 웹훅 구성을 생성

스크립트를 사용하여 GMSA 웹훅과 위에서 언급한 관련 오브젝트를 배포 및 구성할 수 있다. 스크립트는 --dry-run=server 옵션으로 실행되어 클러스터에 대한 변경 사항을 검토할 수 있다.

스크립트에서 사용하는 YAML 템플릿을 사용하여 웹훅 및 (파라미터를 적절히 대체하여) 관련 오브젝트를 수동으로 배포할 수도 있다.

액티브 디렉터리에서 GMSA 및 윈도우 노드 구성

쿠버네티스의 파드가 GMSA를 사용하도록 구성되기 전에 윈도우 GMSA 문서에 설명된 대로 액티브 디렉터리에서 원하는 GMSA를 프로비저닝해야 한다. 윈도우 GMSA 문서에 설명된 대로 원하는 GMSA와 연결된 시크릿 자격 증명에 접근하려면 (쿠버네티스 클러스터의 일부인) 윈도우 워커 노드를 액티브 디렉터리에서 구성해야 한다.

GMSA 자격 증명 사양 리소스 생성

(앞에서 설명한 대로) GMSACredentialSpec CRD를 설치하면 GMSA 자격 증명 사양이 포함된 사용자 정의 리소스를 구성할 수 있다. GMSA 자격 증명 사양에는 시크릿 또는 민감한 데이터가 포함되어 있지 않다. 이것은 컨테이너 런타임이 원하는 윈도우 컨테이너 GMSA를 설명하는 데 사용할 수 있는 정보이다. GMSA 자격 증명 사양은 PowerShell 스크립트 유틸리티를 사용하여 YAML 형식으로 생성할 수 있다.

다음은 JSON 형식으로 GMSA 자격 증명 사양 YAML을 수동으로 생성한 다음 변환하는 단계이다.

  1. CredentialSpec 모듈 가져오기(import): ipmo CredentialSpec.psm1

  2. New-CredentialSpec을 사용하여 JSON 형식의 자격 증명 사양을 만든다. WebApp1이라는 GMSA 자격 증명 사양을 만들려면 New-CredentialSpec -Name WebApp1 -AccountName WebApp1 -Domain $(Get-ADDomain -Current LocalComputer)를 호출한다.

  3. Get-CredentialSpec을 사용하여 JSON 파일의 경로를 표시한다.

  4. credspec 파일을 JSON에서 YAML 형식으로 변환하고 필요한 헤더 필드 apiVersion, kind, metadata, credspec을 적용하여 쿠버네티스에서 구성할 수 있는 GMSACredentialSpec 사용자 정의 리소스로 만든다.

다음 YAML 구성은 gmsa-WebApp1이라는 GMSA 자격 증명 사양을 설명한다.

apiVersion: windows.k8s.io/v1
kind: GMSACredentialSpec
metadata:
  name: gmsa-WebApp1  #임의의 이름이지만 참조로 사용된다.
credspec:
  ActiveDirectoryConfig:
    GroupManagedServiceAccounts:
    - Name: WebApp1   #GMSA 계정의 사용자 이름
      Scope: CONTOSO  #NETBIOS 도메인 명
    - Name: WebApp1   #GMSA 계정의 사용자 이름
      Scope: contoso.com #DNS 도메인 명
  CmsPlugins:
  - ActiveDirectory
  DomainJoinConfig:
    DnsName: contoso.com  #DNS 도메인 명
    DnsTreeName: contoso.com #DNS 도메인 명 루트
    Guid: 244818ae-87ac-4fcd-92ec-e79e5252348a  #GUID
    MachineAccountName: WebApp1 #GMSA 계정의 사용자 이름
    NetBiosName: CONTOSO  #NETBIOS 도메인 명
    Sid: S-1-5-21-2126449477-2524075714-3094792973 #SID of GMSA

위의 자격 증명 사양 리소스는 gmsa-Webapp1-credspec.yaml로 저장되고 kubectl apply -f gmsa-Webapp1-credspec.yml을 사용하여 클러스터에 적용될 수 있다.

특정 GMSA 자격 증명 사양에서 RBAC를 활성화하도록 cluster role 구성

각 GMSA 자격 증명 사양 리소스에 대해 cluster role을 정의해야 한다. 이것은 일반적으로 서비스 어카운트인 주체에 의해 특정 GMSA 리소스에 대한 use 동사를 승인한다. 다음 예는 위에서 gmsa-WebApp1 자격 증명 사양의 사용을 승인하는 클러스터 롤(cluster role)을 보여준다. 파일을 gmsa-webapp1-role.yaml로 저장하고 kubectl apply -f gmsa-webapp1-role.yaml을 사용하여 적용한다.

#credspec을 읽을 Role 생성
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: webapp1-role
rules:
- apiGroups: ["windows.k8s.io"]
  resources: ["gmsacredentialspecs"]
  verbs: ["use"]
  resourceNames: ["gmsa-WebApp1"]

특정 GMSA credspecs를 사용하도록 서비스 어카운트에 롤 할당

(파드가 사용하게 되는) 서비스 어카운트는 위에서 생성한 클러스터 롤에 바인딩되어야 한다. 이렇게 하면 서비스 어카운트가 원하는 GMSA 자격 증명 사양 리소스를 사용할 수 있다. 다음은 위에서 생성한 gmsa-WebApp1 자격 증명 사양 리소스를 사용하기 위해 webapp1-role 클러스터 롤에 바인딩되는 기본(default) 서비스 어카운트이다.

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: allow-default-svc-account-read-on-gmsa-WebApp1
  namespace: default
subjects:
- kind: ServiceAccount
  name: default
  namespace: default
roleRef:
  kind: ClusterRole
  name: webapp1-role
  apiGroup: rbac.authorization.k8s.io

파드 사양에서 GMSA 자격 증명 사양 참조 구성

파드 사양 필드 securityContext.windowsOptions.gmsaCredentialSpecName은 파드 사양에서 원하는 GMSA 자격 증명 사양 사용자 정의 리소스에 대한 참조를 지정하는 데 사용된다. 이렇게 하면 지정된 GMSA를 사용하도록 파드 사양의 모든 컨테이너가 구성된다. 다음은 gmsa-WebApp1을 참조하도록 채워진 어노테이션이 있는 샘플 파드 사양이다.

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: with-creds
  name: with-creds
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      run: with-creds
  template:
    metadata:
      labels:
        run: with-creds
    spec:
      securityContext:
        windowsOptions:
          gmsaCredentialSpecName: gmsa-webapp1
      containers:
      - image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
        imagePullPolicy: Always
        name: iis
      nodeSelector:
        kubernetes.io/os: windows

파드 사양의 개별 컨테이너는 컨테이너별 securityContext.windowsOptions.gmsaCredentialSpecName 필드를 사용하여 원하는 GMSA credspec을 지정할 수도 있다. 다음은 예이다.

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: with-creds
  name: with-creds
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      run: with-creds
  template:
    metadata:
      labels:
        run: with-creds
    spec:
      containers:
      - image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
        imagePullPolicy: Always
        name: iis
        securityContext:
          windowsOptions:
            gmsaCredentialSpecName: gmsa-Webapp1
      nodeSelector:
        kubernetes.io/os: windows

(위에서 설명한 대로) GMSA 필드가 채워진 파드 사양이 클러스터에 적용되면 다음과 같은 일련의 이벤트가 발생한다.

  1. 변형 웹훅은 GMSA 자격 증명 사양 리소스에 대한 모든 참조를 확인하고 GMSA 자격 증명 사양의 내용으로 확장한다.

  2. 검증 웹훅은 파드와 연결된 서비스 어카운트가 지정된 GMSA 자격 증명 사양의 use 동사에 대해 승인되었는지 확인한다.

  3. 컨테이너 런타임은 컨테이너가 액티브 디렉터리에서 GMSA의 ID를 가정하고 해당 ID를 사용하여 도메인의 서비스에 접근할 수 있도록 지정된 GMSA 자격 증명 사양으로 각 윈도우 컨테이너를 구성한다.

호스트네임 또는 FQDN을 사용하는 경우에 네트워크 공유에 인증하기

파드에서 호스트네임 또는 FQDN을 사용하여 SMB 공유에 연결할 때 문제를 겪고 있으나, IPv4 주소로는 해당 공유에 접속이 가능한 상황이라면, 윈도우 노드에 다음 레지스트리 키를 등록했는지 확인한다.

reg add "HKLM\SYSTEM\CurrentControlSet\Services\hns\State" /v EnableCompartmentNamespace /t REG_DWORD /d 1

그런 다음 동작 변경 사항을 적용하려면 실행 중인 파드를 다시 생성해야 한다. 이 레지스트리 키가 어떻게 사용되는지에 대한 자세한 정보는 여기에서 볼 수 있다.

문제 해결

GMSA가 사용자 환경에서 작동하도록 하는 데 어려움이 있는 경우 취할 수 있는 몇 가지 문제 해결 단계가 있다.

먼저 credspec이 파드에 전달되었는지 확인한다. 이렇게 하려면 파드 중 하나에서 exec를 실행하고 nltest.exe /parentdomain 명령의 출력을 확인해야 한다.

아래 예에서 파드는 credspec을 올바르게 가져오지 못했다.

kubectl exec -it iis-auth-7776966999-n5nzr powershell.exe

nltest.exe /parentdomain 는 다음과 같은 오류를 발생시킨다.

Getting parent domain failed: Status = 1722 0x6ba RPC_S_SERVER_UNAVAILABLE

파드가 credspec을 올바르게 가져오면 다음으로 도메인과의 통신을 확인한다. 먼저 파드 내부에서 nslookup을 빠르게 수행하여 도메인의 루트를 찾는다.

이것은 다음의 세 가지를 의미한다.

  1. 파드는 DC에 도달할 수 있다.
  2. DC는 파드에 도달할 수 있다.
  3. DNS가 올바르게 작동하고 있다.

DNS 및 통신 테스트를 통과하면 다음으로 파드가 도메인과 보안 채널 통신을 설정했는지 확인해야 한다. 이렇게 하려면 파드에서 다시 exec를 실행하고 nltest.exe /query 명령을 실행한다.

nltest.exe /query

결과는 다음과 같다.

I_NetLogonControl failed: Status = 1722 0x6ba RPC_S_SERVER_UNAVAILABLE

이것은 어떤 이유로 파드가 credspec에 지정된 계정을 사용하여 도메인에 로그온할 수 없음을 알려준다. 다음을 실행하여 보안 채널 복구를 시도할 수 있다.

nltest /sc_reset:domain.example

명령이 성공하면 다음과 유사한 출력이 표시된다.

Flags: 30 HAS_IP  HAS_TIMESERV
Trusted DC Name \\dc10.domain.example
Trusted DC Connection Status Status = 0 0x0 NERR_Success
The command completed successfully

위의 방법으로 오류가 수정되면 다음 수명 주기 훅(hook)을 파드 사양에 추가하여 단계를 자동화할 수 있다. 오류가 수정되지 않은 경우 credspec을 다시 검사하여 정확하고 완전한지 확인해야 한다.

        image: registry.domain.example/iis-auth:1809v1
        lifecycle:
          postStart:
            exec:
              command: ["powershell.exe","-command","do { Restart-Service -Name netlogon } while ( $($Result = (nltest.exe /query); if ($Result -like '*0x0 NERR_Success*') {return $true} else {return $false}) -eq $false)"]
        imagePullPolicy: IfNotPresent

위의 lifecycle 섹션을 파드 사양에 추가하면, 파드는 nltest.exe /query 명령이 오류 없이 종료될 때까지 나열된 명령을 실행하여 netlogon 서비스를 다시 시작한다.

4 - 컨테이너 및 파드 CPU 리소스 할당

이 페이지에서는 컨테이너의 CPU 요청량과 CPU 상한을 지정하는 방법을 보여준다. 컨테이너는 설정된 상한보다 더 많은 CPU는 사용할 수 없다. 제공된 시스템에 CPU 가용량이 남아있다면, 컨테이너는 요청량만큼의 CPU를 할당받는 것을 보장받는다.

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

버전 확인을 위해서, 다음 커맨드를 실행 kubectl version.

태스크 예제를 수행하기 위해서는 최소 1개의 CPU가 가용한 클러스터가 필요하다.

이 페이지의 몇 가지 단계를 수행하기 위해서는 클러스터 내 metrics-server 서비스 실행이 필요하다. 이미 실행 중인 metrics-server가 있다면 다음 단계를 건너뛸 수 있다.

Minikube를 사용 중이라면, 다음 명령어를 실행해 metric-server를 활성화할 수 있다.

minikube addons enable metrics-server

metric-server(아니면 metrics.k8s.io와 같은 다른 제공자의 리소스 메트릭 API)가 실행 중인지를 확인하기 위해 다음의 명령어를 실행한다.

kubectl get apiservices

리소스 메트릭 API를 사용할 수 있다면 출력에 metrics.k8s.io에 대한 참조가 포함되어 있을 것이다.

NAME
v1beta1.metrics.k8s.io

네임스페이스 생성

이 예제에서 생성한 자원과 클러스터 내 나머지를 분리하기 위해 네임스페이스(Namespace)를 생성한다.

kubectl create namespace cpu-example

CPU 요청량 및 상한 지정

컨테이너에 CPU 요청량을 지정하기 위해서는 컨테이너의 리소스 매니페스트에 resources:requests 필드를 포함한다. CPU 상한을 지정하기 위해서는 resources:limits 필드를 포함한다.

이 예제에서는, 하나의 컨테이너를 가진 파드를 생성한다. 컨테이너는 0.5 CPU 요청량과 1 CPU 상한을 갖는다. 아래는 파드의 구성 파일이다.

apiVersion: v1
kind: Pod
metadata:
  name: cpu-demo
  namespace: cpu-example
spec:
  containers:
  - name: cpu-demo-ctr
    image: vish/stress
    resources:
      limits:
        cpu: "1"
      requests:
        cpu: "0.5"
    args:
    - -cpus
    - "2"

구성 파일 내 args 섹션은 컨테이너가 시작될 때 인수(argument)를 제공한다. -cpus "2" 인수는 컨테이너가 2 CPU 할당을 시도하도록 한다.

파드 생성:

kubectl apply -f https://k8s.io/examples/pods/resource/cpu-request-limit.yaml --namespace=cpu-example

파드가 실행 중인지 확인:

kubectl get pod cpu-demo --namespace=cpu-example

파드에 대한 자세한 정보 확인:

kubectl get pod cpu-demo --output=yaml --namespace=cpu-example

출력은 파드 내 하나의 컨테이너에 0.5 milliCPU 요청량과 1 CPU 상한이 있는 것을 보여준다.

resources:
  limits:
    cpu: "1"
  requests:
    cpu: 500m

kubectl top을 실행하여 파드 메트릭 가져오기:

kubectl top pod cpu-demo --namespace=cpu-example

출력은 파드가 974 milliCPU를 사용하는 것을 보여주는데, 이는 파드의 1 CPU 상한보다는 약간 적은 수치이다.

NAME                        CPU(cores)   MEMORY(bytes)
cpu-demo                    974m         <something>

만약 -cpu "2"로 설정한다면, 컨테이너가 2 CPU를 사용하도록 설정한 것이 된다. 하지만 컨테이너는 1 CPU까지만을 사용하도록 허용되어 있다는 사실을 기억하자. 컨테이너는 상한보다 더 많은 CPU 리소스를 사용하려고 하기 때문에, 컨테이너의 CPU 사용은 쓰로틀(throttled) 될 것이다.

CPU 단위(unit)

CPU 리소스는 CPU 단위로 측정된다. 쿠버네티스에서 1 CPU는, 다음과 같다.

  • 1 AWS vCPU
  • 1 GCP Core
  • 1 Azure vCore
  • 1 하이퍼스레드 (베어메탈 서버의 하이퍼스레딩 인텔 프로세서)

분수 값도 가능하다. 0.5 CPU를 요청한 컨테이너는 1 CPU를 요청한 컨테이너 CPU의 절반 가량을 보장받는다. 접미사 m을 사용하여 밀리(milli)를 표현할 수도 있다. 예를 들어서 100m CPU, 100milliCPU, 그리고 0.1 CPU는 모두 같다. 1m보다 정밀한 표현은 허용하지 않는다.

CPU는 상대적인 수량이 아닌, 절대적인 수량으로 요청된다. 즉 0.1는 싱글 코어, 듀얼 코어, 48-코어 머신에서도 같은 양을 나타낸다.

파드 삭제:

kubectl delete pod cpu-demo --namespace=cpu-example

노드보다 훨씬 높은 CPU 요청량을 지정할 경우

CPU 요청량과 상한은 컨테이너와 연관되어 있지만, 파드가 CPU 요청량과 상한을 갖는다고 생각하는 것이 유용하다. 특정 파드의 CPU 요청량은 해당 파드의 모든 컨테이너 CPU 요청량의 합과 같다. 마찬가지로, 특정 파드의 CPU 상한은 해당 파드의 모든 컨테이너 CPU 상한의 합과 같다.

파드 스케줄링은 요청량에 따라 수행된다. 파드는 파드 CPU 요청량을 만족할 정도로 노드에 충분한 CPU 리소스가 있을 때에만 노드에 스케줄링한다.

이 예제에서는, 클러스터의 모든 노드 가용량을 초과하는 CPU 요청량을 가진 파드를 생성했다. 아래는 하나의 컨테이너를 가진 파드에 대한 설정 파일이다. 컨테이너는 100 CPU을 요청하고 있는데, 이것은 클러스터의 모든 노드 가용량을 초과하는 것이다.

apiVersion: v1
kind: Pod
metadata:
  name: cpu-demo-2
  namespace: cpu-example
spec:
  containers:
  - name: cpu-demo-ctr-2
    image: vish/stress
    resources:
      limits:
        cpu: "100"
      requests:
        cpu: "100"
    args:
    - -cpus
    - "2"

파드 생성:

kubectl apply -f https://k8s.io/examples/pods/resource/cpu-request-limit-2.yaml --namespace=cpu-example

파드 상태 확인:

kubectl get pod cpu-demo-2 --namespace=cpu-example

출력은 파드 상태가 Pending 상태임을 보여준다. 이것은 파드는 어떤 노드에서도 실행되도록 스케줄되지 않았고, 이후에도 Pending 상태가 지속될 것이라는 것을 의미한다.

NAME         READY     STATUS    RESTARTS   AGE
cpu-demo-2   0/1       Pending   0          7m

이벤트를 포함한 파드 상세 정보 확인:

kubectl describe pod cpu-demo-2 --namespace=cpu-example

출력은 노드의 CPU 리소스가 부족하여 파드가 스케줄링될 수 없음을 보여준다.

Events:
  Reason                        Message
  ------                        -------
  FailedScheduling      No nodes are available that match all of the following predicates:: Insufficient cpu (3).

파드 삭제:

kubectl delete pod cpu-demo-2 --namespace=cpu-example

CPU 상한을 지정하지 않을 경우

컨테이너에 CPU 상한을 지정하지 않으면 다음 상황 중 하나가 발생한다.

  • 컨테이너가 사용할 수 있는 CPU 리소스에 상한선이 없다. 컨테이너는 실행 중인 노드에서 사용 가능한 모든 CPU 리소스를 사용해버릴 수도 있다.

  • 기본 CPU 상한이 지정된 네임스페이스에서 실행 중인 컨테이너에는 해당 기본 상한이 자동으로 할당된다. 클러스터 관리자들은 리밋레인지(LimitRange)를 사용해 CPU 상한의 기본 값을 지정할 수 있다.

CPU 상한은 지정했지만 CPU 요청량을 지정하지 않을 경우

만약 CPU 상한은 지정했지만 CPU 요청량을 지정하지 않았다면, 쿠버네티스는 자동으로 상한에 맞는 CPU 요청량을 지정한다. 비슷하게, 컨테이너가 자신의 메모리 상한을 지정했지만 메모리 요청량을 지정하지 않았다면, 쿠버네티스는 자동으로 상한에 맞는 메모리 요청량을 지정한다.

CPU 요청량 및 상한 개념 도입 동기

클러스터에서 실행되는 컨테이너에 CPU 요청량과 상한을 구성하면 클러스터 내 노드들의 가용 가능한 CPU 리소스를 효율적으로 사용할 수 있게 된다. 파드의 CPU 요청량을 낮게 유지하면 파드가 높은 확률로 스케줄링 될 수 있다. CPU 상한이 CPU 요청량보다 크도록 설정한다면 다음 두 가지를 달성할 수 있다.

  • 가용한 CPU 리소스가 있는 경우 파드가 이를 버스트(burst) 하여 사용할 수 있다.
  • 파드가 버스트 중 사용할 수 있는 CPU 리소스 양을 적절히 제한할 수 있다.

정리

네임스페이스 삭제:

kubectl delete namespace cpu-example

다음 내용

앱 개발자들을 위한 문서

클러스터 관리자들을 위한 문서

5 - 파드에 대한 서비스 품질(QoS) 구성

이 페이지는 특정 서비스 품질(QoS) 클래스를 할당하기 위해 어떻게 파드를 구성해야 하는지 보여준다. 쿠버네티스는 QoS 클래스를 사용하여 파드 스케줄링과 축출을 결정한다.

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

버전 확인을 위해서, 다음 커맨드를 실행 kubectl version.

QoS 클래스

쿠버네티스가 파드를 생성할 때, 파드에 다음의 QoS 클래스 중 하나를 할당한다.

  • Guaranteed
  • Burstable
  • BestEffort

네임스페이스 생성

이 연습에서 생성한 리소스가 클러스터의 나머지와 격리되도록 네임스페이스를 생성한다.

kubectl create namespace qos-example

Guaranteed QoS 클래스가 할당되는 파드 생성

파드에 Guaranteed QoS 클래스 할당을 위한 전제 조건은 다음과 같다.

  • 파드 내 모든 컨테이너는 메모리 상한과 메모리 요청량을 가지고 있어야 한다.
  • 파드 내 모든 컨테이너의 메모리 상한이 메모리 요청량과 일치해야 한다.
  • 파드 내 모든 컨테이너는 CPU 상한과 CPU 요청량을 가지고 있어야 한다.
  • 파드 내 모든 컨테이너의 CPU 상한이 CPU 요청량과 일치해야 한다.

이러한 제약은 초기화 컨테이너와 앱 컨테이너 모두에 동일하게 적용된다.

다음은 하나의 컨테이너를 갖는 파드의 구성 파일이다. 해당 컨테이너는 메모리 상한과 메모리 요청량을 갖고 있고, 200MiB로 동일하다. 해당 컨테이너는 CPU 상한과 CPU 요청량을 가지며, 700 milliCPU로 동일하다.

apiVersion: v1
kind: Pod
metadata:
  name: qos-demo
  namespace: qos-example
spec:
  containers:
  - name: qos-demo-ctr
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "700m"
      requests:
        memory: "200Mi"
        cpu: "700m"

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/pods/qos/qos-pod.yaml --namespace=qos-example

파드의 상세 정보를 본다.

kubectl get pod qos-demo --namespace=qos-example --output=yaml

출력 결과는 쿠버네티스가 파드에 Guaranteed QoS 클래스를 부여했음을 보여준다. 또한 파드의 컨테이너가 메모리 요청량과 일치하는 메모리 상한을 가지며, CPU 요청량과 일치하는 CPU 상한을 갖고 있음을 확인할 수 있다.

spec:
  containers:
    ...
    resources:
      limits:
        cpu: 700m
        memory: 200Mi
      requests:
        cpu: 700m
        memory: 200Mi
    ...
status:
  qosClass: Guaranteed

파드를 삭제한다.

kubectl delete pod qos-demo --namespace=qos-example

Burstable QoS 클래스가 할당되는 파드 생성

다음의 경우 파드에 Burstable QoS 클래스가 부여된다.

  • 파드가 Guaranteed QoS 클래스 기준을 만족하지 않는다.
  • 파드 내에서 최소한 하나의 컨테이너가 메모리 또는 CPU 요청량/상한을 가진다.

컨테이너가 하나인 파드의 구성 파일은 다음과 같다. 컨테이너는 200MiB의 메모리 상한과 100MiB의 메모리 요청량을 가진다.

apiVersion: v1
kind: Pod
metadata:
  name: qos-demo-2
  namespace: qos-example
spec:
  containers:
  - name: qos-demo-2-ctr
    image: nginx
    resources:
      limits:
        memory: "200Mi"
      requests:
        memory: "100Mi"

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/pods/qos/qos-pod-2.yaml --namespace=qos-example

파드의 상세 정보를 본다.

kubectl get pod qos-demo-2 --namespace=qos-example --output=yaml

출력 결과는 쿠버네티스가 파드에 Burstable QoS 클래스를 부여했음을 보여준다.

spec:
  containers:
  - image: nginx
    imagePullPolicy: Always
    name: qos-demo-2-ctr
    resources:
      limits:
        memory: 200Mi
      requests:
        memory: 100Mi
  ...
status:
  qosClass: Burstable

파드를 삭제한다.

kubectl delete pod qos-demo-2 --namespace=qos-example

BestEffort QoS 클래스가 할당되는 파드 생성

파드에 BestEffort QoS 클래스를 부여하려면, 파드의 컨테이너에 메모리와 CPU에 대한 상한이나 요청량이 없어야 한다.

다음은 컨테이너가 하나인 파드의 구성 파일이다. 해당 컨테이너는 메모리와 CPU의 상한이나 요청량을 갖지 않는다.

apiVersion: v1
kind: Pod
metadata:
  name: qos-demo-3
  namespace: qos-example
spec:
  containers:
  - name: qos-demo-3-ctr
    image: nginx

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/pods/qos/qos-pod-3.yaml --namespace=qos-example

파드의 상세 정보를 본다.

kubectl get pod qos-demo-3 --namespace=qos-example --output=yaml

출력 결과는 쿠버네티스가 파드에 BestEffort QoS 클래스를 부여했음을 보여준다.

spec:
  containers:
    ...
    resources: {}
  ...
status:
  qosClass: BestEffort

파드를 삭제한다.

kubectl delete pod qos-demo-3 --namespace=qos-example

컨테이너가 두 개인 파드 생성

컨테이너가 두 개인 파드의 구성 파일이다. 한 컨테이너는 200MiB의 메모리 요청량을 지정한다. 다른 컨테이너는 어떤 요청량이나 상한을 지정하지 않는다.

apiVersion: v1
kind: Pod
metadata:
  name: qos-demo-4
  namespace: qos-example
spec:
  containers:

  - name: qos-demo-4-ctr-1
    image: nginx
    resources:
      requests:
        memory: "200Mi"

  - name: qos-demo-4-ctr-2
    image: redis

참고로 이 파드는 Burstable QoS 클래스의 기준을 충족한다. 즉, Guaranteed QoS 클래스에 대한 기준을 충족하지 않으며, 해당 컨테이너 중 하나가 메모리 요청량을 갖는다.

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/pods/qos/qos-pod-4.yaml --namespace=qos-example

파드의 상세 정보를 본다.

kubectl get pod qos-demo-4 --namespace=qos-example --output=yaml

출력 결과는 쿠버네티스가 파드에 Burstable QoS 클래스를 부여했음을 보여준다.

spec:
  containers:
    ...
    name: qos-demo-4-ctr-1
    resources:
      requests:
        memory: 200Mi
    ...
    name: qos-demo-4-ctr-2
    resources: {}
    ...
status:
  qosClass: Burstable

파드를 삭제한다.

kubectl delete pod qos-demo-4 --namespace=qos-example

정리

네임스페이스를 삭제한다.

kubectl delete namespace qos-example

다음 내용

앱 개발자를 위한 문서

클러스터 관리자를 위한 문서

6 - 컨테이너에 확장 리소스 지정

기능 상태: Kubernetes v1.32 [stable]

이 페이지는 컨테이너에 확장 리소스를 지정하는 방법을 보여준다.

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

버전 확인을 위해서, 다음 커맨드를 실행 kubectl version.

이 태스크를 수행하기 전에 노드에 대한 확장 리소스 알리기에서 연습한다. 그러면 노드 중 하나가 동글(dongle) 리소스를 알리도록 구성될 것이다.

파드에 확장 리소스 지정

확장 리소스를 요청하려면 컨테이너 매니페스트에 resources:requests 필드를 포함한다. 확장 리소스는 *.kubernetes.io/ 외부의 모든 도메인으로 정규화된다. 유효한 확장 리소스 이름은 example.com/foo 형식을 갖는다. 여기서 example.com은 조직의 도메인으로 대체하고, foo는 리소스를 설명할 수 있는 이름으로 짓는다.

다음은 컨테이너가 하나 있는 파드의 구성 파일이다.

apiVersion: v1
kind: Pod
metadata:
  name: extended-resource-demo
spec:
  containers:
  - name: extended-resource-demo-ctr
    image: nginx
    resources:
      requests:
        example.com/dongle: 3
      limits:
        example.com/dongle: 3

구성 파일에서 컨테이너가 3개의 동글을 요청하는 것을 알 수 있다.

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/pods/resource/extended-resource-pod.yaml

파드가 실행 중인지 확인한다.

kubectl get pod extended-resource-demo

파드의 상세 정보를 확인한다.

kubectl describe pod extended-resource-demo

출력은 동글 요청을 보여준다.

Limits:
  example.com/dongle: 3
Requests:
  example.com/dongle: 3

두 번째 파드 생성 시도

다음은 컨테이너가 하나 있는 파드의 구성 파일이다. 컨테이너는 두 개의 동글을 요청한다.

apiVersion: v1
kind: Pod
metadata:
  name: extended-resource-demo-2
spec:
  containers:
  - name: extended-resource-demo-2-ctr
    image: nginx
    resources:
      requests:
        example.com/dongle: 2
      limits:
        example.com/dongle: 2

첫 번째 파드가 사용 가능한 4개의 동글 중 3개를 사용했기 때문에 쿠버네티스는 두 개의 동글에 대한 요청을 충족시킬 수 없을 것이다.

파드 생성을 시도한다.

kubectl apply -f https://k8s.io/examples/pods/resource/extended-resource-pod-2.yaml

파드 상세 정보를 확인한다.

kubectl describe pod extended-resource-demo-2

출력은 두 개의 동글을 가용할 수 있는 노드가 없기 때문에 파드를 스케줄할 수 없음을 보여준다.

Conditions:
  Type    Status
  PodScheduled  False
...
Events:
  ...
  ... Warning   FailedScheduling  pod (extended-resource-demo-2) failed to fit in any node
fit failure summary on nodes : Insufficient example.com/dongle (1)

파드 상태를 확인한다.

kubectl get pod extended-resource-demo-2

출력은 파드가 생성됐지만 노드에서 실행되도록 스케줄되지 않았음을 보여준다. 파드는 Pending 상태이다.

NAME                       READY     STATUS    RESTARTS   AGE
extended-resource-demo-2   0/1       Pending   0          6m

정리

연습을 위해 생성한 파드를 삭제한다.

kubectl delete pod extended-resource-demo
kubectl delete pod extended-resource-demo-2

다음 내용

애플리케션 개발자들을 위한 문서

클러스터 관리자들을 위한 문서

7 - 스토리지의 볼륨을 사용하는 파드 구성

이 페이지는 스토리지의 볼륨을 사용하는 파드를 구성하는 방법을 설명한다.

컨테이너 파일 시스템은 컨테이너가 살아있는 동안만 존재한다. 따라서 컨테이너가 종료되고 재시작할 때, 파일 시스템 변경사항이 손실된다. 컨테이너와 독립적이며 보다 일관된 스토리지를 위해 사용자는 볼륨을 사용할 수 있다. 이것은 레디스(Redis)와 같은 키-값 저장소나 데이터베이스와 같은 스테이트풀 애플리케이션에 매우 중요하다.

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

버전 확인을 위해서, 다음 커맨드를 실행 kubectl version.

파드에 볼륨 구성

이 연습에서는 하나의 컨테이너를 실행하는 파드를 생성한다. 이 파드는 컨테이너가 종료되고, 재시작 하더라도 파드의 수명동안 지속되는 emptyDir 유형의 볼륨이 있다. 파드의 구성 파일은 다음과 같다.

apiVersion: v1
kind: Pod
metadata:
  name: redis
spec:
  containers:
  - name: redis
    image: redis
    volumeMounts:
    - name: redis-storage
      mountPath: /data/redis
  volumes:
  - name: redis-storage
    emptyDir: {}
  1. 파드 생성

    kubectl apply -f https://k8s.io/examples/pods/storage/redis.yaml
    
  2. 파드의 컨테이너가 Running 중인지 확인하고, 파드의 변경사항을 지켜본다.

    kubectl get pod redis --watch
    

    출력은 이와 유사하다.

    NAME      READY     STATUS    RESTARTS   AGE
    redis     1/1       Running   0          13s
    
  3. 다른 터미널에서 실행 중인 컨테이너의 셸을 획득한다.

    kubectl exec -it redis -- /bin/bash
    
  4. 셸에서 /data/redis 로 이동하고, 파일을 생성한다.

    root@redis:/data# cd /data/redis/
    root@redis:/data/redis# echo Hello > test-file
    
  5. 셸에서 실행 중인 프로세스 목록을 확인한다.

    root@redis:/data/redis# apt-get update
    root@redis:/data/redis# apt-get install procps
    root@redis:/data/redis# ps aux
    

    출력은 이와 유사하다.

    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    redis        1  0.1  0.1  33308  3828 ?        Ssl  00:46   0:00 redis-server *:6379
    root        12  0.0  0.0  20228  3020 ?        Ss   00:47   0:00 /bin/bash
    root        15  0.0  0.0  17500  2072 ?        R+   00:48   0:00 ps aux
    
  6. 셸에서 Redis 프로세스를 강제종료(kill)한다.

    root@redis:/data/redis# kill <pid>
    

    여기서 <pid>는 Redis 프로세스 ID(PID) 이다.

  7. 원래 터미널에서, Redis 파드의 변경을 지켜본다. 결국, 다음과 유사한 것을 보게 될 것이다.

    NAME      READY     STATUS     RESTARTS   AGE
    redis     1/1       Running    0          13s
    redis     0/1       Completed  0         6m
    redis     1/1       Running    1         6m
    

이때, 컨테이너는 종료되고 재시작된다. 이는 Redis 파드의 restartPolicyAlways 이기 때문이다.

  1. 재시작된 컨테이너의 셸을 획득한다.

    kubectl exec -it redis -- /bin/bash
    
  2. 셸에서 /data/redis 로 이동하고, test-file 이 여전히 존재하는지 확인한다.

    root@redis:/data/redis# cd /data/redis/
    root@redis:/data/redis# ls
    test-file
    
  3. 이 연습을 위해 생성한 파드를 삭제한다.

    kubectl delete pod redis
    

다음 내용

  • 볼륨을 참고한다.

  • 파드을 참고한다.

  • 쿠버네티스는 emptyDir 이 제공하는 로컬 디스크 스토리지뿐만 아니라, 중요한 데이터에 선호하는 GCE의 PD, EC2의 EBS를 포함해서 네트워크 연결 스토리지(NAS) 솔루션을 지원하며, 노드의 디바이스 마운트, 언마운트와 같은 세부사항을 처리한다. 자세한 내용은 볼륨을 참고한다.

8 - 스토리지로 퍼시스턴트볼륨(PersistentVolume)을 사용하도록 파드 설정하기

이 페이지는 스토리지에 대해 퍼시스턴트볼륨클레임(PersistentVolumeClaim)을 사용하도록 파드를 설정하는 방법을 보여준다. 과정의 요약은 다음과 같다.

  1. 클러스터 관리자로서, 물리적 스토리지와 연결되는 퍼시스턴트볼륨을 생성한다. 볼륨을 특정 파드와 연결하지 않는다.

  2. 그 다음 개발자 / 클러스터 사용자의 역할로서, 적합한 퍼시스턴트볼륨에 자동으로 바인딩되는 퍼시스턴트볼륨클레임을 생성한다.

  3. 스토리지에 대해 위의 퍼시스턴트볼륨클레임을 사용하는 파드를 생성한다.

시작하기 전에

  • 사용자는 노드가 단 하나만 있는 쿠버네티스 클러스터가 필요하고, kubectl 커맨드라인 툴이 사용자의 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 만약 사용자가 아직 단일 노드 클러스터를 가지고 있지 않다면, Minikube를 사용하여 클러스터 하나를 생성할 수 있다.

  • 퍼시스턴트 볼륨의 관련 자료에 익숙해지도록 한다.

사용자 노드에 index.html 파일 생성하기

사용자 클러스터의 단일 노드에 연결되는 셸을 연다. 셸을 여는 방법은 클러스터 설정에 따라 달라진다. 예를 들어 Minikube를 사용하는 경우, minikube ssh 명령어를 입력하여 노드로 연결되는 셸을 열 수 있다.

해당 노드의 셸에서 /mnt/data 디렉터리를 생성한다.

# 사용자 노드에서 슈퍼유저로 명령을 수행하기 위하여
# "sudo"를 사용한다고 가정한다
sudo mkdir /mnt/data

/mnt/data 디렉터리에서 index.html 파일을 생성한다.

# 이번에도 사용자 노드에서 슈퍼유저로 명령을 수행하기 위하여
# "sudo"를 사용한다고 가정한다
sudo sh -c "echo 'Hello from Kubernetes storage' > /mnt/data/index.html"

index.html 파일이 존재하는지 테스트한다.

cat /mnt/data/index.html

결과는 다음과 같다.

Hello from Kubernetes storage

이제 사용자 노드에서 셸을 종료해도 된다.

퍼시스턴트볼륨 생성하기

이 예제에서, 사용자는 hostPath 퍼시스턴트볼륨을 생성한다. 쿠버네티스는 단일 노드에서의 개발과 테스트를 위해 hostPath를 지원한다. hostPath 퍼시스턴트볼륨은 네트워크로 연결된 스토리지를 모방하기 위해, 노드의 파일이나 디렉터리를 사용한다.

운영 클러스터에서, 사용자가 hostPath를 사용하지는 않는다. 대신, 클러스터 관리자는 Google Compute Engine 영구 디스크, NFS 공유 또는 Amazone Elastic Block Store 볼륨과 같은 네트워크 자원을 프로비저닝한다. 클러스터 관리자는 스토리지클래스(StorageClasses)를 사용하여 동적 프로비저닝을 설정할 수도 있다.

hostPath 퍼시스턴트볼륨의 설정 파일은 아래와 같다.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: task-pv-volume
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt/data"

설정 파일에 클러스터 노드의 /mnt/data 에 볼륨이 있다고 지정한다. 또한 설정에서 볼륨 크기를 10 기가바이트로 지정하고 단일 노드가 읽기-쓰기 모드로 볼륨을 마운트할 수 있는 ReadWriteOnce 접근 모드를 지정한다. 여기서는 퍼시스턴트볼륨클레임의 스토리지클래스 이름manual 로 정의하며, 퍼시스턴트볼륨클레임의 요청을 이 퍼시스턴트볼륨에 바인딩하는 데 사용한다.

퍼시스턴트볼륨을 생성한다.

kubectl apply -f https://k8s.io/examples/pods/storage/pv-volume.yaml

퍼시스턴트볼륨에 대한 정보를 조회한다.

kubectl get pv task-pv-volume

결과는 퍼시스턴트볼륨의 STATUSAvailable 임을 보여준다. 이는 아직 퍼시스턴트볼륨클레임이 바인딩되지 않았다는 것을 의미한다.

NAME             CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS      CLAIM     STORAGECLASS   REASON    AGE
task-pv-volume   10Gi       RWO           Retain          Available             manual                   4s

퍼시스턴트볼륨클레임 생성하기

다음 단계는 퍼시스턴트볼륨클레임을 생성하는 단계이다. 파드는 퍼시스턴트볼륨클레임을 사용하여 물리적인 스토리지를 요청한다. 이 예제에서, 사용자는 적어도 하나 이상의 노드에 대해 읽기-쓰기 접근을 지원하며 최소 3 기가바이트의 볼륨을 요청하는 퍼시스턴트볼륨클레임을 생성한다.

퍼시스턴트볼륨클레임에 대한 설정 파일은 다음과 같다.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: task-pv-claim
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi

퍼시스턴트볼륨클레임을 생성한다.

kubectl apply -f https://k8s.io/examples/pods/storage/pv-claim.yaml

사용자가 퍼시스턴트볼륨클레임을 생성한 후에, 쿠버네티스 컨트롤 플레인은 클레임의 요구사항을 만족하는 퍼시스턴트볼륨을 찾는다. 컨트롤 플레인이 동일한 스토리지클래스를 갖는 적절한 퍼시스턴트볼륨을 찾으면, 볼륨에 클레임을 바인딩한다.

퍼시스턴트볼륨을 다시 확인한다.

kubectl get pv task-pv-volume

이제 결과는 STATUSBound 임을 보여준다.

NAME             CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM                   STORAGECLASS   REASON    AGE
task-pv-volume   10Gi       RWO           Retain          Bound     default/task-pv-claim   manual                   2m

퍼시스턴트볼륨클레임을 확인한다.

kubectl get pvc task-pv-claim

결과는 퍼시스턴트볼륨클레임이 사용자의 퍼시스턴트볼륨인 task-pv-volume 에 바인딩되어 있음을 보여준다.

NAME            STATUS    VOLUME           CAPACITY   ACCESSMODES   STORAGECLASS   AGE
task-pv-claim   Bound     task-pv-volume   10Gi       RWO           manual         30s

파드 생성하기

다음 단계는 볼륨으로 퍼시스턴트볼륨클레임을 사용하는 파드를 만드는 단계이다.

파드에 대한 설정 파일은 다음과 같다.

apiVersion: v1
kind: Pod
metadata:
  name: task-pv-pod
spec:
  volumes:
    - name: task-pv-storage
      persistentVolumeClaim:
        claimName: task-pv-claim
  containers:
    - name: task-pv-container
      image: nginx
      ports:
        - containerPort: 80
          name: "http-server"
      volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: task-pv-storage


파드의 설정 파일은 퍼시스턴트볼륨클레임을 지정하지만, 퍼시스턴트볼륨을 지정하지는 않는다는 것을 유념하자. 파드의 관점에서 볼때, 클레임은 볼륨이다.

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/pods/storage/pv-pod.yaml

파드의 컨테이너가 실행 중임을 확인한다.

kubectl get pod task-pv-pod

사용자 파드에서 구동되고 있는 컨테이너에 셸로 접근한다.

kubectl exec -it task-pv-pod -- /bin/bash

사용자의 셸에서, nginx가 hostPath 볼륨으로부터 index.html 파일을 제공하는지 확인한다.

# 이전 단계에서 "kubectl exec" 명령을 실행한 root 셸 안에서 
# 다음의 3개 명령을 실행해야 한다.
apt update
apt install curl
curl http://localhost/

결과는 hostPath 볼륨에 있는 index.html 파일에 사용자가 작성한 텍스트를 보여준다.

Hello from Kubernetes storage

만약 사용자가 위와 같은 메시지를 확인하면, 파드가 퍼시스턴트볼륨클레임의 스토리지를 사용하도록 성공적으로 설정한 것이다.

정리하기

파드, 퍼시스턴트볼륨클레임, 퍼시스턴트볼륨을 삭제한다.

kubectl delete pod task-pv-pod
kubectl delete pvc task-pv-claim
kubectl delete pv task-pv-volume

만약 클러스터의 노드에 대한 셸이 열려져 있지 않은 경우, 이전과 동일한 방식으로 새로운 셸을 연다.

사용자 노드의 셸에서, 생성한 파일과 디렉터리를 제거한다.

# 사용자 노드에서 슈퍼유저로 명령을 수행하기 위하여
# "sudo"를 사용한다고 가정한다
sudo rm /mnt/data/index.html
sudo rmdir /mnt/data

이제 사용자 노드에서 셸을 종료해도 된다.

하나의 퍼시스턴트볼륨을 두 경로에 마운트하기


apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  containers:
    - name: test
      image: nginx
      volumeMounts:
        # a mount for site-data
        - name: config
          mountPath: /usr/share/nginx/html
          subPath: html
        # another mount for nginx config
        - name: config
          mountPath: /etc/nginx/nginx.conf
          subPath: nginx.conf
  volumes:
    - name: config
      persistentVolumeClaim:
        claimName: test-nfs-claim

하나의 퍼시스턴트볼륨을 nginx 컨테이너의 두 경로에 마운트할 수 있다.

/usr/share/nginx/html - 정적 웹사이트 용 /etc/nginx/nginx.conf - 기본 환경 설정 용

접근 제어

그룹 ID(GID)로 설정된 스토리지는 동일한 GID를 사용하는 파드에서만 쓰기 작업을 허용한다. GID가 일치하지 않거나 누락되었을 경우 권한 거부 오류가 발생한다. 사용자와의 조정 필요성을 줄이기 위하여 관리자는 퍼시스턴트 볼륨에 GID로 어노테이션을 달 수 있다. 그 뒤에, 퍼시스턴트볼륨을 사용하는 모든 파드에 대하여 GID가 자동으로 추가된다.

다음과 같이 pv.beta.kubernetes.io/gid 어노테이션을 사용한다.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv1
  annotations:
    pv.beta.kubernetes.io/gid: "1234"

파드가 GID 어노테이션이 있는 퍼시스턴트볼륨을 사용하면, 어노테이션으로 달린 GID가 파드의 보안 컨텍스트에 지정된 GID와 동일한 방식으로 파드의 모든 컨테이너에 적용된다. 파드의 명세 혹은 퍼시스턴트볼륨의 어노테이션으로부터 생성된 모든 GID는, 각 컨테이너에서 실행되는 첫 번째 프로세스에 적용된다.

다음 내용

Reference

9 - 파드의 스토리지에 프로젝티드 볼륨(Projected Volume)을 사용하도록 구성

이 페이지는 프로젝티드 볼륨을 사용하여 여러 기존 볼륨 소스들을 동일한 디렉터리에 마운트하는 방법을 보여준다. 현재 시크릿(secret), 컨피그맵(configMap), downwardAPI, 그리고 서비스어카운트토큰(serviceAccountToken) 볼륨이 프로젝티드(projected)될 수 있다.

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

버전 확인을 위해서, 다음 커맨드를 실행 kubectl version.

파드에 프로젝티드 볼륨을 구성

이 연습에서는 로컬 파일에 유저네임과 패스워드를 시크릿으로 생성한다. 이후 하나의 컨테이너를 포함한 파드를 생성하는 데, 이 때 시크릿을 동일한 공유 디렉터리에 마운트하기 위해 프로젝티드 볼륨을 사용한다.

다음은 파드의 구성 파일이다.

apiVersion: v1
kind: Pod
metadata:
  name: test-projected-volume
spec:
  containers:
  - name: test-projected-volume
    image: busybox:1.28
    args:
    - sleep
    - "86400"
    volumeMounts:
    - name: all-in-one
      mountPath: "/projected-volume"
      readOnly: true
  volumes:
  - name: all-in-one
    projected:
      sources:
      - secret:
          name: user
      - secret:
          name: pass
  1. 시크릿을 생성한다.

    # 유저네임과 패스워드를 포함한 파일들을 생성한다.
    echo -n "admin" > ./username.txt
    echo -n "1f2d1e2e67df" > ./password.txt
    
    # 생성한 파일들을 시크릿으로 패키징한다.
    kubectl create secret generic user --from-file=./username.txt
    kubectl create secret generic pass --from-file=./password.txt
    
  2. 파드를 생성한다.

    kubectl apply -f https://k8s.io/examples/pods/storage/projected.yaml
    
  3. 파드의 컨테이너가 정상적으로 실행되는지 확인한 다음, 파드에 대한 변경 사항을 확인한다.

    kubectl get --watch pod test-projected-volume
    

    The output looks like this:

    NAME                    READY     STATUS    RESTARTS   AGE
    test-projected-volume   1/1       Running   0          14s
    
  4. 다른 터미널을 이용해, 실행 중인 컨테이너에 대한 셸을 가져온다.

    kubectl exec -it test-projected-volume -- /bin/sh
    
  5. 셸에서 projected-volume 디렉터리에 프로젝티드 소스들이 포함되어 있는지 확인한다.

    ls /projected-volume/
    

정리하기

파드와 시크릿을 제거한다.

kubectl delete pod test-projected-volume
kubectl delete secret user pass

다음 내용

10 - 프라이빗 레지스트리에서 이미지 받아오기

이 페이지는 프라이빗 컨테이너 레지스트리나 리포지터리로부터 이미지를 받아오기 위해 시크릿(Secret)을 사용하는 파드를 생성하는 방법을 보여준다. 현재 많은 곳에서 프라이빗 레지스트리가 사용되고 있다. 여기서는 예시 레지스트리로 Docker Hub을 사용한다.

시작하기 전에

  • 쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

  • 이 실습을 수행하기 위해, docker 명령줄 도구와 도커 ID 및 비밀번호가 필요하다.

  • 다른 프라이빗 컨테이너 레지스트리를 사용하는 경우, 해당 레지스트리를 위한 명령줄 도구 및 레지스트리 로그인 정보가 필요하다.

도커 허브 로그인

노트북에 프라이빗 이미지를 받아오기 위하여 레지스트리 인증을 필수로 수행해야 한다.

docker 도구를 사용하여 도커 허브에 로그인한다. 자세한 정보는 도커 ID 계정로그 인 섹션을 참조한다.

docker login

프롬프트가 나타나면, 도커 ID를 입력한 다음, 사용하려는 자격증명(액세스 토큰, 또는 도커 ID의 비밀번호)을 입력한다.

로그인 프로세스를 수행하면 권한 토큰 정보를 가지고 있는 config.json 파일이 생성되거나 업데이트된다. 쿠버네티스가 이 파일을 어떻게 해석하는지 참고한다.

config.json 파일을 확인하자.

cat ~/.docker/config.json

하단과 유사한 결과를 확인할 수 있다.

{
    "auths": {
        "https://index.docker.io/v1/": {
            "auth": "c3R...zE2"
        }
    }
}

기존의 자격 증명을 기반으로 시크릿 생성하기

쿠버네티스 클러스터는 프라이빗 이미지를 받아올 때, 컨테이너 레지스트리에 인증하기 위하여 kubernetes.io/dockerconfigjson 타입의 시크릿을 사용한다.

만약 이미 docker login 을 수행하였다면, 이 때 생성된 자격 증명을 쿠버네티스 클러스터로 복사할 수 있다.

kubectl create secret generic regcred \
    --from-file=.dockerconfigjson=<path/to/.docker/config.json> \
    --type=kubernetes.io/dockerconfigjson

오브젝트에 대한 더 세밀한 제어(새로운 시크릿에 대한 네임스페이스나 레이블을 지정하는 등)가 필요할 경우, 시크릿을 사용자 정의한 후에 저장할 수도 있다. 다음을 확인하자.

  • 데이터 항목의 이름을 .dockerconfigjson 으로 설정한다
  • 도커 구성 파일을 base64로 인코딩하고 그 문자열을 data[".dockerconfigjson"] 필드에 자르지 않고 한 줄로 이어서 붙여넣는다
  • typekubernetes.io/dockerconfigjson 으로 설정한다

예:

apiVersion: v1
kind: Secret
metadata:
  name: myregistrykey
  namespace: awesomeapps
data:
  .dockerconfigjson: UmVhbGx5IHJlYWxseSByZWVlZWVlZWVlZWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGx5eXl5eXl5eXl5eXl5eXl5eXl5eSBsbGxsbGxsbGxsbGxsbG9vb29vb29vb29vb29vb29vb29vb29vb29vb25ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubmdnZ2dnZ2dnZ2dnZ2dnZ2dnZ2cgYXV0aCBrZXlzCg==
type: kubernetes.io/dockerconfigjson

만약 error: no objects passed to create 메세지가 출력될 경우, base64로 인코딩된 문자열이 유효하지 않음을 의미한다. 또한 Secret "myregistrykey" is invalid: data[.dockerconfigjson]: invalid value ... 메세지가 출력될 경우, base64로 인코딩된 문자열이 정상적으로 디코딩되었으나, .docker/config.json 파일로 파싱되지 못한 것을 의미한다.

커맨드 라인에서 자격 증명을 통하여 시크릿 생성하기

regcred 라는 이름의 시크릿을 생성하자.

kubectl create secret docker-registry regcred --docker-server=<your-registry-server> --docker-username=<your-name> --docker-password=<your-pword> --docker-email=<your-email>

아래의 각 항목에 대한 설명을 참고한다.

  • <your-registry-server> 은 프라이빗 도커 저장소의 FQDN 주소이다. 도커허브(DockerHub)는 https://index.docker.io/v1/ 를 사용한다.
  • <your-name> 은 도커 사용자의 계정이다.
  • <your-pword> 은 도커 사용자의 비밀번호이다.
  • <your-email> 은 도커 사용자의 이메일 주소이다.

이를 통해 regcred 라는 시크릿으로 클러스터 내에서 도커 자격 증명을 생성했다.

시크릿 regcred 검증하기

방금 생성한 regcred 시크릿의 내용을 확인하기 위하여, YAML 형식으로 시크릿을 확인하자.

kubectl get secret regcred --output=yaml

결과는 다음과 같다.

apiVersion: v1
kind: Secret
metadata:
  ...
  name: regcred
  ...
data:
  .dockerconfigjson: eyJodHRwczovL2luZGV4L ... J0QUl6RTIifX0=
type: kubernetes.io/dockerconfigjson

.dockerconfigjson 필드의 값은 도커 자격 증명의 base64 인코딩 결과이다.

.dockerconfigjson 필드의 값을 확인하기 위하여, 시크릿 데이터를 읽을 수 있는 형식으로 변경한다.

kubectl get secret regcred --output="jsonpath={.data.\.dockerconfigjson}" | base64 --decode

결과는 다음과 같다.

{"auths":{"your.private.registry.example.com":{"username":"janedoe","password":"xxxxxxxxxxx","email":"jdoe@example.com","auth":"c3R...zE2"}}}

auth 필드의 값을 확인하기 위하여, base64로 인코딩된 데이터를 읽을 수 있는 형식으로 변경한다.

echo "c3R...zE2" | base64 --decode

결과로, 사용자 이름과 비밀번호가 : 로 연결되어 아래와 같이 표현된다.

janedoe:xxxxxxxxxxx

참고로 시크릿 데이터에는 사용자의 로컬에 있는 ~/.docker/config.json 파일과 유사한 인증 토큰이 포함되어 있다.

이를 통해 regcred 라는 시크릿으로 클러스터 내에서 도커 자격 증명을 생성했다.

시크릿을 사용하는 파드 생성하기

다음은 regcred 에 있는 도커 자격 증명에 접근해야 하는 예제 파드의 매니페스트이다.

apiVersion: v1
kind: Pod
metadata:
  name: private-reg
spec:
  containers:
  - name: private-reg-container
    image: <your-private-image>
  imagePullSecrets:
  - name: regcred

위 파일을 컴퓨터에 다운로드한다.

curl -L -o my-private-reg-pod.yaml https://k8s.io/examples/pods/private-reg-pod.yaml

my-private-reg-pod.yaml 파일 안에서, <your-private-image> 값을 다음과 같은 프라이빗 저장소 안의 이미지 경로로 변경한다.

your.private.registry.example.com/janedoe/jdoe-private:v1

프라이빗 저장소에서 이미지를 받아오기 위하여, 쿠버네티스에서 자격 증명이 필요하다. 구성 파일의 imagePullSecrets 필드를 통해 쿠버네티스가 regcred 라는 시크릿으로부터 자격 증명을 가져올 수 있다.

시크릿을 사용해서 파드를 생성하고, 파드가 실행되는지 확인하자.

kubectl apply -f my-private-reg-pod.yaml
kubectl get pod private-reg

다음 내용

11 - 노드 어피니티를 사용해 노드에 파드 할당

이 문서는 쿠버네티스 클러스터의 특정 노드에 노드 어피니티를 사용해 쿠버네티스 파드를 할당하는 방법을 설명한다.

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

쿠버네티스 서버의 버전은 다음과 같거나 더 높아야 함. 버전: v1.10. 버전 확인을 위해서, 다음 커맨드를 실행 kubectl version.

노드에 레이블 추가

  1. 클러스터의 노드를 레이블과 함께 나열하자.

    kubectl get nodes --show-labels
    

    결과는 아래와 같다.

    NAME      STATUS    ROLES    AGE     VERSION        LABELS
    worker0   Ready     <none>   1d      v1.13.0        ...,kubernetes.io/hostname=worker0
    worker1   Ready     <none>   1d      v1.13.0        ...,kubernetes.io/hostname=worker1
    worker2   Ready     <none>   1d      v1.13.0        ...,kubernetes.io/hostname=worker2
    
  2. 노드 한 개를 선택하고, 레이블을 추가하자.

    kubectl label nodes <your-node-name> disktype=ssd
    

    <your-node-name> 는 선택한 노드의 이름이다.

  3. 선택한 노드가 disktype=ssd 레이블을 갖고 있는지 확인하자.

    kubectl get nodes --show-labels
    

    결과는 아래와 같다.

    NAME      STATUS    ROLES    AGE     VERSION        LABELS
    worker0   Ready     <none>   1d      v1.13.0        ...,disktype=ssd,kubernetes.io/hostname=worker0
    worker1   Ready     <none>   1d      v1.13.0        ...,kubernetes.io/hostname=worker1
    worker2   Ready     <none>   1d      v1.13.0        ...,kubernetes.io/hostname=worker2
    

    위의 결과에서, worker0 노드에 disktype=ssd 레이블이 있는 것을 확인할 수 있다.

필수적인 노드 어피니티를 사용해 파드 스케줄하기

이 매니페스트는 disktype: ssd 라는 requiredDuringSchedulingIgnoredDuringExecution 노드 어피니티를 가진 파드를 설명한다. 파드가 disktype=ssd 레이블이 있는 노드에만 스케줄될 것이라는 것을 의미한다.

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd            
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  1. 매니페스트를 적용하여 선택한 노드에 스케줄된 파드를 생성한다.

    kubectl apply -f https://k8s.io/examples/pods/pod-nginx-required-affinity.yaml
    
  2. 파드가 선택한 노드에서 실행 중인지 확인하자.

    kubectl get pods --output=wide
    

    결과는 아래와 같다.

    NAME     READY     STATUS    RESTARTS   AGE    IP           NODE
    nginx    1/1       Running   0          13s    10.200.0.4   worker0
    

선호하는 노드 어피니티를 사용해 파드 스케줄하기

이 매니페스트는 disktype: ssd 라는 preferredDuringSchedulingIgnoredDuringExecution 노드 어피니티를 가진 파드를 설명한다. 파드가 disktype=ssd 레이블이 있는 노드를 선호한다는 것을 의미한다.

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd          
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  1. 매니페스트를 적용하여 선택한 노드에 스케줄된 파드를 생성한다.

    kubectl apply -f https://k8s.io/examples/pods/pod-nginx-preferred-affinity.yaml
    
  2. 파드가 선택한 노드에서 실행 중인지 확인하자.

    kubectl get pods --output=wide
    

    결과는 아래와 같다.

    NAME     READY     STATUS    RESTARTS   AGE    IP           NODE
    nginx    1/1       Running   0          13s    10.200.0.4   worker0
    

다음 내용

노드 어피니티에 대해 더 알아보기.

12 - 노드에 파드 할당

이 문서는 쿠버네티스 클러스터의 특정 노드에 쿠버네티스 파드를 할당하는 방법을 설명한다.

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

버전 확인을 위해서, 다음 커맨드를 실행 kubectl version.

노드에 레이블 추가

  1. 클러스터의 노드를 레이블과 함께 나열하자.

    kubectl get nodes --show-labels
    

    결과는 아래와 같다.

    NAME      STATUS    ROLES    AGE     VERSION        LABELS
    worker0   Ready     <none>   1d      v1.13.0        ...,kubernetes.io/hostname=worker0
    worker1   Ready     <none>   1d      v1.13.0        ...,kubernetes.io/hostname=worker1
    worker2   Ready     <none>   1d      v1.13.0        ...,kubernetes.io/hostname=worker2
    
  2. 노드 한 개를 선택하고, 레이블을 추가하자.

    kubectl label nodes <your-node-name> disktype=ssd
    

    <your-node-name>는 선택한 노드의 이름이다.

  3. 선택한 노드가 disktype=ssd 레이블을 갖고 있는지 확인하자.

    kubectl get nodes --show-labels
    

    결과는 아래와 같다.

    NAME      STATUS    ROLES    AGE     VERSION        LABELS
    worker0   Ready     <none>   1d      v1.13.0        ...,disktype=ssd,kubernetes.io/hostname=worker0
    worker1   Ready     <none>   1d      v1.13.0        ...,kubernetes.io/hostname=worker1
    worker2   Ready     <none>   1d      v1.13.0        ...,kubernetes.io/hostname=worker2
    

    위의 결과에서, worker0 노드에 disktype=ssd 레이블이 있는 것을 확인할 수 있다.

선택한 노드에 스케줄되도록 파드 생성하기

이 파드 구성 파일은 disktype: ssd라는 선택하는 노드 셀렉터를 가진 파드를 설명한다. 즉, disktype=ssd 레이블이 있는 노드에 파드가 스케줄될 것이라는 것을 의미한다.

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
    disktype: ssd
  1. 구성 파일을 사용해서 선택한 노드로 스케줄되도록 파드를 생성하자.

    kubectl apply -f https://k8s.io/examples/pods/pod-nginx.yaml
    
  2. 파드가 선택한 노드에서 실행 중인지 확인하자.

    kubectl get pods --output=wide
    

    결과는 아래와 같다.

    NAME     READY     STATUS    RESTARTS   AGE    IP           NODE
    nginx    1/1       Running   0          13s    10.200.0.4   worker0
    

특정 노드에 스케줄되도록 파드 생성하기

nodeName 설정을 통해 특정 노드로 파드를 배포할 수 있다.

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  nodeName: foo-node # 특정 노드에 파드 스케줄
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent

설정 파일을 사용해 foo-node 노드에 파드를 스케줄되도록 만들어 보자.

다음 내용

13 - 초기화 컨테이너에 대한 구성

이 페이지는 애플리케이션 실행 전에 파드를 초기화하기 위해 어떻게 초기화 컨테이너를 구성해야 하는지 보여준다.

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

버전 확인을 위해서, 다음 커맨드를 실행 kubectl version.

초기화 컨테이너를 갖는 파드 생성

이 연습에서 하나의 애플리케이션 컨테이너와 하나의 초기화 컨테이너를 갖는 파드를 생성한다. 초기화 컨테이너는 애플리케이션 시작 전에 실행을 종료한다.

아래는 해당 파드의 구성 파일이다.

apiVersion: v1
kind: Pod
metadata:
  name: init-demo
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containerPort: 80
    volumeMounts:
    - name: workdir
      mountPath: /usr/share/nginx/html
  # 이 컨테이너들은 파드 초기화 중에 실행된다.
  initContainers:
  - name: install
    image: busybox:1.28
    command:
    - wget
    - "-O"
    - "/work-dir/index.html"
    - http://info.cern.ch
    volumeMounts:
    - name: workdir
      mountPath: "/work-dir"
  dnsPolicy: Default
  volumes:
  - name: workdir
    emptyDir: {}

이 구성 파일에서, 파드가 가진 볼륨을 초기화 컨테이너와 애플리케이션 컨테이너가 공유하는 것을 볼 수 있다.

초기화 컨테이너는 공유된 볼륨을 /work-dir 에 마운트하고, 애플리케이션 컨테이너는 공유된 볼륨을 /usr/share/nginx/html 에 마운트한다. 초기화 컨테이너는 다음 명령을 실행 후 종료한다.

wget -O /work-dir/index.html http://info.cern.ch

초기화 컨테이너는 nginx 서버의 루트 디렉터리 내 index.html 파일을 저장한다.

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/pods/init-containers.yaml

nginx 컨테이너가 실행 중인지 확인한다.

kubectl get pod init-demo

출력 결과는 nginx 컨테이너가 실행 중임을 보여준다.

NAME        READY     STATUS    RESTARTS   AGE
init-demo   1/1       Running   0          1m

init-demo 파드 내 실행 중인 nginx 컨테이너의 셸을 실행한다.

kubectl exec -it init-demo -- /bin/bash

셸에서 GET 요청을 nginx 서버로 전송한다.

root@nginx:~# apt-get update
root@nginx:~# apt-get install curl
root@nginx:~# curl localhost

출력 결과는 nginx가 초기화 컨테이너에 의해 저장된 웹 페이지를 제공하고 있음을 보여준다.

<html><head></head><body><header>
<title>http://info.cern.ch</title>
</header>

<h1>http://info.cern.ch - home of the first website</h1>
  ...
  <li><a href="http://info.cern.ch/hypertext/WWW/TheProject.html">Browse the first website</a></li>
  ...

다음 내용

14 - 파드에 유저 네임스페이스 사용하기

기능 상태: Kubernetes v1.25 [alpha]

이 페이지는 스테이트리스(stateless) 파드에 유저 네임스페이스를 구성하는 방법을 다룬다. 이를 통해 컨테이너 내부에서 실행 중인 유저를 호스트의 유저로부터 분리할 수 있다.

컨테이너에서 루트로 실행되는 프로세스는 호스트에서 다른(루트가 아닌) 유저로 실행할 수 있다. 즉, 프로세스는 유저 네임스페이스 내부의 작업에 대한 모든 권한을 갖지만 네임스페이스 외부의 작업에 대해서는 권한이 없다.

이 기능을 사용하여 손상된 컨테이너가 호스트 또는 동일한 노드의 다른 파드에 미칠 피해를 줄일 수 있다. 유저 네임스페이스를 이용하면, HIGH 또는 CRITICAL 로 분류되는 여러 보안 취약점을 보완할 수 있다. 유저 네임스페이스는 향후 발생할 수 있는 여러 취약점도 완화시킬 것으로 예상된다.

유저 네임스페이스를 사용하지 않고 루트로 실행하는 컨테이너는 컨테이너 브레이크아웃(breakout)이 발생하면 노드의 루트 권한을 갖는다. 그리고 컨테이너에 어떤 기능이 부여되어 있다면 해당 기능은 호스트에서도 유효하다. 유저 네임스페이스를 이용한다면 전혀 해당되지 않는 내용이다.

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

쿠버네티스 서버의 버전은 다음과 같거나 더 높아야 함. 버전: v1.25. 버전 확인을 위해서, 다음 커맨드를 실행 kubectl version.

  • 노드의 운영체제는 리눅스를 사용한다.
  • 호스트에서 커맨드를 exec 할 수 있어야 한다.
  • 파드 내부로 exec 할 수 있어야 한다.
  • 기능 게이트 'UserNamespacesStatelessPodsSupport'를 활성화해야 한다.

추가적으로, 쿠버네티스 스테이트리스(stateless) 파드에서 이 기능을 사용하려면 컨테이너 런타임에서 지원이 필요하다.

  • CRI-O: v1.25는 유저 네임스페이스를 지원한다.

컨테이너 런타임이 유저 네임스페이스를 지원하지 않으면, 새 pod.spec 필드는 별다른 경고 없이 무시되고 파드는 유저 네임스페이스 없이 생성된다 는 사실을 명심한다.

유저 네임스페이스를 사용하는 파드를 동작시키기

스테이트리스 파드의 유저 네임스페이스는 .spechostUsers 필드를 false로 설정하여 사용할 수 있다. 다음은 예시이다.

apiVersion: v1
kind: Pod
metadata:
  name: userns
spec:
  hostUsers: false
  containers:
  - name: shell
    command: ["sleep", "infinity"]
    image: debian
  1. 클러스터에 파드를 생성한다.

    kubectl apply -f https://k8s.io/examples/pods/user-namespaces-stateless.yaml
    
  2. 컨테이너에 연결하고 readlink /proc/self/ns/user를 실행한다.

    kubectl attach -it userns bash
    

그리고 명령을 실행한다. 결과는 다음과 유사하다.

readlink /proc/self/ns/user
user:[4026531837]
cat /proc/self/uid_map
0          0 4294967295

그런 다음 호스트에서 셸을 열고 동일한 명령을 실행한다.

결과는 분명 다를 것이다. 이는 호스트와 파드가 다른 유저 네임스페이스를 사용하고 있음을 의미한다. 유저 네임스페이스를 따로 만들지 않으면 호스트와 파드는 동일한 유저 네임스페이스를 사용한다.

유저 네임스페이스 내에서 kubelet을 실행하고 있다면, 파드에서 실행한 명령의 결과와 호스트에서 실행한 결과를 비교한다.

readlink /proc/$pid/ns/user
user:[4026534732]

$pid은 kubelet의 PID로 대체한다.

15 - 스태틱(static) 파드 생성하기

스태틱 파드API 서버 없이 특정 노드에 있는 kubelet 데몬에 의해 직접 관리된다. 컨트롤 플레인에 의해 관리되는 파드(예를 들어 디플로이먼트(Deployment))와는 달리, kubelet 이 각각의 스태틱 파드를 감시한다. (만약 실패할 경우 다시 구동한다.)

스태틱 파드는 항상 특정 노드에 있는 하나의 Kubelet에 매여 있다.

Kubelet 은 각각의 스태틱 파드에 대하여 쿠버네티스 API 서버에서 미러 파드(mirror pod)를 생성하려고 자동으로 시도한다. 즉, 노드에서 구동되는 파드는 API 서버에 의해서 볼 수 있지만, API 서버에서 제어될 수는 없다. 파드 이름에는 노드 호스트 이름 앞에 하이픈을 붙여 접미사로 추가된다.

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

버전 확인을 위해서, 다음 커맨드를 실행 kubectl version.

이 페이지는 파드를 실행하기 위해 CRI-O를 사용하며, 노드에서 Fedora 운영 체제를 구동하고 있다고 가정한다. 다른 배포판이나 쿠버네티스 설치 지침과는 다소 상이할 수 있다.

스태틱 파드 생성하기

파일 시스템이 호스팅하는 구성 파일이나 웹이 호스팅하는 구성 파일을 사용하여 스태틱 파드를 구성할 수 있다.

파일시스템이 호스팅 하는 스태틱 파드 매니페스트

매니페스트는 특정 디렉터리에 있는 JSON 이나 YAML 형식의 표준 파드 정의이다. kubelet 구성 파일staticPodPath: <the directory> 필드를 사용하자. 명시한 디렉터리를 정기적으로 스캔하여, 디렉터리 안의 YAML/JSON 파일이 생성되거나 삭제되었을 때 스태틱 파드를 생성하거나 삭제한다. Kubelet 이 특정 디렉터리를 스캔할 때 점(.)으로 시작하는 단어를 무시한다는 점을 유의하자.

예를 들어, 다음은 스태틱 파드로 간단한 웹 서버를 구동하는 방법을 보여준다.

  1. 스태틱 파드를 실행할 노드를 선택한다. 이 예제에서는 my-model 이다.

    ssh my-node1
    
  2. /etc/kubernetes/manifests 와 같은 디렉터리를 선택하고 웹 서버 파드의 정의를 해당 위치에, 예를 들어 /etc/kubernetes/manifests/static-web.yaml 에 배치한다.

      # kubelet 이 동작하고 있는 노드에서 이 명령을 수행한다.
    mkdir -p /etc/kubernetes/manifests/
    cat <<EOF >/etc/kubernetes/manifests/static-web.yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: static-web
      labels:
        role: myrole
    spec:
      containers:
        - name: web
          image: nginx
          ports:
            - name: web
              containerPort: 80
              protocol: TCP
    EOF
    
  3. 노드에서 kubelet 실행 시에 --pod-manifest-path=/etc/kubernetes/manifests/ 와 같이 인자를 제공하여 해당 디렉터리를 사용하도록 구성한다. Fedora 의 경우 이 줄을 포함하기 위하여 /etc/kubernetes/kubelet 파일을 다음과 같이 수정한다.

    KUBELET_ARGS="--cluster-dns=10.254.0.10 --cluster-domain=kube.local --pod-manifest-path=/etc/kubernetes/manifests/"
    

    혹은 kubelet 구성 파일staticPodPath: <the directory> 필드를 추가한다.

  4. kubelet을 재시작한다. Fedora의 경우 아래와 같이 수행한다.

    # kubelet 이 동작하고 있는 노드에서 이 명령을 수행한다.
    systemctl restart kubelet
    

웹이 호스팅 하는 스태틱 파드 매니페스트

Kubelet은 --manifest-url=<URL> 의 인수로 지정된 파일을 주기적으로 다운로드하여 해당 파일을 파드의 정의가 포함된 JSON/YAML 파일로 해석한다. 파일시스템이 호스팅 하는 매니페스트 의 작동 방식과 유사하게 kubelet은 스케줄에 맞춰 매니페스트 파일을 다시 가져온다. 스태틱 파드의 목록에 변경된 부분이 있을 경우, kubelet 은 이를 적용한다.

이 방법을 사용하기 위하여 다음을 수행한다.

  1. kubelet 에게 파일의 URL을 전달하기 위하여 YAML 파일을 생성하고 이를 웹 서버에 저장한다.

    apiVersion: v1
    kind: Pod
    metadata:
      name: static-web
      labels:
        role: myrole
    spec:
      containers:
        - name: web
          image: nginx
          ports:
            - name: web
              containerPort: 80
              protocol: TCP
    
  2. 선택한 노드에서 --manifest-url=<manifest-url> 을 실행하여 웹 메니페스트를 사용하도록 kubelet을 구성한다. Fedora 의 경우 이 줄을 포함하기 위하여 /etc/kubernetes/kubelet 파일을 수정한다.

    KUBELET_ARGS="--cluster-dns=10.254.0.10 --cluster-domain=kube.local --manifest-url=<manifest-url>"
    
  3. Kubelet을 재시작한다. Fedora의 경우 아래와 같이 수행한다.

    # kubelet 이 동작하고 있는 노드에서 이 명령을 수행한다.
    systemctl restart kubelet
    

스태틱 파드 행동 관찰하기

Kubelet 을 시작하면, 정의된 모든 스태틱 파드가 자동으로 시작된다. 스태틱 파드를 정의하고, kubelet을 재시작했으므로, 새로운 스태틱 파드가 이미 실행 중이어야 한다.

(노드에서) 구동되고 있는 (스태틱 파드를 포함한) 컨테이너들을 볼 수 있다.

# kubelet 이 동작하고 있는 노드에서 이 명령을 수행한다.
crictl ps

결과는 다음과 유사하다.

CONTAINER       IMAGE                                 CREATED           STATE      NAME    ATTEMPT    POD ID
129fd7d382018   docker.io/library/nginx@sha256:...    11 minutes ago    Running    web     0          34533c6729106

API 서버에서 미러 파드를 볼 수 있다.

kubectl get pods
NAME         READY   STATUS    RESTARTS        AGE
static-web   1/1     Running   0               2m

스태틱 파드에 있는 레이블 은 미러 파드로 전파된다. 셀렉터 등을 통하여 이러한 레이블을 사용할 수 있다.

만약 API 서버로부터 미러 파드를 지우기 위하여 kubectl 을 사용하려 해도, kubelet 은 스태틱 파드를 지우지 않는다.

kubectl delete pod static-web
pod "static-web" deleted

파드가 여전히 구동 중인 것을 볼 수 있다.

kubectl get pods
NAME         READY   STATUS    RESTARTS   AGE
static-web   1/1     Running   0          4s

kubelet 이 구동 중인 노드로 돌아가서 컨테이너를 수동으로 중지할 수 있다. 일정 시간이 지나면, kubelet이 파드를 자동으로 인식하고 다시 시작하는 것을 볼 수 있다.

# kubelet 이 동작하고 있는 노드에서 이 명령을 수행한다.
crictl stop 129fd7d382018 # 예제를 수행하는 사용자의 컨테이너 ID로 변경한다.
sleep 20
crictl ps
CONTAINER       IMAGE                                 CREATED           STATE      NAME    ATTEMPT    POD ID
89db4553e1eeb   docker.io/library/nginx@sha256:...    19 seconds ago    Running    web     1          34533c6729106

스태틱 파드의 동적 추가 및 제거

실행 중인 kubelet 은 주기적으로, 설정된 디렉터리(예제에서는 /etc/kubernetes/manifests)에서 변경 사항을 스캔하고, 이 디렉터리에 새로운 파일이 생성되거나 삭제될 경우, 파드를 생성/삭제 한다.

# 예제를 수행하는 사용자가 파일시스템이 호스팅하는 스태틱 파드 설정을 사용한다고 가정한다.
# kubelet 이 동작하고 있는 노드에서 이 명령을 수행한다.
#
mv /etc/kubernetes/manifests/static-web.yaml /tmp
sleep 20
crictl ps
# 구동 중인 nginx 컨테이너가 없는 것을 확인한다.
mv /tmp/static-web.yaml  /etc/kubernetes/manifests/
sleep 20
crictl ps
CONTAINER       IMAGE                                 CREATED           STATE      NAME    ATTEMPT    POD ID
f427638871c35   docker.io/library/nginx@sha256:...    19 seconds ago    Running    web     1          34533c6729106

16 - 도커 컴포즈 파일을 쿠버네티스 리소스로 변환하기

Kompose는 무엇일까? Kompose는 컴포즈(즉, Docker Compose)를 컨테이너 오케스트레이션(쿠버네티스나 오픈시프트)으로 변환하는 도구이다.

더 자세한 내용은 Kompose 웹사이트 http://kompose.io에서 찾아볼 수 있다.

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

버전 확인을 위해서, 다음 커맨드를 실행 kubectl version.

Kompose 설치하기

Kompose를 설치하기 위한 여러가지 방법들이 있다. 우리는 깃허브 최신 릴리스에서 바이너리를 다운 받는 방법을 사용할 것이다.

Kompose는 3주 주기로 깃허브에 릴리스된다. 현재 릴리스에 관한 모든 정보는 깃허브 릴리스 페이지에서 확인할 수 있다.

# Linux
curl -L https://github.com/kubernetes/kompose/releases/download/v1.26.0/kompose-linux-amd64 -o kompose

# macOS
curl -L https://github.com/kubernetes/kompose/releases/download/v1.26.0/kompose-darwin-amd64 -o kompose

# Windows
curl -L https://github.com/kubernetes/kompose/releases/download/v1.26.0/kompose-windows-amd64.exe -o kompose.exe

chmod +x kompose
sudo mv ./kompose /usr/local/bin/kompose

또 다른 방법으로, tarball를 다운로드 받을 수 있다.

go get을 통해 설치를 진행하면 최신 개발 변경점을 담고 있는 마스터 브랜치를 pull 한다.

go get -u github.com/kubernetes/kompose

Kompose는 EPEL CentOS 저장소이다. 만약 EPEL 저장소를 설치하고 활성화하지 않았다면, sudo yum install epel-release으로 이를 수행할 수 있다.

시스템에 EPEL이 활성화되어 있다면, 다른 패키지처럼 Kompose를 설치할 수 있다.

sudo yum -y install kompose

Kompose는 페도라 24, 25, 그리고 26 저장소에 있다. 다른 패키지들처럼 설치할 수 있다.

sudo dnf -y install kompose

macOS에서는 Homebrew를 통해 최신 릴리스를 설치할 수 있다.

brew install kompose

Kompose 사용하기

몇 단계를 수행하면, 도커 컴포즈를 쿠버네티스로 변환할 수 있다. docker-compose.yml 파일만 있으면 된다.

  1. docker-compose.yml 파일이 존재하는 디렉토리로 이동한다. 만약 없다면, 아래 예제로 테스트한다.

    version: "2"
    
    services:
    
      redis-master:
        image: registry.k8s.io/redis:e2e
        ports:
          - "6379"
    
      redis-slave:
        image: gcr.io/google_samples/gb-redisslave:v3
        ports:
          - "6379"
        environment:
          - GET_HOSTS_FROM=dns
    
      frontend:
        image: gcr.io/google-samples/gb-frontend:v4
        ports:
          - "80:80"
        environment:
          - GET_HOSTS_FROM=dns
        labels:
          kompose.service.type: LoadBalancer
    
  2. docker-compose.yml 파일을 kubectl를 통해 활용할 수 있는 파일들로 변환하기 위해서는, kompose convert를 실행한 후, kubectl apply -f <output file>를 실행한다.

    kompose convert
    

    결과는 다음과 같다.

    INFO Kubernetes file "frontend-service.yaml" created
       INFO Kubernetes file "frontend-service.yaml" created
    INFO Kubernetes file "frontend-service.yaml" created
    INFO Kubernetes file "redis-master-service.yaml" created
       INFO Kubernetes file "redis-master-service.yaml" created
    INFO Kubernetes file "redis-master-service.yaml" created
    INFO Kubernetes file "redis-slave-service.yaml" created
       INFO Kubernetes file "redis-slave-service.yaml" created
    INFO Kubernetes file "redis-slave-service.yaml" created
    INFO Kubernetes file "frontend-deployment.yaml" created
       INFO Kubernetes file "frontend-deployment.yaml" created
    INFO Kubernetes file "frontend-deployment.yaml" created
    INFO Kubernetes file "redis-master-deployment.yaml" created
       INFO Kubernetes file "redis-master-deployment.yaml" created
    INFO Kubernetes file "redis-master-deployment.yaml" created
    INFO Kubernetes file "redis-slave-deployment.yaml" created
       INFO Kubernetes file "redis-slave-deployment.yaml" created
    INFO Kubernetes file "redis-slave-deployment.yaml" created
    
     kubectl apply -f frontend-service.yaml,redis-master-service.yaml,redis-slave-service.yaml,frontend-deployment.yaml,redis-master-deployment.yaml,redis-slave-deployment.yaml
    

    결과는 다음과 같다.

    service/frontend created
    service/redis-master created
    service/redis-slave created
    deployment.apps/frontend created
    deployment.apps/redis-master created
    deployment.apps/redis-slave created
    

    디플로이먼트들은 쿠버네티스에서 실행된다.

  3. 애플리케이션에 접근하기.

    minikube를 개발 환경으로 사용하고 있다면

    minikube service frontend
    

    이 외에는, 서비스가 사용중인 IP를 확인해보자!

    kubectl describe svc frontend
    
    Name:                   frontend
    Namespace:              default
    Labels:                 service=frontend
    Selector:               service=frontend
    Type:                   LoadBalancer
    IP:                     10.0.0.183
    LoadBalancer Ingress:   192.0.2.89
    Port:                   80      80/TCP
    NodePort:               80      31144/TCP
    Endpoints:              172.17.0.4:80
    Session Affinity:       None
    No events.
    

    클라우드 환경을 사용하고 있다면, IP는 LoadBalancer Ingress 옆에 나열되어 있을 것이다.

    curl http://192.0.2.89
    

사용자 가이드

Kompose는 오픈시프트와 쿠버네티스 두 제공자를 지원한다. --provider 글로벌 옵션을 통해 대상 제공자를 선택할 수 있다. 만약 제공자가 명시되지 않았다면, 쿠버네티스가 기본값으로 설정된다.

kompose convert

Kompose는 도커 컴포즈 V1, V2, 그리고 V3 파일에 대한 쿠버네티스와 오픈시프트 오브젝트로의 변환을 지원한다.

쿠버네티스 kompose convert 예제

kompose --file docker-voting.yml convert
WARN Unsupported key networks - ignoring
WARN Unsupported key build - ignoring
INFO Kubernetes file "worker-svc.yaml" created
INFO Kubernetes file "db-svc.yaml" created
INFO Kubernetes file "redis-svc.yaml" created
INFO Kubernetes file "result-svc.yaml" created
INFO Kubernetes file "vote-svc.yaml" created
INFO Kubernetes file "redis-deployment.yaml" created
INFO Kubernetes file "result-deployment.yaml" created
INFO Kubernetes file "vote-deployment.yaml" created
INFO Kubernetes file "worker-deployment.yaml" created
INFO Kubernetes file "db-deployment.yaml" created
ls
db-deployment.yaml  docker-compose.yml         docker-gitlab.yml  redis-deployment.yaml  result-deployment.yaml  vote-deployment.yaml  worker-deployment.yaml
db-svc.yaml         docker-voting.yml          redis-svc.yaml     result-svc.yaml        vote-svc.yaml           worker-svc.yaml

동시에 여러 도커 컴포즈 파일들을 명시할 수도 있다

kompose -f docker-compose.yml -f docker-guestbook.yml convert
INFO Kubernetes file "frontend-service.yaml" created         
INFO Kubernetes file "mlbparks-service.yaml" created         
INFO Kubernetes file "mongodb-service.yaml" created          
INFO Kubernetes file "redis-master-service.yaml" created     
INFO Kubernetes file "redis-slave-service.yaml" created      
INFO Kubernetes file "frontend-deployment.yaml" created      
INFO Kubernetes file "mlbparks-deployment.yaml" created      
INFO Kubernetes file "mongodb-deployment.yaml" created       
INFO Kubernetes file "mongodb-claim0-persistentvolumeclaim.yaml" created
INFO Kubernetes file "redis-master-deployment.yaml" created  
INFO Kubernetes file "redis-slave-deployment.yaml" created   
ls
mlbparks-deployment.yaml  mongodb-service.yaml                       redis-slave-service.jsonmlbparks-service.yaml  
frontend-deployment.yaml  mongodb-claim0-persistentvolumeclaim.yaml  redis-master-service.yaml
frontend-service.yaml     mongodb-deployment.yaml                    redis-slave-deployment.yaml
redis-master-deployment.yaml

만약 여러 도커 컴포즈 파일들이 명시되면 설정들은 병합된다. 공통적인 설정들은 그 다음 파일의 내용으로 덮어씌워진다.

오픈시프트 kompose convert 예제

kompose --provider openshift --file docker-voting.yml convert
WARN [worker] Service cannot be created because of missing port.
INFO OpenShift file "vote-service.yaml" created             
INFO OpenShift file "db-service.yaml" created               
INFO OpenShift file "redis-service.yaml" created            
INFO OpenShift file "result-service.yaml" created           
INFO OpenShift file "vote-deploymentconfig.yaml" created    
INFO OpenShift file "vote-imagestream.yaml" created         
INFO OpenShift file "worker-deploymentconfig.yaml" created  
INFO OpenShift file "worker-imagestream.yaml" created       
INFO OpenShift file "db-deploymentconfig.yaml" created      
INFO OpenShift file "db-imagestream.yaml" created           
INFO OpenShift file "redis-deploymentconfig.yaml" created   
INFO OpenShift file "redis-imagestream.yaml" created        
INFO OpenShift file "result-deploymentconfig.yaml" created  
INFO OpenShift file "result-imagestream.yaml" created  

서비스 내 빌드 명령에 대한 빌드컨피그(buildconfig) 생성도 지원한다. 기본적으로, 현재 깃 브랜치에 대한 리모트 저장소를 소스 저장소로 사용한다. 그리고 현재 브랜치를 빌드를 위한 소스 브랜치로 사용한다. --build-repo--build-branch 옵션으로 다른 소스 저장소와 브랜치를 각각 지정할 수 있다.

kompose --provider openshift --file buildconfig/docker-compose.yml convert
WARN [foo] Service cannot be created because of missing port.
INFO OpenShift Buildconfig using git@github.com:rtnpro/kompose.git::master as source.
INFO OpenShift file "foo-deploymentconfig.yaml" created     
INFO OpenShift file "foo-imagestream.yaml" created          
INFO OpenShift file "foo-buildconfig.yaml" created

다른 형식으로의 변환

kompose의 기본 변환은 쿠버네티스 디플로이먼트서비스를 yaml 형식으로 생성하는 것이다. 또 다른 방법으로는 -j 옵션으로 json을 생성할 수도 있다. 또한, 레플리케이션 컨트롤러 오브젝트와, 데몬셋, Helm 차트를 생성할 수도 있다.

kompose convert -j
INFO Kubernetes file "redis-svc.json" created
INFO Kubernetes file "web-svc.json" created
INFO Kubernetes file "redis-deployment.json" created
INFO Kubernetes file "web-deployment.json" created

*-deployment.json 파일은 디플로이먼트 오브젝트들을 담고 있다.

kompose convert --replication-controller
INFO Kubernetes file "redis-svc.yaml" created
INFO Kubernetes file "web-svc.yaml" created
INFO Kubernetes file "redis-replicationcontroller.yaml" created
INFO Kubernetes file "web-replicationcontroller.yaml" created

*-replicationcontroller.yaml 파일들은 레플리케이션 컨트롤러 오브젝트들을 담고 있다. 만약 레플리카를 명시하고 싶다면, --replicas 플래그를 사용한다. kompose convert --replication-controller --replicas 3.

kompose convert --daemon-set
INFO Kubernetes file "redis-svc.yaml" created
INFO Kubernetes file "web-svc.yaml" created
INFO Kubernetes file "redis-daemonset.yaml" created
INFO Kubernetes file "web-daemonset.yaml" created

*-daemonset.yaml 파일들은 데몬셋 오브젝트를 담고 있다.

만약 헬름을 통해 차트를 생성하고 싶다면, 아래 명령을 실행한다.

kompose convert -c
INFO Kubernetes file "web-svc.yaml" created
INFO Kubernetes file "redis-svc.yaml" created
INFO Kubernetes file "web-deployment.yaml" created
INFO Kubernetes file "redis-deployment.yaml" created
chart created in "./docker-compose/"
tree docker-compose/
docker-compose
├── Chart.yaml
├── README.md
└── templates
    ├── redis-deployment.yaml
    ├── redis-svc.yaml
    ├── web-deployment.yaml
    └── web-svc.yaml

차트 구조는 헬름 차트를 만들기 위한 골격을 제공한다.

레이블

kompose는 서비스의 행동을 변환 시에 명시적으로 정의하기 위해 Kompose에 특화된 레이블들을 docker-compose.yml 파일에서 지원한다.

  • kompose.service.type는 서비스의 종류를 정의한다.

    예를 들어

    version: "2"
    services:
      nginx:
        image: nginx
        dockerfile: foobar
        build: ./foobar
        cap_add:
          - ALL
        container_name: foobar
        labels:
          kompose.service.type: nodeport
    
  • kompose.service.expose는 서비스가 클러스터 외부에서 접근 가능한지를 정의한다. 값이 "true"로 설정되어 있다면, 제공자는 자동으로 엔드포인트를 설정한다. 이외의 값의 경우에는, 호스트네임으로 값이 설정된다. 서비스에 여러 포드들이 정의되어 있다면, 첫번째 포트가 선택되어 노출된다.

    • 쿠버네티스 제공자는, 인그레스 컨트롤러가 이미 설정되었다고 가정한 상태에서 인그레스 리소스가 생성된다.
    • 오픈시프트 제공자는 라우트를 생성한다.

    예를 들어:

    version: "2"
    services:
      web:
        image: tuna/docker-counter23
        ports:
        - "5000:5000"
        links:
        - redis
        labels:
          kompose.service.expose: "counter.example.com"
      redis:
        image: redis:3.0
        ports:
        - "6379"
    

현재 지원하는 옵션들은 다음과 같다.

Key Value
kompose.service.type nodeport / clusterip / loadbalancer
kompose.service.expose true / hostname

재시작

만약 컨트롤러 없이 일반 파드들을 생성하고 싶다면, 도커 컴포즈에 restart를 명시하여 이를 정의한다. restart 값을 설정하였을 때 어떤 일이 일어나는지는 아래 표를 확인한다.

docker-compose restart object created Pod restartPolicy
"" controller object Always
always controller object Always
on-failure Pod OnFailure
no Pod Never

예를 들어, pival 서비스는 아래 파드가 될 것이다. 이 컨테이너의 계산된 값은 pi이다.

version: '2'

services:
  pival:
    image: perl
    command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
    restart: "on-failure"

디플로이먼트 설정에 관한 주의사항

만약 도커 컴포즈 파일에 서비스를 위한 볼륨이 명시되어 있다면, 디플로이먼트(쿠버네티스)나 디플로이먼트컨피그(오픈시프트) 전략은 "롤링 업데이트"(기본값)이 아닌 "재생성"으로 변경된다. 이것은 서비스의 여러 인스턴스들이 동시에 같은 볼륨에 접근하는 것을 막기 위함이다.

만약 도커 컴포즈 파일의 서비스 이름에 _이 포함되어 있다면 (예를 들어, web_service와 같은), -으로 대체되고 서비스의 이름 또한 마찬가지로 변경될 것이다 (예를 들어, web-service로). 이는 쿠버네티스가 오브젝트 이름에 _를 허용하지 않기 때문이다.

서비스 이름을 변경할 경우 docker-compose 파일의 일부가 작동하지 않을 수도 있다.

도커 컴포즈 버전

Kompose는 다음의 도커 컴포즈 버전을 지원한다. 1, 2, 그리고 3. 버전 2.1와 3.2는 실험적인 환경이기 때문에 제한적으로 지원한다.

호환하지 않는 도커 컴포즈 키 리스트를 포함한 3개의 버전들에 관한 모든 호환성 리스트는 변환 문서에서 확인 할 수 있다.