Skip to content

Kubernetes Service 통신 완벽 가이드 + Tailscale DNS 문제 해결

목차

  1. 문제 상황 분석
  2. Kubernetes Service 통신 흐름
  3. CoreDNS 동작 원리
  4. Tailscale이 DNS에 미치는 영향
  5. 완전한 패킷 흐름 분석
  6. 문제 해결 방법
  7. 트러블슈팅 가이드
  8. 모니터링 및 디버깅

문제 상황 분석

실제 발생한 문제

요청:
  Pod → frigate.video-management-system.svc.cluster.local

CoreDNS가 받은 쿼리:
  frigate.video-management-system.svc.cluster.local.tailb45a71.ts.net
                                                    ^^^^^^^^^^^^^^^^
                                                    Tailscale 도메인 추가됨!

결과:
  [ERROR] Failed to resolve (Caused by NameResolutionError)

로그 분석

CoreDNS 에러:
[ERROR] plugin/errors: 2 frigate.video-management-system.svc.cluster.local.tailb45a71.ts.net. A: read udp 10.42.0.162:51979->8.8.8.8:53: i/o timeout

분석:
1. 원래 도메인: frigate.video-management-system.svc.cluster.local
2. 실제 쿼리: frigate.video-management-system.svc.cluster.local.tailb45a71.ts.net
3. CoreDNS가 8.8.8.8 (외부 DNS)에 쿼리 → 실패
4. 클러스터 내부 도메인인데 외부로 쿼리를 보냄 (잘못됨!)

Kubernetes Service 통신 흐름

Pod to Service 통신 전체 그림

┌─────────────────────────────────────────────────────────────────┐
│                    Kubernetes Cluster                           │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │ Pod A (Airflow Worker)                                   │  │
│  │ IP: 10.42.0.100                                          │  │
│  │ Namespace: workflow-engine                               │  │
│  │                                                          │  │
│  │  Application Code:                                       │  │
│  │  requests.get('http://frigate.video-management-system    │  │
│  │                     .svc.cluster.local:5000/api/...')   │  │
│  └────────────────────────┬─────────────────────────────────┘  │
│                           │                                     │
│                           │ 1. DNS Query                        │
│                           │    "frigate.video-management-system │
│                           │     .svc.cluster.local"             │
│                           ▼                                     │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │ /etc/resolv.conf (Pod 내부)                              │  │
│  ├──────────────────────────────────────────────────────────┤  │
│  │ nameserver 10.43.0.10  ← CoreDNS Service IP              │  │
│  │ search workflow-engine.svc.cluster.local                 │  │
│  │        svc.cluster.local                                 │  │
│  │        cluster.local                                     │  │
│  │ options ndots:5                                          │  │
│  └────────────────────────┬─────────────────────────────────┘  │
│                           │                                     │
│                           │ 2. DNS Query 전송                   │
│                           ▼                                     │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │ CoreDNS Pod (kube-system namespace)                      │  │
│  │ IP: 10.42.1.5                                            │  │
│  │                                                          │  │
│  │ Corefile:                                                │  │
│  │   cluster.local {                                        │  │
│  │     kubernetes cluster.local in-addr.arpa ip6.arpa {    │  │
│  │       pods insecure                                      │  │
│  │       fallthrough in-addr.arpa ip6.arpa                 │  │
│  │     }                                                    │  │
│  │   }                                                      │  │
│  └────────────────────────┬─────────────────────────────────┘  │
│                           │                                     │
│                           │ 3. Kubernetes API 조회              │
│                           │    "frigate" Service 정보 요청      │
│                           ▼                                     │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │ Kubernetes API Server                                    │  │
│  │                                                          │  │
│  │ Services:                                                │  │
│  │   - frigate.video-management-system                      │  │
│  │     ClusterIP: 10.43.123.45                             │  │
│  │     Port: 5000                                          │  │
│  │     Selector: app=frigate                               │  │
│  └────────────────────────┬─────────────────────────────────┘  │
│                           │                                     │
│                           │ 4. DNS Response                     │
│                           │    A record: 10.43.123.45           │
│                           ▼                                     │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │ Pod A                                                    │  │
│  │ DNS Response 수신: 10.43.123.45                          │  │
│  └────────────────────────┬─────────────────────────────────┘  │
│                           │                                     │
│                           │ 5. HTTP Request                     │
│                           │    Dst: 10.43.123.45:5000           │
│                           ▼                                     │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │ kube-proxy (iptables or IPVS)                            │  │
│  │                                                          │  │
│  │ iptables rules:                                          │  │
│  │   -A KUBE-SERVICES -d 10.43.123.45/32 -p tcp            │  │
│  │    --dport 5000 -j KUBE-SVC-XXX                         │  │
│  │                                                          │  │
│  │   -A KUBE-SVC-XXX -j KUBE-SEP-AAA (50% probability)     │  │
│  │   -A KUBE-SVC-XXX -j KUBE-SEP-BBB (50% probability)     │  │
│  │                                                          │  │
│  │   -A KUBE-SEP-AAA -p tcp -j DNAT                        │  │
│  │    --to-destination 10.42.2.10:5000  ← Frigate Pod 1    │  │
│  │   -A KUBE-SEP-BBB -p tcp -j DNAT                        │  │
│  │    --to-destination 10.42.2.11:5000  ← Frigate Pod 2    │  │
│  └────────────────────────┬─────────────────────────────────┘  │
│                           │                                     │
│                           │ 6. DNAT 적용                        │
│                           │    10.43.123.45:5000               │
│                           │    → 10.42.2.10:5000 (실제 Pod)    │
│                           ▼                                     │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │ Frigate Pod                                              │  │
│  │ IP: 10.42.2.10                                           │  │
│  │ Port: 5000                                               │  │
│  │                                                          │  │
│  │ Application 처리                                          │  │
│  └────────────────────────┬─────────────────────────────────┘  │
│                           │                                     │
│                           │ 7. HTTP Response                    │
│                           ▼                                     │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │ Pod A                                                    │  │
│  │ Response 수신                                             │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

CoreDNS 동작 원리

CoreDNS 설정 파일 (Corefile)

yaml
# kubectl -n kube-system get cm coredns -o yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health {
           lameduck 5s
        }
        ready

        # Kubernetes plugin: 클러스터 내부 DNS
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }

        # Prometheus metrics
        prometheus :9153

        # Forward plugin: 외부 DNS
        forward . /etc/resolv.conf {
           max_concurrent 1000
        }

        # Cache
        cache 30

        # Loop detection
        loop

        # Reload
        reload

        # Load balance
        loadbalance
    }

DNS 쿼리 처리 과정

┌────────────────────────────────────────────────────────────┐
│           CoreDNS Query Processing                         │
└────────────────────────────────────────────────────────────┘

쿼리: frigate.video-management-system.svc.cluster.local

┌─────────────────────────────────────────────────────────────┐
│ Step 1: Query 수신                                          │
├─────────────────────────────────────────────────────────────┤
│ - Client: 10.42.0.100 (Pod A)                               │
│ - Query: frigate.video-management-system.svc.cluster.local │
│ - Type: A (IPv4 주소 조회)                                  │
└──────────────────────┬──────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ Step 2: Plugin Chain 시작                                   │
├─────────────────────────────────────────────────────────────┤
│ 1. errors plugin                                            │
│    → 에러 로깅 준비                                          │
│                                                             │
│ 2. health plugin                                            │
│    → 헬스체크 (pass)                                        │
│                                                             │
│ 3. ready plugin                                             │
│    → Readiness check (pass)                                 │
└──────────────────────┬──────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ Step 3: kubernetes plugin                                   │
├─────────────────────────────────────────────────────────────┤
│ Zone 확인: cluster.local?                                   │
│   YES → Kubernetes API 조회                                 │
│                                                             │
│ Query Kubernetes API:                                       │
│   GET /api/v1/namespaces/video-management-system/          │
│       services/frigate                                      │
│                                                             │
│ Response:                                                   │
│   {                                                         │
│     "metadata": {                                           │
│       "name": "frigate",                                    │
│       "namespace": "video-management-system"                │
│     },                                                      │
│     "spec": {                                               │
│       "clusterIP": "10.43.123.45",                          │
│       "ports": [{                                           │
│         "port": 5000,                                       │
│         "targetPort": 5000                                  │
│       }]                                                    │
│     }                                                       │
│   }                                                         │
└──────────────────────┬──────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ Step 4: DNS Response 생성                                   │
├─────────────────────────────────────────────────────────────┤
│ Answer:                                                     │
│   frigate.video-management-system.svc.cluster.local.        │
│   30 IN A 10.43.123.45                                      │
│                                                             │
│ Authority: (none)                                           │
│ Additional: (none)                                          │
└──────────────────────┬──────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ Step 5: cache plugin                                        │
├─────────────────────────────────────────────────────────────┤
│ Cache에 저장 (TTL: 30초)                                    │
│   Key: frigate.video-management-system.svc.cluster.local    │
│   Value: 10.43.123.45                                       │
└──────────────────────┬──────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ Step 6: Response 전송                                       │
├─────────────────────────────────────────────────────────────┤
│ UDP Packet:                                                 │
│   Src: 10.43.0.10:53 (CoreDNS)                             │
│   Dst: 10.42.0.100:random (Pod A)                          │
│   Answer: 10.43.123.45                                      │
└─────────────────────────────────────────────────────────────┘

Search Domain과 ndots

bash
# Pod 내부 /etc/resolv.conf
nameserver 10.43.0.10
search workflow-engine.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

ndots:5의 의미:

  • 도메인에 점(.)이 5개 미만이면 search domain을 붙여서 시도
  • frigate.video-management-system.svc.cluster.local
    • 점이 4개 → search domain 시도!

실제 DNS 쿼리 순서:

Original query: frigate.video-management-system.svc.cluster.local

ndots=5 → 점이 4개 → Search domain 적용!

1st try: frigate.video-management-system.svc.cluster.local.workflow-engine.svc.cluster.local
   → NXDOMAIN (없음)

2nd try: frigate.video-management-system.svc.cluster.local.svc.cluster.local
   → NXDOMAIN (없음)

3rd try: frigate.video-management-system.svc.cluster.local.cluster.local
   → NXDOMAIN (없음)

4th try: frigate.video-management-system.svc.cluster.local (원본 그대로)
   → SUCCESS! 10.43.123.45

성능 문제: 불필요한 DNS 쿼리 3번 발생!

해결 방법:

python
# 방법 1: FQDN 사용 (마지막에 점 추가)
url = "http://frigate.video-management-system.svc.cluster.local.:5000"
#                                                            ↑
#                                                            점 추가!

# 방법 2: ndots 조정 (Pod spec)
dnsConfig:
  options:
  - name: ndots
    value: "1"

Tailscale이 DNS에 미치는 영향

Tailscale 기본 동작

┌─────────────────────────────────────────────────────────────┐
│                  Tailscale Network                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Node A (Kubernetes Node)                                   │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ tailscaled                                           │  │
│  │ Tailscale IP: 100.64.1.10                            │  │
│  │ Hostname: k8s-node-1.tailb45a71.ts.net               │  │
│  │                                                      │  │
│  │ /etc/resolv.conf (Host):                            │  │
│  │   nameserver 100.100.100.100  ← Tailscale DNS       │  │
│  │   search tailb45a71.ts.net                          │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                             │
│  Pod (inside Node A)                                        │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ /etc/resolv.conf:                                    │  │
│  │   nameserver 10.43.0.10  ← CoreDNS                   │  │
│  │   search namespace.svc.cluster.local                 │  │
│  │          svc.cluster.local                           │  │
│  │          cluster.local                               │  │
│  │          tailb45a71.ts.net  ← Tailscale domain 추가! │  │
│  │   options ndots:5                                    │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

문제 발생 메커니즘

┌────────────────────────────────────────────────────────────┐
│ Problem: Tailscale Search Domain 간섭                       │
└────────────────────────────────────────────────────────────┘

Query: frigate.video-management-system.svc.cluster.local

Pod의 /etc/resolv.conf:
  nameserver 10.43.0.10
  search workflow-engine.svc.cluster.local
         svc.cluster.local
         cluster.local
         tailb45a71.ts.net  ← 문제!
  options ndots:5

ndots=5 → 점이 4개 → Search domain 시도!

1st: frigate.video-management-system.svc.cluster.local
     .workflow-engine.svc.cluster.local
   → CoreDNS → NXDOMAIN

2nd: frigate.video-management-system.svc.cluster.local
     .svc.cluster.local
   → CoreDNS → NXDOMAIN

3rd: frigate.video-management-system.svc.cluster.local
     .cluster.local
   → CoreDNS → NXDOMAIN

4th: frigate.video-management-system.svc.cluster.local
     .tailb45a71.ts.net  ← 여기서 문제!
   → CoreDNS → kubernetes plugin (cluster.local이 아님)
   → forward plugin (외부 DNS로 전달)
   → 8.8.8.8 or 172.27.150.2 (upstream DNS)
   → Timeout! (존재하지 않는 도메인)

5th: frigate.video-management-system.svc.cluster.local
     (원본 그대로)
   → CoreDNS → kubernetes plugin → SUCCESS!

CoreDNS 로그에서 확인

[ERROR] plugin/errors: 2 frigate.video-management-system.svc.cluster.local.tailb45a71.ts.net. A: read udp 10.42.0.162:51979->8.8.8.8:53: i/o timeout

분석:
- 쿼리: frigate.video-management-system.svc.cluster.local.tailb45a71.ts.net
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
         Kubernetes 도메인                                 Tailscale 도메인

- CoreDNS가 이 쿼리를 받음
- kubernetes plugin: "cluster.local"이 아니므로 pass
- forward plugin: 외부 DNS(8.8.8.8)로 전달
- 8.8.8.8: 이런 도메인 없음 → Timeout

완전한 패킷 흐름 분석

정상 케이스 (Tailscale 없음)

┌─────────────────────────────────────────────────────────────┐
│ Step 1: Application → DNS Query                             │
└─────────────────────────────────────────────────────────────┘

Application (Python):
  requests.get('http://frigate.video-management-system.svc.cluster.local:5000')

↓ getaddrinfo() system call

glibc resolver:
  /etc/resolv.conf 읽기
  nameserver 10.43.0.10
  search workflow-engine.svc.cluster.local svc.cluster.local cluster.local
  options ndots:5

ndots 체크:
  "frigate.video-management-system.svc.cluster.local" → 점 4개
  → Search domain 시도!

DNS Query 1:
  frigate.video-management-system.svc.cluster.local.workflow-engine.svc.cluster.local

┌─────────────────────────────────────────────────────────────┐
│ Step 2: DNS Query → CoreDNS                                 │
└─────────────────────────────────────────────────────────────┘

UDP Packet:
  Src: 10.42.0.100:random
  Dst: 10.43.0.10:53 (CoreDNS Service IP)

L2: Ethernet
  Src MAC: Pod A veth MAC
  Dst MAC: cni0 bridge MAC

L3: IP
  Src IP: 10.42.0.100
  Dst IP: 10.43.0.10

L4: UDP
  Src Port: 54321 (random)
  Dst Port: 53

L7: DNS Query
  Transaction ID: 0x1234
  Flags: Standard query
  Questions: 1
    Name: frigate.video-management-system.svc.cluster.local.workflow-engine.svc.cluster.local
    Type: A
    Class: IN

┌─────────────────────────────────────────────────────────────┐
│ Step 3: CoreDNS Processing                                  │
└─────────────────────────────────────────────────────────────┘

CoreDNS receives query

kubernetes plugin:
  Zone match? "cluster.local"
    frigate.video-management-system.svc.cluster.local.workflow-engine.svc.cluster.local
    → 끝이 "cluster.local"? YES!

  Parse query:
    Service: frigate
    Namespace: video-management-system
    But... ".workflow-engine.svc.cluster.local"이 추가로 붙음
    → Kubernetes API 조회 실패 → NXDOMAIN

DNS Response:
  RCODE: NXDOMAIN (No such domain)

┌─────────────────────────────────────────────────────────────┐
│ Step 4: Retry (Search domain 계속)                          │
└─────────────────────────────────────────────────────────────┘

DNS Query 2:
  frigate.video-management-system.svc.cluster.local.svc.cluster.local
  → NXDOMAIN

DNS Query 3:
  frigate.video-management-system.svc.cluster.local.cluster.local
  → NXDOMAIN

DNS Query 4:
  frigate.video-management-system.svc.cluster.local (원본)
  → SUCCESS! A 10.43.123.45

┌─────────────────────────────────────────────────────────────┐
│ Step 5: HTTP Request → Service                              │
└─────────────────────────────────────────────────────────────┘

TCP Handshake:
  SYN: 10.42.0.100:54322 → 10.43.123.45:5000

iptables DNAT (kube-proxy):
  -A KUBE-SERVICES -d 10.43.123.45/32 -p tcp --dport 5000
     -j KUBE-SVC-FRIGATE

  -A KUBE-SVC-FRIGATE -m statistic --mode random --probability 0.5
     -j KUBE-SEP-FRIGATE-POD1

  -A KUBE-SEP-FRIGATE-POD1 -p tcp
     -j DNAT --to-destination 10.42.2.10:5000

Packet 변환:
  Before DNAT: 10.42.0.100 → 10.43.123.45:5000
  After DNAT:  10.42.0.100 → 10.42.2.10:5000

┌─────────────────────────────────────────────────────────────┐
│ Step 6: Packet Routing to Pod                               │
└─────────────────────────────────────────────────────────────┘

Routing decision:
  Dst: 10.42.2.10
  Route table: 10.42.2.0/24 via flannel.1 (VXLAN)

VXLAN Encapsulation:
  Outer IP: Node A IP → Node B IP
  Outer UDP: Port 4789
  VXLAN: VNI 1
  Inner IP: 10.42.0.100 → 10.42.2.10
  Inner TCP: 54322 → 5000

┌─────────────────────────────────────────────────────────────┐
│ Step 7: Frigate Pod Receives Request                        │
└─────────────────────────────────────────────────────────────┘

VXLAN Decapsulation at Node B

TCP Connection:
  10.42.0.100:54322 ←→ 10.42.2.10:5000

HTTP Request:
  GET /api/115/recordings?after=... HTTP/1.1
  Host: frigate.video-management-system.svc.cluster.local:5000

Frigate Application processes request

HTTP Response:
  HTTP/1.1 200 OK
  Content-Type: application/json
  ...

문제 케이스 (Tailscale 있음)

┌─────────────────────────────────────────────────────────────┐
│ Step 1-3: 동일 (Search domain 시도)                         │
└─────────────────────────────────────────────────────────────┘

Query 1-3: NXDOMAIN (동일)

┌─────────────────────────────────────────────────────────────┐
│ Step 4: Tailscale Search Domain 시도 (문제!)                │
└─────────────────────────────────────────────────────────────┘

DNS Query 4:
  frigate.video-management-system.svc.cluster.local.tailb45a71.ts.net
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
  Kubernetes 도메인                                  Tailscale 도메인

CoreDNS receives query:

kubernetes plugin:
  Zone match? "cluster.local"
    frigate...cluster.local.tailb45a71.ts.net
    → 끝이 "cluster.local"? NO! (끝이 "ts.net")
    → Pass (처리 안 함)

forward plugin:
  외부 DNS로 전달
  forward . /etc/resolv.conf

/etc/resolv.conf (CoreDNS Pod):
  nameserver 8.8.8.8
  nameserver 172.27.150.2

UDP Query:
  Src: CoreDNS Pod (10.42.0.162)
  Dst: 8.8.8.8:53

  Question:
    frigate.video-management-system.svc.cluster.local.tailb45a71.ts.net

8.8.8.8 (Google DNS):
  "그런 도메인 모름"
  → No response (timeout)

CoreDNS:
  [ERROR] plugin/errors: 2 frigate.video-management-system.svc.cluster.local.tailb45a71.ts.net. A: read udp 10.42.0.162:51979->8.8.8.8:53: i/o timeout

Retry with 172.27.150.2:
  → Timeout again

┌─────────────────────────────────────────────────────────────┐
│ Step 5: 최종적으로 원본 도메인 시도                           │
└─────────────────────────────────────────────────────────────┘

DNS Query 5:
  frigate.video-management-system.svc.cluster.local (원본)

CoreDNS kubernetes plugin:
  → SUCCESS! 10.43.123.45

BUT: 이미 많은 시간 소비 (5초 timeout × 여러 번)
     → Application timeout 발생 가능!

문제 해결 방법

방법 1: CoreDNS Rewrite Plugin (권장)

Tailscale search domain을 무시하도록 설정:

yaml
# ConfigMap 수정
kubectl -n kube-system edit cm coredns

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health {
           lameduck 5s
        }
        ready

        # ========================================
        # Tailscale search domain 제거 (추가!)
        # ========================================
        rewrite stop {
            name regex (.*)\.tailb45a71\.ts\.net\.cluster\.local {1}.cluster.local
            answer name (.*)\.cluster\.local {1}.tailb45a71.ts.net.cluster.local
        }

        rewrite stop {
            name regex (.*)\.tailb45a71\.ts\.net {1}
            answer auto
        }

        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }

        prometheus :9153
        forward . /etc/resolv.conf {
           max_concurrent 1000
        }
        cache 30
        loop
        reload
        loadbalance
    }

동작 원리:

Query: frigate.video-management-system.svc.cluster.local.tailb45a71.ts.net

rewrite plugin:
  Pattern match: (.*)\.tailb45a71\.ts\.net
  Capture: frigate.video-management-system.svc.cluster.local
  Rewrite to: frigate.video-management-system.svc.cluster.local

kubernetes plugin:
  Query: frigate.video-management-system.svc.cluster.local
  → SUCCESS!

방법 2: Tailscale DNS 설정 수정

Tailscale이 Kubernetes에 search domain을 주입하지 않도록:

bash
# Tailscale 설정 확인
tailscale status
tailscale netcheck

# Node에서 Tailscale 설정 수정
# /etc/systemd/system/tailscaled.service.d/override.conf
[Service]
Environment="TS_ACCEPT_DNS=false"

# 재시작
sudo systemctl daemon-reload
sudo systemctl restart tailscaled

또는 Tailscale Admin Console에서:

DNS → Global nameservers → Override local DNS
→ Disable "Add search domains to local DNS"

방법 3: Pod DNS Policy 설정

특정 Pod가 Tailscale DNS를 우회하도록:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: airflow-worker
spec:
  dnsPolicy: "None"  # 기본 DNS 설정 무시
  dnsConfig:
    nameservers:
    - 10.43.0.10  # CoreDNS 직접 지정
    searches:
    - workflow-engine.svc.cluster.local
    - svc.cluster.local
    - cluster.local
    # tailb45a71.ts.net 제외!
    options:
    - name: ndots
      value: "2"  # 성능 최적화
    - name: timeout
      value: "2"
    - name: attempts
      value: "2"

방법 4: FQDN 사용 (임시 해결)

애플리케이션 코드 수정:

python
# Before (문제)
url = "http://frigate.video-management-system.svc.cluster.local:5000"

# After (해결)
url = "http://frigate.video-management-system.svc.cluster.local.:5000"
#                                                             ↑
#                                                             점 추가!

왜 동작하는가?:

  • 마지막 점(.) = FQDN (Fully Qualified Domain Name)
  • ndots 체크 무시 → search domain 시도 안 함
  • 바로 원본 도메인으로 쿼리

방법 5: NodeLocal DNSCache 사용

DNS 성능 최적화 + Tailscale 문제 우회:

bash
# NodeLocal DNSCache 설치
kubectl apply -f https://k8s.io/examples/admin/dns/nodelocaldns.yaml

동작:

Pod → 169.254.20.10 (Node-local DNS cache)
      → Cache hit? Return
      → Cache miss? CoreDNS (10.43.0.10)

장점:

  • DNS 쿼리 속도 향상
  • CoreDNS 부하 감소
  • Tailscale search domain 영향 최소화

트러블슈팅 가이드

증상 1: DNS 해석 실패

bash
# Pod 내부에서
kubectl exec -it <pod> -- nslookup frigate.video-management-system.svc.cluster.local

# 실패 시 출력:
Server:         10.43.0.10
Address:        10.43.0.10#53

** server can't find frigate.video-management-system.svc.cluster.local: NXDOMAIN

진단 단계:

bash
# 1. Pod DNS 설정 확인
kubectl exec -it <pod> -- cat /etc/resolv.conf

출력:
nameserver 10.43.0.10
search namespace.svc.cluster.local svc.cluster.local cluster.local tailb45a71.ts.net
options ndots:5

문제: tailb45a71.ts.net이 포함됨!

# 2. CoreDNS 로그 확인
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=100

문제 로그:
[ERROR] plugin/errors: 2 frigate...cluster.local.tailb45a71.ts.net. A: read udp ... i/o timeout

# 3. CoreDNS ConfigMap 확인
kubectl -n kube-system get cm coredns -o yaml

확인 사항:
- rewrite plugin 설정 여부
- kubernetes plugin zone 설정

# 4. Service 존재 확인
kubectl -n video-management-system get svc frigate

출력:
NAME      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
frigate   ClusterIP   10.43.123.45   <none>        5000/TCP   30d

# 5. CoreDNS에서 직접 쿼리
kubectl run -it --rm debug --image=busybox --restart=Never -- \
  nslookup frigate.video-management-system.svc.cluster.local 10.43.0.10

성공하면: CoreDNS는 정상, Pod DNS 설정 문제
실패하면: CoreDNS 설정 문제

증상 2: DNS 응답 느림

bash
# DNS 쿼리 시간 측정
kubectl exec -it <pod> -- time nslookup frigate.video-management-system.svc.cluster.local

출력:
real    0m 5.023s 너무 느림!
user    0m 0.001s
sys     0m 0.002s

원인 분석:

bash
# tcpdump로 DNS 쿼리 캡처
kubectl exec -it <pod> -- tcpdump -i any -nn port 53

출력 분석:
10:30:01.000 A? frigate...workflow-engine.svc.cluster.local
10:30:01.100 NXDOMAIN
10:30:01.200 A? frigate...svc.cluster.local
10:30:01.300 NXDOMAIN
10:30:01.400 A? frigate...cluster.local
10:30:01.500 NXDOMAIN
10:30:01.600 A? frigate...tailb45a71.ts.net 여기서 timeout!
10:30:06.600 Timeout
10:30:06.700 A? frigate...cluster.local (원본)
10:30:06.800 A 10.43.123.45 성공

 5초 이상 소요!

해결:

bash
# 방법 1: ndots 조정
kubectl edit deployment <deployment>

spec:
  template:
    spec:
      dnsConfig:
        options:
        - name: ndots
          value: "2"  # 5 → 2로 변경

# 방법 2: FQDN 사용 (코드 수정)
# 방법 3: CoreDNS rewrite plugin 추가

증상 3: 특정 도메인만 실패

bash
# 증상
kubectl exec -it <pod> -- curl http://frigate.video-management-system.svc.cluster.local:5000
# → 실패

kubectl exec -it <pod> -- curl http://google.com
# → 성공

진단:

bash
# CoreDNS forward 설정 확인
kubectl -n kube-system get cm coredns -o yaml | grep -A 5 forward

출력:
forward . /etc/resolv.conf {
   max_concurrent 1000
}

# CoreDNS Pod의 /etc/resolv.conf 확인
kubectl -n kube-system exec -it <coredns-pod> -- cat /etc/resolv.conf

출력:
nameserver 8.8.8.8
nameserver 172.27.150.2
search tailb45a71.ts.net 문제!

# 외부 도메인은 성공 (8.8.8.8이 응답)
# 클러스터 도메인 + Tailscale suffix는 실패

모니터링 및 디버깅

CoreDNS 메트릭 확인

bash
# Prometheus metrics
kubectl -n kube-system port-forward svc/kube-dns 9153:9153

# 메트릭 확인
curl http://localhost:9153/metrics

주요 메트릭:
# coredns_dns_request_duration_seconds: DNS 쿼리 시간
# coredns_dns_requests_total: 총 DNS 요청 수
# coredns_dns_responses_total: DNS 응답 (NOERROR, NXDOMAIN 등)
# coredns_forward_requests_total: Forward된 쿼리 수

Grafana Dashboard:

yaml
# Prometheus Query 예시
# 실패한 DNS 쿼리 비율
sum(rate(coredns_dns_responses_total{rcode="NXDOMAIN"}[5m]))
/
sum(rate(coredns_dns_responses_total[5m])) * 100

# Tailscale 도메인 쿼리 수
sum(rate(coredns_forward_requests_total{to=~".*tailb45a71.ts.net.*"}[5m]))

# DNS 쿼리 지연 시간 (p99)
histogram_quantile(0.99,
  sum(rate(coredns_dns_request_duration_seconds_bucket[5m])) by (le)
)

패킷 캡처로 분석

bash
# CoreDNS Pod에서 패킷 캡처
kubectl -n kube-system exec -it <coredns-pod> -- \
  tcpdump -i any -nn -s 0 -w /tmp/coredns.pcap port 53

# 파일 다운로드
kubectl -n kube-system cp <coredns-pod>:/tmp/coredns.pcap ./coredns.pcap

# Wireshark로 분석
wireshark coredns.pcap

분석 포인트:

Filter: dns

정상 쿼리:
  Query: frigate.video-management-system.svc.cluster.local
  Response time: 0.001s
  Answer: 10.43.123.45

문제 쿼리:
  Query: frigate.video-management-system.svc.cluster.local.tailb45a71.ts.net
  Response time: 5.000s (timeout)
  Answer: (none)

DNS 쿼리 추적 (dig)

bash
# Pod 내부에서
kubectl exec -it <pod> -- sh

# dig 설치 (Alpine 기준)
apk add bind-tools

# DNS 쿼리 추적
dig +trace frigate.video-management-system.svc.cluster.local @10.43.0.10

출력:
; <<>> DiG 9.16.1 <<>> +trace frigate...
;; global options: +cmd
.                       518400  IN      NS      a.root-servers.net.
...

; 최종 응답
frigate.video-management-system.svc.cluster.local. 30 IN A 10.43.123.45

;; Query time: 1 msec
;; SERVER: 10.43.0.10#53(10.43.0.10)

로그 기반 모니터링

bash
# CoreDNS 에러 로그 실시간 모니터링
kubectl -n kube-system logs -f -l k8s-app=kube-dns | grep ERROR

# Tailscale 관련 에러만
kubectl -n kube-system logs -f -l k8s-app=kube-dns | grep tailb45a71

# 특정 도메인 쿼리만
kubectl -n kube-system logs -f -l k8s-app=kube-dns | grep frigate

정리

문제 원인 요약

1. Tailscale이 Node에 설치됨
   └→ /etc/resolv.conf에 search domain 추가
       search tailb45a71.ts.net

2. Kubernetes가 Pod 생성 시 resolv.conf 상속
   └→ Pod의 /etc/resolv.conf에도 tailb45a71.ts.net 추가

3. Pod에서 Service 호출
   └→ frigate.video-management-system.svc.cluster.local

4. glibc resolver의 ndots=5 체크
   └→ 점이 4개 → Search domain 시도!

5. 4번째 시도에서 Tailscale domain 추가
   └→ frigate...cluster.local.tailb45a71.ts.net

6. CoreDNS가 이 쿼리를 받음
   └→ kubernetes plugin: "cluster.local"로 끝나지 않음 → Pass
   └→ forward plugin: 외부 DNS(8.8.8.8)로 전달

7. 외부 DNS가 응답 못함 (존재하지 않는 도메인)
   └→ Timeout (5초)

8. 최종적으로 원본 도메인으로 재시도
   └→ 성공하지만 이미 많은 시간 소비

최적 해결 방법

┌─────────────────────────────────────────────────────────────┐
│               권장 해결 방법 우선순위                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│ 1순위: CoreDNS rewrite plugin (클러스터 레벨)                │
│   - 모든 Pod에 적용                                         │
│   - 애플리케이션 코드 수정 불필요                            │
│   - 성능 영향 최소                                           │
│                                                             │
│ 2순위: Pod dnsPolicy/dnsConfig (Pod 레벨)                   │
│   - 특정 워크로드만 조정                                     │
│   - ndots 최적화 가능                                        │
│   - 세밀한 제어 가능                                         │
│                                                             │
│ 3순위: Tailscale DNS 설정 변경 (Node 레벨)                  │
│   - Tailscale 전체에 영향                                    │
│   - 다른 Tailscale 기능 영향 가능                            │
│                                                             │
│ 4순위: FQDN 사용 (코드 레벨)                                 │
│   - 임시 해결책                                              │
│   - 모든 도메인에 점(.) 추가 필요                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

성능 최적화

yaml
# 최적화된 DNS 설정
apiVersion: v1
kind: Pod
metadata:
  name: optimized-pod
spec:
  dnsPolicy: "None"
  dnsConfig:
    nameservers:
    - 10.43.0.10
    searches:
    - video-management-system.svc.cluster.local  # 자주 쓰는 namespace
    - svc.cluster.local
    - cluster.local
    options:
    - name: ndots
      value: "2"  # 성능 향상 (5 → 2)
    - name: timeout
      value: "2"  # 빠른 실패
    - name: attempts
      value: "2"  # 재시도 횟수
    - name: single-request-reopen  # A와 AAAA 쿼리 병렬화
    - name: use-vc  # TCP 사용 (대용량 응답)

성능 개선 효과:

Before (ndots=5, Tailscale domain 포함):
  - DNS 쿼리 시간: 5초+
  - 불필요한 쿼리: 4번

After (ndots=2, Tailscale domain 제거):
  - DNS 쿼리 시간: 0.001초
  - 불필요한 쿼리: 0번

→ 5000배 성능 향상!

이제 Kubernetes Service DNS 통신과 Tailscale DNS 간섭 문제를 완벽히 이해하고 해결할 수 있습니다!