일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- MongoDB
- aws codecommit
- namespace
- 포인트앱
- 하나머니
- docker network
- AWS
- S3
- 실사용
- Container
- mininet
- MongoEngine
- 재테크
- clone
- 후기
- codecommit
- docker
- network
- 커피머니불리기
- 토스카드
- 도커
- DocumentDB
- Python
- 리워드앱
- python3
- VPC
- 리뷰
- built-in
- 앱테크
- Linux
- Today
- Total
ㅍㅍㅋㄷ
Docker Network 구조(3) - container 외부 통신 구조 본문
Docker Network 구조(3) - Container 외부 통신 구조
[Contents]
1. Docker Network 구조(1) - docker0와 container network 구조
2. Docker Network 구조(2) - Container network 방식 4가지
3. Docker Network 구조(3) - Container 외부 통신 구조
Docker host에 container 가 배포되면 각 container 에는 격리된 네트워크 환경(namespace)이 제공된다. 이 네트워크 환경은 오로지 각각의 container 만을 위한 네트워크 환경이다. 각 container에는 통신을 위한 인터페이스도 새롭게 할당되며, mac 주소와 prviate IP도 부여 받게 된다.
각 Container 들의 인터페이스는 자신들이 상주하고 있는 Docker host 와 통신을 위해 linux bridge 방식으로 binding 되어 있다. 따라서 같은 Docker host 내에 배포된 container 들 사이에는 각자 할당받은 Private IP를 이용해 자유롭게 통신이 가능하다.
그렇다면, web 서비스를 하는 container 를 생성한다고 가정해보자.
web 서비스를 위한 container 라 한다면, http 통신을 위한 80 포트는 반드시 외부와 통신이 되어야 서비스가 가능하다.
하지만, container는 Private IP를 부여 받고, Docker host 내 bridge 형태로 연결되어 있기 때문에 직접적으로 외부와의 통신은 불가능한 상태이다.
container 가 외부로 서비스되기 위해, 어떤 구조로 동작하는지 알아보자.
Container Port 외부로 노출(expose) 하기
container 를 생성하면 기본적으로 외부와 통신이 불가능한 상태이다. 따라서 외부와 통신을 위해서는 container를 외부로 노출할 Port를 지정해야한다. 노출할 port를 지정하는 방법은 container 를 생성할때 -p option을 이용하면 된다.
root@~~# docker run -d -p 8080:80 --name web_svr01 httpd
위 명령대로 실행하면, 외부에서 Docker host 의 8080 포트로 요청이 들어오면 web_svr01 컨테이너의 80 포트로 해당 요청을 forwarding 하겠다는 의미이다. 아래와 같이 container 상태를 살펴 보면 8080 -> 80 포트로 forwarding 되어 있는 것을 볼 수 있다.
root@~~# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0fa48359f36d httpd "httpd-foreground" 12 minutes ago Up 12 minutes 0.0.0.0:8080->80/tcp web_svr01
Docker host에서 netstat 명령을 통해 listening 상태를 확인해 보면 아래와 같이 8080 포트가 active 되어 있는것을 확인할 수 있다.
root@~~# netstat -nlp | grep 8080
tcp6 0 0 :::8080 :::* LISTEN 12581/docker-proxy
그런데 이 8080 포트를 listen 하고 있는 프로세스는 다름아닌 docker-proxy 라는 프로세스이다.
docker-proxy 프로세스는 무엇인지 알아보자.
docker-proxy
이 프로세스의 목적은 그 이름처럼 docker host 로 들어온 요청을 container 로 넘기는 것 뿐이다. docker-proxy 는 kernel이 아닌 userland 에서 수행되기 때문에 kernel 과 상관없이 host가 받은 패킷을 그대로 container의 port로 넘긴다.
container 를 시작할때 port를 외부로 노출하도록 설정하게 되면, docker host에는 docker-proxy 라는 프로세스가 생성되게 된다.
UID PID PPID C STIME TTY TIME CMD
root 12581 9576 0 06:31 ? 00:00:00 docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8080 -container-ip 172.17.0.2 -container-port 80
docker-proxy 프로세스는 container의 port를 노출하도록 설정한 수 만큼 추가로 프로세스가 생성된다 (run process per port). 만약 하나의 Port를 오픈하는 두개의 Container를 생성한다면 docker-proxy는 두개가 생성된다. 또한, 한개의 container 에 두개의 port 에 대해 외부로 노출하도록 설정한다면, 마찬가지로 docker-proxy 프로세스는 두개가 생성된다.
하지만, 실제로는 docker host로 요청이 들어온 패킷이 container 로 전달되는 것은 docker-proxy 와 무관하게 docker host의 iptables 에 의해 동작된다.
즉, docker-proxy 프로세스를 kill 해도 외부에서 들어오는 요청이 container로 전달되는데 문제가 없다는 의미이다. (응?!)
이부분에 대해 자세히 알아보기 전에 먼저 iptables 를 이용한 port 포워딩 설정을 살펴보자.
iptables 를 이용한 DNAT
먼저, docker host의 iptables 를 확인해 보자.
root@~~# iptables -t nat -L -n
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
MASQUERADE tcp -- 172.17.0.2 172.17.0.2 tcp dpt:80
Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80
iptables 내역을 보면,
먼저 Docker host에 들어온 패킷이 PREROUTING chain 을 통해 DOCKER Chain 으로 전달하고,
Docker Chain에서는 DNAT로 8080 포트로 들어온 요청을 172.17.0.2 IP를 가진 container 의 80 포트로 포워딩 되는 것을 알 수 있다.
반대로, Container 에서 외부로 나갈때는 POSTROUTING 을 거쳐 MASQUERADE 되어 외부로 나간다.
여기에서 볼 수 있는 container 관련 iptables rule 관리는 docker daemon 이 자동으로 제어하게 된다.
또한, NAT 를 위한 ip_forward 설정도 docker daemon 에서 제어하게 된다.
root@~~# sysctl -a | grep ip_forward
net.ipv4.ip_forward = 1
docker-proxy 를 사용하는 이유
docker host에서 container 로 패킷을 전달하는 방법이 iptables를 이용한 DNAT 방식이라면, 위에서 언급된 docker-proxy 프로세스는 과연 무슨 역할을 할까.
docker-proxy가 존재하는 가장 큰 이유는, docker host가 iptables 의 NAT를 사용하지 못하는 상황에 대한 처리이다. 만약 정책상의 이유로 docker host의 iptables 나 ip_forward 를 enable 하지 못하는 경우에는 docker-proxy 프로세스가 패킷을 포워딩하는 역할을 대신하게 된다.
아래는 docker host 의 iptables 를 disable 하는 설정이다.
/etc/default/docker 파일을 열고(없다면 생성) 아래와 같이 DOCKER_OPTS 를 추가한 후 docker daemon 을 재시작하면 된다.
root@~~# cat /etc/default/docker
DOCKER_OPTS="--iptables=false"
iptables=false 로 설정 한후 container를 실행하면 아래와 같이 DNAT rule에 아무것도 없다. 이런 경우 docker-proxy 프로세스가 직접 패킷을 받아 container로 포워딩을 처리한다.
root@~~# iptables -t nat -L -n
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0
또한, 그것과 별개로 docker host의 internal network (172.17.0.0/16) 에서는 현재의 NAT 설정을 통해서 노출된 port(여기서는 8080) 로 포워딩이 불가능하다. 이런 경우에도 docker-proxy가 이를 대신 처리하게 된다. 만약, docker-proxy 프로세스를 kill 한후 docker host에서 localhost 로 8080 포트를 요청하면 container의 80 포트로 포워딩이 안된다. 이는 같은 host 내에 배포된 container 사이에서도 마찬가지다.
root@~~# telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
root@~~# ps -aef | grep docker-proxy
root 21833 21725 0 05:49 docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8080 -container-ip 172.17.0.2 -container-port 80
root@~~# kill -9 21833
root@~~# telnet localhost 8080
Trying 127.0.0.1...
telnet: Unable to connect to remote host: Connection refused
이런 특수한 예외 상황 때문에 docker 측에서도 docker-proxy 프로세스를 통해 이를 처리하는 것으로 보인다.
그럼에도, 많은 사람들이 이 docker-proxy 프로세스의 필요성에 의구심을 갖는다.
게다가, docker-proxy 프로세스가 의외로 메모리도 차지하는 편이다.
root@~~# ps -eo user,pid,rss,time,cmd --sort -rss | head -n 5
USER PID RSS TIME CMD
root 9576 39340 00:00:54 /usr/bin/docker daemon --raw-logs
root 16296 11768 00:00:00 docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8080 -container-ip 172.17.0.2 -container-port 80
root 9585 8724 00:00:03 docker-containerd -l /var/run/docker/libcontainerd/docker-containerd.sock --runtime docker-runc --start-timeout 2m
daemon 16345 4708 00:00:01 httpd -DFOREGROUND
docker-proxy 프로세스 하나당 약 11 MB(11768 kb) 가까이 사용한다. 만약 docker host 한대에 여러 포트를 사용하는 container를 추가로 올릴때마다 그만큼의 메모리 사용량이 증가할 것이다.
그래서 커뮤니티 같은데 보면 docker-proxy 프로세스를 disable 할수 없는지 문의 하는 것을 볼 수 있다. ( https://github.com/docker/docker/issues/8356 )
그래서 이 요청 사항을 받아들였는지, docker 1.7 버전 부터는 docker-proxy 대신 locahost 의 hairpin NAT 방식이 가능하도록 지원한다.
docker-proxy disable 설정
아래는 docker-proxy를 disable 하는 설정이다.
/etc/default/docker 파일에 DOCKER_OPTS 을 추가한 후 docker daemon 을 재시작하면 된다.
root@~~# cat /etc/default/docker
DOCKER_OPTS="--userland-proxy=false"
그러면, container 에서 port를 오픈하더라도 docker-proxy 프로세스는 생성되지 않는다. 하지만 내부 network 에서도 외부 오픈된 port로 통신이 가능하도록 NAT에 추가적으로 설정 된다. 아래와 같이 iptables 의 POSTROUTING 룰에 MASQUERADE 로 local network (172.17.X.X) 에 대한 룰이 추가되는 것을 볼 수 있다.
iptables -t nat -L -n
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match src-type LOCAL
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
MASQUERADE tcp -- 172.17.0.2 172.17.0.2 tcp dpt:80
Chain DOCKER (2 references)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80
[ 참고 ]
- http://windsock.io/tag/docker-proxy/
- http://serverfault.com/questions/633604/what-is-the-point-of-the-docker-proxy-process-why-is-a-userspace-tcp-proxy-need
- https://github.com/docker/docker/issues/8356
- https://github.com/docker/docker/pull/6810
- https://github.com/docker/docker/pull/9078
'IT > Docker' 카테고리의 다른 글
Dockerfile Entrypoint 와 CMD의 올바른 사용 방법 (5) | 2019.06.12 |
---|---|
Docker Network 구조(4) - container link 구조 (9) | 2016.07.11 |
Docker Network 구조(2) - container network 방식 4가지 (1) | 2016.05.20 |
Docker container IP 확인 (4) | 2016.05.18 |
docker container에 접속하기 (2) | 2015.07.21 |