LFCS — 리눅스 재단 시스템 관리자 · 네트워킹 · 이론
이름이 주소가 되는 길, 그리고 주소가 바뀌던 날
한 줄 요약
네트워크 문제는 "안 된다" 로 보고되지만 실제로는 이름 해석·라우팅·소켓 세 구간 중 하나에서 끝난다. 어느 구간인지 말하지 않은 진단은 진단이 아니다.
왜 이게 필요했나
curl 이 실패했다. 여기서 곧장 DNS 를 의심하는 것은 절반만 맞다. 리눅스 호스트에서 이름 해석은 DNS 만의 일이 아니다. 먼저 /etc/nsswitch.conf 의 hosts: 행이 어떤 순서로 어디에 물어볼지를 정한다. 보통 files dns 이고, 그래서 /etc/hosts 가 DNS 보다 먼저 답한다. 그다음에야 /etc/resolv.conf 가 어느 서버에 물을지, 어떤 검색 도메인을 붙일지를 결정한다.
이 구조 때문에 진단 도구를 고르는 방법이 정해진다. getent hosts 는 이 경로 전체를 타고, dig 는 DNS 만 본다. 둘의 결과가 다르면 문제는 DNS 서버가 아니라 스텁 리졸버 구간에 있다. 이 한 줄이 장애 회의를 5분 단축한다.
/etc/resolv.conf 에서 기억할 값들이 있다. nameserver 는 최대 3개까지만 쓰인다. options timeout 은 기본 5초, options attempts 는 기본 2회다. 그래서 첫 번째 네임서버가 죽어 있으면 사용자는 기본값 기준 5초를 그대로 체감한다 — 이중화를 해 두고도 느린 이유다. options ndots 는 이름에 점이 몇 개 이상 있어야 검색 도메인을 붙이지 않고 절대 이름으로 먼저 질의할지의 임계값이고 기본은 1 이다. 컨테이너 환경에서 이 값이 크게 잡혀 있으면 외부 이름 하나를 푸는 데 실패 질의가 여러 번 앞서 나간다.
어떻게 동작하나
라우팅 테이블은 위에서 아래로 훑는 목록이 아니라 가장 긴 프리픽스가 이기는 조회다. 10.0.0.0/24 와 0.0.0.0/0 이 둘 다 있으면 10.0.0.5 로 가는 패킷은 언제나 앞쪽을 탄다. 기본 경로는 프리픽스 길이 0 짜리, 즉 "아무것도 매칭되지 않을 때" 의 경로다. ip route 출력에서 via 는 다음 홉, dev 는 나가는 장치, src 는 출발지 주소로 쓸 값이다.
소켓 상태는 그 자체가 진단 정보다.
| 상태 | 뜻 | 많이 보이면 |
| --- | --- | --- |
| LISTEN | 서버가 연결을 기다림 | 정상 |
| ESTABLISHED | 양방향 연결 성립 | 정상. 개수가 한도에 붙으면 문제 |
| SYN-SENT | 연결 요청을 보내고 응답 대기 | 상대가 없거나 중간에서 버려짐 |
| TIME-WAIT | 능동적으로 닫은 쪽이 마지막 패킷을 기다림 | 짧은 연결을 대량으로 여닫는 중 |
| CLOSE-WAIT | 상대가 닫았는데 이쪽이 닫지 않음 | 애플리케이션 버그 신호 |
TIME-WAIT 이 많은 것은 대개 정상이다. 연결을 먼저 끊은 쪽에 남는 상태이고, 뒤늦게 도착한 패킷이 새 연결을 오염시키지 않도록 하는 장치다. 반면 CLOSE-WAIT 이 쌓이는 것은 코드가 소켓을 닫지 않고 있다는 뜻이라 성격이 완전히 다르다.
현장에서 만나는 모습
저자의 홈랩 클러스터가 어느 날 통째로 응답하지 않았다. 로그에는 dial tcp 10.0.0.111:6443: connect: no route to host 만 반복됐다. 진단은 5분 만에 끝났다 — ip -4 addr show 를 보니 컨트롤 플레인 노드의 주소가 .111 에서 .120 으로 바뀌어 있었고, 출력에 scope global dynamic 이 붙어 있었다. DHCP 임대 갱신 과정에서 다른 주소를 받은 것이다.
결정적인 증거는 인증서에 있었다. apiserver 인증서의 SAN 목록에는 10.0.0.111 만 있고 .120 이 없었다. 그래서 etcd 와 kube-apiserver 는 존재하지 않는 .111 에 바인드하려다 실패해 재시작 루프에 빠졌고, kube-scheduler 와 controller-manager 는 127.0.0.1 에 바인드하니 프로세스는 살아 있으되 아무 일도 못 하는 상태가 됐다. 주소 한 개가 바뀌어 7노드짜리 플랫폼 전체가 멈춘 것이다.
재발 방지는 netplan 으로 정적 주소를 박는 것이었다. 인터페이스에 dhcp4: false 를 주고 주소와 기본 경로, 네임서버를 직접 지정한다. 그런데 그것만으로는 부족했다. cloud-init 이 부팅 때 네트워크 설정을 다시 생성해 덮어써 버리기 때문에, 다음 한 줄을 함께 넣어야 했다.
echo 'network: {config: disabled}' > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg여기에 더해 공유기 쪽에서도 해당 MAC 에 DHCP 예약을 걸거나 그 주소를 풀에서 빼야 한다. 호스트만 고정해 두면 다른 기기가 같은 주소를 임대받아 충돌할 수 있기 때문이다. 세 가지를 다 해야 "고정" 이다. 적용 후 ip addr 출력에서 dynamic 표시가 사라진 것이 확인 신호였다.
교훈이 하나 더 있다. 이 클러스터는 controlPlaneEndpoint 가 VIP 나 DNS 이름이 아니라 특정 노드의 물리 IP 로 박혀 있었다. 그래서 나중에 컨트롤 플레인을 3대로 늘려 etcd 쿼럼을 갖췄는데도, 그 한 대가 죽으면 나머지 두 대가 멀쩡해도 모든 클라이언트가 문을 못 찾는 상태가 됐다. 주소를 간접화하지 않으면 이중화는 데이터 가용성만 올리고 접근 가용성은 그대로다.
다음 실습에서 할 것
netplan 정적 주소 파일과 cloud-init 비활성화 파일을 직접 쓴다. /etc/hosts 에 항목을 넣고 getent 로 그 경로가 실제로 타지는지 확인하고, /etc/resolv.conf 와 /etc/nsswitch.conf 를 읽어 요약을 만든다. 리스너를 하나 띄워 ss 로 소켓을 관찰하고, ip route 에서 기본 경로를 파싱한다. 마지막으로 방화벽 규칙을 문서로 설계하면서 규칙 순서가 왜 생사를 가르는지 확인한다. 이 환경에서는 iptables 와 ping 이 막혀 있어 규칙 적용과 도달성 확인은 개념으로 다룬다.