본문 바로가기
Service Mesh/Linkerd

Linkerd 서킷 브레이킹 (Circuit Breaking)

by wlsdn3004 2023. 4. 19.
728x90
반응형

서킷 브레이킹이란?

서킷 브레이킹은 분산 시스템에서 서비스 간의 의존성과 가용성을 보장하기 위한 중요한 패턴 중 하나이다.

 

일반적으로 분산 시스템에서는 다양한 서비스들이 서로에게 의존하면서 동작한다. 이때, 하나의 서비스의 장애가 전체 시스템에 영향을 미칠 수 있기 때문에 각 서비스의 상태를 모니터링하고, 장애가 발생한 서비스에 대해서는 다른 대체 서비스를 호출하도록 구성하게 된다.

 

서킷 브레이커는 이러한 대체 서비스 호출을 처리하는 데에 있어, 서비스 간 연결을 관리하는 역할을 담당한다.

 

서비스 장애 상황 시 호출이 지연 또는 실패할 경우 서킷 브레이커는 장애 서비스를 일정 기간 동안 엔드포인트에서 제거하고 서비스의 상태가 정상으로 회복되기를 기다린다. 이때, 장애 서비스에 대한 호출은 다른 서비스로 라우팅 되게 함으로써, 서비스 간의 지연 또는 장애 현상이 전파되는 것을 방지할 수 있게 된다.

 

기존 Linkerd에서는 envoy proxy를 사용하지 않다 보니 아쉽게도 서킷 브레이킹 기능을 지원하지 않았었는데, 2.13.1 버전부터 서킷 브레이킹 기능 중 failure-accrual consecutive 기능이 업데이트되었다. 해당 기능은 연속적인 실패가 일어날 경우 실패 서비스는 엔드포인트에서 제거되는 기능이다.

 

그래서 이번 포스팅에서는 실습을 통해 서킷 브레이킹 기능을 확인해 보려 한다.

 

실습 시나리오는 다음과 같다.

Nginx-client가 Nginx와 Nginx2를 호출하는데, Nginx에 연속 Fail이 발생하여 Nginx2로만 요청을 보내게 되는 상황을 가정한다.

 

전제 조건

 

실습 환경

  • AWS EKS 1.22

 

실습

1. 실습을 위한 Pod 배포


실습을 위한 Nginx 서버 기반 Pod 3개를 띄우고 하나의 서비스에 연결하자.

## nginx-client
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-client
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-client
  template:
    metadata:
      name: nginx-client
      labels:
        app: nginx-client
      annotations:
        linkerd.io/inject: enabled
    spec:
      containers:
        - name: nginx-client
          image: nginx
          ports:
            - containerPort: 80
              name: http

---
## nginx
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
      annotations:
        linkerd.io/inject: enabled
    spec:
      containers:
      - name: nginx
        image: nginx:latest
---
## nginx2
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx2
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
      annotations:
        linkerd.io/inject: enabled
    spec:
      containers:
      - name: nginx2
        image: nginx:latest

 

## Service 
apiVersion: v1
kind: Service
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx

 

/test 경로로 요청을 보내면 nginx2는 정상 응답을, nginx는 502 에러 응답을 하게 설정하였다.

## nginx2

    location /test {
        return 200 "hello world";
    }
## nginx

    location /test {
        return 502;
    }

 

2. 서킷 브레이킹 테스트


위 nginx Pod 배포가 다 되었으면 서킷 브레이킹 설정 전에 nginx와 nginx2로 균등하게 흐르는지 확인해 보자.

$ kubectl exec -it svc/nginx-client -c nginx-client -- /bin/bash
$ while true
do 
  curl nginx/test
  sleep 0.1
done

어느 정도 균등하게 트래픽이 흐르는 것을 확인할 수 있다.

 

다음은 서킷 브레이킹 기능인 failure-accrual을 설정 해보자. nginx서비스에 아래와 같이 annotations를 추가한다.

apiVersion: v1
kind: Service
metadata:
  annotations:
    balancer.linkerd.io/failure-accrual: consecutive

 

failure-accrual 설정이 되었으면 nginx-client에 접근하여 nginx, nginx2 Pod에 요청을 주어 동작을 확인해 보자.

$ kubectl exec -it svc/nginx-client -c nginx-client -- /bin/bash
$ while true
do 
  curl nginx/test
  sleep 0.1
done

 

Web Ui 대시보드의 Top tool을 통해 확인해 보면 아래와 같이 nginx2와 nginx의 요청 count가 확연히 차이 나는 것을 확인할 수 있다.

 

아래와 같이 추가 파라미터 값을 사용해서 테스트해 보겠다.

apiVersion: v1
kind: Service
metadata:
  annotations:
    balancer.linkerd.io/failure-accrual: consecutive
    balancer.linkerd.io/failure-accrual-consecutive-jitter-ratio: "50.0"
    balancer.linkerd.io/failure-accrual-consecutive-max-failures: "3"
    balancer.linkerd.io/failure-accrual-consecutive-max-penalty: 60s
    balancer.linkerd.io/failure-accrual-consecutive-min-penalty: 20s

옵션 설명은 다음과 같다.

  • balancer.linkerd.io/failure-accrual
     연속적인 fail이 발생하면 엔드포인트에서 제거되는 기능 활성화 (현재 consecutive 값만 지원)
  • balancer.linkerd.io/failure-accrual-consecutive-jitter-ratio
    서킷 브레이킹 backoff에 영향을 주는 jitter의 비율 (기본값: 0.5 / 0.0~100.0까지 설정 가능)
  • balancer.linkerd.io/failure-accrual-consecutive-max-failures
    엔드포인트에 제거되기 위한 연속 fail 발생 수 (기본값: 7)
  • balancer.linkerd.io/failure-accrual-consecutive-max-penalty
    엔드포인트에 제거된 상태로 유지되는 최대 시간 (기본값: 60s)
  • balancer.linkerd.io/failure-accrual-consecutive-min-penalty
    엔드포인트에 제거된 상태로 유지되는 최소 시간 (기본값: 1s)

 

실제로 적용이 잘 되었는지 linkerd cli 명령을 통해 확인해 보자.

$ linkerd diagnostics policy svc/nginx 80 -o json | jq '.protocol.Kind.Detect.http1.failure_accrual'
{
  "Kind": {
    "ConsecutiveFailures": {
      "max_failures": 3,
      "backoff": {
        "min_backoff": {
          "seconds": 20
        },
        "max_backoff": {
          "seconds": 60
        },
        "jitter_ratio": 50
      }
    }
  }
}

 

위 설정은 3번 실패 시 "20~60초 (jitter-ratio% 포함) " 사이(초) 동안 엔드포인트에서 제거되어 정상 서비스로만 가게 되는 설정이다. 아래 그림을 보면 3번 실패 후 nginx2 서버로만 트래픽이 흐르는 걸 확인할 수 있다.

 

[ jitter ratio ]
일정한 범위 내 간격을 조절하여 평균 시간을 변화시켜 시간을 랜덤화 하는 기술이다.
서비스가 여러 개일 때 한 번에 엔드포인트에서 제거되었다 포함되면 충돌 및 부하 문제가 생길 수 있기 때문에 제거되는 시간의 간격이 서로 다르게 조정되도록 하여 문제를 방지할 수 있다.

 

일반적인 backoff 시간을 계산하기 위해 Jitter ratio를 0.0으로 설정하고 테스트해 보았다.

[failure-accrual 설정 값]
balancer.linkerd.io/failure-accrual-consecutive-jitter-ratio: 0.0

balancer.linkerd.io/failure-accrual-consecutive-max-failures : 3
balancer.linkerd.io/failure-accrual-consecutive-max-penalty : 60s
balancer.linkerd.io/failure-accrual-consecutive-min-penalty : 1s

[3번 실패 시 backoff 시간]
첫 번째 : 1초
두 번째 : 2초
세 번째 : 4초
네 번째 : 8초
다섯 번째 : 16초
여섯 번째 : 32초
일곱 번째 : 60초
여덟 번째 : 60초

 

 

마치며


이처럼 서킷 브레이커는 분산 시스템에서 서비스 간 연결을 안정적으로 관리할 수 있는 중요한 기술 중 하나이다.
이를 통해 장애 서비스에 대한 호출을 방지하고, 전체 시스템의 안정성과 신뢰성을 높일 수 있다.

하지만 서킷 브레이커를 사용할 때는 네트워크 지연과 같은 외부 요인도 고려해야 하며, 시스템 환경에 따라 실패 판단 기준과 페널티 조정 방식 등을 조정해야 하기 때문에 구현할 때 설계의 복잡성과 유지보수의 어려움을 겪을 수 있으니 기능 도입 전 환경과 기능을 고려한 충분한 테스트가 필요할 것이다.

반응형

'Service Mesh > Linkerd' 카테고리의 다른 글

Linkerd + Prometheus 통합 구성  (0) 2023.04.29
Linkerd 멀티클러스터 통신 구성  (0) 2023.04.28
Linkerd 버전 업그레이드  (1) 2023.04.19
Linkerd 트래픽 Retry, Timeout  (0) 2023.04.19
Linkerd 권한 부여 정책  (0) 2023.04.05

댓글