ย้ายจาก DigitalOcean ไป Hetzner
(isayeter.com)- ย้าย โครงสร้างพื้นฐาน production มูลค่า $1,432 ต่อเดือน ไปยัง dedicated server ราคา $233 ต่อเดือน พร้อมเปลี่ยนระบบปฏิบัติการโดยยังคงความต่อเนื่องของบริการแบบไม่มี downtime
- ตั้งค่า ฐานข้อมูล MySQL 30 ตัว และ Nginx virtual host 34 ตัว รวมถึง GitLab EE, Neo4J, Supervisor, Gearman บนเซิร์ฟเวอร์ใหม่ให้เหมือนเดิม แล้วเสร็จสิ้นการย้ายด้วยการทำ replication แบบเรียลไทม์และ incremental sync รอบสุดท้าย
- หัวใจของการย้ายฐานข้อมูลคือการผสาน การประมวลผลแบบขนานของ mydumper·myloader เข้ากับ MySQL replication และยังแก้ปัญหาเรื่องสคีมา sys กับสิทธิ์ที่เกิดขึ้นระหว่างอัปเกรดจาก MySQL 5.7 ไป 8.0
- ขั้นตอน cutover ดำเนินตามลำดับ ลด DNS TTL, เปลี่ยน Nginx บนเซิร์ฟเวอร์เดิมให้เป็น reverse proxy, และ เปลี่ยน A record ทั้งหมดพร้อมกัน ทำให้แม้ระหว่าง DNS propagation คำขอที่ยังวิ่งเข้า IP เดิมก็ถูกส่งต่อไปยังเซิร์ฟเวอร์ใหม่
- ผลลัพธ์คือ ประหยัดได้ $1,199 ต่อเดือน, $14,388 ต่อปี พร้อมได้ CPU·หน่วยความจำ·สตอเรจที่สูงขึ้น และ downtime 0 นาที
เบื้องหลังของการย้ายระบบ
- ในบริบทของการดำเนินบริษัทซอฟต์แวร์ในตุรกี ภาวะ เงินเฟ้อที่พุ่งสูง และ ค่าเงินลีราตุรกีอ่อนตัว ทำให้ภาระค่าโครงสร้างพื้นฐานที่คิดเป็นดอลลาร์เพิ่มขึ้นอย่างมาก
- ค่าใช้จ่ายของเซิร์ฟเวอร์ DigitalOcean เดิมอยู่ที่ $1,432 ต่อเดือน โดยมีสเปกเป็น RAM 192GB, 32 vCPU, SSD 600GB, block volume 1TB จำนวน 2 ตัว และรวมแบ็กอัป
- เป้าหมายใหม่คือ Hetzner AX162-R ซึ่งเป็น dedicated server ที่ใช้ AMD EPYC 9454P 48 คอร์ 96 เธรด, 256GB DDR5 และ 1.92TB NVMe Gen4 RAID1
- ค่าใช้จ่ายรายเดือนลดลงเหลือ $233 ทำให้ประหยัดได้ $1,199 ต่อเดือน และ $14,388 ต่อปี
- แม้จะไม่ได้ไม่พอใจกับความน่าเชื่อถือหรือประสบการณ์นักพัฒนาของเซิร์ฟเวอร์เดิม แต่สำหรับ steady-state workload แล้ว ความคุ้มค่าต่อราคาก็ไม่สมเหตุสมผลอีกต่อไป
สภาพแวดล้อมการใช้งานเดิม
- สแตกที่ใช้งานไม่ใช่แค่สภาพแวดล้อมทดสอบแบบง่าย ๆ แต่เป็น production จริง
- ฐานข้อมูล MySQL 30 ตัว รวมข้อมูลทั้งหมด 248GB
- ให้บริการ Nginx virtual host 34 ตัว ครอบคลุมหลายโดเมน
- มีแบ็กอัป GitLab EE ขนาด 42GB
- รัน Neo4J Graph DB ขนาด 30GB
- ใช้ Supervisor จัดการ background worker หลายสิบตัว
- ใช้คิวงาน Gearman
- ให้บริการแอปมือถือแบบ live กับผู้ใช้หลายแสนคน
- ระบบปฏิบัติการของเซิร์ฟเวอร์เดิมคือ CentOS 7 ซึ่งสิ้นสุดการซัพพอร์ตไปแล้ว
- ระบบปฏิบัติการของเซิร์ฟเวอร์ใหม่คือ AlmaLinux 9.7 ซึ่งเป็นดิสโทรที่เข้ากันได้กับ RHEL 9 และเป็นตัวเลือกสืบทอดจาก CentOS อย่างเป็นธรรมชาติ
- การย้ายครั้งนี้ไม่ใช่แค่เพื่อลดต้นทุน แต่ยังเป็นโอกาสในการออกจากระบบปฏิบัติการที่ไม่ได้รับ security update มาหลายปี
กลยุทธ์แบบไม่มี downtime
- ไม่ใช้วิธีเปลี่ยน DNS แล้วรีสตาร์ตบริการแบบง่าย ๆ แต่ดำเนินการย้ายแบบไม่มี downtime ผ่าน กระบวนการ 6 ขั้นตอน
-
ขั้นตอนที่ 1: ติดตั้งสแตกทั้งหมดบนเซิร์ฟเวอร์ใหม่
- ติดตั้ง Nginx ด้วยการคอมไพล์จากซอร์สโดยใช้แฟล็กเหมือนของเดิม
- ติดตั้ง PHP ผ่าน Remi repo และใช้ไฟล์ตั้งค่า
.iniเดิมจากเซิร์ฟเวอร์เก่า - ติดตั้ง MySQL 8.0, Neo4J Graph DB, GitLab EE, Node.js, Supervisor, Gearman และตั้งค่าให้ทำงานเหมือนเดิม
- ก่อนแตะ DNS ได้ทำให้ทุกบริการทำงานเหมือนเซิร์ฟเวอร์เดิมเรียบร้อยแล้ว
- ใบรับรอง SSL จัดการโดยคัดลอกไดเรกทอรี
/etc/letsencrypt/ทั้งหมดจากเซิร์ฟเวอร์เดิมด้วย rsync - หลังจากทราฟฟิกทั้งหมดถูกย้ายไปยังเซิร์ฟเวอร์ใหม่แล้ว จึงบังคับต่ออายุใบรับรองทั้งหมดด้วย
certbot renew --force-renewal
-
ขั้นตอนที่ 2: ทำสำเนาไฟล์เว็บด้วย rsync
- คัดลอกไดเรกทอรี
/var/www/htmlทั้งหมด ขนาดประมาณ 65GB และมี ไฟล์ 1.5 ล้านไฟล์ ด้วยrsyncผ่าน SSH - ใช้ตัวเลือก
--checksumเพื่อตรวจสอบความถูกต้องครบถ้วน - ก่อน cutover ทำ incremental sync รอบสุดท้ายเพิ่มเติมเพื่อเก็บไฟล์ที่มีการเปลี่ยนแปลง
- คัดลอกไดเรกทอรี
-
ขั้นตอนที่ 3: ทำ MySQL master-slave replication
- แทนที่จะ dump แล้ว restore จนฐานข้อมูลต้องหยุด ใช้วิธีตั้งค่า replication แบบเรียลไทม์
- ตั้งให้เซิร์ฟเวอร์เดิมเป็น master และเซิร์ฟเวอร์ใหม่เป็น read-only slave
- โหลดข้อมูลก้อนใหญ่เริ่มต้นด้วย
mydumperจากนั้นเริ่ม replication จาก ตำแหน่ง binlog ที่ถูกบันทึกไว้อย่างแม่นยำใน metadata ของ dump - ทำให้ฐานข้อมูลทั้งสองฝั่งซิงก์แบบเรียลไทม์จนถึงเวลาที่ cutover
-
ขั้นตอนที่ 4: ลด DNS TTL
- เรียกใช้ DigitalOcean DNS API ผ่านสคริปต์เพื่อลด TTL ของ A/AAAA record ทั้งหมด จาก 3600 วินาทีเหลือ 300 วินาที
- MX และ TXT record ไม่ได้ถูกเปลี่ยน
- เพราะการเปลี่ยน TTL ของ mail record อาจทำให้เกิดปัญหาด้านการส่งอีเมล จึงตั้งใจแยกไว้
- รอ 1 ชั่วโมงให้ TTL เดิมหมดอายุทั่วโลก แล้วจึงพร้อม cutover ภายใน 5 นาที
-
ขั้นตอนที่ 5: เปลี่ยน Nginx บนเซิร์ฟเวอร์เดิมให้เป็น reverse proxy
- สคริปต์ Python ทำการ parse
server {}block ทั้งหมดของคอนฟิกไซต์ Nginx 34 ตัว - สำรองคอนฟิกเดิมไว้ แล้วแทนที่ด้วยคอนฟิก proxy ที่ชี้ไปยังเซิร์ฟเวอร์ใหม่
- ระหว่าง DNS propagation คำขอที่ยังเข้ามายัง IP เดิมก็จะถูกส่งต่อไปยังเซิร์ฟเวอร์ใหม่อย่างเงียบ ๆ
- สำหรับผู้ใช้จะมองไม่เห็นการหยุดชะงัก
- สคริปต์ Python ทำการ parse
-
ขั้นตอนที่ 6: DNS cutover และปิดเซิร์ฟเวอร์เดิม
- ใช้สคริปต์ Python เรียก DigitalOcean API เพื่อเปลี่ยน A record ทั้งหมด ไปยัง IP ของเซิร์ฟเวอร์ใหม่ภายในไม่กี่วินาที
- คงเซิร์ฟเวอร์เดิมไว้เป็น cold standby เป็นเวลา 1 สัปดาห์ก่อนปิด
- ตลอดทั้งกระบวนการ บริการยังคงตอบสนองได้เองหรือผ่าน proxy จึงไม่มีช่วงว่างด้านความพร้อมใช้งาน
การย้าย MySQL
- ในงานทั้งหมด ส่วนที่ ซับซ้อนที่สุด คือกระบวนการย้าย MySQL
-
การ dump ข้อมูล
- ใช้ mydumper แทน
mysqldumpมาตรฐาน - อาศัย 48 คอร์ CPU ของเซิร์ฟเวอร์ใหม่เพื่อ export/import แบบขนาน ทำให้งานที่ถ้าใช้
mysqldumpแบบเธรดเดียวจะกินเวลาหลายวัน ลดเหลือเพียงไม่กี่ชั่วโมง - ตัวเลือกสำคัญที่ใช้ ได้แก่
--threads 32,--compress,--trx-consistency-only,--skip-definer,--chunk-filesize 256 - ไฟล์
metadataของ dump หลักบันทึกตำแหน่ง binlog ณ เวลาที่ snapshot ไว้File: mysql-bin.000004Position: 21834307
- ค่านี้ถูกใช้เป็นจุดเริ่มต้นของ replication ในภายหลัง
- ใช้ mydumper แทน
-
การส่ง dump
- เมื่อ dump เสร็จแล้ว จึงส่งไปยังเซิร์ฟเวอร์ใหม่ด้วย rsync ผ่าน SSH
- ส่ง compressed chunk รวมทั้งหมด 248GB
- ตัวเลือก
--compressของmydumperช่วยเพิ่มความเร็วในการส่งผ่านเครือข่าย
-
การโหลดข้อมูล
- ใช้
myloader - ตัวเลือกหลักคือ
--threads 32,--overwrite-tables,--ignore-errors 1062,--skip-definer
- ใช้
-
ปัญหาระหว่างเปลี่ยนจาก MySQL 5.7 ไป 8.0
- เนื่องจากสภาพแวดล้อม CentOS 7 เซิร์ฟเวอร์เดิมจึงยังคงใช้ MySQL 5.7
- ก่อนย้าย ได้ตรวจสอบความเข้ากันได้ของข้อมูลกับ MySQL 8.0 ด้วย
mysqlcheck --check-upgradeและผลออกมาว่าไม่มีปัญหา - บนเซิร์ฟเวอร์ใหม่ติดตั้ง MySQL 8.0 Community รุ่นล่าสุด
- เวลาการรัน query ตลอดทั้งโปรเจกต์ลดลงอย่างมีนัยสำคัญ โดยต้นฉบับระบุเหตุผลว่าเป็นเพราะ optimizer ที่ดีขึ้น และ การปรับปรุง InnoDB ของ MySQL 8.0
- อย่างไรก็ตาม ก็เกิดปัญหาจากการกระโดดเวอร์ชัน
- หลัง import โครงสร้างคอลัมน์ของตาราง
mysql.userมีเพียง 45 คอลัมน์ แทนที่จะเป็น 51 ตามที่คาดไว้ - ส่งผลให้
mysql.infoschemaหายไปและเกิดปัญหาในการยืนยันตัวตนผู้ใช้
- หลัง import โครงสร้างคอลัมน์ของตาราง
- การลองแก้ครั้งแรกใช้คำสั่งด้านล่าง
systemctl stop mysqldmysqld --upgrade=FORCE --user=mysql &
- ครั้งแรกไม่สำเร็จเพราะเกิดข้อผิดพลาด
ERROR: 'sys.innodb_buffer_stats_by_schema' is not VIEW - สาเหตุคือ สคีมา sys ถูก import มาเป็นตารางธรรมดา ไม่ใช่ view
- วิธีแก้คือรัน
DROP DATABASE sys;แล้วจึงรันอัปเกรดซ้ำ - หลังจากนั้นจึงทำงานได้ตามปกติ
การตั้งค่า MySQL replication
- หลังจากทั้งสองเซิร์ฟเวอร์โหลด dump เสร็จแล้ว ก็ตั้งค่าเซิร์ฟเวอร์ใหม่ให้เป็น replica ของเซิร์ฟเวอร์เดิม
- ในคำสั่ง
CHANGE MASTER TOได้กำหนด IP ของเซิร์ฟเวอร์เดิม, ผู้ใช้สำหรับ replication, พอร์ต 3306,MASTER_LOG_FILE='mysql-bin.000004',MASTER_LOG_POS=21834307 - จากนั้นรัน
START SLAVE; - เกือบทันที replication ก็หยุดด้วย error 1062 Duplicate Key
- สาเหตุคือ dump ถูกทำเป็นสองช่วง และในช่วงเวลานั้นมีการเขียนลงบางตาราง ทำให้เมื่อ replay binlog จึงพยายาม insert แถวเดิมซ้ำกับที่มีอยู่ใน dump แล้ว
- เพื่อแก้ปัญหา จึงใช้การตั้งค่าด้านล่าง
SET GLOBAL slave_exec_mode = 'IDEMPOTENT';START SLAVE;
- โหมด IDEMPOTENT จะข้าม duplicate key และ missing row error อย่างเงียบ ๆ
- ฐานข้อมูลสำคัญทั้งหมดซิงก์ได้โดยไม่มีข้อผิดพลาด และภายในไม่กี่นาทีค่า
Seconds_Behind_Masterก็ลดลงเป็น 0
การตรวจสอบก่อน cutover
- ก่อนแตะ DNS จำเป็นต้องยืนยันว่าทุกบริการบนเซิร์ฟเวอร์ใหม่ทำงานได้ถูกต้อง
- วิธีตรวจสอบคือแก้ไฟล์
/etc/hostsบนเครื่องโลคัลชั่วคราว เพื่อแมปโดเมนไปยัง IP ของเซิร์ฟเวอร์ใหม่ - ทำให้เบราว์เซอร์และ Postman ส่งคำขอไปยังเซิร์ฟเวอร์ใหม่ ขณะที่ผู้ใช้ภายนอกยังคงเข้าเซิร์ฟเวอร์เดิม
- ตรวจสอบ API endpoint, แอดมินพาเนล และสถานะการตอบสนองของแต่ละบริการ
- เมื่อยืนยันครบทุกอย่างแล้วจึงเริ่ม cutover จริง
ปัญหาสิทธิ์ SUPER
- หลังจาก master-slave replication ซิงก์กันสมบูรณ์แล้ว พบว่าบนเซิร์ฟเวอร์ใหม่แม้จะตั้ง
read_only = 1แต่ คำสั่ง INSERT ยังสำเร็จ - สาเหตุคือผู้ใช้ PHP application ทุกคนมี สิทธิ์ SUPER
- ใน MySQL สิทธิ์ SUPER สามารถข้าม
read_onlyได้ - ตรวจพบว่ามีสิทธิ์
SUPERอยู่ในผลลัพธ์ของSHOW GRANTS FOR 'some_db_user'@'localhost'; - จึงรัน
REVOKE SUPER ON *.* FROM 'some_db_user'@'localhost';ซ้ำสำหรับ ผู้ใช้แอปพลิเคชัน 24 ราย - จากนั้นรัน
FLUSH PRIVILEGES; - หลังจากนั้น
read_only = 1จึงบล็อกการเขียนจากผู้ใช้แอปพลิเคชันได้ถูกต้อง ขณะที่ยังคงให้ replication ทำงานต่อได้
การเตรียม DNS
- ทุกโดเมนถูกจัดการด้วย DigitalOcean DNS และเนมเซิร์ฟเวอร์เชื่อมผ่าน GoDaddy
- งานลด TTL ถูกทำเป็นสคริปต์ที่ยิงไปยัง DigitalOcean API
- จำกัดขอบเขตการเปลี่ยนแปลงไว้เฉพาะ A และ AAAA record
- MX และ TXT record ไม่ได้ถูกแตะต้อง
- เพราะความเป็นไปได้ที่จะเกิดปัญหาเรื่องการส่งผ่านของ Google Workspace จึงไม่นำ TTL ของ record ที่เกี่ยวกับอีเมลไปเปลี่ยน
- หลังรอ 1 ชั่วโมงให้ TTL เดิมหมดอายุ ก็พร้อมสำหรับ cutover
เปลี่ยน Nginx บนเซิร์ฟเวอร์เดิมเป็น reverse proxy
- แทนที่จะแก้ไฟล์คอนฟิก 34 ไฟล์ด้วยมือ ได้ใช้สคริปต์ Python แปลงอัตโนมัติ
- สคริปต์จะ parse
server {}block ของทุกไฟล์คอนฟิก ระบุ content block หลัก แล้วแทนที่ด้วยคอนฟิก proxy - คอนฟิกต้นฉบับถูกสำรองเป็นไฟล์
.backup - ในตัวอย่างคอนฟิกใช้
proxy_pass https://NEW_SERVER_IP;,proxy_set_header Host $host;,proxy_set_header X-Real-IP $remote_addr;,proxy_read_timeout 150; - ตัวเลือกสำคัญคือ
proxy_ssl_verify off- เพราะใบรับรอง SSL บนเซิร์ฟเวอร์ใหม่ใช้ได้กับโดเมน แต่ใช้ไม่ได้กับ IP address
- ในสภาพแวดล้อมที่ควบคุมปลายทางทั้งสองฝั่งได้ จึงยอมปิดการตรวจสอบในจุดนี้
ขั้นตอน cutover
- เงื่อนไขก่อน cutover คือ replication lag ต้องเป็น
Seconds_Behind_Master: 0และ reverse proxy ต้องพร้อมแล้ว - ลำดับการทำงานมีดังนี้
- บนเซิร์ฟเวอร์ใหม่รัน
STOP SLAVE; - บนเซิร์ฟเวอร์ใหม่รัน
SET GLOBAL read_only = 0; - บนเซิร์ฟเวอร์ใหม่รัน
RESET SLAVE ALL; - รัน
supervisorctl start allบนเซิร์ฟเวอร์ใหม่ - บนเซิร์ฟเวอร์เดิมรัน
nginx -t && systemctl reload nginxเพื่อเปิดใช้งาน proxy - บนเซิร์ฟเวอร์เดิมรัน
supervisorctl stop all - บน Mac โลคัลรัน
python3 do_cutover.pyเพื่อเปลี่ยน A record ทั้งหมดของ DNS ไปยัง IP ของเซิร์ฟเวอร์ใหม่ - รอ propagation ประมาณ 5 นาที
- คอมเมนต์ทุกรายการใน crontab บนเซิร์ฟเวอร์เดิม
- บนเซิร์ฟเวอร์ใหม่รัน
- สคริปต์ DNS cutover เรียก DigitalOcean API เพื่อเปลี่ยน A record ทั้งหมดภายในประมาณ 10 วินาที
งานเพิ่มเติมหลัง cutover
- หลังการย้ายเสร็จ พบว่า GitLab project webhook จำนวนมากยังคงชี้ไปยัง IP ของเซิร์ฟเวอร์เดิม
- จึงเขียนและใช้สคริปต์ที่สแกนทุกโปรเจกต์ผ่าน GitLab API แล้วอัปเดต webhook ทั้งหมดแบบรวดเดียว
ผลลัพธ์สุดท้าย
- ค่าใช้จ่ายรายเดือนลดจาก $1,432 เหลือ $233
- ประหยัดรายปีได้ $14,388
- ในด้านประสิทธิภาพก็ได้เซิร์ฟเวอร์ที่แรงขึ้นด้วย
- CPU เพิ่มจาก 32 vCPU เป็น 96 logical CPU
- RAM เพิ่มจาก 192GB เป็น 256GB DDR5
- สตอเรจเปลี่ยนจากโครงสร้างผสมประมาณ 2.6TB เป็น 2TB NVMe RAID1
- downtime เท่ากับ 0 นาที
- การย้ายทั้งหมดใช้เวลาประมาณ 24 ชั่วโมง
- ไม่มีผลกระทบต่อผู้ใช้
บทเรียนสำคัญ
- MySQL replication คือเครื่องมือหลักของการย้ายแบบไม่มี downtime
- ตั้งค่าให้เร็ว ปล่อยให้ไล่ตามจนทัน แล้วค่อยทำ cutover
- ต้องตรวจสอบสิทธิ์ของผู้ใช้ MySQL ก่อนย้ายเสมอ
- ถ้ามี สิทธิ์ SUPER ก็จะข้าม
read_onlyทำให้สภาพแวดล้อม slave ไม่ได้เป็น read-only จริง
- ถ้ามี สิทธิ์ SUPER ก็จะข้าม
- งานอย่างอัปเดต DNS, แก้คอนฟิก Nginx และแก้ webhook ควร ทำเป็นสคริปต์
- ถ้าต้องจัดการไซต์ 34 แห่งขึ้นไปด้วยมือ จะใช้เวลานานและเพิ่มโอกาสผิดพลาด
- ชุด mydumper + myloader เร็วกว่า
mysqldumpมากเมื่อใช้กับชุดข้อมูลขนาดใหญ่- การ dump และ restore แบบขนาน 32 เธรด ลดงานที่กินเวลาหลายวันให้เหลือไม่กี่ชั่วโมง
- สำหรับ steady-state workload ผู้ให้บริการคลาวด์อาจมีราคาสูงเกินไป และ dedicated server อาจให้ประสิทธิภาพสูงกว่าในต้นทุนที่ต่ำกว่า
สคริปต์บน GitHub
- สคริปต์ Python ทั้งหมดที่ใช้ในการย้ายถูกเผยแพร่บน GitHub
- รายการสคริปต์ที่รวมอยู่
do_list_domains_ttl.py- ใช้ดู A record, IP และ TTL ของทุกโดเมนใน DigitalOcean
do_ttl_update.py- ลด TTL ของทุก A/AAAA record ลงเหลือ 300 วินาทีแบบรวดเดียว
do_to_hetzner_bulk_dns_records_import.py- ย้ายทุก DNS zone จาก DigitalOcean ไปยัง Hetzner DNS
do_cutover_to_new_ip.py- สลับ A record ทั้งหมดจาก IP ของเซิร์ฟเวอร์เดิมไปยัง IP ของเซิร์ฟเวอร์ใหม่
nginx_reverse_proxy_update.py- แปลงคอนฟิก nginx site ทั้งหมดให้เป็น reverse proxy
mysql_compare.py- เปรียบเทียบจำนวน row ของทุกตารางระหว่าง MySQL สองเซิร์ฟเวอร์
final_gitlab_webhook_update.py- อัปเดต GitLab project webhook ทั้งหมดให้ชี้ไปยัง IP ของเซิร์ฟเวอร์ใหม่
mydumper- ไลบรารี mydumper
- ทุกสคริปต์รองรับโหมด
DRY_RUN = Trueเพื่อให้พรีวิวอย่างปลอดภัยก่อนใช้งานจริง
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
เมื่อหลายเดือนก่อนฉันย้ายเซิร์ฟเวอร์สองเครื่องจาก Linode และ DO ไป Hetzner แล้วลดค่าใช้จ่ายได้ มหาศาลในระดับใกล้เคียงกัน สิ่งที่น่าประทับใจกว่าคือมันเป็น สแตกสุดยุ่งเหยิง ที่มีหลายสิบเว็บไซต์ ใช้หลายภาษา มีไลบรารีเก่า ๆ ปนกันไปหมด รวมถึง MySQL และ Redis ด้วย แต่ Claude Code ย้ายทั้งหมดให้ แถมยังเขียนโค้ดบางส่วนใหม่เพื่อจัดการกับไลบรารีที่ไม่มีให้อีกด้วย ตอนนี้การย้ายระบบซับซ้อนแบบนี้ง่ายขึ้นมาก เลยคิดว่าอนาคตการย้ายข้ามผู้ให้บริการน่าจะยิ่งทำได้คล่องขึ้น
ฉันกำลังวางแผนย้ายจาก AWS ไป Hetzner Amazon บางครั้งตั้งราคา แพงกว่าคู่แข่งถึง 20 เท่า แล้วถ้าอยากได้ราคาที่พอรับได้ก็ต้องผูกมัดระยะยาว แถมยังทำให้การย้ายข้อมูลออกมีค่าใช้จ่ายสูงมากจนรู้สึกว่า เป็นปฏิปักษ์กับลูกค้า สุด ๆ พวกเขาอาจคิดว่าค่าธรรมเนียม egress จะช่วยกักคนไว้ แต่จริง ๆ แล้วมันกลายเป็นแรงกดดันให้คนย้ายทั้งระบบ เมื่อแค่ย้ายบางส่วนไปหาคู่แข่งก็เริ่มคุ้มแล้ว อย่างไรก็ตาม ของฉันย้ายง่ายขึ้นหน่อยเพราะไม่ได้สร้างแพลตฟอร์มทับบนบริการเฉพาะของ Amazon
ทุกครั้งที่เห็นบทความแบบนี้ ฉันมักแปลกใจที่คนไม่ค่อยพูดถึง redundancy หรือพวก load balancer เลย ถ้าเซิร์ฟเวอร์เครื่องเดียวตาย หลายบริการก็อาจล่มพร้อมกัน เลยสงสัยว่าทุกคนมองว่ามันโอเคจริง ๆ เหรอ อาจประหยัดเงินได้ก็จริง แต่สุดท้ายอาจต้องเสียเวลา maintenance และเจอปัญหาในอนาคตมากขึ้น
พวกเราที่ lithus.eu เคยย้ายลูกค้าจากหลายคลาวด์มา Hetzner บ่อย ๆ โดยทั่วไปจะจัดแบบ หลายเซิร์ฟเวอร์ บางครั้งก็หลาย AZ แล้วใช้ Kubernetes กระจาย workload เพื่อให้ได้ HA ถ้ามีแค่โหนดเดียว Kubernetes อาจจะเกินจำเป็น แต่ถ้ามีหลายโหนดมันสมเหตุสมผลขึ้นมาก เรื่องแบ็กอัปเราใช้ทั้ง Velero และแบ็กอัประดับแอป เช่น Postgres ก็ใช้ WAL backup เพื่อให้ทำ PITR ได้ด้วย ข้อมูลสถานะจะเก็บไว้บนอย่างน้อยสองโหนดเพื่อรับประกัน HA ในแง่ประสิทธิภาพ เบียร์เมทัลก็มักจะดีกว่าด้วย และหลายครั้ง latency ลดลงครึ่งหนึ่งเมื่อเทียบกับ AWS ผมคิดว่าเหตุผลไม่ใช่แค่ตัว virtualization เอง แต่เป็นปัจจัยแวดล้อมอย่าง NVMe, network latency ที่ต่ำกว่า และ cache contention ที่น้อยกว่า รายละเอียดเพิ่มเติมเคยเขียนไว้ใน โพสต์ HN ก่อนหน้านี้
อ่านบทความนี้ค่อนข้างลำบาก รู้สึกเหมือน Claude เป็นคนทำ migration แล้วหลังจากนั้นเราก็ต้องมาอ่าน รายงาน ที่ Claude เขียนต่ออีกที ถ้า LLM ช่วยให้ประหยัดได้ขนาดนี้ก็น่าชื่นชม แต่ถ้าจะเผยแพร่เป็นบทความ อย่างน้อยก็น่าจะช่วยขัดเกลา ตัดความซ้ำ และเก็บ สำนวนแบบ LLM ออกบ้าง
รู้สึกว่าต้องระวัง Hetzner เหมือนกัน เมื่อก่อนฉันชอบมาก แต่ช่วงหลังย้ายออกแล้ว พวกเขาปิด VM ทั้งหมดประมาณ 30 เครื่องที่ใช้ใน CI/CD pipeline ของเรา เพราะมี ข้อพิพาทบิล 36 ดอลลาร์ แค่รายการเดียว ทั้งที่เราส่งหลักฐานการจ่ายครบถ้วนรวมถึงรายการจากธนาคารไปให้แล้ว แต่พวกเขาไม่ยอมดู แม้เราจะพยายามติดต่อด่วน สุดท้ายก็โดนตัดการเข้าถึงทั้งหมด ตอนนี้ย้ายไป Scaleway แล้ว
เมื่อไม่กี่เดือนก่อนฉันมองหาทางเลือกแทน AWS สำหรับโปรเจกต์ SaaS เล็ก ๆ ที่ทำเป็น side project ตอนแรกก็พิจารณา Hetzner อย่างจริงจังทั้งเพราะต้องการลดต้นทุนและอยากสนับสนุนคลาวด์ใน EU ถึงจะต้องทำอะไรเองมากหน่อยก็พร้อมรับได้ แต่สุดท้ายสิ่งที่เป็นตัวฉุดจริง ๆ คือ ชื่อเสียงของ IP กฎไฟร์วอลล์แบบ managed ของ AWS ที่บริษัทฉันใช้มีอันหนึ่งที่บล็อก IP ของ Hetzner จำนวนมาก หรืออาจทั้งหมดเลยด้วยซ้ำ และแม้แต่บนโน้ตบุ๊กทำงานของฉัน เว็บไซต์ที่โฮสต์บน IP ของ Hetzner ก็เปิดไม่ได้เพราะนโยบาย IT จะใช้ Cloudflare ก็น่าจะช่วยได้บ้าง แต่ก็เคยเห็นคนพูดว่า DDoS protection ยังอ่อนอยู่ สุดท้ายฉันเลือก DO App Platform ในรีเจียน EU และตัวเลือก managed DB ก็เป็นข้อได้เปรียบใหญ่เช่นกัน
การแชร์ประสบการณ์ migration แบบนี้มีประโยชน์มากและน่าขอบคุณ ฉันมองการเทียบ DO กับ Hetzner คล้าย trade-off ระหว่างเปิด DoorDash หรือ UberEats กับการลงมือทำมื้อเย็นเอง สัดส่วนเรื่องต้นทุนก็ให้ความรู้สึกคล้ายกัน ฉันดูแลทั้ง 3 big cloud และ on-prem แต่สำหรับงานจุกจิกหรือการทดสอบ PoC ฉันก็ยังกลับไปใช้คอนโซลของ DigitalOcean อยู่ดี ความ สะดวก แบบกดไม่กี่ปุ่มก็ได้เซิร์ฟเวอร์หรือบักเก็ต มี sane default และเปิดแบ็กอัปได้ด้วยเช็กบ็อกซ์เดียว มันมีคุณค่าแน่นอนเมื่อคิดรวมกับมูลค่าของเวลา
ฉันสงสัยว่าเขาทำแบ็กอัป DB กันอย่างไร มี replica หรือ standby ไหม หรือแค่แบ็กอัประดับรายชั่วโมงเฉย ๆ ใน สถาปัตยกรรมเซิร์ฟเวอร์เดี่ยว แบบนี้ ถ้าเจอฮาร์ดแวร์เสียอย่าง SSD พัง แอปก็อาจหยุดทันที และโดยเฉพาะถ้า SSD ตาย อาจต้องล่มเป็นชั่วโมงหรือเป็นวันระหว่างตั้งใหม่ทั้งหมด
ภาพมีม ในส่วนหัวเป็นภาพที่ฉันทำเอง เดิมเอาไปใช้ใน บทความนี้ พอเห็นมันถูกใช้ถึง สองครั้ง ก็รู้สึกดีใจ