어제까지 잘 들어가던 bastion 서버가 아무 반응이 없었다. 멀티 테넌트 SaaS 전환 프로젝트의 개발 환경에 들어가는 입구라 이게 막히면 클러스터도 AWS CLI도 못 쓴다. 이상한 건 한 팀원 분은 잘 들어가졌고, 또 한 분은 나와 같이 접속이 안되고있었다. 처음엔 내 ssh 클라이언트가 고장 난 줄 알았다.

debug1: Reading configuration data ~/.ssh/config
debug1: ~/.ssh/config line 6: Applying options for example-bastion
debug1: Connecting to 203.0.113.10 [203.0.113.10] port 2222.
debug1: connect to address 203.0.113.10 port 2222: Operation timed out
ssh: connect to host 203.0.113.10 port 2222: Operation timed out

ssh -v로 보면 키 교환이나 인증 로그는 한 줄도 없다. ssh가 말을 걸기도 전에 TCP 연결이 안 열린 것이다. 키가 틀렸다면 Permission denied가, 서버가 포트를 안 열었다면 Connection refused가 나왔을 것이다. nc로 보면 bastion 세 대 모두 타임아웃인데 github.com의 22번 포트는 붙었다.

bastion으로 가는 길을 물어보니 en0(Wi-Fi)이 아니라 Zscaler의 터널 인터페이스였다.

route -n get 203.0.113.10
#    route to: 203.0.113.10
#     gateway: 100.64.0.1
#   interface: utun4

Zscaler는 default route는 그대로 Wi-Fi에 두고, 그보다 좁은 대역(2/7, 16/4 같은 줄)을 잘게 쪼개 utun4로 보낸다. 라우팅은 더 구체적인 경로가 이기므로(longest prefix match) 거의 모든 인터넷 트래픽이 Zscaler 터널을 탄다. 바깥에서 나는 사무실 회선이 아니라 Zscaler 서울 노드의 출구 IP(192.0.2.120)로 보이고, bastion 보안 그룹은 그 IP를 허용하지 않았다. 보안 그룹은 허용되지 않은 패킷에 거절 응답을 보내지 않고 그냥 버린다. 타임아웃이 난 이유가 이거였다.

확인은 bastion으로 가는 길만 회선으로 보내 보는 것으로 했다. /32 host route는 Zscaler가 깔아 둔 어떤 대역보다 구체적이다.

sudo route add -host 203.0.113.10 192.168.0.1 && nc -vz -G 5 203.0.113.10 2222; sudo route delete -host 203.0.113.10
# add host 203.0.113.10: gateway 192.168.0.1
# Connection to 203.0.113.10 port 2222 [tcp/*] succeeded!
# delete host 203.0.113.10

바로 붙었다. 경로 말고 바뀐 건 bastion이 보는 출발지 IP 하나뿐이니 원인은 확정이다. 그 자리에서 bastion 세 대에 host route를 넣고 일을 이어 갔다. 게이트웨이 192.168.0.1은 그때 붙어 있던 사무실 네트워크 기준이고, 회사가 깔아 둔 보안 도구를 비켜 가는 방법이라 진단과 임시 조치로만 써야 한다. 전날까지는 왜 됐는지, 팀원은 왜 되는지는 끝내 풀지 못했다.

2주 뒤, 로그부터 판 게 실수였다

9월 22일 아침, ssh example-bastion이 또 안 됐다. 처음 든 생각은 “컴퓨터를 오래 켜 둬서 그런가”였다. 9월 7일 일은 머릿속에 없었다. ssh -v, nc, route -n get, Zscaler 터널 로그와 압축된 지난 로그까지 처음부터 다시 풀어 봤다. 10분 남짓 파고서야 쉘 히스토리에서 9월 7일의 route add를 다시 만났다.

netstat -rn -f inet | grep 203.0.113.10
# (출력 없음: host route가 없다)

uptime
# 9:05  up 16:45, ...

전날 오후에 재부팅했고, route add로 넣은 경로는 메모리에만 있어서 재부팅이나 네트워크 변경 때 사라진다. 원인은 2주 전과 똑같았다. 라우팅 테이블 한 줄만 먼저 봤으면 끝날 일이었다. 그래서 순서를 바꿨다. 같은 증상이면 로그를 열기 전에 라우팅 테이블부터 본다.

# 1. 우회 route가 살아 있는지
netstat -rn -f inet | grep 203.0.113.10

# 2. 없으면 지금 네트워크의 게이트웨이를 확인하고
route -n get default

# 3. 다시 넣고 바로 확인
sudo route add -host 203.0.113.10 192.168.0.1 && nc -vz -G 5 203.0.113.10 2222

정식 해결은 bastion 보안 그룹에 Zscaler 출구 대역(192.0.2.0/24)을 추가하는 것이다. 출구 IP는 노드에 따라 바뀔 수 있으니 IP 하나가 아니라 대역으로 넣는다. 운영사 인프라 쪽 보안 그룹이라 내가 직접 바꿀 수는 없고, 9월 22일 기준으로 아직 반영되지 않았다.