1 - 구성 모범 사례
이 문서는 사용자 가이드, 시작하기 문서 및 예제들에 걸쳐 소개된 구성 모범 사례를 강조하고 통합한다.
이 문서는 지속적으로 변경 가능하다. 이 목록에 없지만 다른 사람들에게 유용할 것 같은 무엇인가를 생각하고 있다면, 새로운 이슈를 생성하거나 풀 리퀘스트를 제출하는 것을 망설이지 말기를 바란다.
일반적인 구성 팁
구성을 정의할 때, 안정된 최신 API 버전을 명시한다.
구성 파일들은 클러스터에 적용되기 전에 버전 컨트롤에 저장되어 있어야 한다. 이는 만약 필요하다면 구성의 변경 사항을 빠르게 되돌릴 수 있도록 해준다. 이는 또한 클러스터의 재-생성과 복원을 도와준다.
JSON보다는 YAML을 사용해 구성 파일을 작성한다. 비록 이러한 포맷들은 대부분의 모든 상황에서 통용되어 사용될 수 있지만, YAML이 좀 더 사용자 친화적인 성향을 가진다.
의미상 맞다면 가능한 연관된 오브젝트들을 하나의 파일에 모아 놓는다. 때로는 여러 개의 파일보다 하나의 파일이 더 관리하기 쉽다. 이 문법의 예시로서 guestbook-all-in-one.yaml 파일을 참고한다.
많은
kubectl
커맨드들은 디렉터리에 대해 호출될 수 있다. 예를 들어, 구성 파일들의 디렉터리에 대해kubectl apply
를 호출할 수 있다.불필요하게 기본 값을 명시하지 않는다. 간단하고 최소한의 설정은 에러를 덜 발생시킨다.
더 나은 인트로스펙션(introspection)을 위해서, 어노테이션에 오브젝트의 설명을 넣는다.
"단독(Naked)" 파드 vs 레플리카셋(ReplicaSet), 디플로이먼트(Deployment), 그리고 잡(Job)
가능하다면 단독 파드(즉, 레플리카셋이나 디플로이먼트에 연결되지 않은 파드)를 사용하지 않는다. 단독 파드는 노드 장애 이벤트가 발생해도 다시 스케줄링되지 않는다.
명백하게
restartPolicy: Never
를 사용하는 상황을 제외한다면, 의도한 파드의 수가 항상 사용 가능한 상태를 유지하는 레플리카셋을 생성하고, 파드를 교체하는 전략(롤링 업데이트와 같은)을 명시하는 디플로이먼트는 파드를 직접 생성하기 위해 항상 선호되는 방법이다. 잡 또한 적절할 수 있다.
서비스
서비스에 대응하는 백엔드 워크로드(디플로이먼트 또는 레플리카셋) 또는 서비스 접근이 필요한 어떠한 워크로드를 생성하기 전에 서비스를 미리 생성한다. 쿠버네티스가 컨테이너를 시작할 때, 쿠버네티스는 컨테이너 시작 당시에 생성되어 있는 모든 서비스를 가리키는 환경 변수를 컨테이너에 제공한다. 예를 들어,
foo
라는 이름의 서비스가 존재한다면, 모든 컨테이너들은 초기 환경에서 다음의 변수들을 얻을 것이다.FOO_SERVICE_HOST=<서비스가 동작 중인 호스트> FOO_SERVICE_PORT=<서비스가 동작 중인 포트>
이는 순서를 정하는 일이 요구됨을 암시한다 -
파드
가 접근하기를 원하는 어떠한서비스
는파드
스스로가 생성되기 전에 미리 생성되어 있어야 하며, 그렇지 않으면 환경 변수가 설정되지 않을 것이다. DNS는 이러한 제한을 가지고 있지 않다.선택적인(그렇지만 매우 권장되는) 클러스터 애드온은 DNS 서버이다. DNS 서버는 새로운
서비스
를 위한 쿠버네티스 API를 Watch하며, 각 서비스를 위한 DNS 레코드 셋을 생성한다. 만약 DNS가 클러스터에 걸쳐 활성화되어 있다면, 모든파드
는서비스
의 이름을 자동으로 해석할 수 있어야 한다.반드시 필요한 것이 아니라면 파드에
hostPort
를 명시하지 않는다. <hostIP
,hostPort
,protocol
> 조합은 유일해야 하기 때문에,hostPort
로 바인드하는 것은 파드가 스케줄링될 수 있는 위치의 개수를 제한한다. 만약hostIP
와protocol
을 뚜렷히 명시하지 않으면, 쿠버네티스는hostIP
의 기본 값으로0.0.0.0
를,protocol
의 기본 값으로TCP
를 사용한다.만약 오직 디버깅의 목적으로 포트에 접근해야 한다면, apiserver proxy 또는
kubectl port-forward
를 사용할 수 있다.만약 파드의 포트를 노드에서 명시적으로 노출해야 한다면,
hostPort
에 의존하기 전에 NodePort 서비스를 사용하는 것을 고려할 수 있다.hostPort
와 같은 이유로,hostNetwork
를 사용하는 것을 피한다.kube-proxy
로드 밸런싱이 필요하지 않을 때, 서비스 발견을 위해 헤드리스 서비스(ClusterIP
의 값을None
으로 가지는)를 사용한다.
레이블 사용하기
{ app: myapp, tier: frontend, phase: test, deployment: v3 }
처럼 애플리케이션이나 디플로이먼트의 속성에 대한 의미 를 식별하는 레이블을 정의해 사용한다. 다른 리소스를 위해 적절한 파드를 선택하는 용도로 이러한 레이블을 이용할 수 있다. 예를 들어, 모든tier: frontend
파드를 선택하거나,app: myapp
의 모든phase: test
컴포넌트를 선택하는 서비스를 생각해 볼 수 있다. 이 접근 방법의 예시는 방명록 앱을 참고한다.
릴리스에 특정되는 레이블을 서비스의 셀렉터에서 생략함으로써 여러 개의 디플로이먼트에 걸치는 서비스를 생성할 수 있다. 동작 중인 서비스를 다운타임 없이 갱신하려면, 디플로이먼트를 사용한다.
오브젝트의 의도한 상태는 디플로이먼트에 의해 기술되며, 만약 그 스펙에 대한 변화가 적용될 경우, 디플로이먼트 컨트롤러는 일정한 비율로 실제 상태를 의도한 상태로 변화시킨다.
일반적인 활용 사례인 경우 쿠버네티스 공통 레이블을 사용한다. 이 표준화된 레이블은
kubectl
및 대시보드와 같은 도구들이 상호 운용이 가능한 방식으로 동작할 수 있도록 메타데이터를 향상시킨다.디버깅을 위해 레이블을 조작할 수 있다. (레플리카셋과 같은) 쿠버네티스 컨트롤러와 서비스는 셀렉터 레이블을 사용해 파드를 선택하기 때문에, 관련된 레이블을 파드에서 삭제하는 것은 컨트롤러로부터 관리되거나 서비스로부터 트래픽을 전달받는 것을 중단시킨다. 만약 이미 존재하는 파드의 레이블을 삭제한다면, 파드의 컨트롤러는 그 자리를 대신할 새로운 파드를 생성한다. 이것은 이전에 "살아 있는" 파드를 "격리된" 환경에서 디버그할 수 있는 유용한 방법이다. 레이블을 상호적으로 추가하고 삭제하기 위해서,
kubectl label
를 사용할 수 있다.
컨테이너 이미지
imagePullPolicy와 이미지의 태그는 kubelet이 명시된 이미지를 풀(pull) 하려고 시도할 때 영향을 미친다.
imagePullPolicy: IfNotPresent
: 이미지가 로컬에 이미 존재하지 않으면 이미지가 풀(Pull) 된다.imagePullPolicy: Always
: kubelet이 컨테이너를 시작할 때마다, kubelet은 컨테이너 이미지 레지스트리를 쿼리해서 이름을 이미지 다이제스트(digest)로 확인한다. kubelet에 정확한 다이제스트가 저장된 컨테이너 이미지가 로컬로 캐시된 경우, kubelet은 캐시된 이미지를 사용한다. 그렇지 않으면, kubelet은 확인한 다이제스트를 사용해서 이미지를 다운로드(pull)하고, 해당 이미지를 사용해서 컨테이너를 시작한다.imagePullPolicy
가 생략되어 있고, 이미지 태그가:latest
이거나 생략되어 있다면imagePullPolicy
는 자동으로Always
가 적용된다. 태그 값을 변경하더라도 이 값은IfNotPresent
로 업데이트 되지 않는다.imagePullPolicy
가 생략되어 있고, 이미지 태그가 존재하지만:latest
가 아니라면imagePullPolicy
는 자동으로IfNotPresent
가 적용된다. 태그가 나중에 제거되거나:latest
로 변경되더라도Always
로 업데이트 되지 않는다.imagePullPolicy: Never
: 이미지가 로컬에 존재한다고 가정한다. 이미지를 풀(Pull) 하기 위해 시도하지 않는다.
참고: 컨테이너가 항상 같은 버전의 이미지를 사용하도록 하기 위해,<이미지 이름>:<태그>
를<이미지 이름>@<다이제스트>
(예시image@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2
)로 변경해서 이미지의 다이제스트를 명시할 수 있다. 다이제스트는 특정 버전의 이미지를 고유하게 식별하며, 다이제스트 값을 변경하지 않는 한 쿠버네티스에 의해 절대로 변경되지 않는다.
참고: 운영 환경에서 컨테이너를 생성할 때:latest
태그의 사용을 피하는 것이 좋은데, 이는 어떠한 버전의 이미지가 실행 중인지 추적하기가 어렵고, 적절히 롤백하기가 더 어려워지기 때문이다.
참고: 레지스트리가 안정적으로 동작하는 상황에서는,imagePullPolicy: Always
로 설정되어 있더라도 기반 이미지 관리 도구의 캐싱 정책을 통해 이미지 풀(pull) 작업의 효율성을 높일 수 있다. 예를 들어, 도커를 사용하는 경우 이미지가 이미 존재한다면 풀(Pull) 시도는 빠르게 진행되는데, 이는 모든 이미지 레이어가 캐시되어 있으며 이미지 다운로드가 필요하지 않기 때문이다.
kubectl 사용하기
kubectl apply -f <디렉터리>
를 사용한다. 이 명령어는<디렉터리>
내부의 모든.yaml
,.yml
, 그리고.json
쿠버네티스 구성 파일을 찾아apply
에 전달한다.get
과delete
동작을 위해 특정 오브젝트의 이름 대신 레이블 셀렉터를 사용한다. 레이블 셀렉터와 효율적으로 레이블 사용하기를 참고할 수 있다.단일 컨테이너로 구성된 디플로이먼트와 서비스를 빠르게 생성하기 위해
kubectl create deployment
와kubectl expose
를 사용한다. 클러스터 내부의 애플리케이션에 접근하기 위한 서비스 사용에서 예시를 확인할 수 있다.
2 - 컨피그맵(ConfigMap)
컨피그맵은 키-값 쌍으로 기밀이 아닌 데이터를 저장하는 데 사용하는 API 오브젝트이다. 파드는 볼륨에서 환경 변수, 커맨드-라인 인수 또는 구성 파일로 컨피그맵을 사용할 수 있다.
컨피그맵을 사용하면 컨테이너 이미지에서 환경별 구성을 분리하여, 애플리케이션을 쉽게 이식할 수 있다.
주의: 컨피그맵은 보안 또는 암호화를 제공하지 않는다. 저장하려는 데이터가 기밀인 경우, 컨피그맵 대신 시크릿(Secret) 또는 추가(써드파티) 도구를 사용하여 데이터를 비공개로 유지하자.
사용 동기
애플리케이션 코드와 별도로 구성 데이터를 설정하려면 컨피그맵을 사용하자.
예를 들어, 자신의 컴퓨터(개발용)와 클라우드(실제 트래픽 처리)에서
실행할 수 있는 애플리케이션을 개발한다고 가정해보자.
DATABASE_HOST
라는 환경 변수를 찾기 위해 코드를 작성한다.
로컬에서는 해당 변수를 localhost
로 설정한다. 클라우드에서는, 데이터베이스
컴포넌트를 클러스터에 노출하는 쿠버네티스 서비스를
참조하도록 설정한다.
이를 통해 클라우드에서 실행 중인 컨테이너 이미지를 가져와
필요한 경우 정확히 동일한 코드를 로컬에서 디버깅할 수 있다.
컨피그맵은 많은 양의 데이터를 보유하도록 설계되지 않았다. 컨피그맵에 저장된 데이터는 1MiB를 초과할 수 없다. 이 제한보다 큰 설정을 저장해야 하는 경우, 볼륨을 마운트하는 것을 고려하거나 별도의 데이터베이스 또는 파일 서비스를 사용할 수 있다.
컨피그맵 오브젝트
컨피그맵은 다른 오브젝트가 사용할 구성을 저장할 수 있는
API 오브젝트이다.
spec
이 있는 대부분의 쿠버네티스 오브젝트와 달리, 컨피그맵에는 data
및 binaryData
필드가 있다. 이러한 필드는 키-값 쌍을 값으로 허용한다. data
필드와
binaryData
는 모두 선택 사항이다. data
필드는
UTF-8 바이트 시퀀스를 포함하도록 설계되었으며 binaryData
필드는 바이너리
데이터를 base64로 인코딩된 문자열로 포함하도록 설계되었다.
컨피그맵의 이름은 유효한 DNS 서브도메인 이름이어야 한다.
data
또는 binaryData
필드 아래의 각 키는
영숫자 문자, -
, _
또는 .
으로 구성되어야 한다. data
에 저장된 키는
binaryData
필드의 키와 겹치지 않아야 한다.
v1.19부터 컨피그맵 정의에 immutable
필드를 추가하여
변경할 수 없는 컨피그맵을 만들 수 있다.
컨피그맵과 파드
컨피그맵을 참조하는 파드 spec
을 작성하고 컨피그맵의 데이터를
기반으로 해당 파드의 컨테이너를 구성할 수 있다. 파드와 컨피그맵은
동일한 네임스페이스에 있어야 한다.
다음은 단일 값을 가진 키와, 값이 구성 형식의 일부처럼 보이는 키를 가진 컨피그맵의 예시이다.
apiVersion: v1
kind: ConfigMap
metadata:
name: game-demo
data:
# 속성과 비슷한 키; 각 키는 간단한 값으로 매핑됨
player_initial_lives: "3"
ui_properties_file_name: "user-interface.properties"
# 파일과 비슷한 키
game.properties: |
enemy.types=aliens,monsters
player.maximum-lives=5
user-interface.properties: |
color.good=purple
color.bad=yellow
allow.textmode=true
컨피그맵을 사용하여 파드 내부에 컨테이너를 구성할 수 있는 네 가지 방법이 있다.
- 컨테이너 커맨드와 인수 내에서
- 컨테이너에 대한 환경 변수
- 애플리케이션이 읽을 수 있도록 읽기 전용 볼륨에 파일 추가
- 쿠버네티스 API를 사용하여 컨피그맵을 읽는 파드 내에서 실행할 코드 작성
이러한 방법들은 소비되는 데이터를 모델링하는 방식에 따라 다르게 쓰인다. 처음 세 가지 방법의 경우, kubelet은 파드의 컨테이너를 시작할 때 컨피그맵의 데이터를 사용한다.
네 번째 방법은 컨피그맵과 데이터를 읽기 위해 코드를 작성해야 한다는 것을 의미한다. 그러나, 쿠버네티스 API를 직접 사용하기 때문에, 애플리케이션은 컨피그맵이 변경될 때마다 업데이트를 받기 위해 구독할 수 있고, 업데이트가 있으면 반응한다. 쿠버네티스 API에 직접 접근하면, 이 기술을 사용하여 다른 네임스페이스의 컨피그맵에 접근할 수도 있다.
다음은 game-demo
의 값을 사용하여 파드를 구성하는 파드 예시이다.
apiVersion: v1
kind: Pod
metadata:
name: configmap-demo-pod
spec:
containers:
- name: demo
image: alpine
command: ["sleep", "3600"]
env:
# 환경 변수 정의
- name: PLAYER_INITIAL_LIVES # 참고로 여기서는 컨피그맵의 키 이름과
# 대소문자가 다르다.
valueFrom:
configMapKeyRef:
name: game-demo # 이 값의 컨피그맵.
key: player_initial_lives # 가져올 키.
- name: UI_PROPERTIES_FILE_NAME
valueFrom:
configMapKeyRef:
name: game-demo
key: ui_properties_file_name
volumeMounts:
- name: config
mountPath: "/config"
readOnly: true
volumes:
# 파드 레벨에서 볼륨을 설정한 다음, 해당 파드 내의 컨테이너에 마운트한다.
- name: config
configMap:
# 마운트하려는 컨피그맵의 이름을 제공한다.
name: game-demo
# 컨피그맵에서 파일로 생성할 키 배열
items:
- key: "game.properties"
path: "game.properties"
- key: "user-interface.properties"
path: "user-interface.properties"
컨피그맵은 단일 라인 속성(single line property) 값과 멀티 라인의 파일과 비슷한(multi-line file-like) 값을 구분하지 않는다. 더 중요한 것은 파드와 다른 오브젝트가 이러한 값을 소비하는 방식이다.
이 예제에서, 볼륨을 정의하고 demo
컨테이너에
/config
로 마운트하면 컨피그맵에 4개의 키가 있더라도
/config/game.properties
와 /config/user-interface.properties
2개의 파일이 생성된다. 이것은 파드 정의가
volume
섹션에서 items
배열을 지정하기 때문이다.
items
배열을 완전히 생략하면, 컨피그맵의 모든 키가
키와 이름이 같은 파일이 되고, 4개의 파일을 얻게 된다.
컨피그맵 사용하기
컨피그맵은 데이터 볼륨으로 마운트할 수 있다. 컨피그맵은 파드에 직접적으로 노출되지 않고, 시스템의 다른 부분에서도 사용할 수 있다. 예를 들어, 컨피그맵은 시스템의 다른 부분이 구성을 위해 사용해야 하는 데이터를 보유할 수 있다.
컨피그맵을 사용하는 가장 일반적인 방법은 동일한 네임스페이스의 파드에서 실행되는 컨테이너에 대한 설정을 구성하는 것이다. 컨피그맵을 별도로 사용할 수도 있다.
예를 들어, 컨피그맵에 기반한 동작을 조정하는 애드온이나 오퍼레이터를 사용할 수도 있다.
파드에서 컨피그맵을 파일로 사용하기
파드의 볼륨에서 컨피그맵을 사용하려면 다음을 수행한다.
- 컨피그맵을 생성하거나 기존 컨피그맵을 사용한다. 여러 파드가 동일한 컨피그맵을 참조할 수 있다.
- 파드 정의를 수정해서
.spec.volumes[]
아래에 볼륨을 추가한다. 볼륨 이름은 원하는 대로 정하고, 컨피그맵 오브젝트를 참조하도록.spec.volumes[].configMap.name
필드를 설정한다. - 컨피그맵이 필요한 각 컨테이너에
.spec.containers[].volumeMounts[]
를 추가한다..spec.containers[].volumeMounts[].readOnly = true
를 설정하고 컨피그맵이 연결되기를 원하는 곳에 사용하지 않은 디렉터리 이름으로.spec.containers[].volumeMounts[].mountPath
를 지정한다. - 프로그램이 해당 디렉터리에서 파일을 찾도록 이미지 또는 커맨드 라인을
수정한다. 컨피그맵의
data
맵 각 키는mountPath
아래의 파일 이름이 된다.
다음은 볼륨에 컨피그맵을 마운트하는 파드의 예시이다.
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: redis
volumeMounts:
- name: foo
mountPath: "/etc/foo"
readOnly: true
volumes:
- name: foo
configmap:
name: myconfigmap
사용하려는 각 컨피그맵은 .spec.volumes
에서 참조해야 한다.
파드에 여러 컨테이너가 있는 경우 각 컨테이너에는 자체 volumeMounts
블록이 필요하지만,
컨피그맵은 각 컨피그맵 당 하나의 .spec.volumes
만 필요하다.
마운트된 컨피그맵이 자동으로 업데이트
현재 볼륨에서 사용된 컨피그맵이 업데이트되면, 프로젝션된 키도 마찬가지로 업데이트된다.
kubelet은 모든 주기적인 동기화에서 마운트된 컨피그맵이 최신 상태인지 확인한다.
그러나, kubelet은 로컬 캐시를 사용해서 컨피그맵의 현재 값을 가져온다.
캐시 유형은 KubeletConfiguration 구조체의
ConfigMapAndSecretChangeDetectionStrategy
필드를 사용해서 구성할 수 있다.
컨피그맵은 watch(기본값), ttl 기반 또는 API 서버로 직접
모든 요청을 리디렉션할 수 있다.
따라서 컨피그맵이 업데이트되는 순간부터 새 키가 파드에 업데이트되는 순간까지의
총 지연시간은 kubelet 동기화 기간 + 캐시 전파 지연만큼 길 수 있다. 여기서 캐시
전파 지연은 선택한 캐시 유형에 따라 달라질 수 있다(전파
지연을 지켜보거나, 캐시의 ttl 또는 0에 상응함).
환경 변수로 사용되는 컨피그맵은 자동으로 업데이트되지 않으며 파드를 다시 시작해야 한다.
변경할 수 없는(immutable) 컨피그맵
Kubernetes v1.19 [beta]
쿠버네티스 베타 기능인 변경할 수 없는 시크릿과 컨피그맵 은 개별 시크릿과 컨피그맵을 변경할 수 없는 것으로 설정하는 옵션을 제공한다. 컨피그맵을 광범위하게 사용하는 클러스터(최소 수만 개의 고유한 컨피그맵이 파드에 마운트)의 경우 데이터 변경을 방지하면 다음과 같은 이점이 있다.
- 애플리케이션 중단을 일으킬 수 있는 우발적(또는 원하지 않는) 업데이트로부터 보호
- immutable로 표시된 컨피그맵에 대한 감시를 중단하여, kube-apiserver의 부하를 크게 줄임으로써 클러스터의 성능을 향상시킴
이 기능은 ImmutableEphemeralVolumes
기능 게이트에 의해 제어된다.
immutable
필드를 true
로 설정하여 변경할 수 없는 컨피그맵을 생성할 수 있다.
다음은 예시이다.
apiVersion: v1
kind: ConfigMap
metadata:
...
data:
...
immutable: true
컨피그맵을 immutable로 표시하면, 이 변경 사항을 되돌리거나
data
또는 binaryData
필드 내용을 변경할 수 없다. 컨피그맵만
삭제하고 다시 작성할 수 있다. 기존 파드는 삭제된 컨피그맵에 대한 마운트 지점을
유지하므로, 이러한 파드를 다시 작성하는 것을 권장한다.
다음 내용
- 시크릿에 대해 읽어본다.
- 컨피그맵을 사용하도록 파드 구성하기를 읽어본다.
- 코드를 구성에서 분리하려는 동기를 이해하려면 Twelve-Factor 앱을 읽어본다.
3 - 시크릿(Secret)
쿠버네티스 시크릿을 사용하면 비밀번호, OAuth 토큰, ssh 키와 같은 민감한 정보를 저장하고 관리할 수 있다. 기밀 정보를 시크릿에 저장하는 것이 파드(Pod) 정의나 컨테이너 이미지 내에 그대로 두는 것보다 안전하고 유연하다. 자세한 내용은 시크릿 디자인 문서를 참고한다.
시크릿은 암호, 토큰 또는 키와 같은 소량의 중요한 데이터를 포함하는 오브젝트이다. 그렇지 않으면 이러한 정보가 파드 명세나 이미지에 포함될 수 있다. 사용자는 시크릿을 만들 수 있고 시스템도 일부 시크릿을 만들 수 있다.
주의:쿠버네티스 시크릿은 기본적으로 암호화되지 않은 base64 인코딩 문자열로 저장된다. 기본적으로 API 액세스 권한이 있는 모든 사용자 또는 쿠버네티스의 기본 데이터 저장소 etcd에 액세스할 수 있는 모든 사용자가 일반 텍스트로 검색할 수 있다. 시크릿을 안전하게 사용하려면 (최소한) 다음과 같이 하는 것이 좋다.
- 시크릿에 대한 암호화 활성화.
- 시크릿 읽기 및 쓰기를 제한하는 RBAC 규칙 활성화 또는 구성. 파드를 만들 권한이 있는 모든 사용자는 시크릿을 암묵적으로 얻을 수 있다.
시크릿 개요
시크릿을 사용하려면, 파드가 시크릿을 참조해야 한다. 시크릿은 세 가지 방법으로 파드와 함께 사용할 수 있다.
- 하나 이상의 컨테이너에 마운트된 볼륨 내의 파일로써 사용.
- 컨테이너 환경 변수로써 사용.
- 파드의 이미지를 가져올 때 kubelet에 의해 사용.
시크릿 오브젝트의 이름은 유효한
DNS 서브도메인 이름이어야 한다.
사용자는 시크릿을 위한 파일을 구성할 때 data
및 (또는) stringData
필드를
명시할 수 있다. 해당 data
와 stringData
필드는 선택적으로 명시할 수 있다.
data
필드의 모든 키(key)에 해당하는 값(value)은 base64로 인코딩된 문자열이어야 한다.
만약 사용자에게 base64로의 문자열 변환이 적합하지 않다면,
임의의 문자열을 값으로 받는 stringData
필드를 대신 사용할 수 있다.
data
및 stringData
의 키는 영숫자 문자,
-
, _
, 또는 .
으로 구성되어야 한다. stringData
필드의 모든 키-값 쌍은 의도적으로
data
필드로 합쳐진다. 만약 키가 data
와 stringData
필드 모두에 정의되어
있으면, stringData
필드에 지정된 값이
우선적으로 사용된다.
시크릿 타입
시크릿을 생성할 때, Secret
리소스의 type
필드를 사용하거나, (활용 가능하다면) kubectl
의
유사한 특정 커맨드라인 플래그를 사용하여 시크릿의 타입을 명시할 수 있다.
시크릿 타입은 시크릿 데이터의 프로그래믹 처리를 촉진시키기 위해 사용된다.
쿠버네티스는 일반적인 사용 시나리오를 위해 몇 가지 빌트인 타입을 제공한다. 이 타입은 쿠버네티스가 부과하여 수행되는 검증 및 제약에 따라 달라진다.
빌트인 타입 | 사용처 |
---|---|
Opaque | 임의의 사용자 정의 데이터 |
kubernetes.io/service-account-token | 서비스 어카운트 토큰 |
kubernetes.io/dockercfg | 직렬화 된(serialized) ~/.dockercfg 파일 |
kubernetes.io/dockerconfigjson | 직렬화 된 ~/.docker/config.json 파일 |
kubernetes.io/basic-auth | 기본 인증을 위한 자격 증명(credential) |
kubernetes.io/ssh-auth | SSH를 위한 자격 증명 |
kubernetes.io/tls | TLS 클라이언트나 서버를 위한 데이터 |
bootstrap.kubernetes.io/token | 부트스트랩 토큰 데이터 |
사용자는 시크릿 오브젝트의 type
값에 비어 있지 않은 문자열을 할당하여 자신만의 시크릿
타입을 정의하고 사용할 수 있다. 비어 있는 문자열은 Opaque
타입으로 인식된다.
쿠버네티스는 타입 명칭에 제약을 부과하지는 않는다. 그러나 만약
빌트인 타입 중 하나를 사용한다면, 해당 타입에 정의된 모든 요구 사항을
만족시켜야 한다.
불투명(Opaque) 시크릿
Opaque
은 시크릿 구성 파일에서 누락된 경우의 기본 시크릿 타입이다.
kubectl
을 사용하여 시크릿을 생성할 때 Opaque
시크릿 타입을 나타내기
위해서는 generic
하위 커맨드를 사용할 것이다. 예를 들어, 다음 커맨드는
타입 Opaque
의 비어 있는 시크릿을 생성한다.
kubectl create secret generic empty-secret
kubectl get secret empty-secret
출력은 다음과 같다.
NAME TYPE DATA AGE
empty-secret Opaque 0 2m6s
해당 DATA
열은 시크릿에 저장된 데이터 아이템의 수를 보여준다.
이 경우, 0
은 비어 있는 시크릿을 하나 생성하였다는 것을 의미한다.
서비스 어카운트 토큰 시크릿
kubernetes.io/service-account-token
시크릿 타입은 서비스 어카운트를 확인하는 토큰을 저장하기 위해서 사용한다. 이 시크릿 타입을 사용할 때는,
kubernetes.io/service-account.name
어노테이션이 존재하는 서비스
어카운트 이름으로 설정되도록 해야 한다. 쿠버네티스 컨트롤러는
kubernetes.io/service-account.uid
및 실제 토큰
콘텐츠로 설정된 data
필드의 token
키와 같은,
몇 가지 다른 필드들을 채운다.
다음은 서비스 어카운트 토큰 시크릿의 구성 예시이다.
apiVersion: v1
kind: Secret
metadata:
name: secret-sa-sample
annotations:
kubernetes.io/service-account.name: "sa-name"
type: kubernetes.io/service-account-token
data:
# 사용자는 불투명 시크릿을 사용하므로 추가적인 키 값 쌍을 포함할 수 있다.
extra: YmFyCg==
Pod
를 생성할 때, 쿠버네티스는 자동으로 서비스 어카운트 시크릿을
생성하고 자동으로 파드가 해당 시크릿을 사용하도록 수정한다. 해당 서비스
어카운트 토큰 시크릿은 API 접속을 위한 자격 증명을 포함한다.
이러한 API 자격 증명의 자동 생성과 사용은 원하는 경우 해제하거나 기각할 수 있다. 그러나 만약 사용자가 API 서버에 안전하게 접근하는 것만 필요하다면, 이것이 권장되는 워크플로우이다.
서비스 어카운트 문서를 보면
서비스 어카운트가 동작하는 방법에 대한 더 자세한 정보를 얻을 수 있다.
또한 파드에서 서비스 어카운트를 참조하는 방법을
Pod
의
automountServiceAccountToken
필드와 serviceAccountName
필드를 통해 확인할 수 있다.
도커 컨피그 시크릿
이미지에 대한 도커 레지스트리 접속 자격 증명을 저장하기 위한
시크릿을 생성하기 위해서 다음의 type
값 중 하나를 사용할 수 있다.
kubernetes.io/dockercfg
kubernetes.io/dockerconfigjson
kubernetes.io/dockercfg
는 직렬화 된 도커 커맨드라인 구성을
위한 기존(legacy) 포맷 ~/.dockercfg
를 저장하기 위해 할당된 타입이다.
시크릿 타입을 사용할 때는, data
필드가 base64 포맷으로
인코딩된 ~/.dockercfg
파일의 콘텐츠를 값으로 가지는 .dockercfg
키를 포함하고 있는지
확실히 확인해야 한다.
kubernetes.io/dockerconfigjson
타입은 ~/.dockercfg
의
새로운 포맷인 ~/.docker/config.json
파일과 동일한 포맷 법칙을
따르는 직렬화 된 JSON의 저장을 위해 디자인되었다.
이 시크릿 타입을 사용할 때는, 시크릿 오브젝트의 data
필드가 .dockerconfigjson
키를
꼭 포함해야 한다. ~/.docker/config.json
파일을 위한 콘텐츠는
base64로 인코딩된 문자열으로 제공되어야 한다.
아래는 시크릿의 kubernetes.io/dockercfg
타입 예시이다.
apiVersion: v1
kind: Secret
metadata:
name: secret-dockercfg
type: kubernetes.io/dockercfg
data:
.dockercfg: |
"<base64 encoded ~/.dockercfg file>"
참고: 만약 base64 인코딩 수행을 원하지 않는다면, 그 대신stringData
필드의 사용을 선택할 수 있다.
이러한 타입들을 매니페스트를 사용하여 생성하는 경우, API
서버는 해당 data
필드에 기대하는 키가 존재하는지 확인하고,
제공된 값이 유효한 JSON으로 파싱될 수 있는지 검증한다. API
서버가 해당 JSON이 실제 도커 컨피그 파일인지를 검증하지는 않는다.
도커 컨피그 파일을 가지고 있지 않거나 도커 레지스트리 시크릿을 생성하기
위해 kubectl
을 사용하고 싶은 경우, 다음과 같이 처리할 수 있다.
kubectl create secret docker-registry secret-tiger-docker \
--docker-username=tiger \
--docker-password=pass113 \
--docker-email=tiger@acme.com
이 커맨드는 kubernetes.io/dockerconfigjson
타입의 시크릿을 생성한다.
만약 data
필드로부터 .dockerconfigjson
콘텐츠를 복사(dump)해오면,
다음과 같이 유효한 도커 JSON 콘텐츠를
즉석에서 얻게 될 것이다.
{
"auths": {
"https://index.docker.io/v1/": {
"username": "tiger",
"password": "pass113",
"email": "tiger@acme.com",
"auth": "dGlnZXI6cGFzczExMw=="
}
}
}
기본 인증 시크릿
kubernetes.io/basic-auth
타입은 기본 인증을 위한 자격 증명을 저장하기
위해 제공된다. 이 시크릿 타입을 사용할 때는 시크릿의 data
필드가
다음의 두 키를 포함해야 한다.
username
: 인증을 위한 사용자 이름password
: 인증을 위한 암호나 토큰
위의 두 키에 대한 두 값은 모두 base64로 인코딩된 문자열이다. 물론,
시크릿 생성 시 stringData
를 사용하여 평문 텍스트 콘텐츠(clear text content)를 제공할
수도 있다.
다음의 YAML은 기본 인증 시크릿을 위한 구성 예시이다.
apiVersion: v1
kind: Secret
metadata:
name: secret-basic-auth
type: kubernetes.io/basic-auth
stringData:
username: admin
password: t0p-Secret
이 기본 인증 시크릿 타입은 사용자 편의만을 위해서 제공된다.
사용자는 기본 인증에서 사용되는 자격 증명을 위한 Opaque
를 생성할 수도 있다.
그러나, 빌트인 시크릿 타입을 사용하는 것은 사용자의 자격 증명들의 포맷을 통합하는 데 도움이 되고,
API 서버는 요구되는 키가 시크릿 구성에서 제공되고 있는지
검증도 한다.
SSH 인증 시크릿
이 빌트인 타입 kubernetes.io/ssh-auth
는 SSH 인증에 사용되는 데이터를
저장하기 위해서 제공된다. 이 시크릿 타입을 사용할 때는 ssh-privatekey
키-값 쌍을 사용할 SSH 자격 증명으로 data
(또는 stringData
)
필드에 명시해야 할 것이다.
다음 YAML은 SSH 인증 시크릿의 구성 예시이다.
apiVersion: v1
kind: Secret
metadata:
name: secret-ssh-auth
type: kubernetes.io/ssh-auth
data:
# 본 예시를 위해 축약된 데이터임
ssh-privatekey: |
MIIEpQIBAAKCAQEAulqb/Y ...
SSH 인증 시크릿 타입은 사용자 편의만을 위해서 제공된다.
사용자는 SSH 인증에서 사용되는 자격 증명을 위한 Opaque
를 생성할 수도 있다.
그러나, 빌트인 시크릿 타입을 사용하는 것은 사용자의 자격 증명들의 포맷을 통합하는 데 도움이 되고,
API 서버는 요구되는 키가 시크릿 구성에서 제공되고 있는지
검증도 한다.
주의: SSH 개인 키는 자체적으로 SSH 클라이언트와 호스트 서버 간에 신뢰할 수 있는 통신을 설정하지 않는다. 컨피그맵(ConfigMap)에 추가된known_hosts
파일과 같은 "중간자(man in the middle)" 공격을 완화하려면 신뢰를 설정하는 2차 수단이 필요하다.
TLS 시크릿
쿠버네티스는 보통 TLS를 위해 사용되는 인증서와 관련된 키를 저장하기 위해서
빌트인 시크릿 타입 kubernetes.io/tls
를 제공한다.
이 데이터는 인그레스 리소스의 TLS 종료에 주로 사용되지만, 다른
리소스나 워크로드에 의해 직접적으로 사용될 수도 있다.
이 타입의 시크릿을 사용할 때는 tls.key
와 tls.crt
키가 시크릿 구성의
data
(또는 stringData
) 필드에서 제공되어야 한다. 그러나, API
서버가 각 키에 대한 값이 유효한지 실제로 검증하지는 않는다.
다음 YAML은 TLS 시크릿을 위한 구성 예시를 포함한다.
apiVersion: v1
kind: Secret
metadata:
name: secret-tls
type: kubernetes.io/tls
data:
# 본 예시를 위해 축약된 데이터임
tls.crt: |
MIIC2DCCAcCgAwIBAgIBATANBgkqh ...
tls.key: |
MIIEpgIBAAKCAQEA7yn3bRHQ5FHMQ ...
TLS 시크릿 타입은 사용자 편의만을 위해서 제공된다. 사용자는 TLS 서버 및/또는
클라이언트를 위해 사용되는 자격 증명을 위한 Opaque
를 생성할 수도 있다. 그러나, 빌트인
시크릿 타입을 사용하는 것은 사용자의 자격 증명들의 포맷을 통합하는 데 도움이 되고,
API 서버는 요구되는 키가 시크릿 구성에서 제공되고 있는지 검증도 한다.
kubectl
를 사용하여 TLS 시크릿을 생성할 때, tls
하위 커맨드를
다음 예시와 같이 사용할 수 있다.
kubectl create secret tls my-tls-secret \
--cert=path/to/cert/file \
--key=path/to/key/file
공개/개인 키 쌍은 사전에 존재해야 한다. --cert
를 위한 공개 키 인증서는
.PEM 으로 인코딩(Base64로 인코딩된 DER 포맷)되어야 하며, --key
를 위해 주어진
개인 키에 맞아야 한다.
개인 키는 일반적으로 PEM 개인 키 포맷이라고 하는,
암호화되지 않은 형태(unencrypted)이어야 한다. 두 가지 방식 모두에 대해서, PEM의
시작과 끝 라인(예를 들면, 인증서의 --------BEGIN CERTIFICATE-----
및 -------END CERTIFICATE----
)
은 포함되면 안 된다.
부트스트랩 토큰 시크릿
부트스트랩 토큰 시크릿은 시크릿 type
을 bootstrap.kubernetes.io/token
으로
명확하게 지정하면 생성할 수 있다. 이 타입의 시크릿은 노드 부트스트랩 과정 중에 사용되는
토큰을 위해 디자인되었다. 이것은 잘 알려진 컨피그맵에 서명하는 데 사용되는
토큰을 저장한다.
부트스트랩 토큰 시크릿은 보통 kube-system
네임스페이스에 생성되며
<token-id>
가 해당 토큰 ID의 6개 문자의 문자열으로 구성된 bootstrap-token-<token-id>
형태로
이름이 지정된다.
쿠버네티스 매니페스트로서, 부트스트렙 토큰 시크릿은 다음과 유사할 것이다.
apiVersion: v1
kind: Secret
metadata:
name: bootstrap-token-5emitj
namespace: kube-system
type: bootstrap.kubernetes.io/token
data:
auth-extra-groups: c3lzdGVtOmJvb3RzdHJhcHBlcnM6a3ViZWFkbTpkZWZhdWx0LW5vZGUtdG9rZW4=
expiration: MjAyMC0wOS0xM1QwNDozOToxMFo=
token-id: NWVtaXRq
token-secret: a3E0Z2lodnN6emduMXAwcg==
usage-bootstrap-authentication: dHJ1ZQ==
usage-bootstrap-signing: dHJ1ZQ==
부트스트랩 타입 시크릿은 data
아래 명시된 다음의 키들을 가진다.
token-id
: 토큰 식별자로 임의의 6개 문자의 문자열. 필수 사항.token-secret
: 실제 토큰 시크릿으로 임의의 16개 문자의 문자열. 필수 사항.description
: 토큰의 사용처를 설명하는 사람이 읽을 수 있는 문자열. 선택 사항.expiration
: 토큰이 만료되어야 하는 시기를 명시한 RFC3339를 사용하는 절대 UTC 시간. 선택 사항.usage-bootstrap-<usage>
: 부트스트랩 토큰의 추가적인 사용처를 나타내는 불리언(boolean) 플래그.auth-extra-groups
:system:bootstrappers
그룹에 추가로 인증될 쉼표로 구분된 그룹 이름 목록.
위의 YAML은 모두 base64로 인코딩된 문자열 값이므로 혼란스러워 보일 수 있다. 사실은 다음 YAML을 사용하여 동일한 시크릿을 생성할 수 있다.
apiVersion: v1
kind: Secret
metadata:
# 시크릿 이름이 어떻게 지정되었는지 확인
name: bootstrap-token-5emitj
# 부트스트랩 토큰 시크릿은 일반적으로 kube-system 네임스페이스에 포함
namespace: kube-system
type: bootstrap.kubernetes.io/token
stringData:
auth-extra-groups: "system:bootstrappers:kubeadm:default-node-token"
expiration: "2020-09-13T04:39:10Z"
# 이 토큰 ID는 이름에 사용됨
token-id: "5emitj"
token-secret: "kq4gihvszzgn1p0r"
# 이 토큰은 인증을 위해서 사용될 수 있음
usage-bootstrap-authentication: "true"
# 또한 서명(signing)에도 사용될 수 있음
usage-bootstrap-signing: "true"
시크릿 생성하기
시크릿을 생성하기 위한 몇 가지 옵션이 있다.
시크릿 편집하기
기존 시크릿은 다음 명령을 사용하여 편집할 수 있다.
kubectl edit secrets mysecret
이렇게 하면 기본으로 설정된 에디터가 열리고 data
필드에 base64로 인코딩된 시크릿 값을 업데이트할 수 있다.
# 아래 오브젝트를 수정한다. '#'로 시작하는 줄은 무시되고,
# 빈 파일은 편집이 취소될 것이다. 이 파일을 저장하는 도중에 오류가 발생하면
# 관련 오류와 함께 다시 열린다.
#
apiVersion: v1
data:
username: YWRtaW4=
password: MWYyZDFlMmU2N2Rm
kind: Secret
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: { ... }
creationTimestamp: 2016-01-22T18:41:56Z
name: mysecret
namespace: default
resourceVersion: "164619"
uid: cfee02d6-c137-11e5-8d73-42010af00002
type: Opaque
시크릿 사용하기
시크릿은 데이터 볼륨으로 마운트되거나 파드의 컨테이너에서 사용할 환경 변수로 노출될 수 있다. 또한, 시크릿은 파드에 직접 노출되지 않고, 시스템의 다른 부분에서도 사용할 수 있다. 예를 들어, 시크릿은 시스템의 다른 부분이 사용자를 대신해서 외부 시스템과 상호 작용하는 데 사용해야 하는 자격 증명을 보유할 수 있다.
시크릿을 파드의 파일로 사용하기
파드의 볼륨에서 시크릿을 사용하려면 다음과 같이 한다.
- 시크릿을 생성하거나 기존 시크릿을 사용한다. 여러 파드가 동일한 시크릿을 참조할 수 있다.
.spec.volumes[].
아래에 볼륨을 추가하려면 파드 정의를 수정한다. 볼륨의 이름을 뭐든지 지정하고, 시크릿 오브젝트의 이름과 동일한.spec.volumes[].secret.secretName
필드를 생성한다.- 시크릿이 필요한 각 컨테이너에
.spec.containers[].volumeMounts[]
를 추가한다. 시크릿을 표시하려는 사용되지 않은 디렉터리 이름에.spec.containers[].volumeMounts[].readOnly = true
와.spec.containers[].volumeMounts[].mountPath
를 지정한다. - 프로그램이 해당 디렉터리에서 파일을 찾도록 이미지 또는 커맨드 라인을 수정한다. 시크릿
data
맵의 각 키는mountPath
아래의 파일명이 된다.
다음은 볼륨에 시크릿을 마운트하는 파드의 예시이다.
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: redis
volumeMounts:
- name: foo
mountPath: "/etc/foo"
readOnly: true
volumes:
- name: foo
secret:
secretName: mysecret
사용하려는 각 시크릿은 .spec.volumes
에서 참조해야 한다.
파드에 여러 컨테이너가 있는 경우, 모든 컨테이너는
자체 volumeMounts
블록이 필요하지만, 시크릿에 대해서는 시크릿당 하나의 .spec.volumes
만 필요하다.
많은 파일을 하나의 시크릿으로 패키징하거나, 여러 시크릿을 사용할 수 있으며, 어느 쪽이든 편리한 방법을 사용하면 된다.
특정 경로에 대한 시크릿 키 투영하기
시크릿 키가 투영되는 볼륨 내 경로를 제어할 수도 있다.
.spec.volumes[].secret.items
필드를 사용하여 각 키의 대상 경로를 변경할 수 있다.
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: redis
volumeMounts:
- name: foo
mountPath: "/etc/foo"
readOnly: true
volumes:
- name: foo
secret:
secretName: mysecret
items:
- key: username
path: my-group/my-username
다음과 같은 일들이 일어날 것이다.
username
시크릿은/etc/foo/username
대신/etc/foo/my-group/my-username
아래의 파일에 저장된다.password
시크릿은 투영되지 않는다.
.spec.volumes[].secret.items
를 사용하면, items
에 지정된 키만 투영된다.
시크릿의 모든 키를 사용하려면, 모든 키가 items
필드에 나열되어야 한다.
나열된 모든 키는 해당 시크릿에 존재해야 한다. 그렇지 않으면, 볼륨이 생성되지 않는다.
시크릿 파일 퍼미션
단일 시크릿 키에 대한 파일 접근 퍼미션 비트를 설정할 수 있다.
만약 사용자가 퍼미션을 지정하지 않는다면, 기본적으로 0644
가 사용된다.
전체 시크릿 볼륨에 대한 기본 모드를 설정하고 필요한 경우 키별로 오버라이드할 수도 있다.
예를 들어, 다음과 같은 기본 모드를 지정할 수 있다.
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: redis
volumeMounts:
- name: foo
mountPath: "/etc/foo"
volumes:
- name: foo
secret:
secretName: mysecret
defaultMode: 0400
그러고 나면, 시크릿이 /etc/foo
에 마운트되고 시크릿 볼륨 마운트로 생성된
모든 파일의 퍼미션은 0400
이 될 것이다.
참고로 JSON 스펙은 8진수 표기법을 지원하지 않으므로, 0400 퍼미션에 대해서 값 256을 사용한다. 파드에 대해 JSON 대신 YAML을 사용하는 경우, 8진수 표기법을 사용하여 보다 자연스러운 방식으로 퍼미션을 지정할 수 있다.
참고로 파드에 kubectl exec
을 사용하는 경우, 예상되는 파일 모드를 찾기 위해
심볼릭 링크를 따라가야 한다. 예를 들면, 다음과 같다.
파드에서 시크릿 파일 모드를 확인한다.
kubectl exec mypod -it sh
cd /etc/foo
ls -l
출력 결과는 다음과 비슷하다.
total 0
lrwxrwxrwx 1 root root 15 May 18 00:18 password -> ..data/password
lrwxrwxrwx 1 root root 15 May 18 00:18 username -> ..data/username
올바른 파일 모드를 찾으려면 심볼릭 링크를 따라간다.
cd /etc/foo/..data
ls -l
출력 결과는 다음과 비슷하다.
total 8
-r-------- 1 root root 12 May 18 00:18 password
-r-------- 1 root root 5 May 18 00:18 username
이전 예제에서와 같이 매핑을 사용하여, 다음과 같이 다른 파일에 대해 다른 퍼미션을 지정할 수도 있다.
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: redis
volumeMounts:
- name: foo
mountPath: "/etc/foo"
volumes:
- name: foo
secret:
secretName: mysecret
items:
- key: username
path: my-group/my-username
mode: 0777
이 경우, /etc/foo/my-group/my-username
에 있는 파일은
결과적으로 0777
퍼미션 값을 갖게 된다. JSON을 사용하는 경우, JSON 제한으로 인해
10진수 표기법(511
)으로 모드를 지정해야 한다.
참고로 이 퍼미션 값은 나중에 읽을 때 10진수 표기법으로 표시될 수 있다.
볼륨에서 시크릿 값 사용하기
시크릿 볼륨을 마운트하는 컨테이너 내부에서, 시크릿 키는 파일로 나타나고 시크릿 값은 base64로 디코딩되어 이런 파일 내에 저장된다. 다음은 위의 예에서 컨테이너 내부에서 실행된 명령의 결과이다.
ls /etc/foo/
출력 결과는 다음과 비슷하다.
username
password
cat /etc/foo/username
출력 결과는 다음과 비슷하다.
admin
cat /etc/foo/password
출력 결과는 다음과 비슷하다.
1f2d1e2e67df
컨테이너의 프로그램은 파일에서 시크릿을 읽는 역할을 한다.
마운트된 시크릿은 자동으로 업데이트됨
볼륨에서 현재 사용되는 시크릿이 업데이트되면, 투영된 키도 결국 업데이트된다.
kubelet은 마운트된 시크릿이 모든 주기적인 동기화에서 최신 상태인지 여부를 확인한다.
그러나, kubelet은 시크릿의 현재 값을 가져 오기 위해 로컬 캐시를 사용한다.
캐시의 유형은 KubeletConfiguration 구조체의
ConfigMapAndSecretChangeDetectionStrategy
필드를 사용하여 구성할 수 있다.
시크릿은 watch(기본값), ttl 기반 또는 API 서버로 모든 요청을 직접
리디렉션하여 전파할 수 있다.
결과적으로, 시크릿이 업데이트된 순간부터 새로운 키가 파드에 투영되는
순간까지의 총 지연 시간은 kubelet 동기화 시간 + 캐시
전파 지연만큼 길 수 있다. 여기서 캐시 전파 지연은 선택한 캐시 유형에 따라
달라질 수 있다(캐시 전파 지연은 각 캐시 유형에 따라 watch 전파 지연, 캐시의 ttl, 또는 0 에 상응함).
참고: 시크릿을 subPath 볼륨 마운트로 사용하는 컨테이너는 시크릿 업데이트를 받지 않는다.
시크릿을 환경 변수로 사용하기
파드에서 환경 변수에 시크릿을 사용하려면 다음과 같이 한다.
- 시크릿을 생성하거나 기존 시크릿을 사용한다. 여러 파드가 동일한 시크릿을 참조할 수 있다.
- 사용하려는 각 시크릿 키에 대한 환경 변수를 추가하려면 시크릿 키 값을 사용하려는 각 컨테이너에서 파드 정의를 수정한다. 시크릿 키를 사용하는 환경 변수는 시크릿의 이름과 키를
env[].valueFrom.secretKeyRef
에 채워야 한다. - 프로그램이 지정된 환경 변수에서 값을 찾도록 이미지 및/또는 커맨드 라인을 수정한다.
다음은 환경 변수의 시크릿을 사용하는 파드의 예시이다.
apiVersion: v1
kind: Pod
metadata:
name: secret-env-pod
spec:
containers:
- name: mycontainer
image: redis
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: mysecret
key: username
- name: SECRET_PASSWORD
valueFrom:
secretKeyRef:
name: mysecret
key: password
restartPolicy: Never
환경 변수에서 시크릿 값 사용하기
환경 변수에서 시크릿을 사용하는 컨테이너 내부에서, 시크릿 키는 시크릿 데이터의 base64 디코딩된 값을 포함하는 일반 환경 변수로 나타난다. 다음은 위의 예에서 컨테이너 내부에서 실행된 명령의 결과이다.
echo $SECRET_USERNAME
출력 결과는 다음과 비슷하다.
admin
echo $SECRET_PASSWORD
출력 결과는 다음과 비슷하다.
1f2d1e2e67df
시크릿 업데이트 후 환경 변수가 업데이트되지 않음
컨테이너가 환경 변수에서 이미 시크릿을 사용하는 경우, 다시 시작하지 않는 한 컨테이너에서 시크릿 업데이트를 볼 수 없다. 시크릿이 변경될 때 재시작을 트리거하는 써드파티 솔루션이 있다.
변경할 수 없는(immutable) 시크릿
Kubernetes v1.19 [beta]
쿠버네티스 베타 기능인 변경할 수 없는 시크릿과 컨피그맵 은 개별 시크릿과 컨피그맵을 변경할 수 없는 것으로 설정하는 옵션을 제공한다. 시크릿을 광범위하게 사용하는 클러스터(최소 수만 개의 고유한 시크릿이 파드에 마운트)의 경우, 데이터 변경을 방지하면 다음과 같은 이점이 있다.
- 애플리케이션 중단을 유발할 수 있는 우발적(또는 원하지 않는) 업데이트로부터 보호
- immutable로 표시된 시크릿에 대한 감시를 중단하여, kube-apiserver의 부하를 크게 줄임으로써 클러스터의 성능을 향상시킴
이 기능은 v1.19부터 기본적으로 활성화된 ImmutableEphemeralVolumes
기능 게이트에
의해 제어된다. immutable
필드를 true
로 설정하여
변경할 수 없는 시크릿을 생성할 수 있다. 다음은 예시이다.
apiVersion: v1
kind: Secret
metadata:
...
data:
...
immutable: true
참고: 시크릿 또는 컨피그맵을 immutable로 표시하면, 이 변경 사항을 되돌리거나data
필드 내용을 변경할 수 없다. 시크릿을 삭제하고 다시 생성할 수만 있다. 기존 파드는 삭제된 시크릿에 대한 마운트 포인트를 유지하며, 이러한 파드를 다시 생성하는 것을 권장한다.
imagePullSecrets 사용하기
imagePullSecrets
필드는 동일한 네임스페이스의 시크릿에 대한 참조 목록이다.
imagePullSecretsDocker
를 사용하여 도커(또는 다른 컨테이너) 이미지 레지스트리
비밀번호가 포함된 시크릿을 kubelet에 전달할 수 있다. kubelet은 이 정보를 사용해서 파드를 대신하여 프라이빗 이미지를 가져온다.
imagePullSecrets
필드에 대한 자세한 정보는 PodSpec API를 참고한다.
imagePullSecret 수동으로 지정하기
컨테이너 이미지 문서에서 ImagePullSecrets
지정하는 방법을 배울 수 있다.
imagePullSecrets가 자동으로 연결되도록 정렬하기
수동으로 imagePullSecrets
를 생성하고, 서비스어카운트(ServiceAccount)에서
참조할 수 있다. 해당 서비스어카운트로 생성되거나
기본적인 서비스어카운트로 생성된 모든 파드는 파드의 imagePullSecrets
필드를 가져오고 서비스 어카운트의 필드로 설정한다.
해당 프로세스에 대한 자세한 설명은
서비스 어카운트에 ImagePullSecrets 추가하기를 참고한다.
상세 내용
제약 사항
시크릿 볼륨 소스는 지정된 오브젝트 참조가 실제로 시크릿 유형의 오브젝트를 가리키는지 확인하기 위해 유효성을 검사한다. 따라서, 시크릿에 의존하는 모든 파드보다 먼저 시크릿을 만들어야 한다.
시크릿 리소스는 네임스페이스에 존재한다. 시크릿은 동일한 네임스페이스에 있는 파드에서만 참조할 수 있다.
개별 시크릿의 크기는 1MiB로 제한된다. 이는 API 서버와 kubelet 메모리를 소진시키는 매우 큰 시크릿 생성을 막기 위한 것이다. 그러나, 많은 작은 시크릿을 만들어도 메모리가 고갈될 수 있다. 시크릿으로 인한 메모리 사용에 대한 보다 포괄적인 제한은 향후 버전에 계획된 기능이다.
kubelet은 API 서버에서 시크릿을 가져오는 파드에 대한
시크릿 사용만 지원한다.
여기에는 kubectl
을 사용하거나, 레플리케이션 컨트롤러를 통해 간접적으로 생성된 모든
파드가 포함된다. kubelet의 --manifest-url
플래그, --config
플래그 또는
kubectl의 REST API(이 방법들은 파드를 생성하는 일반적인 방법이 아님)로
생성된 파드는 포함하지 않는다.
시크릿은 optional(선택 사항)로 표시되지 않는 한 파드에서 환경 변수로 사용되기 전에 생성되어야 한다. 존재하지 않는 시크릿을 참조하면 파드가 시작되지 않는다.
명명된 시크릿에 존재하지 않는 키에 대한 참조(secretKeyRef
필드)는
파드가 시작되지 않도록 한다.
잘못된 환경 변수 이름으로 간주되는 키가 있는 envFrom
필드로
환경 변수를 채우는 데 사용되는 시크릿은 해당 키를
건너뛴다. 이러면 해당 파드가 시작될 수 있다. 원인이 InvalidVariableNames
인
이벤트가 발생하며 건너뛴 유효하지 않은 키 목록이
포함된 메시지가 생성된다. 다음의 예는 2개의 유효하지 않은
키(1badkey
와 2alsobad
)가 포함된 default/mysecret을 참조하는 파드를 보여준다.
kubectl get events
출력 결과는 다음과 비슷하다.
LASTSEEN FIRSTSEEN COUNT NAME KIND SUBOBJECT TYPE REASON
0s 0s 1 dapi-test-pod Pod Warning InvalidEnvironmentVariableNames kubelet, 127.0.0.1 Keys [1badkey, 2alsobad] from the EnvFrom secret default/mysecret were skipped since they are considered invalid environment variable names.
시크릿 및 파드 수명 상호 작용
쿠버네티스 API를 호출하여 파드가 생성될 때, 참조된 시크릿이 있는지 확인하지 않는다. 일단 파드가 스케줄되면, kubelet은 시크릿 값 가져오기를 시도한다. 시크릿이 존재하지 않거나 API 서버에 대한 일시적인 연결 부족으로 인해 시크릿을 가져올 수 없는 경우, kubelet은 주기적으로 재시도한다. kubelet은 아직 시작되지 않은 이유를 설명하는 파드에 대한 이벤트를 보고한다. 시크릿을 가져오면, kubelet은 이를 포함하는 볼륨을 생성하고 마운트한다. 모든 파드의 볼륨이 마운트될 때까지 파드의 컨테이너는 시작되지 않는다.
사용 사레
사용 사례: 컨테이너 환경 변수로 사용하기
시크릿 정의를 작성한다.
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
USER_NAME: YWRtaW4=
PASSWORD: MWYyZDFlMmU2N2Rm
시크릿을 생성한다.
kubectl apply -f mysecret.yaml
모든 시크릿 데이터를 컨테이너 환경 변수로 정의하는 데 envFrom
을 사용한다. 시크릿의 키는 파드의 환경 변수 이름이 된다.
apiVersion: v1
kind: Pod
metadata:
name: secret-test-pod
spec:
containers:
- name: test-container
image: k8s.gcr.io/busybox
command: [ "/bin/sh", "-c", "env" ]
envFrom:
- secretRef:
name: mysecret
restartPolicy: Never
사용 사례: ssh 키가 있는 파드
몇 가지 ssh 키를 포함하는 시크릿을 생성한다.
kubectl create secret generic ssh-key-secret --from-file=ssh-privatekey=/path/to/.ssh/id_rsa --from-file=ssh-publickey=/path/to/.ssh/id_rsa.pub
출력 결과는 다음과 비슷하다.
secret "ssh-key-secret" created
ssh 키를 포함하는 secretGenerator
필드가 있는 kustomization.yaml
를 만들 수도 있다.
주의: 사용자 자신의 ssh 키를 보내기 전에 신중하게 생각한다. 클러스터의 다른 사용자가 시크릿에 접근할 수 있다. 쿠버네티스 클러스터를 공유하는 모든 사용자가 접근할 수 있도록 하려는 서비스 어카운트를 사용하고, 사용자가 손상된 경우 이 계정을 취소할 수 있다.
이제 ssh 키를 가진 시크릿을 참조하고 볼륨에서 시크릿을 사용하는 파드를 만들 수 있다.
apiVersion: v1
kind: Pod
metadata:
name: secret-test-pod
labels:
name: secret-test
spec:
volumes:
- name: secret-volume
secret:
secretName: ssh-key-secret
containers:
- name: ssh-test-container
image: mySshImage
volumeMounts:
- name: secret-volume
readOnly: true
mountPath: "/etc/secret-volume"
컨테이너의 명령이 실행될 때, 다음 위치에서 키 부분을 사용할 수 있다.
/etc/secret-volume/ssh-publickey
/etc/secret-volume/ssh-privatekey
그러면 컨테이너는 ssh 연결을 맺기 위해 시크릿 데이터를 자유롭게 사용할 수 있다.
사용 사례: 운영 / 테스트 자격 증명이 있는 파드
이 예제에서는 운영 환경의 자격 증명이 포함된 시크릿을 사용하는 파드와 테스트 환경의 자격 증명이 있는 시크릿을 사용하는 다른 파드를 보여준다.
사용자는 secretGenerator
필드가 있는 kustomization.yaml
을 만들거나
kubectl create secret
을 실행할 수 있다.
kubectl create secret generic prod-db-secret --from-literal=username=produser --from-literal=password=Y4nys7f11
출력 결과는 다음과 비슷하다.
secret "prod-db-secret" created
테스트 환경의 자격 증명에 대한 시크릿을 만들 수도 있다.
kubectl create secret generic test-db-secret --from-literal=username=testuser --from-literal=password=iluvtests
출력 결과는 다음과 비슷하다.
secret "test-db-secret" created
참고:
$
,\
,*
,=
그리고!
와 같은 특수 문자는 사용자의 셸에 의해 해석되고 이스케이핑이 필요하다. 대부분의 셸에서 비밀번호를 이스케이프하는 가장 쉬운 방법은 작은 따옴표('
)로 묶는 것이다. 예를 들어, 실제 비밀번호가S!B\*d$zDsb=
이면, 다음과 같은 명령을 실행해야 한다.kubectl create secret generic dev-db-secret --from-literal=username=devuser --from-literal=password='S!B\*d$zDsb='
파일(
--from-file
)에서는 비밀번호의 특수 문자를 이스케이프할 필요가 없다.
이제 파드를 생성한다.
cat <<EOF > pod.yaml
apiVersion: v1
kind: List
items:
- kind: Pod
apiVersion: v1
metadata:
name: prod-db-client-pod
labels:
name: prod-db-client
spec:
volumes:
- name: secret-volume
secret:
secretName: prod-db-secret
containers:
- name: db-client-container
image: myClientImage
volumeMounts:
- name: secret-volume
readOnly: true
mountPath: "/etc/secret-volume"
- kind: Pod
apiVersion: v1
metadata:
name: test-db-client-pod
labels:
name: test-db-client
spec:
volumes:
- name: secret-volume
secret:
secretName: test-db-secret
containers:
- name: db-client-container
image: myClientImage
volumeMounts:
- name: secret-volume
readOnly: true
mountPath: "/etc/secret-volume"
EOF
동일한 kustomization.yaml에 파드를 추가한다.
cat <<EOF >> kustomization.yaml
resources:
- pod.yaml
EOF
다음을 실행하여 API 서버에 이러한 모든 오브젝트를 적용한다.
kubectl apply -k .
두 컨테이너 모두 각 컨테이너의 환경에 대한 값을 가진 파일시스템에 다음의 파일이 존재한다.
/etc/secret-volume/username
/etc/secret-volume/password
두 파드의 사양이 한 필드에서만 어떻게 다른지 확인한다. 이를 통해 공통 파드 템플릿에서 다양한 기능을 가진 파드를 생성할 수 있다.
두 개의 서비스 어카운트를 사용하여 기본 파드 명세를 더욱 단순화할 수 있다.
prod-db-secret
을 가진prod-user
test-db-secret
을 가진test-user
파드 명세는 다음과 같이 단축된다.
apiVersion: v1
kind: Pod
metadata:
name: prod-db-client-pod
labels:
name: prod-db-client
spec:
serviceAccount: prod-db-client
containers:
- name: db-client-container
image: myClientImage
사용 사례: 시크릿 볼륨의 도트 파일(dotfile)
점으로 시작하는 키를 정의하여 데이터를 "숨김"으로 만들 수 있다.
이 키는 도트 파일 또는 "숨겨진" 파일을 나타낸다. 예를 들어, 다음 시크릿이 secret-volume
볼륨에
마운트되면 아래와 같다.
apiVersion: v1
kind: Secret
metadata:
name: dotfile-secret
data:
.secret-file: dmFsdWUtMg0KDQo=
---
apiVersion: v1
kind: Pod
metadata:
name: secret-dotfiles-pod
spec:
volumes:
- name: secret-volume
secret:
secretName: dotfile-secret
containers:
- name: dotfile-test-container
image: k8s.gcr.io/busybox
command:
- ls
- "-l"
- "/etc/secret-volume"
volumeMounts:
- name: secret-volume
readOnly: true
mountPath: "/etc/secret-volume"
볼륨은 .secret-file
이라는 하나의 파일을 포함하고,
dotfile-test-container
는 /etc/secret-volume/.secret-file
경로에
이 파일을 가지게 된다.
참고:ls -l
명령의 결과에서 숨겨진 점으로 시작하는 파일들은 디렉터리 내용을 나열할 때ls -la
를 사용해야 이 파일들을 볼 수 있다.
사용 사례: 파드의 한 컨테이너에 표시되는 시크릿
HTTP 요청을 처리하고, 복잡한 비즈니스 로직을 수행한 다음, HMAC이 있는 일부 메시지에 서명해야 하는 프로그램을 고려한다. 애플리케이션 로직이 복잡하기 때문에, 서버에서 눈에 띄지 않는 원격 파일 읽기 공격이 있을 수 있으며, 이로 인해 개인 키가 공격자에게 노출될 수 있다.
이는 두 개의 컨테이너의 두 개 프로세스로 나눌 수 있다. 사용자 상호 작용과 비즈니스 로직을 처리하지만, 개인 키를 볼 수 없는 프론트엔드 컨테이너와 개인 키를 볼 수 있고, 프론트엔드의 간단한 서명 요청(예를 들어, localhost 네트워킹을 통해)에 응답하는 서명자 컨테이너로 나눌 수 있다.
이 분할된 접근 방식을 사용하면, 공격자는 이제 애플리케이션 서버를 속여서 파일을 읽는 것보다 다소 어려운 임의적인 어떤 작업을 수행해야 한다.
모범 사례
시크릿 API를 사용하는 클라이언트
시크릿 API와 상호 작용하는 애플리케이션을 배포할 때, RBAC과 같은 인가 정책을 사용하여 접근를 제한해야 한다.
시크릿은 종종 다양한 중요도에 걸친 값을 보유하며, 이 중 많은 부분이 쿠버네티스(예: 서비스 어카운트 토큰)와 외부 시스템으로 단계적으로 확대될 수 있다. 개별 앱이 상호 작용할 것으로 예상되는 시크릿의 힘에 대해 추론할 수 있더라도 동일한 네임스페이스 내의 다른 앱이 이러한 가정을 무효화할 수 있다.
이러한 이유로 네임스페이스 내 시크릿에 대한 watch
와 list
요청은
매우 강력한 기능이며, 시크릿을 나열하면 클라이언트가 해당 네임스페이스에
있는 모든 시크릿의 값을 검사할 수 있기 때문에 피해야 한다. 클러스터의
모든 시크릿을 감시(watch
)하고 나열(list
)하는 기능은 가장 특권이 있는 시스템 레벨의
컴포넌트에 대해서만 예약되어야 한다.
시크릿 API에 접근해야 하는 애플리케이션은 필요한 시크릿에 대한 get
요청을
수행해야 한다. 이를 통해 관리자는 앱에 필요한
개별 인스턴스에 대한 접근을 허용 목록에 추가하면서 모든 시크릿에 대한 접근을
제한할 수 있다.
get
반복을 통한 성능 향상을 위해, 클라이언트는 시크릿을
참조한 다음 리소스를 감시(watch
)하고, 참조가 변경되면 시크릿을 다시 요청하는 리소스를
설계할 수 있다. 덧붙여, 클라이언트에게 개별 리소스를 감시(watch
)하도록 하는 "대량 감시" API도
제안되었으며, 쿠버네티스의 후속 릴리스에서 사용할 수
있을 것이다.
보안 속성
보호
시크릿은 시크릿을 사용하는 파드와 독립적으로 생성될 수 있으므로, 파드 생성, 보기, 편집 워크플로 중에 시크릿이 노출될 위험이 적다. 또한 시스템은 가능한 경우 디스크에 기록하지 않는 등 시크릿에 대한 추가 예방 조치를 취할 수 있다.
해당 노드의 파드에 필요한 경우에만 시크릿이 노드로 전송된다.
kubelet은 시크릿이 디스크 저장소에 기록되지 않도록 시크릿을
tmpfs
에 저장한다. 일단 시크릿에 의존하는 파드가 삭제되면, kubelet은
시크릿 데이터의 로컬 복제본도 삭제한다.
동일한 노드의 여러 파드에 대한 시크릿이 있을 수 있다. 그러나 파드가 요청하는 시크릿만 해당 컨테이너 내에서 잠재적으로 볼 수 있다. 따라서, 하나의 파드는 다른 파드의 시크릿에 접근할 수 없다.
파드에는 여러 개의 컨테이너가 있을 수 있다. 그러나, 파드의 각 컨테이너는
컨테이너 내에서 볼 수 있도록 파드의 volumeMounts
에 있는 시크릿 볼륨을
요청해야 한다. 이것은 유용한 파드 레벨에서의 보안
파티션을 구성하는 데 사용할 수 있다.
대부분의 쿠버네티스 배포판에서, 사용자와 API 서버 간, API 서버에서 kubelet으로의 통신은 SSL/TLS로 보호된다. 이러한 채널을 통해 전송될 때 시크릿이 보호된다.
Kubernetes v1.13 [beta]
시크릿 데이터에 대해 저장 시 암호화(encryption at rest)를 활성화할 수 있으며, 이를 통해 보안성에 대한 보장 없이는 시크릿이 etcd에 저장되지 않도록 한다 .
위험
- API 서버에서 시크릿 데이터는 etcd에 저장된다.
따라서,
- 관리자는 클러스터 데이터에 대해 저장 시 암호화를 활성화해야 한다. (v1.13 이상 필요)
- 관리자는 etcd에 대한 접근을 admin 사용자로 제한해야 한다.
- 관리자는 더 이상 사용하지 않을 때 etcd에서 사용하는 디스크를 지우거나 폐기할 수 있다.
- 클러스터에서 etcd를 실행하는 경우, 관리자는 etcd peer-to-peer 통신에 대해 SSL/TLS를 사용해야 한다.
- base64로 인코딩된 시크릿 데이터가 있는 매니페스트(JSON 또는 YAML) 파일을 통해 시크릿을 구성하는 경우, 이 파일을 공유하거나 소스 리포지터리에 체크인하면 시크릿이 손상된다. Base64 인코딩은 암호화 방법이 아니며 일반 텍스트와 동일한 것으로 간주된다.
- 실수로 기록하거나 신뢰할 수 없는 상대방에게 전송하지 않는 것과 같이, 애플리케이션은 볼륨에서 읽은 후에 시크릿 값을 보호해야 한다.
- 시크릿을 사용하는 파드를 생성할 수 있는 사용자는 해당 시크릿의 값도 볼 수 있다. API 서버 정책이 해당 사용자가 시크릿을 읽을 수 있도록 허용하지 않더라도, 사용자는 시크릿을 노출하는 파드를 실행할 수 있다.
- 현재, 모든 노드에 대한 루트 권한이 있는 모든 사용자는 kubelet을 가장하여 API 서버에서 모든 시크릿을 읽을 수 있다. 단일 노드에 대한 루트 취약점 공격의 영향을 제한하기 위해, 실제로 필요한 노드에만 시크릿을 보내는 것이 앞으로 계획된 기능이다.
다음 내용
kubectl
을 사용한 시크릿 관리하는 방법 배우기- 구성 파일을 사용한 시크릿 관리하는 방법 배우기
- kustomize를 사용한 시크릿 관리하는 방법 배우기
4 - 컨테이너 리소스 관리
파드를 지정할 때, 컨테이너에 필요한 각 리소스의 양을 선택적으로 지정할 수 있다. 지정할 가장 일반적인 리소스는 CPU와 메모리(RAM) 그리고 다른 것들이 있다.
파드에서 컨테이너에 대한 리소스 요청(request) 을 지정하면, 스케줄러는 이 정보를 사용하여 파드가 배치될 노드를 결정한다. 컨테이너에 대한 리소스 제한(limit) 을 지정하면, kubelet은 실행 중인 컨테이너가 설정한 제한보다 많은 리소스를 사용할 수 없도록 해당 제한을 적용한다. 또한 kubelet은 컨테이너가 사용할 수 있도록 해당 시스템 리소스의 최소 요청 량을 예약한다.
요청 및 제한
파드가 실행 중인 노드에 사용 가능한 리소스가 충분하면, 컨테이너가 해당
리소스에 지정한 request
보다 더 많은 리소스를 사용할 수 있도록 허용된다.
그러나, 컨테이너는 리소스 limit
보다 더 많은 리소스를 사용할 수는 없다.
예를 들어, 컨테이너에 대해 256MiB의 memory
요청을 설정하고, 해당 컨테이너가
8GiB의 메모리를 가진 노드로 스케줄된 파드에 있고 다른 파드는 없는 경우, 컨테이너는 더 많은 RAM을
사용할 수 있다.
해당 컨테이너에 대해 4GiB의 memory
제한을 설정하면, kubelet(그리고
컨테이너 런타임)이 제한을 적용한다.
런타임은 컨테이너가 구성된 리소스 제한을 초과하여 사용하지 못하게 한다. 예를 들어,
컨테이너의 프로세스가 허용된 양보다 많은 메모리를 사용하려고 하면,
시스템 커널은 메모리 부족(out of memory, OOM) 오류와 함께 할당을 시도한 프로세스를
종료한다.
제한은 반응적(시스템이 위반을 감지한 후에 개입)으로 또는 강제적(시스템이 컨테이너가 제한을 초과하지 않도록 방지)으로 구현할 수 있다. 런타임마다 다른 방식으로 동일한 제약을 구현할 수 있다.
참고: 컨테이너가 자체 메모리 제한을 지정하지만, 메모리 요청을 지정하지 않는 경우, 쿠버네티스는 제한과 일치하는 메모리 요청을 자동으로 할당한다. 마찬가지로, 컨테이너가 자체 CPU 제한을 지정하지만, CPU 요청을 지정하지 않는 경우, 쿠버네티스는 제한과 일치하는 CPU 요청을 자동으로 할당한다.
리소스 타입
CPU 와 메모리 는 각각 리소스 타입 이다. 리소스 타입에는 기본 단위가 있다. CPU는 컴퓨팅 처리를 나타내며 쿠버네티스 CPU 단위로 지정된다. 메모리는 바이트 단위로 지정된다. 쿠버네티스 v1.14 이상을 사용하는 경우, huge page 리소스를 지정할 수 있다. Huge page는 노드 커널이 기본 페이지 크기보다 훨씬 큰 메모리 블록을 할당하는 리눅스 관련 기능이다.
예를 들어, 기본 페이지 크기가 4KiB인 시스템에서, hugepages-2Mi: 80Mi
제한을
지정할 수 있다. 컨테이너가 40개 이상의 2MiB huge page(총 80MiB)를
할당하려고 하면 해당 할당이 실패한다.
참고:hugepages-*
리소스를 오버커밋할 수 없다. 이것은memory
및cpu
리소스와는 다르다.
CPU와 메모리를 통칭하여 컴퓨트 리소스 또는 리소스 라고 한다. 컴퓨트 리소스는 요청, 할당 및 소비될 수 있는 측정 가능한 수량이다. 이것은 API 리소스와는 다르다. 파드 및 서비스와 같은 API 리소스는 쿠버네티스 API 서버를 통해 읽고 수정할 수 있는 오브젝트이다.
파드와 컨테이너의 리소스 요청 및 제한
파드의 각 컨테이너는 다음 중 하나 이상을 지정할 수 있다.
spec.containers[].resources.limits.cpu
spec.containers[].resources.limits.memory
spec.containers[].resources.limits.hugepages-<size>
spec.containers[].resources.requests.cpu
spec.containers[].resources.requests.memory
spec.containers[].resources.requests.hugepages-<size>
요청과 제한은 개별 컨테이너에서만 지정할 수 있지만, 파드 리소스 요청 및 제한에 대해 이야기하는 것이 편리하다. 특정 리소스 타입에 대한 파드 리소스 요청/제한 은 파드의 각 컨테이너에 대한 해당 타입의 리소스 요청/제한의 합이다.
쿠버네티스의 리소스 단위
CPU의 의미
CPU 리소스에 대한 제한 및 요청은 cpu 단위로 측정된다. 쿠버네티스의 CPU 1개는 클라우드 공급자용 vCPU/Core 1개 와 베어메탈 인텔 프로세서에서의 1개 하이퍼스레드 에 해당한다.
분수의 요청이 허용된다.
0.5
의 spec.containers[].resources.requests.cpu
요청을 가진
컨테이너는 CPU 1개를 요구하는 컨테이너의 절반만큼 CPU를 보장한다. 0.1
이라는 표현은
"백 밀리cpu"로 읽을 수 있는 100m
표현과 동일하다. 어떤 사람들은
"백 밀리코어"라고 말하는데, 같은 것을 의미하는 것으로 이해된다.
0.1
과 같이 소수점이 있는 요청은 API에 의해 100m
로 변환되며,
1m
도 허용되지 않게 정밀하다. 이러한 이유로, 100m
형식이
선호될 수 있다.
CPU는 항상 절대 수량으로 요청되며, 상대적 수량은 아니다. 0.1은 단일 코어, 이중 코어 또는 48코어 시스템에서 동일한 양의 CPU이다.
메모리의 의미
memory
에 대한 제한 및 요청은 바이트 단위로 측정된다.
E, P, T, G, M, K와 같은 접미사 중 하나를 사용하여 메모리를
일반 정수 또는 고정 소수점 숫자로 표현할 수 있다. Ei, Pi, Ti, Gi, Mi, Ki와
같은 2의 거듭제곱을 사용할 수도 있다. 예를 들어, 다음은 대략 동일한 값을 나타낸다.
128974848, 129e6, 129M, 123Mi
다음은 예제이다. 다음 파드에는 두 개의 컨테이너가 있다. 각 컨테이너에는 0.25 cpu와 64MiB(226 바이트)의 메모리 요청이 있다. 각 컨테이너는 0.5 cpu와 128MiB 메모리로 제한된다. 파드에 0.5 cpu와 128 MiB 메모리, 1 cpu와 256MiB 메모리 제한이 있다고 말할 수 있다.
apiVersion: v1
kind: Pod
metadata:
name: frontend
spec:
containers:
- name: app
image: images.my-company.example/app:v4
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
- name: log-aggregator
image: images.my-company.example/log-aggregator:v6
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
리소스 요청이 포함된 파드를 스케줄링하는 방법
파드를 생성할 때, 쿠버네티스 스케줄러는 파드를 실행할 노드를 선택한다. 각 노드는 파드에 제공할 수 있는 CPU와 메모리 양과 같은 각 리소스 타입에 대해 최대 용량을 갖는다. 스케줄러는 각 리소스 타입마다 스케줄된 컨테이너의 리소스 요청 합계가 노드 용량보다 작도록 한다. 참고로 노드의 실제 메모리나 CPU 리소스 사용량은 매우 적지만, 용량 확인에 실패한 경우 스케줄러는 여전히 노드에 파드를 배치하지 않는다. 이는 리소스 사용량이 나중에 증가할 때, 예를 들어, 일일 요청 비율이 최대일 때 노드의 리소스 부족을 방지한다.
리소스 제한이 있는 파드가 실행되는 방법
kubelet은 파드의 컨테이너를 시작할 때, CPU와 메모리 제한을 컨테이너 런타임으로 전달한다.
도커를 사용하는 경우에는 다음과 같다.
spec.containers[].resources.requests.cpu
는 잠재적인 분수이며, 1024를 곱한 값인 코어 값으로 변환된다. 이 숫자 또는 2보다 큰 값은docker run
명령에서--cpu-shares
플래그의 값으로 사용된다.이
spec.containers[].resources.limits.cpu
값은 밀리코어 값으로 변환되고 100을 곱한 값이다. 그 결과 값은 컨테이너가 100ms마다 사용할 수 있는 총 CPU 시간이다. 이 간격 동안 컨테이너는 CPU 시간을 초과하여 사용할 수 없다.참고: 기본 쿼터 기간은 100ms이다. 최소 CPU 쿼터는 1ms이다.spec.containers[].resources.limits.memory
는 정수로 변환되어,docker run
명령에서--memory
플래그의 값으로 사용된다.
컨테이너가 메모리 제한을 초과하면, 컨테이너는 종료될 수 있다. 다시 시작할 수 있으면, 다른 타입의 런타임 오류와 마찬가지로, kubelet이 다시 시작한다.
컨테이너가 메모리 요청을 초과하면, 노드에 메모리가 부족할 때마다 파드가 축출될 수 있다.
컨테이너가 오랫동안 CPU 제한을 초과하는 것은 허용되거나 허용되지 않을 수 있다. 그러나, 과도한 CPU 사용으로 인해 종료되지는 않는다.
리소스 제한으로 인해 컨테이너를 스케줄할 수 없는지 또는 종료 중인지 확인하려면, 문제 해결 섹션을 참조한다.
컴퓨트 및 메모리 리소스 사용량 모니터링
파드의 리소스 사용량은 파드 상태의 일부로 보고된다.
클러스터에서 선택적인 모니터링 도구를 사용할 수 있다면, 메트릭 API에서 직접 또는 모니터링 도구에서 파드 리소스 사용량을 검색할 수 있다.
로컬 임시(ephemeral) 스토리지
Kubernetes v1.10 [beta]
노드에는 로컬에 연결된 쓰기 가능 장치 또는, 때로는 RAM에 의해 지원되는 로컬 임시 스토리지가 있다. "임시"는 내구성에 대한 장기간의 보증이 없음을 의미한다.
파드는 스크래치 공간, 캐싱 및 로그에 대해 임시 로컬 스토리지를 사용한다.
kubelet은 로컬 임시 스토리지를 사용하여 컨테이너에
emptyDir
볼륨을 마운트하기 위해 파드에 스크래치 공간을 제공할 수 있다.
kubelet은 이러한 종류의 스토리지를 사용하여 노드-레벨 컨테이너 로그, 컨테이너 이미지 및 실행 중인 컨테이너의 쓰기 가능 계층을 보유한다.
주의: 노드가 실패하면, 임시 스토리지의 데이터가 손실될 수 있다. 애플리케이션은 로컬 임시 스토리지에서 성능에 대한 SLA(예: 디스크 IOPS)를 기대할 수 없다.
베타 기능에서, 쿠버네티스는 파드가 사용할 수 있는 임시 로컬 스토리지의 양을 추적, 예약 및 제한할 수 있도록 해준다.
로컬 임시 스토리지 구성
쿠버네티스는 노드에서 로컬 임시 스토리지를 구성하는 두 가지 방법을 지원한다.
이 구성에서, 모든 종류의 임시 로컬 데이터(emptyDir
볼륨,
쓰기 가능 계층, 컨테이너 이미지, 로그)를 하나의 파일시스템에 배치한다.
kubelet을 구성하는 가장 효과적인 방법은 이 파일시스템을 쿠버네티스(kubelet) 데이터 전용으로
하는 것이다.
kubelet은 또한 노드-레벨 컨테이너 로그를 작성하고 임시 로컬 스토리지와 유사하게 처리한다.
kubelet은 구성된 로그 디렉터리 내의 파일에 로그를 기록한다(기본적으로
/var/log
). 그리고 로컬에 저장된 다른 데이터에 대한 기본 디렉터리가 있다(기본적으로
/var/lib/kubelet
).
일반적으로, /var/lib/kubelet
와 /var/log
모두 시스템 루트 파일시스템에 위치하고,
그리고 kubelet은 이런 레이아웃을 염두에 두고 설계되었다.
노드는 쿠버네티스에서 사용하지 않는 다른 많은 파일시스템을 가질 수 있다.
사용하고 있는 노드에 실행 중인 파드에서 발생하는 임시 데이터를
위한 파일시스템을 가진다(로그와 emptyDir
볼륨). 이 파일시스템을
다른 데이터(예를 들어, 쿠버네티스와 관련없는 시스템 로그)를 위해 사용할 수 있다. 이 파일시스템은
루트 파일시스템일 수도 있다.
kubelet은 또한 노드-레벨 컨테이너 로그를 첫 번째 파일시스템에 기록하고, 임시 로컬 스토리지와 유사하게 처리한다.
또한 다른 논리 스토리지 장치가 지원하는 별도의 파일시스템을 사용한다. 이 구성에서, 컨테이너 이미지 계층과 쓰기 가능한 계층을 배치하도록 kubelet에 지시하는 디렉터리는 이 두 번째 파일시스템에 있다.
첫 번째 파일시스템에는 이미지 계층이나 쓰기 가능한 계층이 없다.
노드는 쿠버네티스에서 사용하지 않는 다른 많은 파일시스템을 가질 수 있다.
kubelet은 사용 중인 로컬 스토리지 양을 측정할 수 있다. 이것은 다음을 제공한다.
LocalStorageCapacityIsolation
기능 게이트(이 기능이 기본적으로 설정되어 있음)를 활성화하고,- 로컬 임시 스토리지에 대한 지원되는 구성 중 하나를 사용하여 노드를 설정한다.
다른 구성을 사용하는 경우, kubelet은 임시 로컬 스토리지에 대한 리소스 제한을 적용하지 않는다.
참고: kubelet은 로컬 임시 스토리지가 아닌 컨테이너 메모리 사용으로tmpfs
emptyDir 볼륨을 추적한다.
로컬 임시 스토리지에 대한 요청 및 제한 설정
임시-스토리지 를 사용하여 로컬 임시 저장소를 관리할 수 있다. 파드의 각 컨테이너는 다음 중 하나 이상을 지정할 수 있다.
spec.containers[].resources.limits.ephemeral-storage
spec.containers[].resources.requests.ephemeral-storage
ephemeral-storage
에 대한 제한 및 요청은 바이트 단위로 측정된다. E, P, T, G, M, K와
같은 접미사 중 하나를 사용하여 스토리지를 일반 정수 또는 고정 소수점 숫자로 표현할 수 있다.
Ei, Pi, Ti, Gi, Mi, Ki와 같은 2의 거듭제곱을 사용할 수도 있다.
예를 들어, 다음은 대략 동일한 값을 나타낸다.
128974848, 129e6, 129M, 123Mi
다음 예에서, 파드에 두 개의 컨테이너가 있다. 각 컨테이너에는 2GiB의 로컬 임시 스토리지 요청이 있다. 각 컨테이너에는 4GiB의 로컬 임시 스토리지 제한이 있다. 따라서, 파드는 4GiB의 로컬 임시 스토리지 요청과 8GiB 로컬 임시 스토리지 제한을 가진다.
apiVersion: v1
kind: Pod
metadata:
name: frontend
spec:
containers:
- name: app
image: images.my-company.example/app:v4
resources:
requests:
ephemeral-storage: "2Gi"
limits:
ephemeral-storage: "4Gi"
- name: log-aggregator
image: images.my-company.example/log-aggregator:v6
resources:
requests:
ephemeral-storage: "2Gi"
limits:
ephemeral-storage: "4Gi"
임시-스토리지 요청이 있는 파드의 스케줄링 방법
파드를 생성할 때, 쿠버네티스 스케줄러는 파드를 실행할 노드를 선택한다. 각 노드에는 파드에 제공할 수 있는 최대 임시 스토리지 공간이 있다. 자세한 정보는, 노드 할당 가능을 참조한다.
스케줄러는 스케줄된 컨테이너의 리소스 요청 합계가 노드 용량보다 작도록 한다.
임시 스토리지 소비 관리
kubelet이 로컬 임시 스토리지를 리소스로 관리하는 경우, kubelet은 다음에서 스토리지 사용을 측정한다.
- tmpfs
emptyDir
볼륨을 제외한emptyDir
볼륨 - 노드-레벨 로그가 있는 디렉터리
- 쓰기 가능한 컨테이너 계층
허용하는 것보다 더 많은 임시 스토리지를 파드가 사용하는 경우, kubelet은 파드 축출을 트리거하는 축출 신호를 설정한다.
컨테이너-레벨 격리의 경우, 컨테이너의 쓰기 가능한 계층과 로그 사용량이 스토리지 제한을 초과하면, kubelet은 파드를 축출하도록 표시한다.
파드-레벨 격리에 대해 kubelet은 해당 파드의 컨테이너에 대한 제한을 합하여
전체 파드 스토리지 제한을 해결한다. 이 경우, 모든
컨테이너와 파드의 emptyDir
볼륨의 로컬 임시 스토리지 사용량 합계가
전체 파드 스토리지 제한을 초과하면, kubelet은 파드를 축출 대상으로
표시한다.
kubelet은 파드 스토리지 사용을 측정하는 다양한 방법을 지원한다.
kubelet은 각 emptyDir
볼륨, 컨테이너 로그 디렉터리 및 쓰기 가능한 컨테이너 계층을
스캔하는 정기적인 스케줄 검사를 수행한다.
스캔은 사용된 공간의 양을 측정한다.
참고:이 모드에서, kubelet은 삭제된 파일의 열린 파일 디스크립터를 추적하지 않는다.
여러분(또는 컨테이너)이
emptyDir
볼륨 안에 파일을 생성하면, 그 파일이 열리고, 파일이 열려있는 동안 파일을 삭제하면, 삭제된 파일의 inode는 해당 파일을 닫을 때까지 유지되지만 kubelet은 사용 중인 공간으로 분류하지 않는다.
Kubernetes v1.15 [alpha]
프로젝트 쿼터는 파일시스템에서 스토리지 사용을 관리하기 위한 운영체제 레벨의 기능이다. 쿠버네티스를 사용하면, 스토리지 사용을 모니터링하기 위해 프로젝트 쿼터를 사용할 수 있다. 노드에서 'emptyDir' 볼륨을 지원하는 파일시스템이 프로젝트 쿼터 지원을 제공하는지 확인한다. 예를 들어, XFS와 ext4fs는 프로젝트 쿼터를 지원한다.
참고: 프로젝트 쿼터를 통해 스토리지 사용을 모니터링할 수 있다. 이는 제한을 강제하지 않는다.
쿠버네티스는 1048576
부터 프로젝트 ID를 사용한다. 사용 중인 ID는
/etc/projects
와 /etc/projid
에 등록되어 있다. 이 범위의 프로젝트 ID가
시스템에서 다른 목적으로 사용되는 경우, 쿠버네티스가
이를 사용하지 않도록 해당 프로젝트 ID를 /etc/projects
와 /etc/projid
에
등록해야 한다.
쿼터는 디렉터리 검색보다 빠르고 정확하다. 디렉터리가 프로젝트에 할당되면, 디렉터리 아래에 생성된 모든 파일이 해당 프로젝트에 생성되며, 커널은 해당 프로젝트의 파일에서 사용 중인 블록 수를 추적하기만 하면 된다. 파일이 생성되고 삭제되었지만, 열린 파일 디스크립터가 있으면, 계속 공간을 소비한다. 쿼터 추적은 공간을 정확하게 기록하는 반면 디렉터리 스캔은 삭제된 파일이 사용한 스토리지를 간과한다.
프로젝트 쿼터를 사용하려면, 다음을 수행해야 한다.
kubelet 구성의
featureGates
필드 또는--feature-gates
커맨드 라인 플래그를 사용하여LocalStorageCapacityIsolationFSQuotaMonitoring=true
기능 게이트를 활성화한다.루트 파일시스템(또는 선택적인 런타임 파일시스템)에 프로젝트 쿼터가 활성화되어 있는지 확인한다. 모든 XFS 파일시스템은 프로젝트 쿼터를 지원한다. ext4 파일시스템의 경우, 파일시스템이 마운트되지 않은 상태에서 프로젝트 쿼터 추적 기능을 활성화해야 한다.
# ext4인 /dev/block-device가 마운트되지 않은 경우 sudo tune2fs -O project -Q prjquota /dev/block-device
루트 파일시스템(또는 선택적인 런타임 파일시스템)은 프로젝트 쿼터를 활성화한 상태에서 마운트해야 힌다. XFS와 ext4fs 모두에서, 마운트 옵션의 이름은
prjquota
이다.
확장된 리소스
확장된 리소스는 kubernetes.io
도메인 외부의 전체 주소(fully-qualified)
리소스 이름이다. 쿠버네티스에 내장되지 않은 리소스를 클러스터 운영자가 알리고
사용자는 사용할 수 있다.
확장된 리소스를 사용하려면 두 단계가 필요한다. 먼저, 클러스터 운영자는 확장된 리소스를 알려야 한다. 둘째, 사용자는 파드의 확장된 리소스를 요청해야 한다.
확장된 리소스 관리
노드-레벨의 확장된 리소스
노드-레벨의 확장된 리소스는 노드에 연결된다.
장치 플러그인 관리 리소스
각 노드에서 장치 플러그인 관리 리소스를 알리는 방법은 장치 플러그인을 참조한다.
기타 리소스
새로운 노드-레벨의 확장된 리소스를 알리기 위해, 클러스터 운영자는
API 서버에 PATCH
HTTP 요청을 제출하여 클러스터의
노드에 대해 status.capacity
에서 사용할 수 있는 수량을 지정할 수 있다. 이 작업
후에는, 노드의 status.capacity
에 새로운 리소스가 포함된다. 이
status.allocatable
필드는 kubelet에 의해 비동기적으로 새로운
리소스로 자동 업데이트된다. 참고로 스케줄러가 파드 적합성을 평가할 때 노드
status.allocatable
값을 사용하므로, 노드 용량을
새 리소스로 패치하는 것과 해당 노드에서 리소스를 스케줄하도록 요청하는 첫 번째 파드
사이에 약간의 지연이 있을 수 있다.
예제:
다음은 curl
을 사용하여 마스터가 k8s-master
인 노드 k8s-node-1
에
5개의 "example.com/foo" 리소스를 알리는 HTTP 요청을 구성하는 방법을
보여주는 예이다.
curl --header "Content-Type: application/json-patch+json" \
--request PATCH \
--data '[{"op": "add", "path": "/status/capacity/example.com~1foo", "value": "5"}]' \
http://k8s-master:8080/api/v1/nodes/k8s-node-1/status
참고: 앞의 요청에서,~1
은 패치 경로에서 문자/
의 인코딩이다. JSON-Patch의 작업 경로 값은 JSON-Pointer로 해석된다. 더 자세한 내용은, IETF RFC 6901, 섹션 3을 참조한다.
클러스터-레벨의 확장된 리소스
클러스터-레벨의 확장된 리소스는 노드에 연결되지 않는다. 이들은 일반적으로 리소스 소비와 리소스 쿼터를 처리하는 스케줄러 익스텐더(extender)에 의해 관리된다.
스케줄러 정책 구성에서 스케줄러 익스텐더가 처리하는 확장된 리소스를 지정할 수 있다.
예제:
스케줄러 정책에 대한 다음의 구성은 클러스터-레벨의 확장된 리소스 "example.com/foo"가 스케줄러 익스텐더에 의해 처리됨을 나타낸다.
- 파드가 "example.com/foo"를 요청하는 경우에만 스케줄러가 파드를 스케줄러 익스텐더로 보낸다.
- 이
ignoredByScheduler
필드는 스케줄러가PodFitsResources
속성에서 "example.com/foo" 리소스를 확인하지 않도록 지정한다.
{
"kind": "Policy",
"apiVersion": "v1",
"extenders": [
{
"urlPrefix":"<extender-endpoint>",
"bindVerb": "bind",
"managedResources": [
{
"name": "example.com/foo",
"ignoredByScheduler": true
}
]
}
]
}
확장된 리소스 소비
사용자는 CPU와 메모리 같은 파드 스펙의 확장된 리소스를 사용할 수 있다. 스케줄러는 리소스 어카운팅(resource accounting)을 관리하여 사용 가능한 양보다 많은 양의 리소스가 여러 파드에 동시에 할당되지 않도록 한다.
API 서버는 확장된 리소스의 수량을 정수로 제한한다.
유효한 수량의 예로는 3
, 3000m
그리고 3Ki
를 들 수 있다. 유효하지 않은
수량의 예는 0.5
와 1500m
이다.
참고: 확장된 리소스는 불명확한 정수 리소스를 대체한다. 사용자는 예약된kubernetes.io
이외의 모든 도메인 이름 접두사를 사용할 수 있다.
파드에서 확장된 리소스를 사용하려면, 컨테이너 사양에서 spec.containers[].resources.limits
맵에 리소스 이름을 키로 포함한다.
참고: 확장된 리소스는 오버커밋할 수 없으므로, 컨테이너 사양에 둘 다 있으면 요청과 제한이 동일해야 한다.
파드는 CPU, 메모리 및 확장된 리소스를 포함하여 모든 리소스 요청이
충족되는 경우에만 예약된다. 리소스 요청을 충족할 수 없다면
파드는 PENDING
상태를 유지한다.
예제:
아래의 파드는 2개의 CPU와 1개의 "example.com/foo"(확장된 리소스)를 요청한다.
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-container
image: myimage
resources:
requests:
cpu: 2
example.com/foo: 1
limits:
example.com/foo: 1
PID 제한
프로세스 ID(PID) 제한은 kubelet의 구성에 대해 주어진 파드가 사용할 수 있는 PID 수를 제한할 수 있도록 허용한다. 자세한 내용은 Pid 제한을 참고한다.
문제 해결
내 파드가 failedScheduling 이벤트 메시지로 보류 중이다
파드가 배치될 수 있는 노드를 스케줄러가 찾을 수 없으면, 노드를 찾을 수 있을 때까지 파드는 스케줄되지 않은 상태로 유지한다. 스케줄러가 다음과 같이 파드의 위치를 찾지 못하면 이벤트가 생성된다.
kubectl describe pod frontend | grep -A 3 Events
Events:
FirstSeen LastSeen Count From Subobject PathReason Message
36s 5s 6 {scheduler } FailedScheduling Failed for reason PodExceedsFreeCPU and possibly others
위의 예에서, 노드의 CPU 리소스가 충분하지 않아 이름이 "frontend"인 파드를 스케줄하지 못했다. 비슷한 메시지로 메모리 부족(PodExceedsFreeMemory)으로 인한 장애도 알릴 수 있다. 일반적으로, 파드가 이 타입의 메시지로 보류 중인 경우, 몇 가지 시도해 볼 것들이 있다.
- 클러스터에 더 많은 노드를 추가한다.
- 불필요한 파드를 종료하여 보류 중인 파드를 위한 공간을 확보한다.
- 파드가 모든 노드보다 크지 않은지 확인한다. 예를 들어, 모든
노드의 용량이
cpu: 1
인 경우,cpu: 1.1
요청이 있는 파드는 절대 스케줄되지 않는다.
kubectl describe nodes
명령으로 노드 용량과 할당된 양을
확인할 수 있다. 예를 들면, 다음과 같다.
kubectl describe nodes e2e-test-node-pool-4lw4
Name: e2e-test-node-pool-4lw4
[ ... 명확하게 하기 위해 라인들을 제거함 ...]
Capacity:
cpu: 2
memory: 7679792Ki
pods: 110
Allocatable:
cpu: 1800m
memory: 7474992Ki
pods: 110
[ ... 명확하게 하기 위해 라인들을 제거함 ...]
Non-terminated Pods: (5 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits
--------- ---- ------------ ---------- --------------- -------------
kube-system fluentd-gcp-v1.38-28bv1 100m (5%) 0 (0%) 200Mi (2%) 200Mi (2%)
kube-system kube-dns-3297075139-61lj3 260m (13%) 0 (0%) 100Mi (1%) 170Mi (2%)
kube-system kube-proxy-e2e-test-... 100m (5%) 0 (0%) 0 (0%) 0 (0%)
kube-system monitoring-influxdb-grafana-v4-z1m12 200m (10%) 200m (10%) 600Mi (8%) 600Mi (8%)
kube-system node-problem-detector-v0.1-fj7m3 20m (1%) 200m (10%) 20Mi (0%) 100Mi (1%)
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
CPU Requests CPU Limits Memory Requests Memory Limits
------------ ---------- --------------- -------------
680m (34%) 400m (20%) 920Mi (11%) 1070Mi (13%)
위의 출력에서, 파드가 1120m 이상의 CPU 또는 6.23Gi의 메모리를 요청하는 것은 노드에 맞지 않음을 알 수 있다.
Pods
섹션을 살펴보면, 파드가 노드에서 공간을 차지하는 것을
볼 수 있다.
시스템 데몬이 사용 가능한 리소스의 일부를 사용하기 때문에, 파드에
사용 가능한 리소스의 양이 노드 용량보다 적다. allocatable
필드
NodeStatus는
파드가 사용할 수 있는 리소스의 양을 제공한다. 자세한 정보는
노드 할당 가능 리소스를 참조한다.
리소스 쿼터 기능은 소비될 수 있는 리소스의 총량을 제한하도록 구성할 수 있다. 네임스페이스와 함께 사용하면, 한 팀이 모든 리소스를 사용하는 경우를 방지할 수 있다.
내 컨테이너가 종료되었다
리소스가 부족하여 컨테이너가 종료될 수 있다. 리소스
제한에 도달하여 컨테이너가 종료되고 있는지 확인하려면,
관심있는 파드에 대해 kubectl describe pod
를 호출한다.
kubectl describe pod simmemleak-hra99
Name: simmemleak-hra99
Namespace: default
Image(s): saadali/simmemleak
Node: kubernetes-node-tf0f/10.240.216.66
Labels: name=simmemleak
Status: Running
Reason:
Message:
IP: 10.244.2.75
Replication Controllers: simmemleak (1/1 replicas created)
Containers:
simmemleak:
Image: saadali/simmemleak
Limits:
cpu: 100m
memory: 50Mi
State: Running
Started: Tue, 07 Jul 2015 12:54:41 -0700
Last Termination State: Terminated
Exit Code: 1
Started: Fri, 07 Jul 2015 12:54:30 -0700
Finished: Fri, 07 Jul 2015 12:54:33 -0700
Ready: False
Restart Count: 5
Conditions:
Type Status
Ready False
Events:
FirstSeen LastSeen Count From SubobjectPath Reason Message
Tue, 07 Jul 2015 12:53:51 -0700 Tue, 07 Jul 2015 12:53:51 -0700 1 {scheduler } scheduled Successfully assigned simmemleak-hra99 to kubernetes-node-tf0f
Tue, 07 Jul 2015 12:53:51 -0700 Tue, 07 Jul 2015 12:53:51 -0700 1 {kubelet kubernetes-node-tf0f} implicitly required container POD pulled Pod container image "k8s.gcr.io/pause:0.8.0" already present on machine
Tue, 07 Jul 2015 12:53:51 -0700 Tue, 07 Jul 2015 12:53:51 -0700 1 {kubelet kubernetes-node-tf0f} implicitly required container POD created Created with docker id 6a41280f516d
Tue, 07 Jul 2015 12:53:51 -0700 Tue, 07 Jul 2015 12:53:51 -0700 1 {kubelet kubernetes-node-tf0f} implicitly required container POD started Started with docker id 6a41280f516d
Tue, 07 Jul 2015 12:53:51 -0700 Tue, 07 Jul 2015 12:53:51 -0700 1 {kubelet kubernetes-node-tf0f} spec.containers{simmemleak} created Created with docker id 87348f12526a
앞의 예제에서, Restart Count: 5
표시는 파드의 simmemleak
컨테이너가 종료되고 5번 다시 시작되었음을 나타낸다.
이전에 종료된 컨테이너의 상태를 가져오기 위해 -o go-template=...
옵션을 사용해서
kubectl get pod
를 호출할 수 있다.
kubectl get pod -o go-template='{{range.status.containerStatuses}}{{"Container Name: "}}{{.name}}{{"\r\nLastState: "}}{{.lastState}}{{end}}' simmemleak-hra99
Container Name: simmemleak
LastState: map[terminated:map[exitCode:137 reason:OOM Killed startedAt:2015-07-07T20:58:43Z finishedAt:2015-07-07T20:58:43Z containerID:docker://0e4095bba1feccdfe7ef9fb6ebffe972b4b14285d5acdec6f0d3ae8a22fad8b2]]
컨테이너가 reason:OOM Killed
(OOM
은 메모리 부족(Out Of Memory)의 약자) 때문에 종료된 것을 알 수 있다.
다음 내용
- 컨테이너와 파드에 메모리 리소스를 할당하는 핸즈온 경험을 해보자.
- 컨테이너와 파드에 CPU 리소스를 할당하는 핸즈온 경험을 해보자.
- 요청과 제한의 차이점에 대한 자세한 내용은, 리소스 QoS를 참조한다.
- 컨테이너 API 레퍼런스 읽어보기
- ResourceRequirements API 레퍼런스 읽어보기
- XFS의 프로젝트 쿼터에 대해 읽어보기
- kube-scheduler 정책 레퍼런스 (v1)에 대해 더 읽어보기
5 - kubeconfig 파일을 사용하여 클러스터 접근 구성하기
kubeconfig 파일들을 사용하여 클러스터, 사용자, 네임스페이스 및 인증 메커니즘에 대한 정보를 관리하자.
kubectl
커맨드라인 툴은 kubeconfig 파일을 사용하여
클러스터의 선택과
클러스터의 API 서버와의 통신에 필요한 정보를 찾는다.
참고: 클러스터에 대한 접근을 구성하는 데 사용되는 파일을 kubeconfig 파일 이라 한다. 이는 구성 파일을 참조하는 일반적인 방법을 의미한다.kubeconfig
라는 이름의 파일이 있다는 의미는 아니다.
기본적으로 kubectl
은 $HOME/.kube
디렉터리에서 config
라는 이름의 파일을 찾는다.
KUBECONFIG
환경 변수를 설정하거나
--kubeconfig
플래그를 지정해서
다른 kubeconfig 파일을 사용할 수 있다.
kubeconfig 파일을 생성하고 지정하는 단계별 지시사항은 다중 클러스터로 접근 구성하기를 참조한다.
다중 클러스터, 사용자와 인증 메커니즘 지원
여러 클러스터가 있고, 사용자와 구성 요소가 다양한 방식으로 인증한다고 가정하자. 예를 들면 다음과 같다.
- 실행 중인 kubelet은 인증서를 이용하여 인증할 수 있다.
- 사용자는 토큰으로 인증할 수 있다.
- 관리자는 개별 사용자에게 제공하는 인증서 집합을 가지고 있다.
kubeconfig 파일을 사용하면 클러스터와 사용자와 네임스페이스를 구성할 수 있다. 또한 컨텍스트를 정의하여 빠르고 쉽게 클러스터와 네임스페이스 간에 전환할 수 있다.
컨텍스트
kubeconfig에서 컨텍스트 요소는 편리한 이름으로 접속 매개 변수를 묶는데 사용한다.
각 컨텍스트는 클러스터, 네임스페이스와 사용자라는 세 가지 매개 변수를 가진다.
기본적으로 kubectl
커맨드라인 툴은 현재 컨텍스트 의 매개 변수를
사용하여 클러스터와 통신한다.
현재 컨택스트를 선택하려면 다음을 실행한다.
kubectl config use-context
KUBECONFIG 환경 변수
KUBECONFIG
환경 변수는 kubeconfig 파일 목록을 보유한다.
리눅스 및 Mac의 경우 이는 콜론(:)으로 구분된 목록이다.
윈도우는 세미콜론(;)으로 구분한다. KUBECONFIG
환경 변수가 필수는 아니다.
KUBECONFIG
환경 변수가 없으면,
kubectl
은 기본 kubeconfig 파일인 $HOME/.kube/config
를 사용한다.
KUBECONFIG
환경 변수가 존재하면, kubectl
은
KUBECONFIG
환경 변수에 나열된 파일을 병합한 결과 형태의
효과적 구성을 이용한다.
kubeconfig 파일 병합
구성을 보려면, 다음 커맨드를 입력한다.
kubectl config view
앞서 설명한 것처럼, 이 출력 내용은 단일 kubeconfig 파일이나 여러 kubeconfig 파일을 병합한 결과 일 수 있다.
다음은 kubeconfig 파일을 병합할 때에 kubectl
에서 사용하는 규칙이다.
--kubeconfig
플래그를 설정했으면, 지정한 파일만 사용한다. 병합하지 않는다. 이 플래그는 오직 한 개 인스턴스만 허용한다.그렇지 않고,
KUBECONFIG
환경 변수를 설정하였다면 병합해야 하는 파일의 목록으로 사용한다.KUBECONFIG
환경 변수의 나열된 파일은 다음 규칙에 따라 병합한다.- 빈 파일명은 무시한다.
- 역 직렬화 불가한 파일 내용에 대해서 오류를 일으킨다.
- 특정 값이나 맵 키를 설정한 첫 번째 파일을 우선한다.
- 값이나 맵 키를 변경하지 않는다.
예:
현재 컨텍스트
를 설정할 첫 번째 파일의 컨택스트를 유지한다. 예: 두 파일이red-user
를 지정했다면, 첫 번째 파일의red-user
값만을 사용한다. 두 번째 파일의red-user
하위에 충돌하지 않는 항목이 있어도 버린다.
KUBECONFIG
환경 변수 설정의 예로, KUBECONFIG 환경 변수 설정를 참조한다.그렇지 않다면, 병합하지 않고 기본 kubeconfig 파일인
$HOME/.kube/config
를 사용한다.이 체인에서 첫 번째를 기반으로 사용할 컨텍스트를 결정한다.
- 커맨드라인 플래그의
--context
를 사용한다. - 병합된 kubeconfig 파일에서
current-context
를 사용한다.
이 시점에서는 빈 컨텍스트도 허용한다.
- 커맨드라인 플래그의
클러스터와 사용자를 결정한다. 이 시점에서는 컨텍스트가 있을 수도 있고 없을 수도 있다. 사용자에 대해 한 번, 클러스터에 대해 한 번 총 두 번에 걸친 이 체인에서 첫 번째 것을 기반으로 클러스터와 사용자를 결정한다.
- 커맨드라인 플래그가 존재하면,
--user
또는--cluster
를 사용한다. - 컨텍스트가 비어있지 않다면, 컨텍스트에서 사용자 또는 클러스터를 가져온다.
이 시점에서는 사용자와 클러스터는 비워둘 수 있다.
- 커맨드라인 플래그가 존재하면,
사용할 실제 클러스터 정보를 결정한다. 이 시점에서 클러스터 정보가 있을 수 있고 없을 수도 있다. 이 체인을 기반으로 클러스터 정보를 구축한다. 첫 번째 것을 사용한다.
- 커맨드라인 플래그가 존재하면,
--server
,--certificate-authority
,--insecure-skip-tls-verify
를 사용한다. - 병합된 kubeconfig 파일에서 클러스터 정보 속성이 있다면 사용한다.
- 서버 위치가 없다면 실패한다.
- 커맨드라인 플래그가 존재하면,
사용할 실제 사용자 정보를 결정한다. 사용자 당 하나의 인증 기법만 허용하는 것을 제외하고는 클러스터 정보와 동일한 규칙을 사용하여 사용자 정보를 작성한다.
- 커맨드라인 플래그가 존재하면,
--client-certificate
,--client-key
,--username
,--password
,--token
을 사용한다. - 병합된 kubeconfig 파일에서
user
필드를 사용한다. - 충돌하는 두 가지 기법이 있다면 실패한다.
- 커맨드라인 플래그가 존재하면,
여전히 누락된 정보는 기본 값을 사용하고 인증 정보를 묻는 메시지가 표시될 수 있다.
파일 참조
kubeconfig 파일에서 파일과 경로 참조는 kubeconfig 파일의 위치와 관련 있다.
커맨드라인 상에 파일 참조는 현재 디렉터리를 기준으로 한다.
$HOME/.kube/config
에서 상대 경로는 상대적으로, 절대 경로는
절대적으로 저장한다.
다음 내용
6 - 파드 우선순위(priority)와 선점(preemption)
Kubernetes v1.14 [stable]
파드는 우선순위 를 가질 수 있다. 우선순위는 다른 파드에 대한 상대적인 파드의 중요성을 나타낸다. 파드를 스케줄링할 수 없는 경우, 스케줄러는 우선순위가 낮은 파드를 선점(축출)하여 보류 중인 파드를 스케줄링할 수 있게 한다.
경고:모든 사용자를 신뢰할 수 없는 클러스터에서, 악의적인 사용자가 우선순위가 가장 높은 파드를 생성하여 다른 파드가 축출되거나 스케줄링되지 않을 수 있다. 관리자는 리소스쿼터를 사용하여 사용자가 우선순위가 높은 파드를 생성하지 못하게 할 수 있다.
자세한 내용은 기본적으로 프라이어리티 클래스(Priority Class) 소비 제한을 참고한다.
우선순위와 선점을 사용하는 방법
우선순위와 선점을 사용하려면 다음을 참고한다.
하나 이상의 프라이어리티클래스를 추가한다.
추가된 프라이어리티클래스 중 하나에
priorityClassName
이 설정된 파드를 생성한다. 물론 파드를 직접 생성할 필요는 없다. 일반적으로 디플로이먼트와 같은 컬렉션 오브젝트의 파드 템플릿에priorityClassName
을 추가한다.
이 단계에 대한 자세한 내용은 계속 읽어보자.
참고: 쿠버네티스는 이미system-cluster-critical
과system-node-critical
, 두 개의 프라이어리티클래스를 제공한다. 이들은 일반적인 클래스이며 중요한(critical) 컴포넌트가 항상 먼저 스케줄링이 되도록 하는 데 사용된다.
프라이어리티클래스
프라이어리티클래스는 프라이어리티 클래스 이름에서 우선순위의 정수 값으로의 매핑을
정의하는 네임스페이스가 아닌(non-namespaced) 오브젝트이다. 이름은
프라이어리티클래스 오브젝트의 메타데이터의 name
필드에 지정된다. 값은
필수 value
필드에 지정되어 있다. 값이 클수록, 우선순위가
높다.
프라이어리티클래스 오브젝트의 이름은 유효한
DNS 서브 도메인 이름이어야 하며,
system-
접두사를 붙일 수 없다.
프라이어리티클래스 오브젝트는 10억 이하의 32비트 정수 값을 가질 수 있다. 일반적으로 선점하거나 축출해서는 안되는 중요한 시스템 파드에는 더 큰 숫자가 예약되어 있다. 클러스터 관리자는 원하는 각 매핑에 대해 프라이어리티클래스 오브젝트를 하나씩 생성해야 한다.
프라이어리티클래스에는 globalDefault
와 description
두 개의 선택적인 필드도 있다.
globalDefault
필드는 이 프라이어리티클래스의 값을 priorityClassName
이 없는
파드에 사용해야 함을 나타낸다. 시스템에서 globalDefault
가 true
로 설정된
프라이어리티클래스는 하나만 존재할 수 있다. globalDefault
가 설정된
프라이어리티클래스가 없을 경우, priorityClassName
이 없는 파드의
우선순위는 0이다.
description
필드는 임의의 문자열이다. 이 필드는 이 프라이어리티클래스를 언제
사용해야 하는지를 클러스터 사용자에게 알려주기 위한 것이다.
PodPriority와 기존 클러스터에 대한 참고 사항
이 기능없이 기존 클러스터를 업그레이드 하는 경우, 기존 파드의 우선순위는 사실상 0이다.
globalDefault
가true
로 설정된 프라이어리티클래스를 추가해도 기존 파드의 우선순위는 변경되지 않는다. 이러한 프라이어리티클래스의 값은 프라이어리티클래스를 추가한 후 생성된 파드에만 사용된다.프라이어리티클래스를 삭제하면, 삭제된 프라이어리티클래스의 이름을 사용하는 기존 파드는 변경되지 않고 남아있지만, 삭제된 프라이어리티클래스의 이름을 사용하는 파드는 더 이상 생성할 수 없다.
프라이어리티클래스 예제
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "이 프라이어리티 클래스는 XYZ 서비스 파드에만 사용해야 한다."
비-선점 프라이어리티클래스
Kubernetes v1.19 [beta]
PreemptionPolicy: Never
를 가진 파드는 낮은 우선순위 파드의 스케줄링 대기열의
앞쪽에 배치되지만,
그 파드는 다른 파드를 축출할 수 없다.
스케줄링 대기 중인 비-선점 파드는 충분한 리소스가 확보되고
스케줄링될 수 있을 때까지
스케줄링 대기열에 대기한다.
다른 파드와 마찬가지로,
비-선점 파드는
스케줄러 백오프(back-off)에 종속된다.
이는 스케줄러가 이러한 파드를 스케줄링하려고 시도하고 스케줄링할 수 없는 경우,
더 적은 횟수로 재시도하여,
우선순위가 낮은 다른 파드를 미리 스케줄링할 수 있음을 의미한다.
비-선점 파드는 다른 우선순위가 높은 파드에 의해 축출될 수 있다.
PreemptionPolicy
는 기본값으로 PreemptLowerPriority
로 설정되어,
해당 프라이어리티클래스의 파드가 우선순위가 낮은 파드를 축출할 수
있다(기존의 기본 동작과 동일).
PreemptionPolicy
가 Never
로 설정된 경우,
해당 프라이어리티클래스의 파드는 비-선점될 것이다.
예제 유스케이스는 데이터 과학 관련 워크로드이다.
사용자는 다른 워크로드보다 우선순위가 높은 잡(job)을 제출할 수 있지만,
실행 중인 파드를 축출하여 기존의 작업을 삭제하지는 않을 것이다.
클러스터 리소스가 "자연스럽게" 충분히 사용할 수 있게 되면,
PreemptionPolicy: Never
의 우선순위가 높은 잡이
다른 대기 중인 파드보다 먼저 스케줄링된다.
비-선점 프라이어리티클래스 예제
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority-nonpreempting
value: 1000000
preemptionPolicy: Never
globalDefault: false
description: "이 프라이어리티 클래스는 다른 파드를 축출하지 않는다."
파드 우선순위
프라이어리티클래스가 하나 이상 있으면, 그것의 명세에서 이들 프라이어리티클래스 이름 중 하나를
지정하는 파드를 생성할 수 있다. 우선순위 어드미션
컨트롤러는 priorityClassName
필드를 사용하고 우선순위의 정수 값을
채운다. 프라이어리티 클래스를 찾을 수 없으면, 파드가 거부된다.
다음의 YAML은 이전 예제에서 생성된 프라이어리티클래스를 사용하는 파드 구성의 예이다. 우선순위 어드미션 컨트롤러는 명세를 확인하고 파드의 우선순위를 1000000으로 해석한다.
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
priorityClassName: high-priority
스케줄링 순서에 대한 파드 우선순위의 영향
파드 우선순위가 활성화되면, 스케줄러가 우선순위에 따라 보류 중인 파드를 주문하고 보류 중인 파드는 스케줄링 대기열에서 우선순위가 낮은 다른 보류 중인 파드보다 우선한다. 결과적으로, 스케줄링 요구 사항이 충족되는 경우 우선순위가 더 낮은 파드보다 우선순위가 높은 파드가 더 빨리 스케줄링될 수 있다. 이러한 파드를 스케줄링할 수 없는 경우, 스케줄러는 계속 진행하고 우선순위가 낮은 다른 파드를 스케줄링하려고 한다.
선점
파드가 생성되면, 대기열로 이동하여 스케줄링을 기다린다. 스케줄러가 대기열에서 파드를 선택하여 노드에 스케줄링하려고 한다. 파드의 지정된 모든 요구 사항을 충족하는 노드가 없으면, 보류 중인 파드에 대해 선점 로직이 트리거된다. 보류 중인 파드를 P라 하자. 선점 로직은 P보다 우선순위가 낮은 하나 이상의 파드를 제거하면 해당 노드에서 P를 스케줄링할 수 있는 노드를 찾는다. 이러한 노드가 발견되면, 하나 이상의 우선순위가 낮은 파드가 노드에서 축출된다. 파드가 축출된 후, P는 노드에 스케줄링될 수 있다.
사용자 노출 정보
파드 P가 노드 N에서 하나 이상의 파드를 축출할 경우, 파드 P의 상태 nominatedNodeName
필드는 노드 N의 이름으로 설정된다. 이 필드는 스케줄러가 파드 P에
예약된 리소스를 추적하는 데 도움이 되고 사용자에게 클러스터의 선점에 대한
정보를 제공한다.
파드 P는 반드시 "지정된 노드"로 스케줄링되지는 않는다.
피해자 파드가 축출된 후, 그것은 정상적(graceful)으로 종료되는 기간을 갖는다.
스케줄러가 종료될 피해자 파드를 기다리는 동안 다른 노드를 사용할 수
있게 되면, 스케줄러는 파드 P를 스케줄링하기 위해 다른 노드를 사용한다. 그 결과,
파드 스펙의 nominatedNodeName
과 nodeName
은 항상 동일하지 않다. 또한,
스케줄러가 노드 N에서 파드를 축출했지만, 파드 P보다 우선순위가 높은 파드가
도착하면, 스케줄러가 노드 N에 새로운 우선순위가 높은 파드를 제공할 수 있다. 이러한
경우, 스케줄러는 파드 P의 nominatedNodeName
을 지운다. 이렇게하면, 스케줄러는
파드 P가 다른 노드에서 파드를 축출할 수 있도록 한다.
선점의 한계
선점 피해자의 정상적인 종료
파드가 축출되면, 축출된 피해자 파드는 정상적인 종료 기간을 가진다. 피해자 파드는 작업을 종료하고 빠져나가는 데(exit) 많은 시간을 가진다. 그렇지 않으면, 파드는 강제종료(kill) 된다. 이 정상적인 종료 기간은 스케줄러가 파드를 축출하는 지점과 보류 중인 파드(P)를 노드(N)에서 스케줄링할 수 있는 시점 사이의 시간 간격을 만든다. 그 동안, 스케줄러는 보류 중인 다른 파드를 계속 스케줄링한다. 피해자 파드가 빠져나가거나 종료되면, 스케줄러는 보류 대기열에서 파드를 스케줄하려고 한다. 따라서, 일반적으로 스케줄러가 피해자 파드를 축출하는 시점과 파드 P가 스케줄링된 시점 사이에 시간 간격이 있다. 이러한 차이를 최소화하기 위해, 우선순위가 낮은 파드의 정상적인 종료 기간을 0 또는 작은 수로 설정할 수 있다.
PodDisruptionBudget을 지원하지만, 보장하지 않음
Pod Disruption Budget(PDB)은 애플리케이션 소유자가 자발적 중단에서 동시에 다운된 복제된 애플리케이션의 파드 수를 제한할 수 있다. 쿠버네티스는 파드를 선점할 때 PDB를 지원하지만, PDB를 따르는 것이 최선의 노력이다. 스케줄러는 선점에 의해 PDB를 위반하지 않은 피해자 파드를 찾으려고 하지만, 그러한 피해자 파드가 발견되지 않으면, 선점은 여전히 발생하며, PDB를 위반하더라도 우선순위가 낮은 파드는 제거된다.
우선순위가 낮은 파드에 대한 파드-간 어피니티
이 질문에 대한 답변이 '예'인 경우에만 노드가 선점 대상으로 간주된다. "대기 중인 파드보다 우선순위가 낮은 모든 파드가 노드에서 제거되면, 보류 중인 파드를 노드에 스케줄링할 수 있습니까?"
참고: 선점으로 우선순위가 낮은 모든 파드를 반드시 제거할 필요는 없다. 우선순위가 낮은 모든 파드보다 적은 수를 제거하여 보류 중인 파드를 스케줄링할 수 있는 경우, 우선순위가 낮은 파드의 일부만 제거된다. 그럼에도 불구하고, 앞의 질문에 대한 대답은 '예'여야 한다. 답변이 '아니오'인 경우, 노드가 선점 대상으로 간주되지 않는다.
보류 중인 파드가 노드에 있는 하나 이상의 우선순위가 낮은 파드에 대한 파드-간 어피니티를 가진 경우에, 우선순위가 낮은 파드가 없을 때 파드-간 어피니티 규칙을 충족할 수 없다. 이 경우, 스케줄러는 노드의 파드를 축출하지 않는다. 대신, 다른 노드를 찾는다. 스케줄러가 적합한 노드를 찾거나 찾지 못할 수 있다. 보류 중인 파드를 스케줄링할 수 있다는 보장은 없다.
이 문제에 대한 권장 솔루션은 우선순위가 같거나 높은 파드에 대해서만 파드-간 어피니티를 생성하는 것이다.
교차 노드 선점
보류 중인 파드 P가 노드 N에 스케줄링될 수 있도록 노드 N이 선점 대상으로 고려되고 있다고 가정한다. 다른 노드의 파드가 축출된 경우에만 P가 N에서 실행 가능해질 수 있다. 예를 들면 다음과 같다.
- 파드 P는 노드 N에 대해 고려된다.
- 파드 Q는 노드 N과 동일한 영역의 다른 노드에서 실행 중이다.
- 파드 P는 파드 Q(
topologyKey: topology.kubernetes.io/zone
)와 영역(zone) 전체의 안티-어피니티를 갖는다. - 영역에서 파드 P와 다른 파드 간의 안티-어피니티에 대한 다른 경우는 없다.
- 노드 N에서 파드 P를 스케줄링하기 위해, 파드 Q를 축출할 수 있지만, 스케줄러는 교차-노드 선점을 수행하지 않는다. 따라서, 파드 P가 노드 N에서 스케줄링할 수 없는 것으로 간주된다.
파드 Q가 노드에서 제거되면, 파드 안티-어피니티 위반이 사라지고, 파드 P는 노드 N에서 스케줄링될 수 있다.
수요가 충분하고 합리적인 성능의 알고리즘을 찾을 경우 향후 버전에서 교차 노드 선점의 추가를 고려할 수 있다.
문제 해결
파드 우선순위와 선점은 원하지 않는 부작용을 가질 수 있다. 다음은 잠재적 문제의 예시와 이를 해결하는 방법이다.
파드가 불필요하게 선점(축출)됨
선점은 우선순위가 높은 보류 중인 파드를 위한 공간을 만들기 위해 리소스 압박을 받고 있는
클러스터에서 기존 파드를 제거한다. 실수로 특정 파드에 높은 우선순위를
부여하면, 의도하지 않은 높은 우선순위 파드가 클러스터에서
선점을 유발할 수 있다. 파드 우선순위는 파드 명세에서
priorityClassName
필드를 설정하여 지정한다. 그런 다음
우선순위의 정수 값이 분석되어 podSpec
의 priority
필드에 채워진다.
문제를 해결하기 위해, 해당 파드가 우선순위가 낮은 클래스를 사용하도록 priorityClassName
을
변경하거나, 해당 필드를 비워둘 수 있다. 빈
priorityClassName
은 기본값이 0으로 해석된다.
파드가 축출되면, 축출된 파드에 대한 이벤트가 기록된다. 선점은 클러스터가 파드에 대한 리소스를 충분히 가지지 못한 경우에만 발생한다. 이러한 경우, 선점은 보류 중인 파드(선점자)의 우선순위가 피해자 파드보다 높은 경우에만 발생한다. 보류 중인 파드가 없거나, 보류 중인 파드의 우선순위가 피해자 파드와 같거나 낮은 경우 선점이 발생하지 않아야 한다. 그러한 시나리오에서 선점이 발생하면, 이슈를 올리기 바란다.
파드가 축출되었지만, 선점자는 스케줄링되지 않음
파드가 축출되면, 요청된 정상적인 종료 기간(기본적으로 30초)이 주어진다. 이 기간 내에 대상 파드가 종료되지 않으면, 강제 종료된다. 모든 피해자 파드가 사라지면, 선점자 파드를 스케줄링할 수 있다.
선점자 파드가 피해자 파드가 없어지기를 기다리는 동안, 동일한 노드에 적합한 우선순위가 높은 파드가 생성될 수 있다. 이 경우, 스케줄러는 선점자 대신 우선순위가 높은 파드를 스케줄링한다.
이것은 예상된 동작이다. 우선순위가 높은 파드는 우선순위가 낮은 파드를 대체해야 한다.
우선순위가 높은 파드는 우선순위가 낮은 파드보다 우선함
스케줄러가 보류 중인 파드를 실행할 수 있는 노드를 찾으려고 한다. 노드를 찾지 못하면, 스케줄러는 임의의 노드에서 우선순위가 낮은 파드를 제거하여 보류 중인 파드를 위한 공간을 만든다. 우선순위가 낮은 파드가 있는 노드가 보류 중인 파드를 실행할 수 없는 경우, 스케줄러는 선점을 위해 우선순위가 높은 다른 노드(다른 노드의 파드와 비교)를 선택할 수 있다. 피해자 파드는 여전히 선점자 파드보다 우선순위가 낮아야 한다.
선점할 수 있는 여러 노드가 있는 경우, 스케줄러는 우선순위가 가장 낮은 파드 세트를 가진 노드를 선택하려고 한다. 그러나, 이러한 파드가 위반될 PodDisruptionBudget을 가지고 있고 축출된 경우 스케줄러는 우선순위가 높은 파드를 가진 다른 노드를 선택할 수 있다.
선점을 위해 여러 개의 노드가 존재하고 위의 시나리오 중 어느 것도 적용되지 않는 경우, 스케줄러는 우선순위가 가장 낮은 노드를 선택한다.
파드 우선순위와 서비스 품질 간의 상호 작용
파드 우선순위와 QoS 클래스는
상호 작용이 거의 없고 QoS 클래스를 기반으로 파드 우선순위를 설정하는 데 대한
기본 제한이 없는 두 개의 직교(orthogonal) 기능이다. 스케줄러의
선점 로직은 선점 대상을 선택할 때 QoS를 고려하지 않는다.
선점은 파드 우선순위를 고려하고 우선순위가 가장 낮은 대상 세트를
선택하려고 한다. 우선순위가 가장 높은 파드는 스케줄러가
선점자 파드를 스케줄링할 수 없거나 우선순위가 가장 낮은 파드가
PodDisruptionBudget
으로 보호되는 경우에만, 우선순위가 가장 낮은 파드를
축출 대상으로 고려한다.
QoS와 파드 우선순위를 모두 고려하는 유일한 컴포넌트는 kubelet 리소스 부족 축출이다. kubelet은 부족한 리소스의 사용이 요청을 초과하는지 여부에 따라, 그런 다음 우선순위에 따라, 파드의 스케줄링 요청에 대한 부족한 컴퓨팅 리소스의 소비에 의해 먼저 축출 대상 파드의 순위를 매긴다. 더 자세한 내용은 엔드유저 파드 축출을 참조한다.
kubelet 리소스 부족 축출은 사용량이 요청을 초과하지 않는 경우 파드를 축출하지 않는다. 우선순위가 낮은 파드가 요청을 초과하지 않으면, 축출되지 않는다. 요청을 초과하는 우선순위가 더 높은 다른 파드가 축출될 수 있다.
다음 내용
- 프라이어리티클래스와 관련하여 리소스쿼터 사용에 대해 기본적으로 프라이어리티 클래스 소비 제한을 읽어보자.