쿠버네티스 밖 머신에 같은 신원을 준다
목표
같은 VM 안의 네트워크 네임스페이스로 만든 '메시 밖 머신' 에 VM 용 사이드카를 띄워 WorkloadGroup·WorkloadEntry 로 메시에 넣고, 양쪽 방향의 mTLS 와 STRICT·인가 정책이 파드와 똑같이 걸리는 것을 확인한다.
왜 중요한가
컨테이너로 옮기지 못한 머신은 메시의 예외가 되기 쉽고, 예외는 PERMISSIVE 나 넓은 허용 규칙으로 이어져 보안의 구멍이 된다. 그 머신에 같은 사이드카와 같은 신원을 주면 예외 없이 같은 정책을 걸 수 있다. 절차가 길고 이름 풀이·토큰·가로채기 규칙에서 막히기 쉬워서, 한 번 끝까지 해 보는 것이 가장 빠른 공부다.
단계
kubectl apply -f /opt/fixtures/istlab/vmworkload-app.yaml로 재료를 올리고 파드가 준비될 때까지 기다리세요. 이 VM 에는 네트워크 네임스페이스legacy(192.168.77.2)가 있고 그 안에서 앱이 8080 으로 돕니다./root/istlab-vm/01-outside.txt에 세 줄을 적으세요 — client 에서http://192.168.77.2:8080/의 상태 코드from_client=, 메시 밖 probe 에서 같은 요청의 상태 코드from_outside=,istioctl proxy-status에 legacy 가 보이면 yes 아니면 no 인in_mesh=./root/istlab-vm/vmns.yaml에 네 리소스를 적어 적용하세요 — 네임스페이스vmns(주입 라벨), 서비스어카운트legacy-sa,WorkloadGrouplegacy(메타데이터 라벨app: legacy, 템플릿의 서비스어카운트legacy-sa, 포트http: 8080), 그리고 셀렉터app: legacy로 8080 을 내놓는Servicelegacy(포트 이름http).kubectl -n vmns get workloadgroup legacy -o yaml을/root/istlab-vm/wg.yaml에 저장하고,istioctl x workload entry configure -f /root/istlab-vm/wg.yaml -o /root/istlab-vm/files --clusterID Kubernetes --ingressIP <istiod 서비스의 ClusterIP> --internalIP 192.168.77.2로 VM 용 파일을 만드세요./root/istlab-vm/files에cluster.env·istio-token·mesh.yaml·root-cert.pem·hosts가 생겨야 합니다.- 03단계의 파일을 공식 문서의 자리에 놓고(
root-cert.pem→/etc/certs/root-cert.pem,istio-token→/var/run/secrets/tokens/istio-token,cluster.env→/var/lib/istio/envoy/cluster.env,mesh.yaml→/etc/istio/config/mesh,hosts의 줄은 네임스페이스용/etc/netns/legacy/hosts에 덧붙임), 소유자를istio-proxy로 바꾼 뒤 네임스페이스 안에서/usr/local/bin/istio-start.sh를 띄우세요(작업 디렉터리/,POD_NAME=legacy-vm).istioctl proxy-status에legacy-vm.vmns가 보여야 하고,/root/istlab-vm/04-iptables.txt에netns_backend=(네임스페이스 안에서 ISTIO 규칙이 든 쪽:legacy또는nft)과host_istio_rules=(호스트의 nat 표 — nft·legacy 둘 다 — 에 있는 ISTIO 규칙 수) 두 줄을 적으세요. /root/istlab-vm/workloadentry.yaml에WorkloadEntrylegacy-vm(네임스페이스vmns, 주소192.168.77.2, 라벨app: legacy, 서비스어카운트legacy-sa)를 적어 적용하세요. 적용 뒤 client 에서http://legacy.vmns:8080/이 본문legacy-vm과 200 을 돌려줘야 합니다.- 네임스페이스 안에서
x-request-id를 붙여http://web.shop/을 부르고, 그 요청을 받은 web 사이드카의 접근 로그 한 줄을 그대로/root/istlab-vm/06-from-vm.log에 저장하세요. /root/istlab-vm/secure.yaml에 두 리소스를 적어 적용하세요 —vmns의PeerAuthenticationstrict(STRICT)와AuthorizationPolicylegacy-only-client(셀렉터app: legacy,ALLOW, 주체cluster.local/ns/shop/sa/client). 적용 뒤 client 는 200,other의 intruder 는 403, 메시 밖 probe 가192.168.77.2:8080을 평문으로 부르면 연결이 끊겨야 합니다./root/istlab-vm/08-report.md에 다섯 줄 —vm_identity=(VM 사이드카가 받은 인증서의 SPIFFE URI),netns_iptables_backend=·host_istio_rules=(4단계),plain_from_outside=(지금 probe 의 평문 요청이 통하면allowed, 끊기면rejected),intruder=(지금 intruder 의 상태 코드) — 을 적고 그 아래 배운 것을 네 줄 이상 적으세요.
참고
- 이 VM 은 준비에 3~5분 걸립니다. 네트워크 네임스페이스
legacy(192.168.77.2)와 그 안의 앱(8080), VM 용 사이드카 꾸러미(istio-sidecar.deb 1.31.0)가 조리법으로 이미 준비되어 있습니다. - 네임스페이스 안의 명령은
ip netns exec legacy <명령>으로 칩니다. 그 안의/etc/hosts와/etc/resolv.conf는/etc/netns/legacy/의 파일입니다. - 사이드카 로그는
/var/log/istio/istio.log, 시작 로그는/var/log/istio/start.log입니다. 다시 띄울 때는 네임스페이스 안의 pilot-agent 를 먼저 끄세요(ip netns pids legacy로 찾습니다). - 이 VM 은 세션이 한 시간을 넘기면 istio-token 이 만료됩니다. 4단계까지는 한 시간 안에 마치세요.
- 흔한 실수 —
/etc/netns/legacy/hosts를 통째로 덮어써 호스트 이름 줄을 지우는 것. istio-start.sh 의 sudo 가 이름 풀이를 기다리며 수십 초 멈춥니다.
메시 밖 머신은 지금 어떻게 보이나
kubectl apply -f /opt/fixtures/istlab/vmworkload-app.yaml 로 재료를 올리고 파드가 준비될 때까지 기다리세요. 이 VM 에는 네트워크 네임스페이스 legacy(192.168.77.2)가 있고 그 안에서 앱이 8080 으로 돕니다. /root/istlab-vm/01-outside.txt 에 세 줄을 적으세요 — client 에서 http://192.168.77.2:8080/ 의 상태 코드 from_client=, 메시 밖 probe 에서 같은 요청의 상태 코드 from_outside=, istioctl proxy-status 에 legacy 가 보이면 yes 아니면 no 인 in_mesh=.
네트워크 네임스페이스는 커널이 따로 주는 네트워크 스택입니다 — 주소·라우팅 표·iptables 가 전부 따로라, 같은 VM 안에 있어도 '다른 기계' 처럼 행동합니다. 호스트와는 veth 쌍(192.168.77.1 ↔ 192.168.77.2)으로 이어져 있고, 파드에서 그 주소로 가는 트래픽은 호스트가 라우팅합니다. 안을 들여다볼 때는 ip netns exec legacy <명령> 을 씁니다. 지금 이 머신은 메시가 모르는 주소라 client 의 사이드카는 그냥 흘려보냅니다.
VM 을 위한 틀 — WorkloadGroup 과 서비스
/root/istlab-vm/vmns.yaml 에 네 리소스를 적어 적용하세요 — 네임스페이스 vmns(주입 라벨), 서비스어카운트 legacy-sa, WorkloadGroup legacy(메타데이터 라벨 app: legacy, 템플릿의 서비스어카운트 legacy-sa, 포트 http: 8080), 그리고 셀렉터 app: legacy 로 8080 을 내놓는 Service legacy(포트 이름 http).
WorkloadGroup 은 VM 을 위한 파드 템플릿입니다 — 라벨·서비스어카운트·포트처럼 '이런 머신들이 이런 신원으로 이 포트를 연다' 를 적습니다. 쿠버네티스 서비스의 셀렉터는 파드뿐 아니라 Istio 의 WorkloadEntry 도 고르므로, VM 을 기존 서비스 이름 뒤에 둘 수 있습니다. 아직 VM 한 대(WorkloadEntry)는 없으므로 서비스의 엔드포인트는 비어 있습니다.
VM 에 건넬 파일을 만든다
kubectl -n vmns get workloadgroup legacy -o yaml 을 /root/istlab-vm/wg.yaml 에 저장하고, istioctl x workload entry configure -f /root/istlab-vm/wg.yaml -o /root/istlab-vm/files --clusterID Kubernetes --ingressIP <istiod 서비스의 ClusterIP> --internalIP 192.168.77.2 로 VM 용 파일을 만드세요. /root/istlab-vm/files 에 cluster.env·istio-token·mesh.yaml·root-cert.pem·hosts 가 생겨야 합니다.
VM 의 사이드카가 알아야 할 것은 다섯입니다 — 자기가 누구인지(cluster.env 의 네임스페이스·서비스어카운트·라벨), 처음 인증서를 받을 때 내밀 토큰(istio-token, 서비스어카운트 토큰), 메시 설정(mesh.yaml), 믿을 루트(root-cert.pem), istiod 를 찾을 주소(hosts). 여러 네트워크에 걸친 설치라면 동서 게이트웨이의 주소를 주지만, 이 VM 에서는 네트워크 네임스페이스가 호스트의 kube-proxy 규칙을 타고 istiod 의 ClusterIP 에 바로 닿습니다. istiod 주소는 kubectl -n istio-system get svc istiod -o jsonpath='{.spec.clusterIP}'.
메시 밖 머신에서 사이드카를 띄운다
03단계의 파일을 공식 문서의 자리에 놓고(root-cert.pem → /etc/certs/root-cert.pem, istio-token → /var/run/secrets/tokens/istio-token, cluster.env → /var/lib/istio/envoy/cluster.env, mesh.yaml → /etc/istio/config/mesh, hosts 의 줄은 네임스페이스용 /etc/netns/legacy/hosts 에 덧붙임), 소유자를 istio-proxy 로 바꾼 뒤 네임스페이스 안에서 /usr/local/bin/istio-start.sh 를 띄우세요(작업 디렉터리 /, POD_NAME=legacy-vm). istioctl proxy-status 에 legacy-vm.vmns 가 보여야 하고, /root/istlab-vm/04-iptables.txt 에 netns_backend=(네임스페이스 안에서 ISTIO 규칙이 든 쪽: legacy 또는 nft)과 host_istio_rules=(호스트의 nat 표 — nft·legacy 둘 다 — 에 있는 ISTIO 규칙 수) 두 줄을 적으세요.
istio-sidecar.deb(조리법이 이미 깔아 둠)은 pilot-agent·envoy·istio-start.sh 를 넣습니다. istio-start.sh 는 iptables 로 가로채기 규칙을 심고 istio-proxy 사용자로 pilot-agent 를 띄웁니다. 이것을 ip netns exec legacy setsid --fork nohup env POD_NAME=legacy-vm /usr/local/bin/istio-start.sh > 로그 2>&1 </dev/null 로 네임스페이스 안에서 돌리면, 규칙이 그 네임스페이스의 iptables 에만 들어갑니다. 스크립트가 ./var/lib/istio 같은 상대 경로를 쓰므로 cd / 뒤에 띄우세요. 규칙은 ip netns exec legacy iptables-legacy -t nat -S 와 iptables-nft 로, 호스트 쪽은 네임스페이스 없이 봅니다. 로그는 /var/log/istio/istio.log 입니다.
VM 한 대를 서비스 뒤에 넣는다 — WorkloadEntry
/root/istlab-vm/workloadentry.yaml 에 WorkloadEntry legacy-vm(네임스페이스 vmns, 주소 192.168.77.2, 라벨 app: legacy, 서비스어카운트 legacy-sa)를 적어 적용하세요. 적용 뒤 client 에서 http://legacy.vmns:8080/ 이 본문 legacy-vm 과 200 을 돌려줘야 합니다.
WorkloadEntry 는 VM 한 대를 파드처럼 나타내는 리소스입니다. 라벨이 서비스 legacy 의 셀렉터와 맞으므로, istiod 는 이 주소를 그 서비스의 엔드포인트로 사이드카들에게 내려보냅니다. client 의 사이드카는 VM 의 사이드카와 mTLS 로 통신하고, 들어가는 쪽은 네임스페이스 안의 iptables 가 8080 을 VM 사이드카로 돌립니다. istioctl proxy-config endpoints client.shop --cluster 'outbound|8080||legacy.vmns.svc.cluster.local' 에 192.168.77.2 가 보여야 합니다. 자동 등록은 이 설치에서 꺼져 있어 손으로 만듭니다.
VM 에서 메시 안 서비스를 이름으로 부른다
네임스페이스 안에서 x-request-id 를 붙여 http://web.shop/ 을 부르고, 그 요청을 받은 web 사이드카의 접근 로그 한 줄을 그대로 /root/istlab-vm/06-from-vm.log 에 저장하세요.
VM 의 사이드카도 DNS 프록시를 켜 두어서(cluster.env 의 ISTIO_META_DNS_CAPTURE) 클러스터 서비스 이름을 풀고, 나가는 요청은 사이드카가 받아 mTLS 로 보냅니다. web 쪽 로그에서 그 요청의 출발지가 192.168.77.2 이고 SNI 가 outbound_.80_._.web.shop.svc.cluster.local 인지 보세요 — 평문이 아니라 메시 안의 연결이라는 증거입니다. 부를 때는 ip netns exec legacy curl … 입니다.
VM 앞에도 mTLS 와 인가를 건다
/root/istlab-vm/secure.yaml 에 두 리소스를 적어 적용하세요 — vmns 의 PeerAuthentication strict(STRICT)와 AuthorizationPolicy legacy-only-client(셀렉터 app: legacy, ALLOW, 주체 cluster.local/ns/shop/sa/client). 적용 뒤 client 는 200, other 의 intruder 는 403, 메시 밖 probe 가 192.168.77.2:8080 을 평문으로 부르면 연결이 끊겨야 합니다.
PeerAuthentication 과 AuthorizationPolicy 는 파드든 VM 이든 워크로드 라벨로 고릅니다. VM 의 사이드카가 cluster.env 의 라벨(app: legacy)을 들고 istiod 에 붙었으므로, 같은 정책이 VM 의 사이드카에 내려갑니다. 메시 밖에서 평문으로 들어오는 요청은 네임스페이스 안의 iptables 가 사이드카로 돌리고, STRICT 인 사이드카가 그 연결을 끊습니다. 정책이 퍼지는 데 몇 초가 걸립니다.
메시 밖 머신이 메시 안으로 들어온 길을 적는다
/root/istlab-vm/08-report.md 에 다섯 줄 — vm_identity=(VM 사이드카가 받은 인증서의 SPIFFE URI), netns_iptables_backend=·host_istio_rules=(4단계), plain_from_outside=(지금 probe 의 평문 요청이 통하면 allowed, 끊기면 rejected), intruder=(지금 intruder 의 상태 코드) — 을 적고 그 아래 배운 것을 네 줄 이상 적으세요.
VM 사이드카의 인증서는 네임스페이스 안에서 Envoy 관리 포트로 볼 수 있습니다 — ip netns exec legacy curl -s 'localhost:15000/config_dump?resource=dynamic_active_secrets' 의 default 인증서를 base64 로 풀어 openssl x509 -noout -ext subjectAltName 으로 읽습니다(파드가 아니라서 istioctl proxy-config 는 이 프록시에 붙지 못합니다).