رفتن به محتوا
LoopX

proxy و registryهای جایگزین

توی این درس یاد می‌گیری داکر را وقتی فقط از طریق یک 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)، و تلفن کارمندها (کانتینرها). برای هر کدام باید جدا بگویی «از مرکز تلفن رد شو»؛ کار روی یکی، دیگری را درست نمی‌کند.

هر جا نیاز به proxy دارد را جدا تنظیم می‌کنی: daemon با daemon.json یا systemd، build با build-arg، و کانتینرها با ~/.docker/config.json یا -e.

مثال ۱: یک آزمایشگاه ایزوله و یک proxy آموزشی

Section titled “مثال ۱: یک آزمایشگاه ایزوله و یک proxy آموزشی”

برای اینکه به داکر اصلی دست نزنیم، یک daemon جدا (داکر داخل داکر) می‌سازیم. و یک proxy کوچک که فقط تونل HTTPS (CONNECT) را پشتیبانی می‌کند و هر درخواست را چاپ می‌کند:

px/proxy.py
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 را روی آن اجرا کن:

Terminal window
docker run -d --privileged --name lx-px-dind -e DOCKER_TLS_CERTDIR= docker:dind >/dev/null
until docker exec lx-px-dind docker info >/dev/null 2>&1; do sleep 1; done
docker cp px/proxy.py lx-px-dind:/var/tmp/proxy.py
docker exec lx-px-dind docker network create --subnet 10.55.0.0/24 lxnet >/dev/null
docker 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>&1
sleep 2
docker exec lx-px-dind docker ps --format '{{.Names}}: {{.Status}}'
docker exec lx-px-dind docker version --format 'daemon آزمایشی: نسخه‌ی {{.Server.Version}}'
خروجی
lx-proxy: Up 2 seconds
daemon آزمایشی: نسخه‌ی 29.8.2

مثال ۲: proxy برای daemon با daemon.json

Section titled “مثال ۲: proxy برای daemon با daemon.json”

کلید proxies در daemon.json به daemon می‌گوید برای pull و push از proxy استفاده کند. این تغییر با restart اعمال می‌شود (reload کافی نیست):

Terminal window
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"
}
}
LXEOF
docker restart lx-px-dind >/dev/null
until docker exec lx-px-dind docker info >/dev/null 2>&1; do sleep 1; done; sleep 3
until docker exec lx-px-dind docker ps -q --filter name=lx-proxy --filter status=running | grep -q .; do sleep 1; done; sleep 2
docker 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/8

docker info تنظیم را تأیید می‌کند. حالا یک image می‌کشیم و لاگ proxy را نگاه می‌کنیم:

Terminal window
docker exec lx-px-dind docker pull alpine 2>&1 | tail -1
echo "--- لاگ 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 را درست نمی‌کند؟

مثال ۳: proxy خراب چه خطایی می‌دهد؟

Section titled “مثال ۳: proxy خراب چه خطایی می‌دهد؟”
Terminal window
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" } }
LXEOF
docker restart lx-px-dind >/dev/null
until docker exec lx-px-dind docker info >/dev/null 2>&1; do sleep 1; done; sleep 3
until docker exec lx-px-dind docker ps -q --filter name=lx-proxy --filter status=running | grep -q .; do sleep 1; done; sleep 2
docker exec lx-px-dind docker pull busybox 2>&1 | tail -1 | cut -c1-260
خروجی
Error 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 انجام شود:

Terminal window
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"
}
}
LXEOF
docker restart lx-px-dind >/dev/null
until docker exec lx-px-dind docker info >/dev/null 2>&1; do sleep 1; done; sleep 3
until docker exec lx-px-dind docker ps -q --filter name=lx-proxy --filter status=running | grep -q .; do sleep 1; done; sleep 2
docker exec lx-px-dind docker pull busybox 2>&1 | tail -1
after=$(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 دقیقاً همان کار را می‌کنی.

اول سرویس بدون تنظیم:

Terminal window
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'
LXEOF
systemctl daemon-reload
systemctl start demo
sleep 1
journalctl -u demo -n 1 --no-pager -o cat
خروجی
HTTP_PROXY=[]

حالا drop-in را با خود systemctl edit demo می‌سازیم. این دستور یک ویرایشگر باز می‌کند؛ برای اینکه در اسکریپت جا بشود، متغیر SYSTEMD_EDITOR را روی یک «ویرایشگر» کوچک می‌گذارم که به‌جای تو متن را می‌نویسد، و چون systemctl edit ترمینال (tty) می‌خواهد آن را با script اجرا می‌کنم (در عمل خودت در ترمینال و nano یا vim می‌نویسی و این‌ها لازم نیست):

cat > /usr/local/bin/fake-editor <<'LXEOF'
#!/bin/sh
printf '[Service]\nEnvironment="HTTP_PROXY=http://proxy.example:3128"\nEnvironment="NO_PROXY=localhost,127.0.0.1"\n' > "$1"
LXEOF
chmod +x /usr/local/bin/fake-editor
script -qec 'SYSTEMD_EDITOR=/usr/local/bin/fake-editor systemctl edit demo' /dev/null >/dev/null
systemctl cat demo | grep -vE '^$'
systemctl restart demo
sleep 1
systemctl show demo --property=Environment
journalctl -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 docker
systemctl show --property=Environment docker
docker 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 تعریفشان کنی:

bp/Dockerfile
FROM alpine
ARG MY_VAR=hello
RUN env | grep -iE '^(http_proxy|https_proxy|no_proxy|MY_VAR)=' | sort
Terminal window
docker 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:3128
MY_VAR=hello
NO_PROXY=localhost
http_proxy=http://proxy.example:3128
no_proxy=localhost

داخل RUN متغیرها دیده می‌شوند (هم حرف بزرگ هم کوچک). نکته‌ی مهم این است که این مقدارها داخل image نمی‌مانند:

Terminal window
echo "--- تاریخچه‌ی build (فقط ARG غیر-proxy دیده می‌شود):"
docker history --no-trunc lx-bp --format '{{.CreatedBy}}' | grep -E 'RUN' | cut -c1-70
echo "--- متغیرهای proxy در کانتینر اجراشده از image:"
docker run --rm lx-bp env | grep -ci proxy
docker 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 امتحان می‌کنم.)

Terminal window
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"}}}
LXEOF
docker 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 پاک نمی‌کند) و کش را اجرا می‌کنیم:

Terminal window
docker exec -i lx-px-dind sh -c 'cat > /etc/docker/daemon.json' <<'LXEOF'
{ "proxies": {}, "registry-mirrors": [], "insecure-registries": [] }
LXEOF
docker restart lx-px-dind >/dev/null
until docker exec lx-px-dind docker info >/dev/null 2>&1; do sleep 1; done; sleep 3
until docker exec lx-px-dind docker ps -q --filter name=lx-proxy --filter status=running | grep -q .; do sleep 1; done; sleep 2
docker 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>&1
sleep 2
docker 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 seconds
lx-proxy: Up 19 seconds
خطوط proxy در docker info: 0

متغیر REGISTRY_PROXY_REMOTEURL registry را به حالت pull-through cache می‌برد. حالا daemon را به آن وصل می‌کنیم. چون کش ما با HTTP کار می‌کند آن را insecure اعلام می‌کنیم (برای شبکه‌ی کاملاً داخلی؛ برای کار واقعی TLS بگذار):

Terminal window
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"] }
LXEOF
docker exec lx-px-dind sh -c 'kill -HUP $(pidof dockerd)'; sleep 3
docker 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>&1
echo "کش قبل از 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 -1
echo "کش بعد از 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 آمد، از داخل کش گذشت و ذخیره شد. بار دوم از کش می‌آید:

Terminal window
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/null
docker exec lx-px-dind docker pull alpine 2>&1 | tail -1
n2=$(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 جایگزین را بنویسی.

وقتی 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 جابه‌جا شدند.
می‌خواهم 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 رد نمی‌شوند

۱) تنظیم فقط export HTTPS_PROXY و انتظار کار کردن docker pull

Section titled “۱) تنظیم فقط export HTTPS_PROXY و انتظار کار کردن docker pull”

مثال ۲: daemon متغیر شل تو را نمی‌بیند. راه‌حل: daemon.json یا systemd.

اعمال proxies نیاز به restart دارد (مثال ۲). راه‌حل: sudo systemctl restart docker و بعد docker info | grep -i proxy.

۳) آدرس proxy اشتباه (proxyconnect tcp)

Section titled “۳) آدرس proxy اشتباه (proxyconnect tcp)”

مثال ۳. راه‌حل: آدرس و پورت را با curl -x از بیرون داکر تست کن.

Terminal window
mkdir -p m4 && printf 'FROM alpine\nARG TOKEN=s3cret-value\nRUN echo build\n' > m4/Dockerfile
docker build -q -t lx-m4 m4 >/dev/null
docker history --no-trunc lx-m4 --format '{{.CreatedBy}}' | grep -o 'TOKEN=[^ ]*' | head -1
docker rmi lx-m4 >/dev/null
خروجی
TOKEN=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 چه خطوطی دارد. (روی داکر خودت فقط ببین؛ چیزی عوض نکن.)

دیدن جواب
Terminal window
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 از مثال ۱ را به کار ببر.)

دیدن جواب
Terminal window
docker run -d --privileged --name lx-px-e2 -e DOCKER_TLS_CERTDIR= docker:dind >/dev/null
until docker exec lx-px-e2 docker info >/dev/null 2>&1; do sleep 1; done
docker cp px/proxy.py lx-px-e2:/var/tmp/proxy.py
docker exec lx-px-e2 docker network create --subnet 10.56.0.0/24 n >/dev/null
docker 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>&1
sleep 2
docker 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/null
until docker exec lx-px-e2 docker info >/dev/null 2>&1; do sleep 1; done; sleep 3
for 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 2
docker 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 -1
echo "اتصال‌های تونل‌شده: $(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:3128
docker.io/library/busybox:latest
اتصال‌های تونل‌شده: 7
✎ تمرینسخت

ثابت کن یک ARG معمولی در docker history می‌ماند ولی HTTP_PROXY که با --build-arg می‌دهی نمی‌ماند. یک Dockerfile با ARG NOTE=visible بساز، با --build-arg HTTP_PROXY=http://proxy.example:3128 بساز و تاریخچه را بررسی کن.

دیدن جواب
Terminal window
mkdir -p e3 && printf 'FROM alpine\nARG NOTE=visible\nRUN echo hi\n' > e3/Dockerfile
docker build -q -t lx-e3 --build-arg HTTP_PROXY=http://proxy.example:3128 e3 >/dev/null
H=$(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/null
خروجی
NOTE در تاریخچه: 2
HTTP_PROXY در تاریخچه: 0
؟ آزمونک
  1. daemon داکر برای pull از کجا proxy را می‌خواند؟

  2. تغییر proxies در daemon.json چگونه اعمال می‌شود؟

  3. برای proxy در زمان build؟

  4. no-proxy برای چیست؟

  5. pull-through cache چه می‌کند؟

  6. چرا رمز را با ARG در Dockerfile نگذاریم؟

  • سه جای جدا برای 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 dockerdrop-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:2registry به‌عنوان pull-through cache
curl -x http://PROXY:PORT https://registry-1.docker.io/v2/تست proxy از بیرون داکر