
사내 프록시로만 밖에 나갈 수 있는 기업망에 구축형 OwlAI를 설치하면, 클라우드 LLM으로 가는 호출은 HTTPS_PROXY를 따라 프록시로 나가야 합니다. 같은 백엔드가 사내망에 둔 vLLM 추론 서버와 음성인식 서버도 부르는데, 이 요청은 프록시를 거치지 않고 바로 붙어야 합니다. 설치 담당자는 curl에 하던 대로 NO_PROXY에 10.0.0.0/8을 적고, 호스트에서 curl로 추론 서버에 붙어 보면 잘 됩니다. 그런데 OwlAI의 채팅은 프록시의 403이나 타임아웃으로 실패합니다. 백엔드의 HTTP 클라이언트(httpx)는 NO_PROXY의 CIDR 항목을 읽지 않아서, 사내 추론 서버로 가는 요청도 프록시로 보내기 때문입니다. 프록시가 사내 주소까지 중계해 주는 망이면 더 조용합니다. 호출은 성공하지만 질문과 조회 결과가 든 프롬프트가 매번 프록시를 지나고, 추론 서버를 평문 HTTP로 두었다면 프록시가 그 내용을 그대로 봅니다.
이 글은 구축형 OwlAI가 이 어긋남을 다루는 방법을 씁니다. 프록시를 적는 자리 세 곳이 각각 어디까지 닿는지 정리하고, 내부 사이드카 호출과 헬스 체크는 설정과 상관없이 프록시를 타지 않게 코드가 끕니다. 프록시를 따르는 호출은 httpx의 NO_PROXY 해석을 그대로 쓰고, 그 해석과 설치 담당자의 뜻이 어긋나면 설치 전 점검과 기동 로그가 알립니다.
프록시를 적는 자리는 세 곳이고 닿는 범위가 다르다
도커 호스트에서 프록시를 적는 자리는 셋입니다. 같은 프록시 주소를 적어도 닿는 곳이 서로 다릅니다.
| 자리 | 닿는 곳 |
|---|---|
도커 데몬(systemd drop-in의 Environment=HTTPS_PROXY=… 또는 daemon.json의 "proxies") |
이미지 pull만 |
도커 클라이언트(~/.docker/config.json의 "proxies") |
이미지 빌드와 모든 컨테이너의 환경변수(대문자·소문자 둘 다) |
설치 디렉터리 .env의 HTTPS_PROXY·HTTP_PROXY·NO_PROXY |
백엔드·워커 같은 앱 컨테이너 |
데몬에 적은 프록시는 컨테이너 안으로 들어가지 않습니다. 그래서 이미지는 받아지는데 LLM 호출은 밖에 나가지 못하는 설치가 생깁니다. 반대로 도커 클라이언트에 적은 프록시는 DB·프런트엔드·사이드카까지 모든 컨테이너에 들어갑니다. 이 경우 noProxy에 서비스 이름이 하나라도 빠지면, 그 서비스로 가는 컨테이너 사이의 호출이 프록시로 나갑니다.
내부 호출과 헬스 체크는 설정과 상관없이 프록시를 타지 않는다
백엔드가 문서 렌더·스크립트 실행 사이드카를 부를 때는 HTTP 클라이언트의 환경 프록시를 끕니다(httpx trust_env=False). 같은 compose 안의 서비스에 프록시가 필요한 배치는 없습니다. 그런데 이 동작을 NO_PROXY에 맡기면 서비스 이름 하나가 빠지는 순간 내부 호출이 사내 프록시로 나가 403이 됩니다. 이 옵션은 SSL_CERT_FILE도 함께 무시하게 만들기 때문에 TLS 검증 설정을 따로 명시해 넘깁니다(「인증서 오류는 검증을 끄지 않고, 빠진 CA와 틀린 이름을 고쳐서 푼다」).
헬스 체크도 같습니다. 도커 클라이언트가 프록시 변수를 넣은 컨테이너에서 헬스 체크가 127.0.0.1을 부르면, 그 요청이 프록시로 나가 타임아웃이 나고 컨테이너는 unhealthy가 됩니다. 배포 스크립트는 서비스를 하나씩 바꾸며 헬스 체크를 기다리므로, 여기서 배포가 멈춥니다. 그래서 파이썬 헬스 체크는 프록시 처리기를 비운 요청기로 부르고, 프런트엔드 이미지의 busybox wget은 -Y off로 프록시를 끕니다. busybox wget은 HTTP_PROXY는 읽지만 NO_PROXY는 읽지 않아서(busybox 버그 13861), NO_PROXY에 127.0.0.1을 넣어도 소용이 없습니다.
LLM·커넥터·웹 도구 호출은 환경 프록시를 따른다
LLM SDK, 커넥터(MCP 서버), 웹 검색·웹 페이지 도구는 환경 프록시를 그대로 따릅니다. 사내 프록시로만 밖에 나갈 수 있는 설치가 있기 때문입니다. 사내망에 둔 vLLM·음성인식·리랭커도 이 쪽에 듭니다. 추론 서버의 주소는 설치마다 config.yaml에 적는 값이라, 코드가 그 주소를 내부라고 단정할 수 없습니다. 그래서 이 서버들은 NO_PROXY가 덮어야 합니다.
커넥터와 URL로 파일을 주고받는 도구가 쓰는 클라이언트는 연결을 열 때 주소를 직접 풀어 확인합니다(「도구 결과에 나온 주소도 모델이 먼저 썼다면 열지 않는다」). httpx는 전송(transport)을 직접 넘겨 만든 클라이언트에서 환경 프록시를 읽지 않으므로, 이 클라이언트는 환경 변수에서 httpx와 같은 규칙으로 프록시 표를 다시 만들어 붙입니다. 그래서 한 설치 안에서 어떤 클라이언트가 보내든 같은 URL은 같은 길로 갑니다.
httpx는 NO_PROXY의 CIDR 항목을 읽지 않는다
NO_PROXY는 표준이 없는 관례라, 항목을 읽는 방법이 도구마다 다릅니다. 같은 NO_PROXY=10.0.0.0/8에서 10.20.0.31로 가는 요청이 어디로 가는지 도구별로 보면 다음과 같습니다.
| 도구 | CIDR 항목 | 10.20.0.31로 가는 요청 |
|---|---|---|
| curl 7.86.0 이상 | 읽는다(공식 문서 CURLOPT_NOPROXY) |
바로 붙는다 |
| requests 2.34 | 읽는다 | 바로 붙는다 |
| httpx 0.28 | 읽지 않는다 | 프록시로 간다 |
| 파이썬 표준 라이브러리 urllib | 읽지 않는다 | 프록시로 간다 |
httpx는 NO_PROXY 항목마다 URL 패턴 하나를 만듭니다. 10.0.0.0/8은 호스트가 10.0.0.0인 패턴이 되어, 그 주소 하나 말고는 아무것도 맞지 않습니다. 이 동작은 httpx 0.28.1 소스(get_environment_proxies)와 실제 실행으로 확인했습니다(2026-11-03). httpx 공식 문서도 NO_PROXY를 「호스트 이름·URL 목록」으로만 설명합니다. 설치 담당자가 호스트에서 curl로 시험하면 통과하므로, 이 차이는 OwlAI가 첫 요청을 보낸 뒤에야 드러납니다.
httpx가 읽는 항목은 네 가지입니다.
- 이름:
vllm.corp.example.com은 그 이름과 하위 도메인을 뺀다. - 점으로 시작하는 접미사:
.corp.example.com은 하위 도메인만 뺀다. - 정확한 IP:
10.20.0.31은 그 주소 하나를 뺀다. *: 모든 요청을 프록시에서 뺀다.
그래서 NO_PROXY에는 설치 전 점검이 확인하는 컨테이너 사이의 서비스 이름과, 추론 서버의 이름이나 IP, 사내 도메인 접미사를 나열합니다.
NO_PROXY=localhost,127.0.0.1,postgres,render,studio,backend,versitygw,10.20.0.31,.corp.example.com
어긋남은 설치 전 점검과 기동 로그가 알린다
설치 전 점검(「설치 전 점검은 호스트를 읽기만 하고, 고칠 명령은 사람에게 넘긴다」)은 세 자리를 모두 읽어 어느 자리에 프록시가 있는지 한 줄로 남기고, 프록시가 있으면 컨테이너가 실제로 받을 NO_PROXY를 httpx와 같은 규칙으로 판정합니다.
| 항목 | 판정 |
|---|---|
proxy.no_proxy_client·proxy.no_proxy_env |
localhost·127.0.0.1·서비스 이름 가운데 빠진 것이 있으면 WARN |
proxy.cidr_client·proxy.cidr_env |
CIDR 항목이 있으면 WARN |
proxy.lan_endpoints |
config.yaml의 엔드포인트 가운데 사내망 주소로 보이는데 NO_PROXY가 덮지 않는 것이 있으면 WARN |
사내망 주소로 보는 기준은 사설·루프백·링크로컬 IP, 점이 없는 이름, .internal·.local·.corp처럼 사설망에서 쓰는 접미사입니다. config.example.yaml의 자리표시 주소를 고치지 않은 채 프록시를 켜면 추론 엔드포인트마다 이 경고가 나옵니다.
설치 뒤에 프록시가 바뀌는 경우는 백엔드와 워커가 기동할 때 다시 봅니다. NO_PROXY에 CIDR 항목이 있으면 net.proxy.no_proxy_cidr_ignored를, 설정의 추론 엔드포인트가 프록시로 나가게 되면 net.proxy.internal_endpoint_proxied를 WARNING으로 남깁니다. 뒤쪽 로그에는 프로바이더·역할·호스트가 실려, 어느 엔드포인트를 NO_PROXY에 더할지 바로 압니다. 엔드포인트 IP가 NO_PROXY의 CIDR 안에 들면 이 경고의 대상으로 셉니다. 설치 담당자가 그 대역을 빼려 했다는 뜻이기 때문입니다.
기동을 멈추는 경우는 하나입니다. httpx가 아예 읽지 못하는 항목이 NO_PROXY에 있으면, 환경을 따르는 모든 HTTP 클라이언트가 만들어지는 단계에서 InvalidURL로 실패합니다. IPv6 CIDR(fd00::/8)이나 대괄호로 감싼 IPv6 주소([::1])가 그렇고, 프록시를 설정하지 않아도 실패합니다. 이 설정이면 LLM·커넥터·웹 도구 호출이 하나도 되지 않으므로, 기동이 net.proxy.no_proxy_unusable에 그 항목과 이유를 적고 멈춥니다.
설계에서 고른 것
내부 사이드카 호출과 헬스 체크는 NO_PROXY에 맡기는 대신 코드에서 프록시를 끄는 쪽을 골랐습니다. NO_PROXY에 맡기면 설치 담당자가 프록시 경로를 한 곳에서 다룰 수 있습니다. 그 대신 서비스 이름 하나를 빠뜨리면 배포가 헬스 체크에서 멈추고, 새 사이드카가 생길 때마다 고객의 .env를 고쳐야 합니다. 대가로 사이드카를 프록시 너머에 두는 배치는 쓸 수 없는데, 같은 호스트의 compose 안에서 그럴 일은 없습니다.
NO_PROXY 해석은 curl처럼 CIDR을 읽도록 고치는 대신 httpx의 동작을 그대로 따랐습니다. 고치면 설치 담당자가 익숙한 CIDR 표기를 쓸 수 있습니다. 그 대신 OwlAI가 직접 만든 클라이언트와 라이브러리가 속에서 만드는 클라이언트가 같은 URL을 다른 길로 보내게 되고, 그 차이는 호출 하나만 이상하게 실패하는 모양으로 나타납니다. 대가는 설치 담당자가 추론 서버를 이름이나 IP로 하나씩 적어야 한다는 점이고, 빠뜨리면 점검과 기동 로그가 그 호스트를 짚어 줍니다.
사내 추론 서버가 프록시로 나가는 경우는 기동을 막는 대신 경고로 남겼습니다. 사내망 주소로 보는 기준은 추정이고, 사내 주소도 프록시를 거쳐야 하는 망이 있습니다. 기동을 막으면 그런 설치는 OwlAI를 띄울 수 없습니다. 확정된 장애인 읽지 못하는 NO_PROXY 항목만 기동을 멈춥니다.
사내 프록시 뒤에서 httpx를 쓰는 서비스를 운영한다면, NO_PROXY의 CIDR 항목부터 찾아 이름·접미사·IP로 바꾸는 것이 첫 확인입니다. 컨테이너 안에서 env | grep -i proxy로 실제로 받은 값을 보고, curl이 아니라 그 서비스가 쓰는 클라이언트로 추론 서버를 불러 봐야 같은 판정이 나옵니다.
어떤 문제를 해결하나요?
설치·운영과 품질의 다른 글

엔지니어링 노트 · 설치·운영과 품질
설치 전 점검은 호스트를 읽기만 하고, 고칠 명령은 사람에게 넘긴다
구축형 OwlAI는 고객이 고른 호스트 한 대에 Docker Compose로 올라갑니다. 배포판과 커널, 도커 판, 보안 모듈, 인증서는 고객마다 다릅니다.

엔지니어링 노트 · 설치·운영과 품질
도커 주소 풀은 설치하는 사람이 정하고, 점검은 사내 대역과 대조한다
구축형 OwlAI를 기업망 호스트에 설치하고 몇 주 뒤, 어제까지 붙던 사내 DB나 추론 서버에 컨테이너에서 닿지 않는 날이 올 수 있습니다. 같은 호스트의 셸에서도 닿지 않습니다.

엔지니어링 노트 · 설치·운영과 품질
배포와 폐쇄망 적재는 태그 대신 다이제스트로 확인한다
구축형 OwlAI는 고객 호스트에서 컨테이너 이미지 아홉 종으로 뜹니다. 시험 환경에서 통과한 이미지와 고객 호스트에서 뜨는 이미지가 같은 바이트여야 시험 결과가 고객 설치에도 통합니다.
