LabHub
배우기 러닝패스 코스

Kubernetes運用実務

オペレーター1つがクラスタ全体を遅くした

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

기본 FlowSchema 와 PriorityLevelConfiguration 을 순서대로 읽고, 폭주하는 서비스 어카운트를 위한 격리 칸을 만들어 분류가 실제로 바뀌는 것을 응답 헤더로 증명한 뒤, 우선순위 값이 겹쳤을 때의 규칙과 exempt 남용을 막는 감시 스크립트, 그리고 좌석 수 지표까지 확인한다.

왜 중요한가

API 서버는 들어오는 모든 요청을 분류하고 격리한다. FlowSchema 가 분류표이고, 요청은 matchingPrecedence 가 작은 것부터 대조해 처음 맞는 하나로 결정된다. 그 스키마가 가리키는 PriorityLevelConfiguration 이 그 칸의 폭 — 동시에 몇 개를 실행할 수 있고 넘치면 줄을 세울지 바로 거절할지 — 를 정한다. 이 구조가 중요한 이유는 하나다. 폭주하는 클라이언트가 있을 때 누가 굶는지를 설계할 수 있기 때문이다. 분류표를 읽을 줄 모르면 429 가 쌓이는 날 할 수 있는 일이 재시작뿐이고, 그건 같은 폭주를 다시 맞겠다는 뜻이다.

단계

  1. 클러스터가 기본으로 넣어 둔 FlowSchema(이 실습에서 만들 것들과 구분하기 위해 이름이 ops-zz-ops- 로 시작하지 않는 것으로 정의합니다)를 전부 /root/ops-apf/flowschemas.tsv 에 저장하세요 — <matchingPrecedence><이름><우선순위 등급 이름> 한 줄씩이고, 요청이 심사되는 순서대로 정렬합니다(숫자 정렬입니다).
  2. 클러스터가 기본으로 넣어 둔 PriorityLevelConfiguration(이름이 ops- 로 시작하지 않는 것)을 전부 /root/ops-apf/levels.tsv 에 저장하세요 — <이름><type><nominalConcurrencyShares><limitResponse.type> 한 줄씩, 이름 오름차순입니다. Exempt 등급처럼 값이 없는 칸은 - 로 적으세요.
  3. 네임스페이스 ops-apf 를 만들고 서비스 어카운트 셋을 만드세요 — widget-operator, plain-reader, bulk-writer. 그다음 /root/ops-apf/classify.sh 를 만드세요 — 인자로 받은 서비스 어카운트를 흉내 내어 요청 하나를 보내고 응답 헤더의 X-Kubernetes-Pf-Flowschema-Uid 로 걸린 FlowSchema 이름만 표준출력에 찍습니다. plain-reader 로 돌려 그 결과를 /root/ops-apf/baseline.tsv 에 한 줄로 저장하세요 — plain-reader<스키마 이름>.
  4. /root/ops-apf/ops-noisy.yaml 에 PriorityLevelConfiguration ops-noisy 를 쓰세요 — type: Limited, nominalConcurrencyShares: 5, lendablePercent: 50, borrowingLimitPercent: 20, limitResponsetype: Queue 이고 queuingqueues: 16, handSize: 4, queueLengthLimit: 50 입니다. 적용하세요.
  5. /root/ops-apf/ops-noisy-operator.yaml 에 FlowSchema ops-noisy-operator 를 쓰세요 — matchingPrecedence: 950, priorityLevelConfiguration.nameops-noisy, distinguisherMethod.typeByUser, 규칙은 subject 가 kind: ServiceAccountops-apf 네임스페이스의 widget-operator 이고 resourceRules 는 verbs·apiGroups·resources·namespaces 를 모두 * 로 둡니다. 적용하세요.
  6. classify.shwidget-operatorplain-reader 로 각각 돌려 결과를 /root/ops-apf/classified.tsv 에 두 줄로 저장하세요 — <어카운트 이름><스키마 이름> 이고 어카운트 이름 오름차순입니다.
  7. /root/ops-apf/ops-bulk-a.yaml/root/ops-apf/zz-ops-bulk-b.yaml 에 FlowSchema 두 개를 쓰세요. 둘 다 ops-apf 의 서비스 어카운트 bulk-writer 를 대상으로 하고 ops-noisy 를 가리키며 resourceRules 는 모두 * 입니다. ops-bulk-amatchingPrecedence: 960·distinguisherMethod.type: ByUser, zz-ops-bulk-bmatchingPrecedence: 940·distinguisherMethod.type: ByNamespace 입니다. 둘 다 적용한 뒤 classify.sh bulk-writer 의 결과를 /root/ops-apf/precedence.tsv 에 두 줄로 저장하세요 — ops-bulk-a960<win 또는 lose>, zz-ops-bulk-b940<win 또는 lose>. 그리고 만약 두 스키마의 값이 똑같이 960 이었다면 어느 쪽이 이겼을지 그 이름만 /root/ops-apf/tie.txt 에 한 줄로 쓰세요.
  8. /root/ops-apf/exempt-allow.txtexempt 우선순위 등급을 가리켜도 되는 FlowSchema 이름을 한 줄씩 적으세요(지금 클러스터에 실제로 그런 스키마가 어떤 것들인지 먼저 확인하고, 그 이름들만 적습니다). 그리고 /root/ops-apf/exempt-guard.sh 를 만드세요 — exempt 를 가리키는 FlowSchema 를 모두 찾아 허용 목록에 있으면 OK <이름>, 없으면 VIOLATION <이름> 을 이름 순으로 표준출력에만 찍고, 위반이 하나라도 있으면 0 이 아닌 코드로 끝나야 합니다. 지금 상태에서 돌리면 위반이 없어야 합니다.
  9. API 서버의 지표에서 apiserver_flowcontrol_nominal_limit_seats 를 읽어 /root/ops-apf/seats.tsv 에 저장하세요 — <우선순위 등급 이름><좌석 수> 한 줄씩, 이름 오름차순입니다. ops-noisy 가 그 안에 있어야 합니다.

참고

분류표를 우선순위 순으로 읽는다

클러스터가 기본으로 넣어 둔 FlowSchema(이 실습에서 만들 것들과 구분하기 위해 이름이 ops-zz-ops- 로 시작하지 않는 것으로 정의합니다)를 전부 /root/ops-apf/flowschemas.tsv 에 저장하세요 — <matchingPrecedence><이름><우선순위 등급 이름> 한 줄씩이고, 요청이 심사되는 순서대로 정렬합니다(숫자 정렬입니다).

모든 들어오는 요청은 FlowSchema 를 하나씩 대조하며 내려가고 처음 맞는 것에서 멈춥니다. 대조 순서를 정하는 것이 matchingPrecedence 인데, 이름과 달리 값이 작을수록 먼저 봅니다. 문자열 정렬로는 1000 이 2 보다 앞에 오니 숫자 정렬을 쓰세요.

각 칸의 폭을 읽는다

클러스터가 기본으로 넣어 둔 PriorityLevelConfiguration(이름이 ops- 로 시작하지 않는 것)을 전부 /root/ops-apf/levels.tsv 에 저장하세요 — <이름><type><nominalConcurrencyShares><limitResponse.type> 한 줄씩, 이름 오름차순입니다. Exempt 등급처럼 값이 없는 칸은 - 로 적으세요.

Limited 등급만 동시 실행 수를 나눠 가집니다. Exempt 는 아예 심사를 받지 않아 관련 필드가 없습니다. 값이 없는 칸을 jq 로 다룰 때 // 는 false 를 빈 값으로 취급하니, 여기서는 null 만 비교하는 편이 안전합니다.

지금은 어느 칸으로 가는지 실측한다

네임스페이스 ops-apf 를 만들고 서비스 어카운트 셋을 만드세요 — widget-operator, plain-reader, bulk-writer. 그다음 /root/ops-apf/classify.sh 를 만드세요 — 인자로 받은 서비스 어카운트를 흉내 내어 요청 하나를 보내고 응답 헤더의 X-Kubernetes-Pf-Flowschema-Uid 로 걸린 FlowSchema 이름만 표준출력에 찍습니다. plain-reader 로 돌려 그 결과를 /root/ops-apf/baseline.tsv 에 한 줄로 저장하세요 — plain-reader<스키마 이름>.

APF 분류는 인가보다 먼저 일어납니다 — 그래서 권한이 없어 403 이 돌아와도 헤더는 붙어 있습니다. 헤더를 보려면 kubectl --v=8 을 쓰고 표준오류까지 받아야 합니다. 흉내 내기는 --as=system:serviceaccount:<네임스페이스>:<이름> 입니다.

폭주하는 오퍼레이터를 위한 칸을 만든다

/root/ops-apf/ops-noisy.yaml 에 PriorityLevelConfiguration ops-noisy 를 쓰세요 — type: Limited, nominalConcurrencyShares: 5, lendablePercent: 50, borrowingLimitPercent: 20, limitResponsetype: Queue 이고 queuingqueues: 16, handSize: 4, queueLengthLimit: 50 입니다. 적용하세요.

좌석 수를 직접 적지 않고 지분으로 적는 이유가 있습니다 — 서버의 총 동시 실행 수가 바뀌어도 모든 등급이 같은 비율로 함께 늘고 줄기 때문입니다. lendablePercent 는 남는 좌석을 빌려 줄 수 있는 비율이고, borrowingLimitPercent 는 남에게서 빌려 올 수 있는 상한입니다.

그 칸으로 보내는 규칙을 쓴다

/root/ops-apf/ops-noisy-operator.yaml 에 FlowSchema ops-noisy-operator 를 쓰세요 — matchingPrecedence: 950, priorityLevelConfiguration.nameops-noisy, distinguisherMethod.typeByUser, 규칙은 subject 가 kind: ServiceAccountops-apf 네임스페이스의 widget-operator 이고 resourceRules 는 verbs·apiGroups·resources·namespaces 를 모두 * 로 둡니다. 적용하세요.

FlowSchema 의 status 에 Dangling 조건이 있습니다 — 가리키는 우선순위 등급이 없으면 True 가 되고 그 스키마는 무시됩니다. 적용한 뒤 이 조건이 False 인지 확인하는 습관을 들이면 오타로 스키마가 통째로 죽는 사고를 바로 잡을 수 있습니다.

분류가 정말 바뀌었는지 헤더로 증명한다

classify.shwidget-operatorplain-reader 로 각각 돌려 결과를 /root/ops-apf/classified.tsv 에 두 줄로 저장하세요 — <어카운트 이름><스키마 이름> 이고 어카운트 이름 오름차순입니다.

규칙을 만들었다고 분류가 바뀐 것은 아닙니다. 대상이 아닌 어카운트는 그대로 예전 스키마에 걸려야 하고, 대상인 어카운트만 새 스키마로 가야 합니다. 두 줄을 함께 남겨야 규칙이 너무 넓게 걸리지 않았다는 증거가 됩니다.

같은 요청에 두 규칙이 걸릴 때

/root/ops-apf/ops-bulk-a.yaml/root/ops-apf/zz-ops-bulk-b.yaml 에 FlowSchema 두 개를 쓰세요. 둘 다 ops-apf 의 서비스 어카운트 bulk-writer 를 대상으로 하고 ops-noisy 를 가리키며 resourceRules 는 모두 * 입니다. ops-bulk-amatchingPrecedence: 960·distinguisherMethod.type: ByUser, zz-ops-bulk-bmatchingPrecedence: 940·distinguisherMethod.type: ByNamespace 입니다. 둘 다 적용한 뒤 classify.sh bulk-writer 의 결과를 /root/ops-apf/precedence.tsv 에 두 줄로 저장하세요 — ops-bulk-a960<win 또는 lose>, zz-ops-bulk-b940<win 또는 lose>. 그리고 만약 두 스키마의 값이 똑같이 960 이었다면 어느 쪽이 이겼을지 그 이름만 /root/ops-apf/tie.txt 에 한 줄로 쓰세요.

두 스키마가 같은 요청에 걸리면 먼저 심사되는 쪽이 이기고 거기서 대조가 끝납니다. 값이 완전히 같을 때의 규칙은 공식 문서에 적혀 있습니다 — 이름을 사전순으로 비교해 작은 쪽이 이깁니다. 다만 문서는 같은 값을 두지 말라고 권합니다.

exempt 를 가리키는 규칙을 감시한다

/root/ops-apf/exempt-allow.txtexempt 우선순위 등급을 가리켜도 되는 FlowSchema 이름을 한 줄씩 적으세요(지금 클러스터에 실제로 그런 스키마가 어떤 것들인지 먼저 확인하고, 그 이름들만 적습니다). 그리고 /root/ops-apf/exempt-guard.sh 를 만드세요 — exempt 를 가리키는 FlowSchema 를 모두 찾아 허용 목록에 있으면 OK <이름>, 없으면 VIOLATION <이름> 을 이름 순으로 표준출력에만 찍고, 위반이 하나라도 있으면 0 이 아닌 코드로 끝나야 합니다. 지금 상태에서 돌리면 위반이 없어야 합니다.

exempt 등급으로 보낸 요청은 동시 실행 수 제한도 대기열도 없이 즉시 처리됩니다. 편해 보이지만 그만큼 서버를 지켜 줄 장치가 사라지므로, 새로 이 등급을 가리키는 스키마가 생기면 사람이 알아야 합니다. 이런 감시는 CI 에서 돌리기 좋습니다.

새 칸이 좌석을 실제로 받았는지 지표로 확인한다

API 서버의 지표에서 apiserver_flowcontrol_nominal_limit_seats 를 읽어 /root/ops-apf/seats.tsv 에 저장하세요 — <우선순위 등급 이름><좌석 수> 한 줄씩, 이름 오름차순입니다. ops-noisy 가 그 안에 있어야 합니다.

지표는 kubectl get --raw /metrics 로 받습니다. 등급을 하나 더 만들면 총 좌석이 늘어나는 것이 아니라 같은 총량을 지분대로 다시 나눕니다 — 그래서 새 등급을 만들면 기존 등급들의 좌석 수가 함께 줄어듭니다. 그 숫자를 직접 보면 지분의 뜻이 분명해집니다.