Open Two Doors, Post Two Guards
한국어 원문으로 표시합니다.
목표
진짜 Istio 메시에서 Istio Gateway 와 Gateway API 두 방식으로 인그레스를 열고, TLS 종료와 가중치 분배, 게이트웨이 인가 정책이 실제로 어디에 걸리는지 응답과 Envoy 설정으로 확인한다.
왜 중요한가
인그레스는 메시의 첫 관문이라 여기서 틀리면 밖에서 바로 보인다. 그런데 Istio 와 Gateway API 가 함께 쓰이는 지금은 같은 서비스에 문이 둘 생기기 쉽고, 두 문은 서로 다른 파드라 인증서도 정책도 따로다. 매니페스트가 '적용됐다' 는 것만으로는 이 차이가 보이지 않아서, 실제 요청과 게이트웨이 Envoy 가 받은 설정으로 확인한다.
단계
kubectl apply -f /opt/fixtures/istlab/ingress-app.yaml로 네임스페이스mall의 web-v1·web-v2 를 올리고 두 Deployment 가 준비될 때까지 기다리세요. 그다음/root/istlab-ingress/01-gateway.txt에 인그레스 게이트웨이의ingress_cluster_ip=(서비스istio-system/istio-ingressgateway의 ClusterIP)와ingress_pod=(그 게이트웨이 파드 이름) 두 줄을 적으세요./root/istlab-ingress/gateway.yaml에 두 리소스를 적어 적용하세요 — 네임스페이스mall의 IstioGatewaymall-gw(셀렉터istio: ingressgateway, 포트 80 HTTP, 호스트mall.example.com)와VirtualServicemall-ingress(호스트mall.example.com, 게이트웨이mall-gw, 목적지web-v1.mall.svc.cluster.local포트 80). 적용 뒤Host: mall.example.com으로 게이트웨이의 ClusterIP 를 부르면v1이 와야 합니다.openssl로CN=mall.example.com, SANDNS:mall.example.com인 자체 서명 인증서와 키를/root/istlab-ingress/tls/mall.crt·/root/istlab-ingress/tls/mall.key로 만들고,istio-system에 TLS Secretmall-cred로 넣으세요. 그다음/root/istlab-ingress/gateway.yaml의 Gateway 에 포트 443 HTTPS 서버(호스트mall.example.com,tls.mode: SIMPLE,credentialName: mall-cred)를 더해 다시 적용합니다.curl --cacert /root/istlab-ingress/tls/mall.crt --resolve mall.example.com:443:<ClusterIP> https://mall.example.com/이v1을 돌려줘야 합니다./root/istlab-ingress/gwapi.yaml에 두 리소스를 적어 적용하세요 — 네임스페이스mall의 ConfigMapmall-api-options(data.service에spec: {type: ClusterIP})와 Gateway API 의Gatewaymall-api(gatewayClassName: istio,infrastructure.parametersRef로 그 ConfigMap, 리스너http포트 80 HTTP, 호스트 이름api.mall.example.com, 같은 네임스페이스의 라우트만 허용). Gateway 가Programmed가 될 때까지 기다리세요./root/istlab-ingress/httproute.yaml에HTTPRouteapi-split을 적어 적용하세요 — 부모는 Gatewaymall-api, 호스트 이름api.mall.example.com, 규칙 둘: 헤더x-canary: yes이면web-v2(포트 80)로, 그 밖에는web-v180 과web-v220 의 가중치로 나눕니다.mall-api-istio서비스의 ClusterIP 로x-canary: yes요청을 보내면 언제나v2가 와야 합니다./root/istlab-ingress/06-compare.txt에 네 줄을 적으세요 — Istio Gateway 가 고른 게이트웨이 파드의 네임스페이스istio_gateway_ns=, Gateway API 가 만든 게이트웨이 파드의 네임스페이스gwapi_gateway_ns=, 그 Deployment 이름gwapi_deployment=, 그 Service 의 종류gwapi_service_type=./root/istlab-ingress/deny-admin.yaml에istio-system의AuthorizationPolicydeny-admin을 적어 적용하세요 — 셀렉터istio: ingressgateway,action: DENY, 경로/admin과/admin/*. 적용 뒤/root/istlab-ingress/07-scope.txt에istio_gateway_admin=(mall.example.com/admin의 상태 코드)와gwapi_admin=(api.mall.example.com/admin을 Gateway API 게이트웨이로 보낸 상태 코드) 두 줄을 적으세요./root/istlab-ingress/08-report.md에 다섯 줄 —tls_fingerprint_match=(게이트웨이가 443 에서 내보이는 인증서의 SHA-256 지문이/root/istlab-ingress/tls/mall.crt와 같으면 yes),gwapi_namespace=,weights=(v1/v2 가중치, 예50/50),admin_via_istio_gateway=,admin_via_gateway_api=— 을 적고 그 아래 배운 것을 네 줄 이상 적으세요.
참고
- 이 VM 은 준비에 2~4분 걸립니다.
kubectl·istioctl(1.31.0)은 로그인 셸에서 바로 쓸 수 있습니다. - 게이트웨이는 LoadBalancer 주소 대신 ClusterIP 로 부르세요 —
kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.spec.clusterIP}'. VM 의 셸은 클러스터 서비스 주소에 닿습니다. kubectl get gateway는 Istio 와 Gateway API 중 어느 쪽을 볼지 모호합니다.gateways.networking.istio.io·gateways.gateway.networking.k8s.io처럼 그룹까지 적으세요.- 설정을 바꾼 직후에는 게이트웨이로 퍼지는 데 몇 초가 걸립니다. 한 번에 판단하지 말고 몇 번 되풀이하세요.
- 흔한 실수 — TLS Secret 을
mall네임스페이스에 만드는 것. 게이트웨이 파드가 있는istio-system에 있어야 합니다.
메시 안에 앱 두 벌을 올리고 문지기를 찾는다
kubectl apply -f /opt/fixtures/istlab/ingress-app.yaml 로 네임스페이스 mall 의 web-v1·web-v2 를 올리고 두 Deployment 가 준비될 때까지 기다리세요. 그다음 /root/istlab-ingress/01-gateway.txt 에 인그레스 게이트웨이의 ingress_cluster_ip=(서비스 istio-system/istio-ingressgateway 의 ClusterIP)와 ingress_pod=(그 게이트웨이 파드 이름) 두 줄을 적으세요.
default 프로파일은 istiod 와 함께 istio-ingressgateway 를 미리 세워 둡니다. 이 게이트웨이는 사이드카가 아니라 단독으로 도는 Envoy 이고, 지금은 아무 설정도 받지 않아 어떤 요청에도 404 를 줍니다. 서비스 종류는 LoadBalancer 라 k3s 가 노드 주소를 붙여 주지만, 실습에서는 흔들리지 않는 ClusterIP 로 부릅니다. 파드 이름은 -l istio=ingressgateway 로 고르세요.
Istio Gateway 와 VirtualService 로 문을 연다
/root/istlab-ingress/gateway.yaml 에 두 리소스를 적어 적용하세요 — 네임스페이스 mall 의 Istio Gateway mall-gw(셀렉터 istio: ingressgateway, 포트 80 HTTP, 호스트 mall.example.com)와 VirtualService mall-ingress(호스트 mall.example.com, 게이트웨이 mall-gw, 목적지 web-v1.mall.svc.cluster.local 포트 80). 적용 뒤 Host: mall.example.com 으로 게이트웨이의 ClusterIP 를 부르면 v1 이 와야 합니다.
Istio 의 Gateway 는 이미 떠 있는 게이트웨이 파드를 셀렉터로 골라 '이 포트·호스트로 받아라' 를 알려 줄 뿐, 어디로 보낼지는 모릅니다. 그것은 gateways 에 그 Gateway 이름을 적은 VirtualService 가 정합니다. 둘 중 하나만 있으면 404 입니다. 반영에 몇 초가 걸리니 응답이 바뀔 때까지 되풀이해 보세요. 쿠버네티스에는 gateway 라는 이름의 리소스가 둘(Istio·Gateway API) 있어서, 조회할 때는 kubectl get gateways.networking.istio.io 처럼 그룹까지 적는 편이 안전합니다.
게이트웨이에서 TLS 를 끝낸다
openssl 로 CN=mall.example.com, SAN DNS:mall.example.com 인 자체 서명 인증서와 키를 /root/istlab-ingress/tls/mall.crt·/root/istlab-ingress/tls/mall.key 로 만들고, istio-system 에 TLS Secret mall-cred 로 넣으세요. 그다음 /root/istlab-ingress/gateway.yaml 의 Gateway 에 포트 443 HTTPS 서버(호스트 mall.example.com, tls.mode: SIMPLE, credentialName: mall-cred)를 더해 다시 적용합니다. curl --cacert /root/istlab-ingress/tls/mall.crt --resolve mall.example.com:443:<ClusterIP> https://mall.example.com/ 이 v1 을 돌려줘야 합니다.
게이트웨이는 인증서 파일을 마운트하지 않습니다. credentialName 이 가리키는 Secret 을 istiod 가 SDS 로 게이트웨이 Envoy 에 내려보내고, Secret 을 바꾸면 게이트웨이를 재시작하지 않아도 새 인증서를 씁니다. 그래서 Secret 은 게이트웨이 파드와 같은 네임스페이스(istio-system)에 둡니다. curl --resolve 는 DNS 없이 이름을 주소에 묶어 SNI 와 인증서 검증을 함께 확인하게 해 줍니다. 게이트웨이가 무엇을 받았는지는 istioctl proxy-config secret -n istio-system deploy/istio-ingressgateway 로 봅니다.
Gateway API 는 게이트웨이를 새로 세운다
/root/istlab-ingress/gwapi.yaml 에 두 리소스를 적어 적용하세요 — 네임스페이스 mall 의 ConfigMap mall-api-options(data.service 에 spec: {type: ClusterIP})와 Gateway API 의 Gateway mall-api(gatewayClassName: istio, infrastructure.parametersRef 로 그 ConfigMap, 리스너 http 포트 80 HTTP, 호스트 이름 api.mall.example.com, 같은 네임스페이스의 라우트만 허용). Gateway 가 Programmed 가 될 때까지 기다리세요.
Istio 의 Gateway 와 달리 Gateway API 의 Gateway 는 게이트웨이 Deployment·Service 를 스스로 만듭니다(이름은 <Gateway 이름>-istio). 기본 서비스 종류는 LoadBalancer 인데, 이 VM 의 k3s 는 80 포트를 이미 인그레스 게이트웨이에 내줘서 새 LoadBalancer 가 주소를 받지 못하고 <pending> 에 머뭅니다 — 그러면 Gateway 가 Programmed 가 되지 않습니다(실측). 공식 문서의 방법대로 infrastructure.parametersRef 로 ConfigMap 을 가리켜 서비스 종류를 바꾸세요. 상태는 kubectl -n mall get gateways.gateway.networking.k8s.io mall-api -o yaml 의 conditions 에 있습니다.
HTTPRoute 로 헤더와 가중치를 나눈다
/root/istlab-ingress/httproute.yaml 에 HTTPRoute api-split 을 적어 적용하세요 — 부모는 Gateway mall-api, 호스트 이름 api.mall.example.com, 규칙 둘: 헤더 x-canary: yes 이면 web-v2(포트 80)로, 그 밖에는 web-v1 80 과 web-v2 20 의 가중치로 나눕니다. mall-api-istio 서비스의 ClusterIP 로 x-canary: yes 요청을 보내면 언제나 v2 가 와야 합니다.
HTTPRoute 는 parentRefs 로 어느 Gateway 에 붙을지 스스로 밝힙니다(Istio 의 VirtualService 가 gateways 로 밝히는 것과 방향이 같습니다). 규칙은 더 구체적인 것(헤더 조건)이 이깁니다. 가중치는 요청 몇 번으로는 확인이 흔들리므로, 게이트웨이 Envoy 가 실제로 받은 라우트 표 — istioctl proxy-config routes deploy/mall-api-istio -n mall -o json 의 weightedClusters — 에서 확인하세요.
두 게이트웨이는 어디에 서 있나
/root/istlab-ingress/06-compare.txt 에 네 줄을 적으세요 — Istio Gateway 가 고른 게이트웨이 파드의 네임스페이스 istio_gateway_ns=, Gateway API 가 만든 게이트웨이 파드의 네임스페이스 gwapi_gateway_ns=, 그 Deployment 이름 gwapi_deployment=, 그 Service 의 종류 gwapi_service_type=.
Istio Gateway 는 이미 있는 게이트웨이를 골라 쓰고, Gateway API 는 Gateway 가 있는 네임스페이스에 게이트웨이를 세웁니다. 그래서 팀마다 자기 네임스페이스에 게이트웨이를 두고 따로 늘리고 줄일 수 있습니다. 대신 게이트웨이가 늘어나는 만큼 자원도 늘어납니다. 만든 것에는 라벨 gateway.networking.k8s.io/gateway-name=mall-api 가 붙습니다.
문 앞에서 /admin 을 막는다 — 그리고 막히지 않는 문
/root/istlab-ingress/deny-admin.yaml 에 istio-system 의 AuthorizationPolicy deny-admin 을 적어 적용하세요 — 셀렉터 istio: ingressgateway, action: DENY, 경로 /admin 과 /admin/*. 적용 뒤 /root/istlab-ingress/07-scope.txt 에 istio_gateway_admin=(mall.example.com/admin 의 상태 코드)와 gwapi_admin=(api.mall.example.com/admin 을 Gateway API 게이트웨이로 보낸 상태 코드) 두 줄을 적으세요.
게이트웨이도 Envoy 워크로드라 인가 정책을 셀렉터로 붙일 수 있고, 게이트웨이에서 막으면 요청이 메시 안으로 들어오지도 않습니다. 그런데 셀렉터가 고른 것은 istio-ingressgateway 파드뿐입니다. Gateway API 가 세운 게이트웨이는 다른 파드라 이 정책이 걸리지 않습니다 — 두 번째 문을 연 순간 첫 문의 경비가 그 문을 지키지 않는다는 뜻입니다. 그 사실을 숫자로 남기세요.
문이 둘일 때 무엇을 확인할지 적는다
/root/istlab-ingress/08-report.md 에 다섯 줄 — tls_fingerprint_match=(게이트웨이가 443 에서 내보이는 인증서의 SHA-256 지문이 /root/istlab-ingress/tls/mall.crt 와 같으면 yes), gwapi_namespace=, weights=(v1/v2 가중치, 예 50/50), admin_via_istio_gateway=, admin_via_gateway_api= — 을 적고 그 아래 배운 것을 네 줄 이상 적으세요.
지문은 openssl s_client -connect <ClusterIP>:443 -servername mall.example.com 의 인증서와 파일을 openssl x509 -noout -fingerprint -sha256 으로 나란히 보면 됩니다. 나머지는 6·7단계 기록에서 옮기세요.