LabHub
배우기 러닝패스 코스

ICA — Istio認定アソシエイト

503が出ているのに、設定はすべて適用済みだという

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

실제 Istio 1.31 메시(VM 안의 k3s)에서, 인계받은 설정이 만든 네 가지 증상 — 헤더 규칙 무시, /api 의 503, 카나리 헤더의 503, 사이드카 없는 클라이언트의 연결 끊김 — 을 증거에서 원인으로 좁혀 고칩니다. 마지막으로 istiod 를 잠시 내려 컨트롤 플레인이 없을 때 무엇이 계속되고 무엇이 막히는지 직접 봅니다.

왜 중요한가

메시에서 503 은 원인이 하나가 아닙니다. 설정이 가리키는 클러스터가 아예 없을 수도(NC), 클러스터는 있는데 고를 파드가 없을 수도(UH), 요청이 HTTP 로 해석되지도 못했을 수도(NR) 있습니다. kubectl apply 가 성공했다는 것은 API 서버가 받았다는 뜻일 뿐, Envoy 가 그 규칙을 어떻게 쓰는지는 따로 확인해야 합니다. 그래서 문제 해결은 세 층으로 나눕니다.

단계

  1. 인계받은 설정을 적용하고 네 파드의 uid·사이드카 여부를 기록합니다.
  2. 헤더 규칙이 무시되는 이유를 리스너에서 찾고(tcp_proxy), 서비스 포트 이름으로 프로토콜을 바로잡습니다.
  3. Telemetry API 로 네임스페이스 접근 로그를 켭니다.
  4. /api 의 503 을 접근 로그(NC)와 analyze 로 확인하고 없는 subset 참조를 고칩니다.
  5. 카나리 헤더의 503 을 접근 로그(UH)와 빈 엔드포인트로 확인하고 subset 라벨을 고칩니다.
  6. 사이드카 없는 legacy 의 끊김을 서버 로그(NR)로 확인하고 STRICT 를 유지한 채 메시에 넣습니다.
  7. istiod 를 내렸다 올리며 기존 트래픽·설정 쓰기·새 파드가 각각 어떻게 되는지 기록합니다.
  8. 증거 파일에서 옮긴 보고서를 씁니다.

참고

인계받은 메시의 파드와 프록시 목록 적기

인계받은 설정 /opt/fixtures/ica-debug/incident.yaml 을 그대로 적용하고 네임스페이스 ica-debug 의 네 파드(web-v1·web-v2·client·legacy)가 Ready 가 될 때까지 기다립니다. 그 시점의 목록을 /root/ica-debug/inventory.json 에 JSON 배열로 저장합니다. 원소마다 name·uid·sidecar(파드에 istio-proxy 가 있으면 true) 세 키입니다. 설정은 아직 고치지 않습니다.

무엇이 메시에 들어와 있는지부터 적어야 뒤의 증상을 해석할 수 있습니다. 이 VM 의 Istio 1.31 은 istio-proxy 를 spec.containers 가 아니라 spec.initContainersrestartPolicy: Always 로 넣습니다(쿠버네티스 네이티브 사이드카). 그래서 두 목록을 모두 봐야 sidecar 를 바르게 판단합니다. 그리고 istioctl proxy-status 에 어떤 파드가 istiod 에 붙어 있는지도 함께 보세요. legacy 파드는 라벨로 주입을 거절했습니다. uid 는 클러스터가 준 값이라 지어내면 실패합니다.

헤더 규칙을 썼는데 트래픽이 그냥 반반으로 간다

client 에서 x-canary: yes 헤더를 붙여 http://web/ 을 여러 번 호출하면 v1 과 v2 가 섞여 나옵니다(규칙대로라면 한쪽만 나와야 합니다). 고치기 전에 client 사이드카의 80 포트 리스너를 istioctl proxy-config listeners client.ica-debug --port 80 -o json 으로 /root/ica-debug/listener-before.json 에 저장합니다. 그다음 서비스 web 의 포트 이름을 http-web 으로 바꿔 Istio 가 이 포트를 HTTP 로 다루게 합니다(포트 번호·targetPort 는 그대로).

VirtualService 의 http 규칙은 Envoy 가 그 포트를 HTTP 로 해석할 때만 쓰입니다. Istio 는 서비스 포트의 이름 접두사(<프로토콜>-이름)나 appProtocol 로 프로토콜을 정합니다. 저장한 리스너 JSON 에서 서비스 ClusterIP 리스너의 필터 이름이 tcp_proxy 인지 http_connection_manager 인지 찾아보세요. 포트 이름은 kubectl patch svc 의 json patch 로 바꿀 수 있습니다.

프록시가 무슨 일이 있었는지 말하게 하기

ica-debug 네임스페이스 전체에 Envoy 접근 로그를 켭니다. Telemetry 리소스를 /root/ica-debug/telemetry.yaml 에 적어 적용하세요 — spec.accessLogging 의 provider 이름은 envoy 입니다. 켜진 뒤에는 client 에서 보낸 요청이 kubectl logs client -c istio-proxy 에 한 줄씩 남아야 합니다.

minimal 프로파일은 meshConfig 에 접근 로그 파일을 지정하지 않아 기본으로 로그가 남지 않습니다. 메시 전체 설정을 고치는 대신 Telemetry API 로 네임스페이스 범위에서 켤 수 있습니다(apiVersion telemetry.istio.io/v1). 요청에 x-request-id 헤더를 직접 붙이면 로그에서 그 줄을 쉽게 찾을 수 있습니다.

/api 만 503, 그런데 서버 파드는 멀쩡하다

client 에서 http://web/api/orders 를 호출하면 503 이 납니다. 그 요청에 대응하는 client 사이드카의 접근 로그 한 줄을 그대로 /root/ica-debug/nc.log 에 저장한 뒤, 원인을 고칩니다: VirtualService webapi 규칙이 가리키는 subset 을 DestinationRule 에 실제로 있는 v1 으로 바꿉니다. 고친 뒤 /api/orders 는 200 과 본문 v1 을 돌려줘야 하고 istioctl analyze -n ica-debug 에 IST0101 이 없어야 합니다.

Envoy 접근 로그의 응답 코드 바로 뒤 칸이 응답 플래그입니다. 플래그와 그 뒤의 세부 사유를 Envoy 문서의 response flags 표와 대조하세요. 서버 사이드카에는 이 요청이 도착하지 않았다는 점도 단서입니다. istioctl analyze 는 같은 원인을 설정만 보고 찾아냅니다.

카나리 헤더를 붙이면 no healthy upstream

client 에서 x-canary: yes 헤더로 http://web/ 을 호출하면 이번에는 no healthy upstream 과 503 이 옵니다. 그 요청의 client 사이드카 접근 로그 한 줄을 그대로 /root/ica-debug/uh.log 에 저장하고, 같은 시점의 istioctl proxy-config endpoints client.ica-debug --cluster 'outbound|80|v2|web.ica-debug.svc.cluster.local' -o json 출력을 /root/ica-debug/endpoints-before.json 에 저장합니다. 그다음 DestinationRule web 의 subset v2 가 실제 파드 라벨(version: v2)을 고르게 고칩니다.

NC 와 달리 이번에는 클러스터(outbound|80|v2|…)는 있습니다. 문제는 그 클러스터에 들어간 엔드포인트입니다. subset 은 서비스 엔드포인트를 파드 라벨로 한 번 더 거르는 것이라, 라벨 값이 한 글자만 달라도 빈 목록이 됩니다. kubectl get pod --show-labels 와 DestinationRule 의 labels 를 나란히 놓고 보세요. 1.31 의 analyze 는 이 경우를 IST0173 으로 알려 줍니다.

사이드카 없는 옛 클라이언트만 연결이 끊긴다

legacy 파드에서 curl http://web/ 을 하면 응답 없이 연결이 끊깁니다(curl 종료 코드 56). 서버 쪽(web-v1 또는 web-v2) 사이드카 로그에서 legacy 파드 IP 가 출발지로 찍힌 줄을 찾아 그대로 /root/ica-debug/nr.log 에 저장합니다. 그다음 STRICT 는 그대로 둔 채 legacy 를 메시에 넣어 고칩니다 — 주입을 거절하던 라벨을 뺀 파드 정의를 /root/ica-debug/legacy.yaml 에 적고 파드를 다시 만듭니다. 고친 뒤 legacy 에서 http://web/ 이 200 이어야 합니다.

PeerAuthentication STRICT 인 서버 사이드카는 mTLS 로 들어오는 연결만 받습니다. 사이드카가 없는 클라이언트는 평문을 보내고, 서버 사이드카는 그 연결에 맞는 필터 체인을 찾지 못합니다 — HTTP 요청으로 해석되기 전 단계라 로그의 메서드·경로 칸이 비어 있습니다. PERMISSIVE 로 낮추는 것도 연결은 되게 하지만 이 단계의 답이 아닙니다. 파드의 라벨·주석은 실행 중에 바꿔도 주입되지 않으니 지우고 다시 만들어야 합니다.

istiod 가 내려간 동안 무엇이 멈추는가

istiod 를 kubectl -n istio-system scale deploy istiod --replicas=0 으로 내리고 파드가 사라진 것을 확인한 뒤, 세 가지를 해 보고 결과를 /root/ica-debug/istiod-down.txt 에 세 줄로 적습니다 — traffic= 뒤에 client 에서 http://web/ 을 호출한 본문, config-write= 뒤에 kubectl -n ica-debug annotate virtualservice web lab.example/probe=1 --overwrite 의 출력, new-pod= 뒤에 kubectl -n ica-debug run probe --image=curlimages/curl:8.10.1 --restart=Never --command -- sleep 60 의 출력. 기록한 뒤 istiod 를 replicas 1 로 되돌리고, 네 프록시(client·web-v1·web-v2·legacy)가 다시 istioctl proxy-status 에 나타날 때까지 기다립니다.

컨트롤 플레인은 설정을 나눠 주고 인증서를 발급하고 주입·검증 웹훅을 받습니다. 이미 설정을 받은 Envoy 는 istiod 가 없어도 마지막 설정으로 계속 동작합니다. 반면 Istio 리소스를 쓰는 요청과 새 파드 생성은 API 서버가 웹훅을 불러야 하는데, 웹훅의 failurePolicy 가 무엇인지 kubectl get validatingwebhookconfiguration,mutatingwebhookconfiguration -o yaml 로 확인해 보세요. 되돌리는 것을 잊으면 뒤의 모든 설정 변경이 막힙니다.

네 가지 503·끊김을 증거로 분류한 보고서

/root/ica-debug/report.json 에 JSON 객체 하나를 씁니다. 키와 값의 뜻 — header_rules_ignored_filter: 고치기 전 리스너에서 본 네트워크 필터 이름의 마지막 조각(예: xxx_proxy), api_503_flag·canary_503_flag·legacy_reset_flag: nc.log·uh.log·nr.log 에 찍힌 응답 플래그, istiod_down_traffic: istiod 가 없을 때 받은 본문, istiod_down_config_write: 그때 설정 쓰기가 되었으면 accepted, 거절되었으면 rejected. 값은 여러분이 남긴 증거 파일과 맞아야 하고, 네 증상이 지금은 모두 고쳐져 있어야 합니다.

보고서는 기억이 아니라 증거에서 옮겨 적습니다. 로그 줄에서 응답 코드 다음 칸을 읽고, 리스너 JSON 에서 서비스 ClusterIP 리스너의 필터 이름을 찾으세요. 채점기는 같은 파일들을 읽어 대조하고, 헤더·/api·legacy 요청을 지금 다시 보내 확인합니다.