Windows - WSL 2 환경의 Docker Desktop 네트워크
지난 글에서 다뤘듯이,
"WSL --debug-shell"로 살펴보는 WSL 2 VM의 리눅스 환경
; https://www.sysnet.pe.kr/2/0/13650
WSL 2 VM에서 네임스페이스를 분리시켜 실행한 "docker-desktop" 컨테이너가 있고, 다시 그것에서 분리해 "LinuxKit" 컨테이너를 실행하고, 다시 분리해 docker 관련 컨테이너들이 실행되는 것입니다.
따라서 실질적인 docker 데몬이 시작한 환경은 "LinuxKit"이고, 그 환경을 조사하고 싶다면, 예전
DockerDesktopVM에 대해서 썼던 바로 그 방식과 유사하게 진입할 수 있습니다.
// Getting a Shell in the Docker for Windows Moby VM
// https://www.bretfisher.com/getting-a-shell-in-the-docker-for-windows-vm/
c:\temp> docker run -it --rm --privileged --pid=host justincormack/nsenter1
~ # ps a | grep docker
59 root 0:00 /usr/bin/runc run --preserve-fds=3 01-docker
71 root 0:00 /usr/libexec/docker/docker-init /usr/bin/entrypoint.sh
143 root 1:05 /usr/local/bin/dockerd --config-file /run/config/docker/daemon.json --containerd /run/containerd/containerd.sock --pidfile /run/desktop/docker.pid --swarm-default-advertise-addr=192.168.65.3 --host-gateway-ip 192.168.65.254
...[생략]...
16183 root 0:00 grep docker
LinuxKit 컨테이너의 네트워크 환경을 살펴볼까요? ^^
우선 그 전에 VM의 네트워크 환경이 있을 것입니다. 해당 VM은 Hyper-V의 "vEthernet (WSL (Hyper-V firewall))" 가상 어댑터로 "vSwitch (WSL (Hyper-V firewall))" 가상 스위치에 연결됩니다. 이후, 그 위에서 생성되는 리눅스 배포본들은 그 환경을 그대로 이어받아 사용하는데요, 그래서
VM의 ifconfig 결과와
docker-desktop 배포본의 ifconfig 결과는 같습니다.
하지만, LinuxKit 컨테이너 레벨에서는 달라지는데요, 우선, docker는 자신이 사용할 내부 가상 네트워크를 임의로 생성하는 것이 가능합니다. 실졔로 Docker Desktop을 설치하면 기본적으로 3개의 네트워크를 가지는데요,
C:\Windows\System32> docker network ls
NETWORK ID NAME DRIVER SCOPE
39115851b938 bridge bridge local
29b1107e441f host host local
1650344b2376 none null local
각각의 네트워크 구성을 "docker network inspect" 명령어로 정리하면 이렇게 나옵니다.
[bridge]
"Driver": "bridge",
"EnableIPv6": false,
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
[host]
"Driver": "host"
(null)
[none]
"Driver": "null"
(null)
그리고 bridge 네트워크로 연결해 실행한 컨테이너에서 ifconfig 명령어를 수행하면 다음과 같이 나옵니다.
// docker에서 생성한 임의의 컨테이너에서 ifconfig 출력
# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.2 netmask 255.255.0.0 broadcast 172.17.255.255
...[생략]...
이와 함께 LinuxKit 컨테이너에서 ifconfig 명령어를 수행하면 이런 결과가 나옵니다.
// LinuxKit 컨테이너에서 실행한 ifconfig 출력 정리
[cni0] inet addr:10.1.0.1 Bcast:10.1.255.255 Mask:255.255.0.0
[docker0] inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0
[eth0] inet addr:192.168.65.3 Bcast:192.168.65.255 Mask:255.255.255.0
[lo] inet addr:127.0.0.1 Mask:255.0.0.0
[services1] inet addr:192.168.65.6 Bcast:0.0.0.0 Mask:255.255.255.255
하나씩 볼까요? ^^ 우선 "[cni0]"는 k8s를 함께 설치했을 때 나오는 것입니다.
그다음 "[docker0] inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0" 항목은 낯이 익은 IP 대역인데요, 바로 docker가 기본 구성한 가상 네트워크인 bridge입니다. 다시 말해, Windows Command Shell에서 "
docker network create" 명령어를 수행하면 (윈도우가 아닌) LinuxKit 컨테이너 환경 내에서 가상 네트워크를 생성하는 것입니다. (참고로 bridge 네트워크를 추가하면 기본적으로는 172.18.0.1/16, 172.19.0.1/16 등으로 순차 생성됩니다.)
마지막으로 [eth0] 196.168.65.0/24 네트워크가 재미있는데요, 이 구성은 Docker Desktop for Windows의 "Resources" / "Network" 메뉴로 바꿀 수 있습니다. 그러니까, 그것의 기본값이 Docker subnet == "192.168.65.0/24"인 것이고, 해당 네트워크에서 "LinuxKit 컨테이너"의 가상 IP는 "192.168.65.3"이 됩니다.
또한, 위의 환경에서 host.docker.internal, gateway.docker.internal은 ping을 해보면 각각 다음과 같이 나옵니다.
host.docker.internal 192.168.65.254
gateway.docker.internal 192.168.65.1
문서에 따르면, 192.168.65.0/24 네트워크는 (순수) 리눅스 운영체제에서 docker를 호스팅하는 경우에는 만들지 않는다고 합니다. 그런 의미에서, 아마도 192.168.65.0/24는 컨테이너에서 (VM을 건너뛰고) 호스트 네트워크와 연결하기 위한 보조 수단으로써 제공하는 듯합니다. 사실, Docker Desktop은 개발자 친화적으로 만들어진 응용 프로그램이고, 개발자가 주로 머무르는 환경이 (docker를 띄우기 위해 필요한 VM이 아닌) 호스트 운영체제가 되기 때문에 그것과 좀 더 부드럽게 연동하기 위한 네트워크로써 필요했을 것입니다. 실제로 192.168.65.0/24 네트워크는 거의 사용 용도가 없는데요, 제가 찾은 유일한 용도는 docker container 내부에서 "host.docker.internal (192.168.65.254)" 주소가 윈도우 호스트로 연결된다는 점입니다.
// 윈도우 호스트 측에 IIS 서비스를 설치했다면, docker run으로 띄운 컨테이너에서 IIS 80 테스트 가능
C:\temp> docker run -it --rm ubuntil
root@8091c139af60:/# curl host.docker.internal
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
...[생략]...
<body>
<div id="container">
<a href="http://go.microsoft.com/fwlink/?linkid=66138&clcid=0x409"><img src="iisstart.png" alt="IIS" width="960" height="600" /></a>
</div>
</body>
// 위의 "ubuntil" 이미지는 아래의 dockerfile 구성으로 "docker build -t ubuntil ." 명령을 실행해 생성한 이미지라고 가정.
// FROM ubuntu:latest
// RUN apt-get -y update && apt-get -y install net-tools
재미있는 점은, 윈도우의 VM으로 인해 필요하게 된 네트워크이고, host.docker.internal이 윈도우 호스트 측에 매핑된 것인데 그 과정이 필요 없는 리눅스 환경의 docker에서도 해당 DNS를 쓰고 싶다는 요청이 역으로 발생한 듯합니다. 그래서
리눅스의 경우에는 "--add-host=host.docker.internal:host-gateway" 옵션을 적용해 컨테이너를 실행했을 때 host.docker.internal에 대한 이름 풀이가 호스트 측으로 가능해집니다.
혹시 위의 설명 외에 192.168.65.0/24 네트워크에 대한 구체적인 용도를 아시는 분은 덧글 부탁드립니다. ^^
참고로 지난 글에서,
Visual Studio로 개발 시 기본 등록하는 dev tag 이미지로 Docker Desktop k8s에서 실행하는 방법
; https://www.sysnet.pe.kr/2/0/13645
hostPath를 "/run/desktop/mnt/host/c/temp/WebApplication1" 같은 식으로 지정했었는데, 실제로 이 경로는 "LinuxKit 컨테이너"에서 확인할 수 있습니다.
// LinuxKit 컨테이너 내부에서 실행
// docker run -it --rm --privileged --pid=host justincormack/nsenter1
# ls -l /run/desktop/mnt/host
total 0
drwxrwxrwx 1 root root 4096 Jun 11 23:02 c
drwxrwxrwx 1 root root 4096 Jun 9 01:52 d
drwxrwxrwt 5 root root 120 Jun 12 23:26 wsl
drwxrwxrwt 7 root root 300 Jun 13 15:03 wslg
그런데, 사실 /mnt/host 경로도 존재하긴 합니다. 예상과는 달리 서로
link로 연결되지도 않았는데요,
# ls -l /mnt/host
total 0
drwxrwxrwx 1 root root 4096 Jun 11 23:02 c
drwxrwxrwx 1 root root 4096 Jun 9 01:52 d
drwxrwxrwt 5 root root 120 Jun 12 23:26 wsl
drwxrwxrwt 7 root root 300 Jun 13 15:03 wslg
반면 권한까지도 완전히 똑같은데, 왜? k8s의 hostPath에는 "/mnt/host"로는 연결이 안 되고 반드시 "/run/desktop"으로만 정상적으로 동작하는 걸까요? (혹시 아시는 분은 덧글 부탁드립니다. ^^)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]