들어가며
이번 포스팅은 CloudNet@ 커뮤니티에서 주최하는 KANS 스터디 1주 차 주제인 "컨테이너 격리 & 네트워크 및 보안"에 대해서 정리한 내용입니다.
(스터디 내용이 많아 "도커 컨테이너 격리"와 "컨테이너 네트워크 & IPTables"로 포스팅을 나누어 작성합니다.)
이전 스터디 링크 - https://devlos.tistory.com/91
도커 없이 네트워크 네임스페이스 환경에서 통신 구성
1.1 RED ↔ BLUE 네트워크 네임스페이스 간 통신
호스트와 격리된 네트워크 네임스페이스를 생성하고, 이를 페어로 구성합니다.
가상 이더넷 인터페이스를 만들고, 피어를 구성합니다. ip 명령어로 확인해 보면, peer를 통해서 DOWN 상태의 veth 인터페이스 두 개가 생성됩니다. 생성된 두 가지는 짝으로 페어 된 것으로 연결된 인터페이스가 확인됩니다.
# veth (가상 이더넷 디바이스) 생성, man ip-link
ip link add veth0 type veth peer name veth1
# veth 생성 확인(상태 DOWN), ifconfig 에는 peer 정보 확인 안됨
# very pair 정보 확인 : ({iface}@if{pair#N})
ip -c link
ip -c addr # 축약 ip -c a
ifconfig -a
다음으로 네트워크 네임스페이스를 생성하고, veth들을 namespace로 집어넣습니다.
# 네트워크 네임스페이스 생성 , man ip-netns
ip netns add RED
ip netns add BLUE
# 네트워크 네임스페이스 확인
ip netns list
# veth0 을 RED 네트워크 네임스페이스로 옮김
ip link set veth0 netns RED
ip netns list
## 호스트의 ip a 목록에서 보이지 않음, veth1의 peer 정보가 변경됨
ip -c link
## RED 네임스페이스에서 ip a 확인됨(상태 DOWN), peer 정보 확인, link-netns RED, man ip-netns
ip netns exec RED ip -c a
# veth1 을 BLUE 네트워크 네임스페이스로 옮김
ip link set veth1 netns BLUE
ip -c link
ip netns exec BLUE ip -c a
다음으로, DOWN 된 veth들을 UP 상태로 활성화시킨 후, 각각 veth에 IP를 할당시킵니다.
# 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 BLUE ip addr add 11.11.11.3/24 dev veth1
ip netns exec RED ip -c a
ip netns exec BLUE ip -c a
마지막으로 두개의 veth를 이용하여 통신이 되는지 확인해 봅니다.
# 터미널1 (RED 11.11.11.2)
## nsenter : 네임스페이스에 attach 하여 지정한 프로그램을 실행
tree /var/run/netns
nsenter --net=/var/run/netns/RED
ip -c a
## neighbour/arp tables management , man ip-neighbour
ip -c neigh
## 라우팅 정보, iptables 정보
ip -c route
iptables -t filter -S
iptables -t nat -S
# 터미널2 (호스트)
lsns -t net # nsenter 실행 후 TYPE(net) CMD(-bash) 생성 확인
ip -c a
ip -c neigh
ip -c route
iptables -t filter -S
iptables -t nat -S
# 터미널3 (BLUE 11.11.11.3)
nsenter --net=/var/run/netns/BLUE
ip -c a
ip -c neigh
ip -c route
iptables -t filter -S
iptables -t nat -S
# ping 통신 확인
# 터미널3 (BLUE 11.11.11.3)
tcpdump -i veth1
ip -c neigh
exit
# 터미널1 (RED 11.11.11.2)
ping 11.11.11.3 -c 1
ip -c neigh
exit
# 삭제
ip netns delete RED
ip netns delete BLUE
1.2 RED ← Bridge(br0) → BLUE 간 통신
브릿지 네트워크는 호스트와 연결되어 외부와 통신이 가능하게 합니다. 즉 스위치나 허브 역할을 하게 됩니다. 컨테이너가 많아지면, 브릿지를 연결할 경우 간편하게 통신할 수 있습니다.
# 네트워크 네임스페이스 및 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
# 확인
ip netns list
ip -c link
ip netns exec RED ip -c a
ip netns exec BLUE ip -c a
# 브리지 정보 확인
brctl show
# br0 브리지 생성
ip link add br0 type bridge
# br0 브리지 정보 확인
brctl show br0
brctl showmacs br0
brctl showstp br0
# reth1 beth1 을 br0 연결
ip link set reth1 master br0
ip link set beth1 master br0
brctl show br0
brctl showmacs br0
ip -br -c link
다음으로 각 veth에 IP를 할당하고, 활성화 상태로 만듭니다.
# reth0 beth0 에 IP 설정 및 활성화, 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
ip -br -c addr
마지막으로 통신 테스트를 수행해 봅니다.
# 터미널1 (RED 11.11.11.2)
nsenter --net=/var/run/netns/RED
ip -c a;echo; ip -c route;echo; ip -c neigh
## 현재 네트워크 네임스페이스 정보 확인
ip netns identify $$
RED
# 터미널2 (호스트)
brctl showmacs br0
bridge fdb show
bridge fdb show dev br0
iptables -t filter -S
iptables -t filter -L -n -v
# 터미널3 (BLUE 11.11.11.3)
nsenter --net=/var/run/netns/BLUE
ip -c a;echo; ip -c route;echo; ip -c neigh
## 현재 네트워크 네임스페이스 정보 확인
ip netns identify $$
BLUE
==============================================================
# 터미널2 (호스트)
# ping 통신 전 사전 설정
## iptables 정보 확인
iptables -t filter -S | grep '\-P'
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
iptables -nvL -t filter
## Ubuntu 호스트에서 패킷 라우팅 설정 확인 : 커널의 IP Forwarding (routing) 기능 확인 - 0(off), 1(on)
## echo 1 > /proc/sys/net/ipv4/ip_forward
cat /proc/sys/net/ipv4/ip_forward
1
==============================================================
# ping 통신 테스트
# 터미널1 (RED 11.11.11.2) >> ping 왜 실패했을까요?
ping -c 1 11.11.11.3
# 터미널2 (호스트)
tcpdump -l -i br0
watch -d 'iptables -v --numeric --table filter --list FORWARD'
watch -d 'iptables -v --numeric --table filter --list FORWARD;echo;iptables -v --numeric --table filter --list DOCKER-USER;echo;iptables -v --numeric --table filter --list DOCKER-ISOLATION-STAGE-1'
# 터미널3 (BLUE 11.11.11.3)
tcpdump -l -i beth0
통신이 안됩니다..
통신이 실패한 원인에 대해 살펴보겠습니다.
쿠버네티스에서도 통신이 실패한 경우 IP테이블을 무조건 확인해봐야 합니다. 커널의 Netfilter라는 hook을 통해 네트워크 필터링에 걸리게 되면 통신이 되지 않습니다.
목적지가 호스트 애플리케이션인 경우 다음과 같은 과정을 통합니다.
하지만 컨테이너의 경우 veth를 통해 통신을 하고, 통신 요청이 들어왔을 때 컨테이너가 FORWARD 테이블을 보고 Postrouting을 수행해야 하는데, 이 과정에서 문제가 생긴 것입니다.
결과적으로 FORWRAD 체인에서 차단이 된 것을 풀어줘야 통신이 가능합니다.
# 터미널2 (호스트)
# iptables 설정 정보 확인
iptables -t filter -S
iptables -t nat -S | grep '\-P'
# iptables 설정 추가 -t(table), -I(insert chain), -j(jump to - ACCEPT 허용)
iptables -t filter -I DOCKER-USER -j ACCEPT
iptables -nvL -t filter
iptables -t filter -S
iptables -t nat -S | grep '\-P'
watch -d 'iptables -v --numeric --table filter --list FORWARD;echo;iptables -v --numeric --table filter --list DOCKER-USER;echo;iptables -v --numeric --table filter --list DOCKER-ISOLATION-STAGE-1'
tcpdump -l -i br0
==============================================================
# ping 통신 테스트
# 터미널1 (RED 11.11.11.2)
ping -c 1 11.11.11.3
ip -c neigh
# 터미널2 (호스트)
watch -d 'iptables -v --numeric --table filter --list FORWARD;echo;iptables -v --numeric --table filter --list DOCKER-USER;echo;iptables -v --numeric --table filter --list DOCKER-ISOLATION-STAGE-1'
tcpdump -l -i br0
## 정보 확인
ip -c neigh
# 터미널3 (BLUE 11.11.11.3)
tcpdump -l -i beth0
ip -c neigh
이제 통신이 되는군요..
1.3 RED/BLUE → 호스트 & 외부(인터넷) 통신
호스트에서 컨테이너로 통신을 연결할 경우 br0에 IP가 없으면 통신이 안됩니다.
# 터미널1 (RED 11.11.11.2)
nsenter --net=/var/run/netns/RED
tcpdump -i any
# 터미널3 (호스트)
exit
tcpdump -i br0 -n
# 터미널2 (호스트) >> 호스트에서 RED 로 통신이 안되는 이유가 무엇일까요?
ping -c 1 11.11.11.2
ip -c route
ip -c addr
통신이 가능하려면 br0에도 IP를 할당해 주어야 합니다!!
# 터미널1 (RED 11.11.11.2)
nsenter --net=/var/run/netns/RED
tcpdump -i any
# 터미널3 (호스트)
exit
tcpdump -i br0 -n
# 터미널2 (호스트) >> 호스트에서 RED 로 통신이 안되는 이유가 무엇일까요?
ping -c 1 11.11.11.2
ip -c route
ip -c addr
==============================================================
# 터미널2 (호스트) >> br0 에 IP 추가(라우팅 정보)
ip addr add 11.11.11.1/24 dev br0
ping -c 1 11.11.11.2
ping -c 1 11.11.11.3
# 터미널1 (RED 11.11.11.2) >> 192.168.50.10 와 통신이 안되는 이유는 무엇일까요?
ping -c 1 11.11.11.1
ping -c 1 192.168.50.10
ip -c route
ip -c addr
tcpdump -i any icmp -n
# 터미널1 (RED 11.11.11.2)
ip route add default via 11.11.11.1
ping -c 1 192.168.50.10
ip -c route
ping -c 1 8.8.8.8 >> 외부 대역(인터넷)과 통신이 안되는 이유가 무엇일까요?
# 터미널2 (호스트)
iptables -S -t nat
iptables -nvL -t nat
## POSTROUTING : 라우팅 Outbound or 포워딩 트래픽에 의해 트리거되는 netfilter hook
## POSTROUTING 에서는 SNAT(Source NAT) 설정
iptables -t nat -A POSTROUTING -s 11.11.11.0/24 -j MASQUERADE
watch -d 'iptables -v --numeric --table nat --list POSTROUTING'
iptables -nvL -t nat
conntrack -L --src-nat
# 터미널1 (RED 11.11.11.2)
ping -c 1 8.8.8.8
exit
# 터미널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
iptables 명령어를 사용하여 NAT(Network Address Translation) 테이블의 POSTROUTING 체인에 MASQUERADE 규칙을 추가하면 이 규칙은 출발지 IP가 11.11.11.0/24 네트워크 대역에 속하는 패킷의 출발지 IP를 현재 시스템의 eth0 IP로 변환되어 통신이 가능해집니다.
(ㅎㅎ.. 저도 실습하며 nseter를 하지 않고, host namespace에 라우팅을 잘못 설정하여 접속이 몇 번 끊어졌네요.. 접속이 끊어지면 AWS 인스턴스를 다시 실행시키면 됩니다!)
네트워크 네임스페이스만으로도 직접 수동으로 설정할 경우 이렇게 복잡하네요.. 그래서 도커는 컨테이너 실행(run) 시 자동으로 해당 컨테이너의 네임스페이스(네트워크, 마운트, PID 등)를 생성(삭제 등)하여 호스트와 격리해 준다고 합니다.
도커 네트워크 모델
도커 네트워크 모델은 다음과 같이 Bridged, Host Mode, MacVLAN Bridge Mode L2, IPVLAN L3 Mode가 있습니다.
출처 - http://www.abusedbits.com/2016/09/docker-host-networking-modes.html
Bridge mode에서의 네트워크 연결 상태를 다음과 같이 확인할 수 있습니다.
docker run -it --name=PINK --rm busybox
ip a
ip neigh
# 터미널3 (ORANGE) : ORANGE 이름의 busybox 컨테이너 생성
docker run -it --name=ORANGE --rm busybox
ip a
ip neigh
# 터미널2 (호스트)
## 컨테이너 생성 확인
docker ps
## veth 에 각각 서로 연결되는 veth peer 가 추가됬음을 확인, docker UP 확인
ip -br -c link
## 브릿지 정보 확인
brctl show docker0
Host 모드는 호스트의 네트워크 환경을 그대로 사용할 수 있습니다. 애플리케이션을 별도로 포트포워딩 하지 않고 바로 사용할 수 있지만, 동일한 포트 사용 시 충돌이 발생하게 됩니다.
# 컨테이너 실행
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
STATUS -> Exited (1) 3 seconds ago my_nginx_2
...
# 삭제
docker container stop my_nginx
컨테이너를 외부에 노출할 때 port forwarding을 이용하여 외부에 노출합니다!
내외부 접속 확인 및 Network 정보를 확인해 보았습니다.
마치며
이번 포스팅에서는 도커 네트워크에 대해 실습해 보았습니다. 컨테이너 격리와 네트워크 기본 지식들을 통해 보안 취약점 및 네트워크 관련 자료들을 더 이해할 수 있게 된 것 같습니다. 부지런히 공부를 했지만 역시 어렵네요.. ㅠㅠ 심화 과정이라 확실히 개념이 많이 깊어진 것 같습니다.
하지만 기존 도커 및 쿠버네티스를 운영하며 트러블 슈팅 관련하여 chatgpt에만 의존하던 저를 한 단계 개선할 수 있는 좋은 기회인 것 같습니다!
완주를 희망하며.. 이번 포스팅을 마치도록 하겠습니다!
감사합니다 :)
'클라우드 컴퓨팅 & NoSQL > [KANS] 쿠버네티스 네트워크 심화 스터디' 카테고리의 다른 글
[3주차(2/2) - k8s Calico CNI mode & 운영] KANS 스터디 (24.09.08) (0) | 2024.09.18 |
---|---|
[3주차(1/2) - k8s Calico CNI] KANS 스터디 (24.09.08) (1) | 2024.09.18 |
[2주차(2/2) - Flannel CNI] KANS 스터디 (24.09.01) (4) | 2024.09.05 |
[2주차(1/2) - K8S Pause container] KANS 스터디 (24.09.01) (6) | 2024.09.04 |
[1주차(1/2) - 도커 컨테이너 격리] KANS 스터디 (24.08.25) (0) | 2024.09.01 |