회로 차단기
회로 차단기 (Circuit Breakers)
회로 차단(circuit breaking)은 어떤 엔드포인트가 불건전(unhealthy)하다고 판단되면 Linkerd가 그 엔드포인트로의 요청 라우팅을 일시적으로 멈추고, 대신 해당 요청을 Service의 다른 레플리카로 보내는 강력한 기능이에요. 이 튜토리얼에서는 백엔드 레플리카가 불건전할 때 클라이언트 성공률을 높이도록 Service에 회로 차단을 켜는 방법을 살펴볼게요.
본문
회로 차단은 어떤 엔드포인트가 불건전하다고 판단되면 Linkerd가 잠시 그 엔드포인트로의 요청 라우팅을 멈추고, 대신 그 요청을 Service의 다른 레플리카로 보내는 강력한 기능이에요.
이 튜토리얼에서는 백엔드 레플리카가 불건전할 때 클라이언트 성공률을 높이도록 Service에서 회로 차단을 켜는 방법을 볼게요.
Linkerd가 회로 차단을 어떻게 구현하는지 자세한 내용은 레퍼런스 문서를 참고해 주세요.
Linkerd 프로덕션 팁
이 페이지는 오픈소스 커뮤니티의 best-effort 지침을 담고 있어요. 미션 크리티컬 애플리케이션을 운영하는 프로덕션 사용자는 Linkerd 프로덕션 리소스를 숙지하고/또는 상용 Linkerd 제공업체와 연결하는 것이 좋아요.
사전 준비
이 가이드를 사용하려면 Kubernetes 클러스터가 필요해요.
- Linkerd와 Linkerd-Viz. 아직 설치하지 않았다면 Installing Linkerd Guide를 따라 주세요.
데모 설정
한 경비원은 항상 진실을 말하고 다른 경비원은 항상 거짓을 말하는 수수께끼를 기억하시나요? 이 데모에는 항상 HTTP 200을 반환하는 good 포드와 항상 HTTP 500을 반환하는 bad 포드가 있어요. 그리고 이 두 포드를 포함하는 Service에 트래픽을 보내는 로드 생성기도 만들 거예요.
로드 생성에는 Slow-Cooker를, 백엔드 포드에는 BB를 사용할 거예요.
이 컴포넌트들을 클러스터에 추가하고 Linkerd 데이터 플레인에 포함시키려면 다음을 실행해요.
`cat ---
apiVersion: v1
kind: Namespace
metadata:
name: circuit-breaking-demo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: good
namespace: circuit-breaking-demo
spec:
replicas: 1
selector:
matchLabels:
class: good
template:
metadata:
labels:
class: good
app: bb
spec:
containers:
- name: terminus
image: buoyantio/bb:v0.0.6
args:
- terminus
- "--h1-server-port=8080"
ports:
- containerPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: bad
namespace: circuit-breaking-demo
spec:
replicas: 1
selector:
matchLabels:
class: bad
template:
metadata:
labels:
class: bad
app: bb
spec:
containers:
- name: terminus
image: buoyantio/bb:v0.0.6
args:
- terminus
- "--h1-server-port=8080"
- "--percent-failure=100"
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: bb
namespace: circuit-breaking-demo
spec:
ports:
- name: http
port: 8080
targetPort: 8080
selector:
app: bb
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: slow-cooker
namespace: circuit-breaking-demo
spec:
replicas: 1
selector:
matchLabels:
app: slow-cooker
template:
metadata:
labels:
app: slow-cooker
spec:
containers:
- args:
- -c
- |
sleep 5 # wait for pods to start
/slow_cooker/slow_cooker --qps 10 http://bb:8080
command:
- /bin/sh
image: buoyantio/slow_cooker:1.3.0
name: slow-cooker
EOF
`
이제 good과 bad 포드의 성공률을 살펴볼 수 있어요.
`> linkerd viz -n circuit-breaking-demo stat deploy
NAME MESHED SUCCESS RPS LATENCY_P50 LATENCY_P95 LATENCY_P99 TCP_CONN
bad 1/1 6.43% 4.7rps 1ms 1ms 4ms 2
good 1/1 100.00% 5.9rps 1ms 1ms 1ms 3
slow-cooker 1/1 100.00% 0.3rps 1ms 1ms 1ms 1
`
여기서 good과 bad 디플로이먼트가 비슷한 양의 트래픽을 각각 받지만, good의 성공률은 100%인 반면 bad의 성공률은 매우 낮다는 걸 볼 수 있어요(헬스체크 프로브만 성공하고 있어요). 트래픽 생성기 관점에서 어떻게 보이는지도 살펴볼 수 있어요.
`> linkerd viz -n circuit-breaking-demo stat deploy/slow-cooker --to svc/bb
NAME MESHED SUCCESS RPS LATENCY_P50 LATENCY_P95 LATENCY_P99 TCP_CONN
slow-cooker 1/1 51.00% 10.0rps 1ms 1ms 2ms 2
`
slow-cooker 관점에서 보면, slow-cooker가 Service로 보내는 요청의 약 50%가 실패하고 있어요. 회로 차단을 사용해 bad 포드로 가는 트래픽을 끊으면 이 문제를 개선할 수 있어요.
회로 끊기
Linkerd는 *연속 실패 누적(consecutive failure accrual)*이라는 유형의 회로 차단을 지원해요. 이는 Linkerd 내부 로드 밸런서에서 각 엔드포인트의 연속 실패를 추적하는 방식으로 동작해요. 연속으로 너무 많은 실패가 발생하면 해당 엔드포인트를 잠시 무시하고 Linkerd는 남은 엔드포인트 사이에서만 로드 밸런싱해요. 백오프 기간이 지나면 엔드포인트를 다시 도입해 그 엔드포인트가 건강해졌는지 판단해요.
bb Service에 주석을 추가해 연속 실패 누적을 켜 봐요.
`kubectl annotate -n circuit-breaking-demo svc/bb balancer.linkerd.io/failure-accrual=consecutive
`
경고
회로 차단은 ServiceProfile과 호환되지 않아요. 주석이 달린 Service에 ServiceProfile이 정의되어 있다면, 그 ServiceProfile이 존재하는 한 프록시는 회로 차단을 수행하지 않아요.
Linkerd 진단 명령으로 실패 누적이 올바르게 구성되었는지 확인할 수 있어요. linkerd diagnostics policy 명령은 Linkerd가 Service로 트래픽을 보낼 때 사용할 정책을 출력해요. jq 유틸리티로 출력을 필터링해 실패 누적 부분만 볼게요.
`> linkerd diagnostics policy -n circuit-breaking-demo svc/bb 8080 -o json | jq '.protocol.Kind.Detect.http1.failure_accrual'
{
"Kind": {
"ConsecutiveFailures": {
"max_failures": 7,
"backoff": {
"min_backoff": {
"seconds": 1
},
"max_backoff": {
"seconds": 60
},
"jitter_ratio": 0.5
}
}
}
}
`
이 출력은 Linkerd가 bb Service와 통신할 때 ConsecutiveFailures 실패 누적을 사용한다는 뜻이에요. 또한 max_failures가 7이라는 건, 연속 실패 7번을 관찰하면 회로 차단기를 발동시킨다는 의미예요. 여기 나온 각 파라미터에 대해서는 이 글의 마지막에서 더 자세히 다룰게요.
회로 차단기가 적용된 지금 각 포드가 얼마나 많은 트래픽을 받는지 살펴봐요.
`> linkerd viz -n circuit-breaking-demo stat deploy
NAME MESHED SUCCESS RPS LATENCY_P50 LATENCY_P95 LATENCY_P99 TCP_CONN
bad 1/1 94.74% 0.3rps 1ms 1ms 1ms 3
good 1/1 100.00% 10.3rps 1ms 1ms 4ms 4
slow-cooker 1/1 100.00% 0.3rps 1ms 1ms 1ms 1
`
bad 포드의 RPS가 크게 낮아진 것을 확인할 수 있어요. 회로 차단기가 slow-cooker에서 bad로 가는 거의 모든 트래픽을 멈춘 거예요.
이것이 slow-cooker에 어떤 영향을 미쳤는지도 볼 수 있어요.
`> linkerd viz -n circuit-breaking-demo stat deploy/slow-cooker --to svc/bb
NAME MESHED SUCCESS RPS LATENCY_P50 LATENCY_P95 LATENCY_P99 TCP_CONN
slow-cooker 1/1 99.83% 10.0rps 1ms 1ms 1ms 4
`
이제 slow-cooker의 요청이 거의 모두 good 포드로 라우팅되어 성공하고 있어요!
회로 차단 튜닝
앞서 linkerd diagnostics policy 명령을 실행했을 때 봤듯이 연속 실패 누적은 여러 파라미터로 제어돼요. 이 파라미터들은 각각 기본값이 있지만 주석으로 수동 구성할 수 있어요.
- balancer.linkerd.io/failure-accrual-consecutive-max-failures회로 차단기가 발동하기 전에 Linkerd가 관찰해야 하는 연속 실패 횟수(기본값: 7). 회로 차단이 더 쉽게 발동되길 원하면 더 낮은 값을, 트래픽이 덜 고르게 분산되는 대신 성공률을 높일 수 있어요. 회로 차단기가 너무 쉽게 발동해 건강한 엔드포인트에서 트래픽이 끊기는 경우에는 더 높은 값을 고려해 보세요.
- balancer.linkerd.io/failure-accrual-consecutive-max-penalty엔드포인트가 복원되기 전에 회로 차단기가 발동 상태를 유지하는 최대 시간(기본값: 60s). 회로 차단기를 발동시킨 엔드포인트로 가는 트래픽을 줄이고 싶다면 더 긴 시간을, 엔드포인트가 다시 건강해진 후 발동된 회로 차단기가 더 빨리 복구되길 원한다면 더 짧은 시간을 고려해 보세요.
- balancer.linkerd.io/failure-accrual-consecutive-min-penalty엔드포인트가 복원되기 전에 회로 차단기가 발동 상태를 유지하는 최소 시간(기본값: 1s). failure-accrual-consecutive-max-penalty와 비슷한 방식으로 조정하는 걸 고려해 보세요.
- balancer.linkerd.io/failure-accrual-consecutive-jitter-ratio회로 차단기 백오프에 도입할 지터(jitter)의 양(기본값: 0.5). 이건 조정할 일이 거의 없겠지만, 많은 클라이언트가 동시에 회로 차단된 엔드포인트로 요청을 보내 뾰족한 트래픽 패턴이 생긴다면 늘리는 걸 고려해 볼 수 있어요.
실패 누적 구성에 대한 자세한 내용은 레퍼런스 문서를 참고해 주세요.