배포 당시를 대략적으로 표현한 아키텍처
배경
- 제공받은 EC2에 기본적으로 UFW가 설치되어 있었고, 방화벽 설정을 UFW를 통해 관리했다.
- 모든 요청을 Nginx가 받고, 각 애플리케이션 컨테이너로 포워딩하도록 하고 싶었다.
- 세 컨테이너 모두 같은 Docker network에 포함시켜 뒀기 때문에 Nginx 설정에서는
proxy_pass http://container_name:port 이렇게 포워딩하도록 설정해 뒀었다.
상황
- 하지만 분명 8080 포트를 allow 한적 없는데, 로컬에서 http://domain:8080으로 요청을 보내니 응답이 오는 문제가 생겼었다.
- 직접 ufw deny 8080을 해봐도 정상적으로 요청이 가고 응답이 왔다.
UFW가 적용되지 않은 이유
도커 공식 문서의 Packet filtering and firewalls 파트 시작에서 언급하기를, 리눅스 환경에서 도커는 iptables 정책을 조작하여 네트워크 격리를 제공한다고 한다.
바로 그 아래 문단을 보면 도커는 iptables의 FORWARD 체인에 DOCKER-USER, DOCKER라는 두 개의 체인을 설정하는데, 이 두 체인이 들어오는 패킷들을 항상 먼저 검사하도록 한다고 한다.
내게 필요한 내용은 그 아래 있었다.
도커의 모든 iptables 정책은 DOCKER 체인에 추가되는데 이는 건들지 말고, 추가 정책을 Docker가 지정한 정책보다 앞에 적용되도록 하고 싶으면 DOCKER-USER 체인에 추가하라는 것이다.
그리고 직접 추가하는 정책이나 iptables 기반의 방화벽에 의해 FORWARD 체인에 추가되는 정책들은 DOCKER-USER, DOCKER 체인의 작동 이후 작동된다고 이야기하고 있다.
그렇기 때문에 우리가 -p 옵션으로 포트를 publish 하게 되면, 방화벽 정책으로 어떤 설정을 하든 이 포트는 publish 되어 외부에서 접근할 수 있다는 것이다.
문서를 내려보면 도커와 ufw에 대해서도 다루고 있다.
Docker를 통해 포트를 publish하면 위 문단에서 언급한 대로 트래픽이 ufw가 설정한 정책에 걸리기 전에 도커의 정책에 먼저 걸려 우회된다는 것.
해결
대부분 해결 방법으로 Docker daemon에 iptables:false 옵션을 추가하거나, git에 공개된 툴을 사용해 해결하는 방법이었다.
다른 방법이 없을까 찾아보기 시작했고, -e(Expose) 옵션에 대해 알게 되었다.
이 글에서는 expose는 컨테이너 간, 내부에서 통신 가능하도록 어느 포트를 사용 중이라고 알리는 용도이고, publish는 외부 접근을 허용하도록 한다고 설명하고 있다.
도커 공식문서 run 옵션에 대한 설명 부분에도 -p 옵션으로 publish하게 되면 UFW 설정을 하더라도 publish 된다고 설명하고 있다.
expose의 경우 호스트 시스템(내 경우 EC2의 Ubuntu)의 네트워크 인터페이스에 포트를 publish 하지 않는다고 한다.
이를 보고 힌트를 얻어 젠킨스를 통해 컨테이너를 띄울 때 아래와 같이 옵션을 바꾸어 주었다.
원하던 대로 모든 요청은 Nginx를 통해서 각 애플리케이션으로 포워딩되고, Spring과 React 컨테이너에 외부에서 접근할 수 없게 설정할 수 있었다.
정리
- 도커는 iptables에 DOCKER-USER, DOCKER라는 두 체인을 FORWARD체인에 추가하여 네트워크 격리를 지원한다.
- UFW를 통해 설정한 정책은 도커가 추가한 체인보다 후순위로 적용되기 때문에, 도커의 -p 옵션을 통해 publish한 포트에 대한 트래픽은 UFW 정책에 도달하기 전 우회된다.