컨테이너 네트워크
August 2024 (2581 Words, 15 Minutes)
컨테이너 네트워크 & IPTables
위에 기술했던대로 컨테이너는 네트워크 네임스페이스로 호슽트와 네트워크 격리환경을 구성합니다.
리눅스에서 방화벽 기능을 제공하는 IPTables는 호스트와 컨테이너의 통신에 관여를 합니다,
컨테이너 네트워크 네임스페이스간 통신
두개의 이미지에 보이는 호스트 내의 격리된 두개의 Namespace 간 통신을 테스트합니다.
# 터미널1~3 관리자
sudo su -
whoami
# veth (가상 이더넷 디바이스) 생성, man ip-link
ip link add veth0 type veth peer name veth1
# 네트워크 네임스페이스 생성, 확인
ip netns add RED
ip netns add BLUE
ip netns list
# veth0 을 RED 네트워크 네임스페이스로 옮김
ip link set veth0 netns RED
ip netns list
# veth1 을 BLUE 네트워크 네임스페이스로 옮김
ip link set veth1 netns BLUE
ip netns list
# veth0, veth1 상태 활성화(state UP)
ip netns exec RED ip link set veth0 up
ip netns exec BLUE ip link set veth1 up
ip netns exec RED ip -c a
ip netns exec BLUE ip -c a
# veth0, veth1 에 IP 설정
ip netns exec RED ip addr add 11.11.11.2/24 dev veth0
ip netns exec RED ip -c a
ip netns exec BLUE ip addr add 11.11.11.3/24 dev veth1
ip netns exec BLUE ip -c a
# 터미널1 (RED 11.11.11.2)
nsenter --net=/var/run/netns/RED
# 터미널3 (BLUE 11.11.11.3)
nsenter --net=/var/run/netns/BLUE
# ping 통신 확인 11.11.11.2 -> 11.11.11.3
tcpdump -i veth1
# 터미널1 (RED 11.11.11.2)
ping 11.11.11.3 -c 1
# 삭제
ip netns delete RED
ip netns delete BLUE
컨테이너 네트워크 Bridge 통신
# 네트워크 네임스페이스 및 veth 생성
ip netns add RED
ip link add reth0 type veth peer name reth1
ip link set reth0 netns RED
ip netns add BLUE
ip link add beth0 type veth peer name beth1
ip link set beth0 netns BLUE
# br0 브리지 생성
ip link add br0 type bridge
# reth1 beth1 을 br0 연결
ip link set reth1 master br0
ip link set beth1 master br0
nsenter --net=/var/run/netns/RED
## 현재 네트워크 네임스페이스 정보 확인
ip netns identify $$
RED
nsenter --net=/var/run/netns/BLUE
## 현재 네트워크 네임스페이스 정보 확인
ip netns identify $$
BLUE
# ping 통신 테스트
# 터미널1 (RED 11.11.11.2) >> ping WHY FAIL?
ping -c 1 11.11.11.3
실패원인 분석:
iptables -t filter -S | grep ‘-P’ -P INPUT ACCEPT -P FORWARD DROP -P OUTPUT ACCEPT
에서 확인한 값으로는 Docker가 설치되면서 Default policy로 DROP 설정을 했기때문.
Allow 설정후 Ping 가는것을 확인 할 수 있습니다.
iptables -t filter -I DOCKER-USER -j ACCEPT
Host의 Bridge와 Blue 에서 모두 패킷이 수집됩니다.
컨테이너 → 호스트 & 외부(인터넷) 통신
호스트에 RED/BLUE와 통신 가능한 IP 설정 및 라우팅 추가, iptables NAT 를 통하여 통신 과정을 알아봅니다.
위에서 했던것과 동일한 설정을 다시 합니다.
ip netns add RED
ip link add reth0 netns RED type veth peer name reth1
ip netns add BLUE
ip link add beth0 netns BLUE type veth peer name beth1
ip link add br0 type bridge
ip link set reth1 master br0
ip link set beth1 master br0
ip netns exec RED ip addr add 11.11.11.2/24 dev reth0
ip netns exec BLUE ip addr add 11.11.11.3/24 dev beth0
ip netns exec RED ip link set reth0 up; ip link set reth1 up;
ip netns exec BLUE ip link set beth0 up; ip link set beth1 up;
ip link set br0 up
# iptables -t filter -A FORWARD -s 11.11.11.0/24 -j ACCEPT
iptables -t filter -I DOCKER-USER -j ACCEPT
호스트에서 RED 나 BLUE 로 ping 통신 → RED 에서 외부로 통신
HOST→ RED
터미널2 (호스트) » br0 에 IP 추가
# 터미널1 (RED 11.11.11.2)
nsenter --net=/var/run/netns/RED
# 터미널2 (호스트) >> 호스트에서 RED 로 통신이 안되는 이유가 무엇일까요?
ping -c 1 11.11.11.2 # FAIL
# br0 에 IP 추가(라우팅 정보)
ip addr add 11.11.11.1/24 dev br0
# 터미널3 (호스트)
tcpdump -i br0 -n
RED → HOST
# 터미널1 (RED 11.11.11.2)
ip route add default via 11.11.11.1
# 터미널1 (RED 11.11.11.2)
ping -c 1 192.168.50.10
ping -c 1 8.8.8.8 ## FAIL
## POSTROUTING : 라우팅 Outbound or 포워딩 트래픽에 의해 트리거되는 netfilter hook
## POSTROUTING 에서는 SNAT(Source NAT) 설정
# 호스트 설정
iptables -t nat -A POSTROUTING -s 11.11.11.0/24 -j MASQUERADE
ping -c 1 8.8.8.8
# 터미널3 (BLUE 11.11.11.3)
nsenter --net=/var/run/netns/BLUE # 주의, 꼭 실행 후 아래 진행 할 것
ip route add default via 11.11.11.1
ping -c 1 8.8.8.8
exit
# 삭제
ip netns delete RED
ip netns delete BLUE
ip link delete br0
iptables -t filter -D DOCKER-USER -j ACCEPT
iptables -t nat -D POSTROUTING -s 11.11.11.0/24 -j MASQUERADE
Docker Network
도커 네트워크모드
도커의 기본 네트워크모드로는 Bridge, Host, None
별도의 네트워크 플러그인으로는 macvlan, ipvlan, overlay 모드가 존재합니다.
docker network ls
NETWORK ID NAME DRIVER SCOPE
5d4ec64ed746 bridge bridge local
71da3212e9dc host host local
90a304de3e67 none null local
docker info | grep Network
Network: bridge host ipvlan macvlan null overlay
- Host 는 호스트의 환경을 그대로 사용. 애플리케이션 별도 포트 포워딩 없이 바로 서비스 가능. 컨테이너의 호스트 이름도 호스트 머신의 이름과 동일.
- None 는 말 그래도 아무런 네트워크를 쓰지 않는 것. 외부와의 연결이 단절됨. 컨테이너 내부에 lo 인터페이스만 존재.
http://www.abusedbits.com/2016/09/docker-host-networking-modes.html
Bridge
O’REILLY - Networking and Kubernetes 책
- 도커에서 기본적으로 쓸 수 있는 네트워크 확인, 컨테이너 기본 생성 시 자동으로 docker0 브리지를 사용
- 기본 172.17.0.0/16 대역을 컨테이너가 사용, 대역 변경 설정 가능
- 도커는 IPtables 의 PREROUTING POSTROUTING 의 NAT Chains 를 변경한다
- 컨테이너 → 외부 : POSTROUTING 의 SNAT 처리
- 외부 → 컨테이너(Exposed services ports) : PREROUTING 에서 DNAT 처리
# 도커 네트워크 모드 확인
docker network ls
NETWORK ID NAME DRIVER SCOPE
a22960517f11 bridge bridge local
def31be9614b host host local
f0bf6fd89f14 none null local
# 도커 네트워크(플러그인) 정보 확인
docker info
docker info | grep Network
# 도커 bridge 상세 정보 확인 = docker inspect --type network bridge 동일
# 아래 "Gateway": "172.17.0.1" 정보가 출력되지 않을 경우에는 systemctl restart docker 입력 후 다시 확인
docker network inspect bridge | jq
[
{
"Name": "bridge",
"Id": "7c0902c8e4521b25b6a1233b018886256c8f877fed6a088a1a97e54804135613",
"Created": "2021-10-14T12:22:52.869292205Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
# 브릿지 확인
brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.024229821c4f no
# 네트워크 인터페이스 확인
ip -c addr show docker0
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:29:82:1c:4f brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
# SNAT 정책 확인
iptables -t nat -S
iptables -t nat -S | grep MASQUERADE
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
# 라우팅 확인
ip -c route | grep docker0
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
여기서 생성되는 docker default bridge 대역이 172.17.0.1 이라는것을 주의하자
https://zerone-code.tistory.com/16
간혹가다 이런경우가 발생되기도 한다.
한줄요약 : Nginx Proxy Server를 구축했으나, 172.17 대역의 packet drop이 발생되어 확인결과 proxy server 구축시 설치한 docker 때문에 경로를 못찾던 현상.
하지만 이 주의사항과 동일하게, docker에서는 매번 모든 포트에 대한 맵핑을 수동으로 지어줘야하고, 추상화가 너무 덜 되어있어 불친절하다는 느낌을 강하게 받네요
Host 모드
호스트의 환경을 그대로 사용 가능. 호스트 드라이버의 네트워크는 별도로 생성할 필요 없이 기존의 host 라는 이름의 네트워크를 사용 - 링크 링크2
- 컨테이너의 호스트 이름도 호스트 머신의 이름과 동일. 네트워크도 동일. 애플리케이션 별도 포트 포워딩 없이 바로 서비스 할 수 있음.
# 컨테이너 실행
docker run --rm -d --network host --name my_nginx nginx
# HostConfig.NetworkMode "host" , Config.ExposedPorts "80/tcp" , NetworkSettings.Networks "host" 확인
docker inspect my_nginx
# curl 접속 확인
curl -s localhost | grep -o '<title>.*</title>'
# 호스트에서 tcp 80 listen 확인
ss -tnlp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=3694,fd=7),("nginx",pid=3693,fd=7),("nginx",pid=3649,fd=7))
# 추가 실행 시도
docker run -d network host --name my_nginx_2 nginx
# 확인
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
369c40cf6dfd nginx "/docker-entrypoint.…" 7 seconds ago Exited (1) 3 seconds ago my_nginx_2
...
# 삭제
docker container stop my_nginx
Kubernetes에서도 모니터링 시스템에서의 node-exporter 같은경우들이 HOST 모드를 이용합니다.
hostNetwork: true
hostPID: true
이런 설정들을 이용해 한단계의 Hop을 줄인 Direct 접근을통해 경량화 네트워킹, 컴퓨팅 환경을 마운트하기도 합니다.
kube-prometheus-stack 여러쌍 구성시 host port중복으로 하나씩 수정했던 기억이 나는군요…
컨테이너 외부노출
설정 및 확인 : bridge mode - 링크
# nginx:alpine 웹 컨테이너 3대 실행
# -p 옵션은 컨테이너 포트를 호스트 포트와 바인딩 연결 [호스트의 포트]:[컨테이너의 포트]
## -p 80 만 사용 시 호스트 포트 중 하나(랜덤)과 컨테이너의 80포트와 연결
## -p 여러개 사용하여 여러개 포트 개방
docker run -d --name=web1 -p 10001:80 --rm nginx:alpine
docker run -d --name=web2 -p 10002:80 --rm nginx:alpine
docker run -d --name=web3 -p 10003:80 --rm nginx:alpine
# 컨테이너 확인
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5747f508fe0b nginx:alpine "/docker-entrypoint.…" About a minute ago Up About a minute 0.0.0.0:10003->80/tcp, :::10003->80/tcp web3
222390d43aa5 nginx:alpine "/docker-entrypoint.…" About a minute ago Up About a minute 0.0.0.0:10002->80/tcp, :::10002->80/tcp web2
b10d8e163c77 nginx:alpine "/docker-entrypoint.…" About a minute ago Up About a minute 0.0.0.0:10001->80/tcp, :::10001->80/tcp web1
# iptables 정보 확인
iptables -t nat -S | grep :80
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 10001 -j DNAT --to-destination 172.17.0.2:80
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 10002 -j DNAT --to-destination 172.17.0.3:80
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 10003 -j DNAT --to-destination 172.17.0.4:80
iptables -t filter -S
-A FORWARD -o docker0 -j DOCKER
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT
-A DOCKER -d 172.17.0.3/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT
-A DOCKER -d 172.17.0.4/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT
...
# 외부(자신의 PC)에서 접속(curl 혹은 웹브라우저)
curl -s 192.168.50.10:10001 | grep -o '<title>.*</title>'
curl -s 192.168.50.10:10002 | grep -o '<title>.*</title>'
curl -s 192.168.50.10:10003 | grep -o '<title>.*</title>'
# 연결 정보 확인
conntrack -L
iptables -t nat -L -n -v
# 삭제
docker stop $(docker ps -a -q)