Dynamic Debian Mirroring
ดูแล Debian mirror ที่ debianclub.org มาได้จะครบปีแล้ว โดยอาศัย rsync ตาม วิธีการของ Debian โดย sync วันละสองรอบ เช้า-เย็น พร้อมกับ ป้อนต่อไปอีกสองแห่ง คือ LTN และ mirror ใน มข.
ปัญหาทางเทคนิคที่พบ มีมาเรื่อย ๆ ต้องเข้าไปสั่งแบบ manual เป็นระยะ ๆ เพื่อให้มัน update อยู่เสมอ โดยบังคับตัวเองให้ใช้ mirror ปลายทางอย่าง LTN เพื่อจะได้รู้ทันทีที่มีปัญหาเกิดขึ้น
สำหรับปัญหาที่พบ ไม่มีอะไรร้ายแรงเหมือน สมัยทำที่ LTN แต่ก็ยังมีเรื่องจุกจิกอยู่บ้าง พอสรุปได้ดังนี้
- แหล่งที่อยู่ใกล้ไม่ค่อย update ในสมัยเริ่มแรกนั้น แหล่งที่ใกล้ที่สุดคือฮ่องกง แต่การ update จะไม่สม่ำเสมอ แถมในช่วงแรกยังไม่เปิด anonymous rsync ให้อีกต่างหาก จึงต้องใช้แหล่งถัดไป คือที่สเปน แต่แล้วก็ต้องเปลี่ยนไปมา เพราะหลายครั้งที่สเปนก็ไม่ update ก็เข้าไปเปลี่ยนแหล่ง sync เป็นคราว ๆ ไป เนเธอร์แลนด์บ้าง นิวซีแลนด์บ้าง แต่โดยเฉลี่ยแล้ว แหล่งในเอเชียไม่ค่อย update ในช่วงแรก แถมระยะทางในเครือข่ายยังไล่เลี่ยกับยุโรปอีกต่างหาก
- การ update ล่าช้า โดยปกติผมตั้ง cron ไว้ให้เริ่ม sync หลังกำหนด update ของ ftp-master 2 ชั่วโมง โดยกะว่า mirror หลักต่าง ๆ คง sync กันพอสมควรแล้ว แต่ก็ปรากฏว่า บางวันพอถึงกำหนดที่ว่า mirror ก็ยัง sync กันไม่เสร็จ อาจเป็นเพราะวันนั้นมี update เป็นปริมาณมาก หรือไม่ก็มีปัญหาบางจุดในเครือข่าย
- สคริปต์ rsync ตายกลางคัน อันนี้นาน ๆ จะเจอที เนื่องจาก debianclub อาจจะอยู่ใกล้ backbone แต่ถ้าเป็นสมัย LTN ล่ะก็ เจอประจำ
เหล่านี้ ทำให้ต้องเข้าไปสั่งแบบ manual เป็นครั้งคราว สั่งบ่อยเข้า ก็พบว่าตัวเองกำลังทำงานเป็นแพตเทิร์นบางอย่างซ้ำ ๆ เลยชักเบื่อ เขียนเป็นสคริปต์เลือก mirror รายวันเลยดีกว่า
สคริปต์นั้น จะเลือก mirror ที่ดีที่สุด (ใกล้และ update ที่สุด) โดยแบ่งการทำงานเป็น 2 ขั้น:
- รอให้ master เปลี่ยนแปลง เพื่อจัดการกับปัญหาการ update ล่าช้า โดยแทนที่สคริปต์จะเริ่ม sync ทันที ก็เช็ก master เสียก่อน ว่าเปลี่ยนแปลงหรือยัง ถ้ายัง ก็ sleep ไปก่อน โดยตื่นขึ้นมาเช็กเป็นระยะ ๆ จนกว่าจะพบการเปลี่ยนแปลง จึงจะทำงานต่อไป
- เลือก mirror ที่ดีที่สุด โดยทำ ranking 20 อันดับแรกของ mirror ต่าง ๆ ด้วย
netselect
เอาไว้ แล้วไล่เช็ก timestamp ของแหล่งต่าง ๆ เริ่มจากแหล่งที่ใกล้ที่สุดก่อน ไล่ไปเรื่อย ๆ จนพบแหล่งที่ timestamp มากกว่าหรือเท่ากับของ master ถ้าไล่จนหมดรายชื่อแล้วไม่มีใครใหม่เลย ก็ sleep คอยเช็กเป็นระยะ ๆ ถ้าทำทั้งหมดนี้แล้ว ยังไม่พบ mirror ที่ดี ก็ค่อยบังคับ sync จาก master เป็นคำตอบสุดท้าย
ตัวสคริปต์มีเนื้อหาดังนี้ (license: GPL-2):
การอ่าน timestamp ของ mirror
get_site_stamp() { SITE=$1 wget -q -t 1 -T 5 -O - ftp://$SITE/debian/project/trace/ftp-master.debian.org }
ตัวเลือกต่าง ๆ ของ wget
มีเหตุผลดังนี้
-
-q
ให้มันเงียบ ๆ ไม่ต้องแสดง progress -
-t 1
ลองครั้งเดียวพอ ไม่ต้องลองซ้ำหลายครั้ง เนื่องจากถ้าเจอปัญหา mirror ล่มไป จะได้ไม่ต้องเสียเวลาคอยนาน -
-T 5
หยุดรอแค่ 5 วินาที ซึ่งเป็นเวลาที่มักจะได้คำตอบจาก mirror ที่ตอบสนองปกติ และถ้า mirror ไหนล่มไป ก็ไม่ต้องคอยนาน -
-O -
เพื่อให้เขียนเนื้อหาออก stdout เป็นผลลัพธ์ของฟังก์ชันนี้ - แฟ้ม timestamp ใช้ ftp ไม่ใช่ http เพื่อเลี่ยงการได้เนื้อหาที่ค้างใน proxy หรือในกรณีที่บาง mirror ห้าม access ไดเรกทอรีนี้ผ่าน http
การเปรียบเทียบ timestamp
date_cmp() { DATE1=`date -d "$1" "+%Y%m%d%H%M%S"` DATE2=`date -d "$2" "+%Y%m%d%H%M%S"` expr $DATE1 - $DATE2 }
timestamp จากเซิร์ฟเวอร์ จะได้มาในรูปวัน-เวลาแบบนี้
Wed Jul 30 20:00:01 UTC 2008
ก็จัดการแปลงรูปแบบด้วยคำสั่ง date
ให้อยู่ในรูปตัวเลขที่เรียงลำดับ ปี-เดือน-วัน-ชั่วโมง-นาที-วินาที แล้วก็จับลบกันแบบเลขคณิต เพื่อดูว่าอันไหนก่อนอันไหนหลัง
การรอ master เปลี่ยนแปลง
wait_master_change() { LOCAL_STAMP=`get_site_stamp localhost` MASTER_STAMP=`get_site_stamp ftp.debian.org` RETRIES=0 while [ `date_cmp "$MASTER_STAMP" "$LOCAL_STAMP"` -le 0 ] \ && [ $RETRIES -lt 15 ] do RETRIES=`expr $RETRIES + 1` date "+%H:%M:%S [$RETRIES] - Master unchanged, sleeping." >&2 sleep 600 MASTER_STAMP=`get_site_stamp ftp.debian.org` done if [ `date_cmp "$MASTER_STAMP" "$LOCAL_STAMP"` -le 0 ]; then # master not changed, return failure code return 1 else # master changed, return sucess code return 0 fi }
วนอ่าน timestamp ของ master (ทึกทักเอา ftp.debian.org
เป็น master แต่ความจริงเป็นคนละเซิร์ฟเวอร์กับ ftp-master) มาเปรียบเทียบกับในเครื่อง รอจนกว่า timestamp ของ master จะล้ำหน้าของเครื่องเรา (มีบางขณะเหมือนกัน ที่ของเราล้ำหน้าของ master อันเนื่องมาจากเวลาที่เหลื่อมกันเล็กน้อยระหว่างการ sync ของ ftp.debian.org
กับการ sync ของ mirror อื่น) หรือถ้าครบกำหนดการวนอ่าน (คิดเป็นเวลาหลับ ๆ ตื่น ๆ ก็ร่วม 2 ชั่วโมงครึ่ง) แล้วคืนค่าเป็นรหัสบอก
ฟังก์ชันนี้คืนค่าเป็น return code ของเชลล์ คือ 0 = สำเร็จ, 1 = ล้มเหลว
การตรวจหา mirror ที่ดีที่สุด
choose_best_mirror() { MASTER_STAMP=`get_site_stamp ftp.debian.org` if [ $? -ne 0 ]; then echo "Failed to fetch master timestemp." >&2 exit 1 fi if [ ! -f /var/local/debian-mirrors-rank ]; then echo "/var/local/debian-mirrors-rank missing." >&2 echo "Run '/usr/local/bin/rank-debian-mirrors' to update it." >&2 exit 1 fi for site in `cat /var/local/debian-mirrors-rank`; do SITE_STAMP=`get_site_stamp $site` if [ ! -z "$SITE_STAMP" ] \ && [ `date_cmp "$SITE_STAMP" "$MASTER_STAMP"` -ge 0 ] then echo $site return fi done }
ผมตั้ง cron ในรอบต่างหากนอกสคริปต์นี้ เพื่อทำ ranking ของ mirror ด้วย netselect
แล้วเขียนลงในไฟล์ debian-mirrors-rank
เก็บไว้ เพื่อให้สคริปต์นี้ไปอ่าน rank มาใช้
จากนั้น สคริปต์นี้ก็วนตรวจ timestamp ของแหล่งต่าง ๆ ตามลำดับจากใกล้ไปไกล ถ้าเจอแหล่งไหนที่ timestamp ใหม่ทัดเทียมกับ master (บางแหล่งอาจล้ำหน้า master เล็กน้อย เป็นเรื่องปกติ) ก็หยุดแล้วเขียนชื่อแหล่งนั้นออกทาง stdout เพื่อคืนค่าทันที
สคริปต์ที่เรียก choose_best_mirror
นี้ ก็จะมีลูปหลับ ๆ ตื่น ๆ วนเช็กซ้ำถ้าไม่พบแหล่งที่ดีที่สุดไปเรื่อย ๆ อีกทีหนึ่งเหมือนกัน
สคริปต์นี้ ใช้เวลาเขียน/แก้อยู่นานพอสมควร เพราะมีโอกาสทดสอบจริงได้ไม่เกินวันละสองครั้ง แล้วแต่ละครั้งก็มีปัญหาใหม่ ๆ เข้ามาเรื่อย ๆ ตอนนี้ก็ใช้การได้ดีในระดับหนึ่ง แต่ก็ยังมีโอกาสเจอปัญหาใหม่ ๆ ได้อีก เช่นเมื่อเช้านี้ที่เจอมาหมาด ๆ ก็คือคำสั่ง rsync ตายกลางคัน ต้องเข้าไปสั่งแบบ manual เอา ปัญหานี้ยังไม่แก้ด้วยสคริปต์ ยอม manual ไปก่อน จนกว่าจะได้ไอเดียดี ๆ หรือเริ่มซ้ำซากพอจะให้เบื่อ :-)
หมายเหตุ: ในระยะหลัง แหล่ง sync หลักของ debianclub เริ่มเปลี่ยนมาเป็นไซต์ในเอเชียมากขึ้น โดยหนักที่ไต้หวัน รองลงมาอยู่นอกเอเชียไม่ไกล คือนิวซีแลนด์ ถัดไปเป็นฮ่องกงยามที่เธอลุกขึ้นมา update ตัวเอง และนาน ๆ จะได้ sync จากเกาหลีบ้างเหมือนกัน (แต่เฉพาะปัญหาเมื่อเช้านี้ ที่พึ่งของเรากลายเป็นไอร์แลนด์)
ป้ายกำกับ: debian, debianclub