توی این درس یاد میگیری داکر را وقتی فقط از طریق یک proxy (واسطهی شبکه) به اینترنت میرسد راه بیندازی. میفهمی که سه جای جدا برای تنظیم proxy هست (daemon برای pull/push، زمان build، و داخل کانتینرها)، هر کدام را با daemon.json، systemd، --build-arg HTTP_PROXY و فایل config.json تنظیم میکنی، با NO_PROXY آدرسهای داخلی را از proxy بیرون میگذاری و در انتها یک registry شخصی بهعنوان کش میسازی که image ها را یکبار از Docker Hub میگیرد و بعد از شبکهی خودت سرو میکند.
مسئله: کدام بخش داکر از proxy استفاده میکند؟
Section titled “مسئله: کدام بخش داکر از proxy استفاده میکند؟”در یک شبکهی شرکتی یا محدود، ترمینال تو شاید با export HTTPS_PROXY=... کار کند، ولی docker pull باز هم گیر کند. چرا؟ چون docker pull را خودِ daemon انجام میدهد (نه دستور docker در ترمینال تو) و daemon متغیرهای شل تو را نمیبیند. و بعد از pull، دستورهای داخل RUN در Dockerfile و برنامههای داخل کانتینر هم هرکدام جداگانه به proxy نیاز دارند.
تشبیه: سه تلفن، یک مرکز تلفن
Section titled “تشبیه: سه تلفن، یک مرکز تلفن”proxy مثل مرکز تلفن شرکت است که همهی تماسهای خارجی باید از آن رد شوند. ولی سه نوع تلفن داری: تلفن دفتر (daemon که image میآورد)، تلفن کارگاه ساخت (build)، و تلفن کارمندها (کانتینرها). برای هر کدام باید جدا بگویی «از مرکز تلفن رد شو»؛ کار روی یکی، دیگری را درست نمیکند.
مثالهای عملی
Section titled “مثالهای عملی”مثال ۱: یک آزمایشگاه ایزوله و یک proxy آموزشی
Section titled “مثال ۱: یک آزمایشگاه ایزوله و یک proxy آموزشی”برای اینکه به داکر اصلی دست نزنیم، یک daemon جدا (داکر داخل داکر) میسازیم. و یک proxy کوچک که فقط تونل HTTPS (CONNECT) را پشتیبانی میکند و هر درخواست را چاپ میکند:
import asyncio
async def pipe(reader, writer): try: while data := await reader.read(65536): writer.write(data) await writer.drain() except Exception: pass finally: writer.close()
async def handle(client_r, client_w): request = await client_r.readline() method, target, _ = request.decode().split(" ", 2) while (await client_r.readline()) not in (b"\r\n", b""): pass if method != "CONNECT": client_w.write(b"HTTP/1.1 405 Method Not Allowed\r\n\r\n") client_w.close() return host, port = target.rsplit(":", 1) print("CONNECT", target, flush=True) try: up_r, up_w = await asyncio.open_connection(host, int(port)) except OSError: client_w.write(b"HTTP/1.1 502 Bad Gateway\r\n\r\n") client_w.close() return client_w.write(b"HTTP/1.1 200 Connection established\r\n\r\n") await asyncio.gather(pipe(client_r, up_w), pipe(up_r, client_w))
async def main(): server = await asyncio.start_server(handle, "0.0.0.0", 3128) async with server: await server.serve_forever()
asyncio.run(main())حالا daemon آزمایشی را بالا بیاور، یک شبکه با آدرس ثابت در آن بساز (چون آدرس proxy نباید با هر ریستارت عوض شود) و proxy را روی آن اجرا کن:
docker run -d --privileged --name lx-px-dind -e DOCKER_TLS_CERTDIR= docker:dind >/dev/nulluntil docker exec lx-px-dind docker info >/dev/null 2>&1; do sleep 1; donedocker cp px/proxy.py lx-px-dind:/var/tmp/proxy.pydocker exec lx-px-dind docker network create --subnet 10.55.0.0/24 lxnet >/dev/nulldocker exec lx-px-dind docker run -d --restart unless-stopped --name lx-proxy --network lxnet --ip 10.55.0.10 \ -v /var/tmp/proxy.py:/proxy.py python:3.12-alpine python /proxy.py >/dev/null 2>&1sleep 2docker exec lx-px-dind docker ps --format '{{.Names}}: {{.Status}}'docker exec lx-px-dind docker version --format 'daemon آزمایشی: نسخهی {{.Server.Version}}'lx-proxy: Up 2 secondsdaemon آزمایشی: نسخهی 29.8.2مثال ۲: proxy برای daemon با daemon.json
Section titled “مثال ۲: proxy برای daemon با daemon.json”کلید proxies در daemon.json به daemon میگوید برای pull و push از proxy استفاده کند. این تغییر با restart اعمال میشود (reload کافی نیست):
docker exec -i lx-px-dind sh -c 'mkdir -p /etc/docker && cat > /etc/docker/daemon.json' <<'LXEOF'{ "proxies": { "http-proxy": "http://10.55.0.10:3128", "https-proxy": "http://10.55.0.10:3128", "no-proxy": "localhost,127.0.0.0/8" }}LXEOFdocker restart lx-px-dind >/dev/nulluntil docker exec lx-px-dind docker info >/dev/null 2>&1; do sleep 1; done; sleep 3until docker exec lx-px-dind docker ps -q --filter name=lx-proxy --filter status=running | grep -q .; do sleep 1; done; sleep 2docker exec lx-px-dind docker info 2>/dev/null | grep -E '^ (HTTP|HTTPS|No) Proxy' HTTP Proxy: http://10.55.0.10:3128 HTTPS Proxy: http://10.55.0.10:3128 No Proxy: localhost,127.0.0.0/8docker info تنظیم را تأیید میکند. حالا یک image میکشیم و لاگ proxy را نگاه میکنیم:
docker exec lx-px-dind docker pull alpine 2>&1 | tail -1echo "--- لاگ proxy (هر خط یک اتصال تونلشده):"docker exec lx-px-dind docker logs lx-proxy 2>&1 | sort | uniq -c | sed -E 's/^ +/ /'docker.io/library/alpine:latest--- لاگ proxy (هر خط یک اتصال تونلشده): 1 CONNECT auth.docker.io:443 3 CONNECT production.cloudfront.docker.com:443 3 CONNECT registry-1.docker.io:443هر اتصال daemon به registry از proxy گذشت: registry-1.docker.io (manifest و لایهها)، auth.docker.io (توکن) و CDN دانلود لایهها. اگر docker pull در شبکهی خودت کار نمیکند، همین فهرست دامنهها را میتوانی به مدیر proxy بدهی تا اجازه بدهد.
چرا export HTTPS_PROXY در ترمینال docker pull را درست نمیکند؟
تنظیم باید روی daemon (daemon.json یا systemd) باشد.
مثال ۳: proxy خراب چه خطایی میدهد؟
Section titled “مثال ۳: proxy خراب چه خطایی میدهد؟”docker exec -i lx-px-dind sh -c 'cat > /etc/docker/daemon.json' <<'LXEOF'{ "proxies": { "http-proxy": "http://10.55.0.99:3128", "https-proxy": "http://10.55.0.99:3128" } }LXEOFdocker restart lx-px-dind >/dev/nulluntil docker exec lx-px-dind docker info >/dev/null 2>&1; do sleep 1; done; sleep 3until docker exec lx-px-dind docker ps -q --filter name=lx-proxy --filter status=running | grep -q .; do sleep 1; done; sleep 2docker exec lx-px-dind docker pull busybox 2>&1 | tail -1 | cut -c1-260Error response from daemon: failed to resolve reference "docker.io/library/busybox:latest": failed to do request: Head "https://registry-1.docker.io/v2/library/busybox/manifests/latest": proxyconnect tcp: dial tcp 10.55.0.99:3128: connect: no route to hostآدرس proxy اشتباه یا proxy خاموش: خطا شامل proxyconnect tcp است. اگر این عبارت را دیدی، مشکل از خود proxy و آدرس آن است، نه از registry. راهحل: آدرس و پورت را بررسی کن و از بیرون داکر با curl -x http://PROXY:PORT https://registry-1.docker.io/v2/ امتحان کن.
مثال ۴: no-proxy، آدرسهایی که نباید از proxy بروند
Section titled “مثال ۴: no-proxy، آدرسهایی که نباید از proxy بروند”آدرسهای داخلی (registry شرکت، localhost) نباید از proxy بیرونی رد شوند. کلید no-proxy فهرست دامنهها و IPها را میگیرد. اینجا برای نشاندادنِ اثرش دامنههای Docker Hub را در آن میگذاریم تا pull بدون proxy انجام شود:
before=$(docker exec lx-px-dind docker logs lx-proxy 2>&1 | grep -c CONNECT)docker exec -i lx-px-dind sh -c 'cat > /etc/docker/daemon.json' <<'LXEOF'{ "proxies": { "http-proxy": "http://10.55.0.10:3128", "https-proxy": "http://10.55.0.10:3128", "no-proxy": "localhost,127.0.0.0/8,.docker.io,.docker.com" }}LXEOFdocker restart lx-px-dind >/dev/nulluntil docker exec lx-px-dind docker info >/dev/null 2>&1; do sleep 1; done; sleep 3until docker exec lx-px-dind docker ps -q --filter name=lx-proxy --filter status=running | grep -q .; do sleep 1; done; sleep 2docker exec lx-px-dind docker pull busybox 2>&1 | tail -1after=$(docker exec lx-px-dind docker logs lx-proxy 2>&1 | grep -c CONNECT)echo "اتصالهای ثبتشده در proxy: قبل=$before بعد=$after"docker.io/library/busybox:latestاتصالهای ثبتشده در proxy: قبل=7 بعد=7عدد قبل و بعد برابر است؛ pull انجام شد ولی هیچ اتصال تازهای از proxy رد نشد. فرمت no-proxy: فهرست با ویرگول؛ .example.com یعنی همهی زیردامنهها؛ و CIDR مثل 10.0.0.0/8 برای بازهی IP.
مثال ۵: proxy برای daemon با systemd (لینوکس واقعی)
Section titled “مثال ۵: proxy برای daemon با systemd (لینوکس واقعی)”روی سرور لینوکس (Ubuntu، Debian،…) معمولاً daemon را systemd اداره میکند. بهجای daemon.json میتوانی متغیرهای محیطی را مستقیم برای سرویس تنظیم کنی. دستور رسمی sudo systemctl edit docker فایلی در /etc/systemd/system/docker.service.d/ میسازد. روی این مک داکر با systemd اجرا نمیشود، پس مکانیزم را روی یک سرویس نمونه (demo.service) در یک ماشین systemd واقعی (کانتینر privileged) آزمایش میکنیم. روی سرویس docker دقیقاً همان کار را میکنی.
اول سرویس بدون تنظیم:
cat > /etc/systemd/system/demo.service <<'LXEOF'[Unit]Description=Demo that prints its proxy variable
[Service]ExecStart=/bin/sh -c 'echo "HTTP_PROXY=[$HTTP_PROXY]"; sleep 300'LXEOFsystemctl daemon-reloadsystemctl start demosleep 1journalctl -u demo -n 1 --no-pager -o catHTTP_PROXY=[]حالا drop-in را با خود systemctl edit demo میسازیم. این دستور یک ویرایشگر باز میکند؛ برای اینکه در اسکریپت جا بشود، متغیر SYSTEMD_EDITOR را روی یک «ویرایشگر» کوچک میگذارم که بهجای تو متن را مینویسد، و چون systemctl edit ترمینال (tty) میخواهد آن را با script اجرا میکنم (در عمل خودت در ترمینال و nano یا vim مینویسی و اینها لازم نیست):
cat > /usr/local/bin/fake-editor <<'LXEOF'#!/bin/shprintf '[Service]\nEnvironment="HTTP_PROXY=http://proxy.example:3128"\nEnvironment="NO_PROXY=localhost,127.0.0.1"\n' > "$1"LXEOFchmod +x /usr/local/bin/fake-editorscript -qec 'SYSTEMD_EDITOR=/usr/local/bin/fake-editor systemctl edit demo' /dev/null >/dev/nullsystemctl cat demo | grep -vE '^$'systemctl restart demosleep 1systemctl show demo --property=Environmentjournalctl -u demo -n 1 --no-pager -o cat# /etc/systemd/system/demo.service[Unit]Description=Demo that prints its proxy variable[Service]ExecStart=/bin/sh -c 'echo "HTTP_PROXY=[$HTTP_PROXY]"; sleep 300'Environment=HTTP_PROXY=[]systemctl edit فایل override.conf را در پوشهی demo.service.d ساخت و daemon-reload را خودش زد؛ ولی سرویس را restart نمیکند، باید خودت بزنی. بعد از restart، متغیر داخل سرویس بود (systemctl show و لاگ هر دو نشانش میدهند).
برای داکر روی سرور واقعی (نمونه، اجرا نشده روی این ماشین):
sudo systemctl edit docker# در ویرایشگر بنویس و ذخیره کن:# [Service]# Environment="HTTP_PROXY=http://proxy.example:3128"# Environment="HTTPS_PROXY=http://proxy.example:3128"# Environment="NO_PROXY=localhost,127.0.0.1,.internal"sudo systemctl restart dockersystemctl show --property=Environment dockerdocker info | grep -i proxyکدام را انتخاب کنم؟ daemon.json (کلید proxies) در داکرهای جدید (۲۳ به بعد) سادهتر و مستقل از systemd است؛ drop-in برای سرورهای systemd یا وقتی از قبل متغیرها را مدیریت میکنی. هر دو را همزمان نگذار، مقدار یکی با دیگری قاطی میشود.
مثال ۶: proxy در زمان build با --build-arg
Section titled “مثال ۶: proxy در زمان build با --build-arg”دستورهای RUN (مثل apt-get، pip) داخل build اجرا میشوند و proxy را از آرگومانهای build میگیرند. داکر چند ARG پیشفرض برای proxy دارد (HTTP_PROXY، HTTPS_PROXY، NO_PROXY، FTP_PROXY، ALL_PROXY) که لازم نیست در Dockerfile تعریفشان کنی:
FROM alpineARG MY_VAR=helloRUN env | grep -iE '^(http_proxy|https_proxy|no_proxy|MY_VAR)=' | sortdocker build --no-cache --progress=plain -t lx-bp \ --build-arg HTTP_PROXY=http://proxy.example:3128 \ --build-arg NO_PROXY=localhost bp 2>&1 | grep -E '^#[0-9]+ [0-9.]+ [A-Za-z_]+=' | sed -E 's/^#[0-9]+ [0-9.]+ //'HTTP_PROXY=http://proxy.example:3128MY_VAR=helloNO_PROXY=localhosthttp_proxy=http://proxy.example:3128no_proxy=localhostداخل RUN متغیرها دیده میشوند (هم حرف بزرگ هم کوچک). نکتهی مهم این است که این مقدارها داخل image نمیمانند:
echo "--- تاریخچهی build (فقط ARG غیر-proxy دیده میشود):"docker history --no-trunc lx-bp --format '{{.CreatedBy}}' | grep -E 'RUN' | cut -c1-70echo "--- متغیرهای proxy در کانتینر اجراشده از image:"docker run --rm lx-bp env | grep -ci proxydocker rmi lx-bp >/dev/null--- تاریخچهی build (فقط ARG غیر-proxy دیده میشود):RUN |1 MY_VAR=hello /bin/sh -c env | grep -iE '^(http_proxy|https_prox--- متغیرهای proxy در کانتینر اجراشده از image:0آرگومانهای proxy پیشفرض داکر در docker history ثبت نمیشوند و در کانتینر هم نیستند؛ ولی ARG MY_VAR معمولی ثبت میشود (برای همین رمز را هرگز با ARG نده؛ درس امنیت). در Compose معادلش build: {args: {HTTP_PROXY: ...}} است.
مثال ۷: proxy داخل کانتینرها با config.json کلاینت
Section titled “مثال ۷: proxy داخل کانتینرها با config.json کلاینت”برنامههای داخل کانتینر (مثلاً اپ تو که به API بیرونی میزند) متغیرها را از کلاینت داکر میگیرند: کلید proxies در ~/.docker/config.json همان متغیرها را خودکار به هر کانتینر و build جدید تزریق میکند. (برای اینکه فایل خودت را عوض نکنم، در پوشهی دیگری با DOCKER_CONFIG امتحان میکنم.)
docker exec lx-px-dind sh -c 'mkdir -p /var/tmp/dc && cat > /var/tmp/dc/config.json' <<'LXEOF'{"proxies": {"default": {"httpProxy": "http://proxy.example:3128", "noProxy": "localhost,.internal"}}}LXEOFdocker exec lx-px-dind sh -c 'DOCKER_CONFIG=/var/tmp/dc docker run --rm alpine env' | grep -i proxy | sortبدون هیچ -e، چهار متغیر (بزرگ و کوچک) داخل کانتینر ساخته شد. اگر در همین کانتینر docker run -e HTTP_PROXY=... بدهی، -e برنده است.
مثال ۸: registry جایگزین: یک کش شخصی (pull-through cache)
Section titled “مثال ۸: registry جایگزین: یک کش شخصی (pull-through cache)”بهجای اینکه هر ماشین تیم جداگانه از Docker Hub بکشد، یک registry کش روی سرور تیم میگذاری: اولین بار image را از Docker Hub میگیرد و ذخیره میکند، دفعات بعد از شبکهی خودت سرو میکند. ماشینها آن را بهعنوان میرور (registry-mirrors) میشناسند، همان کلیدی که در درس میرور دیدی، اما این بار خودت صاحب میرور هستی (و نیازی به اعتماد به شخص ثالث نیست).
اول proxy را از daemon برمیداریم (با آرایهی خالی و restart؛ فقط reload پاک نمیکند) و کش را اجرا میکنیم:
docker exec -i lx-px-dind sh -c 'cat > /etc/docker/daemon.json' <<'LXEOF'{ "proxies": {}, "registry-mirrors": [], "insecure-registries": [] }LXEOFdocker restart lx-px-dind >/dev/nulluntil docker exec lx-px-dind docker info >/dev/null 2>&1; do sleep 1; done; sleep 3until docker exec lx-px-dind docker ps -q --filter name=lx-proxy --filter status=running | grep -q .; do sleep 1; done; sleep 2docker exec lx-px-dind docker run -d --restart unless-stopped --name lx-cache --network lxnet --ip 10.55.0.20 \ -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io registry:2 >/dev/null 2>&1sleep 2docker exec lx-px-dind docker ps --format '{{.Names}}: {{.Status}}'docker exec lx-px-dind docker info 2>/dev/null | grep -cE '^ (HTTP|HTTPS) Proxy' | sed 's/^/خطوط proxy در docker info: /'lx-cache: Up 2 secondslx-proxy: Up 19 secondsخطوط proxy در docker info: 0متغیر REGISTRY_PROXY_REMOTEURL registry را به حالت pull-through cache میبرد. حالا daemon را به آن وصل میکنیم. چون کش ما با HTTP کار میکند آن را insecure اعلام میکنیم (برای شبکهی کاملاً داخلی؛ برای کار واقعی TLS بگذار):
docker exec -i lx-px-dind sh -c 'cat > /etc/docker/daemon.json' <<'LXEOF'{ "registry-mirrors": ["http://10.55.0.20:5000"], "insecure-registries": ["10.55.0.20:5000"] }LXEOFdocker exec lx-px-dind sh -c 'kill -HUP $(pidof dockerd)'; sleep 3docker exec lx-px-dind docker info 2>/dev/null | grep -A1 'Registry Mirrors' | tr -s ' 'docker exec lx-px-dind docker rmi alpine busybox >/dev/null 2>&1echo "کش قبل از pull: $(docker exec lx-px-dind wget -qO- http://10.55.0.20:5000/v2/_catalog)"docker exec lx-px-dind docker pull alpine 2>&1 | tail -1echo "کش بعد از pull: $(docker exec lx-px-dind wget -qO- http://10.55.0.20:5000/v2/_catalog)" Registry Mirrors: http://10.55.0.20:5000/کش قبل از pull: {"repositories":[]}docker.io/library/alpine:latestکش بعد از pull: {"repositories":["library/alpine"]}کش خالی بود و بعد از pull شامل library/alpine شد: image از Docker Hub آمد، از داخل کش گذشت و ذخیره شد. بار دوم از کش میآید:
n1=$(docker exec lx-px-dind docker logs lx-cache 2>&1 | grep -c 'GET /v2/library/alpine/blobs')docker exec lx-px-dind docker rmi alpine >/dev/nulldocker exec lx-px-dind docker pull alpine 2>&1 | tail -1n2=$(docker exec lx-px-dind docker logs lx-cache 2>&1 | grep -c 'GET /v2/library/alpine/blobs')[ "$n2" -gt "$n1" ] && echo "درخواستهای لایه به کش رسید (از $n1 به $n2)"docker.io/library/alpine:latestدرخواستهای لایه به کش رسید (از 4 به 8)شمارندهی درخواستهای لایه به کش بالا رفت: دومین pull از کش سرو شد. روی سرور تیم، همین registry را روی یک ماشین بگذار و روی همهی ماشینها registry-mirrors را به آدرس آن بزن؛ میزان دانلود از اینترنت به یک بار برای هر image کاهش مییابد.
registryهای جایگزین: فهرست مرجع
Section titled “registryهای جایگزین: فهرست مرجع”| گزینه | چیست | نکته |
|---|---|---|
| registry شخصی با pull-through cache | registry:2 با REGISTRY_PROXY_REMOTEURL |
مثال ۸؛ کنترل کامل، فقط Docker Hub را کش میکند |
| registry خصوصی برای image های خودت | همان registry:2 یا Harbor (UI، RBAC و اسکن) |
برای تیمها؛ TLS و احراز هویت بگذار |
GHCR (ghcr.io)، Quay (quay.io) |
registryهای عمومی دیگر | بعضی image ها آنجا هم منتشر میشوند؛ آدرس را باید بدانی و در نام بنویسی (ghcr.io/org/app) |
| registry ابری/داخلی شرکت | سرویس مدیریتشده | تنظیم docker login لازم است |
نکته: registry-mirrors فقط برای Docker Hub است. image ای که آدرس registry دیگر دارد، از میرور رد نمیشود؛ برای آنها باید proxy یا خود آدرس registry جایگزین را بنویسی.
پشت پرده
Section titled “پشت پرده”وقتی daemon به یک registry HTTPS وصل میشود و proxy تنظیم است، درخواست اول CONNECT registry-1.docker.io:443 به proxy میرود («یک تونل TCP به این آدرس باز کن»)؛ بعد TLS بین daemon و خود registry از داخل همان تونل انجام میشود، پس proxy نمیتواند محتوا را بخواند یا دستکاری کند (فقط میبیند به کدام دامنه وصل شدی). این در لاگ مثال ۲ دیدی.
سه نکتهی ظریف:
- تغییر
proxiesدرdaemon.jsonنیاز به restart دارد؛kill -HUPآن را اعمال یا پاک نمیکند (مثلregistry-mirrorsکه با HUP اعمال میشد ولی حذف کلید در آن اثر نداشت). برای برداشتن، آرایه یا شیء خالی بنویس و restart کن. - متغیرهای proxy و
no-proxyاز تطبیق دامنه/IP استفاده میکنند و برنامهها در تفسیر CIDR و wildcard کمی با هم فرق دارند؛ برای داکر.example.comو CIDR کار میکند. - IP یک کانتینر proxy یا registry را با اسم کانتینر در
daemon.jsonنمینویسند چون daemon خودش از DNS کانتینرها استفاده نمیکند؛ از یک IP ثابت (شبکهی با subnet مشخص، مثل مثال ۱) یا یک نام دامنهی واقعی استفاده کن. در آزمایش خودم IPهای تصادفی بعد از restart جابهجا شدند.
جدولهای مرجع
Section titled “جدولهای مرجع”| میخواهم proxy برای… | تنظیم | اعمال |
|---|---|---|
| pull و push (daemon) | daemon.json ← proxies یا drop-in در systemd |
restart داکر |
RUN در Dockerfile |
docker build --build-arg HTTP_PROXY=... |
هر build |
| برنامهی داخل کانتینر | ~/.docker/config.json ← proxies.default یا -e HTTP_PROXY |
کانتینر جدید |
curl/apt روی میزبان |
export HTTPS_PROXY=... |
همان شل |
کلید proxies |
معنی |
|---|---|
http-proxy |
proxy برای HTTP |
https-proxy |
proxy برای HTTPS (registryها) |
no-proxy |
فهرست دامنه/IP که از proxy رد نمیشوند |
اشتباهات رایج
Section titled “اشتباهات رایج”۱) تنظیم فقط export HTTPS_PROXY و انتظار کار کردن docker pull
Section titled “۱) تنظیم فقط export HTTPS_PROXY و انتظار کار کردن docker pull”مثال ۲: daemon متغیر شل تو را نمیبیند. راهحل: daemon.json یا systemd.
۲) فراموش کردن restart
Section titled “۲) فراموش کردن restart”اعمال proxies نیاز به restart دارد (مثال ۲). راهحل: sudo systemctl restart docker و بعد docker info | grep -i proxy.
۳) آدرس proxy اشتباه (proxyconnect tcp)
Section titled “۳) آدرس proxy اشتباه (proxyconnect tcp)”مثال ۳. راهحل: آدرس و پورت را با curl -x از بیرون داکر تست کن.
۴) رمز proxy داخل Dockerfile
Section titled “۴) رمز proxy داخل Dockerfile”mkdir -p m4 && printf 'FROM alpine\nARG TOKEN=s3cret-value\nRUN echo build\n' > m4/Dockerfiledocker build -q -t lx-m4 m4 >/dev/nulldocker history --no-trunc lx-m4 --format '{{.CreatedBy}}' | grep -o 'TOKEN=[^ ]*' | head -1docker rmi lx-m4 >/dev/nullTOKEN=s3cret-valueهر ARG معمولی با مقدارش در تاریخچهی image ثبت میشود و هرکس image را بگیرد میبیند. راهحل: رمز را در Dockerfile ننویس؛ از --secret (درس امنیت) یا proxy بدون رمز استفاده کن.
۵) فراموش کردن no-proxy برای آدرسهای داخلی
Section titled “۵) فراموش کردن no-proxy برای آدرسهای داخلی”اگر registry داخلی شرکت (registry.company.internal) را در no-proxy نگذاری، pull از آن به proxy بیرونی میرود و خطا میدهد. راهحل: دامنه یا CIDR داخلی را در no-proxy بنویس.
با docker info نشان بده یک daemon پشت proxy چه خطوطی دارد. (روی داکر خودت فقط ببین؛ چیزی عوض نکن.)
دیدن جواب
docker info 2>/dev/null | grep -E '^ (HTTP|HTTPS|No) Proxy' || echo "این daemon proxy ندارد" HTTP Proxy: http.docker.internal:3128 HTTPS Proxy: http.docker.internal:3128 No Proxy: hubproxy.docker.internalاگر چیزی چاپ نشود یعنی proxy تنظیم نشده است. خروجی بالا نشان میدهد Docker Desktop خودش یک proxy داخلی دارد (http.docker.internal)، چون ترافیک daemon داخل ماشین مجازی از طریق آن عبور میکند؛ روی لینوکس معمولی بدون تنظیم، این خطها نیستند و بعد از تنظیم proxy سه خط HTTP Proxy، HTTPS Proxy و No Proxy میآید.
تمرین اصلی: daemon را برای استفاده از proxy تنظیم کن. در یک daemon آزمایشی جدید، daemon.json را با کلید proxies بنویس، restart کن، با docker info تأیید کن و با لاگ proxy ثابت کن که pull از آن رد شد. (proxy آموزشی px/proxy.py از مثال ۱ را به کار ببر.)
دیدن جواب
docker run -d --privileged --name lx-px-e2 -e DOCKER_TLS_CERTDIR= docker:dind >/dev/nulluntil docker exec lx-px-e2 docker info >/dev/null 2>&1; do sleep 1; donedocker cp px/proxy.py lx-px-e2:/var/tmp/proxy.pydocker exec lx-px-e2 docker network create --subnet 10.56.0.0/24 n >/dev/nulldocker exec lx-px-e2 docker run -d --restart unless-stopped --network n --ip 10.56.0.10 --name px -v /var/tmp/proxy.py:/proxy.py python:3.12-alpine python /proxy.py >/dev/null 2>&1sleep 2docker exec lx-px-e2 sh -c 'mkdir -p /etc/docker; echo "{\"proxies\":{\"http-proxy\":\"http://10.56.0.10:3128\",\"https-proxy\":\"http://10.56.0.10:3128\"}}" > /etc/docker/daemon.json'docker restart lx-px-e2 >/dev/nulluntil docker exec lx-px-e2 docker info >/dev/null 2>&1; do sleep 1; done; sleep 3for i in $(seq 30); do docker exec lx-px-e2 docker ps -q --filter name=px --filter status=running | grep -q . && break; sleep 1; done; sleep 2docker exec lx-px-e2 docker info 2>/dev/null | grep -E '^ (HTTP|HTTPS) Proxy'docker exec lx-px-e2 docker pull busybox 2>&1 | tail -1echo "اتصالهای تونلشده: $(docker exec lx-px-e2 docker logs px 2>&1 | grep -c CONNECT)"docker rm -f lx-px-e2 >/dev/null HTTP Proxy: http://10.56.0.10:3128 HTTPS Proxy: http://10.56.0.10:3128docker.io/library/busybox:latestاتصالهای تونلشده: 7ثابت کن یک ARG معمولی در docker history میماند ولی HTTP_PROXY که با --build-arg میدهی نمیماند. یک Dockerfile با ARG NOTE=visible بساز، با --build-arg HTTP_PROXY=http://proxy.example:3128 بساز و تاریخچه را بررسی کن.
دیدن جواب
mkdir -p e3 && printf 'FROM alpine\nARG NOTE=visible\nRUN echo hi\n' > e3/Dockerfiledocker build -q -t lx-e3 --build-arg HTTP_PROXY=http://proxy.example:3128 e3 >/dev/nullH=$(docker history --no-trunc lx-e3 --format '{{.CreatedBy}}')echo "NOTE در تاریخچه: $(echo "$H" | grep -c 'NOTE=visible')"echo "HTTP_PROXY در تاریخچه: $(echo "$H" | grep -c 'proxy.example')"docker rmi lx-e3 >/dev/nullNOTE در تاریخچه: 2HTTP_PROXY در تاریخچه: 0آزمونک
Section titled “آزمونک”daemon داکر برای pull از کجا proxy را میخواند؟
pull را daemon انجام میدهد و محیط شل تو را نمیبیند.
تغییر proxies در daemon.json چگونه اعمال میشود؟
بر خلاف registry-mirrors که با reload میآید.
برای proxy در زمان build؟
ARGهای proxy پیشفرضاند و در تاریخچهی image ثبت نمیشوند.
no-proxy برای چیست؟
آدرسهای داخلی را از proxy بیرونی جدا میکند.
pull-through cache چه میکند؟
با registry:2 و REGISTRY_PROXY_REMOTEURL.
چرا رمز را با ARG در Dockerfile نگذاریم؟
از --secret استفاده کن.
جمعبندی
Section titled “جمعبندی”- سه جای جدا برای proxy: daemon (pull/push)، build (
RUN) و کانتینرها؛ هر کدام تنظیم خودش را دارد. - daemon:
daemon.jsonکلیدproxiesیا drop-in در systemd (systemctl edit docker)، بعد restart؛ تأیید باdocker info. - build:
--build-arg HTTP_PROXY=...(ثبت نمیشود)؛ کانتینر:config.jsonکلاینت یا-e. no-proxyبرای آدرسهای داخلی؛ خطایproxyconnect tcpیعنی خود proxy مشکل دارد.- registry شخصی با
REGISTRY_PROXY_REMOTEURLیک میرور کش است که خودت کنترلش میکنی. - رمز proxy یا هر راز دیگر را در Dockerfile یا image نگذار.
| دستور | کاری که میکند |
|---|---|
docker info | grep -iE "proxy|mirror" | تنظیم proxy و میرور فعال |
{"proxies":{"http-proxy":"…","https-proxy":"…","no-proxy":"…"}} | proxy daemon در daemon.json (با restart) |
sudo systemctl edit docker | drop-in برای Environment (سرور systemd)؛ بعدش restart |
systemctl show --property=Environment docker | دیدن متغیرهای سرویس |
docker build --build-arg HTTP_PROXY=URL . | proxy در زمان build |
DOCKER_CONFIG=DIR docker run … | کلاینت با config.json دیگر |
-e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io registry:2 | registry بهعنوان pull-through cache |
curl -x http://PROXY:PORT https://registry-1.docker.io/v2/ | تست proxy از بیرون داکر |