태스크

쿠버네티스 문서에서 이 섹션은 개별의 태스크를 수행하는 방법을 보여준다. 한 태스크 페이지는 일반적으로 여러 단계로 이루어진 짧은 시퀀스를 제공함으로써, 하나의 일을 수행하는 방법을 보여준다.

만약 태스크 페이지를 작성하고 싶다면, 문서 풀 리퀘스트(Pull Request) 생성하기를 참조한다.

1 - 도구 설치

컴퓨터에서 쿠버네티스 도구를 설정한다.

kubectl

쿠버네티스 커맨드 라인 도구인 kubectl을 사용하면 쿠버네티스 클러스터에 대해 명령을 실행할 수 있다. kubectl 을 사용하여 애플리케이션을 배포하고, 클러스터 리소스를 검사 및 관리하고, 로그를 볼 수 있다. kubectl 전체 명령어를 포함한 추가 정보는 kubectl 레퍼런스 문서에서 확인할 수 있다.

kubectl 은 다양한 리눅스 플랫폼, macOS, 그리고 윈도우에 설치할 수 있다. 각각에 대한 설치 가이드는 다음과 같다.

kind

kind를 사용하면 로컬 컴퓨터에서 쿠버네티스를 실행할 수 있다. 이 도구를 사용하려면 도커를 설치하고 구성해야 한다.

kind 퀵 스타트 페이지는 kind를 시작하고 실행하기 위해 수행해야 하는 작업을 보여준다.

kind 시작하기 가이드 보기

minikube

kind 와 마찬가지로, minikube는 쿠버네티스를 로컬에서 실행할 수 있는 도구이다. minikube 는 개인용 컴퓨터(윈도우, macOS 및 리눅스 PC 포함)에서 올인원 방식 또는 복수 개의 노드로 쿠버네티스 클러스터를 실행하여, 쿠버네티스를 사용해보거나 일상적인 개발 작업을 수행할 수 있다.

도구 설치에 중점을 두고 있다면 공식 사이트에서의 시작하기! 가이드를 따라 해볼 수 있다.

minikube 시작하기! 가이드 보기

minikube 가 작동하면, 이를 사용하여 샘플 애플리케이션을 실행해볼 수 있다.

kubeadm

kubeadm 도구를 사용하여 쿠버네티스 클러스터를 만들고 관리할 수 있다. 사용자 친화적인 방식으로 최소한의 실행 가능하고 안전한 클러스터를 설정하고 실행하는 데 필요한 작업을 수행한다.

kubeadm 설치 페이지는 kubeadm 설치하는 방법을 보여준다. 설치가 끝나면, 클러스터 생성이 가능하다.

kubeadm 설치 가이드 보기

1.1 - macOS에 kubectl 설치 및 설정

시작하기 전에

클러스터의 마이너(minor) 버전 차이 내에 있는 kubectl 버전을 사용해야 한다. 예를 들어, v1.31 클라이언트는 v1.30, v1.31, v1.32의 컨트롤 플레인과 연동될 수 있다. 호환되는 최신 버전의 kubectl을 사용하면 예기치 않은 문제를 피할 수 있다.

macOS에 kubectl 설치

다음과 같은 방법으로 macOS에 kubectl을 설치할 수 있다.

macOS에서 curl을 사용하여 kubectl 바이너리 설치

  1. 최신 릴리스를 다운로드한다.

    
       curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/amd64/kubectl"
       

    
       curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/arm64/kubectl"
       
  2. 바이너리를 검증한다. (선택 사항)

    kubectl 체크섬 파일을 다운로드한다.

    
       curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/amd64/kubectl.sha256"
       

    
       curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/arm64/kubectl.sha256"
       

    kubectl 바이너리를 체크섬 파일을 통해 검증한다.

    echo "$(cat kubectl.sha256)  kubectl" | shasum -a 256 --check
    

    검증이 성공한다면, 출력은 다음과 같다.

    kubectl: OK
    

    검증이 실패한다면, shasum이 0이 아닌 상태로 종료되며 다음과 유사한 결과를 출력한다.

    kubectl: FAILED
    shasum: WARNING: 1 computed checksum did NOT match
    
  3. kubectl 바이너리를 실행 가능하게 한다.

    chmod +x ./kubectl
    
  4. kubectl 바이너리를 시스템 PATH 의 파일 위치로 옮긴다.

    sudo mv ./kubectl /usr/local/bin/kubectl
    sudo chown root: /usr/local/bin/kubectl
    
  5. 설치한 버전이 최신 버전인지 확인한다.

    kubectl version --client
    

    또는 다음을 실행하여 버전에 대한 더 자세한 정보를 본다.

    kubectl version --client --output=yaml    
    

macOS에서 Homebrew를 사용하여 설치

macOS에서 Homebrew 패키지 관리자를 사용하는 경우, Homebrew로 kubectl을 설치할 수 있다.

  1. 설치 명령을 실행한다.

    brew install kubectl
    

    또는

    brew install kubernetes-cli
    
  2. 설치한 버전이 최신 버전인지 확인한다.

    kubectl version --client
    

macOS에서 Macports를 사용하여 설치

macOS에서 Macports 패키지 관리자를 사용하는 경우, Macports로 kubectl을 설치할 수 있다.

  1. 설치 명령을 실행한다.

    sudo port selfupdate
    sudo port install kubectl
    
  2. 설치한 버전이 최신 버전인지 확인한다.

    kubectl version --client
    

kubectl 구성 확인

kubectl이 쿠버네티스 클러스터를 찾아 접근하려면, kube-up.sh를 사용하여 클러스터를 생성하거나 Minikube 클러스터를 성공적으로 배포할 때 자동으로 생성되는 kubeconfig 파일이 필요하다. 기본적으로, kubectl 구성은 ~/.kube/config 에 있다.

클러스터 상태를 가져와서 kubectl이 올바르게 구성되어 있는지 확인한다.

kubectl cluster-info

URL 응답이 표시되면, kubectl이 클러스터에 접근하도록 올바르게 구성된 것이다.

다음과 비슷한 메시지가 표시되면, kubectl이 올바르게 구성되지 않았거나 쿠버네티스 클러스터에 연결할 수 없다.

The connection to the server <server-name:port> was refused - did you specify the right host or port?

예를 들어, 랩톱에서 로컬로 쿠버네티스 클러스터를 실행하려면, Minikube와 같은 도구를 먼저 설치한 다음 위에서 언급한 명령을 다시 실행해야 한다.

kubectl cluster-info가 URL 응답을 반환하지만 클러스터에 접근할 수 없는 경우, 올바르게 구성되었는지 확인하려면 다음을 사용한다.

kubectl cluster-info dump

선택적 kubectl 구성 및 플러그인

셸 자동 완성 활성화

kubectl은 Bash, Zsh, Fish, 및 PowerShell에 대한 자동 완성 지원을 제공하므로 입력을 위한 타이핑을 많이 절약할 수 있다.

다음은 Bash, Fish, 및 Zsh에 대한 자동 완성을 설정하는 절차이다.

소개

Bash의 kubectl 자동 완성 스크립트는 kubectl completion bash 로 생성할 수 있다. 이 스크립트를 셸에 소싱하면 kubectl 자동 완성이 가능하다.

그러나 kubectl 자동 완성 스크립트는 미리 bash-completion을 설치해야 동작한다.

Bash 업그레이드

여기의 지침에서는 Bash 4.1 이상을 사용한다고 가정한다. 다음을 실행하여 Bash 버전을 확인할 수 있다.

echo $BASH_VERSION

너무 오래된 버전인 경우, Homebrew를 사용하여 설치/업그레이드할 수 있다.

brew install bash

셸을 다시 로드하고 원하는 버전을 사용 중인지 확인한다.

echo $BASH_VERSION $SHELL

Homebrew는 보통 /usr/local/bin/bash 에 설치한다.

bash-completion 설치

bash-completion v2가 이미 설치되어 있는지 type_init_completion 으로 확인할 수 있다. 그렇지 않은 경우, Homebrew로 설치할 수 있다.

brew install bash-completion@2

이 명령의 출력에 명시된 바와 같이, ~/.bash_profile 파일에 다음을 추가한다.

export BASH_COMPLETION_COMPAT_DIR="/usr/local/etc/bash_completion.d"
[[ -r "/usr/local/etc/profile.d/bash_completion.sh" ]] && . "/usr/local/etc/profile.d/bash_completion.sh"

셸을 다시 로드하고 bash-completion v2가 올바르게 설치되었는지 type _init_completion 으로 확인한다.

kubectl 자동 완성 활성화

이제 kubectl 자동 완성 스크립트가 모든 셸 세션에서 제공되도록 해야 한다. 이를 수행하는 방법에는 여러 가지가 있다.

  • 자동 완성 스크립트를 ~/.bash_profile 파일에서 소싱한다.

    echo 'source <(kubectl completion bash)' >>~/.bash_profile
    
  • 자동 완성 스크립트를 /usr/local/etc/bash_completion.d 디렉터리에 추가한다.

    kubectl completion bash >/usr/local/etc/bash_completion.d/kubectl
    
  • kubectl에 대한 앨리어스가 있는 경우, 해당 앨리어스로 작업하기 위해 셸 자동 완성을 확장할 수 있다.

    echo 'alias k=kubectl' >>~/.bash_profile
    echo 'complete -o default -F __start_kubectl k' >>~/.bash_profile
    
  • Homebrew로 kubectl을 설치한 경우(여기의 설명을 참고), kubectl 자동 완성 스크립트가 이미 /usr/local/etc/bash_completion.d/kubectl 에 있을 것이다. 이 경우, 아무 것도 할 필요가 없다.

어떤 경우든, 셸을 다시 로드하면, kubectl 자동 완성 기능이 작동할 것이다.

Fish용 kubectl 자동 완성 스크립트는 kubectl completion fish 명령으로 생성할 수 있다. 셸에서 자동 완성 스크립트를 소싱하면 kubectl 자동 완성 기능이 활성화된다.

모든 셸 세션에서 사용하려면, ~/.config/fish/config.fish 파일에 다음을 추가한다.

kubectl completion fish | source

셸을 다시 로드하면, kubectl 자동 완성 기능이 작동할 것이다.

Zsh용 kubectl 자동 완성 스크립트는 kubectl completion zsh 명령으로 생성할 수 있다. 셸에서 자동 완성 스크립트를 소싱하면 kubectl 자동 완성 기능이 활성화된다.

모든 셸 세션에서 사용하려면, ~/.zshrc 파일에 다음을 추가한다.

source <(kubectl completion zsh)

kubectl에 대한 앨리어스가 있는 경우, kubectl 자동완성이 자동으로 동작할 것이다.

셸을 다시 로드하면, kubectl 자동 완성 기능이 작동할 것이다.

2: command not found: compdef 와 같은 오류가 발생하면, ~/.zshrc 파일의 시작 부분에 다음을 추가한다.

autoload -Uz compinit
compinit

kubectl convert 플러그인 설치

이것은 쿠버네티스 커맨드 라인 도구인 kubectl의 플러그인으로서, 특정 버전의 쿠버네티스 API로 작성된 매니페스트를 다른 버전으로 변환할 수 있도록 한다. 이것은 매니페스트를 최신 쿠버네티스 릴리스의 사용 중단되지 않은 API로 마이그레이션하는 데 특히 유용하다. 더 많은 정보는 다음의 사용 중단되지 않은 API로 마이그레이션을 참고한다.

  1. 다음 명령으로 최신 릴리스를 다운로드한다.

    
       curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/amd64/kubectl-convert"
       

    
       curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/arm64/kubectl-convert"
       
  2. 바이너리를 검증한다. (선택 사항)

    kubectl-convert 체크섬(checksum) 파일을 다운로드한다.

    
       curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/amd64/kubectl-convert.sha256"
       

    
       curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/arm64/kubectl-convert.sha256"
       

    kubectl-convert 바이너리를 체크섬 파일을 통해 검증한다.

    echo "$(cat kubectl-convert.sha256)  kubectl-convert" | shasum -a 256 --check
    

    검증이 성공한다면, 출력은 다음과 같다.

    kubectl-convert: OK
    

    검증이 실패한다면, shasum이 0이 아닌 상태로 종료되며 다음과 유사한 결과를 출력한다.

    kubectl-convert: FAILED
    shasum: WARNING: 1 computed checksum did NOT match
    
  3. kubectl-convert 바이너리를 실행 가능하게 한다.

    chmod +x ./kubectl-convert
    
  4. kubectl-convert 바이너리를 시스템 PATH 의 파일 위치로 옮긴다.

    sudo mv ./kubectl-convert /usr/local/bin/kubectl-convert
    sudo chown root: /usr/local/bin/kubectl-convert
    
  5. 플러그인이 정상적으로 설치되었는지 확인한다.

    kubectl convert --help
    

    에러가 출력되지 않는다면, 플러그인이 정상적으로 설치된 것이다.

다음 내용

1.2 - 리눅스에 kubectl 설치 및 설정

시작하기 전에

클러스터의 마이너(minor) 버전 차이 내에 있는 kubectl 버전을 사용해야 한다. 예를 들어, v1.31 클라이언트는 v1.30, v1.31, v1.32의 컨트롤 플레인과 연동될 수 있다. 호환되는 최신 버전의 kubectl을 사용하면 예기치 않은 문제를 피할 수 있다.

리눅스에 kubectl 설치

다음과 같은 방법으로 리눅스에 kubectl을 설치할 수 있다.

리눅스에서 curl을 사용하여 kubectl 바이너리 설치

  1. 다음 명령으로 최신 릴리스를 다운로드한다.

    curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
    
  2. 바이너리를 검증한다. (선택 사항)

    kubectl 체크섬(checksum) 파일을 다운로드한다.

    curl -LO "https://dl.k8s.io/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl.sha256"
    

    kubectl 바이너리를 체크섬 파일을 통해 검증한다.

    echo "$(cat kubectl.sha256)  kubectl" | sha256sum --check
    

    검증이 성공한다면, 출력은 다음과 같다.

    kubectl: OK
    

    검증이 실패한다면, shasum이 0이 아닌 상태로 종료되며 다음과 유사한 결과를 출력한다.

    kubectl: FAILED
    sha256sum: WARNING: 1 computed checksum did NOT match
    
  3. kubectl 설치

    sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
    
  4. 설치한 버전이 최신인지 확인한다.

    kubectl version --client
    

    또는 다음을 실행하여 버전에 대한 더 자세한 정보를 본다.

    kubectl version --client --output=yaml    
    

기본 패키지 관리 도구를 사용하여 설치

  1. apt 패키지 색인을 업데이트하고 쿠버네티스 apt 리포지터리를 사용하는 데 필요한 패키지들을 설치한다.

    sudo apt-get update
    sudo apt-get install -y apt-transport-https ca-certificates curl
    

    Debian 9(stretch) 또는 그 이전 버전을 사용하는 경우 apt-transport-https도 설치해야 한다.

    sudo apt-get install -y apt-transport-https
    
  2. 구글 클라우드 공개 사이닝 키를 다운로드한다.

    sudo curl -fsSLo /etc/apt/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
    
  3. 쿠버네티스 apt 리포지터리를 추가한다.

    echo "deb [signed-by=/etc/apt/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
    
  4. 새 리포지터리의 apt 패키지 색인을 업데이트하고 kubectl을 설치한다.

    sudo apt-get update
    sudo apt-get install -y kubectl
    

cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\$basearch
enabled=1
gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF
sudo yum install -y kubectl

다른 패키지 관리 도구를 사용하여 설치

snap 패키지 관리자를 지원하는 Ubuntu 또는 다른 리눅스 배포판을 사용하는 경우, kubectl을 snap 애플리케이션으로 설치할 수 있다.

snap install kubectl --classic
kubectl version --client

리눅스 상에서 Homebrew 패키지 관리자를 사용한다면, 설치를 통해 kubectl을 사용할 수 있다.

brew install kubectl
kubectl version --client

kubectl 구성 확인

kubectl이 쿠버네티스 클러스터를 찾아 접근하려면, kube-up.sh를 사용하여 클러스터를 생성하거나 Minikube 클러스터를 성공적으로 배포할 때 자동으로 생성되는 kubeconfig 파일이 필요하다. 기본적으로, kubectl 구성은 ~/.kube/config 에 있다.

클러스터 상태를 가져와서 kubectl이 올바르게 구성되어 있는지 확인한다.

kubectl cluster-info

URL 응답이 표시되면, kubectl이 클러스터에 접근하도록 올바르게 구성된 것이다.

다음과 비슷한 메시지가 표시되면, kubectl이 올바르게 구성되지 않았거나 쿠버네티스 클러스터에 연결할 수 없다.

The connection to the server <server-name:port> was refused - did you specify the right host or port?

예를 들어, 랩톱에서 로컬로 쿠버네티스 클러스터를 실행하려면, Minikube와 같은 도구를 먼저 설치한 다음 위에서 언급한 명령을 다시 실행해야 한다.

kubectl cluster-info가 URL 응답을 반환하지만 클러스터에 접근할 수 없는 경우, 올바르게 구성되었는지 확인하려면 다음을 사용한다.

kubectl cluster-info dump

선택적 kubectl 구성 및 플러그인

셸 자동 완성 활성화

kubectl은 Bash, Zsh, Fish, 및 PowerShell에 대한 자동 완성 지원을 제공하므로 입력을 위한 타이핑을 많이 절약할 수 있다.

다음은 Bash, Fish, 및 Zsh에 대한 자동 완성을 설정하는 절차이다.

소개

Bash의 kubectl 자동 완성 스크립트는 kubectl completion bash 명령으로 생성할 수 있다. 셸에서 자동 완성 스크립트를 소싱(sourcing)하면 kubectl 자동 완성 기능이 활성화된다.

그러나, 자동 완성 스크립트는 bash-completion에 의존하고 있으며, 이 소프트웨어를 먼저 설치해야 한다(type _init_completion 을 실행하여 bash-completion이 이미 설치되어 있는지 확인할 수 있음).

bash-completion 설치

bash-completion은 많은 패키지 관리자에 의해 제공된다(여기 참고). apt-get install bash-completion 또는 yum install bash-completion 등으로 설치할 수 있다.

위의 명령은 bash-completion의 기본 스크립트인 /usr/share/bash-completion/bash_completion 을 생성한다. 패키지 관리자에 따라, ~/.bashrc 파일에서 이 파일을 수동으로 소스(source)해야 한다.

확인하려면, 셸을 다시 로드하고 type _init_completion 을 실행한다. 명령이 성공하면, 이미 설정된 상태이고, 그렇지 않으면 ~/.bashrc 파일에 다음을 추가한다.

source /usr/share/bash-completion/bash_completion

셸을 다시 로드하고 type _init_completion 을 입력하여 bash-completion이 올바르게 설치되었는지 확인한다.

kubectl 자동 완성 활성화

Bash

이제 kubectl 자동 완성 스크립트가 모든 셸 세션에서 제공되도록 해야 한다. 이를 수행할 수 있는 두 가지 방법이 있다.


echo 'source <(kubectl completion bash)' >>~/.bashrc


kubectl completion bash | sudo tee /etc/bash_completion.d/kubectl > /dev/null

kubectl에 대한 앨리어스(alias)가 있는 경우, 해당 앨리어스로 작업하도록 셸 자동 완성을 확장할 수 있다.

echo 'alias k=kubectl' >>~/.bashrc
echo 'complete -o default -F __start_kubectl k' >>~/.bashrc

두 방법 모두 동일하다. 셸을 다시 로드하면, kubectl 자동 완성 기능이 작동할 것이다. 셸의 현재 세션에서 bash 자동 완성을 활성화하려면 exec bash를 실행한다.

exec bash

Fish용 kubectl 자동 완성 스크립트는 kubectl completion fish 명령으로 생성할 수 있다. 셸에서 자동 완성 스크립트를 소싱하면 kubectl 자동 완성 기능이 활성화된다.

모든 셸 세션에서 사용하려면, ~/.config/fish/config.fish 파일에 다음을 추가한다.

kubectl completion fish | source

셸을 다시 로드하면, kubectl 자동 완성 기능이 작동할 것이다.

Zsh용 kubectl 자동 완성 스크립트는 kubectl completion zsh 명령으로 생성할 수 있다. 셸에서 자동 완성 스크립트를 소싱하면 kubectl 자동 완성 기능이 활성화된다.

모든 셸 세션에서 사용하려면, ~/.zshrc 파일에 다음을 추가한다.

source <(kubectl completion zsh)

kubectl에 대한 앨리어스가 있는 경우, kubectl 자동완성이 자동으로 동작할 것이다.

셸을 다시 로드하면, kubectl 자동 완성 기능이 작동할 것이다.

2: command not found: compdef 와 같은 오류가 발생하면, ~/.zshrc 파일의 시작 부분에 다음을 추가한다.

autoload -Uz compinit
compinit

kubectl convert 플러그인 설치

이것은 쿠버네티스 커맨드 라인 도구인 kubectl의 플러그인으로서, 특정 버전의 쿠버네티스 API로 작성된 매니페스트를 다른 버전으로 변환할 수 있도록 한다. 이것은 매니페스트를 최신 쿠버네티스 릴리스의 사용 중단되지 않은 API로 마이그레이션하는 데 특히 유용하다. 더 많은 정보는 다음의 사용 중단되지 않은 API로 마이그레이션을 참고한다.

  1. 다음 명령으로 최신 릴리스를 다운로드한다.

    curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl-convert"
    
  2. 바이너리를 검증한다. (선택 사항)

    kubectl-convert 체크섬(checksum) 파일을 다운로드한다.

    curl -LO "https://dl.k8s.io/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl-convert.sha256"
    

    kubectl-convert 바이너리를 체크섬 파일을 통해 검증한다.

    echo "$(cat kubectl-convert.sha256) kubectl-convert" | sha256sum --check
    

    검증이 성공한다면, 출력은 다음과 같다.

    kubectl-convert: OK
    

    검증이 실패한다면, sha256이 0이 아닌 상태로 종료되며 다음과 유사한 결과를 출력한다.

    kubectl-convert: FAILED
    sha256sum: WARNING: 1 computed checksum did NOT match
    
  3. kubectl-convert 설치

    sudo install -o root -g root -m 0755 kubectl-convert /usr/local/bin/kubectl-convert
    
  4. 플러그인이 정상적으로 설치되었는지 확인한다.

    kubectl convert --help
    

    에러가 출력되지 않는다면, 플러그인이 정상적으로 설치된 것이다.

다음 내용

1.3 - 윈도우에 kubectl 설치 및 설정

시작하기 전에

클러스터의 마이너(minor) 버전 차이 내에 있는 kubectl 버전을 사용해야 한다. 예를 들어, v1.31 클라이언트는 v1.30, v1.31, v1.32의 컨트롤 플레인과 연동될 수 있다. 호환되는 최신 버전의 kubectl을 사용하면 예기치 않은 문제를 피할 수 있다.

윈도우에 kubectl 설치

다음과 같은 방법으로 윈도우에 kubectl을 설치할 수 있다.

윈도우에서 curl을 사용하여 kubectl 바이너리 설치

  1. 최신 패치 릴리스 1.31 다운로드: kubectl 1.31.0

    또는 curl 을 설치한 경우, 다음 명령을 사용한다.

    curl.exe -LO "https://dl.k8s.io/release/v1.31.0/bin/windows/amd64/kubectl.exe"
    
  2. 바이너리를 검증한다. (선택 사항)

    kubectl 체크섬 파일을 다운로드한다.

    curl.exe -LO "https://dl.k8s.io/v1.31.0/bin/windows/amd64/kubectl.exe.sha256"
    

    kubectl 바이너리를 체크섬 파일을 통해 검증한다.

    • 커맨드 프롬프트를 사용하는 경우, CertUtil 의 출력과 다운로드한 체크섬 파일을 수동으로 비교한다.

      CertUtil -hashfile kubectl.exe SHA256
      type kubectl.exe.sha256
      
    • PowerShell을 사용하는 경우, -eq 연산자를 통해 True 또는 False 결과가 출력되는 자동 검증을 수행한다.

      $($(CertUtil -hashfile .\kubectl.exe SHA256)[1] -replace " ", "") -eq $(type .\kubectl.exe.sha256)
      
  3. kubectl 바이너리가 있는 폴더를 PATH 환경 변수의 앞부분 또는 뒷부분에 추가

  4. kubectl 의 버전이 다운로드한 버전과 같은지 확인한다.

    kubectl version --client
    

    또는 다음을 실행하여 버전에 대한 더 자세한 정보를 본다.

    kubectl version --client --output=yaml    
    

Chocolatey, Scoop, 또는 winget을 사용하여 윈도우에 설치

  1. 윈도우에 kubectl을 설치하기 위해서 Chocolatey 패키지 관리자, Scoop 커맨드 라인 설치 프로그램, winget 패키지 관리자를 사용할 수 있다.

     choco install kubernetes-cli
    

     scoop install kubectl
    

    winget install -e --id Kubernetes.kubectl
    
  2. 설치한 버전이 최신 버전인지 확인한다.

    kubectl version --client
    
  3. 홈 디렉터리로 이동한다.

    # cmd.exe를 사용한다면, 다음을 실행한다. cd %USERPROFILE%
    cd ~
    
  4. .kube 디렉터리를 생성한다.

    mkdir .kube
    
  5. 금방 생성한 .kube 디렉터리로 이동한다.

    cd .kube
    
  6. 원격 쿠버네티스 클러스터를 사용하도록 kubectl을 구성한다.

    New-Item config -type file
    

kubectl 구성 확인

kubectl이 쿠버네티스 클러스터를 찾아 접근하려면, kube-up.sh를 사용하여 클러스터를 생성하거나 Minikube 클러스터를 성공적으로 배포할 때 자동으로 생성되는 kubeconfig 파일이 필요하다. 기본적으로, kubectl 구성은 ~/.kube/config 에 있다.

클러스터 상태를 가져와서 kubectl이 올바르게 구성되어 있는지 확인한다.

kubectl cluster-info

URL 응답이 표시되면, kubectl이 클러스터에 접근하도록 올바르게 구성된 것이다.

다음과 비슷한 메시지가 표시되면, kubectl이 올바르게 구성되지 않았거나 쿠버네티스 클러스터에 연결할 수 없다.

The connection to the server <server-name:port> was refused - did you specify the right host or port?

예를 들어, 랩톱에서 로컬로 쿠버네티스 클러스터를 실행하려면, Minikube와 같은 도구를 먼저 설치한 다음 위에서 언급한 명령을 다시 실행해야 한다.

kubectl cluster-info가 URL 응답을 반환하지만 클러스터에 접근할 수 없는 경우, 올바르게 구성되었는지 확인하려면 다음을 사용한다.

kubectl cluster-info dump

선택적 kubectl 구성 및 플러그인

셸 자동 완성 활성화

kubectl은 Bash, Zsh, Fish, 및 PowerShell에 대한 자동 완성 지원을 제공하므로 입력을 위한 타이핑을 많이 절약할 수 있다.

다음은 PowerShell에 대한 자동 완성을 설정하는 절차이다.

PowerShell용 kubectl 자동 완성 스크립트는 kubectl completion powershell 명령으로 생성할 수 있다.

모든 셸 세션에서 사용하려면, $PROFILE 파일에 다음을 추가한다.

kubectl completion powershell | Out-String | Invoke-Expression

이 명령은 PowerShell을 실행할 때마다 자동 완성 스크립트를 재생성한다. 아니면, 생성된 스크립트를 $PROFILE 파일에 직접 추가할 수도 있다.

생성된 스크립트를 $PROFILE 파일에 직접 추가하려면, PowerShell 프롬프트에서 다음 명령줄을 실행한다.

kubectl completion powershell >> $PROFILE

셸을 다시 불러오면, kubectl 자동 완성이 동작할 것이다.

kubectl convert 플러그인 설치

이것은 쿠버네티스 커맨드 라인 도구인 kubectl의 플러그인으로서, 특정 버전의 쿠버네티스 API로 작성된 매니페스트를 다른 버전으로 변환할 수 있도록 한다. 이것은 매니페스트를 최신 쿠버네티스 릴리스의 사용 중단되지 않은 API로 마이그레이션하는 데 특히 유용하다. 더 많은 정보는 다음의 사용 중단되지 않은 API로 마이그레이션을 참고한다.

  1. 다음 명령으로 최신 릴리스를 다운로드한다.

    curl.exe -LO "https://dl.k8s.io/release/v1.31.0/bin/windows/amd64/kubectl-convert.exe"
    
  2. 바이너리를 검증한다. (선택 사항)

    kubectl-convert 체크섬(checksum) 파일을 다운로드한다.

    curl.exe -LO "https://dl.k8s.io/v1.31.0/bin/windows/amd64/kubectl-convert.exe.sha256"
    

    kubectl-convert 바이너리를 체크섬 파일을 통해 검증한다.

    • 커맨드 프롬프트를 사용하는 경우, CertUtil 의 출력과 다운로드한 체크섬 파일을 수동으로 비교한다.

      CertUtil -hashfile kubectl-convert.exe SHA256
      type kubectl-convert.exe.sha256
      
    • PowerShell을 사용하는 경우, -eq 연산자를 통해 True 또는 False 결과가 출력되는 자동 검증을 수행한다.

      $($(CertUtil -hashfile .\kubectl-convert.exe SHA256)[1] -replace " ", "") -eq $(type .\kubectl-convert.exe.sha256)
      
  3. kubectl-convert 바이너리가 있는 폴더를 PATH 환경 변수의 앞부분 또는 뒷부분에 추가

  4. 플러그인이 정상적으로 설치되었는지 확인한다.

    kubectl convert --help
    

    에러가 출력되지 않는다면, 플러그인이 정상적으로 설치된 것이다.

다음 내용

2 - 모니터링, 로깅, 및 디버깅

클러스터를 트러블슈팅할 수 있도록 모니터링과 로깅을 설정하거나, 컨테이너화된 애플리케이션을 디버깅한다.

때때로 문제가 발생할 수 있다. 이 가이드는 이러한 상황을 해결하기 위해 작성되었다. 문제 해결에는 다음 두 가지를 참고해 볼 수 있다.

  • 애플리케이션 디버깅하기 - 쿠버네티스에 코드를 배포하였지만 제대로 동작하지 않는 사용자들에게 유용한 가이드이다.
  • 클러스터 디버깅하기 - 쿠버네티스 클러스터에 문제를 겪고 있는 클러스터 관리자 혹은 기분이 나쁜 사람들에게 유용한 가이드이다.

여러분이 현재 사용중인 릴리스에 대한 알려진 이슈들을 다음의 릴리스 페이지에서 확인해 볼 수도 있다.

도움 받기

여러분의 문제가 위에 소개된 어떠한 가이드로도 해결할 수 없다면, 쿠버네티스 커뮤니티로부터 도움을 받을 수 있는 다양한 방법들을 시도해 볼 수 있다.

질문

이 사이트의 문서들은 다양한 질문들에 대한 답변을 제공할 수 있도록 구성되어 있다. 개념은 쿠버네티스의 아키텍처와 각 컴포넌트들이 어떻게 동작하는지에 대해 설명하고, 시작하기는 쿠버네티스를 시작하는 데 유용한 지침들을 제공한다. 태스크는 흔히 사용되는 작업들을 수행하는 방법에 대해 소개하고, 튜토리얼은 실무, 산업 특화 혹은 종단간 개발에 특화된 시나리오를 통해 차근차근 설명한다. 레퍼런스 섹션에서는 쿠버네티스 APIkubectl과 같은 커맨드 라인 인터페이스(CLI)에 대한 상세한 설명을 다룬다.

도와주세요! 내 질문이 다뤄지지 않았어요! 도움이 필요해요!

스택 오버플로우

여러분들이 겪고 있는 문제와 동일한 문제에 대한 도움을 위해 커뮤니티의 다른 사람들이 이미 질문을 올렸을 수 있다. 쿠버네티스 팀은 쿠버네티스 태그가 등록된 글들을 모니터링하고 있다. 발생한 문제에 도움이 되는 기존 질문이 없다면, 해당 질문이 스택 오버플로우에 적합한지새로운 질문을 올리는 방법에 대한 가이드를 읽은 뒤에 새로운 질문을 올리자!

슬랙

쿠버네티스 슬랙의 #kubernetes-users 채널을 통해 쿠버네티스 커뮤니티의 여러 사람들을 접할 수도 있다. 쿠버네티스 슬랙을 사용하기 위해서는 등록이 필요한데, 다음을 통해 채널 초대 요청을 할 수 있다. (누구나 가입할 수 있다). 슬랙 채널은 여러분이 어떠한 질문을 할 수 있도록 언제나 열려있다. 가입하고 나면 여러분의 웹 브라우저나 슬랙 앱을 통해 쿠버네티스 슬랙 에 참여할 수 있다.

쿠버네티스 슬랙에 참여하게 된다면, 다양한 주제의 흥미와 관련된 여러 채널들에 대해 살펴본다. 가령, 쿠버네티스를 처음 접하는 사람이라면 #kubernetes-novice 채널에 가입할 수 있다. 혹은, 만약 당신이 개발자라면 #kubernetes-contributors 채널에 가입할 수 있다.

또한 각 국가 및 사용 언어별 채널들이 여럿 존재한다. 사용하는 언어로 도움을 받거나 정보를 얻기 위해서는 다음의 채널에 참가한다.

국가 / 언어별 슬랙 채널
국가채널
China(중국)#cn-users, #cn-events
Finland(핀란드)#fi-users
France(프랑스)#fr-users, #fr-events
Germany(독일)#de-users, #de-events
India(인도)#in-users, #in-events
Italy(이탈리아)#it-users, #it-events
Japan(일본)#jp-users, #jp-events
Korea(한국)#kr-users
Netherlands(네덜란드)#nl-users
Norway(노르웨이)#norw-users
Poland(폴란드)#pl-users
Russia(러시아)#ru-users
Spain(스페인)#es-users
Sweden(스웨덴)#se-users
Turkey(터키)#tr-users, #tr-events

포럼

공식 쿠버네티스 포럼에 참여하는 것도 추천되는 방법이다. discuss.kubernetes.io.

버그와 기능 추가 요청

만약 여러분이 버그처럼 보이는 것을 발견했거나, 기능 추가 요청을 하기 위해서는 GitHub 이슈 트래킹 시스템을 사용한다.

이슈를 작성하기 전에는, 여러분의 이슈가 기존 이슈에서 이미 다뤄졌는지 검색해 본다.

버그를 보고하는 경우에는, 해당 문제를 어떻게 재현할 수 있는지에 관련된 상세한 정보를 포함한다. 포함되어야 하는 정보들은 다음과 같다.

  • 쿠버네티스 버전: kubectl version
  • 클라우드 프로바이더, OS 배포판, 네트워크 구성, 및 도커 버전
  • 문제를 재현하기 위한 절차

2.1 - 애플리케이션 트러블슈팅하기

일반적인 컨테이너화된 애플리케이션 이슈를 디버깅한다.

이 문서는 컨테이너화된 애플리케이션의 이슈를 해결하기 위한 자원을 담고 있다. 쿠버네티스 리소스(예: 파드, 서비스, 스테이트풀셋)의 일반적 이슈, 컨테이너 종료 메시지 이해에 대한 조언, 실행 중인 컨테이너를 디버그하는 방법 등을 다룬다.

2.1.1 - 파드 디버깅하기

이 가이드는 쿠버네티스에 배포되었지만 제대로 동작하지 않는 애플리케이션을 디버깅하는 방법을 소개한다. 이 가이드는 클러스터 디버깅에 대한 것은 아니다. 클러스터 디버깅에 대해서는 이 가이드를 참고한다.

문제 진단하기

트러블슈팅의 첫 단계는 문제를 파악하는 것이다. 무엇이 문제인가? 파드인가, 레플리케이션 컨트롤러인가, 서비스인가?

파드 디버깅하기

파드 디버깅의 첫 번째 단계는 파드를 살펴 보는 것이다. 다음의 명령어를 사용하여 파드의 현재 상태와 최근 이벤트를 점검한다.

kubectl describe pods ${POD_NAME}

파드 내부 컨테이너의 상태를 확인한다. 모두 Running 상태인가? 최근에 재시작 되었는가?

파드의 상태에 따라 디버깅을 계속한다.

파드가 계속 pending 상태인 경우

파드가 Pending 상태로 멈춰 있는 경우는, 노드에 스케줄 될 수 없음을 의미한다. 일반적으로 이것은 어떤 유형의 리소스가 부족하거나 스케줄링을 방해하는 다른 요인 때문이다. 상단의 kubectl describe ... 명령의 결과를 확인하자. 파드를 스케줄 할 수 없는 사유에 대한 스케줄러의 메세지가 있을 것이다. 다음과 같은 사유가 있을 수 있다.

  • 리소스가 부족한 경우: 사용자 클러스터의 CPU 나 메모리가 고갈되었을 수 있다. 이러한 경우, 파드를 삭제하거나, 리소스 요청을 조정하거나, 클러스터에 노드를 추가해야 한다. 컴퓨트 자원 문서에서 더 많은 정보를 확인한다.

  • hostPort를 사용하고 있는 경우: 파드를 hostPort에 바인딩할 때, 파드가 스케줄링될 수 있는 장소 수 제한이 존재한다. 대부분의 경우 hostPort는 불필요하므로, 파드를 노출하기 위해서는 서비스(Service) 오브젝트 사용을 고려해 본다. hostPort가 꼭 필요하다면 클러스터의 노드 수 만큼만 파드를 스케줄링할 수 있다.

파드가 계속 waiting 상태인 경우

파드가 Waiting 상태에서 멈춘 경우는, 파드가 워커 노드에 스케줄링되었지만 해당 노드에서 실행될 수 없음을 의미한다. 다시 말하지만, kubectl describe ... 명령은 유용한 정보를 제공한다. 파드가 Waiting 상태에서 멈추는 가장 흔한 원인은 이미지 풀링(pulling)에 실패했기 때문이다. 다음의 3가지 사항을 확인한다.

  • 이미지 이름이 올바른지 확인한다.
  • 해당 이미지를 저장소에 푸시하였는가?
  • 이미지가 풀 될 수 있는지 확인하기 위해 수동으로 이미지를 풀 해본다. 예를 들어, PC에서 도커를 사용하는 경우, docker pull <image> 명령을 실행한다.

파드가 손상(crashing)되었거나 양호하지 않을(unhealthy) 경우

일단 사용자의 파드가 스케줄 되면, 구동중인 파드 디버그하기에 있는 방법을 사용하여 디버깅을 할 수 있다.

파드가 running 상태이지만 해야 할 일을 하고 있지 않은 경우

파드가 예상과 다르게 동작 중이라면, 파드 상세(예: 로컬 머신에 있는 mypod.yaml 파일)에 에러가 있었는데 파드 생성 시에 에러가 조용히 지나쳐진 경우일 수 있다. 종종 파드 상세의 들여쓰기가 잘못되었거나, 키 이름에 오타가 있어서 해당 키가 무시되는 일이 있을 수 있다. 예를 들어, commandcommnd로 잘못 기재했다면 해당 파드는 생성은 되지만 명시한 명령줄을 실행하지 않을 것이다.

가장 먼저 해야 할 일은 파드를 삭제한 다음, --validate 옵션을 사용하여 다시 만들어 보는 것이다. 예를 들어, kubectl apply --validate -f mypod.yaml 를 실행한다. commandcommnd로 잘못 기재했다면 다음과 같은 에러가 발생할 것이다.

I0805 10:43:25.129850   46757 schema.go:126] unknown field: commnd
I0805 10:43:25.129973   46757 schema.go:129] this may be a false alarm, see https://github.com/kubernetes/kubernetes/issues/6842
pods/mypod

다음으로 확인할 것은 apiserver를 통해 확인한 파드 상세가 사용자가 의도한 파드 상세(예: 로컬 머신에 있는 yaml 파일)와 일치하는지 여부이다. 예를 들어, kubectl get pods/mypod -o yaml > mypod-on-apiserver.yaml 를 실행한 다음, 원본 파드 상세(mypod.yaml)와 apiserver를 통해 확인한 파드 상세(mypod-on-apiserver.yaml)를 수동으로 비교한다. 보통 원본 버전에는 없지만 "apiserver" 버전에는 있는 줄들이 존재한다. 이는 예상대로이다. 하지만, 원본 버전에는 있지만 "apiserver" 버전에는 없는 줄들이 있다면, 이는 원본 파드 상세에 문제가 있을 수도 있음을 의미한다.

레플리케이션컨트롤러 디버깅하기

레플리케이션컨트롤러의 경우에는 매우 직관적이다. 파드 생성이 가능하거나 또는 불가능한 경우 둘 뿐이다. 레플리케이션컨트롤러가 파드를 생성할 수 없다면, 위의 지침을 참고하여 파드를 디버깅한다.

사용자는 kubectl describe rc ${CONTROLLER_NAME} 을 사용하여 레플리케이션 컨트롤러와 관련된 이벤트를 검사할 수도 있다.

서비스 디버깅하기

서비스는 파드 집합에 대한 로드 밸런싱 기능을 제공한다. 일반적인 몇몇 문제들 때문에 서비스가 제대로 동작하지 않을 수 있다. 다음 지침을 이용하여 서비스 문제를 디버깅할 수 있다.

먼저, 서비스를 위한 엔드포인트가 존재하는지 확인한다. 모든 서비스 오브젝트에 대해, apiserver는 endpoints 리소스를 생성하고 사용 가능한(available) 상태로 만든다.

다음 명령을 사용하여 이 리소스를 볼 수 있다.

kubectl get endpoints ${SERVICE_NAME}

엔드포인트의 수가 해당 서비스에 속하는 파드의 수와 일치하는지 확인한다. 예를 들어, 서비스가 레플리카 3개인 nginx 컨테이너를 위한 것이라면, 서비스의 엔드포인트 항목에서 서로 다른 3개의 IP 주소가 확인되어야 한다.

서비스에 엔드포인트가 없는 경우

엔드포인트가 없는 상태라면, 서비스가 사용 중인 레이블을 이용하여 파드 목록을 조회해 본다. 다음과 같은 레이블을 갖는 서비스를 가정한다.

...
spec:
  - selector:
     name: nginx
     type: frontend

다음의 명령을 사용하여,

kubectl get pods --selector=name=nginx,type=frontend

이 셀렉터에 매치되는 파드 목록을 조회할 수 있다. 서비스에 속할 것으로 예상하는 파드가 모두 조회 결과에 있는지 확인한다. 파드의 containerPort가 서비스의 targetPort와 일치하는지 확인한다.

네트워크 트래픽이 포워드되지 않는 경우

서비스 디버깅하기에서 더 많은 정보를 확인한다.

다음 내용

위의 방법 중 어떤 것으로도 문제가 해결되지 않는다면, 서비스 디버깅하기 문서를 참조하여 서비스가 실행 중인지, 서비스에 엔드포인트가 있는지, 파드가 실제로 서빙 중인지 확인한다. 예를 들어, DNS가 실행 중이고, iptables 규칙이 설정되어 있고, kube-proxy가 정상적으로 동작하는 것으로 보이는 상황이라면, 위와 같은 사항을 확인해 볼 수 있다.

트러블슈팅 문서에서 더 많은 정보를 볼 수도 있다.

2.1.2 - 서비스 디버깅하기

쿠버네티스를 새로 설치할 때 자주 발생하는 문제 중 하나는 서비스가 제대로 작동하지 않는 현상이다. 디플로이먼트(또는 다른 워크로드 컨트롤러)를 통해 파드를 실행하고 서비스를 생성했지만, 해당 서비스에 엑세스하려고 하면 응답이 없는 경우이다. 이 문서를 통해 무엇이 잘못되었는지 파악하는 데 도움이 되기를 바란다.

파드 안에서 명령어 실행

이 페이지의 많은 단계에서, 클러스터 내에 실행 중인 파드가 어떤 것을 보고 있는지 알고 싶을 것이다. 이를 수행하는 가장 간단한 방법은 대화형 busybox 파드를 실행하는 것이다:

kubectl run -it --rm --restart=Never busybox --image=gcr.io/google-containers/busybox sh

사용하려는 파드가 이미 실행 중인 경우, 다음을 사용하여 명령을 실행할 수 있다.

kubectl exec <POD-NAME> -c <CONTAINER-NAME> -- <COMMAND>

설정

연습을 위해, 파드를 몇 개 실행한다. 자신이 관리하는 서비스를 디버깅하는 경우에는 세부 사항을 상황에 맞게 변경하고, 아니면 아래의 과정을 그대로 수행하여 두 번째 데이터 포인트를 얻을 수 있다.

kubectl create deployment hostnames --image=registry.k8s.io/serve_hostname
deployment.apps/hostnames created

kubectl 명령어는 생성되거나 변경된 리소스의 유형과 이름을 출력하여, 여기서 출력된 리소스 유형과 이름은 다음 명령어에 사용할 수 있다.

디플로이먼트의 레플리카를 3개로 확장한다.

kubectl scale deployment hostnames --replicas=3
deployment.apps/hostnames scaled

참고로, 위의 명령들을 실행하여 디플로이먼트를 실행하는 것은 다음과 같은 YAML을 사용하는 것과 동일하다.

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: hostnames
  name: hostnames
spec:
  selector:
    matchLabels:
      app: hostnames
  replicas: 3
  template:
    metadata:
      labels:
        app: hostnames
    spec:
      containers:
      - name: hostnames
        image: registry.k8s.io/serve_hostname

"app" 레이블은 kubectl create deployment명령에 의해 디플로이먼트의 이름으로 자동으로 설정된다.

파드가 실행 중인지 확인할 수 있다.

kubectl get pods -l app=hostnames
NAME                        READY     STATUS    RESTARTS   AGE
hostnames-632524106-bbpiw   1/1       Running   0          2m
hostnames-632524106-ly40y   1/1       Running   0          2m
hostnames-632524106-tlaok   1/1       Running   0          2m

파드가 서빙 중인지도 확인할 수 있다. 파드 IP주소의 목록을 가져온 뒤 이를 직접 테스트할 수 있다.

kubectl get pods -l app=hostnames \
    -o go-template='{{range .items}}{{.status.podIP}}{{"\n"}}{{end}}'
10.244.0.5
10.244.0.6
10.244.0.7

연습에 사용된 컨테이너 예제는 포트 9376에서 HTTP를 통해 호스트이름을 리턴하지만, 본인의 앱을 디버깅하는 경우, 포트 번호를 파드가 수신 대기 중인 포트 번호로 대체하면 된다.

파드 내에서 다음을 실행한다.

for ep in 10.244.0.5:9376 10.244.0.6:9376 10.244.0.7:9376; do
    wget -qO- $ep
done

다음과 같은 결과가 출력될 것이다.

hostnames-632524106-bbpiw
hostnames-632524106-ly40y
hostnames-632524106-tlaok

이 단계에서 예상한 응답을 받지 못한 경우, 파드가 정상적이지 않거나 또는 예상했던 포트에서 수신 대기를 하지 않고 있을 가능성이 있다. 어떤 일이 발생하고 있는지 확인하는 데 kubectl logs가 유용할 수 있으며, 또는 파드에 직접 kubectl exec를 실행하고 이를 통해 디버그을 수행해야 할 수도 있다.

지금까지 모든 것이 계획대로 진행되었다면, 서비스가 작동하지 않는 이유를 분석해 볼 수 있다.

서비스가 존재하는가?

여기까지 따라왔다면 아직 실제로 서비스를 생성하지 않았다는 것을 알아차렸을 것이다. 이는 의도적인 것이다. 이 단계는 때때로 잊어버리지만, 가장 먼저 확인해야 할 단계이다.

존재하지 않는 서비스에 액세스하려고 하면 어떤 일이 발생할까? 이름을 이용하여 이 서비스를 사용하는 다른 파드가 있다면 다음과 같은 결과를 얻을 것이다.

wget -O- hostnames
Resolving hostnames (hostnames)... failed: Name or service not known.
wget: unable to resolve host address 'hostnames'

가장 먼저 확인해야 할 것은 서비스가 실제로 존재하는지 여부이다.

kubectl get svc hostnames
No resources found.
Error from server (NotFound): services "hostnames" not found

이제 서비스를 생성하자. 이전에도 언급했듯이, 이것은 연습을 위한 예시이다. 본인의 서비스의 세부 사항을 여기에 입력할 수도 있다.

kubectl expose deployment hostnames --port=80 --target-port=9376
service/hostnames exposed

다시 조회해 본다.

kubectl get svc hostnames
NAME        TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
hostnames   ClusterIP   10.0.1.175   <none>        80/TCP    5s

이제 서비스가 존재하는 것을 확인할 수 있다.

위와 마찬가지로, 위의 명령들을 실행하여 서비스를 실행하는 것은 다음과 같은 YAML을 사용하는 것과 동일하다.

apiVersion: v1
kind: Service
metadata:
  labels:
    app: hostnames
  name: hostnames
spec:
  selector:
    app: hostnames
  ports:
  - name: default
    protocol: TCP
    port: 80
    targetPort: 9376

환경설정의 전체 범위를 강조하기 위해, 여기에서 생성한 서비스는 파드와 다른 포트 번호를 사용한다. 실제 많은 서비스에서, 이러한 값은 동일할 수 있다.

대상 파드에 영향을 미치는 네트워크 폴리시 인그레스 규칙이 있는가?

hostnames-* 파드로 들어오는 트래픽에 영향을 줄 수 있는 네트워크 폴리시 인그레스 규칙을 배포하는 경우, 이를 검토해야 한다.

자세한 내용은 Network Policies를 참고한다.

서비스가 DNS 이름으로 작동하는가?

클라이언트가 서비스를 사용하는 가장 일반적인 방법 중 하나는 DNS 이름을 통하는 것이다.

동일한 네임스페이스안의 파드 안에서, 다음을 실행한다.

nslookup hostnames
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      hostnames
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local

이것이 실패하면, 파드와 서비스가 다른 네임스페이스에 있는 경우일 수 있다. 네임스페이스를 지정한 이름(namespace-qualified name)을 사용해 본다(다시 말하지만, 파드 안에서 다음을 실행한다).

nslookup hostnames.default
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      hostnames.default
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local

이 방법이 성공했다면, 교차 네임스페이스 이름을 사용하기 위해 앱을 조정하거나, 또는 앱과 서비스를 동일한 네임스페이스에서 실행한다. 여전히 실패한다면, 완전히 지정된 이름(fully-qualified name)을 사용한다.

nslookup hostnames.default.svc.cluster.local
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      hostnames.default.svc.cluster.local
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local

여기서 접미사: "default.svc.cluster.local"에 유의한다. "default"는 작업 중인 네임스페이스이다. "svc"는 서비스임을 나타낸다. "cluster.local"은 클러스터 도메인을 나타내며, 클러스터마다 다를 수 있다.

동일한 작업을 클러스터의 노드에서 시도해 볼 수도 있다.

nslookup hostnames.default.svc.cluster.local 10.0.0.10
Server:         10.0.0.10
Address:        10.0.0.10#53

Name:   hostnames.default.svc.cluster.local
Address: 10.0.1.175

완전히 지정된 이름은 조회가 가능하지만 상대적인 이름은 조회를 할 수 없는 경우, 파드의 /etc/resolv.conf 파일이 올바른지 확인해야 한다. 파드 내에서 다음을 실행한다.

cat /etc/resolv.conf

다음과 같은 내용이 표시될 것이다.

nameserver 10.0.0.10
search default.svc.cluster.local svc.cluster.local cluster.local example.com
options ndots:5

nameserver 라인은 클러스터의 DNS 서비스를 나타내야 한다. 이는 --cluster-dns 플래그와 함께 kubelet으로 전달된다.

search 라인은 서비스 이름을 찾을 수 있는 적절한 접미사가 포함되어야 한다. 위 예시의 경우, ("default.svc.cluster.local" ->) 로컬 네임스페이스의 서비스, ("svc.cluster.local" ->) 모든 네임스페이스의 서비스, 마지막으로 클러스터 ("cluster.local" ->) 클러스터 범위에서 이름을 찾는다. 클러스터의 구성에 따라 그 뒤에 추가 레코드를 기입할 수도 있다(총 6개까지). 클러스터 접미사는 --cluster-domain 플래그와 함께 kubelet에 전달된다. 이 문서에서, 클러스터 접미사는 "cluster.local"로 간주된다. 당신의 클러스터가 다르게 구성되었을 수도 있으며, 이 경우 이전의 모든 명령어에서 이를 변경해야 한다.

options 라인의 ndots 값은 DNS 클라이언트 라이브러리가 모든 탐색 경우의 수를 고려할 수 있을 만큼 충분히 높게 설정되어야 한다. 쿠버네티스는 기본적으로 5로 설정하며, 이는 쿠버네티스가 생성하는 모든 DNS 이름을 포함할 수 있을 만큼 충분히 높다.

DNS 이름으로 동작하는 서비스가 하나라도 있는가?

위의 작업이 계속 실패하면, 서비스에 대해 DNS 조회가 작동하지 않는 것이다. 한 걸음 뒤로 물러나서 어떤 것이 작동하지 않는지 확인할 수 있다. 쿠버네티스 마스터 서비스는 항상 작동해야 한다. 파드 내에서 다음을 실행한다.

nslookup kubernetes.default
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes.default
Address 1: 10.0.0.1 kubernetes.default.svc.cluster.local

이것이 실패하면, 이 문서의 kube-proxy 섹션을 참고하거나 본 문서의 맨 위로 돌아가서 다시 시작한다. 단, 서비스를 디버깅하는 대신, DNS 서비스를 디버그한다.

서비스가 IP로 작동하는가?

DNS가 작동하는 것을 확인했다고 가정하면, 다음 테스트는 서비스가 IP 주소로 작동하는지 확인하는 것이다. 클러스터의 파드에서, 서비스의 IP에 액세스한다(위와 같이 kubectl get을 사용).

for i in $(seq 1 3); do 
    wget -qO- 10.0.1.175:80
done

이렇게 하면 다음과 같은 결과가 나타난다.

hostnames-632524106-bbpiw
hostnames-632524106-ly40y
hostnames-632524106-tlaok

서비스가 작동하는 경우, 올바른 응답을 받았을 것이다. 그렇지 않은 경우, 잘못되어 있을 수 있는 여러가지 사항이 있다. 아래의 내용을 계속 읽어 본다.

서비스가 올바르게 정의되었는가?

몇 차례 강조하지만, 서비스가 정확하게 기재되어 있고 파드의 포트와 일치하는지 두 번, 세 번 확인해야 한다. 아래의 명령을 실행하여 서비스를 다시 조회해 본다.

kubectl get service hostnames -o json
{
    "kind": "Service",
    "apiVersion": "v1",
    "metadata": {
        "name": "hostnames",
        "namespace": "default",
        "uid": "428c8b6c-24bc-11e5-936d-42010af0a9bc",
        "resourceVersion": "347189",
        "creationTimestamp": "2015-07-07T15:24:29Z",
        "labels": {
            "app": "hostnames"
        }
    },
    "spec": {
        "ports": [
            {
                "name": "default",
                "protocol": "TCP",
                "port": 80,
                "targetPort": 9376,
                "nodePort": 0
            }
        ],
        "selector": {
            "app": "hostnames"
        },
        "clusterIP": "10.0.1.175",
        "type": "ClusterIP",
        "sessionAffinity": "None"
    },
    "status": {
        "loadBalancer": {}
    }
}
  • 액세스하려는 서비스 포트가 spec.ports[]에 나열되어 있는가?
  • 파드에 대한 targetPort가 올바른가(일부 파드는 서비스와 다른 포트를 사용한다)?
  • 숫자 포트를 사용하려는 경우, 자료형이 숫자(9376)인가 문자열 "9376"인가?
  • 이름이 부여된 포트를 사용하려는 경우, 파드가 해당 이름의 포트를 노출하는가?
  • 포트의 'protocol'이 파드의 것과 일치하는가?

서비스에 엔드포인트가 있는가?

여기까지 왔다면, 서비스가 올바르게 정의되어 있고 DNS에 의해 해석될 수 있음을 확인한 것이다. 이제 실행 중인 파드가 서비스에 의해 실제로 선택되고 있는지 확인한다.

이전에 파드가 실행 중임을 확인했다. 다음 명령을 통해 다시 확인할 수 있다.

kubectl get pods -l app=hostnames
NAME                        READY     STATUS    RESTARTS   AGE
hostnames-632524106-bbpiw   1/1       Running   0          1h
hostnames-632524106-ly40y   1/1       Running   0          1h
hostnames-632524106-tlaok   1/1       Running   0          1h

-l app=hostnames 인수는 서비스에 구성된 레이블 셀렉터이다.

"AGE" 열은 파드가 실행된 지 약 1시간 되었다고 표시하는 것으로, 이는 파드가 제대로 실행되고 있으며 충돌하지 않음을 의미한다.

"RESTARTS" 열은 파드가 자주 충돌하지 않거나 다시 시작되지 않음을 나타낸다. 잦은 재시작은 간헐적인 연결 문제를 발생할 수 있다. 재시작 횟수가 높으면, 파드를 디버깅하는 방법에 대해 참고한다.

쿠버네티스 시스템 내부에는 모든 서비스의 셀렉터를 평가하고 그 결과를 해당 엔드포인트 오브젝트에 저장하는 제어 루프가 있다.

kubectl get endpoints hostnames

NAME        ENDPOINTS
hostnames   10.244.0.5:9376,10.244.0.6:9376,10.244.0.7:9376

위의 결과를 통해 엔드포인트 컨트롤러가 서비스에 대한 올바른 파드를 찾았음을 알 수 있다. ENDPOINTS 열이 <none>인 경우, 서비스의 spec.selector 필드가 파드의 metadata.labels 값을 실제로 선택하는지 확인해야 한다. 흔한 실수로, kubectl run 명령으로 디플로이먼트도 생성할 수 있었던 1.18 이전 버전에서, 서비스는 app=hostnames 를 이용하여 파드를 선택하지만 디플로이먼트는 run=hostnames 를 이용하여 파드를 선택하는 것과 같은 오타, 또는 기타 오류가 있을 수 있다.

파드가 작동하고 있는가?

여기까지 왔다면, 서비스가 존재하며 이 서비스가 파드를 선택하고 있는 것이다. 파드 자체에 대해서는 이 연습의 시작부분에서 확인했었다. 이제 파드가 실제로 작동하는지 다시 확인해 보자. 위에서 나열된 엔드포인트를 이용하여 서비스 메카니즘을 우회하고 파드에 직접 접근할 수 있다.

파드 내에서 다음을 실행한다.

for ep in 10.244.0.5:9376 10.244.0.6:9376 10.244.0.7:9376; do
    wget -qO- $ep
done

이렇게 하면 다음과 같은 결과가 나타난다.

hostnames-632524106-bbpiw
hostnames-632524106-ly40y
hostnames-632524106-tlaok

엔드포인트 목록의 각 파드가 호스트네임을 반환할 것이라고 예상할 수 있다. 파드가 호스트네임을 반환하지 않는다면 (또는 파드가 정상 동작을 하지 않는다면) 무슨 일이 일어나고 있는지 조사해야 한다.

kube-proxy가 작동하는가?

여기까지 진행했다면, 서비스가 실행 중이고, 엔드포인트가 있으며, 파드가 실제로 서빙하고 있는 것이다. 이 시점에서는, 전체 서비스 프록시 메커니즘이 의심된다. 하나씩 확인하기로 한다.

kube-proxy는 쿠버네티스 서비스의 기본 구현이며 대부분의 클러스터에서 사용하고 있다. kube-proxy는 모든 노드에서 실행되며, 서비스 추상화를 제공하기 위한 메커니즘 일부를 구성하는 프로그램이다. 사용자의 클러스터에서 kube-proxy를 사용하지 않는 경우, 다음 섹션이 적용되지 않으며, 사용 중인 서비스 구현에 대한 내용을 조사해야 할 것이다.

kube-proxy가 실행 중인가?

노드에서 kube-proxy가 실행 중인지 확인한다. 다음의 명령을 노드에서 직접 실행하면, 아래와 같은 결과를 얻을 수 있다.

ps auxw | grep kube-proxy
root  4194  0.4  0.1 101864 17696 ?    Sl Jul04  25:43 /usr/local/bin/kube-proxy --master=https://kubernetes-master --kubeconfig=/var/lib/kube-proxy/kubeconfig --v=2

다음으로, 반드시 되어야 하는 것(예: 마스터와 통신)이 잘 되는지를 확인한다. 이를 수행하려면, 로그를 확인한다. 로그에 액세스하는 방법은 노드의 OS 별로 다르다. 일부 OS에서는 /var/log/kube-proxy.log와 같은 파일이다. 반면 어떤 OS에서는 로그에 액세스하기 위해 journalctl을 사용한다. 다음과 같은 내용이 표시될 것이다.

I1027 22:14:53.995134    5063 server.go:200] Running in resource-only container "/kube-proxy"
I1027 22:14:53.998163    5063 server.go:247] Using iptables Proxier.
I1027 22:14:54.038140    5063 proxier.go:352] Setting endpoints for "kube-system/kube-dns:dns-tcp" to [10.244.1.3:53]
I1027 22:14:54.038164    5063 proxier.go:352] Setting endpoints for "kube-system/kube-dns:dns" to [10.244.1.3:53]
I1027 22:14:54.038209    5063 proxier.go:352] Setting endpoints for "default/kubernetes:https" to [10.240.0.2:443]
I1027 22:14:54.038238    5063 proxier.go:429] Not syncing iptables until Services and Endpoints have been received from master
I1027 22:14:54.040048    5063 proxier.go:294] Adding new service "default/kubernetes:https" at 10.0.0.1:443/TCP
I1027 22:14:54.040154    5063 proxier.go:294] Adding new service "kube-system/kube-dns:dns" at 10.0.0.10:53/UDP
I1027 22:14:54.040223    5063 proxier.go:294] Adding new service "kube-system/kube-dns:dns-tcp" at 10.0.0.10:53/TCP

마스터와 통신할 수 없다는 오류 메시지가 표시되면 노드 구성 및 설치 단계를 다시 확인해야 한다.

kube-proxy가 올바르게 실행되지 않는 이유 중 하나로 필수 conntrack 바이너리를 찾을 수 없는 경우가 있을 수 있다. 클러스터를 설치하는 방법(예, 사전에 준비 없이 쿠버네티스를 설치)에 따라 일부 리눅스 시스템에서 발생할 수 있다. 이 경우, conntrack 패키지를 수동으로 설치(예: 우분투에서 sudo apt install conntrack)한 다음 다시 시도해야 한다.

Kube-proxy는 몇 가지 모드 중 하나로 실행할 수 있다. 위에 나열된 로그에서, Using iptables Proxier 라인은 kube-proxy가 "iptables" 모드로 실행 중임을 나타낸다. 가장 일반적인 다른 모드는 "ipvs"이다.

Iptables 모드

"iptables" 모드일 때, 노드에서 다음 명령을 실행하면 아래와 같은 내용이 표시될 것이다.

iptables-save | grep hostnames
-A KUBE-SEP-57KPRZ3JQVENLNBR -s 10.244.3.6/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-57KPRZ3JQVENLNBR -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.3.6:9376
-A KUBE-SEP-WNBA2IHDGP2BOBGZ -s 10.244.1.7/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-WNBA2IHDGP2BOBGZ -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.1.7:9376
-A KUBE-SEP-X3P2623AGDH6CDF3 -s 10.244.2.3/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-X3P2623AGDH6CDF3 -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.2.3:9376
-A KUBE-SERVICES -d 10.0.1.175/32 -p tcp -m comment --comment "default/hostnames: cluster IP" -m tcp --dport 80 -j KUBE-SVC-NWV5X2332I4OT4T3
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-WNBA2IHDGP2BOBGZ
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-X3P2623AGDH6CDF3
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -j KUBE-SEP-57KPRZ3JQVENLNBR

각 서비스의 포트에 대해, KUBE-SERVICES 내의 하나의 규칙, 그리고 하나의 KUBE-SVC-<hash> 체인이 있어야 한다. 각 파드 엔드포인트에 대해, 해당 KUBE-SVC-<hash>에는 몇 개의 규칙이 있어야 하고, 또한 몇 개의 규칙을 포함하는 하나의 KUBE-SEP-<hash> 체인이 있어야 한다. 정확한 규칙은 사용자의 정확한 설정(노드 포트 및 로드 밸런서 포함)에 따라 다르다.

IPVS 모드

"ipvs" 모드일 때, 노드에서 다음 명령을 실행하면 아래와 같은 내용이 표시될 것이다.

ipvsadm -ln
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
...
TCP  10.0.1.175:80 rr
  -> 10.244.0.5:9376               Masq    1      0          0
  -> 10.244.0.6:9376               Masq    1      0          0
  -> 10.244.0.7:9376               Masq    1      0          0
...

각 서비스의 포트와 모든 NodePort, 외부 IP 및 로드 밸런서 IP에 대해 kube-proxy는 가상 서버를 생성한다. 각 파드 엔드포인트에 대해, kube-proxy는 각 가상 서버에 대응되는 실제 서버를 생성한다. 예제에서, 'hostnames' 서비스(10.0.1.175:80)에는 3개의 엔드포인트(10.244.0.5:9376, 10.244.0.6:9376, 10.244.0.7:9376)가 있다.

kube-proxy가 프록싱을 수행하고 있는가?

위에서 소개한 사례 중 하나를 마주했다고 가정한다면, 노드 중 하나에서 다음을 실행하여 서비스에 IP로 다시 액세스해 본다.

curl 10.0.1.175:80
hostnames-632524106-bbpiw

그래도 실패한다면, kube-proxy 로그에서 다음과 같은 특정 라인을 확인한다.

Setting endpoints for default/hostnames:default to [10.244.0.5:9376 10.244.0.6:9376 10.244.0.7:9376]

이러한 라인이 보이지 않으면, -v 플래그를 4로 설정하여 kube-proxy를 재시작한 다음 로그를 다시 살펴본다.

엣지 케이스: 파드가 서비스 IP를 통해 자신에게 도달하는 데 실패하는 경우

이러한 일이 일어날 것 같지 않을 수도 있지만 실제로 일어나기도 하며, 정상적이라면 제대로 동작해야 한다.

이러한 현상은 네트워크가 '헤어핀' 트래픽(클러스터 내부에서 U턴하는 트래픽)에 대해 제대로 구성되지 않은 경우에 발생할 수 있으며, 이는 주로 kube-proxyiptables 모드에서 실행되고 파드가 브릿지 네트워크에 연결된 경우에 해당할 수 있다. kubelet은 파드가 자신이 속한 서비스 VIP로 접근하는 경우 서비스의 엔드포인트가 이 트래픽을 다시 서비스의 파드로 로드밸런싱하도록 하는 'hairpin-mode[플래그](/docs/reference/command-line-tools-reference/kubelet/)를 노출한다.hairpin-mode플래그는hairpin-veth또는promiscuous-bridge`로 설정되어야 한다.

이 문제를 해결하기 위한 일반적인 단계는 다음과 같다.

  • hairpin-modehairpin-veth 또는 promiscuous-bridge로 설정되어 있는지 확인한다. 아래와 같은 내용이 표시되어야 한다. 아래 예시에서 hairpin-modepromiscuous-bridge로 설정되어 있다.
ps auxw | grep kubelet
root      3392  1.1  0.8 186804 65208 ?        Sl   00:51  11:11 /usr/local/bin/kubelet --enable-debugging-handlers=true --config=/etc/kubernetes/manifests --allow-privileged=True --v=4 --cluster-dns=10.0.0.10 --cluster-domain=cluster.local --configure-cbr0=true --cgroup-root=/ --system-cgroups=/system --hairpin-mode=promiscuous-bridge --runtime-cgroups=/docker-daemon --kubelet-cgroups=/kubelet --babysit-daemons=true --max-pods=110 --serialize-image-pulls=false --outofdisk-transition-frequency=0
  • 실제로 적용되어 있는 hairpin-mode가 무엇인지 확인한다. 이를 확인하려면, kubelet 로그를 확인해야 한다. 로그 액세스는 노드 OS에 따라 다르다. 일부 OS에서는 /var/log/kubelet.log와 같은 파일인 반면 다른 OS에서는 journalctl을 사용하여 로그에 액세스한다. 실제로 적용되어 있는 hairpin 모드는 호환성으로 인해 --hairpin-mode 플래그와 일치하지 않을 수 있으므로 주의한다. kubelet.log에 hairpin 이라는 키워드가 포함된 로그 라인이 있는지 확인한다. 아래와 같이 실행 중인 헤어핀 모드를 나타내는 로그 라인이 있을 것이다.
I0629 00:51:43.648698    3252 kubelet.go:380] Hairpin mode set to "promiscuous-bridge"
  • 실행 중인 hairpin 모드가 hairpin-veth인 경우, kubelet이 노드의 /sys에서 작동할 수 있는 권한이 있는지 확인한다. 모든 것이 제대로 작동한다면, 다음 명령을 실행했을 때 아래와 같이 표시될 것이다.
for intf in /sys/devices/virtual/net/cbr0/brif/*; do cat $intf/hairpin_mode; done
1
1
1
1
  • 실행 중인 hairpin 모드가 promiscuous-bridge인 경우, Kubelet이 노드 안에서 리눅스 브릿지를 조작할 수 있는 권한이 있는지 확인한다. cbr0 브릿지가 사용되었고 제대로 구성되어 있다면, 다음 명령을 실행했을 때 아래와 같이 표시될 것이다.
ifconfig cbr0 |grep PROMISC
UP BROADCAST RUNNING PROMISC MULTICAST  MTU:1460  Metric:1
  • 위의 방법 중 어느 것으로도 해결되지 않는다면, 도움을 요청한다.

도움 요청하기

여기까지 왔다면, 아주 이상한 일이 발생하고 있는 것이다. 서비스가 실행 중이고, 엔드포인트가 있으며, 파드가 실제로 서빙하고 있는 상태이다. DNS가 작동 중이고, kube-proxy가 오작동하는 것은 아닌 것 같다. 그럼에도 서비스가 동작하지 않는 것이다. 우리가 도울 수 있도록, 어떤 일이 일어나고 있는지 커뮤니티에 알려주세요!

Slack 또는 포럼 또는 GitHub 에 문의한다.

다음 내용

자세한 내용은 트러블슈팅하기 개요 문서 를 참고한다.

2.1.3 - 스테이트풀셋 디버깅하기

이 문서에서는 스테이트풀셋을 디버깅 방법에 대해 설명한다.

시작하기 전에

  • 쿠버네티스 클러스터가 준비되어 있어야 하고, kubectl 커맨드 라인 도구가 클러스터와 통신할 수 있게 사전에 설정되어 있어야 한다.
  • 조사하고자 하는 스테이트풀셋이 사전에 준비되어 있어야 한다.

스테이트풀셋 디버깅하기

레이블이 app.kubernetes.io/name=MyApp으로 지정된 스테이트풀셋 파드를 전부 나열하기 위해서는 다음의 명령을 사용할 수 있다.

kubectl get pods -l app.kubernetes.io/name=MyApp

만약 오랜 시간동안 Unknown이나 Terminating 상태에 있는 파드들을 발견하였다면, 이러한 파드들을 어떻게 다루는지 알아보기 위해 스테이트풀셋 파드 삭제하기를 참고하길 바란다. 스테이트풀셋에 포함된 개별 파드들을 디버깅하기 위해서는 파드 디버그하기 가이드를 참고하길 바란다.

다음 내용

초기화 컨테이너(Init container) 디버그하기를 참고하길 바란다.

2.1.4 - 파드 실패의 원인 검증하기

이 페이지는 컨테이너 종료 메시지를 읽고 쓰는 방법을 보여준다.

종료 메시지는 컨테이너가 치명적인 이벤트에 대한 정보를, 대시보드나 모니터링 소프트웨어 도구와 같이 쉽게 조회 및 표시할 수 있는 위치에 기록하는 방법을 제공한다. 대부분의 경우에 종료 메시지에 넣는 정보는 일반적으로 쿠버네티스 로그에도 쓰여져야 한다.

시작하기 전에

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

종료 메시지 읽기 및 쓰기

이 예제에서는, 하나의 컨테이너를 실행하는 파드를 생성한다. 파드의 매니페스트는 컨테이너가 시작될 때 수행하는 명령어를 지정한다.

apiVersion: v1
kind: Pod
metadata:
  name: termination-demo
spec:
  containers:
  - name: termination-demo-container
    image: debian
    command: ["/bin/sh"]
    args: ["-c", "sleep 10 && echo Sleep expired > /dev/termination-log"]
  1. 다음의 YAML 설정 파일에 기반한 파드를 생성한다.

    kubectl apply -f https://k8s.io/examples/debug/termination.yaml
    

    YAML 파일에 있는 commandargs 필드에서 컨테이너가 10초 간 잠든 뒤에 "Sleep expired" 문자열을 /dev/termination-log 파일에 기록하는 것을 확인할 수 있다. 컨테이너는 "Sleep expired" 메시지를 기록한 후에 종료된다.

  2. 파드와 관련된 정보를 출력한다.

    kubectl get pod termination-demo
    

    파드가 더 이상 실행되지 않을 때까지 앞선 명령어를 반복한다.

  3. 파드에 관한 상세 정보를 출력한다.

    kubectl get pod termination-demo --output=yaml
    

    결과는 "Sleep expired" 메시지를 포함한다.

    apiVersion: v1
    kind: Pod
    ...
        lastState:
          terminated:
            containerID: ...
            exitCode: 0
            finishedAt: ...
            message: |
              Sleep expired          
            ...
    
  4. 종료 메시지만을 포함하는 출력 결과를 보기 위해서는 Go 템플릿을 사용한다.

    kubectl get pod termination-demo -o go-template="{{range .status.containerStatuses}}{{.lastState.terminated.message}}{{end}}"
    

여러 컨테이너를 포함하는 파드의 경우, Go 템플릿을 사용하여 컨테이너 이름도 출력할 수 있다. 이렇게 하여, 어떤 컨테이너가 실패하는지 찾을 수 있다.

kubectl get pod multi-container-pod -o go-template='{{range .status.containerStatuses}}{{printf "%s:\n%s\n\n" .name .lastState.terminated.message}}{{end}}'

종료 메시지 사용자 정의하기

쿠버네티스는 컨테이너의 terminationMessagePath 필드에 지정된 종료 메시지 파일에서 종료 메시지를 검색하며, 이 필드의 기본값은 /dev/termination-log 이다. 이 필드를 사용자 정의 함으로써 쿠버네티스가 종료 메시지를 검색할 때 다른 파일을 사용하도록 조정할 수 있다. 쿠버네티스는 지정된 파일의 내용을 사용하여 컨테이너의 성공 및 실패에 대한 상태 메시지를 채운다.

종료 메시지는 assertion failure 메세지처럼 간결한 최종 상태로 생성된다. kubelet은 4096 바이트보다 긴 메시지를 자른다.

모든 컨테이너의 총 메시지 길이는 12KiB로 제한되며, 각 컨테이너에 균등하게 분할된다. 예를 들어, 12개의 컨테이너(initContainers 또는 containers)가 있는 경우 각 컨테이너에는 1024 바이트의 사용 가능한 종료 메시지 공간이 있다.

기본 종료 메시지 경로는 /dev/termination-log이다. 파드가 시작된 후에는 종료 메시지 경로를 설정할 수 없다.

다음의 예제에서 컨테이너는, 쿠버네티스가 조회할 수 있도록 /tmp/my-log 파일에 종료 메시지를 기록한다.

apiVersion: v1
kind: Pod
metadata:
  name: msg-path-demo
spec:
  containers:
  - name: msg-path-demo-container
    image: debian
    terminationMessagePath: "/tmp/my-log"

또한 사용자는 추가적인 사용자 정의를 위해 컨테이너의 terminationMessagePolicy 필드를 설정할 수 있다. 이 필드의 기본 값은 File 이며, 이는 오직 종료 메시지 파일에서만 종료 메시지가 조회되는 것을 의미한다. terminationMessagePolicy 필드의 값을 "FallbackToLogsOnError 으로 설정함으로써, 종료 메시지 파일이 비어 있고 컨테이너가 오류와 함께 종료 되었을 경우 쿠버네티스가 컨테이너 로그 출력의 마지막 청크를 사용하도록 지시할 수 있다. 로그 출력은 2048 바이트나 80 행 중 더 작은 값으로 제한된다.

다음 내용

2.1.5 - 초기화 컨테이너(Init Containers) 디버그하기

이 페이지는 초기화 컨테이너의 실행과 관련된 문제를 조사하는 방법에 대해 보여준다. 아래 예제의 커맨드 라인은 파드(Pod)를 <pod-name> 으로, 초기화 컨테이너를 <init-container-1><init-container-2> 로 표시한다.

시작하기 전에

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

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

초기화 컨테이너의 상태 체크하기

사용자 파드의 상태를 표시한다.

kubectl get pod <pod-name>

예를 들어, Init:1/2 상태는 두 개의 초기화 컨테이너 중 하나가 성공적으로 완료되었음을 나타낸다.

NAME         READY     STATUS     RESTARTS   AGE
<pod-name>   0/1       Init:1/2   0          7s

상태값과 그 의미에 대한 추가 예제는 파드 상태 이해하기를 참조한다.

초기화 컨테이너에 대한 상세 정보 조회하기

초기화 컨테이너의 실행에 대한 상세 정보를 확인한다.

kubectl describe pod <pod-name>

예를 들어, 2개의 초기화 컨테이너가 있는 파드는 다음과 같이 표시될 수 있다.

Init Containers:
  <init-container-1>:
    Container ID:    ...
    ...
    State:           Terminated
      Reason:        Completed
      Exit Code:     0
      Started:       ...
      Finished:      ...
    Ready:           True
    Restart Count:   0
    ...
  <init-container-2>:
    Container ID:    ...
    ...
    State:           Waiting
      Reason:        CrashLoopBackOff
    Last State:      Terminated
      Reason:        Error
      Exit Code:     1
      Started:       ...
      Finished:      ...
    Ready:           False
    Restart Count:   3
    ...

파드 스펙의 status.initContainerStatuses 필드를 읽어서 프로그래밍 방식으로 초기화 컨테이너의 상태를 조회할 수도 있다.

kubectl get pod nginx --template '{{.status.initContainerStatuses}}'

이 명령은 원시 JSON 방식으로 위와 동일한 정보를 반환한다.

초기화 컨테이너의 로그 조회하기

초기화 컨테이너의 로그를 확인하기 위해 파드의 이름과 초기화 컨테이너의 이름을 같이 전달한다.

kubectl logs <pod-name> -c <init-container-2>

셸 스크립트를 실행하는 초기화 컨테이너는, 초기화 컨테이너가 실행될 때 명령어를 출력한다. 예를 들어, 스크립트의 시작 부분에 set -x 를 추가하고 실행하여 Bash에서 명령어를 출력할 수 있도록 수행할 수 있다.

파드의 상태 이해하기

Init: 으로 시작하는 파드 상태는 초기화 컨테이너의 실행 상태를 요약한다. 아래 표는 초기화 컨테이너를 디버깅하는 동안 사용자가 확인할 수 있는 몇 가지 상태값의 예이다.

상태의미
Init:N/M파드가 M 개의 초기화 컨테이너를 갖고 있으며, 현재까지 N 개가 완료.
Init:Error초기화 컨테이너 실행 실패.
Init:CrashLoopBackOff초기화 컨테이너가 반복적으로 실행 실패.
Pending파드가 아직 초기화 컨테이너를 실행하지 않음.
PodInitializing or Running파드가 이미 초기화 컨테이너 실행을 완료.

2.1.6 - 동작 중인 파드 디버그

이 페이지는 노드에서 동작 중인(혹은 크래시된) 파드를 디버그하는 방법에 대해 설명한다.

시작하기 전에

  • 여러분의 파드는 이미 스케줄링 되어 동작하고 있을 것이다. 만약 파드가 아직 동작중이지 않다면, 애플리케이션 트러블슈팅을 참고한다.
  • 일부 고급 디버깅 과정에서는 해당 파드가 어떤 노드에서 동작하고 있는지 알아야 하고, 해당 노드에서 쉘 명령어를 실행시킬 수 있어야 한다. kubectl을 사용하는 일반적인 디버깅 과정에서는 이러한 접근 권한이 필요하지 않다.

kubectl describe pod 명령으로 파드 상세사항 가져오기

이 예제에서는 앞의 예제와 비슷하게 두 개의 파드를 생성하기 위해 디플로이먼트를 사용할 것이다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        resources:
          limits:
            memory: "128Mi"
            cpu: "500m"
        ports:
        - containerPort: 80

다음 명령을 실행하여 디플로이먼트를 생성한다.

kubectl apply -f https://k8s.io/examples/application/nginx-with-request.yaml
deployment.apps/nginx-deployment created

다음 명령을 실행하여 파드 상태를 확인한다.

kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-67d4bdd6f5-cx2nz   1/1     Running   0          13s
nginx-deployment-67d4bdd6f5-w6kd7   1/1     Running   0          13s

다음과 같이 kubectl describe pod 명령을 사용하여 각 파드에 대한 더 많은 정보를 가져올 수 있다.

kubectl describe pod nginx-deployment-67d4bdd6f5-w6kd7
Name:         nginx-deployment-67d4bdd6f5-w6kd7
Namespace:    default
Priority:     0
Node:         kube-worker-1/192.168.0.113
Start Time:   Thu, 17 Feb 2022 16:51:01 -0500
Labels:       app=nginx
              pod-template-hash=67d4bdd6f5
Annotations:  <none>
Status:       Running
IP:           10.88.0.3
IPs:
  IP:           10.88.0.3
  IP:           2001:db8::1
Controlled By:  ReplicaSet/nginx-deployment-67d4bdd6f5
Containers:
  nginx:
    Container ID:   containerd://5403af59a2b46ee5a23fb0ae4b1e077f7ca5c5fb7af16e1ab21c00e0e616462a
    Image:          nginx
    Image ID:       docker.io/library/nginx@sha256:2834dc507516af02784808c5f48b7cbe38b8ed5d0f4837f16e78d00deb7e7767
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Thu, 17 Feb 2022 16:51:05 -0500
    Ready:          True
    Restart Count:  0
    Limits:
      cpu:     500m
      memory:  128Mi
    Requests:
      cpu:        500m
      memory:     128Mi
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-bgsgp (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  kube-api-access-bgsgp:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   Guaranteed
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  34s   default-scheduler  Successfully assigned default/nginx-deployment-67d4bdd6f5-w6kd7 to kube-worker-1
  Normal  Pulling    31s   kubelet            Pulling image "nginx"
  Normal  Pulled     30s   kubelet            Successfully pulled image "nginx" in 1.146417389s
  Normal  Created    30s   kubelet            Created container nginx
  Normal  Started    30s   kubelet            Started container nginx

위 예시에서 컨테이너와 파드에 대한 구성 정보(레이블, 리소스 요구사항 등) 및 상태 정보(상태(state), 준비성(readiness), 재시작 횟수, 이벤트 등)를 볼 수 있다.

컨테이너의 상태(state)값은 Waiting, Running, 또는 Terminated 중 하나이다. 각 상태에 따라, 추가 정보가 제공될 것이다. 위 예시에서 Running 상태의 컨테이너에 대해서는 컨테이너의 시작 시각을 시스템이 표시해 주는 것을 볼 수 있다.

Ready 값은 컨테이너의 마지막 준비성 프로브(readiness probe) 통과 여부를 알려 준다. (위 예시에서는 컨테이너에 준비성 프로브가 설정되어 있지 않다. 컨테이너에 준비성 프로브가 설정되어 있지 않으면, 컨테이너는 준비(ready) 상태로 간주된다.)

'재시작 카운트'는 컨테이너가 재시작된 횟수를 보여 준다. 이 정보는 재시작 정책이 'always'로 설정된 컨테이너의 반복적인 강제 종료를 알아차리는 데에 유용하다.

위 예시에서 파드와 연관된 유일한 컨디션(Condition)은 True 또는 False 값을 갖는 Ready 컨디션이며, 이 값이 True라는 것은 파드가 요청을 처리할 수 있으며 모든 동일한 서비스를 묶는 로드 밸런싱 풀에 추가되어야 함을 의미한다.

마지막으로, 파드와 관련된 최근 이벤트 로그가 표시된다. 시스템은 동일한 여러 이벤트를 처음/마지막 발생 시간 및 발생 횟수만 압축적으로 표시한다. "From"은 이벤트 로그를 발생하는 구성 요소를 가리키고, "SubobjectPath"는 참조되는 개체(예: 파드 내 컨테이너)를 나타내며, "Reason" 및 "Message"는 발생한 상황을 알려 준다.

예시: Pending 상태의 파드 디버깅하기

이벤트를 사용하여 감지할 수 있는 일반적인 시나리오는 노드에 할당될 수 없는 파드를 생성하는 경우이다. 예를 들어 파드가 노드에 사용 가능한 리소스보다 더 많은 리소스를 요청하거나, 또는 어떤 노드에도 해당되지 않는 레이블 셀렉터를 명시했을 수 있다. 예를 들어 4개 노드로 구성되며 각 (가상) 머신에 1 CPU가 있는 클러스터가 있는 상황에서, 위 예시 대신 2 레플리카가 아니라 5 레플리카를, 500 밀리코어가 아니라 600 밀리코어를 요청하는 디플로이먼트를 배포했다고 해 보자. 이러한 경우 5개의 파드 중 하나는 스케줄링될 수 없을 것이다. (각 노드에는 fluentd, skydns 등의 클러스터 애드온도 실행되고 있으므로, 만약 1000 밀리코어를 요청했다면 파드가 하나도 스케줄될 수 없었을 것이다.)

kubectl get pods
NAME                                READY     STATUS    RESTARTS   AGE
nginx-deployment-1006230814-6winp   1/1       Running   0          7m
nginx-deployment-1006230814-fmgu3   1/1       Running   0          7m
nginx-deployment-1370807587-6ekbw   1/1       Running   0          1m
nginx-deployment-1370807587-fg172   0/1       Pending   0          1m
nginx-deployment-1370807587-fz9sd   0/1       Pending   0          1m

nginx-deployment-1370807587-fz9sd 파드가 왜 실행되지 않는지를 알아 보려면, pending 상태의 파드에 대해 kubectl describe pod 명령을 실행하고 이벤트(event) 항목을 확인해 볼 수 있다.

kubectl describe pod nginx-deployment-1370807587-fz9sd
  Name:		nginx-deployment-1370807587-fz9sd
  Namespace:	default
  Node:		/
  Labels:		app=nginx,pod-template-hash=1370807587
  Status:		Pending
  IP:
  Controllers:	ReplicaSet/nginx-deployment-1370807587
  Containers:
    nginx:
      Image:	nginx
      Port:	80/TCP
      QoS Tier:
        memory:	Guaranteed
        cpu:	Guaranteed
      Limits:
        cpu:	1
        memory:	128Mi
      Requests:
        cpu:	1
        memory:	128Mi
      Environment Variables:
  Volumes:
    default-token-4bcbi:
      Type:	Secret (a volume populated by a Secret)
      SecretName:	default-token-4bcbi
  Events:
    FirstSeen	LastSeen	Count	From			        SubobjectPath	Type		Reason			    Message
    ---------	--------	-----	----			        -------------	--------	------			    -------
    1m		    48s		    7	    {default-scheduler }			        Warning		FailedScheduling	pod (nginx-deployment-1370807587-fz9sd) failed to fit in any node
  fit failure on node (kubernetes-node-6ta5): Node didn't have enough resource: CPU, requested: 1000, used: 1420, capacity: 2000
  fit failure on node (kubernetes-node-wul5): Node didn't have enough resource: CPU, requested: 1000, used: 1100, capacity: 2000

여기서 스케줄러가 기록한 이벤트를 통해, 파드가 FailedScheduling 사유로 인해 스케줄링되지 않았음을 알 수 있다(다른 이유도 있을 수 있음). 이 메시지를 통해 어떤 노드에도 이 파드를 실행하기 위한 충분한 리소스가 없었음을 알 수 있다.

이 상황을 바로잡으려면, kubectl scale 명령으로 디플로이먼트의 레플리카를 4 이하로 줄일 수 있다. (또는 한 파드를 pending 상태로 두어도 되며, 이렇게 해도 문제는 없다.)

kubectl describe pod 출력의 마지막에 있는 것과 같은 이벤트는 etcd에 기록되어 보존되며 클러스터에 어떤 일이 일어나고 있는지에 대한 높은 차원의 정보를 제공한다. 모든 이벤트의 목록을 보려면 다음 명령을 실행한다.

kubectl get events

그런데 이벤트는 네임스페이스 스코프 객체라는 것을 기억해야 한다. 즉 네임스페이스 스코프 객체에 대한 이벤트(예: my-namespace 네임스페이스의 파드에 어떤 일이 발생했는지)가 궁금하다면, 다음과 같이 커맨드에 네임스페이스를 명시해야 한다.

kubectl get events --namespace=my-namespace

모든 네임스페이스에 대한 이벤트를 보려면, --all-namespaces 인자를 사용할 수 있다.

kubectl describe pod 명령 외에도, kubectl get pod 이상의 정보를 얻는 다른 방법은 kubectl get pod 명령에 출력 형식 플래그 -o yaml 인자를 추가하는 것이다. 이렇게 하면 kubectl describe pod 명령보다 더 많은 정보, 원천적으로는 시스템이 파드에 대해 알고 있는 모든 정보를 YAML 형식으로 볼 수 있다. 여기서 어노테이션(레이블 제한이 없는 키-밸류 메타데이터이며, 쿠버네티스 시스템 구성 요소가 내부적으로 사용함), 재시작 정책, 포트, 볼륨과 같은 정보를 볼 수 있을 것이다.

kubectl get pod nginx-deployment-1006230814-6winp -o yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2022-02-17T21:51:01Z"
  generateName: nginx-deployment-67d4bdd6f5-
  labels:
    app: nginx
    pod-template-hash: 67d4bdd6f5
  name: nginx-deployment-67d4bdd6f5-w6kd7
  namespace: default
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: nginx-deployment-67d4bdd6f5
    uid: 7d41dfd4-84c0-4be4-88ab-cedbe626ad82
  resourceVersion: "1364"
  uid: a6501da1-0447-4262-98eb-c03d4002222e
spec:
  containers:
  - image: nginx
    imagePullPolicy: Always
    name: nginx
    ports:
    - containerPort: 80
      protocol: TCP
    resources:
      limits:
        cpu: 500m
        memory: 128Mi
      requests:
        cpu: 500m
        memory: 128Mi
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-bgsgp
      readOnly: true
  dnsPolicy: ClusterFirst
  enableServiceLinks: true
  nodeName: kube-worker-1
  preemptionPolicy: PreemptLowerPriority
  priority: 0
  restartPolicy: Always
  schedulerName: default-scheduler
  securityContext: {}
  serviceAccount: default
  serviceAccountName: default
  terminationGracePeriodSeconds: 30
  tolerations:
  - effect: NoExecute
    key: node.kubernetes.io/not-ready
    operator: Exists
    tolerationSeconds: 300
  - effect: NoExecute
    key: node.kubernetes.io/unreachable
    operator: Exists
    tolerationSeconds: 300
  volumes:
  - name: kube-api-access-bgsgp
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2022-02-17T21:51:01Z"
    status: "True"
    type: Initialized
  - lastProbeTime: null
    lastTransitionTime: "2022-02-17T21:51:06Z"
    status: "True"
    type: Ready
  - lastProbeTime: null
    lastTransitionTime: "2022-02-17T21:51:06Z"
    status: "True"
    type: ContainersReady
  - lastProbeTime: null
    lastTransitionTime: "2022-02-17T21:51:01Z"
    status: "True"
    type: PodScheduled
  containerStatuses:
  - containerID: containerd://5403af59a2b46ee5a23fb0ae4b1e077f7ca5c5fb7af16e1ab21c00e0e616462a
    image: docker.io/library/nginx:latest
    imageID: docker.io/library/nginx@sha256:2834dc507516af02784808c5f48b7cbe38b8ed5d0f4837f16e78d00deb7e7767
    lastState: {}
    name: nginx
    ready: true
    restartCount: 0
    started: true
    state:
      running:
        startedAt: "2022-02-17T21:51:05Z"
  hostIP: 192.168.0.113
  phase: Running
  podIP: 10.88.0.3
  podIPs:
  - ip: 10.88.0.3
  - ip: 2001:db8::1
  qosClass: Guaranteed
  startTime: "2022-02-17T21:51:01Z"

파드의 로그 확인하기

먼저, 확인하고자 하는 컨테이너의 로그를 확인한다.

kubectl logs ${POD_NAME} ${CONTAINER_NAME}

만약 컨테이너가 이전에 크래시 되었다면, 다음의 명령을 통해 컨테이너의 크래시 로그를 살펴볼 수 있다.

kubectl logs --previous ${POD_NAME} ${CONTAINER_NAME}

exec를 통해 컨테이너 디버깅하기

만약 컨테이너 이미지에 디버깅 도구가 포함되어 있다면, kubectl exec을 통해 특정 컨테이너에서 해당 명령들을 실행할 수 있다. (리눅스나 윈도우 OS를 기반으로 만들어진 이미지에는 대부분 디버깅 도구를 포함하고 있다.)

kubectl exec ${POD_NAME} -c ${CONTAINER_NAME} -- ${CMD} ${ARG1} ${ARG2} ... ${ARGN}

예를 들어, 동작 중인 카산드라 파드의 로그를 살펴보기 위해서는 다음과 같은 명령을 실행할 수 있다.

kubectl exec cassandra -- cat /var/log/cassandra/system.log

kubectl exec-i-t 옵션을 사용해서 터미널에서 접근할 수 있는 쉘을 실행시킬 수도 있다. 예를 들면 다음과 같다.

kubectl exec -it cassandra -- sh

더욱 상세한 내용은 동작중인 컨테이너의 쉘에 접근하기를 참고한다.

임시(ephemeral) 디버그 컨테이너를 사용해서 디버깅하기

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

컨테이너가 크래시 됐거나 distroless 이미지처럼 컨테이너 이미지에 디버깅 도구를 포함하고 있지 않아 kubectl exec로는 충분하지 않은 경우에는 임시(Ephemeral) 컨테이너를 사용하는 것이 인터랙티브한 트러블슈팅에 유용하다.

임시 컨테이너를 사용한 디버깅 예시

kubectl debug 명령어를 사용해서 동작 중인 파드에 임시 컨테이너를 추가할 수 있다. 먼저, 다음과 같이 파드를 추가한다.

kubectl run ephemeral-demo --image=registry.k8s.io/pause:3.1 --restart=Never

이 섹션의 예시에서는 디버깅 도구가 포함되지 않은 이미지의 사례를 보여드리기 위해 pause 컨테이너 이미지를 사용했는데, 이 대신 어떠한 이미지를 사용해도 될 것이다.

만약 kubectl exec을 통해 쉘을 생성하려 한다면 다음과 같은 에러를 확인할 수 있을 텐데, 그 이유는 이 이미지에 쉘이 존재하지 않기 때문이다.

kubectl exec -it ephemeral-demo -- sh
OCI runtime exec failed: exec failed: container_linux.go:346: starting container process caused "exec: \"sh\": executable file not found in $PATH": unknown

이 명령어 대신 kubectl debug을 사용해서 디버깅 컨테이너를 생성할 수 있다. 만약 -i/--interactive 인자를 사용한다면, kubectl은 임시 컨테이너의 콘솔에 자동으로 연결할 것이다.

kubectl debug -it ephemeral-demo --image=busybox --target=ephemeral-demo
Defaulting debug container name to debugger-8xzrl.
If you don't see a command prompt, try pressing enter.
/ #

이 명령어는 새로운 busybox 컨테이너를 추가하고 해당 컨테이너로 연결한다. --target 파라미터를 사용하면 다른 컨테이너의 프로세스 네임스페이스를 대상으로 하게 된다. 여기서는 이 옵션이 꼭 필요한데, kubectl run이 생성하는 파드에 대해 프로세스 네임스페이스 공유를 활성화하지 않기 때문이다.

kubectl describe 명령을 사용하면 새롭게 생성된 임시 컨테이너의 상태를 확인할 수 있다.

kubectl describe pod ephemeral-demo
...
Ephemeral Containers:
  debugger-8xzrl:
    Container ID:   docker://b888f9adfd15bd5739fefaa39e1df4dd3c617b9902082b1cfdc29c4028ffb2eb
    Image:          busybox
    Image ID:       docker-pullable://busybox@sha256:1828edd60c5efd34b2bf5dd3282ec0cc04d47b2ff9caa0b6d4f07a21d1c08084
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Wed, 12 Feb 2020 14:25:42 +0100
    Ready:          False
    Restart Count:  0
    Environment:    <none>
    Mounts:         <none>
...

디버깅이 다 끝나면 kubectl delete을 통해 파드를 제거할 수 있다.

kubectl delete pod ephemeral-demo

파드의 복제본을 이용해서 디버깅하기

때때로 파드의 설정 옵션에 따라 특정 상황에서 트러블슈팅을 하기가 어려울 수 있다. 예를 들어, 만일 여러분의 컨테이너 이미지가 쉘을 포함하고 있지 않거나, 여러분의 애플리케이션이 컨테이너 시작에서 크래시가 발생한다면 kubectl exec을 이용해서 컨테이너를 트러블슈팅할 수 없을 수 있다. 이러한 상황에서는 kubectl debug을 사용해서 파드의 복제본을 디버깅을 위한 추가적인 설정 옵션과 함께 생성할 수 있다.

새 컨테이너와 함께 파드의 복제본 생성하기

만일 여러분의 애플리케이션이 동작은 하고 있지만 예상과는 다르게 동작하는 경우, 파드의 복제본에 새로운 컨테이너를 추가함으로써 추가적인 트러블슈팅 도구들을 파드에 함께 추가할 수 있다.

가령, 여러분의 애플리케이션 컨테이너 이미지는 busybox를 기반으로 하고 있는데 여러분은 busybox에는 없는 디버깅 도구를 필요로 한다고 가정해 보자. 이러한 시나리오는 kubectl run 명령을 통해 시뮬레이션 해볼 수 있다.

kubectl run myapp --image=busybox --restart=Never -- sleep 1d

다음의 명령을 실행시켜 디버깅을 위한 새로운 우분투 컨테이너와 함께 myapp-debug이란 이름의 myapp 컨테이너 복제본을 생성할 수 있다.

kubectl debug myapp -it --image=ubuntu --share-processes --copy-to=myapp-debug
Defaulting debug container name to debugger-w7xmf.
If you don't see a command prompt, try pressing enter.
root@myapp-debug:/#

사용이 모두 끝나면, 디버깅에 사용된 파드를 잊지 말고 정리한다.

kubectl delete pod myapp myapp-debug

명령어를 변경하며 파드의 복제본 생성하기

때때로 컨테이너의 명령어를 변경하는 것이 유용한 경우가 있는데, 예를 들면 디버그 플래그를 추가하기 위해서나 애플리케이션이 크래시 되는 경우이다.

다음의 kubectl run 명령을 통해 즉각적으로 크래시가 발생하는 애플리케이션의 사례를 시뮬레이션해 볼 수 있다.

kubectl run --image=busybox myapp -- false

kubectl describe pod myapp 명령을 통해 이 컨테이너에 크래시가 발생하고 있음을 확인할 수 있다.

Containers:
  myapp:
    Image:         busybox
    ...
    Args:
      false
    State:          Waiting
      Reason:       CrashLoopBackOff
    Last State:     Terminated
      Reason:       Error
      Exit Code:    1

이러한 경우에 kubectl debug을 통해 명령어를 지정함으로써 해당 파드의 복제본을 인터랙티브 쉘로 생성할 수 있다.

kubectl debug myapp -it --copy-to=myapp-debug --container=myapp -- sh
If you don't see a command prompt, try pressing enter.
/ #

이제 인터랙티브 쉘에 접근할 수 있으니 파일 시스템 경로를 확인하거나 동작 중인 컨테이너의 명령어를 직접 확인하는 등의 작업이 가능하다.

사용이 모두 끝나면, 디버깅에 사용된 파드들을 잊지 말고 정리한다.

kubectl delete pod myapp myapp-debug

컨테이너 이미지를 변경하며 파드의 복제본 생성하기

특정한 경우에 여러분은 제대로 동작하지 않는 파드의 이미지를 기존 프로덕션 컨테이너 이미지에서 디버깅 빌드나 추가적인 도구를 포함한 이미지로 변경하고 싶을 수 있다.

이 사례를 보여주기 위해 kubectl run 명령을 통해 파드를 생성하였다.

kubectl run myapp --image=busybox --restart=Never -- sleep 1d

여기서는 kubectl debug 명령을 통해 해당 컨테이너 이미지를 ubuntu로 변경하며 복제본을 생성하였다.

kubectl debug myapp --copy-to=myapp-debug --set-image=*=ubuntu

--set-image의 문법은 kubectl set image와 동일하게 container_name=image 형식의 문법을 사용한다. *=ubuntu라는 의미는 모든 컨테이너의 이미지를 ubuntu로 변경하겠다는 의미이다.

사용이 모두 끝나면, 디버깅에 사용된 파드를 잊지 말고 정리한다.

kubectl delete pod myapp myapp-debug

노드의 쉘을 사용해서 디버깅하기

만약 위의 어떠한 방법도 사용할 수 없다면, 파드가 현재 동작 중인 노드를 찾아 해당 노드에서 실행되는 파드를 생성할 수 있다. 다음 kubectl debug 명령을 통해 해당 노드에서 인터랙티브한 쉘을 생성할 수 있다.

kubectl debug node/mynode -it --image=ubuntu
Creating debugging pod node-debugger-mynode-pdx84 with container debugger on node mynode.
If you don't see a command prompt, try pressing enter.
root@ek8s:/#

노드에서 디버깅 세션을 생성할 때 유의해야 할 점은 다음과 같다.

  • kubectl debug는 노드의 이름에 기반해 새로운 파드의 이름을 자동으로 생성한다.
  • 노드의 루트 파일시스템은 /host에 마운트된다.
  • 파드가 특권을 가지고 있지 않더라도, 컨테이너는 호스트 네임스페이스(IPC, 네트워크, PID 네임스페이스)에서 동작한다. 따라서 몇몇 프로세스 정보를 읽어오거나, chroot /host 등의 작업은 수행될 수 없다.
  • 특권을 가진 파드가 필요한 경우에는 직접 생성한다.

사용이 모두 끝나면, 디버깅에 사용된 파드를 잊지 말고 정리한다.

kubectl delete pod node-debugger-mynode-pdx84

2.1.7 - 동작중인 컨테이너의 셸에 접근하기

이 페이지는 동작중인 컨테이너에 접근하기 위해 kubectl exec을 사용하는 방법에 대해 설명한다.

시작하기 전에

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

컨테이너의 셸에 접근하기

이 예시에서는 하나의 컨테이너를 가진 파드를 생성할 것이다. 이 컨테이너는 nginx 이미지를 실행한다. 해당 파드에 대한 설정 파일은 다음과 같다.

apiVersion: v1
kind: Pod
metadata:
  name: shell-demo
spec:
  volumes:
  - name: shared-data
    emptyDir: {}
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: shared-data
      mountPath: /usr/share/nginx/html
  hostNetwork: true
  dnsPolicy: Default

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/application/shell-demo.yaml

다음을 통해 컨테이너가 동작하고 있는지 확인할 수 있다.

kubectl get pod shell-demo

동작중인 컨테이너의 셸에 접근한다.

kubectl exec --stdin --tty shell-demo -- /bin/bash

셸에 접근해서 다음처럼 루트 디렉토리를 확인해 볼 수 있다.

# Run this inside the container
ls /

접근한 셸에서 다른 명령어도 한번 실행해 보아라. 다음은 실행해 볼 명령의 예시이다.

# You can run these example commands inside the container
ls /
cat /proc/mounts
cat /proc/1/maps
apt-get update
apt-get install -y tcpdump
tcpdump
apt-get install -y lsof
lsof
apt-get install -y procps
ps aux
ps aux | grep nginx

nginx의 최상단 페이지 작성하기

앞에서 생성한 파드에 대한 설정을 살펴보아라. 파드에는 emptyDir 볼륨이 사용되었고, 이 컨테이너는 해당 볼륨을 /usr/share/nginx/html 경로에 마운트하였다.

접근한 셸 환경에서 /usr/share/nginx/html 디렉터리에 index.html 파일을 생성해 보아라.

# Run this inside the container
echo 'Hello shell demo' > /usr/share/nginx/html/index.html

셸 환경에서 nginx 서버에 GET 요청을 시도해보면 다음과 같다.

# Run this in the shell inside your container
apt-get update
apt-get install curl
curl http://localhost/

출력 결과는 여러분이 index.html 파일에 작성한 텍스트를 출력할 것이다.

Hello shell demo

셸 사용이 모두 끝났다면 exit을 입력해 종료하라.

exit # To quit the shell in the container

컨테이너에서 개별 명령어 실행하기

셸이 아닌 일반적인 커맨드 환경에서 다음처럼 동작중인 컨테이너의 환경 변수를 출력할 수 있다.

kubectl exec shell-demo env

다른 명령어도 한번 실행해 보아라. 다음은 실행해 볼 명령의 예시이다.

kubectl exec shell-demo -- ps aux
kubectl exec shell-demo -- ls /
kubectl exec shell-demo -- cat /proc/1/mounts

파드에 한 개 이상의 컨테이너가 있을 경우 셸에 접근하기

만일 파드에 한 개 이상의 컨테이너가 있을 경우, kubectl exec 명령어에 --container 혹은 -c 옵션을 사용해서 컨테이너를 지정하라. 예를 들어, 여러분이 my-pod라는 이름의 파드가 있다고 가정해 보자. 이 파드에는 main-apphelper-app 이라는 이름의 두 컨테이너가 있다. 다음 명령어는 main-app 컨테이너에 대한 셸에 접근할 것이다.

kubectl exec -i -t my-pod --container main-app -- /bin/bash

다음 내용

2.2 - 클러스터 트러블슈팅

일반적인 클러스터 이슈를 디버깅한다.

이 문서는 클러스터 트러블슈팅에 대해 설명한다. 사용자가 겪고 있는 문제의 근본 원인으로서 사용자의 애플리케이션을 이미 배제했다고 가정한다. 애플리케이션 디버깅에 대한 팁은 애플리케이션 트러블슈팅 가이드를 참조한다. 자세한 내용은 트러블슈팅 문서를 참조한다.

클러스터 나열하기

클러스터에서 가장 먼저 디버그해야 할 것은 노드가 모두 올바르게 등록되었는지 여부이다.

다음을 실행한다.

kubectl get nodes

그리고 보일 것으로 예상되는 모든 노드가 존재하고 모두 Ready 상태인지 확인한다.

클러스터의 전반적인 상태에 대한 자세한 정보를 얻으려면 다음을 실행할 수 있다.

kubectl cluster-info dump

예제: 다운(down) 상태이거나 통신이 닿지 않는(unreachable) 노드 디버깅하기

때때로 디버깅할 때 노드의 상태를 확인하는 것이 유용할 수 있다(예를 들어, 어떤 노드에서 실행되는 파드가 이상하게 행동하는 것을 발견했거나, 특정 노드에 파드가 스케줄링되지 않는 이유를 알아보기 위해). 파드의 경우와 마찬가지로, kubectl describe nodekubectl get node -o yaml 명령을 사용하여 노드에 대한 상세 정보를 볼 수 있다. 예를 들어, 노드가 다운 상태(네트워크 연결이 끊어졌거나, kubelet이 종료된 후 재시작되지 못했거나 등)라면 아래와 같은 출력이 나올 것이다. 노드가 NotReady 상태라는 것을 나타내는 이벤트(event)와, 더 이상 실행 중이 아닌 파드(NotReady 상태 이후 5분 뒤에 축출되었음)에 주목한다.

kubectl get nodes
NAME                     STATUS       ROLES     AGE     VERSION
kube-worker-1            NotReady     <none>    1h      v1.23.3
kubernetes-node-bols     Ready        <none>    1h      v1.23.3
kubernetes-node-st6x     Ready        <none>    1h      v1.23.3
kubernetes-node-unaj     Ready        <none>    1h      v1.23.3
kubectl describe node kube-worker-1
Name:               kube-worker-1
Roles:              <none>
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=kube-worker-1
                    kubernetes.io/os=linux
Annotations:        kubeadm.alpha.kubernetes.io/cri-socket: /run/containerd/containerd.sock
                    node.alpha.kubernetes.io/ttl: 0
                    volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp:  Thu, 17 Feb 2022 16:46:30 -0500
Taints:             node.kubernetes.io/unreachable:NoExecute
                    node.kubernetes.io/unreachable:NoSchedule
Unschedulable:      false
Lease:
  HolderIdentity:  kube-worker-1
  AcquireTime:     <unset>
  RenewTime:       Thu, 17 Feb 2022 17:13:09 -0500
Conditions:
  Type                 Status    LastHeartbeatTime                 LastTransitionTime                Reason              Message
  ----                 ------    -----------------                 ------------------                ------              -------
  NetworkUnavailable   False     Thu, 17 Feb 2022 17:09:13 -0500   Thu, 17 Feb 2022 17:09:13 -0500   WeaveIsUp           Weave pod has set this
  MemoryPressure       Unknown   Thu, 17 Feb 2022 17:12:40 -0500   Thu, 17 Feb 2022 17:13:52 -0500   NodeStatusUnknown   Kubelet stopped posting node status.
  DiskPressure         Unknown   Thu, 17 Feb 2022 17:12:40 -0500   Thu, 17 Feb 2022 17:13:52 -0500   NodeStatusUnknown   Kubelet stopped posting node status.
  PIDPressure          Unknown   Thu, 17 Feb 2022 17:12:40 -0500   Thu, 17 Feb 2022 17:13:52 -0500   NodeStatusUnknown   Kubelet stopped posting node status.
  Ready                Unknown   Thu, 17 Feb 2022 17:12:40 -0500   Thu, 17 Feb 2022 17:13:52 -0500   NodeStatusUnknown   Kubelet stopped posting node status.
Addresses:
  InternalIP:  192.168.0.113
  Hostname:    kube-worker-1
Capacity:
  cpu:                2
  ephemeral-storage:  15372232Ki
  hugepages-2Mi:      0
  memory:             2025188Ki
  pods:               110
Allocatable:
  cpu:                2
  ephemeral-storage:  14167048988
  hugepages-2Mi:      0
  memory:             1922788Ki
  pods:               110
System Info:
  Machine ID:                 9384e2927f544209b5d7b67474bbf92b
  System UUID:                aa829ca9-73d7-064d-9019-df07404ad448
  Boot ID:                    5a295a03-aaca-4340-af20-1327fa5dab5c
  Kernel Version:             5.13.0-28-generic
  OS Image:                   Ubuntu 21.10
  Operating System:           linux
  Architecture:               amd64
  Container Runtime Version:  containerd://1.5.9
  Kubelet Version:            v1.23.3
  Kube-Proxy Version:         v1.23.3
Non-terminated Pods:          (4 in total)
  Namespace                   Name                                 CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age
  ---------                   ----                                 ------------  ----------  ---------------  -------------  ---
  default                     nginx-deployment-67d4bdd6f5-cx2nz    500m (25%)    500m (25%)  128Mi (6%)       128Mi (6%)     23m
  default                     nginx-deployment-67d4bdd6f5-w6kd7    500m (25%)    500m (25%)  128Mi (6%)       128Mi (6%)     23m
  kube-system                 kube-proxy-dnxbz                     0 (0%)        0 (0%)      0 (0%)           0 (0%)         28m
  kube-system                 weave-net-gjxxp                      100m (5%)     0 (0%)      200Mi (10%)      0 (0%)         28m
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource           Requests     Limits
  --------           --------     ------
  cpu                1100m (55%)  1 (50%)
  memory             456Mi (24%)  256Mi (13%)
  ephemeral-storage  0 (0%)       0 (0%)
  hugepages-2Mi      0 (0%)       0 (0%)
Events:
...
kubectl get node kube-worker-1 -o yaml
apiVersion: v1
kind: Node
metadata:
  annotations:
    kubeadm.alpha.kubernetes.io/cri-socket: /run/containerd/containerd.sock
    node.alpha.kubernetes.io/ttl: "0"
    volumes.kubernetes.io/controller-managed-attach-detach: "true"
  creationTimestamp: "2022-02-17T21:46:30Z"
  labels:
    beta.kubernetes.io/arch: amd64
    beta.kubernetes.io/os: linux
    kubernetes.io/arch: amd64
    kubernetes.io/hostname: kube-worker-1
    kubernetes.io/os: linux
  name: kube-worker-1
  resourceVersion: "4026"
  uid: 98efe7cb-2978-4a0b-842a-1a7bf12c05f8
spec: {}
status:
  addresses:
  - address: 192.168.0.113
    type: InternalIP
  - address: kube-worker-1
    type: Hostname
  allocatable:
    cpu: "2"
    ephemeral-storage: "14167048988"
    hugepages-2Mi: "0"
    memory: 1922788Ki
    pods: "110"
  capacity:
    cpu: "2"
    ephemeral-storage: 15372232Ki
    hugepages-2Mi: "0"
    memory: 2025188Ki
    pods: "110"
  conditions:
  - lastHeartbeatTime: "2022-02-17T22:20:32Z"
    lastTransitionTime: "2022-02-17T22:20:32Z"
    message: Weave pod has set this
    reason: WeaveIsUp
    status: "False"
    type: NetworkUnavailable
  - lastHeartbeatTime: "2022-02-17T22:20:15Z"
    lastTransitionTime: "2022-02-17T22:13:25Z"
    message: kubelet has sufficient memory available
    reason: KubeletHasSufficientMemory
    status: "False"
    type: MemoryPressure
  - lastHeartbeatTime: "2022-02-17T22:20:15Z"
    lastTransitionTime: "2022-02-17T22:13:25Z"
    message: kubelet has no disk pressure
    reason: KubeletHasNoDiskPressure
    status: "False"
    type: DiskPressure
  - lastHeartbeatTime: "2022-02-17T22:20:15Z"
    lastTransitionTime: "2022-02-17T22:13:25Z"
    message: kubelet has sufficient PID available
    reason: KubeletHasSufficientPID
    status: "False"
    type: PIDPressure
  - lastHeartbeatTime: "2022-02-17T22:20:15Z"
    lastTransitionTime: "2022-02-17T22:15:15Z"
    message: kubelet is posting ready status. AppArmor enabled
    reason: KubeletReady
    status: "True"
    type: Ready
  daemonEndpoints:
    kubeletEndpoint:
      Port: 10250
  nodeInfo:
    architecture: amd64
    bootID: 22333234-7a6b-44d4-9ce1-67e31dc7e369
    containerRuntimeVersion: containerd://1.5.9
    kernelVersion: 5.13.0-28-generic
    kubeProxyVersion: v1.23.3
    kubeletVersion: v1.23.3
    machineID: 9384e2927f544209b5d7b67474bbf92b
    operatingSystem: linux
    osImage: Ubuntu 21.10
    systemUUID: aa829ca9-73d7-064d-9019-df07404ad448

로그 보기

현재로서는 클러스터를 더 깊이 파고들려면 관련 머신에서 로그 확인이 필요하다. 관련 로그 파일 위치는 다음과 같다. (systemd 기반 시스템에서는 journalctl을 대신 사용해야 할 수도 있다.)

컨트롤 플레인 노드

  • /var/log/kube-apiserver.log - API 서버, API 제공을 담당
  • /var/log/kube-scheduler.log - 스케줄러, 스케줄 결정을 담당
  • /var/log/kube-controller-manager.log - 레플리케이션 컨트롤러를 담당하는 컨트롤러

워커 노드

  • /var/log/kubelet.log - Kubelet, 노드에서 컨테이너 실행을 담당
  • /var/log/kube-proxy.log - Kube Proxy, 서비스 로드밸런싱을 담당

클러스터 장애 모드

아래에 일부 오류 상황 예시 및 문제를 완화하기 위해 클러스터 설정을 조정하는 방법을 나열한다.

근본 원인

  • VM(들) 종료
  • 클러스터 내 또는 클러스터와 사용자 간의 네트워크 분할
  • 쿠버네티스 소프트웨어의 충돌
  • 데이터 손실 또는 퍼시스턴트 스토리지 사용 불가 (e.g. GCE PD 또는 AWS EBS 볼륨)
  • 운영자 오류, 예를 들면 잘못 구성된 쿠버네티스 소프트웨어 또는 애플리케이션 소프트웨어

특정 시나리오

  • API 서버 VM 종료 또는 API 서버 충돌
    • 다음의 현상을 유발함
      • 새로운 파드, 서비스, 레플리케이션 컨트롤러를 중지, 업데이트 또는 시작할 수 없다.
      • 쿠버네티스 API에 의존하지 않는 기존 파드 및 서비스는 계속 정상적으로 작동할 것이다.
  • API 서버 백업 스토리지 손실
    • 다음의 현상을 유발함
      • API 서버가 구동되지 않을 것이다.
      • kubelet에 도달할 수 없게 되지만, kubelet이 여전히 동일한 파드를 계속 실행하고 동일한 서비스 프록시를 제공할 것이다.
      • API 서버를 재시작하기 전에, 수동으로 복구하거나 API서버 상태를 재생성해야 한다.
  • 지원 서비스 (노드 컨트롤러, 레플리케이션 컨트롤러 매니저, 스케쥴러 등) VM 종료 또는 충돌
    • 현재 그것들은 API 서버와 같은 위치에 있기 때문에 API 서버와 비슷한 상황을 겪을 것이다.
    • 미래에는 이들도 복제본을 가질 것이며 API서버와 별도로 배치될 수도 있다.
    • 지원 서비스들은 상태(persistent state)를 자체적으로 유지하지는 않는다.
  • 개별 노드 (VM 또는 물리적 머신) 종료
    • 다음의 현상을 유발함
      • 해당 노드의 파드가 실행을 중지
  • 네트워크 분할
    • 다음의 현상을 유발함
      • 파티션 A는 파티션 B의 노드가 다운되었다고 생각한다. 파티션 B는 API 서버가 다운되었다고 생각한다. (마스터 VM이 파티션 A에 있다고 가정)
  • Kubelet 소프트웨어 오류
    • 다음의 현상을 유발함
      • 충돌한 kubelet은 노드에서 새 파드를 시작할 수 없다.
      • kubelet이 파드를 삭제할 수도 있고 삭제하지 않을 수도 있다.
      • 노드는 비정상으로 표시된다.
      • 레플리케이션 컨트롤러는 다른 곳에서 새 파드를 시작한다.
  • 클러스터 운영자 오류
    • 다음의 현상을 유발함
      • 파드, 서비스 등의 손실
      • API 서버 백업 저장소 분실
      • API를 읽을 수 없는 사용자
      • 기타

완화

  • 조치: IaaS VM을 위한 IaaS 공급자의 자동 VM 다시 시작 기능을 사용한다.

    • 다음을 완화할 수 있음: API 서버 VM 종료 또는 API 서버 충돌
    • 다음을 완화할 수 있음: 지원 서비스 VM 종료 또는 충돌
  • 조치: API 서버+etcd가 있는 VM에 IaaS 제공자의 안정적인 스토리지(예: GCE PD 또는 AWS EBS 볼륨)를 사용한다.

    • 다음을 완화할 수 있음: API 서버 백업 스토리지 손실
  • 조치: 고가용성 구성을 사용한다.

    • 다음을 완화할 수 있음: 컨트롤 플레인 노드 종료 또는 컨트롤 플레인 구성 요소(스케줄러, API 서버, 컨트롤러 매니저) 충돌
      • 동시에 발생하는 하나 이상의 노드 또는 구성 요소 오류를 허용한다.
    • 다음을 완화할 수 있음: API 서버 백업 스토리지(i.e., etcd의 데이터 디렉터리) 손실
      • 고가용성 etcd 구성을 사용하고 있다고 가정
  • 조치: API 서버 PD/EBS 볼륨의 주기적인 스냅샷

    • 다음을 완화할 수 있음: API 서버 백업 스토리지 손실
    • 다음을 완화할 수 있음: 일부 운영자 오류 사례
    • 다음을 완화할 수 있음: 일부 쿠버네티스 소프트웨어 오류 사례
  • 조치: 파드 앞에 레플리케이션 컨트롤러와 서비스 사용

    • 다음을 완화할 수 있음: 노드 종료
    • 다음을 완화할 수 있음: Kubelet 소프트웨어 오류
  • 조치: 예기치 않은 재시작을 허용하도록 설계된 애플리케이션(컨테이너)

    • 다음을 완화할 수 있음: 노드 종료
    • 다음을 완화할 수 있음: Kubelet 소프트웨어 오류

다음 내용

2.2.1 - 리소스 메트릭 파이프라인

쿠버네티스에서, 메트릭 API(Metrics API) 는 자동 스케일링 및 비슷한 사용 사례를 지원하기 위한 기본적인 메트릭 집합을 제공한다. 이 API는 노드와 파드의 리소스 사용량 정보를 제공하며, 여기에는 CPU 및 메모리 메트릭이 포함된다. 메트릭 API를 클러스터에 배포하면, 쿠버네티스 API의 클라이언트는 이 정보에 대해 질의할 수 있으며, 질의 권한을 관리하기 위해 쿠버네티스의 접근 제어 메커니즘을 이용할 수 있다.

HorizontalPodAutoscaler(HPA) 및 VerticalPodAutoscaler(VPA)는 사용자의 요구 사항을 만족할 수 있도록 워크로드 레플리카와 리소스를 조정하는 데에 메트릭 API의 데이터를 이용한다.

kubectl top 명령을 이용하여 리소스 메트릭을 볼 수도 있다.

그림 1은 리소스 메트릭 파이프라인의 아키텍처를 나타낸다.

flowchart RL subgraph cluster[클러스터] direction RL S[

] A[Metrics-
Server] subgraph B[노드] direction TB D[cAdvisor] --> C[kubelet] E[컨테이너
런타임] --> D E1[컨테이너
런타임] --> D P[파드 데이터] -.- C end L[API
서버] W[HPA] C ---->|요약
API| A -->|메트릭
API| L --> W end L ---> K[kubectl
top] classDef box fill:#fff,stroke:#000,stroke-width:1px,color:#000; class W,B,P,K,cluster,D,E,E1 box classDef spacewhite fill:#ffffff,stroke:#fff,stroke-width:0px,color:#000 class S spacewhite classDef k8s fill:#326ce5,stroke:#fff,stroke-width:1px,color:#fff; class A,L,C k8s

그림 1. 리소스 메트릭 파이프라인

그림의 오른쪽에서 왼쪽 순으로, 아키텍처 구성 요소는 다음과 같다.

  • cAdvisor: kubelet에 포함된 컨테이너 메트릭을 수집, 집계, 노출하는 데몬

  • kubelet: 컨테이너 리소스 관리를 위한 노드 에이전트. 리소스 메트릭은 kubelet API 엔드포인트 /metrics/resource/stats 를 사용하여 접근 가능하다.

  • 요약 API: /stats 엔드포인트를 통해 사용할 수 있는 노드 별 요약된 정보를 탐색 및 수집할 수 있도록 kubelet이 제공하는 API

  • metrics-server: 각 kubelet으로부터 수집한 리소스 메트릭을 수집 및 집계하는 클러스터 애드온 구성 요소. API 서버는 HPA, VPA 및 kubectl top 명령어가 사용할 수 있도록 메트릭 API를 제공한다. metrics-server는 메트릭 API에 대한 기준 구현(reference implementation) 중 하나이다.

  • 메트릭 API: 워크로드 오토스케일링에 사용되는 CPU 및 메모리 정보로의 접근을 지원하는 쿠버네티스 API. 이를 클러스터에서 사용하려면, 메트릭 API를 제공하는 API 확장(extension) 서버가 필요하다.

메트릭 API

기능 상태: Kubernetes 1.8 [beta]

metrics-server는 메트릭 API에 대한 구현이다. 이 API는 클러스터 내 노드와 파드의 CPU 및 메모리 사용 정보에 접근할 수 있게 해 준다. 이것의 주 역할은 리소스 사용 메트릭을 쿠버네티스 오토스케일러 구성 요소에 제공하는 것이다.

다음은 minikube 노드에 대한 메트릭 API 요청 예시이며 가독성 향상을 위해 jq를 활용한다.

kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes/minikube" | jq '.'

다음은 curl을 이용하여 동일한 API 호출을 하는 명령어다.

curl http://localhost:8080/apis/metrics.k8s.io/v1beta1/nodes/minikube

응답 예시는 다음과 같다.

{
  "kind": "NodeMetrics",
  "apiVersion": "metrics.k8s.io/v1beta1",
  "metadata": {
    "name": "minikube",
    "selfLink": "/apis/metrics.k8s.io/v1beta1/nodes/minikube",
    "creationTimestamp": "2022-01-27T18:48:43Z"
  },
  "timestamp": "2022-01-27T18:48:33Z",
  "window": "30s",
  "usage": {
    "cpu": "487558164n",
    "memory": "732212Ki"
  }
}

다음은 kube-system 네임스페이스 내의 kube-scheduler-minikube 파드에 대한 메트릭 API 요청 예시이며 가독성 향상을 위해 jq를 활용한다.

kubectl get --raw "/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/kube-scheduler-minikube" | jq '.'

다음은 curl을 이용하여 동일한 API 호출을 하는 명령어다.

curl http://localhost:8080/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/kube-scheduler-minikube

응답 예시는 다음과 같다.

{
  "kind": "PodMetrics",
  "apiVersion": "metrics.k8s.io/v1beta1",
  "metadata": {
    "name": "kube-scheduler-minikube",
    "namespace": "kube-system",
    "selfLink": "/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/kube-scheduler-minikube",
    "creationTimestamp": "2022-01-27T19:25:00Z"
  },
  "timestamp": "2022-01-27T19:24:31Z",
  "window": "30s",
  "containers": [
    {
      "name": "kube-scheduler",
      "usage": {
        "cpu": "9559630n",
        "memory": "22244Ki"
      }
    }
  ]
}

메트릭 API는 k8s.io/metrics 저장소에 정의되어 있다. metrics.k8s.io API를 사용하기 위해서는 API 집계(aggregation) 계층을 활성화하고 APIService를 등록해야 한다.

메트릭 API에 대해 더 알아보려면, 리소스 메트릭 API 디자인, metrics-server 저장소리소스 메트릭 API를 참고한다.

리소스 사용량 측정

CPU

CPU는 cpu 단위로 측정된 평균 코어 사용량 형태로 보고된다. 쿠버네티스에서 1 cpu는 클라우드 제공자의 경우 1 vCPU/코어에 해당하고, 베어메탈 인텔 프로세서의 경우 1 하이퍼-스레드에 해당한다.

이 값은 커널(Linux 및 Windows 커널 모두)에서 제공하는 누적 CPU 카운터에 대한 비율을 취하여 얻어진다. CPU 값 계산에 사용된 타임 윈도우는 메트릭 API의 window 필드에 표시된다.

쿠버네티스가 어떻게 CPU 리소스를 할당하고 측정하는지 더 알아보려면, CPU의 의미를 참고한다.

메모리

메모리는 메트릭을 수집하는 순간에 바이트 단위로 측정된 워킹 셋(working set) 형태로 보고된다.

이상적인 환경에서, "워킹 셋"은 메모리가 부족한 상태더라도 해제할 수 없는 사용 중인 메모리의 양이다. 그러나 워킹 셋의 계산 방법은 호스트 OS에 따라 다르며 일반적으로 추정치를 추출하기 위해 휴리스틱을 많이 사용한다.

컨테이너의 워킹 셋에 대한 쿠버네티스 모델은 컨테이너 런타임이 해당 컨테이너와 연결된 익명(anonymous) 메모리를 계산할 것으로 예상한다. 호스트 OS가 항상 페이지를 회수할 수는 없기 때문에, 워킹 셋 메트릭에는 일반적으로 일부 캐시된 (파일 기반) 메모리도 포함된다.

쿠버네티스가 어떻게 메모리 리소스를 할당하고 측정하는지 더 알아보려면, 메모리의 의미를 참고한다.

metrics-server

metrics-server는 kubelet으로부터 리소스 메트릭을 수집하고, 이를 HPA(Horizontal Pod Autoscaler) 및 VPA(Vertical Pod Autoscaler)가 활용할 수 있도록 쿠버네티스 API 서버 내에서 메트릭 API(Metrics API)를 통해 노출한다. kubectl top 명령을 사용하여 이 메트릭을 확인해볼 수도 있다.

metrics-server는 쿠버네티스 API를 사용하여 클러스터의 노드와 파드를 추적한다. metrics-server는 각 노드에 HTTP를 통해 질의하여 메트릭을 수집한다. metrics-server는 또한 파드 메타데이터의 내부적 뷰를 작성하고, 파드 헬스(health)에 대한 캐시를 유지한다. 이렇게 캐시된 파드 헬스 정보는 metrics-server가 제공하는 확장 API(extension API)를 통해 이용할 수 있다.

HPA 질의에 대한 예시에서, 예를 들어 HPA 질의에 대한 경우, metrics-server는 디플로이먼트의 어떤 파드가 레이블 셀렉터 조건을 만족하는지 판별해야 한다.

metrics-server는 각 노드로부터 메트릭을 수집하기 위해 kubelet API를 호출한다. 사용 중인 metrics-server 버전에 따라, 다음의 엔드포인트를 사용한다.

  • v0.6.0 이상: 메트릭 리소스 엔드포인트 /metrics/resource
  • 이전 버전: 요약 API 엔드포인트 /stats/summary

다음 내용

metrics-server에 대한 더 많은 정보는 metrics-server 저장소를 확인한다.

또한 다음을 참고할 수도 있다.

kubelet이 어떻게 노드 메트릭을 제공하는지, 그리고 쿠버네티스 API를 통해 이러한 메트릭에 어떻게 접근하는지 알아보려면, 노드 메트릭 데이터 문서를 참조한다.

2.2.2 - 리소스 모니터링 도구

애플리케이션을 스케일하여 신뢰할 수 있는 서비스를 제공하려면, 애플리케이션이 배포되었을 때 애플리케이션이 어떻게 동작하는지를 이해해야 한다. 컨테이너, 파드, 서비스, 그리고 전체 클러스터의 특성을 검사하여 쿠버네티스 클러스터 내의 애플리케이션 성능을 검사할 수 있다. 쿠버네티스는 각 레벨에서 애플리케이션의 리소스 사용량에 대한 상세 정보를 제공한다. 이 정보는 애플리케이션의 성능을 평가하고 병목 현상을 제거하여 전체 성능을 향상할 수 있게 해준다.

쿠버네티스에서 애플리케이션 모니터링은 단일 모니터링 솔루션에 의존하지 않는다. 신규 클러스터에서는, 리소스 메트릭 또는 완전한 메트릭 파이프라인으로 모니터링 통계를 수집할 수 있다.

리소스 메트릭 파이프라인

리소스 메트릭 파이프라인은 Horizontal Pod Autoscaler 컨트롤러와 같은 클러스터 구성요소나 kubectl top 유틸리티에 관련되어 있는 메트릭들로 제한된 집합을 제공한다. 이 메트릭은 경량의 단기 인메모리 저장소인 metrics-server에 의해서 수집되며 metrics.k8s.io API를 통해 노출된다.

metrics-server는 클러스터 상의 모든 노드를 발견하고 각 노드의 kubelet에 CPU와 메모리 사용량을 질의한다. Kubelet은 쿠버네티스 마스터와 노드 간의 다리 역할을 하면서 머신에서 구동되는 파드와 컨테이너를 관리한다. Kubelet은 각각의 파드를 해당하는 컨테이너에 매치시키고 컨테이너 런타임 인터페이스를 통해 컨테이너 런타임에서 개별 컨테이너의 사용량 통계를 가져온다. 컨테이너를 구현하기 위해 리눅스 cgroup 및 네임스페이스를 활용하는 컨테이너 런타임을 사용하며, 해당 컨테이너 런타임이 사용 통계치를 퍼블리싱 하지 않는 경우, kubelet은 해당 통계치를 (cAdvisor의 코드 사용하여) 직접 조회 할 수 있다. 이런 통계가 어떻게 도착하든 kubelet은 취합된 파드 리소스 사용량 통계를 metric-server 리소스 메트릭 API를 통해 노출한다. 이 API는 kubelet의 인증이 필요한 읽기 전용 포트 상의 /metrics/resource/v1beta1에서 제공된다.

완전한 메트릭 파이프라인

완전한 메트릭 파이프라인은 보다 풍부한 메트릭에 접근할 수 있도록 해준다. 쿠버네티스는 Horizontal Pod Autoscaler와 같은 메커니즘을 활용해서 이런 메트릭에 대한 반응으로 클러스터의 현재 상태를 기반으로 자동으로 스케일링하거나 클러스터를 조정할 수 있다. 모니터링 파이프라인은 kubelet에서 메트릭을 가져와서 쿠버네티스에 custom.metrics.k8s.ioexternal.metrics.k8s.io API를 구현한 어댑터를 통해 노출한다.

CNCF 프로젝트인 프로메테우스는 기본적으로 쿠버네티스, 노드, 프로메테우스 자체를 모니터링할 수 있다. CNCF 프로젝트가 아닌 완전한 메트릭 파이프라인 프로젝트는 쿠버네티스 문서의 범위가 아니다.

다음 내용

다음과 같은 추가 디버깅 도구에 대해 더 알아본다.

2.2.3 - 노드 헬스 모니터링하기

노드 문제 감지기(Node Problem Detector) 는 노드의 헬스에 대해 모니터링 및 보고하는 데몬이다. 노드 문제 감지기를 데몬셋(DaemonSet) 혹은 스탠드얼론 데몬(standalone daemon)으로 실행할 수 있다. 노드 문제 감지기는 다양한 데몬으로부터 노드의 문제에 관한 정보를 다양한 데몬으로부터 수집하고, 이러한 컨디션들을 노드컨디션(NodeCondition)이벤트(Event)형태로 API 서버에 보고한다.

노드 문제 감지기 설치 및 사용 방법을 보려면, 노드 문제 감지기 프로젝트 문서를 참조하자.

시작하기 전에

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

제약 사항

  • 노드 문제 감지기는 파일 기반의 커널 로그만 지원한다. journald 와 같은 로그 도구는 지원하지 않는다.

  • 노드 문제 감지기는 커널 로그 형식을 사용하여 커널 이슈를 보고한다. 커널 로그 형식을 확장하는 방법을 배우려면 기타 로그 형식 지원 추가를 살펴보자.

노드 문제 감지기 활성화하기

일부 클라우드 사업자는 노드 문제 감지기를 애드온 으로서 제공한다. 또한, kubectl을 이용하거나 애드온 파드를 생성하여 노드 문제 감지기를 활성화할 수도 있다.

kubectl를 이용하여 노드 문제 감지기 활성화하기

kubectl은 노드 문제 감지기를 관리하는 가장 유연한 방법이다. 현재 환경에 맞게 조정하거나 사용자 정의 노드 문제를 탐지하기 위해 기본 설정값을 덮어쓸 수 있다. 예를 들면 아래와 같다.

  1. node-problem-detector.yaml와 유사하게 노드 문제 감지기 구성을 생성한다:

    apiVersion: apps/v1
       kind: DaemonSet
       metadata:
         name: node-problem-detector-v0.1
         namespace: kube-system
         labels:
           k8s-app: node-problem-detector
           version: v0.1
           kubernetes.io/cluster-service: "true"
       spec:
         selector:
           matchLabels:
             k8s-app: node-problem-detector  
             version: v0.1
             kubernetes.io/cluster-service: "true"
         template:
           metadata:
             labels:
               k8s-app: node-problem-detector
               version: v0.1
               kubernetes.io/cluster-service: "true"
           spec:
             hostNetwork: true
             containers:
             - name: node-problem-detector
               image: registry.k8s.io/node-problem-detector:v0.1
               securityContext:
                 privileged: true
               resources:
                 limits:
                   cpu: "200m"
                   memory: "100Mi"
                 requests:
                   cpu: "20m"
                   memory: "20Mi"
               volumeMounts:
               - name: log
                 mountPath: /log
                 readOnly: true
             volumes:
             - name: log
               hostPath:
                 path: /var/log/
  2. kubectl을 이용하여 노드 문제 감지기를 시작한다.

    kubectl apply -f https://k8s.io/examples/debug/node-problem-detector.yaml
    

애드온 파드를 이용하여 노드 문제 감지기 활성화하기

만약 커스텀 클러스터 부트스트랩 솔루션을 사용중이고 기본 설정값을 덮어쓸 필요가 없다면, 디플로이먼트를 추가로 자동화하기 위해 애드온 파드를 활용할 수 있다.

node-problem-detector.yaml를 생성하고, 컨트롤 플레인 노드의 애드온 파드의 디렉토리 /etc/kubernetes/addons/node-problem-detector에 설정을 저장한다.

설정 덮어쓰기

노드 문제 감지기를 빌드할 때, 기본 설정이 포함되어 있다.

하지만 컨피그맵(ConfigMap)을 이용해 설정을 덮어쓸 수 있다.

  1. config/ 내의 설정 파일을 변경한다.

  2. node-problem-detector-config 컨피그맵(ConfigMap)을 생성한다.

    kubectl create configmap node-problem-detector-config --from-file=config/
    
  3. 컨피그맵(ConfigMap)을 사용하도록 node-problem-detector.yaml을 변경한다.

    apiVersion: apps/v1
       kind: DaemonSet
       metadata:
         name: node-problem-detector-v0.1
         namespace: kube-system
         labels:
           k8s-app: node-problem-detector
           version: v0.1
           kubernetes.io/cluster-service: "true"
       spec:
         selector:
           matchLabels:
             k8s-app: node-problem-detector  
             version: v0.1
             kubernetes.io/cluster-service: "true"
         template:
           metadata:
             labels:
               k8s-app: node-problem-detector
               version: v0.1
               kubernetes.io/cluster-service: "true"
           spec:
             hostNetwork: true
             containers:
             - name: node-problem-detector
               image: registry.k8s.io/node-problem-detector:v0.1
               securityContext:
                 privileged: true
               resources:
                 limits:
                   cpu: "200m"
                   memory: "100Mi"
                 requests:
                   cpu: "20m"
                   memory: "20Mi"
               volumeMounts:
               - name: log
                 mountPath: /log
                 readOnly: true
               - name: config # config/ 디렉토리를 컨피그맵 볼륨(ConfigMap volume)으로 덮어쓴다
                 mountPath: /config
                 readOnly: true
             volumes:
             - name: log
               hostPath:
                 path: /var/log/
             - name: config # 컨피그맵 볼륨(ConfigMap volume)을 정의한다
               configMap:
                 name: node-problem-detector-config
  4. 새로운 설정 파일을 사용하여 노드 문제 감지기를 재생성한다.

    # 만약 노드 문제 감지기가 동작하고 있다면, 재생성 전 삭제한다
    kubectl delete -f https://k8s.io/examples/debug/node-problem-detector.yaml
    kubectl apply -f https://k8s.io/examples/debug/node-problem-detector-configmap.yaml
    

만약 노드 문제 감지기가 클러스터 애드온으로 실행된 경우, 설정 덮어쓰기가 지원되지 않는다. 애드온 매니저는 컨피그맵(ConfigMap)을 지원하지 않는다.

커널 모니터

커널 모니터는 노드 문제 감지기에서 지원하는 시스템 로그 모니터링 데몬이다. 커널 모니터는 커널 로그를 감시하며, 미리 설정된 규칙에 따라 알려진 커널 이슈를 감지한다.

커널 모니터는 config/kernel-monitor.json에 미리 설정된 규칙 모음과 커널 이슈를 매칭한다. 규칙 리스트는 확장 가능하다. 설정을 덮어쓰기 해 규칙 리스트를 확장할 수 있다.

신규 노드컨디션(NodeConditions) 추가하기

신규 NodeCondition를 지원하려면, config/kernel-monitor.jsonconditions필드 내 조건 정의를 생성해야한다. 예를 들면 아래와 같다.

{
  "type": "NodeConditionType",
  "reason": "CamelCaseDefaultNodeConditionReason",
  "message": "arbitrary default node condition message"
}

신규 문제 감지하기

신규 문제를 감지하려면 config/kernel-monitor.jsonrules필드를 신규 규칙 정의로 확장하면 된다.

{
  "type": "temporary/permanent",
  "condition": "NodeConditionOfPermanentIssue",
  "reason": "CamelCaseShortReason",
  "message": "regexp matching the issue in the kernel log"
}

커널 로그 장치를 위한 경로 설정하기

운영 체제 (OS) 배포판의 커널 로그 경로를 확인한다. 리눅스 커널 로그 장치(log device)는 보통 /dev/kmsg와 같이 표시된다. 하지만, 로그 경로 장소는 OS 배포판마다 상이하다. config/kernel-monitor.jsonlog 필드는 컨테이너 내부의 로그 경로를 나타낸다. log 필드를 노드 문제 감지기가 감시하는 장치 경로와 일치하도록 구성하면 된다.

기타 로그 포맷 지원 추가하기

커널 모니터는 커널 로그의 내부 데이터 구조를 해석하기 위해 Translator 플러그인을 사용한다. 신규 로그 포맷을 사용하기 위해 신규 해석기를 구현할 수 있다.

권장 사항 및 제약 사항

노드 헬스를 모니터링하기 위해 클러스터에 노드 문제 탐지기를 실행할 것을 권장한다. 노드 문제 감지기를 실행할 때, 각 노드에 추가 리소스 오버헤드가 발생할 수 있다. 다음과 같은 이유 때문에 일반적으로는 문제가 없다.

  • 커널 로그는 비교적 천천히 늘어난다.
  • 노드 문제 감지기에는 리소스 제한이 설정되어 있다.
  • 높은 부하가 걸리더라도, 리소스 사용량은 허용 가능한 수준이다. 추가 정보를 위해 노드 문제 감지기의 벤치마크 결과를 살펴보자.

2.2.4 - crictl로 쿠버네티스 노드 디버깅하기

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

crictl은 CRI-호환 컨테이너 런타임에 사용할 수 있는 커맨드라인 인터페이스이다. 쿠버네티스 노드에서 컨테이너 런타임과 애플리케이션을 검사하고 디버그하는 데 사용할 수 있다. crictl과 그 소스는 cri-tools 저장소에서 호스팅한다.

시작하기 전에

crictl은 CRI 런타임이 있는 리눅스 운영체제를 필요로 한다.

crictl 설치하기

cri-tools 릴리스 페이지에서 다양한 아키텍처 별로 압축된 crictl 아카이브(archive)를 다운로드할 수 있다. 설치된 쿠버네티스 버전에 해당하는 버전을 다운로드한다. /usr/local/bin/와 같은 시스템 경로의 위치에 압축을 푼다.

일반적인 사용법

crictl 커맨드에는 여러 하위 커맨드와 런타임 플래그가 있다. 자세한 내용은 crictl help 또는 crictl <subcommand> help를 참조한다.

아래 내용 중 하나를 통해 crictl의 엔드포인트를 설정할 수 있다.

  • --runtime-endpoint--image-endpoint 플래그 설정.
  • CONTAINER_RUNTIME_ENDPOINTIMAGE_SERVICE_ENDPOINT 환경 변수 설정.
  • 구성 파일 /etc/crictl.yaml에 엔드포인트 설정. 다른 파일을 지정하기 위해서는 crictl을 실행할 때 --config=PATH_TO_FILE 플래그를 사용한다.

서버에 연결할 때 구성 파일에서 timeout 또는 debug 값을 명시하거나, --timeout 그리고 --debug 커맨드라인 플래그를 사용하여 타임아웃 값을 지정하고 디버깅을 활성화하거나 비활성화할 수 있다.

현재 구성을 보거나 편집하려면 /etc/crictl.yaml의 내용을 보거나 편집한다. 예를 들어, containerd 컨테이너 런타임 사용 시 구성은 아래와 유사하다.

runtime-endpoint: unix:///var/run/containerd/containerd.sock
image-endpoint: unix:///var/run/containerd/containerd.sock
timeout: 10
debug: true

crictl에 대해 자세히 알고 싶다면, crictl 문서를 참조한다.

crictl 커맨드 예시

아래 예시를 통해 crictl 커맨드와 출력을 확인해보자.

파드 목록 조회

모든 파드의 목록 조회

crictl pods

출력은 다음과 유사하다.

POD ID              CREATED              STATE               NAME                         NAMESPACE           ATTEMPT
926f1b5a1d33a       About a minute ago   Ready               sh-84d7dcf559-4r2gq          default             0
4dccb216c4adb       About a minute ago   Ready               nginx-65899c769f-wv2gp       default             0
a86316e96fa89       17 hours ago         Ready               kube-proxy-gblk4             kube-system         0
919630b8f81f1       17 hours ago         Ready               nvidia-device-plugin-zgbbv   kube-system         0

이름으로 파드의 목록 조회

crictl pods --name nginx-65899c769f-wv2gp

출력은 다음과 유사하다.

POD ID              CREATED             STATE               NAME                     NAMESPACE           ATTEMPT
4dccb216c4adb       2 minutes ago       Ready               nginx-65899c769f-wv2gp   default             0

레이블로 파드의 목록 조회

crictl pods --label run=nginx

출력은 다음과 유사하다.

POD ID              CREATED             STATE               NAME                     NAMESPACE           ATTEMPT
4dccb216c4adb       2 minutes ago       Ready               nginx-65899c769f-wv2gp   default             0

이미지 목록 조회

모든 이미지의 목록 조회

crictl images

출력은 다음과 유사하다.

IMAGE                                     TAG                 IMAGE ID            SIZE
busybox                                   latest              8c811b4aec35f       1.15MB
k8s-gcrio.azureedge.net/hyperkube-amd64   v1.10.3             e179bbfe5d238       665MB
k8s-gcrio.azureedge.net/pause-amd64       3.1                 da86e6ba6ca19       742kB
nginx                                     latest              cd5239a0906a6       109MB

저장소로 이미지 목록 조회

crictl images nginx

출력은 다음과 유사하다.

IMAGE               TAG                 IMAGE ID            SIZE
nginx               latest              cd5239a0906a6       109MB

이미지의 IDs 목록만 조회

crictl images -q

출력은 다음과 유사하다.

sha256:8c811b4aec35f259572d0f79207bc0678df4c736eeec50bc9fec37ed936a472a
sha256:e179bbfe5d238de6069f3b03fccbecc3fb4f2019af741bfff1233c4d7b2970c5
sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e
sha256:cd5239a0906a6ccf0562354852fae04bc5b52d72a2aff9a871ddb6bd57553569

컨테이너 목록 조회

모든 컨테이너 목록 조회

crictl ps -a

출력은 다음과 유사하다.

CONTAINER ID        IMAGE                                                                                                             CREATED             STATE               NAME                       ATTEMPT
1f73f2d81bf98       busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47                                   7 minutes ago       Running             sh                         1
9c5951df22c78       busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47                                   8 minutes ago       Exited              sh                         0
87d3992f84f74       nginx@sha256:d0a8828cccb73397acb0073bf34f4d7d8aa315263f1e7806bf8c55d8ac139d5f                                     8 minutes ago       Running             nginx                      0
1941fb4da154f       k8s-gcrio.azureedge.net/hyperkube-amd64@sha256:00d814b1f7763f4ab5be80c58e98140dfc69df107f253d7fdd714b30a714260a   18 hours ago        Running             kube-proxy                 0

실행 중인 컨테이너 목록 조회

crictl ps

출력은 다음과 유사하다.

CONTAINER ID        IMAGE                                                                                                             CREATED             STATE               NAME                       ATTEMPT
1f73f2d81bf98       busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47                                   6 minutes ago       Running             sh                         1
87d3992f84f74       nginx@sha256:d0a8828cccb73397acb0073bf34f4d7d8aa315263f1e7806bf8c55d8ac139d5f                                     7 minutes ago       Running             nginx                      0
1941fb4da154f       k8s-gcrio.azureedge.net/hyperkube-amd64@sha256:00d814b1f7763f4ab5be80c58e98140dfc69df107f253d7fdd714b30a714260a   17 hours ago        Running             kube-proxy                 0

실행 중인 컨테이너 안에서 명령 실행

crictl exec -i -t 1f73f2d81bf98 ls

출력은 다음과 유사하다.

bin   dev   etc   home  proc  root  sys   tmp   usr   var

컨테이너의 로그 조회

모든 컨테이너의 로그 조회

crictl logs 87d3992f84f74

출력은 다음과 유사하다.

10.240.0.96 - - [06/Jun/2018:02:45:49 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"
10.240.0.96 - - [06/Jun/2018:02:45:50 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"
10.240.0.96 - - [06/Jun/2018:02:45:51 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"

최신 N개 줄의 로그만 조회

crictl logs --tail=1 87d3992f84f74

출력은 다음과 유사하다.

10.240.0.96 - - [06/Jun/2018:02:45:51 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"

파드 샌드박스 실행

crictl을 사용하여 파드 샌드박스를 실행하는 것은 컨테이너 런타임 디버깅에 유용하다. 구동 중인 쿠버네티스 클러스터에서 이러한 샌드박스는 kubelet에 의해서 결국 중지 및 삭제된다.

  1. 다음과 같은 JSON 파일 생성한다.

    {
      "metadata": {
        "name": "nginx-sandbox",
        "namespace": "default",
        "attempt": 1,
        "uid": "hdishd83djaidwnduwk28bcsb"
      },
      "log_directory": "/tmp",
      "linux": {
      }
    }
    
  2. crictl runp 커맨드를 사용하여 JSON을 적용하고 샌드박스를 실행한다.

    crictl runp pod-config.json
    

    결과로 샌드박스의 ID가 반환될 것이다.

컨테이너 생성

crictl을 사용하여 컨테이너를 만드는 것은 컨테이너 런타임 디버깅에 유용하다. 구동 중인 쿠버네티스 클러스터에서 이러한 샌드박스는 kubelet에 의해서 결국 중지 및 삭제된다.

  1. busybox 이미지 가져오기

    crictl pull busybox
    
    Image is up to date for busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47
    
  2. 파드와 컨테이너 구성(config) 생성

    파드 구성:

    {
      "metadata": {
        "name": "busybox-sandbox",
        "namespace": "default",
        "attempt": 1,
        "uid": "aewi4aeThua7ooShohbo1phoj"
      },
      "log_directory": "/tmp",
      "linux": {
      }
    }
    

    컨테이너 구성:

    {
      "metadata": {
        "name": "busybox"
      },
      "image":{
        "image": "busybox"
      },
      "command": [
        "top"
      ],
      "log_path":"busybox.log",
      "linux": {
      }
    }
    
  3. 이전에 생성한 파드의 ID 및 컨테이너 구성 파일과 파드 구성 파일을 커맨드 인자로 전달하여, 컨테이너를 생성한다. 결과로 컨테이너의 ID가 반환될 것이다.

    crictl create f84dd361f8dc51518ed291fbadd6db537b0496536c1d2d6c05ff943ce8c9a54f container-config.json pod-config.json
    
  4. 모든 컨테이너의 목록을 조회하여 새로 생성된 컨테이너의 상태가 Created임을 확인한다.

    crictl ps -a
    

    출력은 다음과 유사하다.

    CONTAINER ID        IMAGE               CREATED             STATE               NAME                ATTEMPT
    3e025dd50a72d       busybox             32 seconds ago      Created             busybox             0
    

컨테이너 시작하기

컨테이너를 시작하기 위해서 컨테이너 ID를 crictl start에 인자로 전달한다.

crictl start 3e025dd50a72d956c4f14881fbb5b1080c9275674e95fb67f965f6478a957d60

출력은 다음과 유사하다.

3e025dd50a72d956c4f14881fbb5b1080c9275674e95fb67f965f6478a957d60

컨테이너의 상태가 Running임을 확인한다.

crictl ps

출력은 다음과 유사하다.

CONTAINER ID   IMAGE    CREATED              STATE    NAME     ATTEMPT
3e025dd50a72d  busybox  About a minute ago   Running  busybox  0

다음 내용

2.2.5 - 감사(auditing)

쿠버네티스 감사(auditing) 는 클러스터의 작업 순서를 문서화하는 보안 관련 시간별 레코드 세트를 제공한다. 클러스터는 사용자, 쿠버네티스 API를 사용하는 애플리케이션 및 컨트롤 플레인 자체에서 생성된 활동을 감사한다.

감사를 통해 클러스터 관리자는 다음 질문에 답할 수 있다.

  • 무슨 일이 일어났는지?
  • 언제 일어난 일인지?
  • 누가 시작했는지?
  • 어떤 일이 있었는지?
  • 어디서 관찰되었는지?
  • 어디서부터 시작되었는지?
  • 그래서 어디까지 갔는지?

감사 기록은 kube-apiserver 컴포넌트 내에서 수명주기를 시작한다. 실행의 각 단계에서 각 요청은 감사 이벤트를 생성하고, 감사 이벤트는 특정 정책에 따라 사전 처리되고 백엔드에 기록된다. 정책은 기록된 내용을 결정하고 백엔드는 기록을 유지한다. 현재 백엔드 구현에는 로그 파일 및 웹훅이 포함된다.

각 요청들은 연관된 단계(stage) 와 함께 기록될 수 있다. 정의된 단계는 다음과 같다.

  • RequestReceived - 감사 핸들러가 요청을 수신한 직후, 그리고 핸들러 체인으로 위임되기 전에 생성되는 이벤트에 대한 단계이다.
  • ResponseStarted - 응답 헤더는 전송되었지만, 응답 본문(body)은 전송되기 전인 단계이다. 이 단계는 오래 실행되는 요청(예: watch)에 대해서만 생성된다.
  • ResponseComplete - 응답 내용이 완료되었으며, 더 이상 바이트가 전송되지 않을 때의 단계이다.
  • Panic - 패닉이 발생했을 때 생성되는 이벤트이다.

감사 로깅 기능은 감사에 필요한 일부 컨텍스트가 요청마다 저장되기 때문에 API 서버의 메모리 사용량을 증가시킨다. 메모리 소비량은 감사 로깅 구성에 따라 다르다.

감사 정책

감사 정책은 기록해야 하는 이벤트와 포함해야 하는 데이터에 대한 규칙을 정의한다. 감사 정책 오브젝트 구조는 audit.k8s.io API 그룹에 정의되어 있다. 이벤트가 처리되면 규칙 목록과 순서대로 비교된다. 첫번째 일치 규칙은 이벤트의 감사 수준(audit level) 을 설정한다. 정의된 감사 수준은 다음과 같다.

  • None - 이 규칙에 해당되는 이벤트는 로깅하지 않는다.
  • Metadata - 요청 메타데이터(요청하는 사용자, 타임스탬프, 리소스, 동사(verb) 등)는 로깅하지만 요청/응답 본문은 로깅하지 않는다.
  • Request - 이벤트 메타데이터 및 요청 본문을 로깅하지만 응답 본문은 로깅하지 않는다. 리소스 외의 요청에는 적용되지 않는다.
  • RequestResponse - 이벤트 메타데이터 및 요청/응답 본문을 로깅한다. 리소스 외의 요청에는 적용되지 않는다.

--audit-policy-file 플래그를 사용하여 정책이 포함된 파일을 kube-apiserver에 전달할 수 있다. 플래그를 생략하면 이벤트가 기록되지 않는다. 감사 정책 파일에 rules 필드 반드시 가 제공되어야 한다. 규칙이 없는(0개인) 정책은 적절하지 않은(illegal) 것으로 간주된다.

다음은 감사 정책 파일의 예이다.

apiVersion: audit.k8s.io/v1 # 필수사항임.
kind: Policy
# Request Received 단계의 모든 요청에 대해 감사 이벤트를 생성하지 않음.
omitStages:
  - "RequestReceived"
rules:
  # RequestResponse 수준에서 파드 변경 사항 기록
  - level: RequestResponse
    resources:
    - group: ""
      # 리소스 "파드" 가 RBAC 정책과 부합하는 파드의 하위 리소스에 대한
      # 요청과 일치하지 않음.
      resources: ["pods"]
  # 메타데이터 수준에서 "pods/log", "pods/status"를 기록함.
  - level: Metadata
    resources:
    - group: ""
      resources: ["pods/log", "pods/status"]

  # "controller-leader" 라는 컨피그맵에 요청을 기록하지 않음."
  - level: None
    resources:
    - group: ""
      resources: ["configmaps"]
      resourceNames: ["controller-leader"]

  # 엔드포인트 또는 서비스의 "system:kube-proxy"에 의한 감시 요청 기록하지 않음.
  - level: None
    users: ["system:kube-proxy"]
    verbs: ["watch"]
    resources:
    - group: "" # 핵심 API 그룹
      resources: ["endpoints", "services"]

  # 인증된 요청을 특정 리소스가 아닌 URL 경로에 기록하지 않음.
  - level: None
    userGroups: ["system:authenticated"]
    nonResourceURLs:
    - "/api*" # 와일드카드 매칭(wildcard matching).
    - "/version"

  # kube-system에 컨피그맵 변경 사항의 요청 본문을 기록함.
  - level: Request
    resources:
    - group: "" # 핵심 API 그룹
      resources: ["configmaps"]
    # 이 정책은 "kube-system" 네임스페이스의 리소스에만 적용됨.
    # 빈 문자열 "" 은 네임스페이스가 없는 리소스를 선택하는데 사용할 수 있음.
    namespaces: ["kube-system"]

  # 메타데이터 수준에서 다른 모든 네임스페이스의 컨피그맵과 시크릿 변경 사항을 기록함.
  - level: Metadata
    resources:
    - group: "" # 핵심 API 그룹
      resources: ["secrets", "configmaps"]

  # 요청 수준에서 코어 및 확장에 있는 다른 모든 리소스를 기록함.
  - level: Request
    resources:
    - group: "" # 핵심 API 그룹
    - group: "extensions" # 그룹의 버전을 기재하면 안 된다.

  # 메타데이터 수준에서 다른 모든 요청을 기록하기 위한 모든 수집 정책.
  - level: Metadata
    # 이 정책에 해당하는 감시자와 같은 장기 실행 요청은
    # RequestReceived에서 감사 이벤트를 생성하지 않음.
    omitStages:
      - "RequestReceived"

최소 감사 정책 파일을 사용하여 Metadata 수준에서 모든 요청을 기록할 수 있다.

# Log all requests at the Metadata level.
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata

자체 감사 프로필을 만드는 경우 Google Container-Optimized OS에 대한 감사 프로필을 시작점으로 사용할 수 있다. 감사 정책 파일을 생성하는 configure-helper.sh 스크립트를 확인하면 된다. 스크립트로 대부분의 감사 정책 파일을 볼 수 있다.

정의된 필드에 대한 자세한 내용은 Policy configuration reference를 참조할 수도 있다.

감사 백엔드

감사 백엔드는 감사 이벤트를 외부 저장소에 유지한다. 기본적으로 kube-apiserver는 두 가지 백엔드를 제공한다.

  • 이벤트를 파일 시스템에 기록하는 로그 백엔드
  • 이벤트를 외부 HTTP API로 보내는 Webhook 백엔드

모든 경우에 감사 이벤트는 쿠버네티스 API의 audit.k8s.io API 그룹에서 정의한 구조를 따른다.

로그 백엔드

로그 백엔드는 감사이벤트를 JSONlines 형식으로 파일에 기록한다. 다음의 kube-apiserver 플래그를 사용하여 로그 감사 백엔드를 구성할 수 있다.

  • --audit-log-path 는 로그 백엔드가 감사 이벤트를 쓰는 데 사용하는 로그 파일 경로를 지정한다. 이 플래그를 지정하지 않으면 로그 백엔드가 비활성화된다. - 는 표준 출력을 의미한다.
  • --audit-log-maxage 는 오래된 감사 로그 파일을 보관할 최대 일수를 정의한다.
  • --audit-log-maxbackup 은 보관할 감사 로그 파일의 최대 수를 정의한다.
  • --audit-log-maxsize 는 감사 로그 파일이 로테이트 되기 전의 최대 크기(MB)를 정의한다.

클러스터의 컨트롤 플레인이 kube-apiserver를 파드로 실행하는 경우 감사 레코드가 지속되도록 정책 파일 및 로그 파일의 위치에 hostPath 를 마운트 해야한다. 예를 들면

    --audit-policy-file=/etc/kubernetes/audit-policy.yaml \
    --audit-log-path=/var/log/kubernetes/audit/audit.log

그런 다음 볼륨을 마운트 한다.

...
volumeMounts:
  - mountPath: /etc/kubernetes/audit-policy.yaml
    name: audit
    readOnly: true
  - mountPath: /var/log/kubernetes/audit/
    name: audit-log
    readOnly: false

그리고 마지막으로 hostPath 를 구성한다.

...
volumes:
- name: audit
  hostPath:
    path: /etc/kubernetes/audit-policy.yaml
    type: File

- name: audit-log
  hostPath:
    path: /var/log/kubernetes/audit/
    type: DirectoryOrCreate

웹훅 백엔드

웹훅 감사 백엔드는 원격 웹 API로 감사 이벤트를 전송하는데, 이는 인증 수단을 포함하여 쿠버네티스 API의 한 형태로 간주된다. 다음 kube-apiserver 플래그를 사용하여 웹훅 감사 백엔드를 구성할 수 있다.

  • --audit-webhook-config-file 은 웹훅 구성이 있는 파일의 경로를 지정한다. 웹훅 구성은 효과적으로 전문화된 kubeconfig이다.
  • --audit-webhook-initial-backoff 는 첫 번째 실패한 요청 후 다시 시도하기 전에 대기할 시간을 지정한다. 이후 요청은 지수의 백오프로 재시도 된다.

웹훅 구성 파일은 kubeconfig 형식을 사용하여 서비스의 원격 주소와 서비스에 연결하는 데 사용되는 자격 증명을 지정한다.

이벤트 배치

로그 및 웹 훅 백엔드는 모두 배치를 지원한다. 웹훅 사용을 예로 들면, 다음은 사용 가능한 플래그 목록이다. 로그 백엔드에 대해 동일한 플래그를 가져오려면 플래그 이름에 있는 webhooklog 로 바꾼다. 기본적으로 배치 기능은 webhook 에서 활성화되고 log 에서 비활성화된다. 마찬가지로, 기본적으로 쓰로틀링은 webhook 에서는 활성화되고 log 에서는 비활성화된다.

  • --audit-webhook-mode 은 버퍼링 전략을 정의한다. 다음 중 하나이다.
    • batch - 이벤트를 버퍼링하고 비동기식으로 배치한다. 이것이 기본값이다.
    • blocking - 각 개별 이벤트를 처리할 때 API 서버 응답을 차단한다.
    • blocking-strict - blocking과 동일하지만, RequestReceived 단계에서 감사 로깅 중에 오류가 발생하면 kube-apiserver에 대한 전체 요청이 실패한다.

다음 플래그는 batch 모드에서만 사용된다.

  • --audit-webhook-batch-buffer-size 는 배치하기 전에 버퍼링할 이벤트 수를 정의한다. 들어오는 이벤트의 비율이 버퍼를 초과하면 이벤트가 삭제된다.
  • --audit-webhook-batch-max-size 는 한 배치의 최대 이벤트 수를 정의한다.
  • --audit-webhook-batch-max-wait 는 대기열에서 이벤트를 무조건 배치하기 전에 대기할 최대 시간을 정의한다.
  • --audit-webhook-batch-throttle-qps 는 초당 생성되는 최대 평균 배치 수를 정의한다.
  • --audit-webhook-batch-throttle-burst 는 허용된 QPS가 이전에 충분히 활용되지 않은 경우 동시에 생성되는 최대 배치 수를 정의한다.

파라미터 튜닝

파라미터는 API 서버의 로드를 수용할 수 있도록 설정해야 한다.

예를 들어 kube-apiserver가 초당 100건의 요청을 수신하고 각 요청이 ResponseStartedResponseComplete 단계에서만 감사되는 경우 초당 생성되는 ≅200건의 감사 이벤트를 고려해야 한다. 일괄적으로 최대 100개의 이벤트가 있다고 가정할 때 초당 최소 2개의 쿼리 조절 수준을 설정해야 한다. 백엔드가 이벤트를 쓰는 데 최대 5초가 걸릴 수 있다고 가정하면 버퍼크기를 최대 5초의 이벤트를 보유하도록 설정해야 한다. 즉, 10개의 배치 또는 100개의 이벤트이다.

그러나 대부분의 경우 기본 매개 변수만 있으면 충분하며 수동으로 설정할 필요가 없다. kube-apiserver에서 노출된 다음과 같은 프로메테우스 메트릭과 로그에서 감사 하위 시스템의 상태를 모니터링할 수 있다.

  • apiserver_audit_event_total 의 메트릭에는 내보낸 감사 이벤트의 총 수가 포함된다.
  • apiserver_audit_error_total 의 메트릭에는 내보내기 중 오류로 인해 삭제된 총 이벤트 수가 포함된다.

로그 항목 자르기

로그 및 웹훅 백엔드는 모두 로깅되는 이벤트의 크기 제한을 지원한다. 예를 들어 로그 백엔드에 사용할 수 있는 플래그 목록은 다음과 같다.

  • audit-log-truncate-enabled 는 이벤트 및 자르기 배치가 활성화 되었는지 여부를 나타낸다.
  • audit-log-truncate-max-batch-size 는 기본 백엔드로 전송되는 배치의 바이트 단위의 최대 크기이다.
  • audit-log-truncate-max-event-size 는 기본 백엔드로 전송된 감사 이벤트의 바이트 단위의 최대 크기이다.

기본적으로 webhooklog 모두에서 자르기 기능이 비활성화되어 있으므로 이 기능을 활성화 하기 위해 클러스터 관리자는 audit-log-truncate-enabled 또는 audit-webhook-truncate-enabled 를 설정해야 한다.

다음 내용

2.2.6 - 로컬에서 텔레프레즌스를 이용한 서비스 개발 및 디버깅

쿠버네티스 애플리케이션은 일반적으로 각각 자체 컨테이너에서 실행되는 여러 개의 개별 서비스로 구성된다. 원격 쿠버네티스 클러스터 상에서 이러한 서비스를 개발하고 디버깅하려면 디버깅 도구를 실행하기 위해 동작 중인 컨테이너의 셸(shell)에 접근해야 하기 때문에 번거로울 수 있다.

텔레프레즌스(telepresence)는 원격 쿠버네티스 클러스터로 서비스를 프록시하면서 로컬에서 서비스를 개발 및 디버깅하는 과정을 용이하게 하는 도구이다. 텔레프레즌스를 사용하면 로컬 서비스에 디버거 및 IDE와 같은 사용자 지정 도구를 사용할 수 있고 원격 클러스터에서 실행되는 컨피그맵(ConfigMap), 시크릿(Secret) 및 서비스(Service)에 대한 전체 접근 권한을 서비스에 제공할 수 있다.

이 문서는 텔레프레즌스를 사용하여 원격 클러스터에서 실행 중인 서비스를 로컬로 개발하고 디버그하는 방법을 설명한다.

시작하기 전에

  • 쿠버네티스 클러스터가 설치되어 있어야 한다.
  • kubectl은 클러스터와 통신하도록 구성되어 있어야 한다.
  • 텔레프레즌스가 설치되어 있어야 한다.

로컬 머신을 원격 쿠버네티스 클러스터에 연결

텔레프레즌스를 설치한 후 텔레프레즌스 커넥트(telepresence connect)를 실행하여 데몬을 실행하고 로컬 워크스테이션을 클러스터에 연결한다.

$ telepresence connect
 
Launching Telepresence Daemon
...
Connected to context default (https://<cluster public IP>)

쿠버네티스 구문을 사용하여 서비스에 curl이 가능하다. 예, curl -ik https://kubernetes.default

기존 서비스 개발 또는 디버깅

쿠버네티스에서 애플리케이션을 개발할 때 일반적으로 단일 서비스를 프로그래밍하거나 디버그한다. 서비스를 테스트 및 디버깅하기 위해 다른 서비스에 접근이 필요할 수 있다. 지속적 배포(continuous deployment) 파이프라인을 사용하는 것도 한 가지 옵션이지만, 가장 빠른 배포 파이프라인이라도 프로그래밍 또는 디버그 주기에 지연이 발생할 수 있다.

telepresence intercept $SERVICE_NAME --port $LOCAL_PORT:$REMOTE_PORT 명령을 사용하여 원격 서비스 트래픽을 다시 라우팅하기 위한 "인터셉트(intercept)"를 생성한다.

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

  • $SERVICE_NAME은 로컬 서비스의 이름이다.
  • $LOCAL_PORT는 서비스가 로컬 워크스테이션에서 실행 중인 포트이다.
  • $REMOTE_PORT는 서비스가 클러스터에서 수신하는 포트이다.

이 명령을 실행하면 원격 쿠버네티스 클러스터의 서비스 대신 로컬 서비스에 원격 트래픽을 보내도록 텔레프레즌스에 지시한다. 서비스 소스 코드를 로컬에서 편집하고 저장하여 원격 애플리케이션에 접근할 때 해당 변경 사항이 즉시 적용되는지 확인한다. 디버거 또는 기타 로컬 개발 도구를 사용하여 로컬 서비스를 실행할 수도 있다.

텔레프레즌스는 어떻게 작동하는가?

Telepresence는 원격 클러스터에서 실행 중인 기존 애플리케이션의 컨테이너 옆에 트래픽 에이전트 사이드카(sidecar)를 설치한다. 그런 다음 파드로 들어오는 모든 트래픽 요청을 캡처하고, 이를 원격 클러스터의 애플리케이션에 전달하는 대신, 모든 트래픽(글로벌 인터셉트를 생성하는 경우) 또는 일부 트래픽(개인 인터셉트를 생성하는 경우)을 로컬 개발 환경으로 라우팅한다.

다음 내용

핸즈온 튜토리얼에 관심이 있다면 구글 쿠버네티스 엔진 상의 방명록 애플리케이션을 로컬로 개발하는 과정을 안내하는 이 튜토리얼을 확인한다.

자세한 내용은 텔레프레즌스 웹사이트를 참조한다.

2.2.7 - 윈도우 디버깅 팁

노드-수준 트러블슈팅

  1. 내 파드가 "Container Creating"에서 멈췄거나 계속해서 다시 시작된다.

    퍼즈(pause) 이미지가 OS 버전과 호환되는지 확인한다. 퍼즈 컨테이너에서 최신 / 추천 퍼즈 이미지 및 추가 정보를 확인한다.

  2. 내 파드의 상태가 ErrImgPull 또는 ImagePullBackOff이다.

    파드가 호환되는 윈도우 노드에 스케줄링되었는지 확인한다.

    각 파드와 호환되는 노드를 찾는 방법에 대한 추가 정보는 이 가이드를 참고한다.

네트워크 트러블슈팅

  1. 내 윈도우 파드에 네트워크 연결이 없다.

    가상 머신을 사용하는 경우, 모든 VM 네트워크 어댑터에 MAC 스푸핑이 활성화되어 있는지 확인한다.

  2. 내 윈도우 파드가 외부 리소스를 ping 할 수 없다.

    윈도우 파드에는 현재 ICMP 프로토콜용으로 프로그래밍된 아웃바운드 규칙이 없다. 그러나 TCP/UDP는 지원된다. 클러스터 외부 리소스에 대한 연결을 시연하려는 경우, ping <IP>를 대응되는 curl <IP>명령으로 대체한다.

    여전히 문제가 발생하는 경우, cni.conf의 네트워크 구성에 특별히 추가 확인이 필요하다. 언제든지 이 정적 파일을 편집할 수 있다. 구성 업데이트는 새로 생성된 모든 쿠버네티스 리소스에 적용된다.

    쿠버네티스 네트워킹 요구 사항(쿠버네티스 모델 참조) 중 하나는 클러스터 통신이 NAT 없이 내부적으로 발생해야 한다는 것이다. 이 요구 사항을 준수하기 위해 아웃바운드 NAT가 발생하지 않도록 하는 모든 통신에 대한 ExceptionList가 있다. 그러나 이것은 쿼리하려는 외부 IP를 ExceptionList에서 제외해야 함도 의미한다. 그래야만 윈도우 파드에서 발생하는 트래픽이 제대로 SNAT 되어 외부에서 응답을 받는다. 이와 관련하여 cni.confExceptionList는 다음과 같아야 한다.

    "ExceptionList": [
                    "10.244.0.0/16",  # 클러스터 서브넷
                    "10.96.0.0/12",   # 서비스 서브넷
                    "10.127.130.0/24" # 관리(호스트) 서브넷
                ]
    
  3. 내 윈도우 노드가 NodePort 서비스에 접근할 수 없다.

    노드 자체에서는 로컬 NodePort 접근이 실패한다. 이것은 알려진 제약사항이다. NodePort 접근은 다른 노드 또는 외부 클라이언트에서는 가능하다.

  4. 컨테이너의 vNIC 및 HNS 엔드포인트가 삭제된다.

    이 문제는 hostname-override 파라미터가 kube-proxy에 전달되지 않은 경우 발생할 수 있다. 이를 해결하려면 사용자는 다음과 같이 hostname을 kube-proxy에 전달해야 한다.

    C:\k\kube-proxy.exe --hostname-override=$(hostname)
    
  5. 내 윈도우 노드가 서비스 IP를 사용하여 내 서비스에 접근할 수 없다.

    이는 윈도우에서 현재 네트워킹 스택의 알려진 제약 사항이다. 그러나 윈도우 파드는 서비스 IP에 접근할 수 있다.

  6. kubelet을 시작할 때 네트워크 어댑터를 찾을 수 없다.

    윈도우 네트워킹 스택에는 쿠버네티스 네트워킹이 작동하기 위한 가상 어댑터가 필요하다. (어드민 셸에서) 다음 명령이 결과를 반환하지 않으면, Kubelet이 작동하는 데 필요한 필수 구성 요소인 가상 네트워크 생성이 실패한 것이다.

    Get-HnsNetwork | ? Name -ieq "cbr0"
    Get-NetAdapter | ? Name -Like "vEthernet (Ethernet*"
    

    호스트 네트워크 어댑터가 "Ethernet"이 아닌 경우, 종종 start.ps1 스크립트의 InterfaceName 파라미터를 수정하는 것이 좋다. 그렇지 않으면 start-kubelet.ps1 스크립트의 출력을 참조하여 가상 네트워크 생성 중에 오류가 있는지 확인한다.

  7. DNS 해석(resolution)이 제대로 작동하지 않는다.

    섹션에서 윈도우에서의 DNS 제한을 확인한다.

  8. kubectl port-forward가 "unable to do port forwarding: wincat not found" 에러와 함께 실패한다.

    이 기능은 퍼즈 인프라 컨테이너 mcr.microsoft.com/oss/kubernetes/pause:3.6wincat.exe를 포함시킴으로써 쿠버네티스 1.15에서 구현되었다. 지원되는 쿠버네티스 버전을 사용하고 있는지 확인한다. 퍼즈 인프라 컨테이너를 직접 빌드하려면 wincat을 포함시켜야 한다.

  9. 내 윈도우 서버 노드가 프록시 뒤에 있기 때문에 쿠버네티스 설치에 실패한다.

    프록시 뒤에 있는 경우, 다음 PowerShell 환경 변수를 정의해야 한다.

    [Environment]::SetEnvironmentVariable("HTTP_PROXY", "http://proxy.example.com:80/", [EnvironmentVariableTarget]::Machine)
    [Environment]::SetEnvironmentVariable("HTTPS_PROXY", "http://proxy.example.com:443/", [EnvironmentVariableTarget]::Machine)
    

플란넬 트러블슈팅

  1. 플란넬(flannel)을 사용하면 클러스터에 다시 조인(join)한 후 노드에 이슈가 발생한다.

    이전에 삭제된 노드가 클러스터에 다시 조인될 때마다, flannelD는 새 파드 서브넷을 노드에 할당하려고 한다. 사용자는 다음 경로에서 이전 파드 서브넷 구성 파일을 제거해야 한다.

    Remove-Item C:\k\SourceVip.json
    Remove-Item C:\k\SourceVipRequest.json
    
  2. flanneld가 "Waiting for the Network to be created" 상태에서 멈춘다.

    이슈에 대한 수많은 보고가 있다. 플란넬 네트워크의 관리 IP가 설정될 때의 타이밍 이슈일 가능성이 높다. 해결 방법은 start.ps1을 다시 시작하거나 다음과 같이 수동으로 다시 시작하는 것이다.

    [Environment]::SetEnvironmentVariable("NODE_NAME", "<Windows_Worker_Hostname>")
    C:\flannel\flanneld.exe --kubeconfig-file=c:\k\config --iface=<Windows_Worker_Node_IP> --ip-masq=1 --kube-subnet-mgr=1
    
  3. /run/flannel/subnet.env 누락으로 인해 윈도우 파드를 시작할 수 없다.

    이것은 플란넬이 제대로 실행되지 않았음을 나타낸다. flanneld.exe를 다시 시작하거나 쿠버네티스 마스터의 /run/flannel/subnet.env에서 윈도우 워커 노드의 C:\run\flannel\subnet.env로 파일을 수동으로 복사할 수 있고, FLANNEL_SUBNET 행을 다른 숫자로 수정한다. 예를 들어, 노드 서브넷 10.244.4.1/24가 필요한 경우 다음과 같이 설정한다.

    FLANNEL_NETWORK=10.244.0.0/16
    FLANNEL_SUBNET=10.244.4.1/24
    FLANNEL_MTU=1500
    FLANNEL_IPMASQ=true
    

추가 조사

이러한 단계로 문제가 해결되지 않으면, 다음을 통해 쿠버네티스의 윈도우 노드에서 윈도우 컨테이너를 실행하는 데 도움을 받을 수 있다.

3 - 클러스터 운영

클러스터를 운영하기 위한 공통 태스크를 배운다.

3.1 - kubeadm으로 관리하기

3.1.1 - kubeadm을 사용한 인증서 관리

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

kubeadm으로 생성된 클라이언트 인증서는 1년 후에 만료된다. 이 페이지는 kubeadm으로 인증서 갱신을 관리하는 방법을 설명하며, kubeadm 인증서 관리와 관련된 다른 작업에 대해서도 다룬다.

시작하기 전에

쿠버네티스의 PKI 인증서와 요구 조건에 익숙해야 한다.

사용자 정의 인증서 사용

기본적으로, kubeadm은 클러스터를 실행하는 데 필요한 모든 인증서를 생성한다. 사용자는 자체 인증서를 제공하여 이 동작을 무시할 수 있다.

이렇게 하려면, --cert-dir 플래그 또는 kubeadm ClusterConfigurationcertificatesDir 필드에 지정된 디렉터리에 배치해야 한다. 기본적으로 /etc/kubernetes/pki 이다.

kubeadm init 을 실행하기 전에 지정된 인증서와 개인 키(private key) 쌍이 존재하면, kubeadm은 이를 덮어 쓰지 않는다. 이는 예를 들어, 기존 CA를 /etc/kubernetes/pki/ca.crt/etc/kubernetes/pki/ca.key 에 복사할 수 있고, kubeadm은 이 CA를 사용하여 나머지 인증서에 서명한다는 걸 의미한다.

외부 CA 모드

ca.key 파일이 아닌 ca.crt 파일만 제공할 수도 있다(이는 다른 인증서 쌍이 아닌 루트 CA 파일에만 사용 가능함). 다른 모든 인증서와 kubeconfig 파일이 있으면, kubeadm은 이 조건을 인식하고 "외부 CA" 모드를 활성화한다. kubeadm은 디스크에 CA 키없이 진행한다.

대신, --controllers=csrsigner 사용하여 controller-manager를 독립적으로 실행하고 CA 인증서와 키를 가리킨다.

PKI 인증서와 요구 조건은 외부 CA를 사용하도록 클러스터 설정에 대한 지침을 포함한다.

인증서 만료 확인

check-expiration 하위 명령을 사용하여 인증서가 만료되는 시기를 확인할 수 있다.

kubeadm certs check-expiration

출력 결과는 다음과 비슷하다.

CERTIFICATE                EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY   EXTERNALLY MANAGED
admin.conf                 Dec 30, 2020 23:36 UTC   364d                                    no
apiserver                  Dec 30, 2020 23:36 UTC   364d            ca                      no
apiserver-etcd-client      Dec 30, 2020 23:36 UTC   364d            etcd-ca                 no
apiserver-kubelet-client   Dec 30, 2020 23:36 UTC   364d            ca                      no
controller-manager.conf    Dec 30, 2020 23:36 UTC   364d                                    no
etcd-healthcheck-client    Dec 30, 2020 23:36 UTC   364d            etcd-ca                 no
etcd-peer                  Dec 30, 2020 23:36 UTC   364d            etcd-ca                 no
etcd-server                Dec 30, 2020 23:36 UTC   364d            etcd-ca                 no
front-proxy-client         Dec 30, 2020 23:36 UTC   364d            front-proxy-ca          no
scheduler.conf             Dec 30, 2020 23:36 UTC   364d                                    no

CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
ca                      Dec 28, 2029 23:36 UTC   9y              no
etcd-ca                 Dec 28, 2029 23:36 UTC   9y              no
front-proxy-ca          Dec 28, 2029 23:36 UTC   9y              no

이 명령은 /etc/kubernetes/pki 폴더의 클라이언트 인증서와 kubeadm이 사용하는 KUBECONFIG 파일(admin.conf, controller-manager.confscheduler.conf)에 포함된 클라이언트 인증서의 만료/잔여 기간을 표시한다.

또한, kubeadm은 인증서가 외부에서 관리되는지를 사용자에게 알린다. 이 경우 사용자는 수동으로 또는 다른 도구를 사용해서 인증서 갱신 관리를 해야 한다.

자동 인증서 갱신

kubeadm은 컨트롤 플레인 업그레이드 동안 모든 인증서를 갱신한다.

이 기능은 가장 간단한 유스케이스를 해결하기 위해 설계되었다. 인증서 갱신에 대해 특별한 요구 사항이 없고 쿠버네티스 버전 업그레이드를 정기적으로(매 1년 이내 업그레이드 수행) 수행하는 경우, kubeadm은 클러스터를 최신 상태로 유지하고 합리적으로 보안을 유지한다.

인증서 갱신에 대해 보다 복잡한 요구 사항이 있는 경우, --certificate-renewal=falsekubeadm upgrade apply 또는 kubeadm upgrade node 와 함께 사용하여 기본 동작이 수행되지 않도록 할 수 있다.

수동 인증서 갱신

kubeadm certs renew 명령을 사용하여 언제든지 인증서를 수동으로 갱신할 수 있다.

이 명령은 /etc/kubernetes/pki 에 저장된 CA(또는 프론트 프록시 CA) 인증서와 키를 사용하여 갱신을 수행한다.

명령을 실행한 후에는 컨트롤 플레인 파드를 재시작해야 한다. 이는 현재 일부 구성 요소 및 인증서에 대해 인증서를 동적으로 다시 로드하는 것이 지원되지 않기 때문이다. 스태틱(static) 파드는 API 서버가 아닌 로컬 kubelet에서 관리되므로 kubectl을 사용하여 삭제 및 재시작할 수 없다. 스태틱 파드를 다시 시작하려면 /etc/kubernetes/manifests/에서 매니페스트 파일을 일시적으로 제거하고 20초를 기다리면 된다 (KubeletConfiguration structfileCheckFrequency 값을 참고한다). 파드가 매니페스트 디렉터리에 더 이상 없는 경우 kubelet은 파드를 종료한다. 그런 다음 파일을 다시 이동할 수 있으며 또 다른 fileCheckFrequency 기간이 지나면, kubelet은 파드를 생성하고 구성 요소에 대한 인증서 갱신을 완료할 수 있다.

kubeadm certs renew 는 다음의 옵션을 제공한다.

쿠버네티스 인증서는 일반적으로 1년 후 만료일에 도달한다.

  • --csr-only 는 실제로 인증서를 갱신하지 않고 인증서 서명 요청을 생성하여 외부 CA로 인증서를 갱신하는 데 사용할 수 있다. 자세한 내용은 다음 단락을 참고한다.

  • 모든 인증서 대신 단일 인증서를 갱신할 수도 있다.

쿠버네티스 인증서 API를 사용하여 인증서 갱신

이 섹션에서는 쿠버네티스 인증서 API를 사용하여 수동 인증서 갱신을 실행하는 방법에 대한 자세한 정보를 제공한다.

서명자 설정

쿠버네티스 인증 기관(Certificate Authority)은 기본적으로 작동하지 않는다. cert-manager와 같은 외부 서명자를 설정하거나, 빌트인 서명자를 사용할 수 있다.

빌트인 서명자는 kube-controller-manager의 일부이다.

빌트인 서명자를 활성화하려면, --cluster-signing-cert-file--cluster-signing-key-file 플래그를 전달해야 한다.

새 클러스터를 생성하는 경우, kubeadm 구성 파일을 사용할 수 있다.

apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
controllerManager:
  extraArgs:
    cluster-signing-cert-file: /etc/kubernetes/pki/ca.crt
    cluster-signing-key-file: /etc/kubernetes/pki/ca.key

인증서 서명 요청(CSR) 생성

쿠버네티스 API로 CSR을 작성하려면 CertificateSigningRequest 생성을 본다.

외부 CA로 인증서 갱신

이 섹션에서는 외부 CA를 사용하여 수동 인증서 갱신을 실행하는 방법에 대한 자세한 정보를 제공한다.

외부 CA와 보다 효과적으로 통합하기 위해 kubeadm은 인증서 서명 요청(CSR)을 생성할 수도 있다. CSR은 클라이언트의 서명된 인증서에 대한 CA 요청을 나타낸다. kubeadm 관점에서, 일반적으로 온-디스크(on-disk) CA에 의해 서명되는 모든 인증서는 CSR로 생성될 수 있다. 그러나 CA는 CSR로 생성될 수 없다.

인증서 서명 요청(CSR) 생성

kubeadm certs renew --csr-only 로 인증서 서명 요청을 만들 수 있다.

CSR과 함께 제공되는 개인 키가 모두 출력된다. --csr-dir 로 사용할 디텍터리를 전달하여 지정된 위치로 CSR을 출력할 수 있다. --csr-dir 을 지정하지 않으면, 기본 인증서 디렉터리(/etc/kubernetes/pki)가 사용된다.

kubeadm certs renew --csr-only 로 인증서를 갱신할 수 있다. kubeadm init 과 마찬가지로 출력 디렉터리를 --csr-dir 플래그로 지정할 수 있다.

CSR에는 인증서 이름, 도메인 및 IP가 포함되지만, 용도를 지정하지는 않는다. 인증서를 발행할 때 올바른 인증서 용도를 지정하는 것은 CA의 책임이다.

선호하는 방법으로 인증서에 서명한 후, 인증서와 개인 키를 PKI 디렉터리(기본적으로 /etc/kubernetes/pki)에 복사해야 한다.

인증 기관(CA) 순환(rotation)

Kubeadm은 CA 인증서의 순환이나 교체 기능을 기본적으로 지원하지 않는다.

CA의 수동 순환이나 교체에 대한 보다 상세한 정보는 CA 인증서 수동 순환 문서를 참조한다.

서명된 kubelet 인증서 활성화하기

기본적으로 kubeadm에 의해서 배포된 kubelet 인증서는 자가 서명된(self-signed) 것이다. 이것은 metrics-server와 같은 외부 서비스의 kubelet에 대한 연결은 TLS로 보안되지 않음을 의미한다.

제대로 서명된 인증서를 얻기 위해서 신규 kubeadm 클러스터의 kubelet을 구성하려면 다음의 최소 구성을 kubeadm init 에 전달해야 한다.

apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
serverTLSBootstrap: true

만약 이미 클러스터를 생성했다면 다음을 따라 이를 조정해야 한다.

  • kube-system 네임스페이스에서 kubelet-config-1.31 컨피그맵을 찾아서 수정한다. 해당 컨피그맵에는 kubelet 키가 KubeletConfiguration 문서를 값으로 가진다. serverTLSBootstrap: true 가 되도록 KubeletConfiguration 문서를 수정한다.
  • 각 노드에서, serverTLSBootstrap: true 필드를 /var/lib/kubelet/config.yaml 에 추가한다. 그리고 systemctl restart kubelet 로 kubelet을 재시작한다.

serverTLSBootstrap: true 필드는 kubelet 인증서를 이용한 부트스트랩을 certificates.k8s.io API에 요청함으로써 활성화할 것이다. 한 가지 알려진 제약은 이 인증서들에 대한 CSR(인증서 서명 요청)들이 kube-controller-manager - kubernetes.io/kubelet-serving의 기본 서명자(default signer)에 의해서 자동으로 승인될 수 없다는 점이다. 이것은 사용자나 제 3의 컨트롤러의 액션을 필요로 할 것이다.

이 CSR들은 다음을 통해 볼 수 있다.

kubectl get csr
NAME        AGE     SIGNERNAME                        REQUESTOR                      CONDITION
csr-9wvgt   112s    kubernetes.io/kubelet-serving     system:node:worker-1           Pending
csr-lz97v   1m58s   kubernetes.io/kubelet-serving     system:node:control-plane-1    Pending

이를 승인하기 위해서는 다음을 수행한다.

kubectl certificate approve <CSR-name>

기본적으로, 이 인증서는 1년 후에 만기될 것이다. Kubeadm은 KubeletConfiguration 필드의 rotateCertificatestrue 로 설정한다. 이것은 만기가 다가오면 인증서를 위한 신규 CSR 세트가 생성되는 것을 의미하며, 해당 순환(rotation)을 완료하기 위해서는 승인이 되어야 한다는 것을 의미한다. 더 상세한 이해를 위해서는 인증서 순환를 확인한다.

만약 이 CSR들의 자동 승인을 위한 솔루션을 찾고 있다면 클라우드 제공자와 연락하여 대역 외 메커니즘(out of band mechanism)을 통해 노드의 신분을 검증할 수 있는 CSR 서명자를 가지고 있는지 문의하는 것을 추천한다.

써드파티 커스텀 컨트롤러도 사용될 수 있다.

이러한 컨트롤러는 CSR의 CommonName과 요청된 IPs 및 도메인 네임을 모두 검증하지 않는 한, 보안이 되는 메커니즘이 아니다. 이것을 통해 악의적 행위자가 kubelet 인증서(클라이언트 인증)를 사용하여 아무 IP나 도메인 네임에 대해 인증서를 요청하는 CSR의 생성을 방지할 수 있을 것이다.

추가 사용자를 위한 kubeconfig 파일 생성하기

클러스터 생성 과정에서, kubeadm은 Subject: O = system:masters, CN = kubernetes-admin 값이 설정되도록 admin.conf의 인증서에 서명한다. system:masters는 인증 계층(예: RBAC)을 우회하는 획기적인 슈퍼유저 그룹이다. admin.conf를 추가 사용자와 공유하는 것은 권장하지 않는다!

대신, kubeadm kubeconfig user 명령어를 사용하여 추가 사용자를 위한 kubeconfig 파일을 생성할 수 있다. 이 명령어는 명령줄 플래그와 kubeadm 환경 설정 옵션을 모두 인식한다. 생성된 kubeconfig는 stdout으로 출력되며, kubeadm kubeconfig user ... > somefile.conf 명령어를 사용하여 파일에 기록될 수 있다.

다음은 --config 플래그와 함께 사용될 수 있는 환경 설정 파일 예시이다.

# example.yaml
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
# kubeconfig에서 타겟 "cluster"로 사용될 것이다.
clusterName: "kubernetes"
# kubeconfig에서 클러스터의 "server"(IP 또는 DNS 이름)로 사용될 것이다.
controlPlaneEndpoint: "some-dns-address:6443"
# 클러스터 CA 키 및 인증서가 이 로컬 디렉토리에서 로드될 것이다.
certificatesDir: "/etc/kubernetes/pki"

이러한 항목들이 사용하고자 하는 클러스터의 상세 사항과 일치하는지 확인한다. 기존 클러스터의 환경 설정을 보려면 다음 명령을 사용한다.

kubectl get cm kubeadm-config -n kube-system -o=jsonpath="{.data.ClusterConfiguration}"

다음 예시는 appdevs 그룹의 새 사용자 johndoe를 위해 24시간동안 유효한 인증서와 함께 kubeconfig 파일을 생성할 것이다.

kubeadm kubeconfig user --config example.yaml --org appdevs --client-name johndoe --validity-period 24h

다음 예시는 1주일간 유효한 관리자 크리덴셜을 갖는 kubeconfig 파일을 생성할 것이다.

kubeadm kubeconfig user --config example.yaml --client-name admin --validity-period 168h

3.1.2 - kubeadm 클러스터 업그레이드

이 페이지는 kubeadm으로 생성된 쿠버네티스 클러스터를 1.30.x 버전에서 1.31.x 버전으로, 1.31.x 버전에서 1.31.y(여기서 y > x) 버전으로 업그레이드하는 방법을 설명한다. 업그레이드가 지원되지 않는 경우 마이너 버전을 건너뛴다. 더 자세한 정보는 버전 차이(skew) 정책을 참고한다.

이전 버전의 kubeadm을 사용하여 생성된 클러스터 업그레이드에 대한 정보를 보려면, 이 페이지 대신 다음의 페이지들을 참고한다.

추상적인 업그레이드 작업 절차는 다음과 같다.

  1. 기본 컨트롤 플레인 노드를 업그레이드한다.
  2. 추가 컨트롤 플레인 노드를 업그레이드한다.
  3. 워커(worker) 노드를 업그레이드한다.

시작하기 전에

  • 릴리스 노트를 주의 깊게 읽어야 한다.
  • 클러스터는 정적 컨트롤 플레인 및 etcd 파드 또는 외부 etcd를 사용해야 한다.
  • 데이터베이스에 저장된 앱-레벨 상태와 같은 중요한 컴포넌트를 반드시 백업한다. kubeadm upgrade 는 워크로드에 영향을 미치지 않고, 쿠버네티스 내부의 컴포넌트만 다루지만, 백업은 항상 모범 사례일 정도로 중요하다.
  • 스왑을 비활성화해야 한다.

추가 정보

  • 아래의 지침은 업그레이드 과정 중 언제 각 노드를 드레인해야 하는지를 제시한다. kubelet에 대해 마이너 버전 업그레이드를 하는 경우, 먼저 업그레이드할 노드(들)을 드레인해야 한다. 컨트롤 플레인 노드의 경우, CoreDNS 파드 또는 다른 중요한 워크로드를 실행 중일 수 있다. 더 많은 정보는 노드 드레인하기를 참조한다.
  • 컨테이너 사양 해시 값이 변경되므로, 업그레이드 후 모든 컨테이너가 다시 시작된다.
  • kubelet이 업그레이드된 이후 kubelet 서비스가 성공적으로 재시작되었는지 확인하려면, systemctl status kubelet 명령을 실행하거나, journalctl -xeu kubelet 명령을 실행하여 서비스 로그를 확인할 수 있다.
  • 클러스터를 재구성하기 위해 kubeadm upgrade 시에 kubeadm 구성 API 종류를 명시하여 --config 플래그를 사용하는 것은 추천하지 않으며 예상치 못한 결과를 초래할 수 있다. 대신 kubeadm 클러스터 재구성하기를 참조한다.

업그레이드할 버전 결정

OS 패키지 관리자를 사용하여 쿠버네티스의 최신 패치 릴리스 버전(1.31)을 찾는다.

apt update
apt-cache madison kubeadm
# 목록에서 최신 버전(1.31)을 찾는다
# 1.31.x-00과 같아야 한다. 여기서 x는 최신 패치이다.

yum list --showduplicates kubeadm --disableexcludes=kubernetes
# 목록에서 최신 버전(1.31)을 찾는다
# 1.31.x-0과 같아야 한다. 여기서 x는 최신 패치이다.

컨트롤 플레인 노드 업그레이드

컨트롤 플레인 노드의 업그레이드 절차는 한 번에 한 노드씩 실행해야 한다. 먼저 업그레이드할 컨트롤 플레인 노드를 선택한다. /etc/kubernetes/admin.conf 파일이 있어야 한다.

"kubeadm upgrade" 호출

첫 번째 컨트롤 플레인 노드의 경우

  • kubeadm 업그레이드

     # 1.31.x-00에서 x를 최신 패치 버전으로 바꾼다.
     apt-mark unhold kubeadm && \
     apt-get update && apt-get install -y kubeadm=1.31.x-00 && \
     apt-mark hold kubeadm
    

    # 1.31.x-0에서 x를 최신 패치 버전으로 바꾼다.
    yum install -y kubeadm-1.31.x-0 --disableexcludes=kubernetes
    

  • 다운로드하려는 버전이 잘 받아졌는지 확인한다.

    kubeadm version
    
  • 업그레이드 계획을 확인한다.

    kubeadm upgrade plan
    

    이 명령은 클러스터를 업그레이드할 수 있는지를 확인하고, 업그레이드할 수 있는 버전을 가져온다. 또한 컴포넌트 구성 버전 상태가 있는 표를 보여준다.

  • 업그레이드할 버전을 선택하고, 적절한 명령을 실행한다. 예를 들면 다음과 같다.

    # 이 업그레이드를 위해 선택한 패치 버전으로 x를 바꾼다.
    sudo kubeadm upgrade apply v1.31.x
    

    명령이 완료되면 다음을 확인해야 한다.

    [upgrade/successful] SUCCESS! Your cluster was upgraded to "v1.31.x". Enjoy!
    
    [upgrade/kubelet] Now that your control plane is upgraded, please proceed with upgrading your kubelets if you haven't already done so.
    
  • CNI 제공자 플러그인을 수동으로 업그레이드한다.

    CNI(컨테이너 네트워크 인터페이스) 제공자는 자체 업그레이드 지침을 따를 수 있다. 애드온 페이지에서 사용하는 CNI 제공자를 찾고 추가 업그레이드 단계가 필요한지 여부를 확인한다.

    CNI 제공자가 데몬셋(DaemonSet)으로 실행되는 경우 추가 컨트롤 플레인 노드에는 이 단계가 필요하지 않다.

다른 컨트롤 플레인 노드의 경우

첫 번째 컨트롤 플레인 노드와 동일하지만 다음을 사용한다.

sudo kubeadm upgrade node

아래 명령 대신 위의 명령을 사용한다.

sudo kubeadm upgrade apply

kubeadm upgrade plan 을 호출하고 CNI 공급자 플러그인을 업그레이드할 필요가 없다.

노드 드레인

  • 스케줄 불가능(unschedulable)으로 표시하고 워크로드를 축출하여 유지 보수할 노드를 준비한다.

    # <node-to-drain>을 드레인하는 노드의 이름으로 바꾼다.
    kubectl drain <node-to-drain> --ignore-daemonsets
    

kubelet과 kubectl 업그레이드

  • 모든 컨트롤 플레인 노드에서 kubelet 및 kubectl을 업그레이드한다.

    # replace x in 1.31.x-00의 x를 최신 패치 버전으로 바꾼다
    apt-mark unhold kubelet kubectl && \
    apt-get update && apt-get install -y kubelet=1.31.x-00 kubectl=1.31.x-00 && \
    apt-mark hold kubelet kubectl
    

    # 1.31.x-0에서 x를 최신 패치 버전으로 바꾼다
    yum install -y kubelet-1.31.x-0 kubectl-1.31.x-0 --disableexcludes=kubernetes
    

  • kubelet을 다시 시작한다.

    sudo systemctl daemon-reload
    sudo systemctl restart kubelet
    

노드 uncordon

  • 노드를 스케줄 가능(schedulable)으로 표시하여 노드를 다시 온라인 상태로 전환한다.

    # <node-to-uncordon>을 드레인하려는 노드의 이름으로 바꾼다.
    kubectl uncordon <node-to-uncordon>
    

워커 노드 업그레이드

워커 노드의 업그레이드 절차는 워크로드를 실행하는 데 필요한 최소 용량을 보장하면서, 한 번에 하나의 노드 또는 한 번에 몇 개의 노드로 실행해야 한다.

kubeadm 업그레이드

  • 모든 워커 노드에서 kubeadm을 업그레이드한다.

    # 1.31.x-00의 x를 최신 패치 버전으로 바꾼다
    apt-mark unhold kubeadm && \
    apt-get update && apt-get install -y kubeadm=1.31.x-00 && \
    apt-mark hold kubeadm
    

    # 1.31.x-0에서 x를 최신 패치 버전으로 바꾼다
    yum install -y kubeadm-1.31.x-0 --disableexcludes=kubernetes
    

"kubeadm upgrade" 호출

  • 워커 노드의 경우 로컬 kubelet 구성을 업그레이드한다.

    sudo kubeadm upgrade node
    

노드 드레인

  • 스케줄 불가능(unschedulable)으로 표시하고 워크로드를 축출하여 유지 보수할 노드를 준비한다.

    # <node-to-drain>을 드레인하려는 노드 이름으로 바꾼다.
    kubectl drain <node-to-drain> --ignore-daemonsets
    

kubelet과 kubectl 업그레이드

  • kubelet 및 kubectl을 업그레이드한다.

    # 1.31.x-00의 x를 최신 패치 버전으로 바꾼다
    apt-mark unhold kubelet kubectl && \
    apt-get update && apt-get install -y kubelet=1.31.x-00 kubectl=1.31.x-00 && \
    apt-mark hold kubelet kubectl
    

    # 1.31.x-0에서 x를 최신 패치 버전으로 바꾼다
    yum install -y kubelet-1.31.x-0 kubectl-1.31.x-0 --disableexcludes=kubernetes
    

  • kubelet을 다시 시작한다.

    sudo systemctl daemon-reload
    sudo systemctl restart kubelet
    

노드에 적용된 cordon 해제

  • 스케줄 가능(schedulable)으로 표시하여 노드를 다시 온라인 상태로 만든다.
# <node-to-uncordon>을 노드의 이름으로 바꾼다.
kubectl uncordon <node-to-uncordon>

클러스터 상태 확인

모든 노드에서 kubelet을 업그레이드한 후 kubectl이 클러스터에 접근할 수 있는 곳에서 다음의 명령을 실행하여 모든 노드를 다시 사용할 수 있는지 확인한다.

kubectl get nodes

모든 노드에 대해 STATUS 열에 Ready 가 표시되어야 하고, 버전 번호가 업데이트되어 있어야 한다.

장애 상태에서의 복구

예를 들어 kubeadm upgrade 를 실행하는 중에 예기치 못한 종료로 인해 업그레이드가 실패하고 롤백하지 않는다면, kubeadm upgrade 를 다시 실행할 수 있다. 이 명령은 멱등성을 보장하며 결국 실제 상태가 선언한 의도한 상태인지 확인한다.

잘못된 상태에서 복구하기 위해, 클러스터가 실행 중인 버전을 변경하지 않고 kubeadm upgrade apply --force 를 실행할 수도 있다.

업그레이드하는 동안 kubeadm은 /etc/kubernetes/tmp 아래에 다음과 같은 백업 폴더를 작성한다.

  • kubeadm-backup-etcd-<date>-<time>
  • kubeadm-backup-manifests-<date>-<time>

kubeadm-backup-etcd 는 컨트롤 플레인 노드에 대한 로컬 etcd 멤버 데이터의 백업을 포함한다. etcd 업그레이드가 실패하고 자동 롤백이 작동하지 않으면, 이 폴더의 내용을 /var/lib/etcd 에서 수동으로 복원할 수 있다. 외부 etcd를 사용하는 경우 이 백업 폴더는 비어있다.

kubeadm-backup-manifests 는 컨트롤 플레인 노드에 대한 정적 파드 매니페스트 파일의 백업을 포함한다. 업그레이드가 실패하고 자동 롤백이 작동하지 않으면, 이 폴더의 내용을 /etc/kubernetes/manifests 에서 수동으로 복원할 수 있다. 어떤 이유로 특정 컴포넌트의 업그레이드 전 매니페스트 파일과 업그레이드 후 매니페스트 파일 간에 차이가 없는 경우, 백업 파일은 기록되지 않는다.

작동 원리

kubeadm upgrade apply 는 다음을 수행한다.

  • 클러스터가 업그레이드 가능한 상태인지 확인한다.
    • API 서버에 접근할 수 있다
    • 모든 노드가 Ready 상태에 있다
    • 컨트롤 플레인이 정상적으로 동작한다
  • 버전 차이(skew) 정책을 적용한다.
  • 컨트롤 플레인 이미지가 사용 가능한지 또는 머신으로 가져올 수 있는지 확인한다.
  • 컴포넌트 구성에 버전 업그레이드가 필요한 경우 대체 구성을 생성하거나 사용자가 제공한 것으로 덮어 쓰기한다.
  • 컨트롤 플레인 컴포넌트 또는 롤백 중 하나라도 나타나지 않으면 업그레이드한다.
  • 새로운 CoreDNSkube-proxy 매니페스트를 적용하고 필요한 모든 RBAC 규칙이 생성되도록 한다.
  • API 서버의 새 인증서와 키 파일을 작성하고 180일 후에 만료될 경우 이전 파일을 백업한다.

kubeadm upgrade node 는 추가 컨트롤 플레인 노드에서 다음을 수행한다.

  • 클러스터에서 kubeadm ClusterConfiguration 을 가져온다.
  • 선택적으로 kube-apiserver 인증서를 백업한다.
  • 컨트롤 플레인 컴포넌트에 대한 정적 파드 매니페스트를 업그레이드한다.
  • 이 노드의 kubelet 구성을 업그레이드한다.

kubeadm upgrade node 는 워커 노드에서 다음을 수행한다.

  • 클러스터에서 kubeadm ClusterConfiguration 을 가져온다.
  • 이 노드의 kubelet 구성을 업그레이드한다.

3.1.3 - 윈도우 노드 업그레이드

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

이 페이지는 kubeadm으로 생성된 윈도우 노드를 업그레이드하는 방법을 설명한다.

시작하기 전에

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

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

워커 노드 업그레이드

kubeadm 업그레이드

  1. 윈도우 노드에서, kubeadm을 업그레이드한다.

    # 1.31.0을 사용 중인 쿠버네티스 버전으로 변경한다.
    curl.exe -Lo <kubeadm.exe을 저장할 경로> "https://dl.k8s.io/v1.31.0/bin/windows/amd64/kubeadm.exe"
    

노드 드레인

  1. 쿠버네티스 API에 접근할 수 있는 머신에서, 스케줄 불가능한 것으로 표시하고 워크로드를 축출하여 유지 보수할 노드를 준비한다.

    # <node-to-drain>을 드레이닝하려는 노드 이름으로 바꾼다
    kubectl drain <node-to-drain> --ignore-daemonsets
    

    다음과 비슷한 출력이 표시되어야 한다.

    node/ip-172-31-85-18 cordoned
    node/ip-172-31-85-18 drained
    

kubelet 구성 업그레이드

  1. 윈도우 노드에서, 다음의 명령을 호출하여 새 kubelet 구성을 동기화한다.

    kubeadm upgrade node
    

kubelet 및 kube-proxy 업그레이드

  1. 윈도우 노드에서, kubelet을 업그레이드하고 다시 시작한다.

    stop-service kubelet
    curl.exe -Lo <kubelet.exe을 저장할 경로> "https://dl.k8s.io/v1.31.0/bin/windows/amd64/kubelet.exe"
    restart-service kubelet
    
  2. 윈도우 노드에서, kube-proxy를 업그레이드하고 다시 시작한다.

    stop-service kube-proxy
    curl.exe -Lo <kube-proxy.exe을 저장할 경로> "https://dl.k8s.io/v1.31.0/bin/windows/amd64/kube-proxy.exe"
    restart-service kube-proxy
    

노드에 적용된 cordon 해제

  1. 쿠버네티스 API에 접근할 수 있는 머신에서, 스케줄 가능으로 표시하여 노드를 다시 온라인으로 가져온다.

    # <node-to-drain>을 노드의 이름으로 바꾼다
    kubectl uncordon <node-to-drain>
    

3.1.4 - 리눅스 노드 업그레이드

이 페이지는 kubeadm으로 생성된 리눅스 노드를 업그레이드하는 방법을 설명한다.

시작하기 전에

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

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

워커 노드 업그레이드

kubeadm 업그레이드

kubeadm을 업그레이드한다.

# 1.31.x-00 에서 x 에 최신 버전을 넣는다.
apt-mark unhold kubeadm && \
apt-get update && apt-get install -y kubeadm=1.31.x-00 && \
apt-mark hold kubeadm

# 1.31.x-00 에서 x 에 최신 버전을 넣는다.
yum install -y kubeadm-1.31.x-0 --disableexcludes=kubernetes

"kubeadm upgrade" 호출

  • 워커 노드의 경우 로컬 kubelet 구성을 업그레이드한다.

    sudo kubeadm upgrade node
    

노드 드레인

  • 노드를 스케줄 불가능한 것으로 표시하고 워크로드를 축출하여 유지 보수할 노드를 준비한다.

    # <node-to-drain> 에 드레인하려는 노드의 이름을 넣는다.
    kubectl drain <node-to-drain> --ignore-daemonsets
    

kubelet과 kubectl 업그레이드

  • kubelet과 kubectl 업그레이드.

    # 1.31.x-00 에서 x 에 최신 버전을 넣는다.
    apt-mark unhold kubelet kubectl && \
    apt-get update && apt-get install -y kubelet=1.31.x-00 kubectl=1.31.x-00 && \
    apt-mark hold kubelet kubectl
    

    # 1.31.x-00 에서 x 에 최신 버전을 넣는다.
    yum install -y kubelet-1.31.x-0 kubectl-1.31.x-0 --disableexcludes=kubernetes
    

  • kubelet을 재시작한다.

    sudo systemctl daemon-reload
    sudo systemctl restart kubelet
    

노드에 적용된 cordon 해제

  • 스케줄 가능으로 표시하여 노드를 다시 온라인으로 가져온다.

    # <node-to-uncordon> 에 노드의 이름을 넣는다.
    kubectl uncordon <node-to-uncordon>
    

다음 내용

3.2 - 도커심으로부터 마이그레이션

이 섹션은 도커심에서 다른 컨테이너 런타임으로 마이그레이션할 때에 알아야 할 정보를 제공한다.

쿠버네티스 1.20에서의 도커심 사용 중단(deprecation) 발표 이후, 이것이 다양한 워크로드와 쿠버네티스 설치에 어떻게 영향을 미칠지에 대한 질문이 많았다. 도커심 제거 FAQ는 관련된 문제를 더 잘 이해할 수 있도록 도움을 준다.

도커심은 쿠버네티스 릴리스 v1.24부터 제거되었다. 컨테이너 런타임으로 도커 엔진을 통한 도커심을 사용하는 상황에서 v1.24로 업그레이드하려는 경우, 다른 런타임으로 마이그레이션하거나 다른 방법을 찾아 도커 엔진 지원을 받는 것이 좋다. 선택 가능한 옵션은 컨테이너 런타임 섹션에서 확인한다. 마이그레이션 중 문제를 마주한다면 문제를 보고하면 좋다. 이를 통해 문제를 시기적절하게 해결할 수 있으며, 클러스터도 도커심 제거에 대비할 수 있다.

클러스터는 두 종류 이상의 노드들을 포함할 수 있지만 이는 일반적인 구성은 아니다.

다음 작업을 통해 마이그레이션을 수행할 수 있다.

다음 내용

  • 도커심의 사용 중단 및 제거에 대한 논의를 추적하는 깃허브 이슈가 있다.
  • 도커심에서 마이그레이션하는 것에 관한 결함이나 다른 기술적 문제를 발견한다면, 쿠버네티스 프로젝트에 이슈를 남길 수 있다.

3.2.1 - 도커 엔진 노드를 도커심에서 cri-dockerd로 마이그레이션하기

이 페이지는 도커 엔진 노드가 도커심 대신 cri-dockerd를 사용하도록 마이그레이션하는 방법을 보여 준다. 다음 시나리오에서는 아래 단계를 따라야 한다.

  • 도커심 사용은 중단하고 싶지만, 쿠버네티스의 컨테이너 실행에는 여전히 도커 엔진을 사용하기를 원하는 경우
  • 쿠버네티스 버전 v1.31로 업그레이드를 원하고 기존 클러스터가 도커심을 사용하는 경우. 이러한 경우에는 도커심을 다른 것으로 대체해야 하며 cri-dockerd도 선택지 중 하나이다.

도커심 제거에 관하여 더 배우려면, FAQ page를 읽어보자.

cri-dockerd란 무엇인가?

쿠버네티스 1.23 이하에서는 도커심 이라는 이름의 쿠버네티스 내장 구성요소를 사용하여 도커 엔진을 쿠버네티스 컨테이너 런타임으로 사용할 수 있었다. 도커심 구성 요소는 쿠버네티스 1.24 릴리스에서 제거되었지만, 대신 서드 파티 대체제 cri-dockerd를 사용할 수 있다. cri-dockerd 어댑터를 사용하면 컨테이너 런타임 인터페이스(Container runtime interface, CRI)를 통해 도커 엔진을 사용할 수 있다.

컨테이너 런타임으로 도커 엔진을 계속 사용할 수 있도록 cri-dockerd로 마이그레이션하려는 경우 영향을 받는 각각의 노드에 아래 내용을 진행해야 한다.

  1. cri-dockerd를 설치한다.
  2. 노드를 통제(cordon)하고 비운다(drain).
  3. cri-dockerd를 사용하도록 kubelet를 설정한다.
  4. kubelet을 재시작한다.
  5. 노드가 정상(healthy)인지 확인한다.

중요하지 않은(non-critical) 노드에서 먼저 테스트한다.

cri-dockerd로 마이그레이션하려는 각 노드에 대해 아래 단계를 수행해야 한다.

시작하기 전에

노드의 통제(Cordon)와 비우기(drain)

  1. 새로운 파드를 노드에 스케줄링하는 것을 막기 위해 노드를 통제한다.

    kubectl cordon <NODE_NAME>
    

    <NODE_NAME> 부분에 노드의 이름을 입력한다.

  2. 실행 중인 파드를 안전하게 축출하기 위해 노드를 비운다.

    kubectl drain <NODE_NAME> \
        --ignore-daemonsets
    

cri-dockerd를 사용하도록 kubelet 설정

아래의 단계는 kubeadm 도구를 사용하여 생성된 클러스터에 적용된다. 다른 도구를 사용했다면, 해당 도구에 대한 환경 설정 방법을 참고하여 kubelet 환경 설정을 수정해야 한다.

  1. 영향 받는 각 노드의 /var/lib/kubelet/kubeadm-flags.env를 연다.
  2. --container-runtime-endpoint 플래그를 unix:///var/run/cri-dockerd.sock로 수정한다.

kubeadm 도구는 노드의 소켓을 컨트롤 플레인의 Node 오브젝트의 어노테이션으로 저장한다. 영향을 받는 각 노드의 해당 소켓을 수정하려면 다음을 따른다.

  1. Node 오브젝트의 YAML 표현식을 편집한다.

    KUBECONFIG=/path/to/admin.conf kubectl edit no <NODE_NAME>
    

    각 인자는 다음과 같이 입력한다.

    • /path/to/admin.conf: kubectl 환경 설정 파일(admin.conf)의 경로.
    • <NODE_NAME>: 수정을 원하는 노드의 이름.
  2. kubeadm.alpha.kubernetes.io/cri-socket의 값 /var/run/dockershim.sockunix:///var/run/cri-dockerd.sock로 변경한다.

  3. 변경을 저장한다. Node 오브젝트는 저장 시 업데이트된다.

kubelet 재시작

systemctl restart kubelet

노드가 정상(healthy)인지 확인

노드가 cri-dockerd 엔드포인트를 사용하는지 확인하려면, 사용 런타임 찾기 지침을 따른다. kubelet의 --container-runtime-endpoint 플래그는 unix:///var/run/cri-dockerd.sock 이어야 한다.

노드 통제 해제(Uncordon)

노드에 파드를 스케줄 하도록 통제를 해제한다.

kubectl uncordon <NODE_NAME>

다음 내용

3.3 - 메모리, CPU 와 API 리소스 관리

3.3.1 - 네임스페이스에 대한 기본 메모리 요청량과 상한 구성

한 네임스페이스에 메모리 리소스 상한의 기본값을 정의하며, 이를 통해 미리 설정한 메모리 리소스 상한이 해당 네임스페이스의 새로운 파드에 설정되도록 한다.

이 페이지는 네임스페이스에 대한 기본 메모리 요청량(request) 및 상한(limit)을 구성하는 방법을 보여준다.

쿠버네티스 클러스터를 여러 네임스페이스로 나눌 수 있다. 기본 메모리 상한이 설정되어 있는 네임스페이스에 파드를 생성했는데, 해당 파드의 모든 컨테이너에 메모리 상한이 명시되어 있지 않다면, 컨트롤 플레인이 해당 컨테이너에 기본 메모리 상한을 할당한다.

쿠버네티스는 이 문서의 뒷부분에서 설명하는 특정 조건에서 기본 메모리 요청량을 할당한다.

시작하기 전에

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

클러스터에 네임스페이스를 생성할 수 있는 권한이 있어야 한다.

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

네임스페이스 생성

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

kubectl create namespace default-mem-example

리밋레인지(LimitRange)와 파드 생성

다음은 예시 리밋레인지에 대한 매니페스트이다. 이 매니페스트는 기본 메모리 요청량 및 기본 메모리 상한을 지정한다.

apiVersion: v1
kind: LimitRange
metadata:
  name: mem-limit-range
spec:
  limits:
  - default:
      memory: 512Mi
    defaultRequest:
      memory: 256Mi
    type: Container

default-mem-example 네임스페이스에 리밋레인지를 생성한다.

kubectl apply -f https://k8s.io/examples/admin/resource/memory-defaults.yaml --namespace=default-mem-example

이제 파드를 default-mem-example 네임스페이스에 생성하고, 해당 파드의 어떤 컨테이너도 자체 메모리 요청량(request)과 상한(limit)을 명시하지 않으면, 컨트롤 플레인이 해당 컨테이너에 메모리 요청량의 기본값(256 MiB)과 상한의 기본값(512 MiB)을 지정한다.

다음은 컨테이너가 하나인 파드의 매니페스트이다. 해당 컨테이너는 메모리 요청량과 상한을 지정하지 않는다.

apiVersion: v1
kind: Pod
metadata:
  name: default-mem-demo
spec:
  containers:
  - name: default-mem-demo-ctr
    image: nginx

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/admin/resource/memory-defaults-pod.yaml --namespace=default-mem-example

파드에 대한 자세한 정보를 본다.

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

출력 결과는 파드의 컨테이너에 256MiB의 메모리 요청량과 512MiB의 메모리 상한이 있음을 나타낸다. 이것은 리밋레인지에 의해 지정된 기본값이다.

containers:
- image: nginx
  imagePullPolicy: Always
  name: default-mem-demo-ctr
  resources:
    limits:
      memory: 512Mi
    requests:
      memory: 256Mi

파드를 삭제한다.

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

컨테이너 상한은 지정하고, 요청량을 지정하지 않으면 어떻게 되나?

다음은 컨테이너가 하나인 파드의 매니페스트이다. 해당 컨테이너는 메모리 상한은 지정하지만, 요청량은 지정하지 않는다.

apiVersion: v1
kind: Pod
metadata:
  name: default-mem-demo-2
spec:
  containers:
  - name: default-mem-demo-2-ctr
    image: nginx
    resources:
      limits:
        memory: "1Gi"

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/admin/resource/memory-defaults-pod-2.yaml --namespace=default-mem-example

파드에 대한 자세한 정보를 본다.

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

출력 결과는 컨테이너의 메모리 요청량이 메모리 상한과 일치하도록 설정되었음을 보여준다. 참고로 컨테이너에는 기본 메모리 요청량의 값인 256Mi가 할당되지 않았다.

resources:
  limits:
    memory: 1Gi
  requests:
    memory: 1Gi

컨테이너의 요청량은 지정하고, 상한을 지정하지 않으면 어떻게 되나?

다음은 컨테이너가 하나인 파드의 예시 매니페스트이다. 해당 컨테이너는 메모리 요청량은 지정하지만, 상한은 지정하지 않는다.

apiVersion: v1
kind: Pod
metadata:
  name: default-mem-demo-3
spec:
  containers:
  - name: default-mem-demo-3-ctr
    image: nginx
    resources:
      requests:
        memory: "128Mi"

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/admin/resource/memory-defaults-pod-3.yaml --namespace=default-mem-example

파드 사양을 확인한다.

kubectl get pod default-mem-demo-3 --output=yaml --namespace=default-mem-example

출력을 보면 컨테이너의 매니페스트에 명시한 값대로 컨테이너의 메모리 요청량이 설정된 것을 알 수 있다. 해당 컨테이너의 메모리 상한은 512 MiB로 설정되며, 이는 네임스페이스의 메모리 상한 기본값과 일치한다.

resources:
  limits:
    memory: 512Mi
  requests:
    memory: 128Mi

기본 메모리 상한 및 요청량에 대한 동기

네임스페이스에 리소스 쿼터가 설정되어 있는 경우, 메모리 상한에 기본값을 설정하는 것이 좋다. 다음은 리소스 쿼터가 네임스페이스에 적용하는 세 가지 제한 사항이다.

  • 네임스페이스에서 실행되는 모든 파드에 대해, 모든 컨테이너에 메모리 상한이 있어야 한다. (파드의 모든 컨테이너에 대해 메모리 상한을 지정하면, 쿠버네티스가 파드 내의 컨테이너의 상한을 합산하여 파드-수준 메모리 상한을 추론할 수 있다.)
  • 메모리 상한은 해당 파드가 스케줄링될 노드에 리소스 예약을 적용한다. 해당 네임스페이스의 모든 파드에 대해 예약된 메모리 총량이 지정된 상한을 초과하지 않아야 한다.
  • 해당 네임스페이스의 모든 파드가 실제로 사용하고 있는 메모리의 총량 또한 지정된 상한을 초과하지 않아야 한다.

리밋레인지를 추가할 때에는 다음을 고려해야 한다.

컨테이너를 갖고 있는 해당 네임스페이스의 파드가 자체 메모리 상한을 지정하지 않았다면, 컨트롤 플레인이 해당 컨테이너에 메모리 상한 기본값을 적용하며, 해당 파드는 메모리 리소스쿼터가 적용된 네임스페이스에서 실행되도록 허용될 수 있다.

정리

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

kubectl delete namespace default-mem-example

다음 내용

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

앱 개발자를 위한 문서

3.3.2 - 네임스페이스에 대한 기본 CPU 요청량과 상한 구성

한 네임스페이스에 CPU 리소스 상한의 기본값을 정의하며, 이를 통해 미리 설정한 CPU 리소스 상한이 해당 네임스페이스의 새로운 파드에 설정되도록 한다.

이 페이지는 네임스페이스에 대한 기본 CPU 요청량(request) 및 상한(limit)을 구성하는 방법을 보여준다.

쿠버네티스 클러스터를 여러 네임스페이스로 나눌 수 있다. 기본 CPU 상한이 설정되어 있는 네임스페이스에 파드를 생성했는데, 해당 파드의 모든 컨테이너에 CPU 상한이 명시되어 있지 않다면, 컨트롤 플레인이 해당 컨테이너에 기본 CPU 상한을 할당한다.

쿠버네티스는 기본 CPU 사용량을 할당하는데, 이는 이 페이지의 이후 부분에서 설명될 특정 조건 하에서만 수행된다.

시작하기 전에

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

클러스터에 네임스페이스를 생성할 수 있는 권한이 있어야 한다.

쿠버네티스에서 “1.0 CPU”가 무엇을 의미하는지 익숙하지 않다면, CPU의 의미를 참조한다.

네임스페이스 생성

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

kubectl create namespace default-cpu-example

리밋레인지(LimitRange)와 파드 생성

다음은 예시 리밋레인지에 대한 매니페스트이다. 이 매니페스트는 기본 CPU 요청량 및 기본 CPU 상한을 지정한다.

apiVersion: v1
kind: LimitRange
metadata:
  name: cpu-limit-range
spec:
  limits:
  - default:
      cpu: 1
    defaultRequest:
      cpu: 0.5
    type: Container

default-cpu-example 네임스페이스에 리밋레인지를 생성한다.

kubectl apply -f https://k8s.io/examples/admin/resource/cpu-defaults.yaml --namespace=default-cpu-example

이제 파드를 default-cpu-example 네임스페이스에 생성하고, 해당 파드의 어떤 컨테이너도 자체 CPU 요청량(request)과 상한(limit)을 명시하지 않으면, 컨트롤 플레인이 해당 컨테이너에 CPU 요청량의 기본값(0.5)과 상한의 기본값(1)을 지정한다.

다음은 컨테이너가 하나인 파드의 매니페스트이다. 해당 컨테이너는 CPU 요청량과 상한을 지정하지 않는다.

apiVersion: v1
kind: Pod
metadata:
  name: default-cpu-demo
spec:
  containers:
  - name: default-cpu-demo-ctr
    image: nginx

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/admin/resource/cpu-defaults-pod.yaml --namespace=default-cpu-example

파드의 사양을 확인한다.

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

출력을 보면 파드 내 유일한 컨테이너의 CPU 요청량이 500m cpu("500 밀리cpu"로 읽을 수 있음)이고, CPU 상한이 1 cpu임을 알 수 있다. 이것은 리밋레인지에 의해 지정된 기본값이다.

containers:
- image: nginx
  imagePullPolicy: Always
  name: default-cpu-demo-ctr
  resources:
    limits:
      cpu: "1"
    requests:
      cpu: 500m

컨테이너 상한은 지정하고, 요청량을 지정하지 않으면 어떻게 되나?

다음은 컨테이너가 하나인 파드의 매니페스트이다. 해당 컨테이너는 CPU 상한은 지정하지만, 요청량은 지정하지 않는다.

apiVersion: v1
kind: Pod
metadata:
  name: default-cpu-demo-2
spec:
  containers:
  - name: default-cpu-demo-2-ctr
    image: nginx
    resources:
      limits:
        cpu: "1"

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/admin/resource/cpu-defaults-pod-2.yaml --namespace=default-cpu-example

생성한 파드의 명세를 확인한다.

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

출력 결과는 컨테이너의 CPU 요청량이 CPU 상한과 일치하도록 설정되었음을 보여준다. 참고로 컨테이너에는 CPU 요청량의 기본값인 0.5 cpu가 할당되지 않았다.

resources:
  limits:
    cpu: "1"
  requests:
    cpu: "1"

컨테이너의 요청량은 지정하고, 상한을 지정하지 않으면 어떻게 되나?

다음은 컨테이너가 하나인 파드의 예시 매니페스트이다. 해당 컨테이너는 CPU 요청량은 지정하지만, 상한은 지정하지 않는다.

apiVersion: v1
kind: Pod
metadata:
  name: default-cpu-demo-3
spec:
  containers:
  - name: default-cpu-demo-3-ctr
    image: nginx
    resources:
      requests:
        cpu: "0.75"

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/admin/resource/cpu-defaults-pod-3.yaml --namespace=default-cpu-example

생성한 파드의 명세를 확인한다.

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

출력을 보면 파드 생성 시 명시한 값대로 컨테이너의 CPU 요청량이 설정된 것을 알 수 있다(다시 말해, 매니페스트와 일치한다). 그러나, 해당 컨테이너의 CPU 상한은 1 cpu로 설정되며, 이는 네임스페이스의 CPU 상한 기본값이다.

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

CPU 상한 및 요청량의 기본값에 대한 동기

네임스페이스에 리소스 쿼터가 설정되어 있는 경우, CPU 상한에 대해 기본값을 설정하는 것이 좋다. 다음은 CPU 리소스 쿼터가 네임스페이스에 적용하는 두 가지 제한 사항이다.

  • 네임스페이스에서 실행되는 모든 파드에 대해, 모든 컨테이너에 CPU 상한이 있어야 한다.
  • CPU 상한은 해당 파드가 스케줄링될 노드에 리소스 예약을 적용한다. 해당 네임스페이스의 모든 파드에 대해 예약된 CPU 총량이 지정된 상한을 초과하지 않아야 한다.

리밋레인지를 추가할 때에는 다음을 고려해야 한다.

컨테이너를 갖고 있는 해당 네임스페이스의 파드가 자체 CPU 상한을 지정하지 않았다면, 컨트롤 플레인이 해당 컨테이너에 CPU 상한 기본값을 적용하며, 해당 파드는 CPU 리소스쿼터가 적용된 네임스페이스에서 실행되도록 허용될 수 있다.

정리

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

kubectl delete namespace default-cpu-example

다음 내용

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

앱 개발자를 위한 문서

3.3.3 - 네임스페이스에 대한 메모리의 최소 및 최대 제약 조건 구성

한 네임스페이스 내에서 메모리 리소스 제한의 유효한 범위를 정의하며, 이를 통해 해당 네임스페이스의 새로운 파드가 미리 설정한 범위 안에 들어오도록 한다.

이 페이지는 네임스페이스에서 실행되는 컨테이너가 사용하는 메모리의 최솟값과 최댓값을 설정하는 방법을 보여준다. 리밋레인지(LimitRange) 오브젝트에 최소 및 최대 메모리 값을 지정한다. 파드가 리밋레인지에 의해 부과된 제약 조건을 충족하지 않으면, 네임스페이스에서 생성될 수 없다.

시작하기 전에

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

클러스터에 네임스페이스를 생성할 수 있는 권한이 있어야 한다.

클러스터의 각 노드에는 파드가 사용할 수 있는 메모리가 최소 1GiB 이상 있어야 한다.

네임스페이스 생성

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

kubectl create namespace constraints-mem-example

리밋레인지와 파드 생성

다음은 리밋레인지의 예시 매니페스트이다.

apiVersion: v1
kind: LimitRange
metadata:
  name: mem-min-max-demo-lr
spec:
  limits:
  - max:
      memory: 1Gi
    min:
      memory: 500Mi
    type: Container

리밋레인지를 생성한다.

kubectl apply -f https://k8s.io/examples/admin/resource/memory-constraints.yaml --namespace=constraints-mem-example

리밋레인지에 대한 자세한 정보를 본다.

kubectl get limitrange mem-min-max-demo-lr --namespace=constraints-mem-example --output=yaml

출력 결과는 예상대로 메모리의 최소 및 최대 제약 조건을 보여준다. 그러나 참고로 리밋레인지의 구성 파일에 기본값(default)을 지정하지 않아도 자동으로 생성된다.

  limits:
  - default:
      memory: 1Gi
    defaultRequest:
      memory: 1Gi
    max:
      memory: 1Gi
    min:
      memory: 500Mi
    type: Container

이제 constraints-mem-example 네임스페이스에 파드를 생성할 때마다, 쿠버네티스는 다음 단계를 수행한다.

  • 해당 파드의 어떤 컨테이너도 자체 메모리 요청량(request)과 상한(limit)을 명시하지 않으면, 컨트롤 플레인이 해당 컨테이너에 메모리 요청량과 상한의 기본값(default)을 지정한다.

  • 해당 파드의 모든 컨테이너의 메모리 요청량이 최소 500 MiB 이상인지 확인한다.

  • 해당 파드의 모든 컨테이너의 메모리 요청량이 1024 MiB(1 GiB)를 넘지 않는지 확인한다.

다음은 컨테이너가 하나인 파드의 매니페스트이다. 파드 명세 내에, 파드의 유일한 컨테이너는 600 MiB의 메모리 요청량 및 800 MiB의 메모리 상한을 지정하고 있다. 이는 리밋레인지에 의해 부과된 최소 및 최대 메모리 제약 조건을 충족시킨다.

apiVersion: v1
kind: Pod
metadata:
  name: constraints-mem-demo
spec:
  containers:
  - name: constraints-mem-demo-ctr
    image: nginx
    resources:
      limits:
        memory: "800Mi"
      requests:
        memory: "600Mi"

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/admin/resource/memory-constraints-pod.yaml --namespace=constraints-mem-example

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

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

파드에 대한 자세한 정보를 본다.

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

출력을 보면 파드의 컨테이너의 메모리 요청량이 600 MiB이고 메모리 상한이 800 MiB임을 알 수 있다. 이는 리밋레인지에 의해 해당 네임스페이스에 부과된 제약 조건을 만족시킨다.

resources:
  limits:
     memory: 800Mi
  requests:
    memory: 600Mi

파드를 삭제한다.

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

최대 메모리 제약 조건을 초과하는 파드 생성 시도

다음은 컨테이너가 하나인 파드의 매니페스트이다. 컨테이너는 800MiB의 메모리 요청량과 1.5GiB의 메모리 상한을 지정하고 있다.

apiVersion: v1
kind: Pod
metadata:
  name: constraints-mem-demo-2
spec:
  containers:
  - name: constraints-mem-demo-2-ctr
    image: nginx
    resources:
      limits:
        memory: "1.5Gi"
      requests:
        memory: "800Mi"

파드 생성을 시도한다.

kubectl apply -f https://k8s.io/examples/admin/resource/memory-constraints-pod-2.yaml --namespace=constraints-mem-example

결과를 보면 파드가 생성되지 않은 것을 확인할 수 있으며, 이는 해당 파드가 정의하고 있는 컨테이너가 허용된 것보다 더 많은 메모리를 요청하고 있기 때문이다.

Error from server (Forbidden): error when creating "examples/admin/resource/memory-constraints-pod-2.yaml":
pods "constraints-mem-demo-2" is forbidden: maximum memory usage per Container is 1Gi, but limit is 1536Mi.

최소 메모리 요청량을 충족하지 않는 파드 생성 시도

다음은 컨테이너가 하나인 파드의 매니페스트이다. 컨테이너는 100MiB의 메모리 요청량과 800MiB의 메모리 상한을 지정하고 있다.

apiVersion: v1
kind: Pod
metadata:
  name: constraints-mem-demo-3
spec:
  containers:
  - name: constraints-mem-demo-3-ctr
    image: nginx
    resources:
      limits:
        memory: "800Mi"
      requests:
        memory: "100Mi"

파드 생성을 시도한다.

kubectl apply -f https://k8s.io/examples/admin/resource/memory-constraints-pod-3.yaml --namespace=constraints-mem-example

결과를 보면 파드가 생성되지 않은 것을 확인할 수 있으며, 이는 해당 파드가 정의하고 있는 컨테이너가 지정된 최저 메모리 요청량보다도 낮은 메모리 요청량을 지정하고 있기 때문이다.

Error from server (Forbidden): error when creating "examples/admin/resource/memory-constraints-pod-3.yaml":
pods "constraints-mem-demo-3" is forbidden: minimum memory usage per Container is 500Mi, but request is 100Mi.

메모리 요청량 또는 상한을 지정하지 않은 파드 생성

다음은 컨테이너가 하나인 파드의 매니페스트이다. 해당 컨테이너는 메모리 요청량과 상한을 지정하지 않는다.

apiVersion: v1
kind: Pod
metadata:
  name: constraints-mem-demo-4
spec:
  containers:
  - name: constraints-mem-demo-4-ctr
    image: nginx

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/admin/resource/memory-constraints-pod-4.yaml --namespace=constraints-mem-example

파드에 대한 자세한 정보를 본다.

kubectl get pod constraints-mem-demo-4 --namespace=constraints-mem-example --output=yaml

출력을 보면 파드의 유일한 컨테이너에 대한 메모리 요청량이 1 GiB이고 메모리 상한도 1 GiB이다. 이 컨테이너는 어떻게 이런 값을 얻었을까?

resources:
  limits:
    memory: 1Gi
  requests:
    memory: 1Gi

파드가 해당 컨테이너에 대해 메모리 요청량과 상한을 지정하지 않았으므로, 클러스터가 리밋레인지로부터 메모리의 요청량과 상한 기본값을 적용하였다.

이는 곧 파드 정의에서 이 값들을 볼 수 있음을 의미한다. kubectl describe 명령을 사용하여 확인할 수 있다.

# 출력에서 "Requests:" 섹션을 확인한다
kubectl describe pod constraints-mem-demo-4 --namespace=constraints-mem-example

이 시점에서, 파드는 실행 중일 수도 있고 아닐 수도 있다. 이 태스크의 전제 조건은 노드에 최소 1GiB의 메모리가 있어야 한다는 것이다. 각 노드에 1GiB의 메모리만 있는 경우, 노드에 할당할 수 있는 메모리가 1GiB의 메모리 요청량을 수용하기에 충분하지 않을 수 있다. 메모리가 2GiB인 노드를 사용하는 경우에는, 메모리가 1GiB 요청량을 수용하기에 충분할 것이다.

파드를 삭제한다.

kubectl delete pod constraints-mem-demo-4 --namespace=constraints-mem-example

메모리의 최소 및 최대 제약 조건 적용

리밋레인지에 의해 네임스페이스에 부과된 메모리의 최대 및 최소 제약 조건은 파드를 생성하거나 업데이트할 때만 적용된다. 리밋레인지를 변경해도, 이전에 생성된 파드에는 영향을 미치지 않는다.

메모리의 최소 및 최대 제약 조건에 대한 동기

클러스터 관리자는 파드가 사용할 수 있는 메모리 양에 제한을 둘 수 있다. 예를 들면 다음과 같다.

  • 클러스터의 각 노드에는 2GiB의 메모리가 있다. 클러스터의 어떤 노드도 2GiB 이상의 요청량을 지원할 수 없으므로, 2GiB 이상의 메모리를 요청하는 파드를 수락하지 않으려고 한다.

  • 클러스터는 운영 부서와 개발 부서에서 공유한다. 프로덕션 워크로드가 최대 8GiB의 메모리를 소비하도록 하려면, 개발 워크로드를 512MiB로 제한해야 한다. 프로덕션 및 개발을 위해 별도의 네임스페이스를 만들고, 각 네임스페이스에 메모리 제약 조건을 적용한다.

정리

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

kubectl delete namespace constraints-mem-example

다음 내용

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

앱 개발자를 위한 문서

3.3.4 - 네임스페이스에 대한 CPU의 최소 및 최대 제약 조건 구성

한 네임스페이스 내에서 CPU 리소스 제한의 유효한 범위를 정의하며, 이를 통해 해당 네임스페이스의 새로운 파드가 미리 설정한 범위 안에 들어오도록 한다.

이 페이지는 네임스페이스에서 컨테이너와 파드가 사용하는 CPU 리소스의 최솟값과 최댓값을 설정하는 방법을 보여준다. 리밋레인지(LimitRange) 오브젝트에 CPU의 최솟값과 최댓값을 지정한다. 리밋레인지에 의해 부과된 제약 조건을 파드가 충족하지 않으면, 해당 네임스페이스에 생성될 수 없다.

시작하기 전에

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

클러스터에 네임스페이스를 생성할 수 있는 권한이 있어야 한다.

클러스터의 각 노드는 파드 실행을 위해 적어도 1.0 CPU 이상이 사용 가능해야 한다. 쿠버네티스에서 “1 CPU”가 무엇을 의미하는지 알아보려면 CPU의 의미를 참조한다.

네임스페이스 생성

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

kubectl create namespace constraints-cpu-example

리밋레인지와 파드 생성

다음은 리밋레인지 예제 매니페스트이다.

apiVersion: v1
kind: LimitRange
metadata:
  name: cpu-min-max-demo-lr
spec:
  limits:
  - max:
      cpu: "800m"
    min:
      cpu: "200m"
    type: Container

리밋레인지를 생성한다.

kubectl apply -f https://k8s.io/examples/admin/resource/cpu-constraints.yaml --namespace=constraints-cpu-example

리밋레인지에 대한 자세한 정보를 본다.

kubectl get limitrange cpu-min-max-demo-lr --output=yaml --namespace=constraints-cpu-example

출력 결과는 예상대로 CPU의 최소와 최대 제약 조건을 보여준다. 그러나 참고로 리밋레인지에 대한 구성 파일에 기본값을 지정하지 않아도 자동으로 생성된다.

limits:
- default:
    cpu: 800m
  defaultRequest:
    cpu: 800m
  max:
    cpu: 800m
  min:
    cpu: 200m
  type: Container

이제 constraints-cpu-example 네임스페이스에 파드를 생성할 때마다(또는 다른 쿠버네티스 API 클라이언트가 동일한 파드를 생성할 때마다), 쿠버네티스는 다음 단계를 수행한다.

  • 해당 파드의 어떤 컨테이너도 자체 CPU 요청량(request)과 상한(limit)을 명시하지 않으면, 컨트롤 플레인이 해당 컨테이너에 CPU 요청량과 상한의 기본값(default)을 지정한다.

  • 해당 파드의 모든 컨테이너가 200 millicpu 이상의 CPU 요청량을 지정하는지 확인한다.

  • 해당 파드의 모든 컨테이너가 800 millicpu 이하의 CPU 상한을 지정하는지 확인한다.

다음은 컨테이너가 하나인 파드의 매니페스트이다. 컨테이너 매니페스트는 500 millicpu의 CPU 요청량 및 800 millicpu의 CPU 상한을 지정하고 있다. 이는 이 네임스페이스의 리밋레인지에 의해 부과된 CPU의 최소와 최대 제약 조건을 충족시킨다.

apiVersion: v1
kind: Pod
metadata:
  name: constraints-cpu-demo
spec:
  containers:
  - name: constraints-cpu-demo-ctr
    image: nginx
    resources:
      limits:
        cpu: "800m"
      requests:
        cpu: "500m"

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/admin/resource/cpu-constraints-pod.yaml --namespace=constraints-cpu-example

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

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

파드에 대한 자세한 정보를 본다.

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

출력 결과는 파드 내 유일한 컨테이너의 CPU 요청량이 500 millicpu이고, CPU 상한이 800 millicpu임을 나타낸다. 이는 리밋레인지에 의해 부과된 제약 조건을 만족시킨다.

resources:
  limits:
    cpu: 800m
  requests:
    cpu: 500m

파드 삭제

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

CPU 최대 제약 조건을 초과하는 파드 생성 시도

다음은 컨테이너가 하나인 파드의 매니페스트이다. 컨테이너는 500 millicpu의 CPU 요청량과 1.5 cpu의 CPU 상한을 지정하고 있다.

apiVersion: v1
kind: Pod
metadata:
  name: constraints-cpu-demo-2
spec:
  containers:
  - name: constraints-cpu-demo-2-ctr
    image: nginx
    resources:
      limits:
        cpu: "1.5"
      requests:
        cpu: "500m"

파드 생성을 시도한다.

kubectl apply -f https://k8s.io/examples/admin/resource/cpu-constraints-pod-2.yaml --namespace=constraints-cpu-example

결과를 보면 파드가 생성되지 않은 것을 확인할 수 있으며, 이는 해당 파드가 수용 불가능한 컨테이너를 정의하고 있기 때문이다. 해당 컨테이너가 수용 불가능한 이유는 너무 큰 CPU 상한을 지정하고 있기 때문이다.

Error from server (Forbidden): error when creating "examples/admin/resource/cpu-constraints-pod-2.yaml":
pods "constraints-cpu-demo-2" is forbidden: maximum cpu usage per Container is 800m, but limit is 1500m.

최소 CPU 요청량을 충족하지 않는 파드 생성 시도

다음은 컨테이너가 하나인 파드의 매니페스트이다. 컨테이너는 100 millicpu의 CPU 요청량과 800 millicpu의 CPU 상한을 지정하고 있다.

apiVersion: v1
kind: Pod
metadata:
  name: constraints-cpu-demo-3
spec:
  containers:
  - name: constraints-cpu-demo-3-ctr
    image: nginx
    resources:
      limits:
        cpu: "800m"
      requests:
        cpu: "100m"

파드 생성을 시도한다.

kubectl apply -f https://k8s.io/examples/admin/resource/cpu-constraints-pod-3.yaml --namespace=constraints-cpu-example

결과를 보면 파드가 생성되지 않은 것을 확인할 수 있으며, 이는 해당 파드가 수용 불가능한 컨테이너를 정의하고 있기 때문이다. 해당 컨테이너가 수용 불가능한 이유는 지정된 최저 CPU 요청량보다도 낮은 CPU 요청량을 지정하고 있기 때문이다.

Error from server (Forbidden): error when creating "examples/admin/resource/cpu-constraints-pod-3.yaml":
pods "constraints-cpu-demo-3" is forbidden: minimum cpu usage per Container is 200m, but request is 100m.

CPU 요청량 또는 상한을 지정하지 않은 파드 생성

다음은 컨테이너가 하나인 파드의 매니페스트이다. 컨테이너는 CPU 요청량을 지정하지 않았으며, CPU 상한도 지정하지 않았다.

apiVersion: v1
kind: Pod
metadata:
  name: constraints-cpu-demo-4
spec:
  containers:
  - name: constraints-cpu-demo-4-ctr
    image: vish/stress

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/admin/resource/cpu-constraints-pod-4.yaml --namespace=constraints-cpu-example

파드에 대한 자세한 정보를 본다.

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

출력을 보면 파드의 유일한 컨테이너에 대한 CPU 요청량이 800 millicpu이고, CPU 상한이 800 millicpu이다. 이 컨테이너는 어떻게 이런 값을 얻었을까?

resources:
  limits:
    cpu: 800m
  requests:
    cpu: 800m

컨테이너가 자체 CPU 요청량과 상한을 지정하지 않았으므로, 컨테이너가 이 네임스페이스에 대해 리밋레인지로부터 CPU 요청량과 상한의 기본값을 적용했다.

이 시점에서, 파드는 실행 중일 수도 있고 아닐 수도 있다. 이 태스크의 전제 조건은 노드에 1 CPU 이상 사용 가능해야 한다는 것이다. 각 노드에 1 CPU만 있는 경우, 노드에 할당할 수 있는 CPU가 800 millicpu의 요청량을 수용하기에 충분하지 않을 수 있다. 2 CPU인 노드를 사용하는 경우에는, CPU가 800 millicpu 요청량을 수용하기에 충분할 것이다.

파드를 삭제한다.

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

CPU의 최소 및 최대 제약 조건의 적용

리밋레인지에 의해 네임스페이스에 부과된 CPU의 최대 및 최소 제약 조건은 파드를 생성하거나 업데이트할 때만 적용된다. 리밋레인지를 변경해도, 이전에 생성된 파드에는 영향을 미치지 않는다.

CPU의 최소 및 최대 제약 조건에 대한 동기

클러스터 관리자는 파드가 사용할 수 있는 CPU 리소스에 제한을 둘 수 있다. 예를 들면 다음과 같다.

  • 클러스터의 각 노드에는 2 CPU가 있다. 클러스터의 어떤 노드도 요청량을 지원할 수 없기 때문에, 2 CPU 이상을 요청하는 파드를 수락하지 않으려고 한다.

  • 클러스터는 프로덕션과 개발 부서에서 공유한다. 프로덕션 워크로드가 최대 3 CPU를 소비하도록 하고 싶지만, 개발 워크로드는 1 CPU로 제한하려고 한다. 프로덕션과 개발을 위해 별도의 네임스페이스를 생성하고, 각 네임스페이스에 CPU 제약 조건을 적용한다.

정리

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

kubectl delete namespace constraints-cpu-example

다음 내용

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

앱 개발자를 위한 문서

3.3.5 - 네임스페이스에 대한 메모리 및 CPU 쿼터 구성

한 네임스페이스에 대한 총 메모리 및 CPU 자원 상한을 정의한다.

이 페이지는 네임스페이스에서 실행 중인 모든 파드가 사용할 수 있는 총 메모리 및 CPU 양에 대한 쿼터를 설정하는 방법을 보여준다. 리소스쿼터(ResourceQuota) 오브젝트에 쿼터를 지정할 수 있다.

시작하기 전에

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

클러스터에 네임스페이스를 생성할 수 있는 권한이 있어야 한다.

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

네임스페이스 생성

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

kubectl create namespace quota-mem-cpu-example

리소스쿼터 생성

다음은 예시 리소스쿼터 오브젝트에 대한 매니페스트이다.

apiVersion: v1
kind: ResourceQuota
metadata:
  name: mem-cpu-demo
spec:
  hard:
    requests.cpu: "1"
    requests.memory: 1Gi
    limits.cpu: "2"
    limits.memory: 2Gi

리소스쿼터를 생성한다.

kubectl apply -f https://k8s.io/examples/admin/resource/quota-mem-cpu.yaml --namespace=quota-mem-cpu-example

리소스쿼터에 대한 자세한 정보를 본다.

kubectl get resourcequota mem-cpu-demo --namespace=quota-mem-cpu-example --output=yaml

리소스쿼터는 이러한 요구 사항을 quota-mem-cpu-example 네임스페이스에 배치한다.

  • 네임스페이스의 모든 파드에 대해, 각 컨테이너에는 메모리 요청량(request), 메모리 상한(limit), CPU 요청량 및 CPU 상한이 있어야 한다.
  • 네임스페이스의 모든 파드에 대한 총 메모리 요청량은 1GiB를 초과하지 않아야 한다.
  • 네임스페이스의 모든 파드에 대한 총 메모리 상한은 2GiB를 초과하지 않아야 한다.
  • 네임스페이스의 모든 파드에 대한 총 CPU 요청량은 1 cpu를 초과해서는 안된다.
  • 네임스페이스의 모든 파드에 대한 총 CPU 상한은 2 cpu를 초과해서는 안된다.

쿠버네티스에서 “1 CPU”가 무엇을 의미하는지 알아보려면 CPU의 의미를 참조한다.

파드 생성

다음은 예시 파드에 대한 매니페스트이다.

apiVersion: v1
kind: Pod
metadata:
  name: quota-mem-cpu-demo
spec:
  containers:
  - name: quota-mem-cpu-demo-ctr
    image: nginx
    resources:
      limits:
        memory: "800Mi"
        cpu: "800m"
      requests:
        memory: "600Mi"
        cpu: "400m"

파드를 생성한다.

kubectl apply -f https://k8s.io/examples/admin/resource/quota-mem-cpu-pod.yaml --namespace=quota-mem-cpu-example

파드가 실행 중이고 파드의 (유일한) 컨테이너의 상태가 정상인지 확인한다.

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

다시 한 번, 리소스쿼터에 대한 자세한 정보를 본다.

kubectl get resourcequota mem-cpu-demo --namespace=quota-mem-cpu-example --output=yaml

출력 결과는 쿼터와 사용된 쿼터를 함께 보여준다. 파드의 메모리와 CPU 요청량 및 상한이 쿼터를 초과하지 않은 것을 볼 수 있다.

status:
  hard:
    limits.cpu: "2"
    limits.memory: 2Gi
    requests.cpu: "1"
    requests.memory: 1Gi
  used:
    limits.cpu: 800m
    limits.memory: 800Mi
    requests.cpu: 400m
    requests.memory: 600Mi

jq 도구가 설치되어 있으면, (JSONPath를 사용하여) used 값만을 질의 하고, 정돈된 상태로 출력할 수 있다. 예시는 다음과 같다.

kubectl get resourcequota mem-cpu-demo --namespace=quota-mem-cpu-example -o jsonpath='{ .status.used }' | jq .

두 번째 파드 생성 시도

다음은 두 번째 파드에 대한 매니페스트이다.

apiVersion: v1
kind: Pod
metadata:
  name: quota-mem-cpu-demo-2
spec:
  containers:
  - name: quota-mem-cpu-demo-2-ctr
    image: redis
    resources:
      limits:
        memory: "1Gi"
        cpu: "800m"
      requests:
        memory: "700Mi"
        cpu: "400m"

매니페스트에서, 파드의 메모리 요청량이 700MiB임을 알 수 있다. 사용된 메모리 요청량과 이 새 메모리 요청량의 합계가 메모리 요청량 쿼터를 초과함에 유의한다(600 MiB + 700 MiB > 1 GiB).

파드 생성을 시도한다.

kubectl apply -f https://k8s.io/examples/admin/resource/quota-mem-cpu-pod-2.yaml --namespace=quota-mem-cpu-example

두 번째 파드는 생성되지 않는다. 출력 결과는 두 번째 파드를 생성하면 메모리 요청량의 총 합계가 메모리 요청량 쿼터를 초과함을 보여준다.

Error from server (Forbidden): error when creating "examples/admin/resource/quota-mem-cpu-pod-2.yaml":
pods "quota-mem-cpu-demo-2" is forbidden: exceeded quota: mem-cpu-demo,
requested: requests.memory=700Mi,used: requests.memory=600Mi, limited: requests.memory=1Gi

토론

이 연습에서 보았듯이, 리소스쿼터를 사용하여 네임스페이스에서 실행 중인 모든 파드에 대한 메모리 요청량의 총 합계를 제한할 수 있다. 메모리 상한, CPU 요청량 및 CPU 상한의 총 합계를 제한할 수도 있다.

네임스페이스 내의 총 자원을 관리하는 것 대신, 개별 파드 또는 파드 내의 컨테이너별로 제한하고 싶을 수도 있다. 이러한 종류의 제한을 걸려면, 리밋레인지(LimitRange)를 사용한다.

정리

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

kubectl delete namespace quota-mem-cpu-example

다음 내용

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

앱 개발자를 위한 문서

3.3.6 - 네임스페이스에 대한 파드 쿼터 구성

한 네임스페이스 내에 만들 수 있는 파드의 수를 제한한다.

이 페이지는 네임스페이스에서 실행할 수 있는 총 파드 수에 대한 쿼터를 설정하는 방법을 보여준다. 리소스쿼터(ResourceQuota) 오브젝트에 쿼터를 지정할 수 있다.

시작하기 전에

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

클러스터에 네임스페이스를 생성할 수 있는 권한이 있어야 한다.

네임스페이스 생성

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

kubectl create namespace quota-pod-example

리소스쿼터 생성

다음은 예시 리소스쿼터 오브젝트에 대한 매니페스트이다.

apiVersion: v1
kind: ResourceQuota
metadata:
  name: pod-demo
spec:
  hard:
    pods: "2"

리소스쿼터를 생성한다.

kubectl apply -f https://k8s.io/examples/admin/resource/quota-pod.yaml --namespace=quota-pod-example

리소스쿼터에 대한 자세한 정보를 본다.

kubectl get resourcequota pod-demo --namespace=quota-pod-example --output=yaml

출력 결과는 네임스페이스에 두 개의 파드 쿼터가 있고, 현재 파드가 없음을 보여준다. 즉, 쿼터 중 어느 것도 사용되지 않았다.

spec:
  hard:
    pods: "2"
status:
  hard:
    pods: "2"
  used:
    pods: "0"

다음은 디플로이먼트(Deployment)에 대한 예시 매니페스트이다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pod-quota-demo
spec:
  selector:
    matchLabels:
      purpose: quota-demo
  replicas: 3
  template:
    metadata:
      labels:
        purpose: quota-demo
    spec:
      containers:
      - name: pod-quota-demo
        image: nginx

매니페스트에서, replicas: 3 은 쿠버네티스가 모두 동일한 애플리케이션을 실행하는 세 개의 새로운 파드를 만들도록 지시한다.

디플로이먼트를 생성한다.

kubectl apply -f https://k8s.io/examples/admin/resource/quota-pod-deployment.yaml --namespace=quota-pod-example

디플로이먼트에 대한 자세한 정보를 본다.

kubectl get deployment pod-quota-demo --namespace=quota-pod-example --output=yaml

출력을 보면 디플로이먼트가 3개의 레플리카를 정의하고 있음에도, 앞서 설정한 쿼터로 인해 2개의 파드만 생성되었음을 보여준다.

spec:
  ...
  replicas: 3
...
status:
  availableReplicas: 2
...
lastUpdateTime: 2021-04-02T20:57:05Z
    message: 'unable to create pods: pods "pod-quota-demo-1650323038-" is forbidden:
      exceeded quota: pod-demo, requested: pods=1, used: pods=2, limited: pods=2'

리소스 선택

이 예제에서는 총 파드 수를 제한하는 리소스쿼터를 정의하였다. 하지만, 다른 종류의 오브젝트의 총 수를 제한할 수도 있다. 예를 들어, 한 네임스페이스에 존재할 수 있는 크론잡의 총 수를 제한할 수 있다.

정리

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

kubectl delete namespace quota-pod-example

다음 내용

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

앱 개발자를 위한 문서

3.4 - 네트워크 폴리시 제공자(Network Policy Provider) 설치

3.4.1 - 네트워크 폴리시로 캘리코(Calico) 사용하기

이 페이지는 쿠버네티스에서 캘리코(Calico) 클러스터를 생성하는 몇 가지 빠른 방법을 살펴본다.

시작하기 전에

클라우드지역 클러스터 중에 어디에 배포할지 결정한다.

구글 쿠버네티스 엔진(GKE)에 캘리코 클러스터 생성하기

사전요구사항: gcloud.

  1. 캘리코로 GKE 클러스터를 시작하려면, --enable-network-policy 플래그를 추가한다.

    문법

    gcloud container clusters create [클러스터_이름] --enable-network-policy
    

    예시

    gcloud container clusters create my-calico-cluster --enable-network-policy
    
  2. 배포를 확인하기 위해, 다음 커맨드를 이용하자.

    kubectl get pods --namespace=kube-system
    

    캘리코 파드는 calico로 시작한다. 각각의 상태가 Running임을 확인하자.

kubeadm으로 지역 캘리코 클러스터 생성하기

Kubeadm을 이용해서 15분 이내에 지역 단일 호스트 캘리코 클러스터를 생성하려면, 캘리코 빠른 시작을 참고한다.

다음 내용

클러스터가 동작하면, 쿠버네티스 네트워크 폴리시(NetworkPolicy)를 시도하기 위해 네트워크 폴리시 선언하기를 따라 할 수 있다.

3.4.2 - 네트워크 폴리시로 실리움(Cilium) 사용하기

이 페이지는 어떻게 네트워크 폴리시(NetworkPolicy)로 실리움(Cilium)를 사용하는지 살펴본다.

실리움의 배경에 대해서는 실리움 소개를 읽어보자.

시작하기 전에

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

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

기본 시험을 위해 실리움을 Minikube에 배포하기

실리움에 쉽게 친숙해지기 위해 Minikube에 실리움을 기본적인 데몬셋으로 설치를 수행하는 실리움 쿠버네티스 시작하기 안내를 따라 해볼 수 있다.

Minikube를 시작하려면 최소 버전으로 >= v1.5.2 이 필요하고, 다음의 실행 파라미터로 실행한다.

minikube version
minikube version: v1.5.2
minikube start --network-plugin=cni

minikube의 경우 CLI 도구를 사용하여 실리움을 설치할 수 있다. 그러기 위해서, 먼저 다음과 같은 명령을 사용하여 최신 버전의 CLI를 다운로드 한다.

curl -LO https://github.com/cilium/cilium-cli/releases/latest/download/cilium-linux-amd64.tar.gz

이후 다음과 같은 명령을 사용하여 다운받은 파일을 /usr/local/bin 디렉토리에 압축을 푼다.

sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin
rm cilium-linux-amd64.tar.gz

위의 명령을 실행한 후, 이제 다음 명령으로 실리움(Cilium)을 설치할 수 있다.

cilium install

그런 다음 실리움은 자동으로 클러스터를 구성을 감지하고 성공적인 설치를 위해 적절한 구성요소를 만들고 설치한다. 구성요소는 다음과 같다.

  • 시크릿 cilium-ca의 인증 기관(CA) 및 Hubble (실리움의 관측 가능성 계층)에 대한 인증서.
  • 서비스 어카운트.
  • 클러스터 롤.
  • 컨피그맵.
  • 에이전트 데몬셋 및 오퍼레이터 디플로이먼트.

설치 후, cilium status 명령으로 실리움 디플로이먼트의 전체 상태를 볼 수 있다. status 명령으로 예상 출력을 참조한다. 여기.

시작하기 안내서의 나머지 부분은 예제 애플리케이션을 이용하여 L3/L4(예, IP 주소 + 포트) 모두의 보안 정책뿐만 아니라 L7(예, HTTP)의 보안 정책을 적용하는 방법을 설명한다.

실리움을 실 서비스 용도로 배포하기

실리움을 실 서비스 용도의 배포에 관련한 자세한 방법은 실리움 쿠버네티스 설치 안내를 살펴본다. 이 문서는 자세한 요구사항, 방법과 실제 데몬셋 예시를 포함한다.

실리움 구성요소 이해하기

실리움으로 클러스터를 배포하면 파드가 kube-system 네임스페이스에 추가된다. 파드의 목록을 보려면 다음을 실행한다.

kubectl get pods --namespace=kube-system -l k8s-app=cilium

다음과 유사한 파드의 목록을 볼 것이다.

NAME           READY   STATUS    RESTARTS   AGE
cilium-kkdhz   1/1     Running   0          3m23s
...

cilium 파드는 클러스터 각 노드에서 실행되며, 리눅스 BPF를 사용해서 해당 노드의 파드에 대한 트래픽 네트워크 폴리시를 적용한다.

다음 내용

클러스터가 동작하면, 실리움으로 쿠버네티스 네트워크 폴리시를 시도하기 위해 네트워크 폴리시 선언하기를 따라 할 수 있다. 재미있게 즐기고, 질문이 있다면 실리움 슬랙 채널을 이용하여 연락한다.

3.4.3 - 네트워크 폴리시로 큐브 라우터(Kube-router) 사용하기

이 페이지는 네트워크 폴리시(NetworkPolicy)로 큐브 라우터(Kube-router)를 사용하는 방법을 살펴본다.

시작하기 전에

운영 중인 쿠버네티스 클러스터가 필요하다. 클러스터가 없다면, Kops, Bootkube, Kubeadm 등을 이용해서 클러스터를 생성할 수 있다.

큐브 라우터 애드온 설치하기

큐브 라우터 애드온은 갱신된 모든 네트워크 폴리시 및 파드에 대해 쿠버네티스 API 서버를 감시하고, 정책에 따라 트래픽을 허용하거나 차단하도록 iptables 규칙와 ipset을 구성하는 네트워크 폴리시 컨트롤러와 함께 제공된다. 큐브 라우터 애드온을 설치하는 큐브 라우터를 클러스터 인스톨러와 함께 사용하기 안내서를 따라해 봅니다.

다음 내용

큐브 라우터 애드온을 설치한 후에는, 쿠버네티스 네트워크 폴리시를 시도하기 위해 네트워크 폴리시 선언하기를 따라 할 수 있다.

3.4.4 - 네트워크 폴리시로 로마나(Romana)

이 페이지는 네트워크 폴리시(NetworkPolicy)로 로마나(Romana)를 사용하는 방법을 살펴본다.

시작하기 전에

kubeadm 시작하기의 1, 2, 3 단계를 완료하자.

kubeadm으로 로마나 설치하기

Kubeadm을 위한 컨테이너화된 설치 안내서를 따른다.

네트워크 폴리시 적용하기

네트워크 폴리시를 적용하기 위해 다음 중에 하나를 사용하자.

다음 내용

로마나를 설치한 후에는, 쿠버네티스 네트워크 폴리시를 시도하기 위해 네트워크 폴리시 선언하기를 따라 할 수 있다.

3.4.5 - 네트워크 폴리시로 위브넷(Weave Net) 사용하기

이 페이지는 네트워크 폴리시(NetworkPolicy)로 위브넷(Weave Net)를 사용하는 방법을 살펴본다.

시작하기 전에

쿠버네티스 클러스터가 필요하다. 맨 땅에서부터 시작하기를 위해서 kubeadm 시작하기 안내서를 따른다.

Weave Net 애드온을 설치한다

애드온을 통한 쿠버네티스 통합하기 가이드를 따른다.

쿠버네티스의 위브넷 애드온은 쿠버네티스의 모든 네임스페이스의 네크워크 정책 어노테이션을 자동으로 모니터링하며, 정책에 따라 트래픽을 허용하고 차단하는 iptables 규칙을 구성하는 네트워크 폴리시 컨트롤러와 함께 제공된다.

설치 시험

위브넷이 동작하는지 확인한다.

다음 커맨드를 입력한다.

kubectl get pods -n kube-system -o wide

출력은 다음과 유사하다.

NAME                                    READY     STATUS    RESTARTS   AGE       IP              NODE
weave-net-1t1qg                         2/2       Running   0          9d        192.168.2.10    worknode3
weave-net-231d7                         2/2       Running   1          7d        10.2.0.17       worknodegpu
weave-net-7nmwt                         2/2       Running   3          9d        192.168.2.131   masternode
weave-net-pmw8w                         2/2       Running   0          9d        192.168.2.216   worknode2

위브넷 파드를 가진 각 노드와 모든 파드는 Running이고 2/2 READY이다(2/2는 각 파드가 weaveweave-npc를 가지고 있음을 뜻한다).

다음 내용

위브넷 애드온을 설치하고 나서, 쿠버네티스 네트워크 폴리시를 시도하기 위해 네트워크 폴리시 선언하기를 따라 할 수 있다. 질문이 있으면 슬랙 #weave-community 이나 Weave 유저그룹에 연락한다.

3.5 - 인증서

클라이언트 인증서로 인증을 사용하는 경우 easyrsa, openssl 또는 cfssl 을 통해 인증서를 수동으로 생성할 수 있다.

easyrsa

easyrsa 는 클러스터 인증서를 수동으로 생성할 수 있다.

  1. easyrsa3의 패치 버전을 다운로드하여 압축을 풀고, 초기화한다.

    curl -LO https://dl.k8s.io/easy-rsa/easy-rsa.tar.gz
    tar xzf easy-rsa.tar.gz
    cd easy-rsa-master/easyrsa3
    ./easyrsa init-pki
    
  2. 새로운 인증 기관(CA)을 생성한다. --batch 는 자동 모드를 설정한다. --req-cn 는 CA의 새 루트 인증서에 대한 일반 이름(Common Name (CN))을 지정한다.

    ./easyrsa --batch "--req-cn=${MASTER_IP}@`date +%s`" build-ca nopass
    
  3. 서버 인증서와 키를 생성한다.

    --subject-alt-name 인자로 API 서버에 접근이 가능한 IP와 DNS 이름을 설정한다. MASTER_CLUSTER_IP 는 일반적으로 API 서버와 컨트롤러 관리자 컴포넌트에 대해 --service-cluster-ip-range 인자로 지정된 서비스 CIDR의 첫 번째 IP이다. --days 인자는 인증서가 만료되는 일 수를 설정하는 데 사용된다. 또한, 아래 샘플에서는 cluster.local 을 기본 DNS 도메인 이름으로 사용하고 있다고 가정한다.

    ./easyrsa --subject-alt-name="IP:${MASTER_IP},"\
    "IP:${MASTER_CLUSTER_IP},"\
    "DNS:kubernetes,"\
    "DNS:kubernetes.default,"\
    "DNS:kubernetes.default.svc,"\
    "DNS:kubernetes.default.svc.cluster,"\
    "DNS:kubernetes.default.svc.cluster.local" \
    --days=10000 \
    build-server-full server nopass
    
  4. pki/ca.crt, pki/issued/server.crt 그리고 pki/private/server.key 를 디렉터리에 복사한다.

  5. API 서버를 시작하는 파라미터에 다음과 같이 추가한다.

    --client-ca-file=/yourdirectory/ca.crt
    --tls-cert-file=/yourdirectory/server.crt
    --tls-private-key-file=/yourdirectory/server.key
    

openssl

openssl 은 클러스터 인증서를 수동으로 생성할 수 있다.

  1. ca.key를 2048bit로 생성한다.

    openssl genrsa -out ca.key 2048
    
  2. ca.key에 따라 ca.crt를 생성한다(인증서 유효 기간을 사용하려면 -days를 사용한다).

    openssl req -x509 -new -nodes -key ca.key -subj "/CN=${MASTER_IP}" -days 10000 -out ca.crt
    
  3. server.key를 2048bit로 생성한다.

    openssl genrsa -out server.key 2048
    
  4. 인증서 서명 요청(Certificate Signing Request (CSR))을 생성하기 위한 설정 파일을 생성한다.

    파일에 저장하기 전에 꺾쇠 괄호(예: <MASTER_IP>)로 표시된 값을 실제 값으로 대체한다(예: csr.conf). MASTER_CLUSTER_IP 의 값은 이전 하위 섹션에서 설명한 대로 API 서버의 서비스 클러스터 IP이다. 또한, 아래 샘플에서는 cluster.local 을 기본 DNS 도메인 이름으로 사용하고 있다고 가정한다.

    [ req ]
    default_bits = 2048
    prompt = no
    default_md = sha256
    req_extensions = req_ext
    distinguished_name = dn
    
    [ dn ]
    C = <국가(country)>
    ST = <도(state)>
    L = <시(city)>
    O = <조직(organization)>
    OU = <조직 단위(organization unit)>
    CN = <MASTER_IP>
    
    [ req_ext ]
    subjectAltName = @alt_names
    
    [ alt_names ]
    DNS.1 = kubernetes
    DNS.2 = kubernetes.default
    DNS.3 = kubernetes.default.svc
    DNS.4 = kubernetes.default.svc.cluster
    DNS.5 = kubernetes.default.svc.cluster.local
    IP.1 = <MASTER_IP>
    IP.2 = <MASTER_CLUSTER_IP>
    
    [ v3_ext ]
    authorityKeyIdentifier=keyid,issuer:always
    basicConstraints=CA:FALSE
    keyUsage=keyEncipherment,dataEncipherment
    extendedKeyUsage=serverAuth,clientAuth
    subjectAltName=@alt_names
    
  5. 설정 파일을 기반으로 인증서 서명 요청을 생성한다.

    openssl req -new -key server.key -out server.csr -config csr.conf
    
  6. ca.key, ca.crt 그리고 server.csr을 사용해서 서버 인증서를 생성한다.

    openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
        -CAcreateserial -out server.crt -days 10000 \
        -extensions v3_ext -extfile csr.conf -sha256
    
  7. 인증서 서명 요청을 확인한다.

    openssl req  -noout -text -in ./server.csr
    
  8. 인증서를 확인한다.

    openssl x509  -noout -text -in ./server.crt
    

마지막으로, API 서버 시작 파라미터에 동일한 파라미터를 추가한다.

cfssl

cfssl 은 인증서 생성을 위한 또 다른 도구이다.

  1. 아래에 표시된 대로 커맨드 라인 도구를 다운로드하여 압축을 풀고 준비한다.

    사용 중인 하드웨어 아키텍처 및 cfssl 버전에 따라 샘플 명령을 조정해야 할 수도 있다.

    curl -L https://github.com/cloudflare/cfssl/releases/download/v1.5.0/cfssl_1.5.0_linux_amd64 -o cfssl
    chmod +x cfssl
    curl -L https://github.com/cloudflare/cfssl/releases/download/v1.5.0/cfssljson_1.5.0_linux_amd64 -o cfssljson
    chmod +x cfssljson
    curl -L https://github.com/cloudflare/cfssl/releases/download/v1.5.0/cfssl-certinfo_1.5.0_linux_amd64 -o cfssl-certinfo
    chmod +x cfssl-certinfo
    
  2. 아티팩트(artifact)를 보유할 디렉터리를 생성하고 cfssl을 초기화한다.

mkdir cert
cd cert
../cfssl print-defaults config > config.json
../cfssl print-defaults csr > csr.json
  1. CA 파일을 생성하기 위한 JSON 설정 파일을 ca-config.json 예시와 같이 생성한다.

    {
      "signing": {
        "default": {
          "expiry": "8760h"
        },
        "profiles": {
          "kubernetes": {
            "usages": [
              "signing",
              "key encipherment",
              "server auth",
              "client auth"
            ],
            "expiry": "8760h"
          }
        }
      }
    }
    
  2. CA 인증서 서명 요청(CSR)을 위한 JSON 설정 파일을 ca-csr.json 예시와 같이 생성한다. 꺾쇠 괄호로 표시된 값을 사용하려는 실제 값으로 변경한다.

    {
      "CN": "kubernetes",
      "key": {
        "algo": "rsa",
        "size": 2048
      },
      "names":[{
        "C": "국가<country>",
        "ST": "도<state>",
        "L": "시<city>",
        "O": "조직<organization>",
        "OU": "조직 단위<organization unit>"
      }]
    }
    
  3. CA 키(ca-key.pem)와 인증서(ca.pem)을 생성한다.

    ../cfssl gencert -initca ca-csr.json | ../cfssljson -bare ca
    
  4. API 서버의 키와 인증서를 생성하기 위한 JSON 구성파일을 server-csr.json 예시와 같이 생성한다. 꺾쇠 괄호 안의 값을 사용하려는 실제 값으로 변경한다. MASTER_CLUSTER_IP 는 이전 하위 섹션에서 설명한 API 서버의 클러스터 IP이다. 아래 샘플은 기본 DNS 도메인 이름으로 cluster.local 을 사용한다고 가정한다.

    {
      "CN": "kubernetes",
      "hosts": [
        "127.0.0.1",
        "<MASTER_IP>",
        "<MASTER_CLUSTER_IP>",
        "kubernetes",
        "kubernetes.default",
        "kubernetes.default.svc",
        "kubernetes.default.svc.cluster",
        "kubernetes.default.svc.cluster.local"
      ],
      "key": {
        "algo": "rsa",
        "size": 2048
      },
      "names": [{
         "C": "<국가(country)>",
         "ST": "<도(state)>",
         "L": "<시(city)>",
         "O": "<조직(organization)>",
         "OU": "<조직 단위(organization unit)>"
      }]
    }
    
  5. API 서버 키와 인증서를 생성하면, 기본적으로 server-key.pemserver.pem 파일에 각각 저장된다.

    ../cfssl gencert -ca=ca.pem -ca-key=ca-key.pem \
         --config=ca-config.json -profile=kubernetes \
         server-csr.json | ../cfssljson -bare server
    

자체 서명된 CA 인증서의 배포

클라이언트 노드는 자체 서명된 CA 인증서를 유효한 것으로 인식하지 않을 수 있다. 비-프로덕션 디플로이먼트 또는 회사 방화벽 뒤에서 실행되는 디플로이먼트의 경우, 자체 서명된 CA 인증서를 모든 클라이언트에 배포하고 유효한 인증서의 로컬 목록을 새로 고칠 수 있다.

각 클라이언트에서, 다음 작업을 수행한다.

sudo cp ca.crt /usr/local/share/ca-certificates/kubernetes.crt
sudo update-ca-certificates
Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d....
done.

인증서 API

certificates.k8s.io API를 사용하여 클러스터에서 TLS 인증서 관리에 설명된 대로 인증에 사용할 x509 인증서를 프로비전 할 수 있다.

3.6 - 쿠버네티스 API를 사용하여 클러스터에 접근하기

이 페이지는 쿠버네티스 API를 사용하여 클러스터에 접근하는 방법을 보여준다.

시작하기 전에

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

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

쿠버네티스 API에 접근

kubectl을 사용하여 처음으로 접근

쿠버네티스 API에 처음 접근하는 경우, 쿠버네티스 커맨드 라인 도구인 kubectl 을 사용한다.

클러스터에 접근하려면, 클러스터 위치를 알고 접근할 수 있는 자격 증명이 있어야 한다. 일반적으로, 시작하기 가이드를 통해 작업하거나, 다른 사람이 클러스터를 설정하고 자격 증명과 위치를 제공할 때 자동으로 설정된다.

다음의 명령으로 kubectl이 알고 있는 위치와 자격 증명을 확인한다.

kubectl config view

많은 예제는 kubectl 사용에 대한 소개를 제공한다. 전체 문서는 kubectl 매뉴얼에 있다.

REST API에 직접 접근

kubectl은 API 서버 찾기와 인증을 처리한다. curl 이나 wget 과 같은 http 클라이언트 또는 브라우저를 사용하여 REST API에 직접 접근하려는 경우, API 서버를 찾고 인증할 수 있는 여러 가지 방법이 있다.

  1. 프록시 모드에서 kubectl을 실행한다(권장). 이 방법은 저장된 API 서버 위치를 사용하고 자체 서명된 인증서를 사용하여 API 서버의 ID를 확인하므로 권장한다. 이 방법을 사용하면 중간자(man-in-the-middle, MITM) 공격이 불가능하다.
  2. 또는, 위치와 자격 증명을 http 클라이언트에 직접 제공할 수 있다. 이 방법은 프록시를 혼란스럽게 하는 클라이언트 코드와 동작한다. 중간자 공격으로부터 보호하려면, 브라우저로 루트 인증서를 가져와야 한다.

Go 또는 Python 클라이언트 라이브러리를 사용하면 프록시 모드에서 kubectl에 접근할 수 있다.

kubectl 프록시 사용

다음 명령은 kubectl을 리버스 프록시로 작동하는 모드에서 실행한다. API 서버 찾기와 인증을 처리한다.

다음과 같이 실행한다.

kubectl proxy --port=8080 &

자세한 내용은 kubectl 프록시를 참고한다.

그런 다음 curl, wget 또는 브라우저를 사용하여 API를 탐색할 수 있다.

curl http://localhost:8080/api/

출력은 다음과 비슷하다.

{
  "versions": [
    "v1"
  ],
  "serverAddressByClientCIDRs": [
    {
      "clientCIDR": "0.0.0.0/0",
      "serverAddress": "10.0.1.149:443"
    }
  ]
}

kubectl 프록시 없이 접근

다음과 같이 인증 토큰을 API 서버에 직접 전달하여 kubectl 프록시 사용을 피할 수 있다.

grep/cut 방식을 사용한다.

# .KUBECONFIG에 여러 콘텍스트가 있을 수 있으므로, 가능한 모든 클러스터를 확인한다.
kubectl config view -o jsonpath='{"Cluster name\tServer\n"}{range .clusters[*]}{.name}{"\t"}{.cluster.server}{"\n"}{end}'

# 위의 출력에서 상호 작용하려는 클러스터의 이름을 선택한다.
export CLUSTER_NAME="some_server_name"

# 클러스터 이름을 참조하는 API 서버를 가리킨다.
APISERVER=$(kubectl config view -o jsonpath="{.clusters[?(@.name==\"$CLUSTER_NAME\")].cluster.server}")

# 기본 서비스 어카운트용 토큰을 보관할 시크릿을 생성한다.
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: default-token
  annotations:
    kubernetes.io/service-account.name: default
type: kubernetes.io/service-account-token
EOF

# 토큰 컨트롤러가 해당 시크릿에 토큰을 기록할 때까지 기다린다.
while ! kubectl describe secret default-token | grep -E '^token' >/dev/null; do
  echo "waiting for token..." >&2
  sleep 1
done

# 토큰 값을 얻는다
TOKEN=$(kubectl get secret default-token -o jsonpath='{.data.token}' | base64 --decode)

# TOKEN으로 API 탐색
curl -X GET $APISERVER/api --header "Authorization: Bearer $TOKEN" --insecure

출력은 다음과 비슷하다.

{
  "kind": "APIVersions",
  "versions": [
    "v1"
  ],
  "serverAddressByClientCIDRs": [
    {
      "clientCIDR": "0.0.0.0/0",
      "serverAddress": "10.0.1.149:443"
    }
  ]
}

위의 예는 --insecure 플래그를 사용한다. 이로 인해 MITM 공격이 발생할 수 있다. kubectl이 클러스터에 접근하면 저장된 루트 인증서와 클라이언트 인증서를 사용하여 서버에 접근한다. (~/.kube 디렉터리에 설치된다.) 클러스터 인증서는 일반적으로 자체 서명되므로, http 클라이언트가 루트 인증서를 사용하도록 하려면 특별한 구성이 필요할 수 있다.

일부 클러스터에서, API 서버는 인증이 필요하지 않다. 로컬 호스트에서 제공되거나, 방화벽으로 보호될 수 있다. 이에 대한 표준은 없다. 쿠버네티스 API에 대한 접근 제어는 클러스터 관리자로서 이를 구성하는 방법에 대해 설명한다. 이러한 접근 방식은 향후 고 가용성 지원과 충돌할 수 있다.

API에 프로그래밍 방식으로 접근

쿠버네티스는 공식적으로 Go, Python, Java, dotnet, JavaScriptHaskell 용 클라이언트 라이브러리를 지원한다. 쿠버네티스 팀이 아닌 작성자가 제공하고 유지 관리하는 다른 클라이언트 라이브러리가 있다. 다른 언어에서 API에 접근하고 인증하는 방법에 대해서는 클라이언트 라이브러리를 참고한다.

Go 클라이언트

  • 라이브러리를 얻으려면, 다음 명령을 실행한다. go get k8s.io/client-go@kubernetes-<kubernetes-version-number> 어떤 버전이 지원되는지를 확인하려면 https://github.com/kubernetes/client-go/releases를 참고한다.
  • client-go 클라이언트 위에 애플리케이션을 작성한다.

Go 클라이언트는 kubectl CLI가 API 서버를 찾아 인증하기 위해 사용하는 것과 동일한 kubeconfig 파일을 사용할 수 있다. 이 예제를 참고한다.

package main

import (
  "context"
  "fmt"
  "k8s.io/apimachinery/pkg/apis/meta/v1"
  "k8s.io/client-go/kubernetes"
  "k8s.io/client-go/tools/clientcmd"
)

func main() {
  // kubeconfig에서 현재 콘텍스트를 사용한다
  // path-to-kubeconfig -- 예를 들어, /root/.kube/config
  config, _ := clientcmd.BuildConfigFromFlags("", "<path-to-kubeconfig>")
  // clientset을 생성한다
  clientset, _ := kubernetes.NewForConfig(config)
  // 파드를 나열하기 위해 API에 접근한다
  pods, _ := clientset.CoreV1().Pods("").List(context.TODO(), v1.ListOptions{})
  fmt.Printf("There are %d pods in the cluster\n", len(pods.Items))
}

애플리케이션이 클러스터 내의 파드로 배치된 경우, 파드 내에서 API 접근을 참고한다.

Python 클라이언트

Python 클라이언트를 사용하려면, 다음 명령을 실행한다. pip install kubernetes 추가 설치 옵션은 Python Client Library 페이지를 참고한다.

Python 클라이언트는 kubectl CLI가 API 서버를 찾아 인증하기 위해 사용하는 것과 동일한 kubeconfig 파일을 사용할 수 있다. 이 예제를 참고한다.

from kubernetes import client, config

config.load_kube_config()

v1=client.CoreV1Api()
print("Listing pods with their IPs:")
ret = v1.list_pod_for_all_namespaces(watch=False)
for i in ret.items:
    print("%s\t%s\t%s" % (i.status.pod_ip, i.metadata.namespace, i.metadata.name))

Java 클라이언트

Java 클라이언트를 설치하려면, 다음을 실행한다.

# java 라이브러리를 클론한다
git clone --recursive https://github.com/kubernetes-client/java

# 프로젝트 아티팩트, POM 등을 설치한다
cd java
mvn install

어떤 버전이 지원되는지를 확인하려면 https://github.com/kubernetes-client/java/releases를 참고한다.

Java 클라이언트는 kubectl CLI가 API 서버를 찾아 인증하기 위해 사용하는 것과 동일한 kubeconfig 파일을 사용할 수 있다. 이 예제를 참고한다.

package io.kubernetes.client.examples;

import io.kubernetes.client.ApiClient;
import io.kubernetes.client.ApiException;
import io.kubernetes.client.Configuration;
import io.kubernetes.client.apis.CoreV1Api;
import io.kubernetes.client.models.V1Pod;
import io.kubernetes.client.models.V1PodList;
import io.kubernetes.client.util.ClientBuilder;
import io.kubernetes.client.util.KubeConfig;
import java.io.FileReader;
import java.io.IOException;

/**
 * 쿠버네티스 클러스터 외부의 애플리케이션에서 Java API를 사용하는 방법에 대한 간단한 예
 *
 * <p>이것을 실행하는 가장 쉬운 방법: mvn exec:java
 * -Dexec.mainClass="io.kubernetes.client.examples.KubeConfigFileClientExample"
 *
 */
public class KubeConfigFileClientExample {
  public static void main(String[] args) throws IOException, ApiException {

    // KubeConfig의 파일 경로
    String kubeConfigPath = "~/.kube/config";

    // 파일시스템에서 클러스터 외부 구성인 kubeconfig 로드
    ApiClient client =
        ClientBuilder.kubeconfig(KubeConfig.loadKubeConfig(new FileReader(kubeConfigPath))).build();

    // 전역 디폴트 api-client를 위에서 정의한 클러스터 내 클라이언트로 설정
    Configuration.setDefaultApiClient(client);

    // CoreV1Api는 전역 구성에서 디폴트 api-client를 로드
    CoreV1Api api = new CoreV1Api();

    // CoreV1Api 클라이언트를 호출한다
    V1PodList list = api.listPodForAllNamespaces(null, null, null, null, null, null, null, null, null);
    System.out.println("Listing all pods: ");
    for (V1Pod item : list.getItems()) {
      System.out.println(item.getMetadata().getName());
    }
  }
}

dotnet 클라이언트

dotnet 클라이언트를 사용하려면, 다음 명령을 실행한다. dotnet add package KubernetesClient --version 1.6.1 추가 설치 옵션은 dotnet Client Library 페이지를 참고한다. 어떤 버전이 지원되는지를 확인하려면 https://github.com/kubernetes-client/csharp/releases를 참고한다.

dotnet 클라이언트는 kubectl CLI가 API 서버를 찾아 인증하기 위해 사용하는 것과 동일한 kubeconfig 파일을 사용할 수 있다. 이 예제를 참고한다.

using System;
using k8s;

namespace simple
{
    internal class PodList
    {
        private static void Main(string[] args)
        {
            var config = KubernetesClientConfiguration.BuildDefaultConfig();
            IKubernetes client = new Kubernetes(config);
            Console.WriteLine("Starting Request!");

            var list = client.ListNamespacedPod("default");
            foreach (var item in list.Items)
            {
                Console.WriteLine(item.Metadata.Name);
            }
            if (list.Items.Count == 0)
            {
                Console.WriteLine("Empty!");
            }
        }
    }
}

JavaScript 클라이언트

JavaScript 클라이언트를 설치하려면, 다음 명령을 실행한다. npm install @kubernetes/client-node 어떤 버전이 지원되는지를 확인하려면 https://github.com/kubernetes-client/javascript/releases를 참고한다.

JavaScript 클라이언트는 kubectl CLI가 API 서버를 찾아 인증하기 위해 사용하는 것과 동일한 kubeconfig 파일을 사용할 수 있다. 이 예제를 참고한다.

const k8s = require('@kubernetes/client-node');

const kc = new k8s.KubeConfig();
kc.loadFromDefault();

const k8sApi = kc.makeApiClient(k8s.CoreV1Api);

k8sApi.listNamespacedPod('default').then((res) => {
    console.log(res.body);
});

Haskell 클라이언트

어떤 버전이 지원되는지를 확인하려면 https://github.com/kubernetes-client/haskell/releases를 참고한다.

Haskell 클라이언트는 kubectl CLI가 API 서버를 찾아 인증하기 위해 사용하는 것과 동일한 kubeconfig 파일을 사용할 수 있다. 이 예제를 참고한다.

exampleWithKubeConfig :: IO ()
exampleWithKubeConfig = do
    oidcCache <- atomically $ newTVar $ Map.fromList []
    (mgr, kcfg) <- mkKubeClientConfig oidcCache $ KubeConfigFile "/path/to/kubeconfig"
    dispatchMime
            mgr
            kcfg
            (CoreV1.listPodForAllNamespaces (Accept MimeJSON))
        >>= print

다음 내용

3.7 - DNS 서비스 사용자 정의하기

이 페이지는 클러스터 안에서 사용자의 DNS 파드(Pod) 를 설정하고 DNS 변환(DNS resolution) 절차를 사용자 정의하는 방법을 설명한다.

시작하기 전에

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

클러스터는 CoreDNS 애드온을 구동하고 있어야 한다.

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

소개

DNS는 애드온 관리자클러스터 애드온을 사용하여 자동으로 시작되는 쿠버네티스 내장 서비스이다.

CoreDNS를 디플로이먼트(Deployment)로 실행하고 있을 경우, 일반적으로 고정 IP 주소를 갖는 쿠버네티스 서비스로 노출된다. Kubelet 은 --cluster-dns=<dns-service-ip> 플래그를 사용하여 DNS 확인자 정보를 각 컨테이너에 전달한다.

DNS 이름에도 도메인이 필요하다. 사용자는 kubelet 에 있는 --cluster-domain=<default-local-domain> 플래그를 통하여 로컬 도메인을 설정할 수 있다.

DNS 서버는 정방향 조회(A 및 AAAA 레코드), 포트 조회(SRV 레코드), 역방향 IP 주소 조회(PTR 레코드) 등을 지원한다. 더 자세한 내용은 서비스 및 파드용 DNS를 참고한다.

만약 파드의 dnsPolicydefault 로 지정되어 있는 경우, 파드는 자신이 실행되는 노드의 이름 변환(name resolution) 구성을 상속한다. 파드의 DNS 변환도 노드와 동일하게 작동해야 한다. 그 외에는 알려진 이슈를 참고한다.

만약 위와 같은 방식을 원하지 않거나, 파드를 위해 다른 DNS 설정이 필요한 경우, 사용자는 kubelet 의 --resolv-conf 플래그를 사용할 수 있다. 파드가 DNS를 상속받지 못하도록 하기 위해 이 플래그를 ""로 설정한다. DNS 상속을 위해 /etc/resolv.conf 이외의 파일을 지정할 경우 유효한 파일 경로를 설정한다.

CoreDNS

CoreDNS는 dns 명세를 준수하며 클러스터 DNS 역할을 할 수 있는, 범용적인 권한을 갖는 DNS 서버이다.

CoreDNS 컨피그맵(ConfigMap) 옵션

CoreDNS는 모듈형이자 플러그인이 가능한 DNS 서버이며, 각 플러그인들은 CoreDNS에 새로운 기능을 부가한다. CoreDNS 서버는 CoreDNS 구성 파일인 Corefile을 관리하여 구성할 수 있다. 클러스터 관리자는 CoreDNS Corefile에 대한 컨피그맵을 수정하여 해당 클러스터에 대한 DNS 서비스 검색 동작을 변경할 수 있다.

쿠버네티스에서 CoreDNS는 아래의 기본 Corefile 구성으로 설치된다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health {
            lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
            pods insecure
            fallthrough in-addr.arpa ip6.arpa
            ttl 30
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }    

Corefile의 구성은 CoreDNS의 아래 플러그인을 포함한다.

  • errors: 오류가 표준 출력(stdout)에 기록된다.
  • health: CoreDNS의 상태(healthy)가 http://localhost:8080/health 에 기록된다. 이 확장 구문에서 lameduck 은 프로세스를 비정상 상태(unhealthy)로 만들고, 프로세스가 종료되기 전에 5초 동안 기다린다.
  • ready: 8181 포트의 HTTP 엔드포인트가, 모든 플러그인이 준비되었다는 신호를 보내면 200 OK 를 반환한다.
  • kubernetes: CoreDNS가 쿠버네티스의 서비스 및 파드의 IP를 기반으로 DNS 쿼리에 대해 응답한다. 해당 플러그인에 대한 세부 사항은 CoreDNS 웹사이트에서 확인할 수 있다.
    • ttl 을 사용하면 응답에 대한 사용자 정의 TTL 을 지정할 수 있으며, 기본값은 5초이다. 허용되는 최소 TTL은 0초이며, 최대값은 3600초이다. 레코드가 캐싱되지 않도록 할 경우, TTL을 0으로 설정한다.
    • pods insecure 옵션은 kube-dns 와의 하위 호환성을 위해 제공된다.
    • pods verified 옵션을 사용하여, 일치하는 IP의 동일 네임스페이스(Namespace)에 파드가 존재하는 경우에만 A 레코드를 반환하게 할 수 있다.
    • pods disabled 옵션은 파드 레코드를 사용하지 않을 경우 사용된다.
  • prometheus: CoreDNS의 메트릭은 프로메테우스 형식(OpenMetrics 라고도 알려진)의 http://localhost:9153/metrics 에서 사용 가능하다.
  • forward: 쿠버네티스 클러스터 도메인에 없는 쿼리들은 모두 사전에 정의된 리졸버(/etc/resolv.conf)로 전달된다.
  • cache: 프론트 엔드 캐시를 활성화한다.
  • loop: 간단한 전달 루프(loop)를 감지하고, 루프가 발견되면 CoreDNS 프로세스를 중단(halt)한다.
  • reload: 변경된 Corefile을 자동으로 다시 로드하도록 한다. 컨피그맵 설정을 변경한 후에 변경 사항이 적용되기 위하여 약 2분정도 소요된다.
  • loadbalance: 응답에 대하여 A, AAAA, MX 레코드의 순서를 무작위로 선정하는 라운드-로빈 DNS 로드밸런서이다.

사용자는 컨피그맵을 변경하여 기본 CoreDNS 동작을 변경할 수 있다.

CoreDNS를 사용하는 스텁 도메인(Stub-domain)과 업스트림 네임서버(nameserver)의 설정

CoreDNS는 포워드 플러그인을 사용하여 스텁 도메인 및 업스트림 네임서버를 구성할 수 있다.

예시

만약 클러스터 운영자가 10.150.0.1 에 위치한 Consul 도메인 서버를 가지고 있고, 모든 Consul 이름의 접미사가 .consul.local 인 경우, CoreDNS에서 이를 구성하기 위해 클러스터 관리자는 CoreDNS 컨피그맵에서 다음 구문을 생성한다.

consul.local:53 {
    errors
    cache 30
    forward . 10.150.0.1
}

모든 비 클러스터의 DNS 조회가 172.16.0.1 의 특정 네임서버를 통과하도록 할 경우, /etc/resolv.conf 대신 forward 를 네임서버로 지정한다.

forward .  172.16.0.1

기본 Corefile 구성에 따른 최종 컨피그맵은 다음과 같다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        forward . 172.16.0.1
        cache 30
        loop
        reload
        loadbalance
    }
    consul.local:53 {
        errors
        cache 30
        forward . 10.150.0.1
    }    

다음 내용

3.8 - kubelet 이미지 자격 증명 공급자 구성하기

kubelet의 이미지 자격 증명 공급자 플러그인을 구성한다.
기능 상태: Kubernetes v1.26 [stable]

쿠버네티스 v1.20부터 kubelet은 exec 플러그인을 사용하여 컨테이너 이미지 레지스트리에 대한 자격 증명(credential)을 동적으로 검색할 수 있다. kubelet과 exec 플러그인은 쿠버네티스 버전 API를 사용하여 stdio(stdin, stdout, stderr)를 통해 통신한다. kubelet은 플러그인을 통해 정적 자격 증명을 디스크에 저장하는 대신 컨테이너 레지스트리에 대한 자격 증명을 동적으로 요청할 수 있다. 예를 들어, 플러그인은 로컬 메타데이터 서버와 통신하여 kubelet에 의해 풀(pulled) 된 이미지에 대한 단기 유효(short-lived) 자격 증명을 검색할 수 있다.

아래 중 하나에 해당하면 이 기능의 사용을 고려해도 좋다.

  • 레지스트리에 대한 인증 정보를 찾기 위해 클라우드 공급자 서비스에 대한 API 호출이 필요할 때.
  • 자격 증명 만료 시간이 짧아 새 자격 증명 요청이 자주 필요할 때.
  • 레지스트리 자격 증명을 디스크 또는 이미지 풀 시크릿에 저장하는 것을 허용하지 않을 때.

이 가이드는 kubelet의 이미지 자격 증명 공급자 플러그인 메커니즘을 구성하는 방법을 보여 준다.

시작하기 전에

  • kubelet 자격 증명 공급자 플러그인을 지원하는 노드로 구성된 쿠버네티스 클러스터가 필요하다. 이 기능은 쿠버네티스 1.31에서 사용 가능하다. 쿠버네티스 v1.24 및 v1.25에는 베타 기능으로 포함되었으며, 기본적으로 활성화되어 있다.
  • 자격 증명 공급자 exec 플러그인에 대한 구현체(implementation)가 필요하다. 이를 위해 자체 플러그인을 구축하거나 클라우드 공급자가 제공하는 플러그인을 사용할 수 있다.
쿠버네티스 서버의 버전은 다음과 같거나 더 높아야 함. 버전: v1.26. 버전 확인을 위해서, 다음 커맨드를 실행 kubectl version.

노드에 플러그인 설치하기

자격 증명 공급자 플러그인은 kubelet에 의해 실행될 실행 가능한 바이너리이다. 클러스터의 모든 노드에 플러그인 바이너리가 있고 알려진 디렉터리에 저장됐는지 확인한다. 이후 kubelet 플래그를 구성할 때 해당 디렉터리가 필요하다.

kubelet 구성하기

이 기능을 사용하려면 kubelet에 두 개의 플래그가 설정돼야 한다.

  • --image-credential-provider-config - 자격 증명 공급자 플러그인 구성 파일 경로.
  • --image-credential-provider-bin-dir - 자격 증명 공급자 플러그인 바이너리 파일이 있는 디렉터리 경로.

kubelet 자격 증명 공급자 구성

kubelet은 --image-credential-provider-config로 전달된 구성 파일을 읽고, 컨테이너 이미지에 대해 어떤 exec 플러그인을 호출할지 결정한다. ECR-based 플러그인을 사용하는 경우 사용하게 될 수 있는 구성 파일의 예:

apiVersion: kubelet.config.k8s.io/v1
kind: CredentialProviderConfig
# providers 필드는 kubelet이 활성화할 자격 증명 공급자 헬퍼 플러그인의 목록을 나타낸다. 
# 단일 이미지에 대해 복수 공급자가 매치될 수도 있으며, 
# 이러한 경우 모든 공급자의 자격 증명이 kubelet으로 리턴된다. 
# 단일 이미지에 대해 복수 공급자가 호출된 경우, 결과가 합산된다. 
# 공급자가 중복되는(overlapping) 인증 키를 리턴한 경우, 이 목록의 위쪽에 위치하는 공급자로부터의 값이 사용된다.
providers:
  # name 필드는 자격 증명 공급자를 구분하기 위한 필수 필드이다. 
  # 이 이름은 kubelet이 인식하는 공급자 실행 파일의 이름과 일치해야 한다. 
  # 해당 실행 파일은 kubelet의 bin 디렉토리에 존재해야 한다(--image-credential-provider-bin-dir 플래그로 설정).
  - name: ecr
    # matchImages 필드는 각 이미지에 대해 이 공급자가 활성화되어야 하는지를 
    # 판단하기 위한 문자열의 목록을 나타내는 필수 필드이다. 
    # kubelet이 요청한 이미지가 다음 문자열 중 하나와 매치되면, 
    # 해당 플러그인이 활성화되어 자격 증명을 제공할 수 있게 된다. 
    # 이미지 태그 문자열은 저장소(registry) 도메인 및 URL 경로를 포함해야 한다.
    #
    # matchImages의 각 항목은 패턴을 나타내며, 포트와 경로를 포함할 수 있다. 
    # 도메인 자리에 글롭(glob)도 사용할 수 있으나, 포트와 경로에는 사용할 수 없다. 
    # 글롭은 '*.k8s.io' 또는 'k8s.*.io'와 같이 서브도메인 형태로 사용하거나, 'k8s.*'와 같이 최상위 도메인 형태로 사용할 수 있다. 
    # 'app*.k8s.io'와 같이 서브도메인의 일부를 매칭하는 것도 지원된다. 
    # 각 글롭은 단일 서브도메인 분할만을 매칭할 수 있으므로, `*.io`는 `*.k8s.io`에 매치되지 **않는다**.
    #
    # 다음 사항이 모두 만족될 때에만 image와 matchImage가 매치되었다고 판단한다.
    # - 양쪽의 도메인 파트 수가 동일하고, 각 파트가 매치됨
    # - imageMatch의 URL 경로가 타겟 이미지 URL 경로의 접두사임
    # - imageMatch가 포트를 포함하면, 이미지 쪽에 기재된 포트와 매치됨
    #
    # matchImages 예시는 다음과 같다.
    # - 123456789.dkr.ecr.us-east-1.amazonaws.com
    # - *.azurecr.io
    # - gcr.io
    # - *.*.registry.io
    # - registry.io:8080/path
    matchImages:
      - "*.dkr.ecr.*.amazonaws.com"
      - "*.dkr.ecr.*.amazonaws.cn"
      - "*.dkr.ecr-fips.*.amazonaws.com"
      - "*.dkr.ecr.us-iso-east-1.c2s.ic.gov"
      - "*.dkr.ecr.us-isob-east-1.sc2s.sgov.gov"
    # defaultCacheDuration 필드는 캐시 기간이 플러그인 응답에 명시되지 않은 경우에 
    # 해당 플러그인이 자격 증명을 인메모리 캐시에 보관할 기본 기간을 지정한다. 이 필드는 필수이다.
    defaultCacheDuration: "12h"
    # apiVersion 필드는 CredentialProviderRequest를 실행할 때 기재될 입력 버전을 지정하는 필수 필드이다. 
    # 응답 CredentialProviderResponse는 입력과 동일한 인코딩 버전을 사용해야 한다. 현재 지원되는 값은 다음과 같다.
    # - credentialprovider.kubelet.k8s.io/v1
    apiVersion: credentialprovider.kubelet.k8s.io/v1
    # args 필드는 커맨드를 실행할 때 전달할 인자를 지정하는 필드이다.
    # 이 필드는 선택사항이다.
    args:
      - get-credentials
    # env 필드는 프로세스에 노출할 추가적인 환경 변수를 기재하는 필드이다. 
    # 이들은 호스트의 환경 변수 및 
    # client-go가 플러그인에 인자를 전달하기 위해 사용하는 변수와 합산된다.
    # 이 필드는 선택사항이다.
    env:
      - name: AWS_PROFILE
        value: example_profile

providers 필드는 kubelet에서 사용되는 활성화된 플러그인의 목록으로, 각 항목에는 몇 가지 필수 필드가 있다.

  • name: --image-credential-provider-bin-dir로 전달된 디렉터리에 존재하는 실행 가능한 바이너리의 이름과 반드시 일치해야 하는 플러그인의 이름.
  • matchImages: 이 공급자를 호출할지 결정하기 위해 이미지를 대조하는 데 사용되는 문자열 목록. 아래의 더 많은 내용 참조.
  • defaultCacheDuration: 플러그인에 의해 캐시 기간이 지정되지 않으면, kubelet이 메모리에 자격 증명을 캐시하는 기본 기간.
  • apiVersion: kubelet과 exec 플러그인이 통신할 때 사용하는 API 버전.

각 자격 증명 공급자에게 인수(arg) 및 환경 변수도 선택적으로 제공할 수 있다. 플러그인에 필요한 인수 및 환경 변수 집합을 확인하려면 해당 플러그인 구현자에게 문의하는 것이 좋다.

이미지 매칭 구성

kubelet은 각 자격 증명 공급자에 대한 matchImages 필드를 사용해 파드가 사용하고 있는 특정 이미지에 대해 플러그인을 호출할지 결정한다. Globs는 도메인에서 사용할 수 있지만 포트나 경로에서는 사용할 수 없다. Glob은 *.k8s.io이나 k8s.*.io 같은 서브도메인과 k8s.*와 같은 최상위 도메인으로 지원된다. 또한, app*.k8s.io와 같은 부분 서브도메인을 매칭하는 것도 지원된다. 각 Glob은 단일 하위 도메인 세그먼트만 일치할 수 있으므로 *.io*.k8s.io과 일치하지 않는다.

아래와 같은 경우 이미지 이름과 matchImage 항목 사이에 매치가 존재한다.

  • 둘 다 동일한 수의 도메인 파트를 포함하고 있으며 각 파트가 매치한다.
  • 매치 이미지의 URL 경로는 대상 이미지 URL 경로의 접두사여야 한다.
  • imageMatch에 포트가 포함되어 있으면 해당 포트는 이미지에서도 매치해야 한다.

matchImages 패턴의 예시 값은 아래와 같다.

  • 123456789.dkr.ecr.us-east-1.amazonaws.com
  • *.azurecr.io
  • gcr.io
  • *.*.registry.io
  • foo.registry.io:8080/path

다음 내용

3.9 - 구성 파일을 통해 Kubelet 파라미터 설정하기

커맨드 라인 플래그 대신 디스크 상의 구성 파일을 통해 Kubelet의 구성 파라미터 하위 집합을 설정할 수 있다.

구성 파일을 통해 파라미터를 제공하는 것은 노드 배포 및 구성 관리를 간소화하기 때문에 권장되는 접근 방식이다.

구성 파일 만들기

파일을 통해 구성할 수 있는 Kubelet 구성의 하위 집합은 KubeletConfiguration 구조체에 의해 정의된다.

구성 파일은 이 구조체의 파라미터를 반드시 JSON 또는 YAML로 표현한 파일이어야 한다. Kubelet이 파일에 읽기 권한이 있는지 확인한다.

다음은 이러한 파일에 대한 예시를 보여준다.

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
address: "192.168.0.8",
port: 20250,
serializeImagePulls: false,
evictionHard:
    memory.available:  "200Mi"

이 예제에서, Kubelet은 192.168.0.8 IP 주소와 20250 포트에서 동작하고, 이미지를 병렬로 가져오고, 사용 가능 메모리가 200Mi 아래로 떨어지면 파드를 축출하도록 구성되어 있다. 플래그에 의해 재정의(overridden)되지 않는한, 다른 모든 Kubelet 구성은 기본 제공 기본값으로 유지된다. 구성 파일과 동일한 값을 대상으로 하는 커맨드 라인 플래그는 해당 값을 재정의 한다.

구성 파일을 통해 구성된 Kubelet 프로세스 시작하기

Kubelet의 구성 파일 경로로 설정된 --config 플래그를 사용하여 Kubelet을 시작하면 Kubelet이 이 파일에서 구성을 불러온다.

구성 파일과 동일한 값을 대상으로 하는 커맨드 라인 플래그는 해당 값을 재정의한다는 점을 유의한다. 이렇게 하면 커맨드 라인 API와의 이전 버전과의 호환성을 보장할 수 있다.

Kubelet 구성 파일의 상대 파일 경로는 Kubelet 구성 파일의 위치를 기준으로 확인되는 반면, 커맨드 라인 플래그의 상대 경로는 Kubelet의 현재 작업 디렉터리를 기준으로 확인된다는 점에 유의한다.

일부 기본값은 커맨드 라인 플래그와 Kubelet 구성 파일 간에 다르다는 점에 유의한다. --config가 제공되고 명령줄을 통해 값을 지정하지 않은 경우, KubeletConfiguration 버전의 기본값이 적용된다. 위 예제의 버전은 kubelet.config.k8s.io/v1beta1이다.

다음 내용

3.10 - 기본 스토리지클래스(StorageClass) 변경하기

이 페이지는 특별한 요구사항이 없는 퍼시스턴트볼륨클레임(PersistentVolumeClaim)의 볼륨을 프로비저닝 하는데 사용되는 기본 스토리지 클래스를 변경하는 방법을 보여준다.

시작하기 전에

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

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

왜 기본 스토리지 클래스를 변경하는가?

설치 방법에 따라, 사용자의 쿠버네티스 클러스터는 기본으로 표시된 기존 스토리지클래스와 함께 배포될 수 있다. 이 기본 스토리지클래스는 특정 스토리지 클래스가 필요하지 않은 퍼시스턴트볼륨클레임에 대해 스토리지를 동적으로 프로비저닝 하기 위해 사용된다. 더 자세한 내용은 퍼시스턴트볼륨클레임 문서를 보자.

미리 설치된 기본 스토리지클래스가 사용자의 예상되는 워크로드에 적합하지 않을수도 있다. 예를 들어, 너무 가격이 높은 스토리지를 프로비저닝 해야할 수도 있다. 이런 경우에, 기본 스토리지 클래스를 변경하거나 완전히 비활성화 하여 스토리지의 동적 프로비저닝을 방지할 수 있다.

기본 스토리지클래스를 삭제하는 경우, 사용자의 클러스터에서 구동 중인 애드온 매니저에 의해 자동으로 다시 생성될 수 있으므로 정상적으로 삭제가 되지 않을 수도 있다. 애드온 관리자 및 개별 애드온을 비활성화 하는 방법에 대한 자세한 내용은 설치 문서를 참조하자.

기본 스토리지클래스 변경하기

  1. 사용자의 클러스터에 있는 스토리지클래스 목록을 조회한다.

    kubectl get storageclass
    

    결과는 아래와 유사하다.

    NAME                 PROVISIONER               AGE
    standard (default)   kubernetes.io/gce-pd      1d
    gold                 kubernetes.io/gce-pd      1d
    

    기본 스토리지클래스는 (default) 로 표시되어 있다.

  2. 기본 스토리지클래스를 기본값이 아닌 것으로 표시한다.

    기본 스토리지클래스에는 storageclass.kubernetes.io/is-default-class 의 값이 true 로 설정되어 있다. 다른 값이거나 어노테이션이 없을 경우 false 로 처리된다.

    스토리지클래스를 기본값이 아닌 것으로 표시하려면, 그 값을 false 로 변경해야 한다.

    kubectl patch storageclass standard -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
    

    여기서 standard 는 사용자가 선택한 스토리지클래스의 이름이다.

  3. 스토리지클래스를 기본값으로 표시한다.

    이전 과정과 유사하게, 어노테이션을 추가/설정해야 한다. storageclass.kubernetes.io/is-default-class=true.

    kubectl patch storageclass gold -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
    

    최대 1개의 스토리지클래스를 기본값으로 표시할 수 있다는 것을 알아두자. 만약 2개 이상이 기본값으로 표시되면, 명시적으로 storageClassName 가 지정되지 않은 PersistentVolumeClaim 은 생성될 수 없다.

  4. 사용자가 선택한 스토리지클래스가 기본값으로 되어 있는지 확인한다.

    kubectl get storageclass
    

    결과는 아래와 유사하다.

    NAME             PROVISIONER               AGE
    standard         kubernetes.io/gce-pd      1d
    gold (default)   kubernetes.io/gce-pd      1d
    

다음 내용

3.11 - 네임스페이스를 사용해 클러스터 공유하기

이 페이지는 네임스페이스를 살펴보고, 작업하고, 삭제하는 방법에 대해 다룬다. 또한 쿠버네티스 네임스페이스를 사용해 클러스터를 세분화하는 방법에 대해서도 다룬다.

시작하기 전에

네임스페이스 보기

  1. 아래 명령어를 사용해 클러스터의 현재 네임스페이스를 나열한다.
kubectl get namespaces
NAME          STATUS    AGE
default       Active    11d
kube-system   Active    11d
kube-public   Active    11d

쿠버네티스를 시작하면 세 개의 초기 네임스페이스가 있다.

  • default 다른 네임스페이스가 없는 오브젝트를 위한 기본 네임스페이스
  • kube-system 쿠버네티스 시스템에서 생성된 오브젝트의 네임스페이스
  • kube-public 이 네임스페이스는 자동으로 생성되며 모든 사용자(미인증 사용자를 포함)가 읽을 수 있다. 이 네임스페이스는 일부 리소스를 공개적으로 보고 읽을 수 있어야 하는 경우에 대비하여 대부분이 클러스터 사용을 위해 예약돼 있다. 그러나 이 네임스페이스의 공개적인 성격은 관례일 뿐 요구 사항은 아니다.

아래 명령을 실행해 특정 네임스페이스에 대한 요약 정보를 볼 수 있다.

kubectl get namespaces <name>

자세한 정보를 보는 것도 가능하다.

kubectl describe namespaces <name>
Name:           default
Labels:         <none>
Annotations:    <none>
Status:         Active

No resource quota.

Resource Limits
 Type       Resource    Min Max Default
 ----               --------    --- --- ---
 Container          cpu         -   -   100m

이러한 세부 정보에는 리소스 한도(limit) 범위 뿐만 아니라 리소스 쿼터(만약 있다면)까지 모두 표시된다.

리소스 쿼터는 네임스페이스 내 리소스의 집계 사용량을 추적하며, 네임스페이스에서 사용할 수 있는 하드(Hard) 리소스 사용 제한을 클러스터 운영자가 정의할 수 있도록 해준다.

제한 범위는 하나의 엔티티(entity)가 하나의 네임스페이스에서 사용할 수 있는 리소스 양에 대한 최대/최소 제약 조건을 정의한다.

어드미션 컨트롤: 리밋 레인지(Limit Range)를 참조하자.

네임스페이스는 다음 두 상태 중 하나에 있을 수 있다.

  • Active 네임스페이스가 사용 중이다.
  • Terminating 네임스페이스가 삭제 중이므로 새 오브젝트에 사용할 수 없다.

자세한 내용은 API 레퍼런스의 네임스페이스를 참조한다.

새 네임스페이스 생성하기

  1. my-namespace.yaml이라는 YAML 파일을 생성하고 아래 내용을 작성한다.

    apiVersion: v1
    kind: Namespace
    metadata:
      name: <insert-namespace-name-here>
    

    다음 명령을 실행한다.

    kubectl create -f ./my-namespace.yaml
    
  2. 아래 명령으로 네임스페이스를 생성할 수도 있다.

    kubectl create namespace <insert-namespace-name-here>
    

네임스페이스의 이름은 유효한 DNS 레이블이어야 한다.

옵션 필드인 finalizer는 네임스페이스가 삭제 될 때 관찰자가 리소스를 제거할 수 있도록 한다. 존재하지 않는 파이널라이저(finalizer)를 명시한 경우 네임스페이스는 생성되지만 사용자가 삭제하려 하면 Terminating 상태가 된다.

파이널라이저에 대한 자세한 내용은 네임스페이스 디자인 문서에서 확인할 수 있다.

네임스페이스 삭제하기

다음 명령을 실행해 네임스페이스를 삭제한다.

kubectl delete namespaces <insert-some-namespace-name>

삭제는 비동기적이므로 삭제 후 한동안은 네임스페이스의 상태가 Terminating으로 보일 것이다.

쿠버네티스 네임스페이스를 사용해 클러스터 세분화하기

  1. 기본 네임스페이스 이해하기

    기본적으로 쿠버네티스 클러스터는 클러스터에서 사용할 기본 파드, 서비스, 그리고 디플로이먼트(Deployment) 집합을 가지도록 클러스터를 프로비저닝 할 때 기본 네임스페이스를 인스턴스화한다.

    새 클러스터가 있다고 가정하고 아래 명령을 수행하면 사용 가능한 네임스페이스를 볼 수 있다.

    kubectl get namespaces
    
    NAME      STATUS    AGE
    default   Active    13m
    
  2. 새 네임스페이스 생성하기

    이 예제에서는 내용을 저장할 쿠버네티스 네임스페이스를 추가로 두 개 생성할 것이다.

    개발과 프로덕션 유스케이스에서 공유 쿠버네티스 클러스터를 사용하는 조직이 있다고 가정하자.

    개발 팀은 애플리케이션을 구축하고 실행하는데 사용하는 파드, 서비스, 디플로이먼트의 목록을 볼 수 있는 공간을 클러스터에 유지하려 한다. 이 공간에서는 쿠버네티스 리소스가 자유롭게 추가 및 제거되고, 누가 리소스를 수정할 수 있는지 없는지에 대한 제약이 완화돼 빠른 개발이 가능해진다.

    운영 팀은 운영 사이트를 실행하는 파드, 서비스, 디플로이먼트 집합을 조작할 수 있는 사람과 그렇지 않은 사람들에 대해 엄격한 절차를 적용할 수 있는 공간을 클러스터에 유지하려 한다.

    이 조직이 따를 수 있는 한 가지 패턴은 쿠버네티스 클러스터를 development(개발)production(운영)이라는 두 개의 네임스페이스로 분할하는 것이다.

    우리의 작업을 보존하기 위해 새로운 네임스페이스 두 개를 만들자.

    kubectl을 사용해 development 네임스페이스를 생성한다.

    kubectl create -f https://k8s.io/examples/admin/namespace-dev.json
    

    그런 다음 kubectl을 사용해 production 네임스페이스를 생성한다.

    kubectl create -f https://k8s.io/examples/admin/namespace-prod.json
    

    제대로 생성이 되었는지 확인하기 위해 클러스터 내의 모든 네임스페이스를 나열한다.

    kubectl get namespaces --show-labels
    
    NAME          STATUS    AGE       LABELS
    default       Active    32m       <none>
    development   Active    29s       name=development
    production    Active    23s       name=production
    
  3. 네임스페이스마다 파드 생성

    쿠버네티스 네임스페이스는 클러스터의 파드, 서비스 그리고 디플로이먼트의 범위를 제공한다.

    하나의 네임스페이스와 상호 작용하는 사용자는 다른 네임스페이스의 내용을 볼 수 없다.

    이를 보여주기 위해 development 네임스페이스에 간단한 디플로이먼트와 파드를 생성하자.

    kubectl create deployment snowflake --image=registry.k8s.io/serve_hostname  -n=development --replicas=2
    

    단순히 호스트명을 제공해주는 snowflake라는 파드의 개수를 2개로 유지하는 디플로이먼트를 생성하였다.

    kubectl get deployment -n=development
    
    NAME         READY   UP-TO-DATE   AVAILABLE   AGE
    snowflake    2/2     2            2           2m
    
    kubectl get pods -l app=snowflake -n=development
    
    NAME                         READY     STATUS    RESTARTS   AGE
    snowflake-3968820950-9dgr8   1/1       Running   0          2m
    snowflake-3968820950-vgc4n   1/1       Running   0          2m
    

    개발자들은 production 네임스페이스의 내용에 영향을 끼칠 걱정 없이 하고 싶은 것을 할 수 있으니 대단하지 않은가.

    이제 production 네임스페이스로 전환해 한 네임스페이스의 리소스가 다른 네임스페이스에서는 어떻게 숨겨지는지 보자.

    production 네임스페이스는 비어있어야 하며 아래 명령은 아무 것도 반환하지 않아야 한다.

    kubectl get deployment -n=production
    kubectl get pods -n=production
    

    프로덕션이 가축 키우는 것을 좋아하듯이, 우리도 production 네임스페이스에 cattle(가축)이라는 이름의 파드를 생성한다.

    kubectl create deployment cattle --image=registry.k8s.io/serve_hostname -n=production
    kubectl scale deployment cattle --replicas=5 -n=production
    
    kubectl get deployment -n=production
    
    NAME         READY   UP-TO-DATE   AVAILABLE   AGE
    cattle       5/5     5            5           10s
    
    kubectl get pods -l app=cattle -n=production
    
    NAME                      READY     STATUS    RESTARTS   AGE
    cattle-2263376956-41xy6   1/1       Running   0          34s
    cattle-2263376956-kw466   1/1       Running   0          34s
    cattle-2263376956-n4v97   1/1       Running   0          34s
    cattle-2263376956-p5p3i   1/1       Running   0          34s
    cattle-2263376956-sxpth   1/1       Running   0          34s
    

지금 쯤이면 사용자가 한 네임스페이스에 생성한 리소스는 다른 네임스페이스에서 숨겨져 있어야 한다는 것을 잘 알고 있을 것이다.

쿠버네티스 정책 지원이 발전함에 따라, 이 시나리오를 확장해 각 네임스페이스에 서로 다른 인증 규칙을 제공하는 방법을 보이도록 하겠다.

네임스페이스의 사용 동기 이해하기

단일 클러스터는 여러 사용자 및 사용자 그룹(이하 '사용자 커뮤니티')의 요구를 충족시킬 수 있어야 한다.

쿠버네티스 네임스페이스 는 여러 프로젝트, 팀 또는 고객이 쿠버네티스 클러스터를 공유할 수 있도록 지원한다.

이를 위해 다음을 제공한다.

  1. 이름에 대한 범위
  2. 인증과 정책을 클러스터의 하위 섹션에 연결하는 메커니즘

여러 개의 네임스페이스를 사용하는 것은 선택 사항이다.

각 사용자 커뮤니티는 다른 커뮤니티와 격리된 상태로 작업할 수 있기를 원한다.

각 사용자 커뮤니티는 다음을 가진다.

  1. 리소스 (파드, 서비스, 레플리케이션 컨트롤러(replication controller) 등
  2. 정책 (커뮤니티에서 조치를 수행할 수 있거나 없는 사람)
  3. 제약 조건 (해당 커뮤니티에서는 어느 정도의 쿼터가 허용되는지 등)

클러스터 운영자는 각 사용자 커뮤니티 마다 네임스페이스를 생성할 수 있다.

네임스페이스는 다음을 위한 고유한 범위를 제공한다.

  1. (기본 명명 충돌을 방지하기 위해) 명명된 리소스
  2. 신뢰할 수 있는 사용자에게 관리 권한 위임
  3. 커뮤니티 리소스 소비를 제한하는 기능

유스케이스는 다음을 포함한다.

  1. 클러스터 운영자로서 단일 클러스터에서 여러 사용자 커뮤니티를 지원하려 한다.
  2. 클러스터 운영자로서 클러스터 분할에 대한 권한을 해당 커뮤니티의 신뢰할 수 있는 사용자에게 위임하려 한다.
  3. 클러스터 운영자로서 클러스터를 사용하는 다른 커뮤니티에 미치는 영향을 제한하기 위해 각 커뮤니티가 사용할 수 있는 리소스의 양을 제한하고자 한다.
  4. 클러스터 사용자로서 다른 사용자 커뮤니티가 클러스터에서 수행하는 작업과는 별도로 사용자 커뮤니티와 관련된 리소스와 상호 작용하고 싶다.

네임스페이스와 DNS 이해하기

서비스를 생성하면 상응하는 DNS 엔트리(entry)가 생성된다. 이 엔트리는 <서비스-이름><네임스페이스=이름>.svc.cluster.local 형식을 갖는데, 컨테이너가 <서비스-이름>만 갖는 경우에는 네임스페이스에 국한된 서비스로 연결된다. 이 기능은 개발, 스테이징 및 프로덕션과 같이 여러 네임스페이스 내에서 동일한 설정을 사용할 때 유용하다. 네임스페이스를 넘어서 접근하려면 전체 주소 도메인 이름(FQDN)을 사용해야 한다.

다음 내용

3.12 - 네트워크 폴리시(Network Policy) 선언하기

이 문서는 사용자가 쿠버네티스 네트워크폴리시 API를 사용하여 파드(Pod)가 서로 통신하는 방법을 제어하는 네트워크 폴리시를 선언하는데 도움을 준다.

시작하기 전에

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

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

네트워크 폴리시를 지원하는 네트워크 제공자를 구성하였는지 확인해야 한다. 다음과 같이 네트워크폴리시를 지원하는 많은 네트워크 제공자들이 있다.

nginx 디플로이먼트(Deployment)를 생성하고 서비스(Service)를 통해 노출하기

쿠버네티스 네트워크 폴리시가 어떻게 동작하는지 확인하기 위해서, nginx 디플로이먼트를 생성한다.

kubectl create deployment nginx --image=nginx
deployment.apps/nginx created

nginx 라는 이름의 서비스를 통해 디플로이먼트를 노출한다.

kubectl expose deployment nginx --port=80
service/nginx exposed

위 명령어들은 nginx 파드에 대한 디플로이먼트를 생성하고, nginx 라는 이름의 서비스를 통해 디플로이먼트를 노출한다. nginx 파드와 디플로이먼트는 default 네임스페이스(namespace)에 존재한다.

kubectl get svc,pod
NAME                        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
service/kubernetes          10.100.0.1    <none>        443/TCP    46m
service/nginx               10.100.0.16   <none>        80/TCP     33s

NAME                        READY         STATUS        RESTARTS   AGE
pod/nginx-701339712-e0qfq   1/1           Running       0          35s

다른 파드에서 접근하여 서비스 테스트하기

사용자는 다른 파드에서 새 nginx 서비스에 접근할 수 있어야 한다. default 네임스페이스에 있는 다른 파드에서 nginx 서비스에 접근하기 위하여, busybox 컨테이너를 생성한다.

kubectl run busybox --rm -ti --image=busybox:1.28 -- /bin/sh

사용자 쉘에서, 다음의 명령을 실행한다.

wget --spider --timeout=1 nginx
Connecting to nginx (10.100.0.16:80)
remote file exists

nginx 서비스에 대해 접근 제한하기

access: true 레이블을 가지고 있는 파드만 nginx 서비스에 접근할 수 있도록 하기 위하여, 다음과 같은 네트워크폴리시 오브젝트를 생성한다.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: access-nginx
spec:
  podSelector:
    matchLabels:
      app: nginx
  ingress:
  - from:
    - podSelector:
        matchLabels:
          access: "true"

네트워크폴리시 오브젝트의 이름은 유효한 DNS 서브도메인 이름이어야 한다.

서비스에 정책 할당하기

kubectl을 사용하여 위 nginx-policy.yaml 파일로부터 네트워크폴리시를 생성한다.

kubectl apply -f https://k8s.io/examples/service/networking/nginx-policy.yaml
networkpolicy.networking.k8s.io/access-nginx created

access 레이블이 정의되지 않은 서비스에 접근 테스트

올바른 레이블이 없는 파드에서 nginx 서비스에 접근하려 할 경우, 요청 타임 아웃이 발생한다.

kubectl run busybox --rm -ti --image=busybox:1.28 -- /bin/sh

사용자 쉘에서, 다음의 명령을 실행한다.

wget --spider --timeout=1 nginx
Connecting to nginx (10.100.0.16:80)
wget: download timed out

접근 레이블을 정의하고 다시 테스트

사용자는 요청이 허용되도록 하기 위하여 올바른 레이블을 갖는 파드를 생성한다.

kubectl run busybox --rm -ti --labels="access=true" --image=busybox:1.28 -- /bin/sh

사용자 쉘에서, 다음의 명령을 실행한다.

wget --spider --timeout=1 nginx
Connecting to nginx (10.100.0.16:80)
remote file exists

3.13 - 노드에 대한 확장 리소스 알리기

이 페이지는 노드의 확장 리소스를 지정하는 방법을 보여준다. 확장 리소스를 통해 클러스터 관리자는 쿠버네티스에게 알려지지 않은 노드-레벨 리소스를 알릴 수 있다.

시작하기 전에

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

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

노드의 이름을 확인한다

kubectl get nodes

이 연습에 사용할 노드 중 하나를 선택한다.

노드 중 하나에 새로운 확장 리소스를 알린다

노드에서 새로운 확장 리소스를 알리려면, 쿠버네티스 API 서버에 HTTP PATCH 요청을 보낸다. 예를 들어, 노드 중 하나에 4개의 동글(dongle)이 있다고 가정한다. 다음은 노드에 4개의 동글 리소스를 알리는 PATCH 요청의 예이다.

PATCH /api/v1/nodes/<your-node-name>/status HTTP/1.1
Accept: application/json
Content-Type: application/json-patch+json
Host: k8s-master:8080

[
  {
    "op": "add",
    "path": "/status/capacity/example.com~1dongle",
    "value": "4"
  }
]

참고로 쿠버네티스는 동글이 무엇인지 또는 동글이 무엇을 위한 것인지 알 필요가 없다. 위의 PATCH 요청은 노드에 동글이라고 하는 네 가지 항목이 있음을 쿠버네티스에 알려준다.

쿠버네티스 API 서버에 요청을 쉽게 보낼 수 있도록 프록시를 시작한다.

kubectl proxy

다른 명령 창에서 HTTP PATCH 요청을 보낸다. <your-node-name> 을 노드의 이름으로 바꾼다.

curl --header "Content-Type: application/json-patch+json" \
--request PATCH \
--data '[{"op": "add", "path": "/status/capacity/example.com~1dongle", "value": "4"}]' \
http://localhost:8001/api/v1/nodes/<your-node-name>/status

출력은 노드가 4개의 동글 용량을 가졌음을 나타낸다.

"capacity": {
  "cpu": "2",
  "memory": "2049008Ki",
  "example.com/dongle": "4",

노드의 정보를 확인한다.

kubectl describe node <your-node-name>

다시 한 번, 출력에 동글 리소스가 표시된다.

Capacity:
 cpu:  2
 memory:  2049008Ki
 example.com/dongle:  4

이제, 애플리케이션 개발자는 특정 개수의 동글을 요청하는 파드를 만들 수 있다. 컨테이너에 확장 리소스 할당하기를 참고한다.

토론

확장 리소스는 메모리 및 CPU 리소스와 비슷하다. 예를 들어, 노드에서 실행 중인 모든 컴포넌트가 공유할 특정 양의 메모리와 CPU가 노드에 있는 것처럼, 노드에서 실행 중인 모든 컴포넌트가 특정 동글을 공유할 수 있다. 또한 애플리케이션 개발자가 특정 양의 메모리와 CPU를 요청하는 파드를 생성할 수 있는 것처럼, 특정 동글을 요청하는 파드를 생성할 수 있다.

확장 리소스는 쿠버네티스에게 불투명하다. 쿠버네티스는 그것들이 무엇인지 전혀 모른다. 쿠버네티스는 노드에 특정 개수의 노드만 있다는 것을 알고 있다. 확장 리소스는 정수로 알려야 한다. 예를 들어, 노드는 4.5개의 동글이 아닌, 4개의 동글을 알릴 수 있다.

스토리지 예제

노드에 800GiB의 특별한 종류의 디스크 스토리지가 있다고 가정한다. example.com/special-storage와 같은 특별한 스토리지의 이름을 생성할 수 있다. 그런 다음 특정 크기, 100GiB의 청크로 알릴 수 있다. 이 경우, 노드에는 example.com/special-storage 유형의 8가지 리소스가 있다고 알린다.

Capacity:
 ...
 example.com/special-storage: 8

이 특별한 스토리지에 대한 임의 요청을 허용하려면, 1바이트 크기의 청크로 특별한 스토리지를 알릴 수 있다. 이 경우, example.com/special-storage 유형의 800Gi 리소스를 알린다.

Capacity:
 ...
 example.com/special-storage:  800Gi

그런 다음 컨테이너는 최대 800Gi의 임의 바이트 수의 특별한 스토리지를 요청할 수 있다.

정리

다음은 노드에서 동글 알림을 제거하는 PATCH 요청이다.

PATCH /api/v1/nodes/<your-node-name>/status HTTP/1.1
Accept: application/json
Content-Type: application/json-patch+json
Host: k8s-master:8080

[
  {
    "op": "remove",
    "path": "/status/capacity/example.com~1dongle",
  }
]

쿠버네티스 API 서버에 요청을 쉽게 보낼 수 있도록 프록시를 시작한다.

kubectl proxy

다른 명령 창에서 HTTP PATCH 요청을 보낸다. <your-node-name>을 노드의 이름으로 바꾼다.

curl --header "Content-Type: application/json-patch+json" \
--request PATCH \
--data '[{"op": "remove", "path": "/status/capacity/example.com~1dongle"}]' \
http://localhost:8001/api/v1/nodes/<your-node-name>/status

동글 알림이 제거되었는지 확인한다.

kubectl describe node <your-node-name> | grep dongle

(출력이 보이지 않아야 함)

다음 내용

애플리케이션 개발자를 위한 문서

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

3.14 - 서비스 디스커버리를 위해 CoreDNS 사용하기

이 페이지는 CoreDNS 업그레이드 프로세스와 kube-dns 대신 CoreDNS를 설치하는 방법을 보여준다.

시작하기 전에

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

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

CoreDNS 소개

CoreDNS는 쿠버네티스 클러스터의 DNS 역할을 수행할 수 있는, 유연하고 확장 가능한 DNS 서버이다. 쿠버네티스와 동일하게, CoreDNS 프로젝트도 CNCF가 관리한다.

사용자는 기존 디플로이먼트인 kube-dns를 교체하거나, 클러스터를 배포하고 업그레이드하는 kubeadm과 같은 툴을 사용하여 클러스터 안의 kube-dns 대신 CoreDNS를 사용할 수 있다.

CoreDNS 설치

Kube-dns의 배포나 교체에 관한 매뉴얼은 CoreDNS GitHub 프로젝트에 있는 문서를 확인하자.

CoreDNS로 이관하기

Kubeadm을 사용해 기존 클러스터 업그레이드하기

쿠버네티스 버전 1.21에서, kubeadm은 DNS 애플리케이션으로서의 kube-dns 지원을 제거했다. kubeadm v1.31 버전에서는, DNS 애플리케이션으로 CoreDNS만이 지원된다.

kube-dns를 사용 중인 클러스터를 업그레이드하기 위하여 kubeadm 을 사용할 때 CoreDNS로 전환할 수 있다. 이 경우, kubeadmkube-dns 컨피그맵(ConfigMap)을 기반으로 스텁 도메인(stub domain), 업스트림 네임 서버의 설정을 유지하며 CoreDNS 설정("Corefile")을 생성한다.

CoreDNS 업그레이드하기

쿠버네티스에서의 CoreDNS 버전 페이지에서, 쿠버네티스 각 버전에 대해 kubeadm이 설치하는 CoreDNS의 버전을 확인할 수 있다.

CoreDNS만 업그레이드하고 싶거나 커스텀 이미지를 사용하고 싶은 경우, CoreDNS를 수동으로 업그레이드할 수 있다. 가이드라인 및 따라해보기를 참고하여 부드러운 업그레이드를 수행할 수 있다. 클러스터를 업그레이드할 때 기존 CoreDNS 환경 설정("Corefile")을 보존했는지 확인한다.

kubeadm 도구를 사용하여 클러스터를 업그레이드하는 경우, kubeadm이 자동으로 기존 CoreDNS 환경 설정을 보존한다.

CoreDNS 튜닝하기

리소스 활용이 중요한 경우, CoreDNS 구성을 조정하는 것이 유용할 수 있다. 더 자세한 내용은 CoreDNS 스케일링에 대한 설명서를 확인한다.

다음 내용

CoreDNS 환경 설정("Corefile")을 수정하여 kube-dns보다 더 많은 유스케이스를 지원하도록 CoreDNS를 구성할 수 있다. 더 많은 정보는 CoreDNS의 kubernetes 플러그인 문서를 참고하거나, CoreDNS 블로그의 쿠버네티스를 위한 커스텀 DNS 엔트리를 확인한다.

3.15 - 스토리지 사용량 제한

이 예제는 네임스페이스(namespace)에서 사용되는 스토리지의 용량을 제한하는 방법을 보여준다.

예제에서는 다음과 같은 리소스가 사용된다. 리소스쿼터(ResourceQuota), 리밋레인지(LimitRange), 그리고 퍼시스턴트볼륨클레임(PersistentVolumeClaim).

시작하기 전에

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

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

시나리오: 스토리지 사용량 제한하기

클러스터 관리자는 사용자를 대표하여 클러스터를 운영하고 , 비용을 제어하기 위해 단일 네임스페이스에서 사용할 수 있는 스토리지의 크기를 제어하려고 한다.

관리자는 다음을 제한하려고 한다.

  1. 네임스페이스에 있는 퍼시스턴트볼륨클레임의 수
  2. 각 클레임(claim)이 요청할 수 있는 스토리지의 용량
  3. 네임스페이스가 가질 수 있는 누적 스토리지 용량

스토리지 요청을 제한하기 위한 리밋레인지(LimitRange)

네임스페이스에 리밋레인지(LimitRange)을 추가하면 스토리지 요청 크기가 최소 및 최대값으로 설정된다. 스토리지는 퍼시스턴트 볼륨 클레임(Persistent Volume Claim)을 통해 요청하게 된다. 제한 범위를 적용하는 어드미션 컨트롤러(Admission Controller)는 관리자가 설정한 값보다 높거나 낮은 PVC를 거부한다.

이 예제에서, 10Gi의 스토리지를 요청하는 PVC는 2Gi인 최대값을 초과하기 때문에 거부된다.

apiVersion: v1
kind: LimitRange
metadata:
  name: storagelimits
spec:
  limits:
  - type: PersistentVolumeClaim
    max:
      storage: 2Gi
    min:
      storage: 1Gi

스토리지 요청에 대한 최솟값은 해당 스토리지의 제공자가 최소값을 특정하여 요구하는 경우 사용한다. 예를 들어, AWS EBS 볼륨을 사용할 때는 최소 1Gi를 요청해야 한다.

PVC 수와 누적 스토리지 용량을 제한하는 스토리지쿼터(StorageQuota)

관리자는 네임스페이스의 PVC 수와 해당 PVC의 누적 용량을 제한할 수 있다. 최대값을 초과하는 새 PVC는 거부된다.

이 예제에서 네임스페이스의 6번째 PVC는 최대 카운트 5를 초과하기 때문에 거부된다. 또한, 위의 2Gi 최대 한계(max limit)와 결합된 5Gi 최대 할당량(maximum quota)은 각각 2Gi를 갖는 3개의 PVC를 가질 수 없다. 그것은 5Gi로 한도가 정해진 네임스페이스에 대해 6Gi의 요청이 될 것이다.

apiVersion: v1
kind: ResourceQuota
metadata:
  name: storagequota
spec:
  hard:
    persistentvolumeclaims: "5"
    requests.storage: "5Gi"

요약

리밋레인지(LimitRange)을 지정하면 요청된 스토리지 양을 제한할 수 있으며 리소스쿼터(ResourceQuota)는 네임스페이스에 클레임(claim)수와 누적 스토리지 용량을 효과적으로 제한할 수 있다. 클러스터 관리자는 어느 프로젝트도 할당량을 초과하는 위험이 없도록 클러스터의 스토리지 예산을 계획할 수 있다.

3.16 - 중요한 애드온 파드 스케줄링 보장하기

API 서버, 스케줄러 및 컨트롤러 매니저와 같은 쿠버네티스 주요 컴포넌트들은 컨트롤 플레인 노드에서 동작한다. 반면, 애드온들은 일반 클러스터 노드에서 동작한다. 이러한 애드온들 중 일부(예: 메트릭 서버, DNS, UI)는 클러스터 전부가 정상적으로 동작하는 데 필수적일 수 있다. 만약, 필수 애드온이 축출되고(수동 축출, 혹은 업그레이드와 같은 동작으로 인한 의도하지 않은 축출) pending 상태가 된다면, 클러스터가 더 이상 제대로 동작하지 않을 수 있다. (사용률이 매우 높은 클러스터에서 해당 애드온이 축출되자마자 다른 대기중인 파드가 스케줄링되거나 다른 이유로 노드에서 사용할 수 있는 자원량이 줄어들어 pending 상태가 발생할 수 있다)

유의할 점은, 파드를 중요(critical)로 표시하는 것은 축출을 완전히 방지하기 위함이 아니다. 이것은 단지 파드가 영구적으로 사용할 수 없게 되는 것만을 방지하기 위함이다. 중요로 표시한 스태틱(static) 파드는 축출될 수 없다. 반면, 중요로 표시한 일반적인(non-static) 파드의 경우 항상 다시 스케줄링된다.

파드를 중요(critical)로 표시하기

파드를 중요로 표시하기 위해서는, 해당 파드에 대해 priorityClassName을 system-cluster-critical이나 system-node-critical로 설정한다. system-node-critical은 가장 높은 우선 순위를 가지며, 심지어 system-cluster-critical보다도 우선 순위가 높다.

3.17 - 쿠버네티스 API 활성화 혹은 비활성화하기

이 페이지는 클러스터 컨트롤 플레인의 특정한 API 버전을 활성화하거나 비활성화하는 방법에 대해 설명한다.

API 서버에 --runtime-config=api/<version> 커맨드 라인 인자를 사용함으로서 특정한 API 버전을 활성화하거나 비활성화할 수 있다. 이 인자에 대한 값으로는 콤마로 구분된 API 버전의 목록을 사용한다. 뒤쪽에 위치한 값은 앞쪽의 값보다 우선적으로 사용된다.

runtime-config 커맨드 라인 인자에는 다음의 두 개의 특수 키를 사용할 수도 있다.

  • api/all: 사용할 수 있는 모든 API를 선택한다.
  • api/legacy: 레거시 API만을 선택한다. 여기서 레거시 API란 명시적으로 사용이 중단된 모든 API를 가리킨다.

예를 들어서, v1을 제외한 모든 API 버전을 비활성화하기 위해서는 kube-apiserver--runtime-config=api/all=false,api/v1=true 인자를 사용한다.

다음 내용

kube-apiserver 컴포넌트에 대한 더 자세한 내용은 다음의 문서 를 참고한다.

3.18 - 쿠버네티스 클러스터에서 sysctl 사용하기

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

이 문서는 쿠버네티스 클러스터에서 sysctl 인터페이스를 사용하여 커널 파라미터를 어떻게 구성하고, 사용하는지를 설명한다.

시작하기 전에

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

일부 단계에서는 실행 중인 클러스터의 kubelet에서 커맨드 라인 옵션을 재구성할 필요가 있다.

모든 sysctl 파라미터 나열

리눅스에서 sysctl 인터페이스는 관리자들이 런타임에 커널 파라미터를 수정할 수 있도록 허용한다. 파라미터는 /proc/sys 가상 파일 시스템을 통해 이용할 수 있다. 파라미터는 다음과 같은 다양한 서브 시스템을 포함한다.

  • 커널 (공통 접두사: kernel.)
  • 네트워크 (공통 접두사: net.)
  • 가상 메모리 (공통 접두사: vm.)
  • MDADM (공통 접두사: dev.)
  • 더 많은 서브 시스템은 커널 문서에 설명되어 있다.

모든 파라미터 리스트를 가져오려면 다음 명령을 실행한다.

sudo sysctl -a

unsafe sysctl 활성화하기

sysctl은 safe sysctl과 unsafe sysctl로 구성되어 있다. safe sysctl은 적절한 네임스페이스 뿐만 아니라 동일한 노드의 파드 사이에 고립 되어야 한다. 즉, 하나의 파드에 safe sysctl을 설정한다는 것은 다음을 의미한다.

  • 노드의 다른 파드에 영향을 미치지 않아야 한다
  • 노드의 상태를 손상시키지 않아야 한다
  • CPU 또는 메모리 리소스가 파드의 리소스 제한에 벗어나는 것을 허용하지 않아야 한다

아직까지 대부분 네임스페이스된 sysctl은 safe sysctl로 고려되지 않았다. 다음 sysctl은 safe 명령을 지원한다.

  • kernel.shm_rmid_forced,
  • net.ipv4.ip_local_port_range,
  • net.ipv4.tcp_syncookies,
  • net.ipv4.ping_group_range (쿠버네티스 1.18 이후),
  • net.ipv4.ip_unprivileged_port_start (쿠버네티스 1.22 이후).

kubelet이 더 고립된 방법을 지원하면 추후 쿠버네티스 버전에서 확장될 것이다.

모든 safe sysctl은 기본적으로 활성화된다.

모든 unsafe sysctl은 기본적으로 비활성화되고, 노드별 기본 클러스터 관리자에 의해 수동으로 메뉴얼로 허용되어야 한다. unsafe sysctl이 비활성화된 파드는 스케줄링되지만, 시작에 실패한다.

위의 경고를 염두에 두고 클러스터 관리자는 고성능 또는 실시간 애플리케이션 조정과 같은 매우 특수한 상황에 대해 특정 unsafe sysctl을 허용할 수 있다. unsafe sysctl은 kubelet 플래그를 사용하여 노드별로 활성화된다. 예를 들면, 다음과 같다.

kubelet --allowed-unsafe-sysctls \
  'kernel.msg*,net.core.somaxconn' ...

Minikube의 경우, extra-config 플래그를 통해 이 작업을 수행할 수 있다.

minikube start --extra-config="kubelet.allowed-unsafe-sysctls=kernel.msg*,net.core.somaxconn"...

네임스페이스 sysctl만 이 방법을 사용할 수 있다.

파드에 대한 sysctl 설정

수많은 sysctl은 최근 리눅스 커널에서 네임스페이스 되어 있다. 이는 노드의 각 파드에 대해 개별적으로 설정할 수 있다는 것이다. 쿠버네티스의 파드 securityContext를 통해 네임스페이스 sysctl만 구성할 수 있다.

다음 sysctls는 네임스페이스로 알려져 있다. 이 목록은 이후 버전의 Linux 커널에서 변경될 수 있다.

  • kernel.shm*,
  • kernel.msg*,
  • kernel.sem,
  • fs.mqueue.*,
  • net.* 아래의 파라미터는 컨테이너 네트워킹 네임스페이스에서 설정할 수 있다. 그러나 예외가 존재한다. (예, net.netfilter.nf_conntrack_maxnet.netfilter.nf_conntrack_expect_max는 컨테이너 네트워킹 네임스페이스에서 설정되지만, 네임스페이스가 없다.)

네임스페이스가 없는 sysctl은 node-level sysctl이라고 부른다. 이를 설정해야 한다면, 각 노드의 OS에서 수동으로 구성하거나 특권있는 컨테이너의 데몬셋을 사용하여야 한다.

네임스페이스 sysctl을 구성하기 위해서 파드 securityContext를 사용한다. securityContext는 동일한 파드의 모든 컨테이너에 적용된다.

이 예시는 safe sysctl kernel.shm_rmid_forced와 두 개의 unsafe sysctl인 net.core.somaxconnkernel.msgmax 를 설정하기 위해 파드 securityContext를 사용한다. 스펙에 따르면 safe sysctl과 unsafe sysctl 간 차이는 없다.

apiVersion: v1
kind: Pod
metadata:
  name: sysctl-example
spec:
  securityContext:
    sysctls:
    - name: kernel.shm_rmid_forced
      value: "0"
    - name: net.core.somaxconn
      value: "1024"
    - name: kernel.msgmax
      value: "65536"
  ...

특별한 sysctl 설정이 있는 노드를 클러스터 내에서 _tainted_로 간주하고 sysctl 설정이 필요한 노드에만 파드를 예약하는 것이 좋다. 이를 구현하려면 쿠버네티스 테인트(taint)와 톨러레이션(toleration) 기능 을 사용하는 것이 좋다.

unsafe sysctl을 명시적으로 활성화하지 않은 노드에서 unsafe sysctl을 사용하는 파드가 시작되지 않는다. node-level sysctl과 마찬가지로 테인트와 톨러레이션 특징 또는 노드 테인트를 사용하여 해당 파드를 오른쪽 노드에 스케줄하는 것을 추천한다.

3.19 - 클러스터 업그레이드

이 페이지는 쿠버네티스 클러스터를 업그레이드하기 위해 따라야 할 단계에 대한 개요를 제공한다.

클러스터를 업그레이드하는 방법은 초기 배포 방법에 의존적이며, 배포 이후 관련된 변경 사항에도 의존적일 수 있다.

고수준으로 살펴 본, 수행 단계

  • 컨트롤 플레인 업그레이드하기
  • 클러스터 내부의 노드를 업그레이드하기
  • kubectl과 같은 클라이언트 업그레이드하기
  • 새로운 쿠버네티스 버전에 동반되는 API 변화에 기반하여, 매니페스트 또는 다른 리소스를 조정하기

시작하기 전에

기존 클러스터가 존재해야 한다. 이 페이지는 쿠버네티스 1.30에서 쿠버네티스 1.31로 업그레이드하는 것에 관해 다룬다. 클러스터에서 쿠버네티스 1.30을 실행하지 않는 경우 업그레이드하려는 쿠버네티스 버전에 대한 설명서를 참고한다.

업그레이드 방법

kubeadm

클러스터가 kubeadm 도구를 사용하여 배포된 경우 클러스터 업그레이드 방법에 대한 자세한 내용은 kubeadm 클러스터 업그레이드를 참조한다.

클러스터를 업그레이드한 후에는 kubectl 최신 버전을 설치해야 한다.

수동 배포

다음 순서에 따라 컨트롤 플레인을 수동으로 업데이트해야 한다.

  • etcd (모든 인스턴스)
  • kube-apiserver (모든 컨트롤 플레인 호스트)
  • kube-controller-manager
  • kube-scheduler
  • cloud controller manager (사용하는 경우)

이 때 kubectl 최신 버전을 설치 해야 한다.

클러스터의 각 노드에 대해 해당 노드를 드레인(drain) 한 다음 1.31 kubelet을 사용하는 새 노드로 바꾸거나 해당 노드의 kubelet을 업그레이드하고 노드를 다시 가동한다.

다른 방식의 배포

사용한 클러스터 배포 도구에 따라 해당 배포 도구가 제공하는 문서를 통하여, 유지 관리를 위해 권장되는 설정 단계를 확인한다.

업그레이드 후 작업

클러스터의 스토리지 API 버전 전환

클러스터에서 활성화된 쿠버네티스 리소스를 클러스터 내부적으로 표현(representation)하기 위해서 etcd로 직렬화된 객체는 특정 버전의 API를 사용하여 작성된다.

지원되는 API가 변경되면 이러한 개체를 새 API에서 다시 작성해야 할 수 있다. 이렇게 하지 않으면 결국 더 이상 디코딩할 수 없거나 쿠버네티스 API 서버에서 사용할 수 없는 리소스가 된다.

영향을 받는 각 객체를 지원되는 최신 API를 사용하여 가져온(fetch) 다음, 해당 API를 사용하여 다시 쓴다.

매니페스트 업그레이드

새로운 쿠버네티스 버전으로 업그레이드하면 새로운 API를 제공할 수 있다.

kubectl convert 명령을 사용하여 서로 다른 API 버전 간에 매니페스트를 변환할 수 있다. 예시:

kubectl convert -f pod.yaml --output-version v1

kubectl 도구는 pod.yaml의 내용을 kind를 파드(변경되지 않음, unchanged)로 설정하는 매니페스트로 대체하고, 수정된 apiVersion으로 대체한다.

장치 플러그인

클러스터가 장치 플러그인을 실행 중이고 노드를 최신 장치 플러그인 API 버전이 있는 쿠버네티스 릴리스로 업그레이드해야 하는 경우, 업그레이드 중에 장치 할당이 계속 성공적으로 완료되도록 하려면 장치 플러그인을 업그레이드해야 한다.

API 호환성Kubelet 장치 매니저 API 버전을 참조한다.

3.20 - 클러스터에서 DNS 서비스 오토스케일

이 페이지는 쿠버네티스 클러스터에서 DNS 서비스의 오토스케일링을 구성하고 활성화하는 방법을 보여준다.

시작하기 전에

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

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

  • 이 가이드는 노드가 AMD64 또는 인텔 64 CPU 아키텍처를 사용한다고 가정한다.

  • Kubernetes DNS가 활성화되어 있는지 확인한다.

DNS 수평 오토스케일링이 이미 활성화되어 있는지 확인

kube-system 네임스페이스 에서 클러스터의 디플로이먼트를 나열한다.

kubectl get deployment --namespace=kube-system

출력은 다음과 유사하다.

NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
...
dns-autoscaler            1/1     1            1           ...
...

해당 출력에서 "dns-autoscaler"가 표시되면, DNS 수평 오토스케일링이 이미 활성화되어 있다는 의미이므로, 오토스케일링 파라미터 조정으로 건너뛰면 된다.

DNS 디플로이먼트 이름 가져오기

kube-system 네임스페이스에서 클러스터의 DNS 디플로이먼트를 나열한다.

kubectl get deployment -l k8s-app=kube-dns --namespace=kube-system

출력은 이와 유사하다.

NAME      READY   UP-TO-DATE   AVAILABLE   AGE
...
coredns   2/2     2            2           ...
...

DNS 서비스용 디플로이먼트가 표시되지 않으면, 이름으로 찾을 수 있다.

kubectl get deployment --namespace=kube-system

그리고 coredns 또는 kube-dns라는 디플로이먼트를 찾는다.

스케일 대상은

Deployment/<your-deployment-name>

이며, 여기서 <your-deployment-name>는 DNS 디플로이먼트의 이름이다. 예를 들어, DNS용 디플로이먼트 이름이 coredns인 경우, 스케일 대상은 Deployment/coredns이다.

DNS 수평 오토스케일링 활성화

이 섹션에서는 새로운 디플로이먼트를 만든다. 디플로이먼트의 파드는 cluster-proportional-autoscaler-amd64 이미지 기반의 컨테이너를 실행한다.

다음의 내용으로 dns-horizontal-autoscaler.yaml라는 파일을 만든다.

kind: ServiceAccount
apiVersion: v1
metadata:
  name: kube-dns-autoscaler
  namespace: kube-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: system:kube-dns-autoscaler
rules:
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["list", "watch"]
  - apiGroups: [""]
    resources: ["replicationcontrollers/scale"]
    verbs: ["get", "update"]
  - apiGroups: ["apps"]
    resources: ["deployments/scale", "replicasets/scale"]
    verbs: ["get", "update"]
# 아래 문제가 해결되면 configmaps 규칙을 제거한다.
# kubernetes-incubator/cluster-proportional-autoscaler#16
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "create"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: system:kube-dns-autoscaler
subjects:
  - kind: ServiceAccount
    name: kube-dns-autoscaler
    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: system:kube-dns-autoscaler
  apiGroup: rbac.authorization.k8s.io

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kube-dns-autoscaler
  namespace: kube-system
  labels:
    k8s-app: kube-dns-autoscaler
    kubernetes.io/cluster-service: "true"
spec:
  selector:
    matchLabels:
      k8s-app: kube-dns-autoscaler
  template:
    metadata:
      labels:
        k8s-app: kube-dns-autoscaler
    spec:
      priorityClassName: system-cluster-critical
      securityContext:
        seccompProfile:
          type: RuntimeDefault
        supplementalGroups: [ 65534 ]
        fsGroup: 65534
      nodeSelector:
        kubernetes.io/os: linux
      containers:
      - name: autoscaler
        image: registry.k8s.io/cpa/cluster-proportional-autoscaler:1.8.4
        resources:
            requests:
                cpu: "20m"
                memory: "10Mi"
        command:
          - /cluster-proportional-autoscaler
          - --namespace=kube-system
          - --configmap=kube-dns-autoscaler
          # 타겟은 cluster/addons/dns/kube-dns.yaml.base와 동기화된 상태로 유지해야 한다.
          
          - --target=<SCALE_TARGET>
          # 클러스터가 대규모 노드(많은 코어가 있는)를 사용하는 경우, "coresPerReplica"가 우선시해야 한다.
          # 작은 노드를 사용하는 경우, "nodesPerReplica"가 우선시해야 한다.
          - --default-params={"linear":{"coresPerReplica":256,"nodesPerReplica":16,"preventSinglePointFailure":true,"includeUnschedulableNodes":true}}
          - --logtostderr=true
          - --v=2
      tolerations:
      - key: "CriticalAddonsOnly"
        operator: "Exists"
      serviceAccountName: kube-dns-autoscaler

파일에서, <SCALE_TARGET>을 사용자의 스케일 대상으로 변경한다.

구성 파일이 포함된 디렉토리로 이동하고, 디플로이먼트를 만들기 위해 다음의 커맨드를 입력한다.

kubectl apply -f dns-horizontal-autoscaler.yaml

성공적인 커맨드의 출력은 다음과 같다.

deployment.apps/dns-autoscaler created

DNS 수평 오토스케일링이 활성화되었다.

DNS 오토스케일링 파라미터 조정

dns-autoscaler 컨피그맵이 있는지 확인한다.

kubectl get configmap --namespace=kube-system

출력은 다음과 유사하다.

NAME                  DATA      AGE
...
dns-autoscaler        1         ...
...

컨피그맵에서 데이터를 수정한다.

kubectl edit configmap dns-autoscaler --namespace=kube-system

다음에 해당하는 줄을 찾는다.

linear: '{"coresPerReplica":256,"min":1,"nodesPerReplica":16}'

필요에 따라서 해당 필드를 수정한다. "min" 필드는 최소 DNS 백엔드 수를 나타낸다. 실제 백엔드의 수는 이 방정식을 사용하여 계산된다.

replicas = max( ceil( cores × 1/coresPerReplica ) , ceil( nodes × 1/nodesPerReplica ) )

coresPerReplicanodesPerReplica 값은 모두 부동 소수점이니 주의한다.

해당 아이디어는 클러스터가 코어가 많은 노드를 사용하는 경우, coresPerReplica의 영향을 더 크게 만들고, 코어 수가 적은 노드를 사용하는 경우 nodesPerReplica의 영향을 더 크게 만드는 것이다.

그밖에 다른 스케일링 패턴도 지원한다. cluster-proportional-autoscaler를 참고한다.

DNS 수평 오토스케일링 비활성화

DNS 수평 오토스케일링을 조정하기 위해 몇 가지 옵션이 있다. 사용할 옵션은 조건에 따라 다르다.

옵션 1: dns-autoscaler 디플로이먼트를 레플리카 0개로 축소

이 옵션은 모든 상황에서 작동한다. 다음 커맨드를 입력한다.

kubectl scale deployment --replicas=0 dns-autoscaler --namespace=kube-system

출력은 다음과 같다.

deployment.apps/dns-autoscaler scaled

레플리카 수가 0인지 확인한다.

kubectl get rs --namespace=kube-system

출력은 DESIRED 및 CURRENT 열에 0으로 보여준다.

NAME                                 DESIRED   CURRENT   READY   AGE
...
dns-autoscaler-6b59789fc8            0         0         0       ...
...

옵션 2: dns-autoscaler 디플로이먼트를 삭제

이 옵션은 dns-autoscaler가 자체적으로 제어되는 경우 작동하며, 아무도 이것을 재-생성하지 않음을 의미한다.

kubectl delete deployment dns-autoscaler --namespace=kube-system

출력은 다음과 같다.

deployment.apps "dns-autoscaler" deleted

옵션 3: 마스터노드에서 dns-autoscaler 매니페스트 파일을 삭제

이 옵션은 dns-autoscaler가 (사용 중단되(deprecated)) 애드온 매니저 의 제어를 받고 마스터 노드에 쓰기 권한이 있는 경우 작동한다.

마스터 노드에 로그인하고 해당 매니페스트 파일을 삭제한다. 이 dns-autoscaler의 일반 경로는 다음과 같다.

/etc/kubernetes/addons/dns-horizontal-autoscaler/dns-horizontal-autoscaler.yaml

매니페스트 파일이 삭제된 후, 애드온 매니저는 dns-autoscaler 디플로이먼트를 삭제한다.

DNS 수평 오토스케일링 작동 방식 이해

  • cluster-proportional-autoscaler 애플리케이션은 DNS 서비스와 별도로 배포된다.

  • 오토스케일러 파드는 클러스터의 노드 및 코어 수에 대해 쿠버네티스 API 서버를 폴링(poll)하는 클라이언트를 실행한다.

  • 의도한 레플리카 수는 주어진 스케일링 파라미터로 계산하고 예약 가능한 노드 및 코어를 기반으로 DNS 백엔드에 적용한다.

  • 스케일링 파마리터와 데이터 포인트는 컨피그맵을 통해 자동 오토스케일러에게 제공된다.그리고, 의도한 최근 스케일링 파라미터로 최신 상태가 되도록 폴링 간격에 따라 파라미터 표를 갱신한다.

  • 오토스케일러 파드를 다시 빌드 또는 재 시작하지 않고도 스케일링 파라미터를 변경할 수 있다.

  • 오토스케일러는 linearladder 두 가지 제어 패턴을 지원하는 컨트롤러 인터페이스를 제공한다.

다음 내용

3.21 - 클러스터에서 캐스케이딩 삭제 사용

이 페이지에서는 가비지 수집 중 클러스터에서 사용할 캐스케이딩 삭제 타입을 지정하는 방법을 보여준다.

시작하기 전에

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

또한 다양한 타입들의 캐스케이딩 삭제를 실험하려면 샘플 디플로이먼트를 생성할 필요가 있다. 각 타입에 대해 디플로이먼트를 다시 생성해야 할 수도 있다.

파드에서 소유자 참조 확인

파드에서 ownerReferences 필드가 존재하는지 확인한다.

kubectl get pods -l app=nginx --output=yaml

출력은 다음과 같이 ownerReferences 필드를 가진다.

apiVersion: v1
    ...
    ownerReferences:
    - apiVersion: apps/v1
      blockOwnerDeletion: true
      controller: true
      kind: ReplicaSet
      name: nginx-deployment-6b474476c4
      uid: 4fdcd81c-bd5d-41f7-97af-3a3b759af9a7
    ...

포그라운드(foreground) 캐스케이딩 삭제 사용

기본적으로 쿠버네티스는 종속 오브젝트를 삭제하기 위해서 백그라운드 캐스케이딩 삭제를 사용한다. 클러스터를 실행하는 쿠버네티스 버전에 따라 kubectl 또는 쿠버네티스 API를 사용해 포그라운드 캐스케이딩 삭제로 전환할 수 있다. 버전 확인을 위해서, 다음 커맨드를 실행 kubectl version.

kubectl 또는 쿠버네티스 API를 사용해 포그라운드 캐스케이딩 삭제로 오브젝트들을 삭제할 수 있다.

kubectl 사용

다음 명령어를 실행한다.

kubectl delete deployment nginx-deployment --cascade=foreground

쿠버네티스 API 사용

  1. 로컬 프록시 세션을 시작한다.

    kubectl proxy --port=8080
    
  2. 삭제를 작동시키기 위해 curl을 사용한다.

    curl -X DELETE localhost:8080/apis/apps/v1/namespaces/default/deployments/nginx-deployment \
        -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Foreground"}' \
        -H "Content-Type: application/json"
    

    출력에는 다음과 같이 foregroundDeletion 파이널라이저(finalizer)가 포함되어 있다.

    "kind": "Deployment",
    "apiVersion": "apps/v1",
    "metadata": {
        "name": "nginx-deployment",
        "namespace": "default",
        "uid": "d1ce1b02-cae8-4288-8a53-30e84d8fa505",
        "resourceVersion": "1363097",
        "creationTimestamp": "2021-07-08T20:24:37Z",
        "deletionTimestamp": "2021-07-08T20:27:39Z",
        "finalizers": [
          "foregroundDeletion"
        ]
        ...
    

백그라운드 캐스케이딩 삭제 사용

  1. 샘플 디플로이먼트를 생성한다.
  2. 클러스터를 실행하는 쿠버네티스 버전에 따라 디플로이먼트를 삭제하기 위해 kubectl 또는 쿠버네티스 API를 사용한다. 버전 확인을 위해서, 다음 커맨드를 실행 kubectl version.

kubectl 또는 쿠버네티스 API를 사용해 백그라운드 캐스케이딩 삭제로 오브젝트들을 삭제할 수 있다.

쿠버네티스는 기본적으로 백그라운드 캐스케이딩 삭제를 사용하므로, --cascade 플래그 또는 propagationPolicy 인수 없이 다음 명령을 실행해도 같은 작업을 수행한다.

kubectl 사용

다음 명령어를 실행한다.

kubectl delete deployment nginx-deployment --cascade=background

쿠버네티스 API 사용

  1. 로컬 프록시 세션을 시작한다.

    kubectl proxy --port=8080
    
  2. 삭제를 작동시키기 위해 curl을 사용한다.

    curl -X DELETE localhost:8080/apis/apps/v1/namespaces/default/deployments/nginx-deployment \
        -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Background"}' \
        -H "Content-Type: application/json"
    

    출력은 다음과 유사하다.

    "kind": "Status",
    "apiVersion": "v1",
    ...
    "status": "Success",
    "details": {
        "name": "nginx-deployment",
        "group": "apps",
        "kind": "deployments",
        "uid": "cc9eefb9-2d49-4445-b1c1-d261c9396456"
    }
    

소유자 오브젝트 및 종속된 고아(orphan) 오브젝트 삭제

기본적으로, 쿠버네티스에 오브젝트를 삭제하도록 지시하면 컨트롤러는 종속 오브젝트들도 제거한다. 클러스터를 실행하는 쿠버네티스 버전에 따라 kubectl 또는 쿠버네티스 API를 사용해 종속 오브젝트를 쿠버네티스 고아로 만들 수 있다. 버전 확인을 위해서, 다음 커맨드를 실행 kubectl version.

kubectl 사용

다음 명령어를 실행한다.

kubectl delete deployment nginx-deployment --cascade=orphan

쿠버네티스 API 사용

  1. 로컬 프록시 세션을 시작한다.

    kubectl proxy --port=8080
    
  2. 삭제를 작동시키기 위해 curl을 사용한다.

    curl -X DELETE localhost:8080/apis/apps/v1/namespaces/default/deployments/nginx-deployment \
        -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Orphan"}' \
        -H "Content-Type: application/json"
    

    출력에는 다음과 같이 finalizers 필드에 orphan이 포함되어 있다.

    "kind": "Deployment",
    "apiVersion": "apps/v1",
    "namespace": "default",
    "uid": "6f577034-42a0-479d-be21-78018c466f1f",
    "creationTimestamp": "2021-07-09T16:46:37Z",
    "deletionTimestamp": "2021-07-09T16:47:08Z",
    "deletionGracePeriodSeconds": 0,
    "finalizers": [
      "orphan"
    ],
    ...
    

디플로이먼트가 관리하는 파드들이 계속 실행 중인지 확인할 수 있다.

kubectl get pods -l app=nginx

다음 내용

3.22 - 퍼시스턴트볼륨 반환 정책 변경하기

이 페이지는 쿠버네티스 퍼시트턴트볼륨(PersistentVolume)의 반환 정책을 변경하는 방법을 보여준다.

시작하기 전에

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

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

왜 퍼시스턴트볼륨 반환 정책을 변경하는가?

퍼시스턴트볼륨은 "Retain(보존)", "Recycle(재활용)", "Delete(삭제)" 를 포함한 다양한 반환 정책을 갖는다. 동적으로 프로비저닝 된 퍼시스턴트볼륨의 경우 기본 반환 정책은 "Delete" 이다. 이는 사용자가 해당 PersistentVolumeClaim 을 삭제하면, 동적으로 프로비저닝 된 볼륨이 자동적으로 삭제됨을 의미한다. 볼륨에 중요한 데이터가 포함된 경우, 이러한 자동 삭제는 부적절 할 수 있다. 이 경우에는, "Retain" 정책을 사용하는 것이 더 적합하다. "Retain" 정책에서, 사용자가 퍼시스턴트볼륨클레임을 삭제할 경우 해당하는 퍼시스턴트볼륨은 삭제되지 않는다. 대신, Released 단계로 이동되어, 모든 데이터를 수동으로 복구할 수 있다.

퍼시스턴트볼륨 반환 정책 변경하기

  1. 사용자의 클러스터에서 퍼시스턴트볼륨을 조회한다.

    kubectl get pv
    

    결과는 아래와 같다.

    NAME                                       CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM             STORAGECLASS     REASON    AGE
    pvc-b6efd8da-b7b5-11e6-9d58-0ed433a7dd94   4Gi        RWO           Delete          Bound     default/claim1    manual                     10s
    pvc-b95650f8-b7b5-11e6-9d58-0ed433a7dd94   4Gi        RWO           Delete          Bound     default/claim2    manual                     6s
    pvc-bb3ca71d-b7b5-11e6-9d58-0ed433a7dd94   4Gi        RWO           Delete          Bound     default/claim3    manual                     3s
    

    이 목록은 동적으로 프로비저닝 된 볼륨을 쉽게 식별할 수 있도록 각 볼륨에 바인딩 되어 있는 퍼시스턴트볼륨클레임(PersistentVolumeClaim)의 이름도 포함한다.

  2. 사용자의 퍼시스턴트볼륨 중 하나를 선택한 후에 반환 정책을 변경한다.

    kubectl patch pv <your-pv-name> -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
    

    여기서 <your-pv-name> 는 사용자가 선택한 퍼시스턴트볼륨의 이름이다.

  3. 선택한 PersistentVolume이 올바른 정책을 갖는지 확인한다.

    kubectl get pv
    

    결과는 아래와 같다.

    NAME                                       CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM             STORAGECLASS     REASON    AGE
    pvc-b6efd8da-b7b5-11e6-9d58-0ed433a7dd94   4Gi        RWO           Delete          Bound     default/claim1    manual                     40s
    pvc-b95650f8-b7b5-11e6-9d58-0ed433a7dd94   4Gi        RWO           Delete          Bound     default/claim2    manual                     36s
    pvc-bb3ca71d-b7b5-11e6-9d58-0ed433a7dd94   4Gi        RWO           Retain          Bound     default/claim3    manual                     33s
    

    위 결과에서, default/claim3 클레임과 바인딩 되어 있는 볼륨이 Retain 반환 정책을 갖는 것을 볼 수 있다. 사용자가 default/claim3 클레임을 삭제할 경우, 볼륨은 자동으로 삭제 되지 않는다.

다음 내용

레퍼런스

4 - 파드와 컨테이너 설정

파드와 컨테이너에 대한 공통 구성 태스크들을 수행한다.

4.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

다음 내용

앱 개발자들을 위한

클러스터 관리자들을 위한

4.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.

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

다음 내용

4.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.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 요청량보다 크도록 설정한다면 다음 두 가지를 달