LabHub
배우기 러닝패스 코스

アドレス・サブネット・ゲートウェイ

中から外の住所で自分を呼ぶとき

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

집 안에서 내 공인 도메인으로 접속하면 패킷이 공유기까지 갔다가 되돌아와야 하는데, 많은 공유기가 그걸 못 한다. 이것을 헤어핀 NAT(또는 NAT 루프백)이라 한다.

フロー図: 되돌아와야・자기 자신・1.2.3.4 에서 올 응답・출발지도

왜 이게 필요했나

집에 서버를 두고 labhub.hopto.org 로 열어 두면 밖에서는 잘 되는데 집 안 노트북에서만 안 되는 일이 흔하다. 원인은 이렇다.

  1. 노트북(192.168.0.10)이 labhub.hopto.org 를 조회한다 → 공인 IP(1.2.3.4)가 나온다
  2. 노트북이 1.2.3.4 로 패킷을 보낸다 → 자기 서브넷 밖이므로 공유기로 간다
  3. 공유기는 1.2.3.4 가 자기 자신임을 알아채고, 포트포워딩 규칙에 따라 192.168.0.20(서버)으로 목적지를 바꾼다
  4. 서버가 응답한다. 그런데 응답의 출발지는 192.168.0.20 이고 노트북은 1.2.3.4 에서 올 응답을 기다리고 있다
  5. 노트북은 그 응답을 "내가 보낸 적 없는 연결" 로 보고 버린다

노트북이 공인 주소 1.2.3.4 로 보낸 패킷을 공유기가 목적지만 서버 192.168.0.20 으로 바꿔 넘기면, 서버는 같은 서브넷의 노트북에게 출발지 192.168.0.20 으로 곧장 응답한다. 1.2.3.4 의 답을 기다리던 노트북은 그것을 내가 연 연결이 아니라고 보고 버린다

패킷이 공유기에서 U턴한다고 해서 헤어핀(머리핀)이다. 해결하려면 공유기가 목적지뿐 아니라 출발지도 자기 주소로 바꿔야 한다(SNAT 을 함께 건다). 그러면 서버는 공유기에게 응답하고, 공유기가 노트북에게 되돌려 준다.

어떻게 동작하나

증상을 확인하는 방법은 단순하다.

# 밖에서는 되는데 안에서만 안 되는가?
curl -sI https://labhub.hopto.org        # 집 안에서 → 타임아웃
curl -sI http://192.168.0.20             # 사설 주소로 직접 → 됨

내부 주소로는 되고 도메인으로는 안 되면 헤어핀이다.

어떻게 푸는가

공유기가 헤어핀을 지원하면 켜면 된다(설정 이름이 제조사마다 다르다 — NAT Loopback, Hairpin NAT, 내부 접속 허용).

지원하지 않으면 스플릿 호라이즌 DNS 가 정석이다. 같은 도메인을 안에서 물으면 사설 주소를, 밖에서 물으면 공인 주소를 답하게 한다.

이 방식이 더 좋은 이유가 하나 더 있다. 헤어핀은 트래픽을 공유기까지 왕복시키므로 집 안 통신인데 공유기 성능에 묶인다. 스플릿 DNS 는 스위치 안에서 끝난다.

흔한 착각

hosts 파일로 때우는 것. 노트북 한 대는 되지만 휴대폰과 태블릿은 안 된다. DNS 계층에서 푸는 것이 맞다.

공인 IP 가 바뀌면 끝난다는 착각. DDNS 를 쓰면 공인 IP 는 따라가지만, 내부 DNS 에 박아 둔 사설 주소는 서버를 옮기면 손으로 고쳐야 한다. 두 곳을 관리해야 한다는 사실을 기억해 둔다.

안팎에서 다르게 보이는 것들

헤어핀은 "같은 이름이 위치에 따라 다른 경로가 된다" 는 더 큰 문제의 한 사례다. 같은 뿌리에서 나오는 다른 증상들을 함께 알아 두면, 낯선 증상을 만났을 때도 어디를 볼지 짐작할 수 있다.

인증서 이름이 안 맞는다. 내부에서 사설 주소로 직접 접속하면 인증서에 적힌 도메인과 접속한 주소가 달라 경고가 뜬다. 그래서 스플릿 DNS 로 같은 이름을 쓰되 주소만 다르게 하는 것이 중요하다. 주소로 접속하게 만들면 이 문제가 따라온다.

접속 기록의 출발지가 전부 같아 보인다. 공유기나 프록시가 출발지를 자기 주소로 바꾸면, 서버 쪽 로그에는 모든 요청이 한 주소에서 온 것으로 남는다. 그러면 특정 사용자를 찾을 수 없고, 출발지 기준 차단이나 속도 제한이 전부 무의미해진다. 프록시가 원래 주소를 헤더에 실어 주더라도 그 헤더를 믿을 수 있는 것은 우리가 통제하는 프록시가 붙여 준 것일 때뿐이다. 밖에서 들어온 값을 그대로 믿으면 누구나 자기 주소를 위조할 수 있다.

같은 서비스가 안에서만 느리다. 트래픽이 공유기나 게이트웨이까지 나갔다 돌아오면, 물리적으로는 옆에 있는 장비인데 경로가 길어져 지연이 커진다. 앞에서 스플릿 DNS 가 스위치 안에서 끝난다고 한 것이 이 대목이다.

정리하면 원칙은 하나다. 이름은 어디서나 같게 두고, 그 이름이 가리키는 주소만 위치에 따라 다르게 한다. 이름을 나누면(내부용 도메인을 따로 만들면) 인증서·설정·문서가 전부 두 벌이 되고, 어느 날 한쪽만 고쳐지면서 조용히 어긋나기 시작한다. 그리고 두 벌 중 어느 쪽이 옳은지는 대개 장애가 난 뒤에야 밝혀진다. 이름을 하나로 두는 규칙은 편의가 아니라 사고를 줄이는 장치다. 내부에서만 쓰는 서비스라도 마찬가지로, 처음부터 제대로 된 이름과 인증서를 붙여 두는 편이 나중에 밖으로 열 때 훨씬 싸게 끝난다.

실무에서 진짜 중요한 것

쿠버네티스에도 같은 문제가 있다. 파드가 자기 서비스의 외부 주소(LoadBalancer IP)로 자기 자신을 부르면 같은 헤어핀 상황이 된다. externalTrafficPolicy: Local 을 쓰면 노드가 SNAT 을 하지 않아 이 경우 응답이 돌아오지 못할 수 있다.

그래서 클러스터 안에서는 항상 서비스 이름(svc.namespace.svc.cluster.local)으로 부른다. 외부 주소로 자기를 부르지 않는다 — 이 한 줄이 헤어핀 문제의 절반을 없앤다.