[Kubernetes] istio Service-Mesh 환경에서 Circuit Breaker 활용기 #1

    반응형
    SMALL

    What is Circuit Breaker?

    Circuite breaker는 애플리케이션 또는 서버단에서 장애가 발생했을 때 그 장애가 다른 서비스로 전파되는 것을 방지하기 위한 기술이다. 예를 들면 A 서비스가 B 서비스에 요청을 보낼 때 B 서비스에서 문제가 발생하면 A 서비스 또한 응답을 리턴을 못해주므로 영향이 가게된다. 이러한 상황을 Circuit breaker로 방지할 수 있다.

     

    Circuit Breaker의 상태

    - CLOSED: 정상적으로 호출이 이루어지느 상태

    - OPEN: 장애가 발생하여 호출이 차단된 상태

    - HALF OPEN: 일정 시간이 지난 후 다시 호출을 시도하는 상태

     

    [Configuration] 애플리케이션 준비

    우선 실습을 위한 개발한 Python 애플리케이션 두 개를 생성 후 Docker Hub 또는 AWS ECR과 같은 Registry에 Push 후 진행.

    각 애플리케이션은 service a의 /call-service-b로 호출 시 service_b.py에 호출이 되게끔 설계 했으며, 이때 service b는 30% 확률로 500 오류를 반환한다.

    # service_a.py
    from flask import Flask, jsonify
    import requests
    
    app = Flask(__name__)
    
    @app.route('/call-service-b')
    def call_service_b():
        try:
            response = requests.get('http://service-b:5001/api', timeout=2)
    
            response.raise_for_status()
            return jsonify(response.json()), response.status_code
        except requests.exceptions.RequestException as e:
            return jsonify({"error": "Service B failed", "details": str(e)}), 503
    
    if __name__ == '__main__':
        app.run(host='0.0.0.0', port=5000)
    # service_b.py
    from flask import Flask, jsonify
    import random
    
    app = Flask(__name__)
    
    @app.route('/api')
    def unstable_endpoint():
        if random.random() < 0.3:  # 30% 확률로 에러 발생
            return jsonify({"message": "Error occurred!"}), 500
        return jsonify({"message": "Success!"}), 200
    
    if __name__ == '__main__':
        app.run(host='0.0.0.0', port=5001)

     

    ※ 그 후 deployment 생성 후 istio로 mesh 서비스 환경을 구축

     

    [Configuration] Circuit Breaker를 적용하기

     

    우선 Circuit Breaker를 적용하기 전에 kiali와 curl을 통해 간단하게 테스트 해보자.

     

    위와 같이 10번 시도하였을 때 3번 정로다 오류가 발생한 것을 알 수 있다. 

    이를 해결하기 위해 Circuit Breaker를 사용하여 만약 요청을 한 Pod에서 500을 반환했을 때 Fallback 메커니즘을 사용하여 다른 Pod에서 이를 처리하고 사용자에게 정상 패킷을 반환할 수 있도록 설정해보자.

     

    # destination-rule-service-b.yaml
    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: service-b-circuit-breaker
      namespace: circuit
    spec:
      host: service-b
      trafficPolicy:
        outlierDetection:
          consecutive5xxErrors: 2       # 5xx 오류가 2회 연속 발생하면 Circuit Breaker 발동
          interval: 5s                   # 오류를 체크하는 주기
          baseEjectionTime: 30s          # Circuit Breaker 발동 시 30초간 해당 서비스로의 트래픽을 차단
          maxEjectionPercent: 100        # 서비스 전체를 차단

    위와 같은 Destination Rule Manifest를 작성 후 적용해본 뒤 테스트를 해보자. 그 후 API를 테스트 해보면 아래와 같이 오류가 많이 뜨는 것을 볼 수 있다.

     

    이는 DestinationRule 옵션에서 spec.trafficPolicy에 옵션에 의해 5xx 오류가 5초 동안 2번 발생할 경우 30초 동안 해당 서비스로의 트래픽을 전체 차단하도록 설정이되어있는데, 우리의 애플리케이션은 50% 확률로 500오류를 반환하여 circuit breaker가 계속 OPEN 상태를 유지하고 있으므로 Service Unavailable 오류가 발생하는 것이다.

     

    이를 해결하기 위해선 outlierDetection.baseEjectionTime과 outlierDetection.interval의 시간을 줄어봐야한다.

    # destination-rule-service-b.yaml
    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: service-b-circuit-breaker
      namespace: circuit
    spec:
      host: service-b
      trafficPolicy:
        outlierDetection:
          consecutive5xxErrors: 1 
          interval: 1s          
          baseEjectionTime: 1s 
          maxEjectionPercent: 100

    그 후 테스트를 해보자. 아래와 같이 로그 상에선  200과 500이 확률적으로 반환되었으나, Circuit Breaker를 통해서 실제 사용자에겐 정상 적응 응답이 반환 된 것을 알 수 있다.

    이를 통해 interval, baseejectionTIme 옵션에 대해 설정 값을 바꿈으로 써 애플리케이션의 장애 률이 달라지는 것을 볼 수 있다.

    반응형
    LIST

    댓글