Linkerd 재시도, 시간 초과

다시 해 보다

서비스가 클라이언트로부터 요청을 받아 처리하고 요청이 실패하면 다시 전송하는 기능입니다.

경우에 따라 요청이 실패하면 클라이언트별로 요청 성공률을 높이기 위해 다시 보낼 수 있습니다. 그러나 클라이언트가 과도한 재시도를 수행하면 서비스가 이를 처리하지 못하여 과부하 상태 및 잠재적인 장애 증상이 발생할 수 있다는 점에 유의해야 합니다.

타임아웃

서비스 요청 처리 시간이 일정 시간 이상 소요될 경우 요청을 종료하는 기능입니다.
클라이언트와 서버 간의 통신이 끊기거나 지연되는 상황에서 클라이언트가 무한정 대기하는 것을 방지할 수 있습니다. 단, 처리 중인 요청을 종료하여 문제가 발생할 수 있으므로 애플리케이션의 특성에 따라 적절하게 조정 후 사용하여야 합니다.

실습을 통해 간단하게 테스트해 봅시다.


훈련

1. 테스트를 위한 클라이언트, 서버 사전 구성

요청을 수신하기 위해 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

nginx 서버 구성을 위한 패키지를 다운로드합니다.

$ kubectl expose service nginx --port=80
$ kubectl exec -it svc/nginx -c nginx -- /bin/bash
$ apt update
$ apt-get install build-essential libpcre3 libpcre3-dev zlib1g-dev libssl-dev vim wget

echo-nginx-module 모듈을 사용하기 위한 구성은 nginx 서버에서 요청에 대한 응답이 지연되도록 다음과 같습니다.

$ wget http://nginx.org/download/nginx-1.23.4.tar.gz
$ tar zxvf nginx-1.23.4.tar.gz
$ cd nginx-1.23.4

## nginx 1.23 버전에 호환되는 echo-nginx-module 다운
$ wget https://github.com/openresty/echo-nginx-module/archive/v0.63.tar.gz
$ tar -xvzf v0.63.tar.gz

## 모듈 컴파일
$ ./configure --with-compat --add-dynamic-module=../echo-nginx-module-0.63
make modules
cp objs/ngx_http_echo_module.so /etc/nginx/modules

이 설정은 /test 경로를 통해 요청이 들어오면 502 오류를 반환하고 /tmout 경로를 통해 요청이 오면 10초 후에 에코 메시지를 반환합니다.

$ vi /etc/nginx/conf.d/default.conf
server {
    listen       80;
    listen  (::):80;
    server_name  localhost;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
    
    location /test {
        return 502;
    }
    
    location /tmout {
        echo_sleep 10;
        echo "Hello, world!";
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }


}
$ nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

$ service nginx reload

nginx-client를 배포하여 동일한 nginx 이미지를 사용하여 요청을 보냅니다.

apiVersion: v1
kind: Service
metadata:
  name: nginx-client
spec:
  selector:
    app: nginx-client
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: 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


2. 재시도

ServiceProfile을 통해 nginx 서버의 /test 경로에 대한 재시도 설정 nginx 클라이언트에서 요청을 보내 로그를 확인합시다.

apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
  name: nginx.default.svc.cluster.local
  namespace: default
spec:
  routes:
  - condition:
      method: HEAD
      pathRegex: /test
    isRetryable: true  ## Retry 설정
    name: HEAD /test
$ kubectl logs svc/nginx -c nginx -f
192.168.6.169 - - (19/Apr/2023:03:52:49 +0000) "GET /test HTTP/1.1" 502 497 "-" "curl/7.74.0" "-"
192.168.6.169 - - (19/Apr/2023:03:52:49 +0000) "GET /test HTTP/1.1" 502 497 "-" "curl/7.74.0" "-"
192.168.6.169 - - (19/Apr/2023:03:52:49 +0000) "GET /test HTTP/1.1" 502 497 "-" "curl/7.74.0" "-"
192.168.6.169 - - (19/Apr/2023:03:52:49 +0000) "GET /test HTTP/1.1" 502 497 "-" "curl/7.74.0" "-"
192.168.6.169 - - (19/Apr/2023:03:52:49 +0000) "GET /test HTTP/1.1" 502 497 "-" "curl/7.74.0" "-"
192.168.6.169 - - (19/Apr/2023:03:52:49 +0000) "GET /test HTTP/1.1" 502 497 "-" "curl/7.74.0" "-"
192.168.6.169 - - (19/Apr/2023:03:52:49 +0000) "GET /test HTTP/1.1" 502 497 "-" "curl/7.74.0" "-"
192.168.6.169 - - (19/Apr/2023:03:52:49 +0000) "GET /test HTTP/1.1" 502 497 "-" "curl/7.74.0" "-"
192.168.6.169 - - (19/Apr/2023:03:52:49 +0000) "GET /test HTTP/1.1" 502 497 "-" "curl/7.74.0" "-"
192.168.6.169 - - (19/Apr/2023:03:52:49 +0000) "GET /test HTTP/1.1" 502 497 "-" "curl/7.74.0" "-"
192.168.6.169 - - (19/Apr/2023:03:52:49 +0000) "GET /test HTTP/1.1" 502 497 "-" "curl/7.74.0" "-"
192.168.6.169 - - (19/Apr/2023:03:52:49 +0000) "GET /test HTTP/1.1" 502 497 "-" "curl/7.74.0" "-"
192.168.6.169 - - (19/Apr/2023:03:52:49 +0000) "GET /test HTTP/1.1" 502 497 "-" "curl/7.74.0" "-"

약 1건의 요청 실패에 대해 재시도로 100건의 요청을 재시도한 것을 알 수 있습니다.

재시도 값은 아래 설정을 통해 세부적으로 설정할 수 있습니다.

spec:
  retryBudget:
    retryRatio: 0.2
    minRetriesPerSecond: 10
    ttl: 10s
  • retryRatio : 최대 재시도 비율(0.2 = 20%)
  • minRetriesPerSecond : retryRatio에서 허용하는 것 외에 초당 허용되는 재시도 횟수
  • ttl: retryRatio 계산을 위해 요청이 고려되는 기간


3. 타임아웃

ServiceProfile을 통해 nginx 서버의 /tmout 경로에 대한 재시도 설정 nginx 클라이언트에서 요청을 보내봅시다.

1초의 딜레이가 발생하면 타임아웃되도록 하는 설정입니다.

apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
  name: nginx.default.svc.cluster.local
  namespace: default
spec:
  routes:
  - condition:
      method: GET
      pathRegex: /test
    isRetryable: true
    name: GET /test
    
  ## timeout path에 timeout 설정 추가
  - condition:
      method: GET
      pathRegex: /tmout
    name: GET /tmout
    timeout: 1000ms
$ kubectl exec -it svc/nginx-client -c nginx-client -- /bin/bash
$ curl nginx/tmout -vvv
*   Trying 10.100.119.208:80...
* Connected to nginx (10.100.119.208) port 80 (#0)
> GET /tmout HTTP/1.1
> Host: nginx
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 504 Gateway Timeout
< l5d-proxy-error: HTTP response timeout after 1s
< connection: close
< l5d-proxy-connection: close
< content-length: 0
< date: Wed, 19 Apr 2023 08:00:51 GMT
<
* Closing connection 0

1초 후 504 Gateway Timeout이 발생하여 연결이 끊긴 것을 확인할 수 있습니다.


결론

시간 초과 및 재시도는 서비스의 안정성과 가용성을 개선하는 데 큰 역할을 합니다. 타임아웃 시간이 너무 짧으면 서버에서 응답을 받더라도 클라이언트에서 오류가 발생할 수 있으며, 재시도 횟수나 간격이 너무 짧거나 크면 서버의 부하가 가중될 수 있다. . 따라서 이러한 기능을 설정할 때에는 어플리케이션의 특성에 따라 신중히 고려하여 적용하여야 합니다.