تجربه‌ واقعی از یک داکرایز دردناک: چطور سرویس‌های پرگار رو روی سرور ایران بالا آوردیم

اوایل تصمیم گرفتیم که همه سرویس‌های اصلی شرکت رو به صورت کامل داکرایز کنیم. هدف این بود که از ابتدا همه چیز مرتب و یکپارچه روی سرورهای داخل ایران دیپلوی بشه؛ یعنی بدون نصب دستی چیزی، فقط با اجرای چند دستور همه سرویس‌ها بالا بیان.

اما خیلی زود فهمیدیم که بزرگ‌ترین چالش ما، خود اینترنت ایران و تحریم‌ها هستن!

مرحله اول: داکر نصب نمی‌شد

اولین قدم نصب Docker بود. Docker یک پلتفرم کانتینرسازی محبوبه که به ما کمک می‌کنه نرم‌افزارها و سرویس‌هامون رو در محیطی جدا از سیستم‌عامل و قابل حمل اجرا کنیم.

طبق مستندات رسمی داکر، باید با افزودن ریپوزیتوری رسمی داکر به package manager سیستم عامل آنرا نصب میکردیم

با توجه به انتخاب debian برای سرور های شرکت پرگار باید از این مسیر استفداه میکردیم:

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

اما این روش به خاطر تحریم و فیلترینگ جواب نداد و ارتباط با سرور رسمی داکر برقرار نمی‌شد. نسخه‌ای هم که در مخازن Debian با نام docker.io وجود داره، قدیمیه و برای محیط‌های Production پیشنهاد نمی‌شه.

مرحله دوم: نصب داکر با کمک پراکسی و proxychains

برای حل این مشکل، از یک سرور خارجی که در خارج از ایران قرار داشت، کمک گرفتیم. با استفاده از ابزار های ssh روی سرور خودمون یک پراکسی از نوع SOCKS5 ایجاد کردیم.

برای اینکه از این پراکسی استفاده کنیم، روی سرور داخل ایران ابزار proxychains نصب کردیم. proxychains ابزاریه که می‌تونه هر برنامه‌ای که از اینترنت استفاده می‌کنه رو مجبور کنه ترافیکش رو از یک پراکسی مثل SOCKS5 بفرسته.

سپس proxychains رو اینجوری کانفیگ کردیم:

proxy_dns
random_chain
[ProxyList]
socks5 127.0.0.1 2080

با این روش نصب داکر با موفقیت انجام شد

اما این تازه اول ماجراست، بعد از این باید برای اتصال به داکر هاب راهی پیدا میکردیم چون این  سرویس هم در ایران از دسترس خارجه

مرحله سوم: پروکسی‌کردن dockerd و containerd

تصمیم گرفتیم که پراکسی کردن رو یک لایه جلوتر ببریم. به این فکر افتادیم که خود داکر و زیرسیستمش یعنی containerd رو از پشت پراکسی اجرا کنیم.

dockerd سرویس اصلی داکره که تمام عملیات‌های مربوط به build، run و pull رو مدیریت می‌کنه. containerd هم موتور زمان‌اجرای کانتینرهاست که توسط داکر استفاده می‌شه.

برای این کار، فایل systemd مربوط به سرویس داکر رو اینطور تغییر دادیم:

# docker.service
...
ExecStart=/bin/proxychains /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
...
# containerd.service
...
ExecStart=/bin/bash /usr/bin/containerd
...

اما این روش جواب نداد. داکر بالا می‌اومد ولی موقع build کردن، خطای timeout مربوط به اتصال اینترنت می‌داد. متوجه شدیم که proxychains نمی‌تونه ترافیک داکر رو کنترل کنه یا حداقل نیازمند تنظیماتیه که خارج از دانش ماست.

مرحله چهارم: استفاده از رجیستری ابرآروان

اینجا تصمیم گرفتیم که به جای اتصال مستقیم به Docker Hub، از ابرآروان استفاده کنیم. ابرآروان یک شرکت ایرانی ارائه‌دهنده خدمات ابریه که رجیستری داکر داخلی هم ارائه می‌ده.

این مرحله به خوبی کار کرد. اما مرحله بعدی یعنی Build ایمیج‌ها با مشکل جدیدی روبه‌رو شد.

مرحله پنجم: مشکل pip و npm در ایران

موقع Build کردن داکر، سرویس‌هایی داشتیم که نیاز داشتن از pip و npm بسته‌هایی رو دانلود کنن. pip ابزار مدیریت پکیج‌های پایتونه و npm برای جاوااسکریپت استفاده می‌شه.

در لاگ‌ها دیدیم که اتصال این ابزارها به اینترنت قطع شده. بررسی بیشتر نشون داد که فایروال ملی ایران دسترسی به خیلی از مخازن npm و PyPI رو مسدود کرده، مخصوصاً در هفته‌های اخیر.

مشکل از اونجایی بزرگتر میشه که بیلد کردن ایمیج ها داخل کانتینر های داکر انجام میشه و پراکسی کردن کانتینر ها چالش های بیشتری داره

مرحله ششم: استفاده از redsocks و iptables

برای حل این مشکل رفتیم سراغ ابزار redsocks. این ابزار می‌تونه ترافیک TCP رو دریافت کنه و به جای خروج مستقیم، از یک SOCKS5 یا HTTP proxy عبورش بده. برای اینکه ترافیک سرور رو به سمت redsocks هدایت کنیم، از iptables کمک گرفتیم.

iptables ابزار مدیریت فایرواله که می‌تونه قوانین متنوعی برای هدایت و فیلتر کردن ترافیک شبکه تعریف کنه.

ما قوانین زیر رو به جدول NAT اضافه کردیم:

iptables -t nat -F

iptables -t nat -N REDSOCKS
iptables -t nat -A REDSOCKS -d 0.0.0.0/8 -j RETURN
iptables -t nat -A REDSOCKS -d 10.0.0.0/8 -j RETURN
iptables -t nat -A REDSOCKS -d 127.0.0.0/8 -j RETURN
iptables -t nat -A REDSOCKS -d 169.254.0.0/16 -j RETURN
iptables -t nat -A REDSOCKS -d 172.16.0.0/12 -j RETURN
iptables -t nat -A REDSOCKS -d 192.168.0.0/16 -j RETURN
iptables -t nat -A REDSOCKS -d 224.0.0.0/4 -j RETURN
iptables -t nat -A REDSOCKS -d 240.0.0.0/4 -j RETURN
iptables -t nat -A REDSOCKS -d x.x.x.x -j RETURN
iptables -t nat -A REDSOCKS -p tcp -j REDIRECT --to-ports 12345

iptables -t nat -A OUTPUT -p tcp -j REDSOCKS

این دستورات تمام اتصال هایی که مبداشون دستگاه ما هست رو به redsocks ارسال میکنه و قانون خط دوازدهم کمک میکنه که داده هایی که ssh برای سرور خارجمون ارسال میکنه به redsocks ارسال نشه.
این روش روی host جواب داد، ولی داکر که در namespace شبکه‌ای جدا کار می‌کنه، تحت تأثیر این قانون قرار نگرفت.

مرحله هفتم: تنظیم شبکه BuildKit روی یک bridge مشخص

برای اینکه بتونیم کنترل بیشتری روی شبکه‌ی داکر داشته باشیم، تصمیم گرفتیم از BuildKit استفاده کنیم. BuildKit سیستم ساخت (build engine) جدید داکره که از شبکه‌های سفارشی پشتیبانی می‌کنه.

ما BuildKit رو با یک bridge مشخص به رنج IP دلخواه (10.18.0.0/16) تنظیم کردیم و قوانین iptables رو روی اون رنج اجرا کردیم تا ترافیکش به سمت redsocks بره.

ما قوانین iptables رو به روش زیر تغییر دادیم:

i#ptables -t nat -A OUTPUT -p tcp -j REDSOCKS
iptables -t nat -A PREROUTING -s 10.18.0.0/16 -p tcp -j REDSOCKS

اما باز هم جواب نداد. درخواست‌های اینترنتی باز هم تایم‌اوت می‌شدن. به نظر می‌رسید این روش نیاز به تنظیمات بیشتری داشت.

مرحله آخر: استفاده از host network برای BuildKit

در نهایت تصمیم گرفتیم از گزینه‌ای استفاده کنیم که داکر رو مجبور می‌کنه در زمان build از شبکه‌ی میزبان (host network) استفاده کنه. در این حالت، کانتینر Build از همون interfaceها و قوانین شبکه‌ای سیستم اصلی استفاده می‌کنه.

تنظیمات iptables رو به حالت قبل برگردوندیم و از تنظیمات زیر برای داکر استفاده کردیم:

docker buildx create --name hostnet --driver docker-container --buildkitd-flags '--oci-worker-net=host'
docker buildx use hostnet

این‌بار بالاخره مشکل حل شد. pip و npm بدون مشکل کار کردن و ایمیج‌ها با موفقیت build شدن.

وقتی اینترنت آزاد، خودش یک زیرساخت است

مسیر داکرایز کردن سرویس‌های شرکت پرگار، برخلاف تصور اولیه، بیشتر شبیه به یک ماجراجویی پر پیچ و خم بود تا یک فرآیند فنی ساده. مشکلی که در بسیاری از کشورهای دیگر ظرف چند ساعت و با اجرای چند دستور ساده انجام می‌شود، در اینجا برای ما تبدیل شد به روزها تحقیق، آزمون و خطا، بازنویسی تنظیمات، ساخت پراکسی، جابجایی رجیستری‌ها، مانیتور لاگ‌ها و حتی نوشتن iptables به خط فرمان.

هزینه این مسیر فقط زمان و نیروی انسانی نبود؛ زیرساخت‌هایی هم که می‌تونستن برای توسعه‌ی قابلیت‌های جدید مصرف بشن، به جای آن صرف جنگیدن با محدودیت‌های غیرضروری اینترنت شدن.

اینترنت آزاد، فقط برای سرگرمی یا شبکه‌های اجتماعی نیست؛ بلکه یک زیرساخت پایه برای توسعه‌ی نرم‌افزار، نوآوری، همکاری بین‌المللی و رقابت جهانیه.
وقتی اتصال ساده به یک رجیستری یا مخزن پکیج باعث توقف کامل چرخه تولید می‌شه، یعنی ما داریم زیر پای توسعه‌مون تیشه می‌زنیم.
و تا روزی که این آزادی محدود باشه، هر docker build ساده‌ای ممکنه به کابوس شبانهٔ تیم‌های فنی تبدیل بشه.