..

istio mirroring 트래픽 유실

최근 기존 레거시 서버를 신규 서버로 마이그레이션하는 작업을 진행하고 있습니다. 동일한 입력에 대해 동일한 결과를 반환하는지 확인하고 있는 단계인데요, 이 과정에서 istio의 미러링 기능을 사용하고 있습니다.

istio 미러링

istio를 사용하면 특정 엔드포인트로 들어오는 요청을 손쉽게 미러링할 수 있습니다. 일반적인 virtual service가 정의되어 있다고 해봅시다.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
spec:
  hosts:
    - target.target.svc.cluster.local
  http:
    - route:
        - destination:
            host: target.target.svc.cluster.local

하위에 미러링할 서비스와 비율만 추가해주면 끝입니다.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
spec:
  hosts:
    - target.target.svc.cluster.local
  http:
    - route:
        - destination:
            host: target.target.svc.cluster.local
      mirror:
        host: mirror.mirror.svc.cluster.local
      mirror_percentage: 100

이렇게 되면 기존 서버로 들어가던 모든 요청을 동일하게 미러 서비스로 전송할 수 있습니다.

트래픽 유실

하지만 로그와 데이터를 이용해 결과 정합성을 검증하는 과정에서 문제가 생겼습니다.

  1. 정합성 불일치가 발생해 확인해보니 우선 트래픽을 처리했다는 로그를 확인할 수 없었습니다.
  2. 또한 요청을 받는 시점에 로그를 남기도록 했지만, 해당 로그도 남지 않았습니다.
  3. 특별한 오류 로그도 발생하지 않았습니다.
  4. 해당 시점의 로그를 하나 하나 살펴보니 파드가 종료되었다는 로그가 남아있었습니다. 하지만 서버에는 graceful shutdown이 적용되어 있었습니다. 파드가 종료되었다고 하더라도 들어온 요청은 끝까지 처리할 수 있는 상황이었던 것이죠.

위 네 가지 사항을 고려해볼 때 간헐적으로 미러 서버로 요청이 아예 정상 인입되지 않는 경우가 발생한다고 생각했습니다.

원인

해당 문제를 확인하고, 개발 환경에서 재현을 시도했습니다. 엔드포인트로 요청이 보내는 시점에 미러 서버의 파드를 삭제해가면서 특이한 로그가 발생하는지 확인해보았습니다. 이 결과 istio-proxy(envoy) 컨테이너에서 파드가 종료되는 타이밍에 따라 드물게 503 에러를 반환하는 것을 확인할 수 있었습니다.

추정컨대, 파드가 종료되는 시점에 서버 컨테이너는 인입된 요청 처리를 완료해(혹은 요청이 없어) 정상 종료됨과 동시에 envoy로 트래픽이 들어와 발생하는 문제로 보입니다. 서버 컨테이너는 이미 종료되었으므로 들어온 요청을 처리할 수 없었고, 이 결과 트래픽 인입/처리 로그 또한 남지 않았던 것이죠.

해결 방안

envoy 종료 시점과 서버 종료 시점의 불일치가 원인이 맞다는 가정 하에 문제를 해결하는 방법은 여러 가지가 있을 것입니다.

  • 파드 숫자 늘리기

나이브한 방법으로는 파드의 숫자를 늘리는 방법도 있습니다. 요청을 일반적으로 라운드 로빈으로 분배하므로 파드의 숫자에 따라서 종료되는 파드로 들어가는 요청의 숫자 자체를 줄일 수 있을 것입니다. 또 파드 숫자가 많으니 특정 파드 하나가 제거될 확률 또한 줄어들 것이고요. 하지만 이는 비용 문제와도 연결되어 있고 문제가 발생할 확률을 낮추는 것이지 절대적인 해결책이 될 수는 없습니다.

  • istio에 retry 로직 추가하기

또 다른 방법으로는 503 에러에 대해 재시도 로직을 추가하는 것을 고려해볼 수 있습니다. 하지만 안타깝게도 미러링은 fire and forget으로 동작하므로 재시도를 애초에 시도할 수 없습니다. 이 방법은 정합성 검증 후 신규 서버로 마이그레이션이 끝났을 때나 적용할 수 있습니다.

  • preStop hook 적용하기

파드가 terminating으로 바뀌면 엔드포인트에서 제거되어 더 이상 새로운 요청이 인입되지 않으므로 서버가 종료되는 타이밍만 조절해도 문제를 해결할 수 있을 것이라 생각했습니다. preStop hook을 적용해 서버 컨테이너가 SIGTERM을 받고 종료되기까지 약간의 딜레이를 추가할 수 있을 것입니다. 그러면 그 사이 envoy가 받은 요청들은 서버가 정상 처리할 수 있을 것이고 이후 정상 종료될 것입니다.

결과

종료 신호가 왔을 때 단순히 10초 동안 대기하도록 하는 preStop을 적용했습니다.

lifecycle:
  preStop:
    exec:
      command: ["/bin/sh","-c","sleep 10"]

그 결과 503 에러가 줄어들고, 트래픽 유실 또한 줄어들었음을 확인할 수 있었습니다.