Istio 심화 — 왜 그렇게 흐르는가 · 사이드카 주입과 가로채기 번호표 · 이론
앱은 모르는데 모든 연결이 프록시를 지나는 이유
한 줄 요약
사이드카 주입은 파드에 컨테이너 둘을 더한다. 한 번 돌고 끝나는 istio-init 이 iptables 에 번호표(15001·15006·1337)를 심고, 오래 사는 istio-proxy 가 그 번호에서 기다린다. 앱은 아무것도 바꾸지 않았는데 모든 연결이 프록시를 지나는 이유가 이 번호표다.
왜 이게 필요했나
서비스 메시의 약속은 "코드를 고치지 않고 모든 호출에 mTLS·재시도·관측을 붙인다" 이다. 그러려면 앱이 http://reviews:9080 으로 보낸 연결을 앱 몰래 프록시가 받아야 한다. 라이브러리를 심는 방식은 언어마다 따로 만들어야 하고, 앱이 그 라이브러리를 써 줘야 한다. 그래서 Istio 는 커널 쪽에서 가로챈다 — 파드의 네트워크 네임스페이스에 iptables 규칙을 넣어, 나가는 연결은 15001 로, 들어오는 연결은 15006 으로 꺾는다.
여기서 바로 문제가 둘 생긴다. 첫째, 프록시가 받은 요청을 다시 밖으로 보내면 그 연결도 규칙에 걸려 15001 로 되돌아온다. 끝없는 고리다. 둘째, 규칙을 고치려면 NET_ADMIN 권한이 필요한데, 그 권한을 늘 떠 있는 프록시에 주면 프록시가 뚫렸을 때 파드의 네트워크를 통째로 바꿀 수 있게 된다. 주입 산출물의 모양은 이 두 문제에 대한 답이다.
어떻게 동작하나
istioctl kube-inject 가 만든 매니페스트를 열면 번호가 흩어져 있다. 모아 놓으면 이렇다.
| 번호 | 적힌 곳 | 뜻 |
| --- | --- | --- |
| 15001 | istio-init -p | 앱이 밖으로 보내는 연결이 꺾여 들어오는 곳(virtualOutbound) |
| 15006 | istio-init -z | 밖에서 들어오는 연결이 꺾여 들어오는 곳(virtualInbound) |
| 1337 | istio-init -u, istio-proxy runAsUser | 이 사용자가 보낸 패킷은 가로채지 않는다 |
| 15021 | readinessProbe | 프록시 자신의 준비 상태 |
| 15020 | prometheus.io/port 애너테이션 | 앱과 프록시 지표를 합쳐 내보내는 곳 |
| 15090 | 컨테이너 포트 http-envoy-prom | Envoy 자신의 지표 |
고리 문제는 -u 1337 로 푼다. 프록시 컨테이너를 UID 1337 로 띄우고, 그 UID 가 보낸 패킷은 규칙에서 뺀다. 두 곳의 번호가 어긋나면 프록시가 자기 요청을 다시 받아 CPU 를 태운다. 권한 문제는 컨테이너를 나눠서 푼다. istio-init 은 root 로 NET_ADMIN·NET_RAW 를 들고 규칙만 심은 뒤 끝나고, istio-proxy 는 root 가 아니며 모든 권한을 버리고 파일 시스템도 읽기 전용이다. -d 15090,15021,15020 은 들어오는 쪽 가로채기에서 뺄 포트다. kubelet 의 헬스체크와 프로메테우스의 긁기가 Envoy 의 라우팅을 거치면 안 되기 때문이다.
Envoy 쪽에서 보면 15006 은 virtualInbound 리스너이고, 원래 목적지 포트를 보고 inbound|<포트>|| 클러스터로 넘긴다. 15001 은 virtualOutbound 이고, 메시가 아는 목적지면 그 서비스의 리스너로, 모르면 PassthroughCluster(그냥 흘려보냄) 나 BlackHoleCluster(엔드포인트가 없어 503)로 보낸다. 어느 쪽인지는 메시 설정의 outboundTrafficPolicy 가 정한다.
현장에서 만나는 모습
주입 후 앱이 뜨기 전에 요청을 보내 실패한다. 프록시가 준비되기 전에 앱이 먼저 떠서 밖으로 연결하면, 규칙은 이미 있는데 15001 에서 받을 사람이 없다. holdApplicationUntilProxyStarts 가 있는 이유다. 주입 산출물의 컨테이너 순서를 보면 그 설정이 어떻게 반영되는지 알 수 있다.
"외부 API 만 503 이 납니다." 로그에 BlackHoleCluster 가 찍히면 메시가 REGISTRY_ONLY 이고, 그 호스트를 알려 주는 ServiceEntry 가 없다는 뜻이다. 응답 본문 no healthy upstream 은 앱이 아니라 사이드카가 만든 것이다.
앱이 1337 로 뜬다. 앱 컨테이너를 우연히 UID 1337 로 띄우면 앱의 연결도 가로채기에서 빠져 mTLS 없이 평문으로 나간다. 아무 오류도 나지 않아서 보안 점검에서야 드러난다.
공식 문서: [Application requirements](https://istio.io/latest/docs/ops/deployment/application-requirements/) · [Debugging Envoy and Istiod](https://istio.io/latest/docs/ops/diagnostic-tools/proxy-cmd/)
다음 실습에서 할 것
오프라인으로 실제 주입 산출물을 만들고, 번호가 적힌 자리를 yq 로 하나씩 뽑는다. 그다음 virtualInbound 와 virtualOutbound 를 손으로 세워 앱까지 가는 요청과 BlackHoleCluster 로 떨어지는 요청을 직접 본다.