SSD Nodes Learn 🎉 VPS از $5.50/ماه
راهنماها Matt Connorتوسط Matt Connor

آموزش گام‌به‌گام مهاجرت سرور به VPS جدید

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

مهاجرت سرور به یک VPS جدید به عنوان یک انتقال برنامه‌ریزی‌شده

برای مهاجرت یک سرور به یک VPS جدید، این جابه‌جایی را به عنوان یک انتقال برنامه‌ریزی‌شده (rehearsed cutover) در نظر بگیرید، نه صرفاً یک کپی‌برداری ساده. سرور جدید را از پایه بسازید، داده‌ها را دو بار همگام‌سازی کنید، و پیش از آنکه تغییری در DNS ایجاد کنید، صحت عملکرد سرور جدید را روی آدرس IP اختصاصی خودش اثبات کنید. سپس رکوردها را تغییر دهید و سرور قدیمی را تا زمانی که از پایداری وضعیت مطمئن نشده‌اید، روشن نگه دارید. کپی کردن بایت‌ها بخش آسان کار است. ترتیب عملیات تعیین می‌کند که آیا این جابه‌جایی بدون دردسر انجام می‌شود یا هزینه‌بر خواهد بود.

این راهنما یک سرور Linux را پوشش می‌دهد که یک اپلیکیشن وب، یک دیتابیس و یک گواهی TLS (امنیت لایه انتقال) را اجرا می‌کند. این سناریو اکثر تنظیمات تک‌سروری را شامل می‌شود. دو میزبان در این فرآیند درگیر هستند، بنابراین در هر مثال ذکر شده است که دستور روی کدام میزبان اجرا می‌شود. آدرس‌ها از محدوده‌های مستندسازی انتخاب شده‌اند: 198.51.100.10 سرور قدیمی و 203.0.113.20 سرور جدید است.

پیش از شروع، کل این دستورالعمل را مطالعه کنید. گام نخست، یعنی کاهش TTL در DNS، باید روزها پیش از گامی که واقعاً مد نظر دارید انجام شود.

پیش از هر اقدامی، موجودی تهیه کنید

شما نمی‌توانید سروری را که توصیف نکرده‌اید، بازسازی کنید. یک ساعت وقت بگذارید و تمام کارهایی که سرور قدیمی انجام می‌دهد را یادداشت کنید؛ چرا که همیشه چیزی که پس از مهاجرت از کار می‌افتد، همان موردی است که کسی به یاد نداشته است: یک cron job، یک استثنا در فایروال، یا یک فایل محیطی (environment file) که خارج از دایرکتوری برنامه قرار دارد.

این دستورات را روی سرور قدیمی اجرا کنید و خروجی آن‌ها را در جایی ذخیره کنید که از سرور جدید قابل دسترسی باشد.

# old server: packages you asked for, not the dependencies they pulled in
apt-mark showmanual > ~/inv-packages.txt

# old server: what runs now, and what starts at boot
systemctl list-units --type=service --state=running --no-pager > ~/inv-services.txt
systemctl list-unit-files --state=enabled --no-pager >> ~/inv-services.txt
systemctl list-timers --all --no-pager > ~/inv-timers.txt

# old server: what is listening, on which address and port
sudo ss -tulpn > ~/inv-ports.txt

# old server: human accounts, skipping the system ones
awk -F: '$3 >= 1000 && $3 < 65534 {print $1, $3, $6, $7}' /etc/passwd

apt-mark showmanual فهرستی است که ارزش نگهداری دارد، زیرا تمام بسته‌هایی که به عنوان وابستگی (dependency) نصب شده‌اند را حذف می‌کند. اجرای یک dpkg --get-selections کامل روی سروری که 5 سال کار کرده، دو هزار خط خروجی می‌دهد و هیچ اطلاعاتی درباره هدف اصلی نصب بسته‌ها به شما نمی‌دهد.

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

# old server: per-user crontabs, then the system drop-ins
for u in $(cut -d: -f1 /etc/passwd); do sudo crontab -lu "$u" 2>/dev/null | sed "s/^/$u: /"; done
sudo ls -la /etc/cron.d /etc/cron.daily /etc/cron.hourly

سپس نوبت به بخش‌هایی می‌رسد که فایل‌های معمولی نیستند: قوانین فایروال، گواهی‌ها، پایگاه‌های داده و حجم داده‌ای که واقعاً در حال انتقال آن هستید.

# old server
sudo ufw status verbose        # or: sudo nft list ruleset
sudo certbot certificates
sudo -u postgres psql -c '\l'  # or: sudo mysql -e 'SHOW DATABASES;'
sudo du -xh --max-depth=1 / | sort -h

certbot certificates نام هر گواهی، دامنه‌های تحت پوشش، تاریخ انقضا و مسیر فایل‌ها روی دیسک را چاپ می‌کند. این خروجی، چک‌لیست TLS شماست. du -x روی یک فایل‌سیستم باقی می‌ماند، بنابراین وارد volumeهای پشتیبان mount شده نمی‌شود و عددی 10 برابر بزرگ‌تر از واقعیت گزارش نمی‌کند.

دو مورد خارج از سرور قرار دارند و همیشه فراموش می‌شوند. اول، هر سرویس شخص ثالثی که IP سرور شما را در لیست سفید (allowlist) قرار داده است: درگاه پرداخت، پایگاه داده مدیریت‌شده، SMTP relay یا API شرکای تجاری. سرور جدید IP جدیدی دارد، بنابراین این لیست‌های سفید باید پیش از تغییر نهایی (cutover) به‌روزرسانی شوند، نه پس از آن. دوم، رکوردهای DNS که خودتان ایجاد نکرده‌اید، مانند رکورد MX یا رکورد SPF که در متن خود به IP قدیمی اشاره دارند.

چرا به‌جای کلون کردن فایل‌سیستم ریشه قدیمی، آن را بازسازی می‌کنیم

کلون کردن کل فایل‌سیستم ریشه روی یک VPS جدید سریع‌تر به نظر می‌رسد، و همین‌طور هم هست، تا زمانی که با مشکل مواجه شوید. فایل‌سیستمی که سال‌ها در محیط عملیاتی (production) بوده، حاوی تنظیمات دستی است که هیچ‌کس مستند نکرده، بسته‌هایی از مخازنی که دیگر وجود ندارند، و پیکربندی بوت که برای سخت‌افزار مجازی پلتفرم قدیمی ساخته شده است. شما همه این‌ها را به همراه دلیلی که باعث مهاجرت شما شده، منتقل می‌کنید.

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

چه زمانی بازیابی از روی image یا snapshot انتخاب درستی است

یک استثنای صادقانه برای بازسازی سرور وجود دارد. اگر سرور قدیمی بوت نمی‌شود، یا برنامه به گونه‌ای است که دیگر هیچ‌کس نمی‌تواند آن را از سورس بازسازی کند، بازیابی از روی image یا snapshot ارائه‌دهنده، راهکار عملی است. این روش محدودیت‌های واقعی دارد: فقط در داخل یک ارائه‌دهنده و اغلب فقط در یک خانواده از پلن‌ها کار می‌کند، زیرا دیسک بازیابی‌شده انتظار دارد با دستگاه‌های مجازی و نام‌گذاری شبکه همان پلتفرم مواجه شود.

snapshot گرفتن از یک سرور در حال اجرا، همان مشکل یکپارچگی را دارد که هر کپی فایل‌محور دیگری از یک دیتابیس فعال خواهد داشت. به بازیابی از روی image به چشم یک مسیر بازیابی نگاه کنید، نه یک برنامه مهاجرت؛ و پیش از آنکه برنامه‌ای بر پایه آن بنا کنید، چرا snapshot با backup تفاوت دارد را مطالعه کنید.

نحوه انتقال فایل‌ها: rsync از طریق SSH

دستور rsync را از سرور قدیمی اجرا کنید تا داده‌ها را به سرور جدید ارسال (push) کند. ارسال معمولاً ساده‌تر است، زیرا سرور قدیمی داده‌ها را در اختیار دارد و می‌تواند تمام آن‌ها را با دسترسی sudo بخواند.

# old server: dry run first, and read what it says it will do
sudo rsync -aHAX --dry-run --itemize-changes \
  -e 'ssh -i /root/.ssh/id_ed25519' \
  /srv/app/ deploy@203.0.113.20:/srv/app/

# old server: the real bulk pass
sudo rsync -aHAX --info=progress2 \
  -e 'ssh -i /root/.ssh/id_ed25519' \
  /srv/app/ deploy@203.0.113.20:/srv/app/

استفاده از فلگ‌های مناسب اهمیت دارد. -a مجوزها، مُهر زمانی (timestamps)، لینک‌های نمادین و مالکیت فایل‌ها را حفظ می‌کند. -H لینک‌های سخت (hard links) را به‌صورت لینک سخت نگه می‌دارد و آن‌ها را به کپی‌های مجزا تبدیل نمی‌کند. -A لیست‌های کنترل دسترسی (POSIX ACLs) و -X ویژگی‌های توسعه‌یافته (extended attributes) را کپی می‌کنند. بدون این دو مورد آخر، فایلی که کاملاً یکسان به نظر می‌رسد ممکن است رفتار متفاوتی داشته باشد، زیرا برچسب‌های SELinux و ACLها در ویژگی‌های توسعه‌یافته ذخیره می‌شوند و هیچ جای دیگری ثبت نمی‌شوند.

دو نکته باعث بروز اکثر خطاها در این مرحله می‌شود.

اسلش انتهایی (trailing slash) تعیین می‌کند داده‌ها کجا قرار بگیرند. /srv/app/ به معنای محتویات آن دایرکتوری است. /srv/app به معنای خودِ دایرکتوری است. اگر اشتباه کنید، در نهایت با /srv/app/app در سرور جدید مواجه می‌شوید؛ برنامه اجرا می‌شود اما گزارش می‌دهد که فایل‌ها گم شده‌اند، زیرا مسیرهایی که برنامه با آن‌ها پیکربندی شده، اکنون یک سطح بالاتر از حد انتظار قرار دارند.

در sudo، علامت تیلدا (~) دایرکتوری خانگی کاربر root است. نوشتن -e 'ssh -i ~/.ssh/id_ed25519' در داخل یک sudo rsync باعث می‌شود کلید در مسیر /root/.ssh جستجو شود، نه در دایرکتوری خانگی شما. اگر کلید در آنجا نباشد، SSH پیام Permission denied (publickey) را چاپ می‌کند، rsync پیام rsync: connection unexpectedly closed را نمایش می‌دهد و با کد خروجی غیرصفر متوقف می‌شود. مسیر کلید را به‌صورت کامل بنویسید. اگر پس از اصلاح مسیر، پیام احراز هویت همچنان ظاهر شد، لیست کوتاهی از دلایل خطای publickey وجود دارد و بررسی مجوزهای دایرکتوری در سرور جدید، گام بعدی شماست.

در مورد مالکیت فایل‌ها باید یک تصمیم بگیرید. هنگام اجرا با کاربر root، ابزار rsync به‌صورت پیش‌فرض مالک و گروه را بر اساس نام نگاشت می‌کند؛ بنابراین فایلی که در سرور قدیمی متعلق به www-data است، در سرور جدید نیز متعلق به www-data خواهد بود، حتی اگر UID (شناسه کاربری) عددی آن‌ها متفاوت باشد. این همان چیزی است که برای بازسازی سرور نیاز دارید. فقط زمانی از --numeric-ids استفاده کنید که در حال کپی کردن فایل‌سیستمی هستید که حساب‌های کاربری آن در مقصد وجود ندارند. سپس نتیجه را با ls -ln بررسی کنید، زیرا فایلی که متعلق به یک UID بدون حساب کاربری متناظر باشد، فقط با شماره نمایش داده می‌شود و هر سرویسی که سعی در خواندن آن داشته باشد، با خطا مواجه خواهد شد.

انتقال اصلی را چند روز زودتر، زمانی که سرور قدیمی هنوز در حال سرویس‌دهی است، انجام دهید. هر چند بار که مایلید آن را تکرار کنید: rsync فقط تغییرات را ارسال می‌کند، بنابراین اجرای دوم به‌جای ساعت‌ها، تنها چند دقیقه طول می‌کشد. در اجرای نهایی که در بازه زمانی تغییر (cutover) انجام می‌شود، فلگ --delete را اضافه کنید تا فایل‌هایی که در سرور قدیمی حذف شده‌اند، در سرور جدید نیز پاک شوند.

# old server: final pass, inside the window, after the app has stopped writing
sudo rsync -aHAX --delete --info=progress2 \
  -e 'ssh -i /root/.ssh/id_ed25519' \
  /srv/app/ deploy@203.0.113.20:/srv/app/

--delete فایل‌هایی را که در مبدأ وجود ندارند از مقصد حذف می‌کند، بنابراین یک مسیر مبدأ اشتباه به همراه --delete باعث خالی شدن دایرکتوری مقصد می‌شود. همیشه و در هر بار اجرا، ابتدا آن را با --dry-run تست کنید. انتقال‌های طولانی ممکن است با قطع شدن نشست SSH لپ‌تاپ شما متوقف شوند، بنابراین آن‌ها را در داخل tmux یا screen روی سرور قدیمی اجرا کنید. اگر کپی کردن فایل‌ها باعث اشباع شدن پهنای باند شبکه در حین سرویس‌دهی به کاربران می‌شود، فلگ --bwlimit=20M را اضافه کنید.

نحوه انتقال پایگاه داده: یک dump بومی

پایگاه داده یک دایرکتوری از فایل‌ها نیست، حتی اگر این‌طور به نظر برسد. پایگاه داده مجموعه‌ای از فایل‌ها به همراه وضعیت حافظه (in-memory state) و یک write-ahead log است که تنها در لحظات تعریف‌شده توسط خودِ پایگاه داده، یکپارچه (consistent) است. از ابزار اختصاصی خودِ آن استفاده کنید.

PostgreSQL به دو dump نیاز دارد، زیرا نقش‌ها (roles) در سطح کلاستر هستند و pg_dump آن‌ها را شامل نمی‌شود:

# old server
sudo -u postgres pg_dumpall --globals-only -f /var/backups/globals.sql
sudo -u postgres pg_dump -Fc -f /var/backups/appdb.dump appdb
# new server: restore in this order
sudo -u postgres psql -f /var/backups/globals.sql
sudo -u postgres createdb -O appuser appdb
sudo -u postgres pg_restore -d appdb --no-owner /var/backups/appdb.dump

اگر globals.sql را نادیده بگیرید، تمام جداول بازیابی می‌شوند اما هیچ نقش کاربری قادر به خواندن آن‌ها نخواهد بود، زیرا دستورات GRANT به کاربری اشاره می‌کنند که وجود ندارد. -Fc خروجی را در قالب آرشیو سفارشی می‌نویسد که فقط توسط pg_restore خوانده می‌شود و به شما اجازه می‌دهد بعداً جداول خاصی را بازیابی کنید. بازیابی را در همان نسخه اصلی (major version) یا نسخه‌ای جدیدتر انجام دهید. بازگشت به عقب، مثلاً از 17 به 16، پشتیبانی نمی‌شود و pg_restore پیش از نوشتن هر چیزی، آرشیو را به دلیل خطای نسخه پشتیبانی‌نشده در هدر فایل رد می‌کند.

MySQL و MariaDB از یک دستور با چهار گزینه که به صورت پیش‌فرض فعال نیستند، استفاده می‌کنند:

# old server
sudo mysqldump --single-transaction --routines --triggers --events \
  --databases appdb > /var/backups/appdb.sql
# new server
sudo mysql < /var/backups/appdb.sql

--single-transaction یک snapshot یکپارچه بدون مسدود کردن نویسنده‌ها (writers) می‌گیرد، اما فقط برای جداول InnoDB. یک جدول MyISAM در همان پایگاه داده بدون چنین تضمینی کپی می‌شود، بنابراین پیش از اعتماد به dump، موتورهای ذخیره‌سازی خود را بررسی کنید. --routines، --triggers و --events به صورت پیش‌فرض غیرفعال هستند؛ این یعنی یک dump ساده، داده‌های شما را بازیابی می‌کند اما stored procedureها و scheduled eventهای شما را نادیده می‌گیرد. کاربران پایگاه داده و دسترسی‌های آن‌ها در پایگاه داده سیستمی mysql قرار دارند که dump حاصل از --databases appdb هرگز به آن دسترسی ندارد، بنابراین آن‌ها را روی سرور جدید با CREATE USER و GRANT دوباره ایجاد کنید. MariaDB 11 همان ابزار mariadb-dump را ارائه می‌دهد و mysqldump را به عنوان یک symbolic link نگه می‌دارد، بنابراین تا اوت 2026 هر دو نام کار می‌کنند.

SQLite یک فایل واحد است و کپی کردن آن در حالی که برنامه در حال نوشتن است، منجر به یک فایل ناقص (torn file) می‌شود. این پایگاه داده مسیر امن خود را دارد:

# old server
sqlite3 /var/lib/app/app.db ".backup '/var/backups/app.db'"

صرف‌نظر از نوع موتور، پیش از اعتماد به dump، آن را بررسی کنید. dumpای که به دلیل پر شدن دیسک زودتر از موعد متوقف شده باشد، بدون هیچ هشداری بازیابی می‌شود، اما دقیقاً تا همان نقطه‌ای که فایل ناقص (truncated) شده است.

# old server: a complete mysqldump ends with a line reading "-- Dump completed on ..."
tail -n 3 /var/backups/appdb.sql

# new server: after restore, count rows in a table whose size you know
sudo mysql -e 'SELECT COUNT(*) FROM appdb.orders;'

چرا نمی‌توانید یک دیتابیس در حال اجرا را با rsync کپی کنید

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

دو روش امن برای انتقال خودِ فایل‌ها وجود دارد. دیتابیس را متوقف کنید، کپی را انجام دهید و دوباره آن را اجرا کنید: این روش صحیح و ساده است و هزینه آن، میزان downtime به اندازه مدت زمان کپی است. یا از ابزاری استفاده کنید که برای کپی فیزیکی از یک سرور در حال اجرا ساخته شده است. برای PostgreSQL این ابزار pg_basebackup است که با سرور هماهنگ می‌شود تا کپی نهایی یکپارچه (consistent) باشد:

# new server: pull a physical copy from the old one
pg_basebackup -h 198.51.100.10 -U replicator -D /var/lib/postgresql/16/main -X stream -P

این کار به یک نقش (role) با ویژگی REPLICATION و یک ورودی pg_hba.conf متناظر در سرور قدیمی نیاز دارد، بنابراین راه‌اندازی آن نسبت به یک dump پیچیده‌تر است. زمانی که دیتابیس به اندازه‌ای بزرگ است که dump و restore در بازه زمانی مجاز شما نمی‌گنجد، استفاده از این روش ارزشش را دارد. برای مهاجرت‌های معمول تک‌سرور، استفاده از dump گزینه بهتری است.

گواهی‌ها را پیش از انتقال نهایی بازسازی کنید، نه پس از آن

گواهی TLS به نام دامنه وابسته است، نه به آدرس IP؛ بنابراین فایل گواهی به‌راحتی منتقل می‌شود. آنچه به‌سادگی منتقل نمی‌شود، فرآیند تمدید است. چالش پیش‌فرض HTTP-01 در Certbot از مرجع صدور گواهی می‌خواهد فایلی را از طریق پورت 80 در دامنه‌ای که گواهی برای آن صادر می‌شود، دریافت کند. تا زمانی که DNS به سرور جدید اشاره نکند، این درخواست به سرور قدیمی می‌رسد و تمدید در سرور جدید با شکست مواجه می‌شود.

گزینه اول، کپی کردن گواهی‌های موجود و وضعیت تمدید آن‌هاست. این گواهی‌ها تا تاریخ انقضا، روی هر سروری که باشند معتبر باقی می‌مانند.

# old server
sudo rsync -aHAX -e 'ssh -i /root/.ssh/id_ed25519' \
  /etc/letsencrypt/ root@203.0.113.20:/etc/letsencrypt/

هر فایل در مسیر /etc/letsencrypt/renewal/ مشخص می‌کند که کدام افزونه احراز هویت (authenticator plugin) گواهی را صادر کرده است؛ بنابراین همان افزونه را روی سرور جدید نصب کنید (مثلاً python3-certbot-nginx)، در غیر این صورت اولین تلاش برای تمدید با خطای ناشناس بودن احراز هویت شکست می‌خورد. پیش از آنکه به تمدید وابسته شوید، عملکرد آن را تست کنید:

# new server, after DNS has moved
sudo certbot renew --dry-run

گزینه دوم، صدور یک گواهی تازه روی سرور جدید با استفاده از چالش DNS-01 است. این روش کنترل دامنه را از طریق یک رکورد TXT اثبات می‌کند و هرگز با پورت 80 درگیر نمی‌شود. این روش پیش از مهاجرت و زمانی که دامنه هنوز به سرور قدیمی اشاره دارد نیز کار می‌کند؛ بنابراین اگر بتوانید ارائه‌دهنده DNS خود را خودکارسازی کنید، این گزینه تمیزتر است. بخش صدور گواهی با چالش DNS-01 تنظیمات افزونه و اعتبارنامه‌ها را پوشش می‌دهد.

در هر صورت، بدون تغییر DNS، بررسی کنید که سرور جدید واقعاً چه گواهی‌ای ارائه می‌دهد:

# your laptop
echo | openssl s_client -connect 203.0.113.20:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -subject -dates -issuer

دستور -servername از SNI (نشانگر نام سرور) استفاده می‌کند که باعث می‌شود وب‌سرور، virtual host صحیح را انتخاب کند. اگر این بخش را حذف کنید، گواهی پیش‌فرض آن IP نمایش داده می‌شود و با خطای عدم تطابق مواجه می‌شوید که در ظاهر مشکل جدی به نظر می‌رسد، اما در واقعیت چنین نیست.

کاهش TTL رکورد DNS چند روز پیش از جابه‌جایی

DNS جایی است که حتی مهاجرت‌های دقیق نیز ممکن است در آن با شکست مواجه شوند، زیرا تأخیر در ذات آن نهفته است و نمی‌توانید در روز جابه‌جایی آن را کوتاه کنید. یک resolver که رکورد A شما را کش کرده است، تا پایان مدت زمان TTL (زمان زنده ماندن) که به آن داده شده، به ارائه همان مقدار ادامه می‌دهد. کاهش TTL در لحظه، تأثیری بر resolverهایی که رکورد را ده دقیقه پیش با مقدار قدیمی کش کرده‌اند ندارد: آن‌ها مقدار قدیمی را تا پایان TTL قبلی نگه می‌دارند و تنها پس از آن مقدار جدید و کوتاه‌تر را دریافت می‌کنند. بنابراین، TTL را حداقل به اندازه یک دوره کامل TTL قدیمی، پیش از زمان جابه‌جایی کاهش دهید. یک روز زودتر، بازه زمانی مطمئنی است. اگر این مفاهیم برای شما جدید هستند، راهنمای رکوردها، resolverها و کش کردن پیش‌زمینه مناسبی است.

اعداد زیر حاصل محاسبات ریاضی بر اساس خود TTL هستند، نه یک اندازه‌گیری واقعی.

ChartHow long a stale IP can survive, by record TTL
The data behind this chart
[
  {
    "label": "TTL 3600 s (common default)",
    "ttl_seconds": 3600,
    "worst_case_stale_minutes": 60
  },
  {
    "label": "TTL 900 s",
    "ttl_seconds": 900,
    "worst_case_stale_minutes": 15
  },
  {
    "label": "TTL 300 s (lowered for cutover)",
    "ttl_seconds": 300,
    "worst_case_stale_minutes": 5
  },
  {
    "label": "TTL 60 s (short window)",
    "ttl_seconds": 60,
    "worst_case_stale_minutes": 1
  }
]

یک رکورد منتشر شده با TTL برابر با 3600 ثانیه می‌تواند کاربران را تا 60 دقیقه پس از تغییر، همچنان به IP قدیمی هدایت کند. اگر آن را به 300 ثانیه کاهش دهید، این بدترین حالت به 5 دقیقه می‌رسد. این ارقام را به عنوان یک کفِ حداقلی در نظر بگیرید، نه یک تضمین قطعی. برخی از resolverها حداقل TTL خاص خود را اعمال می‌کنند و مقادیر کمتر از آن را نادیده می‌گیرند؛ همچنین برخی از runtimeهای برنامه‌ها، آدرس resolve شده را تا پایان عمر آن پردازش در حافظه کش می‌کنند، بنابراین کلاینتی که پیش از تغییر شما شروع به کار کرده است، ممکن است تا زمان restart شدن، هرگز دوباره به دنبال آدرس جدید نگردد.

هنگام بررسی فعال شدن TTL جدید، پاسخ authoritative را بخوانید، نه کش محلی خود را:

# your laptop: ask the zone's own nameserver, so no cache is involved
dig +short NS example.com
dig +noall +answer @$(dig +short NS example.com | head -n1) example.com A

فیلد دوم در آن خط پاسخ، همان TTL به ثانیه است. سپس رکوردهایی را که معمولاً فراموش می‌شوند بررسی کنید: رکورد AAAA اگر سرور قدیمی دارای IPv6 بوده است، نام www زمانی که به صورت یک رکورد A مجزا است و نه CNAME، هر رکورد MX که به خود سرور اشاره دارد، رکورد SPF که IP قدیمی را لیست کرده است، و رکورد DNS معکوس (PTR) روی آدرس جدید. اگر سرور شما ایمیل ارسال می‌کند، پیش از جابه‌جایی، رکورد PTR را از طریق پنل کنترل ارائه‌دهنده خود تنظیم کنید؛ زیرا سرورهای دریافت‌کننده ایمیل آن را بررسی می‌کنند و نبود PTR باعث می‌شود ایمیل‌ها ساعت‌ها پس از اینکه همه چیز درست به نظر می‌رسید، ریجکت شوند.

تأیید سرور جدید روی IP پیش از تغییر DNS

شما می‌توانید کل برنامه را روی سرور جدید تست کنید، در حالی که DNS همچنان به سرور قدیمی اشاره دارد. برای یک درخواست، جستجوی نام دامنه را نادیده بگیرید:

# your laptop: force one name to the new IP, for this request only
curl -sS --resolve example.com:443:203.0.113.20 \
  -o /dev/null -w '%{http_code} %{ssl_verify_result}\n' https://example.com/

--resolve فقط مقصد اتصال را تغییر می‌دهد. گواهی TLS همچنان با نام واقعی دامنه تطبیق داده می‌شود، بنابراین این کار هم گواهی و هم سرویس را تأیید می‌کند. %{ssl_verify_result} در صورت تأیید زنجیره گواهی، 0 را چاپ می‌کند.

برای بررسی سایت از طریق مرورگر، با افزودن یک خط به /etc/hosts در لپ‌تاپ خود یا به C:\Windows\System32\drivers\etc\hosts در ویندوز، نام دامنه را برای کل سیستم خود بازنویسی کنید:

203.0.113.20 example.com www.example.com

سپس برنامه را همان‌طور که یک کاربر انجام می‌دهد، بررسی کنید. وارد سیستم شوید. صفحه‌ای را بارگذاری کنید که از دیتابیس می‌خواند. فرمی را ارسال کنید که در دیتابیس می‌نویسد. فایلی را آپلود کنید و مطمئن شوید که روی دیسک ذخیره شده است. هر فرآیندی که ایمیل ارسال می‌کند را فعال کنید و رسیدن آن را بررسی کنید، زیرا ارسال SMTP از یک IP جدید اغلب با مشکل مواجه می‌شود. به محض اتمام کار، خط اضافه شده به فایل hosts را حذف کنید. باقی ماندن این خط باعث می‌شود ساعت‌ها وقت خود را صرف عیب‌یابی سایتی کنید که همه کاربران دیگر به‌خوبی آن را می‌بینند.

مراحل گام‌به‌گام انتقال (Cutover)

  1. چند روز قبل: مقدار TTL را کاهش دهید، rsync کلی را اجرا کنید، سرور جدید را بسازید و آن را با استفاده از override فایل hosts تست کنید.
  2. روز موعود، پیش از شروع بازه زمانی: IP جدید را به لیست مجاز (allowlist) تمام سرویس‌های شخص ثالث اضافه کنید و مطمئن شوید که job پشتیبان‌گیری سرور جدید پیکربندی شده و به مخزن شما اشاره می‌کند.
  3. بازه زمانی را آغاز کنید: برنامه را روی سرور قدیمی در حالت maintenance mode قرار دهید تا دیگر داده‌ای را برای نوشتن نپذیرد.
  4. آخرین dump دیتابیس را بگیرید و سپس مرحله نهایی rsync را با --delete اجرا کنید.
  5. dump را روی سرور جدید بازیابی کرده و سرویس‌ها را استارت کنید.
  6. دوباره از طریق --resolve و override فایل hosts تست کنید؛ این تست باید شامل یک عملیات نوشتن واقعی باشد.
  7. رکوردهای A و AAAA را به IP جدید تغییر دهید.
  8. هر دو سرور را مانیتور کنید. access log سرور قدیمی نشان می‌دهد چه کسانی هنوز به آنجا مراجعه می‌کنند؛ این تعداد باید با گذشت زمان TTL به سمت صفر میل کند.
  9. صفحه maintenance را بردارید.
  10. سرور قدیمی را حداقل به مدت یک هفته روشن و دست‌نخورده باقی بگذارید.

مرحله maintenance mode همان بخشی است که افراد از آن صرف‌نظر می‌کنند، در حالی که همین مرحله از شما محافظت می‌کند. هنگامی که دیتابیس جدید یک عملیات نوشتن را بپذیرد، بازگشت به عقب (rollback) به معنای از دست دادن آن داده یا dump گرفتن از دیتابیس جدید و بارگذاری مجدد آن روی دیتابیس قدیمی است. یک بازه زمانی چند دقیقه‌ای فقط‌خواندنی (read-only) هزینه ناچیزی دارد. داشتن دو دیتابیس که هر دو عملیات نوشتن دریافت کرده‌اند، به معنای روزها تلاش برای تطبیق دستی داده‌هاست.

طرح بازگشت به وضعیت قبل (Rollback)

بازگشت به وضعیت قبل تنها یک اقدام است: تغییر رکوردهای DNS به 198.51.100.10. این کار تنها به دلیل چهار موردی که قبلاً انجام داده‌اید، موفقیت‌آمیز خواهد بود.

  • سرور قدیمی همچنان در حال اجراست، سرویس‌های آن فعال‌اند و داده‌ها دست‌نخورده باقی مانده‌اند. شما فقط نوشتن داده روی آن را متوقف کردید و آن را از رده خارج نکردید.
  • مقدار TTL همچنان پایین است، بنابراین مسیر بازگشت به همان سرعت مسیر رفت است.
  • شما IP جدید را به لیست‌های مجاز (allowlists) شخص ثالث اضافه کردید و آن را جایگزین IP قدیمی نکردید. اگر آدرس قدیمی را حذف کنید، مسیر بازگشت شما در درگاه پرداخت با شکست مواجه می‌شود.
  • سرور جدید هیچ داده‌ای که نتوانید شناسایی کنید دریافت نکرده است، زیرا تنها داده‌های نوشته‌شده تا این لحظه، تراکنش‌های آزمایشی خودتان بوده است.

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

Prove the migration worked

A migration is not finished when the site loads. Check the things that only fail later.

# new server
systemctl --failed
journalctl -p err -b --no-pager | tail -n 40
sudo certbot certificates
sudo systemctl list-timers --all --no-pager

systemctl --failed reporting 0 loaded units listed is the result you want. certbot certificates should show the expiry dates you expect, and list-timers should show every scheduled job from your inventory with a real next-run time, not a blank.

Then reboot the new server once, on purpose, while you are watching. A service that someone started by hand and never enabled works perfectly until the first unplanned reboot at three in the morning.

# new server
sudo reboot

# your laptop, once it comes back
curl -sS -o /dev/null -w '%{http_code}\n' https://example.com/

If the application runs in containers, the same trap has a different shape, because a compose stack needs an explicit restart policy to come back after a reboot.

The last check is the easiest to postpone and the most important: the backup job. A migration that ends with an unbacked-up server has traded one risk for another. Run the backup on the new box by hand, then restore a single file from it into a temporary directory. A restic repository with a restore you have actually tested is the version of this that helps when you need it. If you are running the old and new servers side by side for a week, a consistent way to reach and configure each host stops the two from drifting apart while both are live.

پس از انتقال: سرور قدیمی و آخرین موارد

سرور قدیمی را برای یک تا دو هفته نگه دارید. هزینه آن معادل یک ماه از طرحی است که قصد لغو آن را داشتید و این تنها راه بازگشت (rollback) شماست. سپس مابقی موارد را ببندید.

  • استفاده مجدد از همان hostname در ~/.ssh/config برای سرور جدید، باعث ایجاد WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! در اولین اتصال می‌شود، زیرا آن نام اکنون با یک host key متفاوت پاسخ می‌دهد. پس از اینکه مطمئن شدید چرا این تغییر رخ داده است، ورودی قدیمی را با ssh-keygen -R example.com پاک کنید. این کار را از روی عادت انجام ندهید، زیرا همین هشدار دقیقاً مشابه چیزی است که در حملات شنود (interception attack) مشاهده می‌شود. مهاجرت همچنین فرصت مناسبی برای بازبینی این است که کدام کلیدها به چه منابعی دسترسی دارند؛ موضوعی که در مدیریت کلیدهای SSH در یک ناوگان کوچک به آن پرداخته شده است.
  • یک snapshot یا نسخه پشتیبان نهایی از سرور قدیمی تهیه کنید و آن را در جایی غیر از ارائه‌دهنده قبلی ذخیره کنید.
  • IP قدیمی را از بررسی‌های مانیتورینگ، رکوردهای SPF و لیست‌های مجاز (allowlists) شخص ثالث حذف کنید؛ این کار را به ترتیب و در آخرین مرحله انجام دهید.
  • طرح قدیمی را تنها پس از اطمینان از اینکه نسخه نهایی در جای دیگری قابل خواندن است، لغو کنید.

FAQ

مهاجرت یک سرور به یک VPS جدید چقدر زمان می‌برد؟

قطعی قابل مشاهده برای کاربر معمولاً شامل دامپ نهایی دیتابیس، مرحله نهایی rsync و راه‌اندازی سرویس است، بنابراین برای یک برنامه کوچک بین 10 تا 30 دقیقه زمان می‌برد. زمان تقویمی طولانی‌تر است، زیرا TTL دامنه باید حداقل به اندازه یک دوره TTL قدیمی قبل از تغییر کاهش یابد و یک روز زودتر، ایمن‌تر است. کپی حجم اصلی داده‌ها را نیز از روزهای قبل برنامه‌ریزی کنید. این کار روی سرور در حال اجرا انجام می‌شود و تکرار آن در دفعات بعد، فقط تغییرات ایجاد شده از آخرین مرحله را منتقل می‌کند.

آیا می‌توانم به جای دامپ گرفتن از دیتابیس MySQL یا PostgreSQL در حال اجرا، از rsync استفاده کنم؟

خیر. دستور rsync فایل‌ها را یکی‌یکی کپی می‌کند، در حالی که دیتابیس همزمان در چندین فایل می‌نویسد؛ بنابراین کپی حاصل شامل صفحاتی از لحظات مختلف است و وضعیتی را نشان می‌دهد که دیتابیس هرگز در آن نبوده است. ممکن است دیتابیس از شروع خودداری کند، یا شروع شود و بعداً هنگام اجرای یک کوئری روی صفحه آسیب‌دیده، با خطا مواجه شود. از pg_dump به همراه pg_dumpall --globals-only، یا mysqldump --single-transaction استفاده کنید، یا ابتدا دیتابیس را متوقف کرده و سپس فایل‌ها را کپی کنید. برای یک کلاستر بزرگ PostgreSQL، دستور pg_basebackup یک کپی فیزیکی منسجم از سرور در حال اجرا ایجاد می‌کند.

چگونه قبل از تغییر DNS، VPS جدید را تست کنم؟

جستجوی نام دامنه را روی سیستم خودتان بازنویسی کنید. برای یک درخواست تکی، curl --resolve example.com:443:203.0.113.20 https://example.com/ اتصال را به IP جدید می‌فرستد در حالی که همچنان گواهی را با نام واقعی بررسی می‌کند. برای تست با مرورگر، خط 203.0.113.20 example.com را به فایل /etc/hosts در لپ‌تاپ خود اضافه کنید، وارد سیستم شوید، یک خواندن از دیتابیس، یک نوشتن فرم و یک آپلود فایل را تست کنید و سپس آن خط را حذف کنید. برای بررسی صرف گواهی، دستور openssl s_client -connect 203.0.113.20:443 -servername example.com را اجرا کنید.

چه مقدار TTL باید تنظیم کنم و چه زمانی باید آن را کاهش دهم؟

مقدار رکوردهای A و AAAA را به 300 ثانیه کاهش دهید و این کار را حداقل به اندازه یک دوره کامل TTL قدیمی قبل از جابجایی انجام دهید. Resolverهایی که رکورد را قبل از تغییر شما کش کرده‌اند، مقدار قدیمی را تا پایان TTL قدیمی نگه می‌دارند؛ بنابراین اگر TTL قدیمی 86400 بوده، کاهش آن از یک ساعت قبل هیچ فایده‌ای ندارد. چند روز پس از مهاجرت، زمانی که لاگ دسترسی سرور قدیمی ساکت شد، آن را به مقدار عادی خود بازگردانید.

آیا باید گواهی TLS را کپی کنم یا روی سرور جدید یک گواهی جدید صادر کنم؟

هر دو روش کار می‌کنند. کپی کردن /etc/letsencrypt/ باعث می‌شود گواهی تا تاریخ انقضای فعلی معتبر بماند، اما باید همان پلاگین احراز هویت certbot را روی سرور جدید نصب کنید، در غیر این صورت اولین تمدید با شکست مواجه می‌شود؛ بنابراین پس از تغییر DNS، دستور certbot renew --dry-run را اجرا کنید تا از صحت آن مطمئن شوید. صدور گواهی جدید زمانی که بتوانید از چالش DNS-01 استفاده کنید تمیزتر است، زیرا کنترل شما را از طریق رکورد TXT اثبات می‌کند و قبل از اینکه DNS به سرور جدید اشاره کند، کار می‌کند. چالش HTTP-01 تا زمانی که DNS منتقل نشده باشد، روی سرور جدید قابل استفاده نیست، زیرا درخواست اعتبارسنجی به سرور قدیمی می‌رسد.