Docker 트래픽이 어느 경로를 지나는지 먼저 확인하기
Docker에서 컨테이너 명령마다 HTTP_PROXY와 HTTPS_PROXY를 지정하는 방식은 간단하지만 모든 트래픽을 처리하지는 못합니다. 애플리케이션이 환경 변수를 읽지 않거나, 이미지 레지스트리와 패키지 관리자가 별도의 네트워크 설정을 사용하면 프록시가 적용되지 않습니다. 투명 프록시는 애플리케이션이 프록시를 인식하지 않아도 호스트의 라우팅과 NAT 단계에서 연결을 가로채 Clash 또는 mihomo의 인바운드로 전달하는 방식입니다.
이 글의 구성은 Linux 호스트에서 Docker 브리지 네트워크를 사용하고, 호스트에서 mihomo가 실행 중인 상황을 기준으로 합니다. Docker Desktop의 macOS·Windows 가상 머신 구조는 패킷 경로가 다르므로 같은 iptables 명령을 그대로 적용하면 안 됩니다. 또한 컨테이너 내부에 Clash를 설치하는 방식과 호스트의 Clash가 Docker 트래픽을 받는 방식은 운영 범위가 다릅니다. 여러 컨테이너의 트래픽을 한 곳에서 관리하려면 호스트에 mihomo를 실행하는 구성이 일반적으로 더 관리하기 쉽습니다.
| 트래픽 | 일반적인 발생 위치 | 확인할 설정 |
|---|---|---|
| 실행 중인 컨테이너의 HTTP·HTTPS | Docker bridge 인터페이스 | tun, REDIRECT 또는 TPROXY 규칙 |
| Docker 이미지 pull | Docker 데몬 | Docker 데몬의 프록시 또는 호스트 출구 라우팅 |
| Dockerfile의 RUN 단계 | 빌드 컨테이너 | 빌드 네트워크, BuildKit 프록시 전달 |
| 호스트 자체의 패키지 설치 | 호스트 네트워크 네임스페이스 | 호스트의 시스템 프록시와 DNS |
mihomo 인바운드와 TUN의 기본 구성
컨테이너 트래픽을 투명하게 처리하려면 mihomo가 패킷을 받을 수 있는 인바운드가 필요합니다. 가장 단순한 선택은 호스트의 TUN 인터페이스를 활성화하고 자동 라우팅을 사용하는 것입니다. 이 방식은 애플리케이션별 환경 변수 설정을 줄일 수 있지만, Linux 커널의 라우팅 권한과 DNS 하이재킹 동작을 함께 확인해야 합니다. 호스트에서 실행하는 FlClash나 다른 mihomo 클라이언트가 TUN을 제공한다면, 클라이언트 UI의 TUN 설정을 우선 사용하고 YAML을 중복 적용하지 마세요.
mixed-port: 7890
allow-lan: false
mode: rule
log-level: info
tun:
enable: true
stack: mixed
auto-route: true
auto-detect-interface: true
strict-route: true
dns-hijack:
- any:53
dns:
enable: true
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
nameserver:
- https://1.1.1.1/dns-query
- https://8.8.8.8/dns-query
위 예시는 기능 관계를 설명하기 위한 기본형입니다. 실제 DNS 서버 주소, 네트워크 정책, 클라이언트가 지원하는 필드는 사용하는 mihomo 버전에 맞춰 검증해야 합니다. fake-ip는 도메인을 가상 주소로 바꾸어 규칙 매칭을 쉽게 하지만, 일부 사설 도메인과 서비스 검색 프로토콜은 예외가 필요합니다. 사내 레지스트리, 호스트의 내부 도메인, Kubernetes API 주소가 있다면 fake-ip-filter 또는 도메인별 DNS 정책을 별도로 구성하세요.
TUN 권한과 라우팅 확인
TUN은 단순히 YAML 한 줄을 추가한다고 활성화되지 않습니다. mihomo 프로세스가 /dev/net/tun에 접근할 수 있어야 하고, 자동 라우팅을 만들 수 있는 권한도 필요합니다. 호스트에서 직접 실행하는 경우에는 클라이언트에 네트워크 확장 또는 관리자 승인이 필요할 수 있습니다. 컨테이너 안에서 mihomo를 실행한다면 일반적으로 NET_ADMIN 권한과 TUN 디바이스 전달이 필요하므로 권한 범위를 신중하게 제한해야 합니다.
sudo test -c /dev/net/tun && echo "TUN device: OK"
ip link show
ip route
ip rule
docker network inspect bridge
Docker의 기본 브리지는 보통 호스트에 docker0 인터페이스를 만들고 172.17.0.0/16과 같은 사설 대역을 사용합니다. 실제 대역은 시스템마다 다르므로 주소를 추측하지 말고 docker network inspect bridge 결과를 확인하세요. strict-route를 켠 상태에서 호스트의 기본 경로, Docker 브리지 경로 또는 사설 네트워크가 끊기면 컨테이너가 인터넷뿐 아니라 내부 서비스에도 접근하지 못할 수 있습니다.
Docker 브리지 트래픽을 투명하게 라우팅하기
실행 중인 컨테이너의 TCP 연결을 투명 프록시로 보내려면 Docker 브리지에서 나가는 트래픽이 TUN 또는 REDIRECT 체인을 통과해야 합니다. mihomo의 TUN 자동 라우팅을 사용하는 경우에는 먼저 TUN이 호스트 일반 프로세스의 트래픽을 정상 처리하는지 확인한 뒤 Docker 네트워크를 추가하세요. 일반 호스트에서도 인터넷이 되지 않는 상태에서 Docker만 조정하면 원인을 분리하기 어렵습니다.
별도의 iptables REDIRECT 또는 TPROXY 규칙을 직접 작성할 때는 Docker가 관리하는 체인을 함부로 덮어쓰지 않아야 합니다. Docker는 컨테이너 NAT와 포트 게시를 위해 DOCKER, DOCKER-USER 등의 체인을 사용합니다. 운영 환경에서는 Docker가 관리하는 규칙을 유지하면서 DOCKER-USER를 점검 지점으로 사용하는 편이 안전합니다. nftables을 사용하는 배포판에서는 iptables 호환 계층과 실제 nft 규칙이 서로 다르게 보일 수 있으므로 현재 백엔드도 확인해야 합니다.
sudo iptables -t nat -S
sudo iptables -S DOCKER-USER
sudo nft list ruleset
docker info | grep -i -E "iptables|rootless"
컨테이너에서 프록시 포트로 직접 연결하는 방식은 투명 프록시와 다릅니다. 예를 들어 컨테이너가 호스트의 7890 포트에 접근하려면 Docker 브리지에서 호스트 주소를 해석할 수 있어야 하며, mihomo가 해당 주소에서 연결을 허용해야 합니다. allow-lan: false인 상태에서는 127.0.0.1:7890이 컨테이너에서 호스트의 포트라는 뜻이 아닙니다. 컨테이너의 127.0.0.1은 컨테이너 자신을 가리킵니다.
docker run --rm curlimages/curl:8.10.1 \
curl -I --max-time 10 https://example.com
docker run --rm alpine:3.20 \
sh -c 'apk add --no-cache curl >/dev/null && curl -I --max-time 10 https://example.com'
위 테스트에서 응답이 없다면 먼저 DNS와 라우팅을 나누어 보세요. 도메인 대신 테스트용 IP에 연결해 보고, 컨테이너 내부의 /etc/resolv.conf, 기본 게이트웨이, 호스트의 docker0 주소를 확인합니다. 도메인만 실패하면 DNS 하이재킹이나 fake-IP 예외가 의심되고, IP 연결도 실패하면 NAT, 방화벽, TUN 경로 또는 정책 규칙을 확인해야 합니다.
docker pull과 패키지 저장소 오류를 분리해서 해결하기
docker pull은 이미 실행 중인 컨테이너의 프로세스가 요청하는 것이 아니라 Docker 데몬이 수행합니다. 따라서 컨테이너 내부에 HTTP_PROXY를 넣어도 이미지 다운로드에는 영향을 주지 않습니다. Linux의 Docker 데몬이 systemd 서비스로 실행 중이라면 서비스 환경에 프록시를 지정하거나, 호스트의 투명 라우팅이 데몬 트래픽까지 처리하도록 구성해야 합니다. Docker Desktop에서는 데몬이 별도의 가상 머신 안에 있으므로 Desktop 설정의 프록시 항목을 우선 확인해야 합니다.
sudo systemctl show --property=Environment docker
sudo journalctl -u docker --since "10 minutes ago"
docker pull alpine:3.20
프록시를 systemd drop-in으로 지정하는 방식은 명시적 프록시를 원하는 환경에서 유용합니다. 다만 프록시 포트가 호스트의 루프백 주소에만 열려 있으면 Docker 데몬이 같은 네트워크 네임스페이스에 있지 않을 때 접근하지 못할 수 있습니다. 설정 후에는 반드시 데몬을 재시작하고, 환경 변수에 토큰이나 비밀번호가 포함되어 있다면 파일 권한과 로그 노출을 점검하세요.
sudo systemctl edit docker
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:7890"
Environment="HTTPS_PROXY=http://127.0.0.1:7890"
Environment="NO_PROXY=127.0.0.1,localhost,.local,172.17.0.0/16"
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl show --property=Environment docker
이미지 레지스트리에서 i/o timeout, context deadline exceeded, TLS handshake timeout이 발생하면 DNS, TLS 연결, 프록시 경로를 각각 확인합니다. 레지스트리 주소를 브라우저로 여는 것만으로는 충분하지 않습니다. Docker 데몬이 사용하는 인증서 저장소, SNI, 인증 토큰 발급 주소가 별도로 동작할 수 있기 때문입니다. 사설 레지스트리라면 해당 도메인과 인증 서버를 NO_PROXY 또는 mihomo의 직접 연결 규칙에 포함할지 결정해야 합니다.
Dockerfile 빌드와 패키지 저장소 점검
docker build 중 apt update, npm install, pip install이 실패하는 경우에는 BuildKit이 사용하는 빌드 네트워크를 별도로 봐야 합니다. Docker 데몬의 이미지 pull이 성공해도 빌드 단계의 패키지 다운로드는 실패할 수 있습니다. 먼저 작은 Dockerfile로 연결만 확인하고, 그 다음 패키지 저장소와 캐시를 추가하는 순서가 좋습니다.
FROM alpine:3.20
RUN apk add --no-cache ca-certificates curl
RUN curl -fsSIL --max-time 15 https://dl-cdn.alpinelinux.org/
패키지 저장소가 직접 연결되어야 하는 내부 환경에서는 mihomo 규칙에 저장소 도메인을 명시하고, 외부 저장소는 이미지 레지스트리와 같은 정책 그룹으로 묶지 않는 편이 좋습니다. 예를 들어 사내 APT 미러는 DIRECT, 외부 npm·PyPI 저장소는 프록시 정책으로 보내는 식으로 분리하면 장애 범위를 줄일 수 있습니다. 인증서 오류가 발생하면 프록시를 바꾸기 전에 컨테이너 이미지에 CA 인증서가 설치되어 있는지 확인하세요.
규칙, DNS, 로그와 성능을 함께 점검하기
투명 프록시의 문제는 단순한 연결 실패보다 “어떤 규칙이 선택되었는지 알 수 없다”는 데서 커집니다. Docker에서 사용하는 주요 도메인을 이미지 레지스트리, 인증 서버, 패키지 저장소, 내부 서비스로 분류하고 각각의 정책을 확인하세요. 규칙은 위에서부터 매칭되므로 넓은 GEOIP,CN,DIRECT 또는 MATCH,Proxy 규칙이 앞에 있으면 기대한 도메인 규칙이 실행되지 않을 수 있습니다.
rules:
- DOMAIN-SUFFIX,registry.example.com,DIRECT
- DOMAIN-SUFFIX,auth.example.com,DIRECT
- DOMAIN-SUFFIX,docker.io,Proxy
- DOMAIN-SUFFIX,ghcr.io,Proxy
- GEOIP,LAN,DIRECT
- MATCH,Proxy
실제 도메인은 서비스와 지역에 따라 달라지므로 위 주소를 그대로 복사하지 말고 Docker 데몬 로그와 DNS 질의 기록에서 확인해야 합니다. 인증 흐름에서 한 번만 사용되는 토큰 발급 도메인을 빠뜨리면 레지스트리 본체는 연결되지만 로그인 단계에서 실패할 수 있습니다. mihomo의 연결 로그에서 호스트명, 선택된策略 그룹,出站 결과를 확인하고, 민감한 인증 헤더와 토큰은 외부에 공유하지 마세요.
- DNS 실패: 컨테이너의
/etc/resolv.conf, mihomo DNS 리스너,dns-hijack와 fake-IP 예외를 확인합니다. - 연결 시간 초과: Docker 브리지의 기본 경로, TUN 자동 라우팅, 방화벽과 프록시 노드의 실제 도달성을 확인합니다.
- TLS 오류: 컨테이너의 CA 인증서, 시스템 시간, 프록시의 TLS 처리와 사설 레지스트리 인증서를 확인합니다.
- 간헐적인 실패: 정책 그룹의 자동 선택 결과, UDP·QUIC 경로, MTU와 특정 노드의 패킷 손실을 비교합니다.
- 속도 저하: 로그 레벨을
info또는warning으로 낮추고, 필요하지 않은 스니핑과 과도한 규칙 공급자 업데이트를 줄입니다.
성능을 측정할 때는 한 번의 이미지 다운로드 속도만 보지 말고 DNS 응답 시간, TLS handshake 시간, 첫 바이트 시간, 전체 전송 시간을 나누어 기록하세요. TUN의 패킷 처리, DNS 재작성, 규칙 제공자 조회와 프록시 노드 자체의 혼잡이 서로 다른 병목을 만들 수 있습니다. 컨테이너 수가 많아지면 로그를 debug로 계속 유지하지 말고, 연결 수와 CPU 사용량을 함께 확인해야 합니다. 특히 수천 개의 규칙과 스니핑을 동시에 활성화하면 작은 VPS에서 메모리와 CPU 사용량이 빠르게 증가할 수 있습니다.