Jaynarol Blog

Docker : จัดการโปรเจคทั้งระบบด้วย Docker Compose – Part 3 (Database)

สวัสดีครับ หายไปหลายวันกลับมาเขียนต่อได้สักที บทความนี้เป็นบทความต่อเนื่องนะครับ ใครที่หลงเข้ามายังไม่ได้อ่าน 1, 2 แนะนำให้อ่านทั้งหมดก่อนครับ

 

Database

ในบรรดาระบบที่ผมได้กล่าวไป ผมยกให้เจ้านี่แหละเป็นตัวป่วนที่สุด สมัยที่ยังไม่ได้ใช้ docker การ ship project ไปทั้ง db เป็นเรื่องที่ชวนปวดหัวมากๆเรื่องนึงเลยทีเดียว ยิ่งถ้าในทีมมีการเปลี่ยนโครงสร้าง db ขึ้นมานะ… โอเค… เราจะไม่เข้าเรื่องนั้น เดี๋ยวจะออกทะเลไปไกลกว่านี้ 555+ หัวข้อนี้ผมขอออกตัวไว้ก่อนนะครับ แนวคิดในหัวข้อนี้น่าจะเหมาะสำหรับโปรเจคขนาดเล็กและกลางเท่านั้น ซึ่งโปรเจคส่วนใหญ่ในโลกก็ขนาดประมาณนี้แหละครับ (โปรเจคระดับยักษ์นี่นับบริษัทได้เลย) ส่วนโปคเจคขนาดใหญ่ๆต้องใช้ทักษะและเครื่องมืออื่นๆเข้ามาช่วยซึ่งมีความซับซ้อนกว่านี้มากและแนวคิดอาจต่างออกไปโดยสิ้นเชิงครับ

 

Database ใน Docker?

vradb

เป็นคำถามที่ดีมากคำถามนึงที่ไม่ยกมาพูดคงไม่ได้ ซึ่งสำหรับผมคิดว่าคำตอบไม่มีถูกมีผิด ขึ้นอยู่กับรูปแบบและลักษณะงานที่จะนำไปใช้ครับ

จุดเด่นที่สุดของการใช้ Database ใน Docker ก็คือ “ความคล่องตัว” เราสามารถ Ship โปรเจคไปให้เพื่อนหรือ deployไป host ใหม่และสั่งรันทั้งระบบได้อย่างรวดเร็วและแน่นอนว่ายังมีข้อดีอื่นๆอีกมากมากครับ ไว้อ่านบทความนี้จบจะเข้าใจเอง 😀

ส่วนสิ่งที่ต้องแลกมาก็คือ “ประสิทธิภาพ” เพราะการที่ Database เราอยู่ใน Docker เท่ากับว่ามันมี Layer เพิ่มขึ้นมาอีกอย่างน้อย 1 ชั้นนั่นเองครับ และสิ่งที่ควรระวังให้มากโดยเฉพาะมือใหม่คือ “ข้อมูลหาย” มันจะเกิดขึ้นหากเราจัดการมันด้วยความไม่เข้าใจหรือวิธีที่ไม่ถูกต้องเท่านั้น ซึ่งผมจะอธิบายให้เห็นภาพในหัวข้อต่อไปครับ

ขอเสริมอีกนิดในมุมผมเองครับ ผมชอบที่จะเอา DB ไว้ใน Docker เพราะในชีวิตผมมีโปรเจคเยอะมาก (นับแค่ปีนี้ก็ปาไป 10 กว่าระบบแล้วครับ เล่นบ้าง จริงบ้าง 555+) บางครั้งผมอาจต้องกลับไปแก้งานเก่าของเมื่อต้นปีหรือปีก่อน คำถามคือถ้าระบบพวกนั้นโดนล้างออกจากเครื่องไปแล้วอย่างสมบูรณ์ ผมจะเสียเวลาแค่ไหนในการทำให้มันพร้อมใช้งานอีกครั้ง? ถ้าใช้ Docker ผมบอกได้เลยครับ แค่คำสั่งเดียว docker-compose up -d --build one hit kill ครับ

ผมเชื่อว่าทุกคำตอบไม่มีถูกผิด ทุกคนมีเหตุผลของตัวเอง มันอยู่แค่ว่าเราใช้เทคโนโลยีเพื่อช่วยให้เราสบายขึ้นจริงหรือเปล่า ถ้ามันทำให้เราลำบากลง แสดงว่ามาผิดทางแล้วครับ

 

แนวคิดการจัดการ Database

ก่อนเราจะใช้งานมัน เราต้องเข้าใจก่อนว่าเราทำไปทำไม นั่นคือสิ่งที่ผมจะบอกในหัวข้อนี้ครับ

จริงๆแล้วการใช้ Database ใน Docker นั้นเรียบง่ายมากๆไม่ต่างกับ Image อื่นๆ แต่เพราะปัญหาไม่ได้อยู่ที่การใช้ครับ มันอยู่ที่การจัดการต่างหากจึงทำให้บทความนี้ยาวขนาดนี้แหละครับ 555+

เข้าสาระครับ… ข้อมูลของเราที่อยู่ใน Database มันมีอยู่ 2 ร่างครับ

ร่างที่ 1 (Binary) คือข้อมูลที่มันใช้ในระหว่าง Runtime ซึ่งจะมีขนาดใหญ่ มีไฟล์แปลกๆเยอะแยะเต็มไปหมด

ร่างที่ 2 (Sql File) คือข้อมูลและคำสั่งเพียวๆในรูปแบบไฟล์ SQL (ซึ่งสามารถบีบอัดให้เล็กลงได้อีก)

 

แนวคิดเรียบๆในการจัดการโปรเจคของผมคือ ทุกไฟล์ในโฟลเดอร์โปรเจค ต้องเป็นไฟล์ที่มีความหมายและแก้ไขมันได้ เพราะผมชอบให้ระบบเป็น IaC (Infrastructure as code) ครับ แน่นอนว่า Database ผมก็ต้องอยู่ในรูปแบบที่อ่านได้เช่นกัน ดังนั้น ร่างที่ 2 (Sql File) จึงถือเป็นไฟล์หลักของ DB ในโปรเจค ใช้จัดเก็บช้อมูล ใช้ติดตามว่า DB มีการแก้ไขโครงสร้างไหม รวมถึงใช้แก้ไขแบบสดๆได้ด้วยครับถ้าจำเป็น

กลับมาพูดถึงร่างที่ 1 (Binary) ถึงจะเห็นว่ามันทั้งใหญ่และรกอย่างนั้นแต่มันก็มีความสำคัญมาก เพราะมันคือข้อมูลของเราทั้งหมดที่กำลังทำงานอยู่ครับ สังเกตในไฟล์ docker-compose.yml บรรทัดที่เขียนว่า
- ./tmp/mysql:/var/lib/mysql
นั่นหมายถึงมีการทำ Volume ให้ MySQL Container อ่านและเขียนไฟล์บนเครื่องหลัก (ข้างนอก Container) ตรงนี้สำคัญมากๆ เพราะถ้าเราไม่ทำ Volume และเผลอลบ MySQL Container ไปก็จบเกมเลยครับ ข้อมูลหายแน่นอน (จริงๆมันก็ไม่ได้หายไปถาวรนะครับ มันจะไปค้างอยู่ใน docker volume แต่การเอามันกลับมาค่อนข้างลำบาก ช่วงแรกๆเน้นง่ายๆก่อนดีกว่า) การทำ Volume ก็เพื่อให้ไฟล์ร่างที่ 1 (Binary) มันอยู่ที่เครื่องเราไม่ใช่ใน Container ครับ ซึ่งหากเผลอลบ Container ไปก็ไม่มีปัญหา เราสามารถสร้าง MySQL Container ตัวใหม่ชี้ Volume ที่เดิมแล้วทำงานต่อได้ทันที

 

Database Lifecycle

หัวข้อที่แล้วช่วยให้พอเห็นภาพและเข้าใจการทำงานของ Database แล้วใช่ไหมครับ หัวข้อนี้เราจะมาดูวิธีที่ผมใช้สำหรับจัดการมันกันครับ

docker-db

 

อธิบายจากภาพคือ

  1. Build เมื่อ MySQL Container เริ่มทำงาน หากมันตรวจสอบใน Volume แล้วพบว่าไม่มีข้อมูลร่างที่ 1 (Binary) มันจะทำงานไฟล์ Sql (อยู่ที่ ./build/mysql/) เพื่อสร้าง Database ขึ้นมาใหม่
  2. Dev พอทุกอย่างพร้อมใช้งานเราก็เดพกันตามปกติ ระหว่างนี้ DB เราก็จะมีการเขียนข้อมูลใหม่ๆเข้าไป ซึ่งไฟล์ข้อมูลร่างที่ 1 (Binary) จะอยู่ที่ ./tmp/mysql
  3. Pack เมื่อทำงานเสร็จผมจะเรียกใช้งานไฟล์ ./cmd/pack.sh ด้วยคำสั่ง . cmd/pack.sh (ทำงานที่ root ของ project) โดยในไฟล์นี้มีคำสั่งสำหรับ export ข้อมูลใน DB ไปเก็บไว้ที่ ./build/mysql/ (ทับไฟล์เดิมที่ใช้ตอนสร้างครับ)
  4. Ship ในบางครั้งผมอยากจะล้างให้โปรเจคสะอาดผมก็จะใช้คำสั่ง . cmd/clean.sh มันจะไปลบ ข้อมูลที่  ./tmp/mysql ซึ่งเป็นข้อมูลร่างที่ 1 (Binary) ทำให้ขนาดโฟลเดอร์โปรเจคเราเล็กลงมาก (อย่าลืม Pack ก่อนนะครับ ไม่งั้นข้อมูลหายหมดนะ) จากนี้เราจะส่งโปรเจคไปให้เพื่อนหรือโยนขึ้น production ก็สบายๆแล้วครับ

 

Database Trick

ด้วยรูปแบบการทำงานที่ผมได้กล่าวไป ทำให้เมื่อเราส่งข้อมูลไปที่เครื่องอื่น เราจึงส่งไปเพียง Sql ก็พอครับ ไม่ต้องแบกขยะข้อมูลร่างที่ 1 (Binary) ไปด้วยให้เกะกะ โฟลเดอร์โปรเจคเราจะเล็กมาก ดูแลง่าย มีแค่ขอมูลที่จำเป็นจริงๆเท่านั้นครับ

ถ้าใครใช้ git อยู่ เราสามารถใช้ความสามารถของ git ดู history ของไฟล์ sql ย้อนหลังได้สบายๆ ในทีมเล่นอะไรแผลงๆกับ DB เราก็จะรู้หมดทุกความเคลื่อนไหว เวลาเรา Checkout Rollback หรือย้าย branch เราจะย้ายไปทั้งระบบซึ่งรวมถึง DB ด้วย นั่นหมายถึงระบบเราจะพร้อมใช้งานตลอดเวลาทุกช่วงชีวิตของมัน มันแจ่มตรงนี้แหละครับ เวลาทำงานหลายคนก็แตก branch ของใครของมัน ทำให้ไม่ต้องกลัวว่าในทีมจะแก้ไขโครงสร้าง DB ไปยังไงบ้างเพราะอยู่คนละ branch กัน พอเสร็จแล้วค่อยมา merge เป็นต้นครับ

 

Link Container

เวลาเราเชื่อมต่อข้อมูลกันระหว่าง Container เราจะพยายามไม่ใช้ IP นะครับ เพื่อสร้างความยืดหยุ่นของระบบให้มากที่สุด

อย่างเช่นที่ไฟล์ ./src/index.php การเชื่อมต่อจาก PHP ไปยัง MySQL เราจะระบุชื่อ Host เป็นชื่อของ Container mysql ไปตรงๆเลยครับ ที่เราสามารถทำแบบนี้ได้เพราะ Docker มันจะเพิ่ม host กับ ip ให้เราที่แต่ละ Container โดยอัตโนมัติครับ

 

สรุป

จากข้อมูลทั้งหมดของบทความซีรีย์นี้ ผมคิดว่าน่าจะเป็นความรู้พื้นฐานที่ช่วยให้ผู้อ่าน เห็นภาพรวม, เข้าใจการทำงานของ Docker, การทำงานร่วมกันระหว่าง Container, เข้าใจวิธีการจัดการข้อมูล, ได้ทราบแนวคิดรวมไปถึงเทคนิคต่างๆ ซึ่งสามารถนำไปใช้งานและพลิกแพลงกับระบบงานรูปแบบอื่นๆได้ทั้งสิ้นครับ หรือแม้แต่นำไปต่อยอดเพื่อหาแนวทางที่ตัวเองถนัดก็ได้เช่นกันครับ เพราะแต่ละคนมีวิธีการทำงาน มีเหตุผล มีความชอบต่างกันไป (ถ้าเจอแนวทางดีๆแล้ว อย่าลืมมาบอกผมมั่งนะครับ 🙂 )

Docker ยังมีอะไรให้เล่นและเรียนรู้เยอะกว่านี้อีกมากมายครับไม่ว่าจะเรื่องการ Scale การควบคุม Node ต่างๆด้วย Docker Machine รวมไปถึงยังมี 3rd-party ที่อยู่รอบๆตัวมันเช่น Racher และอื่นๆอีก ดังนั้นแม้แต่ผมเองก็ยังต้องเรียนรู้ไปเรื่อยๆ หยุดไม่ได้เลยครับ

สุดท้ายผมขอทบทวนแนวคิดต่างๆที่ผมใช้ประจำไว้ตรงนี้อีกหน่อยดีกว่า

  1. ข้อมูลทุกอย่างในระบบต้องพยายามจบในโฟลเดอร์เดียวเสมอ (เพื่อความคล่องตัว)
  2. ทุกไฟล์ในโปรเจค ต้องเป็นไฟล์ที่มีความหมายและแก้ไขมันได้ (เพื่อความเข้าใจ)
  3. ใช้ Volume กับข้อมูลสำคัญเสมอ เช่น Source หรือ DB Binary  (เพื่อป้องกันข้อมูลสูญหายและง่ายต่อการแก้ไขในช่วงพัฒนา)
  4. ไม่ใช้ IP ในการเชื่อมต่อระหว่าง Container (เพื่อความยืดหยุ่น)
  5. โปรเจคต้องสามารถลบและสร้าง Container ใหม่ได้ตลอดเวลา และสามารถทำงานต่อได้ทันที (เพราะมันคือ Docker หากทำไม่ได้ แสดงว่าคุณหลงทางแล้ว)

 

ปิดท้าย

บทความนี้เขียนไปเขียนมารู้สึกว่าชื่อบทความน่าจะเป็น Docker และ Database มากกว่านะเนี้ย 555+

จบแล้วครับบทความ Docker Compose แน่นอนครับว่าบทความนี้ผมตั้งใจทำมาก หากข้อมูลส่วนไหนคลุมเครือ ผิดพลาด หรือตกหล่น รบกวนท้วงติงให้ผมแก้ไขเพิ่มเติมได้เลยนะครับ

กว่าจะได้เทคนิคขนาดนี้ผ่านการลองผิดลองถูก อ่านแล้วอ่านอีกไปเยอะมากๆครับ หวังแค่ให้ความรู้ของผมมันมีประโยชน์กับใครบ้างผมก็ดีใจแล้วครับ

แล้วพบกันใหม่ สวัสดีครับ