Serf เป็นเครื่องมือที่ช่วยให้ การจัดการ กับ ระบบคลัสเตอร์ทำได้ง่ายขึ้น ทำงานโดยผ่าน GOSSIP โปรโตคอล ซึง Serf ถูกออกแบบมาเป็นโครงสร้างที่ ไม่ซับซ้อน, ใช้ทรัพยากรน้อย, ทำงานได้เร็ว และ ยืดหยุ่น อยากให้เปรียบเทียบกับ Hearbeat แม้ฟังก์ชั่นอาจจะไม่ตรงตัวนักแต่จะทำให้เข้าใจได้ง่ายกว่า Serf พัฒนาโดย HarshiCorp ซึ่งเป็นคนพัฒนา Vagrant เขียนด้วย ภาษา Go (Golang)
สาเหตุที่ผมเลือกใช้ Serf ในการนำเอามาใช้งานคือ ต้องการหลีกเลี่ยงการออกแบบระบบให้กลายเป็น (Single point of failure) ขณะเดียวกัน ลดความยุ่งยากของระบบที่ทุกสิ่งทุกอย่างรวมอยู่ด้วยกัน (Centralized) นอกจากนั้นผมต้องการนำเอาระบบนี้มาใช้ใน Docker และ ขณะเดียวกันก็ลดความยุ่งยากของการ deployment ระบบ สรุปภาพรวมของความต้องการผมได้ดังนี้
- หลีกเลี่ยง จุดตาย (Single point Of Failure)
- หลีกเลี่ยง ระบบที่รวมอยู่ที่ศูนย์กลาง (De-centralized)
- อยากได้การทำงานของ ระบบที่ไม่ซับซ้อน เพื่อง่ายต่อการทำความเข้าใจ หรือ การเรียนรู้ และ ง่ายต่อการติดตั้ง ปรับแต่ง
- etcd ต้องการ Server etcd ไม่งั้นจะคอย report ERROR ไม่สามารถเชื่อมต่อกับ Node Datacenter ได้
- Zookeeper ระบบมีความซับซ้อน
- Dooserd ดูจาก repository ไม่ค่อยมีการ update และ issue fix มานานแล้วผมจึงใช้
- Google Kubernates มีขนาดใหญ่ไปและผมต้องการระบบอะไร ที่มันมีความซับซ้อนน้อยที่สุด
นั่นคือเหตุผลที่เลือกใช้ Serf ทีนี้มีดูกันว่าหัวข้อแต่ละหัวข้อมีอะไรบ้าง
ความสามารถหลักของ Serf
- ทำงานบนโปรโตคอล GOSSIP – GOSSIP เป็น โปรโตคอลสำหรับระบบกระจายสมัยใหม่ (Modern distribute) ซึ่ง Serf พัฒนาบนโปรโตคอลนี้ ทำให้มีฟังก์ชั่นการทำงานที่ครอบคลุม กับ ปัญหา หรือ ข้อจำกัดในระบบประมวลผลกลุ่ม (Cluster)
- ตรวจสอบความผิดพลาด (Failure detection) – กรณีเกิดความผิดพลาดขึ้น จะมีการตรวจสอบเหตุการณ์ของ Events ต่างๆ ในที่นี้คือ node failure เหตุการณ์ที่เกิดขึ้น กับ Node นั้น จะถูกส่งต่อไปยัง Node อื่นๆ โดยอัตโนมัติ ขณะเดียวกันก็มีการแจ้งการทำงาน และ ลบ Node ที่เกิดเหตุผิดพลาดขึ้นโดยอัตโนมัติ การแจ้งเตือนนี้ จะแจ้งข้อผิดพลาดไปยัง โหนดต่างๆ ทันที
- ปรับแต่งเหตุการณ์ได้ (Custom) Events – นอกจากเหตุการณ์มาตรฐาน (Standard Events) ที่ Serf จับเตรียมไว้ให้แล้ว เราสามารถสร้าง เหตุการณ์ต่างๆ เพื่อให้เหมาะสมกับการทำงานของ แอพพลิเคชั่น ของเรา หรือ ซอฟต์แวร์ Stack ที่เรามีอยู่ได้ จึงทำให้ Serf มีความยืดหยุ่นมากขึ้น
เหตุการณ์มาตรฐาน (Standard Events)
- member-join
- member-leave
- member-failed
- member-updated
- member-reap
แอพพลิเคชั่นที่ทำงาน คล้ายๆ กัน
ตัวอย่างการใช้งาน
ขอยกตัวอย่าง Case ของ Unison multi-host file replication และ ในที่นี้ผมจะพูดถึงเฉพาะ ในส่วนของ Serf จะไม่มีพูดถึง Unison software ซึ่งสามารถติดตามได้ใน บทความที่เกี่่ยวข้องต่อไป เพียงแต่ต้องการประหยัดเวลาในการทำงาน และ แยกหัวข้อออกมา เพื่อให้เข้าใจได้มากขึ้นในบทความที่ไม่ยาวเกินไป
ซอฟต์แวร์ที่นำเอามาใช้งาน
- Docker สำหรับ ทำคอนเทนเนอร์ แอพพลิเคชั่น (package software running environment) เพื่อสะดวกต่อการ deploy และ คอนโทรล สภาวะแวดล้อมของ แอพพลิเคชั่น
- fig สำหรับ builder docker container automation
- Serf สำหรับทำการ register service name (unison node)
- Unison เป็นแอพลิเคชั่นสำหรับทำการ sync ไฟล์ข้ามเครื่องแบบหลาย node (user-level file distribute replication)
- dnsmasq สำหรับทำการ register hostname ให้รู้จักโหนดอื่นๆ
- supervisord สำหรับดูแลบริหารจัดการ Service หลายๆ Service ที่ทำงานอยู่ใน Docker container
ปัญหา
เนื่องจาก Unison ทำการ replication files ไปยังโหนดหลายๆ โหนด โดยทำการเชื่อมต่อกัน เป็น Start topology (Hub, Spoke) ดูจากรูปด้านล่าง
ขณะเดียวกัน เมื่อเราสั่งให้ Docker ทำการ Link connection มันจะทำการ Link connection Docker container ที่เกี่ยวข้องเข้ามาเชื่อมโยงด้วยกัน และ ทำการเพิ่ม node แต่ละ node เข้าไปยัง host file ให้โดยอัตโนมัติ แต่ Docker node (Spoke) แต่ละ node ไม่สามารถทำการ Link กลับไปยัง node แรกได้ (Hub) การเพิ่ม node จึงเป็นเรื่องยุ่งยากที่ต้องไป register name เพื่อให้ node อื่นๆ หรือ Node แรกรู้จักได้
สิ่งที่ยุ่งยากเพิ่มเติมคือ ระบบ Network IP ของ Docker นั้นเป็น แบบ DHCP กรณีที่เรา ลบ node หรือ เพิ่ม node เข้าไป เราไม่สามารถรู้ได้เลย ว่าเราจะได้ เราจะได้ เบอร์ IP อะไร นอกจากกำหนดค่าให้เป็น Static IP และ หรือ ทำการออกแบบระบบให้ใช้ Dynamic DNS แบบ local ซึ่งก็จะทำให้ เกิดปัญหาขึ้น กรณี DNS service เกิดมีปัญหาขึ้นมา (Single Point Of Failure) ทำให้ต้องออกแบบระบบต้องซับซ้อนขึ้นไป ถ้าต้องออกแบบระบบให้มีระบบสำรองโดยอัตโนมัติ (Redundant service) และ ยิ่งถ้าออกแบบระบบให้เป็นแบบ Static DNS ต้องมาคอยเพิ่ม หรือ ลบ node และ ทำ name/ip mapping nodes (Hub, Spoke) ยิ่งจะสร้างความซับซ้อนเข้าไป
ทางแก้ ให้ ระบบทำการ register name และ ทำการ Mapping IP ให้เข้ากับชื่อ ของ Docker container ทุกหมดในครั้งแรกจาก Node ที่เป็น Hub ก่อน จากนั้นให้แต่ Node (Spoke) ทำการ mapping name/IP โดยอัตโนมัติทีหลัง โดยทั้งหมดจะทำโดยอัตโนมัติ เมื่อเปิดการทำงานของ ระบบขึ้น หรือ มีการ ลบ หรือ เพิ่ม Node แต่ละ Node ออกไป (Hub, Spoke) ระบบจะทำการ update ชื่อให้เองโดยอัตโนมัติ โดยเราไม่ต้องกังวล หรือ เสียเวลา กับ เรื่องการทำ Name/IP mapping อีกต่อไป
เนื่องจากผม ทำ pre-configuration ให้หมดแล้วอยู่ใน Docker container สามารถศึกษาการทำงาน และ การ config ได้จากใน folder source-code เลย (ดูหัวข้อ “วิธีการสร้าง Docker container และ วิธีใช้ )
ลำดับขั้นตอนการกำหนดค่า โดยรวม
- ดาวน์โหลด Serf Binary package จาก serfdom.io
- upzip ไฟล์ Serf Binary ที่ได้
- copy file bindary serf ไปยัง Folder ที่อยู่ใน path executable เช่น /usr/sbin เป็นต้น
ตั้งค่าการทำงานของ Serf
- Serf main configuration ซึงจะกำหนด directory config สำหรับ Serf
- Serf operation script ใช้สำหรับ กรณีมี Events ต่างๆ เกิดขึ้นเช่น มีการ ลบ Node, เพิ่ม Node ( Hub , Spoke ) จะให้ทำอะไร ซึ่งจะ script นี้จะไปเพิ่ม name record ของ dnsmasq ให้โดยอัตโนมัติ
- ไฟล์ Script นี้จะอยู่ที่ /etc/serf/handlers/
- สร้าง shell script สำหรับ เพิ่ม Node ( Hub , Spoke ) โดยอัตโนมัติ เมื่อ ระบบถึงสั่งให้ทำงานครั้งแรก
- ไฟล์นี้จะอยู่
- กำหนด Supervisord ให้สั่ง รัน Script นี้ครั้งแรก เท่านั้นเพื่อ register Node ที่เหลือจะเป็นการเรียกใช้ script ของ Serf เพื่อคอยจัดการเวลามีการเพิ่มโหนดลบโหนด
วิธีการสร้าง Docker container และ วิธีใช้ ผมสมมุติว่าเครื่องของผู้ที่จะทดสอบนี้เป็น ระบบปฏิบัติการ Linux Ubuntu นะครับ
ติดตั้ง python และเครื่องมือสำหรับทำการติดตั้ง Python
apt-get install python easy_install git
ติดตั้ง fig
sudo pip install fig
ทำการ clone Repository ThaiopenSource
git clone https://github.com/udomsak/thaiopensource-tut
สั่ง Build container
cd unison
fig build central
fig build node2
fig build node3
สัง Run container
fig up -d central
fig มันจะทำการ Link connection โดยทำการเพิ่ม record เครื่องอื่นๆ ที่เกี่ยวข้องใน Host file ให้โดยอัตโนมัติ
ดูว่าขั้นตอนการทำงาน ไม่มีอะไรผิดปกติ
docker ps
ควรจะแสดงผล ออกมาคล้ายรูปภาพข้างล่างนี้ โดยเราจะมีอยู่ทั้งหมด 3 container คือ
- central
- node2
- node3
เนื่องจากผม config http web สำหรับ ทดสอบการทำงานของ file synchronize เอาไว้ด้วย คือ port 8000,8001,8002 ดังนั้นต้องแน่ใจว่าในเครืองเรา ไม่มี port ดังกล่าวถูกใช้งานอยู่ แต่หากต้องการที่เปลี่ยน network port mapping ให้เข้าไปเปลี่ยนที่ไฟล์ fig.yml
ทดสอบ
docker ps ดูเลข container ID ของ container แต่ละตัว มาใช้เพื่อทำการ access contianer โดยไม่ต้องใช้ secure shell หรือใช้ snippet shell แบบนี้
$central=$(docker ps |grep central | awk '{print 1}')
$node2=$(docker ps |grep node2 | awk '{print 1}')
$node3=$(docker ps |grep node3 | awk '{print 1}')
docker exec -it {container ID} /bin/bash
docker exec -it $central /bin/bash
ping ทดสอบ connection
Container ชื่อ Central (Hub)
docker exec -it $central /bin/bash
ping node2
ping node3
Contianer ชื่อ node2 (Spoke)
docker exec -it $node2 /bin/bash
ping central
ping node3
Container ชื่อ node3 (Spoke)
docker exec -it $node3 /bin/bash
ping central
ping node2
จากที่เขียนมาน่าจะพอทำให้ทุกคนเข้าใจถึง การทำงานของ Serf และการประยุกต์ใช้งาน โดย Serf เองสามารถประยุกต์การใช้งานได้อีกมากจากความยืดหยุ่นของมัน อาทิเช่น การเพิ่มหรือลบโหนดใน Haproxy หรือ MySQL cluster, Web farm ฯลฯ
@Proteus family.