최근 docker를 활용해 간단하게 애플리케이션을 서버에 올렸는데, 최근에 장애가 발생한 적이 있었다. 해당 애플리케이션은 클라우드 환경과 온프레미스 환경을 연결해 주는 프락시 서비스였는데, 이상하게 갑자기 통신이 안된다는 연락을 받은 것이었다. 다른 곳은 문제가 없지만, 172.17 대역으로 나가는 요청이 이상하게 되지 않는다는 것이었다.
직접 디버깅을 하지 못했지만, 원인은 docker를 설치했기 때문이었다. 그러면 왜 docker를 설치한 것이 문제가 되었는지 확인해 보자.
Docker는 설치를 하면 내부적으로 기본 네트워크 대역을 생성한다. 그 과정은 docker daemon을 올릴 때 발생하는데, 상세한 과정은 다음과 같다.
먼저, docker daemon을 실행하는 과정에서 Network Controller를 생성하고, default ipam driver를 세팅한다. ipam.Register() 함수의 인자를 보면 DefaultAddressPool이 있는데, 이게 바로 외부에서 설정할 수 있는 기본 IP 주소 Pool이다.
Register 함수를 보면, 다음과 같이 lAddrPools라는 변수로 받는다. 이를 defaultipam.Register 함수에 전달한다. defaultipam.register() 함수는 기본 IPAM 드라이버를 설치하는 과정이라고 보면 된다.
IPAM driver는 별도로 설정된 Pool이 없을 때 GetLocalScopeDefaultNetworks() 함수를 호출한다.
GetLocalScopeDefaultNetworks 함수는 단순히 localScopeDefaultNetworks 값을 복제해서 반환해 준다. 그런데 여기서 설정되는 bridge 네트워크의 기본 대역을 보면 172.17.0.0/16이다. 리스트 위부터 순서대로 적용되기 때문에, 제일 먼저 172.17.0.0/16 대역이 할당된다.
이렇게 IPAM 세팅이 끝나면 daemon 실행에 앞서, default bridge 네트워크를 생성한다. 만약 dockerd 명령어를 통해 데몬을 실행할 때 --bridge none 옵션을 주면 기본 bridge를 생성하지 않는다.
initBridgeDriver() 함수를 따라 내려가다 보면 결론적으로 RequestPool이라는 함수가 수행된다. 이때 만약 별도로 요청은 Pool이 없으면, PredefinedPool에서 네트워크 대역을 할당한다. 여기서 PredefinedPool이 이전에 살펴보았던 localScopeDefaultNetworks이다.
이렇게 네트워크 대역을 할당하고 나면, 실제 로컬 네트워크의 IP 테이블을 수정한다. 그러면 내부적으로 로컬 네트워크로 172.17.0.0/16 이 추가되고, NAT 라우팅 규칙에 의해서 172.17.0.0/16 대역은 docker0로 향하게 된다.
이와 같은 이유로 기존에 정상적으로 통신하던 172.17 서버와 갑자기 통신이 안되기 시작했다. docker를 설치하는 과정에서, docker가 iptable을 변경했고, 그 순간부터 172.17 서버로 가는 모든 패킷이 docker0로 향했던 것이다.
이를 해결하기 위해서는 docker의 bridge 네트워크 설정을 변경해야 한다. 앞서, DefaultAddressPool 인자가 설정되어 있으면, 기본 Address Pool을 사용하지 않는 것을 확인했다. 즉, DefaultAddressPool 설정 값을 추가하면 회피할 수 있다는 의미이다.
그래서 아래와 같이 /etc/docker/daemon.json 에 default-address-pools를 설정했다.
이제 docker를 재시작하면 다음과 같이 docker0의 IP대역이 변경되는 것을 볼 수 있다.
docker를 사용할 때는 docker 로컬 네트워크를 별도로 설정하기 때문에 해당 네트워크 대역과 기존에 통신하고 있었다면 주의가 필요하다. 기본적으로 아무런 설정을 하지 않으면, 172.17부터 시작하는 IP 대역을 bridge 네트워크에 할당한다. 이를 회피하기 위해서는 default-address-pools 설정을 추가하면 된다.