일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- aws codecommit
- docker
- python3
- network
- Python
- codecommit
- 토스카드
- 하나머니
- clone
- DocumentDB
- MongoDB
- Container
- 실사용
- namespace
- 도커
- 리뷰
- Linux
- 커피머니불리기
- docker network
- VPC
- 후기
- mininet
- 앱테크
- AWS
- 재테크
- S3
- built-in
- MongoEngine
- 포인트앱
- 리워드앱
- Today
- 12
- Total
- 1,423,176
ㅍㅍㅋㄷ
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의 올바른 사용 방법 (3) | 2019.06.12 |
---|---|
Docker Network 구조(4) - container link 구조 (9) | 2016.07.11 |
Docker Network 구조(3) - container 외부 통신 구조 (15) | 2016.07.06 |
Docker Network 구조(2) - container network 방식 4가지 (1) | 2016.05.20 |
Docker container IP 확인 (3) | 2016.05.18 |
docker container에 접속하기 (1) | 2015.07.21 |
- Tag
- Container, dnat, docker, docker 네트워크, docker-proxy, expose, hairpin nat, iptables, nat, network, userland-proxy
-
박지박 2017.07.12 14:52 글 잘봤습니다. 질문이 있는데요
혹시 말씀대로 설정을 하고 docker로 server를 가동시킨후에, 다른 컴퓨터에서 172.17.0.2:8080으로 접속을 하면, 접속이 되야 되는 것인가요??? -
윤희경 2017.09.12 10:23 좋은 글 감사합니다
-
허선행 2017.10.19 09:54 안녕하세요 글 잘 봤습니다.
Docker을 공부중에 있는데 질문이 있습니다.
각 컨테이너에 사설 서비스ip 를 넣고 외부에서 접속을 하고 싶은데 컨테이너에 ip를 넣을 수 있는지요? -
anomie7 2018.11.27 17:35 신고 좋은 글 정말 감사합니다
-
mulder 2019.01.21 19:06 잘 정리하셨네요. 잘 봤습니다. ^^
-
jinkwon.kim 2019.02.27 11:34 신고 좋은 글 감사 합니다.
-
몽상가 2019.05.22 14:02 docker-proxy를 kill하고 iptables nat table의 DOCKER 체인의 첫번째 룰인 RETURN을 제거하면 통신될겁니다. 제 생각엔 nf_conntrack이 성능에 영향을 끼치기도 하고 최대 한도를 커널에서 관리하기에 dropping을 최대한 막고자 userland에 proxy를 둔것으로도 보입니다.
-
클쏭 2019.05.22 15:08 신고 의견감사합니다. :-)
-
잘정리맨 2019.10.16 13:17 잘 정리 해두셨네요 고맙읍니다
-
bmy4415 2020.02.10 14:43 좋은 글 감사합니다. 글을 읽는 도중에 질문이 있습니다. 글 중간에 DOCKER chain을 보면
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
첫번째 rule이 모든 src와 dest에 대해서 RETURN인데, 그러면 DNAT rule은 적용이 안되는 것 아닌가요? -
클쏭 2020.02.10 18:04 신고 맞는 말씀입니다. 실제 동작하려면 RETURN 제거 후 통신될꺼예요. 아마도 굉장히 예전에 쓴 글이라 정확히 기억나지 않는데 docker-proxy 프로세스에 의해 동작된 것으로 보이고 DNAT rule 적용되려면 RETURN 삭제해야 할 것으로 보이네요.
-
김피터 2020.07.08 14:32 -v 옵션 주고 출력해보시면 아마 in-interface가 docker0 로 되어있을겁니다.
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
0 0 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
외부에서 들어오는 패킷은 eth0 등의 인터페이스에서 들어올테니 RETURN 되지 않는거죠 -
oks 2020.03.20 14:56 또한, NAT 를 위한 ip_forward 설정도 docker daemon 에서 제어하게 된다
-> 여기 제어한다는 부분이 궁금한데요 ㅎㅎ
아래 값을 docker 가 시작될 때 1로 변경해서 뜬다는 의미일까요 ??
net.ipv4.ip_forward = 1 -
클쏭 2020.03.20 17:13 신고 넵. 해당 설정에 대한 제어권을 docker 가 가져간다는 의미입니다.
-
☆> 2020.07.29 16:40 신고 상세한 설명 감사드립니다.
현재 생성한 컨테이너에서 외부로 통신을 하려고 하는데 되지않아서
작성해주신 글을 여러번 정독했는데도 방법을 모르겠습니다.
gitlab container를 생성한 상태이고, 외부에서 gitlab container 접속은 되는데
gitlab container상에서 smtp gmail설정한 부분이 통신이 안되는 문제가 발생하여 해당방법을 찾고 있습니다.
gitlab container -> smtp.gmail.com:587 통신이 안되는 문제인데.
혹시 조언을 얻을 수 있을까요?