# 2026-07-09 dns-logical-multia — LOGICAL_DNS + 복수 A레코드 실측

검증 대상: 문서 주장 "LOGICAL_DNS SE host에 복수 A레코드 오면 CDS NACK" vs Envoy 공식 문서 "logical DNS cluster only uses the first IP".

## 0. 사전 상태
```
Thu Jul  9 09:13:26 PM KST 2026
--- pods ---
NAME                         READY   STATUS    RESTARTS   AGE
backend-a-b69577fc-kmbvp     1/1     Running   0          7h38m
backend-b-69bd4df99b-jlnpn   1/1     Running   0          8h
fortio-6bbcfcc5ff-jzfht      2/2     Running   0          8h
lab-dns-666f84996b-64d88     2/2     Running   0          8h
netshoot-76c869b576-h4fqh    2/2     Running   0          8h
--- SE ---
NAME               HOSTS                      LOCATION        RESOLUTION   AGE
gslb-passthrough   ["gslb-pt.lab.internal"]   MESH_EXTERNAL   DNS          6h34m
gslb-strict        ["gslb.lab.internal"]      MESH_EXTERNAL   DNS          8h
--- backend svc ClusterIP ---
NAME        CLUSTERIP
backend-a   10.250.161.145
backend-b   10.250.183.248
--- backend pod IP (참고용, 이번 실험은 svc ClusterIP 사용) ---
NAME                         IP
backend-a-b69577fc-kmbvp     10.255.194.124
backend-b-69bd4df99b-jlnpn   10.255.126.38
--- current /hosts/addn ---
10.250.161.145 gslb.lab.internal
10.255.194.124 gslb-pt.lab.internal
```

## 1. SE를 41(logical)로 전환
```
$ kubectl --context=homelab -n dns-lab delete serviceentry gslb-strict
serviceentry.networking.istio.io "gslb-strict" deleted from dns-lab namespace
$ kubectl --context=homelab apply -f scenarios/50-dns-resolution/41-serviceentry-logical.yaml
serviceentry.networking.istio.io/gslb-logical created
$ kubectl --context=homelab -n dns-lab get serviceentry
NAME               HOSTS                      LOCATION        RESOLUTION        AGE
gslb-logical       ["gslb.lab.internal"]      MESH_EXTERNAL   DNS_ROUND_ROBIN   4s
gslb-passthrough   ["gslb-pt.lab.internal"]   MESH_EXTERNAL   DNS               6h34m
```

## 2. LOGICAL_DNS cluster type 확인 + 단일 A레코드 baseline curl
```
$ istioctl proxy-config cluster deploy/fortio.dns-lab --fqdn gslb.lab.internal
SERVICE FQDN          PORT     SUBSET     DIRECTION     TYPE            DESTINATION RULE
gslb.lab.internal     80       -          outbound      LOGICAL_DNS     gslb-tls-origination.dns-lab
gslb.lab.internal     443      -          outbound      LOGICAL_DNS     gslb-tls-origination.dns-lab

$ istioctl proxy-status (요약)
NAME                                                   CLUSTER        ISTIOD                      VERSION     SUBSCRIBED TYPES

$ dig +short gslb.lab.internal (netshoot)
10.250.161.145

$ baseline curl x3 (fortio sidecar 경유, http origination)
backend-a
 [http_code=200]
backend-a
 [http_code=200]
backend-a
 [http_code=200]
```

## 3. lab-dns에 gslb.lab.internal 2-A레코드 주입
```
IP_A(backend-a svc)=10.250.161.145  IP_B(backend-b svc)=10.250.183.248
$ printf 'IP_A gslb.lab.internal
IP_B gslb.lab.internal
IP_A gslb-pt.lab.internal
' > /hosts/addn
$ cat /hosts/addn
10.250.161.145 gslb.lab.internal
10.250.183.248 gslb.lab.internal
10.250.161.145 gslb-pt.lab.internal
```

## 3-1. dig 확인: gslb.lab.internal이 실제로 2개 IP를 반환하는가
```
-- dig 시도 1 --
gslb.lab.internal.	4	IN	A	10.250.161.145
gslb.lab.internal.	4	IN	A	10.250.183.248
-- dig 시도 2 --
gslb.lab.internal.	4	IN	A	10.250.161.145
gslb.lab.internal.	4	IN	A	10.250.183.248
-- dig 시도 3 --
gslb.lab.internal.	4	IN	A	10.250.161.145
gslb.lab.internal.	4	IN	A	10.250.183.248
-- dig 시도 4 --
gslb.lab.internal.	4	IN	A	10.250.161.145
gslb.lab.internal.	4	IN	A	10.250.183.248
-- dig 시도 5 --
gslb.lab.internal.	3	IN	A	10.250.161.145
gslb.lab.internal.	3	IN	A	10.250.183.248
```

## 4. 35초+ 경과 후 관측 (LOGICAL_DNS + 2 A레코드 상태)

### 4-1. istioctl proxy-status
```
NAME                                                   CLUSTER        ISTIOD                      VERSION     SUBSCRIBED TYPES
fortio-6bbcfcc5ff-jzfht.dns-lab                        Kubernetes     istiod-858c784d94-98fhc     1.30.0      4 (CDS,LDS,EDS,RDS)
netshoot-76c869b576-h4fqh.dns-lab                      Kubernetes     istiod-858c784d94-98fhc     1.30.0      4 (CDS,LDS,EDS,RDS)
```

### 4-2. istiod 로그 (최근 5분, nack/reject/error 검색)
```
(매치 없음)
```

### 4-3. Envoy update 카운터 (⚠ 파드 기동 후 누적치 — 이 시점을 새 베이스라인 T_base로 스냅샷, 이후 델타로 판정)
```
$ fortio sidecar cds/lds counters @ T_base
cluster_manager.cds.update_failure: 34
cluster_manager.cds.update_rejected: 0
cluster_manager.cds.update_success: 43
listener_manager.lds.update_failure: 34
listener_manager.lds.update_rejected: 0
listener_manager.lds.update_success: 43

$ netshoot sidecar cds/lds counters @ T_base
cluster_manager.cds.update_failure: 34
cluster_manager.cds.update_rejected: 0
cluster_manager.cds.update_success: 43
listener_manager.lds.update_failure: 34
listener_manager.lds.update_rejected: 0
listener_manager.lds.update_success: 43
```

## 5. 트래픽 테스트 (2-A레코드 상태, LOGICAL_DNS) — curl 12회
```
req#1: backend-a
 [http_code=200]
req#2: backend-a
 [http_code=200]
req#3: backend-a
 [http_code=200]
req#4: backend-a
 [http_code=200]
req#5: backend-a
 [http_code=200]
req#6: backend-a
 [http_code=200]
req#7: backend-a
 [http_code=200]
req#8: backend-a
 [http_code=200]
req#9: backend-a
 [http_code=200]
req#10: backend-a
 [http_code=200]
req#11: backend-a
 [http_code=200]
req#12: backend-a
 [http_code=200]
```

## 6. LOGICAL cluster 내부 상태 + 카운터 델타 재확인
```
$ netshoot sidecar: pilot-agent request GET clusters | grep gslb
outbound|443||gslb-pt.lab.internal::observability_name::outbound|443||gslb-pt.lab.internal;
outbound|443||gslb-pt.lab.internal::default_priority::max_connections::4294967295
outbound|443||gslb-pt.lab.internal::default_priority::max_pending_requests::4294967295
outbound|443||gslb-pt.lab.internal::default_priority::max_requests::4294967295
outbound|443||gslb-pt.lab.internal::default_priority::max_retries::4294967295
outbound|443||gslb-pt.lab.internal::high_priority::max_connections::1024
outbound|443||gslb-pt.lab.internal::high_priority::max_pending_requests::1024
outbound|443||gslb-pt.lab.internal::high_priority::max_requests::1024
outbound|443||gslb-pt.lab.internal::high_priority::max_retries::3
outbound|443||gslb-pt.lab.internal::added_via_api::true
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::cx_active::0
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::cx_connect_fail::0
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::cx_total::0
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::rq_active::0
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::rq_error::0
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::rq_success::0
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::rq_timeout::0
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::rq_total::0
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::hostname::gslb-pt.lab.internal
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::health_flags::healthy
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::weight::1
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::region::
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::zone::
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::sub_zone::
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::canary::false
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::priority::0
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::success_rate::-1
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::local_origin_success_rate::-1
outbound|443||gslb.lab.internal::observability_name::outbound|443||gslb.lab.internal;
outbound|443||gslb.lab.internal::default_priority::max_connections::4294967295
outbound|443||gslb.lab.internal::default_priority::max_pending_requests::4294967295
outbound|443||gslb.lab.internal::default_priority::max_requests::4294967295
outbound|443||gslb.lab.internal::default_priority::max_retries::4294967295
outbound|443||gslb.lab.internal::high_priority::max_connections::1024
outbound|443||gslb.lab.internal::high_priority::max_pending_requests::1024
outbound|443||gslb.lab.internal::high_priority::max_requests::1024
outbound|443||gslb.lab.internal::high_priority::max_retries::3
outbound|443||gslb.lab.internal::added_via_api::true
outbound|443||gslb.lab.internal::10.250.161.145:443::cx_active::0
outbound|443||gslb.lab.internal::10.250.161.145:443::cx_connect_fail::0
outbound|443||gslb.lab.internal::10.250.161.145:443::cx_total::3
outbound|443||gslb.lab.internal::10.250.161.145:443::rq_active::0
outbound|443||gslb.lab.internal::10.250.161.145:443::rq_error::0
outbound|443||gslb.lab.internal::10.250.161.145:443::rq_success::15
outbound|443||gslb.lab.internal::10.250.161.145:443::rq_timeout::0
outbound|443||gslb.lab.internal::10.250.161.145:443::rq_total::15
outbound|443||gslb.lab.internal::10.250.161.145:443::hostname::gslb.lab.internal
outbound|443||gslb.lab.internal::10.250.161.145:443::health_flags::healthy
outbound|443||gslb.lab.internal::10.250.161.145:443::weight::1
outbound|443||gslb.lab.internal::10.250.161.145:443::region::
outbound|443||gslb.lab.internal::10.250.161.145:443::zone::
outbound|443||gslb.lab.internal::10.250.161.145:443::sub_zone::
outbound|443||gslb.lab.internal::10.250.161.145:443::canary::false
outbound|443||gslb.lab.internal::10.250.161.145:443::priority::0
outbound|443||gslb.lab.internal::10.250.161.145:443::success_rate::-1
outbound|443||gslb.lab.internal::10.250.161.145:443::local_origin_success_rate::-1
outbound|80||gslb.lab.internal::observability_name::outbound|80||gslb.lab.internal;
outbound|80||gslb.lab.internal::default_priority::max_connections::4294967295
outbound|80||gslb.lab.internal::default_priority::max_pending_requests::4294967295
outbound|80||gslb.lab.internal::default_priority::max_requests::4294967295
outbound|80||gslb.lab.internal::default_priority::max_retries::4294967295
outbound|80||gslb.lab.internal::high_priority::max_connections::1024
outbound|80||gslb.lab.internal::high_priority::max_pending_requests::1024
outbound|80||gslb.lab.internal::high_priority::max_requests::1024
outbound|80||gslb.lab.internal::high_priority::max_retries::3
outbound|80||gslb.lab.internal::added_via_api::true
outbound|80||gslb.lab.internal::10.250.161.145:80::cx_active::0
outbound|80||gslb.lab.internal::10.250.161.145:80::cx_connect_fail::0
outbound|80||gslb.lab.internal::10.250.161.145:80::cx_total::0
outbound|80||gslb.lab.internal::10.250.161.145:80::rq_active::0
outbound|80||gslb.lab.internal::10.250.161.145:80::rq_error::0
outbound|80||gslb.lab.internal::10.250.161.145:80::rq_success::0
outbound|80||gslb.lab.internal::10.250.161.145:80::rq_timeout::0
outbound|80||gslb.lab.internal::10.250.161.145:80::rq_total::0
outbound|80||gslb.lab.internal::10.250.161.145:80::hostname::gslb.lab.internal
outbound|80||gslb.lab.internal::10.250.161.145:80::health_flags::healthy
outbound|80||gslb.lab.internal::10.250.161.145:80::weight::1
outbound|80||gslb.lab.internal::10.250.161.145:80::region::
outbound|80||gslb.lab.internal::10.250.161.145:80::zone::
outbound|80||gslb.lab.internal::10.250.161.145:80::sub_zone::
outbound|80||gslb.lab.internal::10.250.161.145:80::canary::false
outbound|80||gslb.lab.internal::10.250.161.145:80::priority::0
outbound|80||gslb.lab.internal::10.250.161.145:80::success_rate::-1
outbound|80||gslb.lab.internal::10.250.161.145:80::local_origin_success_rate::-1

$ fortio sidecar: pilot-agent request GET clusters | grep gslb
outbound|443||gslb.lab.internal::observability_name::outbound|443||gslb.lab.internal;
outbound|443||gslb.lab.internal::default_priority::max_connections::4294967295
outbound|443||gslb.lab.internal::default_priority::max_pending_requests::4294967295
outbound|443||gslb.lab.internal::default_priority::max_requests::4294967295
outbound|443||gslb.lab.internal::default_priority::max_retries::4294967295
outbound|443||gslb.lab.internal::high_priority::max_connections::1024
outbound|443||gslb.lab.internal::high_priority::max_pending_requests::1024
outbound|443||gslb.lab.internal::high_priority::max_requests::1024
outbound|443||gslb.lab.internal::high_priority::max_retries::3
outbound|443||gslb.lab.internal::added_via_api::true
outbound|443||gslb.lab.internal::10.250.161.145:443::cx_active::0
outbound|443||gslb.lab.internal::10.250.161.145:443::cx_connect_fail::0
outbound|443||gslb.lab.internal::10.250.161.145:443::cx_total::0
outbound|443||gslb.lab.internal::10.250.161.145:443::rq_active::0
outbound|443||gslb.lab.internal::10.250.161.145:443::rq_error::0
outbound|443||gslb.lab.internal::10.250.161.145:443::rq_success::0
outbound|443||gslb.lab.internal::10.250.161.145:443::rq_timeout::0
outbound|443||gslb.lab.internal::10.250.161.145:443::rq_total::0
outbound|443||gslb.lab.internal::10.250.161.145:443::hostname::gslb.lab.internal
outbound|443||gslb.lab.internal::10.250.161.145:443::health_flags::healthy
outbound|443||gslb.lab.internal::10.250.161.145:443::weight::1
outbound|443||gslb.lab.internal::10.250.161.145:443::region::
outbound|443||gslb.lab.internal::10.250.161.145:443::zone::
outbound|443||gslb.lab.internal::10.250.161.145:443::sub_zone::
outbound|443||gslb.lab.internal::10.250.161.145:443::canary::false
outbound|443||gslb.lab.internal::10.250.161.145:443::priority::0
outbound|443||gslb.lab.internal::10.250.161.145:443::success_rate::-1
outbound|443||gslb.lab.internal::10.250.161.145:443::local_origin_success_rate::-1
outbound|443||gslb-pt.lab.internal::observability_name::outbound|443||gslb-pt.lab.internal;
outbound|443||gslb-pt.lab.internal::default_priority::max_connections::4294967295
outbound|443||gslb-pt.lab.internal::default_priority::max_pending_requests::4294967295
outbound|443||gslb-pt.lab.internal::default_priority::max_requests::4294967295
outbound|443||gslb-pt.lab.internal::default_priority::max_retries::4294967295
outbound|443||gslb-pt.lab.internal::high_priority::max_connections::1024
outbound|443||gslb-pt.lab.internal::high_priority::max_pending_requests::1024
outbound|443||gslb-pt.lab.internal::high_priority::max_requests::1024
outbound|443||gslb-pt.lab.internal::high_priority::max_retries::3
outbound|443||gslb-pt.lab.internal::added_via_api::true
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::cx_active::0
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::cx_connect_fail::0
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::cx_total::0
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::rq_active::0
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::rq_error::0
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::rq_success::0
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::rq_timeout::0
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::rq_total::0
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::hostname::gslb-pt.lab.internal
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::health_flags::healthy
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::weight::1
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::region::
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::zone::
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::sub_zone::
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::canary::false
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::priority::0
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::success_rate::-1
outbound|443||gslb-pt.lab.internal::10.250.161.145:443::local_origin_success_rate::-1
outbound|80||gslb.lab.internal::observability_name::outbound|80||gslb.lab.internal;
outbound|80||gslb.lab.internal::default_priority::max_connections::4294967295
outbound|80||gslb.lab.internal::default_priority::max_pending_requests::4294967295
outbound|80||gslb.lab.internal::default_priority::max_requests::4294967295
outbound|80||gslb.lab.internal::default_priority::max_retries::4294967295
outbound|80||gslb.lab.internal::high_priority::max_connections::1024
outbound|80||gslb.lab.internal::high_priority::max_pending_requests::1024
outbound|80||gslb.lab.internal::high_priority::max_requests::1024
outbound|80||gslb.lab.internal::high_priority::max_retries::3
outbound|80||gslb.lab.internal::added_via_api::true
outbound|80||gslb.lab.internal::10.250.161.145:80::cx_active::0
outbound|80||gslb.lab.internal::10.250.161.145:80::cx_connect_fail::0
outbound|80||gslb.lab.internal::10.250.161.145:80::cx_total::0
outbound|80||gslb.lab.internal::10.250.161.145:80::rq_active::0
outbound|80||gslb.lab.internal::10.250.161.145:80::rq_error::0
outbound|80||gslb.lab.internal::10.250.161.145:80::rq_success::0
outbound|80||gslb.lab.internal::10.250.161.145:80::rq_timeout::0
outbound|80||gslb.lab.internal::10.250.161.145:80::rq_total::0
outbound|80||gslb.lab.internal::10.250.161.145:80::hostname::gslb.lab.internal
outbound|80||gslb.lab.internal::10.250.161.145:80::health_flags::healthy
outbound|80||gslb.lab.internal::10.250.161.145:80::weight::1
outbound|80||gslb.lab.internal::10.250.161.145:80::region::
outbound|80||gslb.lab.internal::10.250.161.145:80::zone::
outbound|80||gslb.lab.internal::10.250.161.145:80::sub_zone::
outbound|80||gslb.lab.internal::10.250.161.145:80::canary::false
outbound|80||gslb.lab.internal::10.250.161.145:80::priority::0
outbound|80||gslb.lab.internal::10.250.161.145:80::success_rate::-1
outbound|80||gslb.lab.internal::10.250.161.145:80::local_origin_success_rate::-1

$ istioctl proxy-config endpoints (LOGICAL_DNS는 EDS 미사용 — 참고용, 비어있을 것으로 예상)
ENDPOINT               STATUS      OUTLIER CHECK     CLUSTER
10.250.161.145:443     HEALTHY     OK                outbound|443||gslb.lab.internal

$ cds/lds 카운터 델타 (T_base 대비 — 여전히 0이면 트래픽 12회+DNS refresh 중 NACK 없음)
-- fortio --
cluster_manager.cds.update_failure: 34
cluster_manager.cds.update_rejected: 0
cluster_manager.cds.update_success: 43
listener_manager.lds.update_failure: 34
listener_manager.lds.update_rejected: 0
listener_manager.lds.update_success: 43
-- netshoot --
cluster_manager.cds.update_failure: 34
cluster_manager.cds.update_rejected: 0
cluster_manager.cds.update_success: 43
listener_manager.lds.update_failure: 34
listener_manager.lds.update_rejected: 0
listener_manager.lds.update_success: 43
```

## 7. 대조군: SE를 40(strict)으로 전환, 동일 2-A레코드 상태에서 확인
```
$ kubectl delete serviceentry gslb-logical; kubectl apply -f 40-serviceentry-strict.yaml
serviceentry.networking.istio.io "gslb-logical" deleted from dns-lab namespace
serviceentry.networking.istio.io/gslb-strict created
NAME               HOSTS                      LOCATION        RESOLUTION   AGE
gslb-passthrough   ["gslb-pt.lab.internal"]   MESH_EXTERNAL   DNS          15h
gslb-strict        ["gslb.lab.internal"]      MESH_EXTERNAL   DNS          3s

$ cluster type 확인
SERVICE FQDN          PORT     SUBSET     DIRECTION     TYPE           DESTINATION RULE
gslb.lab.internal     80       -          outbound      STRICT_DNS     gslb-tls-origination.dns-lab
gslb.lab.internal     443      -          outbound      STRICT_DNS     gslb-tls-origination.dns-lab
```

## 8. STRICT_DNS 15초 경과 후 cluster 멤버 + 트래픽 분산 확인
```
$ pilot-agent request GET clusters | grep gslb.lab.internal (netshoot)
outbound|443||gslb.lab.internal::10.250.161.145:443::cx_total::0
outbound|443||gslb.lab.internal::10.250.161.145:443::rq_total::0
outbound|443||gslb.lab.internal::10.250.161.145:443::hostname::gslb.lab.internal
outbound|443||gslb.lab.internal::10.250.183.248:443::cx_total::0
outbound|443||gslb.lab.internal::10.250.183.248:443::rq_total::0
outbound|443||gslb.lab.internal::10.250.183.248:443::hostname::gslb.lab.internal

$ curl x12 (STRICT_DNS, 2-A레코드 — 분산 기대)
req#1: backend-a
 [http_code=200]
req#2: backend-a
 [http_code=200]
req#3: backend-a
 [http_code=200]
req#4: backend-b
 [http_code=200]
req#5: backend-b
 [http_code=200]
req#6: backend-a
 [http_code=200]
req#7: backend-b
 [http_code=200]
req#8: backend-a
 [http_code=200]
req#9: backend-a
 [http_code=200]
req#10: backend-a
 [http_code=200]
req#11: backend-b
 [http_code=200]
req#12: backend-b
 [http_code=200]
```

## 9. 실측 B — SE에 endpoints 2개 명시 + DNS_ROUND_ROBIN
```
$ kubectl delete serviceentry gslb-strict (host 충돌 회피 — 51과 같은 host)
serviceentry.networking.istio.io "gslb-strict" deleted from dns-lab namespace

$ dry-run=server 로 51 적용 시도
```

## 10. 종료 상태 복원
```
IP_A_SVC=10.250.161.145  POD_A(gslb-pt target)=10.255.194.124
$ /hosts/addn 을 원복 (gslb.lab.internal=단일 backend-a svc IP, gslb-pt.lab.internal=backend-a pod IP 유지)
10.250.161.145 gslb.lab.internal
10.255.194.124 gslb-pt.lab.internal

$ dig 확인 (단일 A 복원됐는가)
10.250.161.145
10.255.194.124

$ SE를 40(strict)으로 재적용
serviceentry.networking.istio.io/gslb-strict created
NAME               HOSTS                      LOCATION        RESOLUTION   AGE
gslb-passthrough   ["gslb-pt.lab.internal"]   MESH_EXTERNAL   DNS          15h
gslb-strict        ["gslb.lab.internal"]      MESH_EXTERNAL   DNS          3s

$ 51 SE가 클러스터에 존재하지 않음 확인 (dry-run만 했으므로 애초에 생성 안 됨)
Error from server (NotFound): serviceentries.networking.istio.io "gslb-logical-2ep" not found
```

## 11. 최종 sanity 확인
```
$ cluster type (STRICT_DNS 확인)
SERVICE FQDN          PORT     SUBSET     DIRECTION     TYPE           DESTINATION RULE
gslb.lab.internal     80       -          outbound      STRICT_DNS     gslb-tls-origination.dns-lab
gslb.lab.internal     443      -          outbound      STRICT_DNS     gslb-tls-origination.dns-lab

$ 전 파드 READY
NAME                         READY   STATUS    RESTARTS   AGE
backend-a-b69577fc-kmbvp     1/1     Running   0          16h
backend-b-69bd4df99b-jlnpn   1/1     Running   0          17h
fortio-6bbcfcc5ff-jzfht      2/2     Running   0          17h
lab-dns-666f84996b-64d88     2/2     Running   0          17h
netshoot-76c869b576-h4fqh    2/2     Running   0          17h

$ sanity curl — HTTP 경로 (gslb.lab.internal, TLS origination)
backend-a
 [http_code=200]

$ sanity curl — passthrough 경로 (gslb-pt.lab.internal, SNI passthrough)
backend-a
 [http_code=200]
```

## 12. 결론

### 실측 A (런타임 — DNS가 실제로 2개 IP를 반환)

- `gslb.lab.internal`이 A레코드 2개(backend-a, backend-b 순서로 dig 응답 고정)를 반환하는 상태에서
  LOGICAL_DNS(SE resolution: DNS_ROUND_ROBIN)는 **NACK 없이 정상 동작**했다.
- `pilot-agent request GET clusters | grep gslb.lab.internal` 에 잡힌 멤버는 오직
  `10.250.161.145:443`(backend-a, dig 응답의 첫 IP) 하나뿐 — `10.250.183.248`(backend-b)은
  cluster 멤버 목록에 아예 나타나지 않았다.
- curl 15회(baseline 3 + 본 실험 12) 전부 `backend-a`, `http_code=200`. backend-b 응답은 단 한 번도
  관측되지 않음.
- cds/lds update_rejected 카운터는 T_base(2-레코드 주입 후 스냅샷) 대비 **델타 0** — DNS refresh
  주기(TTL 5s, 35초 이상 경과, dig 7회 연속 2-IP 응답 확인)를 지나는 동안 NACK/STALE 징후 전무.
  istiod 로그에도 nack/reject/error 매치 없음.
- 결론: Envoy 공식 문서("a logical DNS cluster only uses the first IP address returned")가 **맞다**.
  기존 문서(41-serviceentry-logical.yaml 주석, README §5)의 "A record가 multi-IP면 CDS NACK" 주장은
  **이 시나리오(실제 DNS 응답의 복수 A레코드)에서는 실측으로 반증됨**.

### STRICT 대조

- 동일 2-레코드 상태에서 SE를 40(strict, resolution: DNS)으로 바꾸면 cluster 멤버에
  `10.250.161.145:443`, `10.250.183.248:443` **둘 다** 잡힘.
- curl 12회 결과 backend-a 7회, backend-b 5회로 **실제 분산** 확인(a,a,a,b,b,a,b,a,a,a,b,b).
- STRICT_DNS는 A record 전체를 endpoint로 펼쳐 LB한다는 기존 이해와 일치.

### 실측 B (설정 레벨 — SE.endpoints 2개 명시 + DNS_ROUND_ROBIN)

- `kubectl apply --dry-run=server` 단계에서 **즉시 거부**:
  `The ServiceEntry "gslb-logical-2ep" is invalid: spec: Invalid value: "object": DNS_ROUND_ROBIN mode cannot have multiple endpoints`
- 이건 istiod validating webhook의 **admission-level 거부**다. CDS/xDS까지 가지도 않고,
  Envoy가 NACK할 기회조차 없이 API 서버 단에서 원천 차단됨.
- 즉 "LOGICAL_DNS는 논리 endpoint 1개" 제약이 실제로 강제되는 지점은 **여기(SE.spec.endpoints를
  명시적으로 여러 개 선언하는 경우)** 이지, "실제 DNS 조회가 여러 IP를 반환하는 경우"가 아니다.

### 종합 — 무엇이 혼동됐었나

기존 문서의 "multi-IP A record → LOGICAL_DNS NACK"라는 주장은 **메커니즘을 잘못 짚었다.**
- 실제 DNS 응답의 다중 A레코드(실측 A) — Envoy가 스스로 c-ares로 질의하고 첫 결과만 쓰는
  데이터플레인 동작이라 **xDS 왕복 자체가 없다.** NACK 발생 지점이 없음.
- SE에 endpoints를 여러 개 "선언"하는 경우(실측 B)만이 istiod가 이를 그대로
  Cluster.load_assignment에 실어 push하려는 시도가 되고, 이 조합은 **CDS 이전, admission
  webhook에서** 거부된다(Envoy 레벨의 "LOGICAL_DNS는 load-assignment endpoint 1개만 허용"
  제약을 istiod가 API 검증으로 선반영한 것으로 보임).
- 두 메커니즘 다 "NACK로 통신이 깨진다"는 결과에 이르지 않는다 — A는 정상 동작(첫 IP만 씀),
  B는 애초에 리소스 생성 자체가 실패(admission reject)해서 "이미 동작 중이던 통신이 깨지는"
  상황이 발생할 수 없다.

⚠️ 스코프 밖 — 이 리포트는 `docs/test-reports/`에만 신규 기록했다. 41-serviceentry-logical.yaml의
함정 주석과 README.md §5는 위 실측과 배치되므로 **후속 작업으로 수정 필요**(이번 태스크 범위는
dns-lab 실측 + docs/test-reports 신규 파일로 한정되어 기존 scenario yaml/README 본문은 건드리지 않음).
