Quantcast
Channel: cc :: somkiat
Viewing all articles
Browse latest Browse all 1997

บันทึกการนำแนวคิด 12-factors มาใช้ในการพัฒนาระบบงานด้วยภาษา Go

$
0
0

แนว 12-factors app นั้นเป็นแนวคิดที่สร้างมาผู้สร้างระบบ Heroku
โดยถูกหยิบมาพูด หรือ ใช้งานมากขึ้นในยุคของ Cloud Native App
ประกอบไปด้วย 12 ข้อ และดูเหมือนว่าจะมีคนคิดเพิ่มมาอีก 3 เป็น 15 ข้อ
ผมเคยเขียน blog อธิบายไว้เมื่อนานมาแล้ว
มาครั้งนี้มีคำถามจากการแบ่งปันเรื่อง Docker + Go + Microservices
ว่ามีตัวอย่างหรือไม่ จึงทำการสร้างตัวอย่างพร้อมอธิบายสั้น ๆ ไว้ให้
เพื่อช่วยให้เข้าใจมากยิ่งขึ้น
มาเริ่มกันเลย

ระบบตัวอย่างจะใช้งานเครื่องมือเหล่านี้

  • ภาษา Go 1.22.3 ใช้งาน library Gin web framework
  • Docker => Docker compose

จาก 12-factors นั้นในข้อที่ 1 เรื่อง Codebase จะข้ามไปเลย
ไม่น่าต้องคุยอะไรมากกันแล้ว

เรื่องที่ 2 คือ Dependencies ของระบบ

ภาษา Go มี Go module ให้ใช้งานแบบง่าย
ดังนั้นเริ่มสร้าง project กันเลย
โดย library ที่ใช้งานอยู่ถูก config ไว้ในไฟล์ go.mod
จะใช้ library อะไรก็เพิ่มกันเลย
โดยในระบบตัวอย่างจะใช้งาน library ดังนี้

  • Gin web framework สำหรับสร้าง Web Server
  • Testify ช่วยให้การเขียน test ง่ายขึ้น
  • ส่วน logging ใช้ standard log ของ Go ไปเลย
  • ใช้งาน Corba แทน flags เพื่อช่วยจัดการ Go CLI ให้ง่ายขึ้น เพื่อสร้าง admin tasks ในข้อ 12 :: Admin processes เช่น ขั้นตอนของการ migrate database เป็นต้น

ตัวอย่างการสร้าง

[gist id="a5e5c04c1cf3bc0be1fe812fa6d14c94" file="1.txt"]

ส่วน code ในไฟล์ server.go ก็ทำการ copy มาจาก official web ของ Gin เลย

[gist id="a5e5c04c1cf3bc0be1fe812fa6d14c94" file="server.go"]

ในการจัดการ dependencies ของระบบนั้น
ไม่ได้สนใจเพียงสิ่งที่ code ใช้เท่านั้น
ถ้ามองให้กว้างขึ้นเรื่องของ OS และ software ต่าง ๆ ที่ใช้งานก็เช่นกัน
ควรที่จะคล้าย หรือ เหมือน production ให้ได้มากที่สุด
เพื่อช่วยให้เราเจอปัญหาได้รวดเร็วที่สุด
เพื่อแก้ไขปัญหาตั้งแต่เนิ่น ๆ ไม่ใช่ไปเจอตอนจะ deploy บน server
มักจะมีคำพูดที่ว่า "ผมทำการ run บนเครื่องผมได้นะ แต่พอเอาขึ้น server แล้วดันทำงานไม่ได้ ไม่รู้ทำไม"

ซึ่งจะตรงกับข้อที่ 10 :: Dev/prod parity
เป็นแนวปฏิบัติที่สำคัญของ Continuous Delivery นั่นเอง

ดังนั้นสามารถนำเอา Docker เข้ามาช่วยได้เลย
ด้วยการสร้าง Dockerfile + Multi-stage build เพื่อกำหนดสิ่งต่าง ๆ ที่จะใช้งานไปเลย เช่น

  • OS ที่จะใช้งาน รวมทั้ง version ด้วย
  • Go

เขียนได้ดังนี้

[gist id="a5e5c04c1cf3bc0be1fe812fa6d14c94" file="Dockerfile"]

ทำให้สามารถแยกขั้นตอนการ build และ run ออกมาตามแนวคิดข้อที่ 5 :: Build, release, run
เพื่อความสะดวกอาจจะเขียนขั้นตอนการทำงานต่าง ๆ ใน

เพื่อช่วยให้ทีมที่เกี่ยวข้องทำงานร่วมกันง่ายยิ่งขึ้น

เรื่องที่ 3 คือ Config ต่าง ๆ ของระบบงาน

โดยปกติเราต้องแยก config ออกจาก code หรือ ระบบงานอยู่แล้ว
มีแนวทางที่หลากหลาย เช่น

  • File config ในรูปแบบต่าง ๆ เช่น properties, json และ YAML เป็นต้น โดยใช้งาน library พวก viper และ godotenv เป็นต้น
  • ส่งผ่าน command line มาได้เลย เช่น flags หรือ ใช้งาน corba ในการจัดการ
  • ส่งผ่าน environment ของ OS นั้น ๆ หรือผ่าน Docker ได้เลย
  • ใช้งาน external config server ก็ได้ เช่น Vault, Consul และ Etcd เป็นต้น

ลองเลือกตาม use case ของระบบงานว่าแบบไหนที่เหมาะสม
หรืออาจจะใช้ร่วมกันก็ได้ เช่น .env ร่วมกับ Docker compose เป็นต้น

เรื่องที่ 5 คือ Backing services (resources) ช่วยให้ทำการเพิ่ม หรือ เปลี่ยนแปลงได้ง่าย

ในแต่ละ environment ด้วย โดยไม่ต้องทำการแก้ไข code ใด
ซึ่งทำงานร่วมกับข้อที่ 3 คือ Config นั่นเอง
ยกตัวอย่างเช่น database, messaging server และ external service เป็นต้น
ลดการผูกมัดให้น้อยลงไปอีก (Loose couple)

ยกตัวอย่างเช่นการใช้งาน PostgreSQL + Viper

[gist id="a5e5c04c1cf3bc0be1fe812fa6d14c94" file="database.go"]

ในการสร้าง database ก็ใช้งาน Docker มาช่วย
ก็ทำให้เราสามารถสร้าง database ได้เร็วขึ้น
และมีการทำงานคล้าย ๆ กับของจริงอีกด้วย

ส่วนข้อ 6,7 และ 8 ไม่น่าต้องอธิบายอะไรแล้ว น่าจะเป็นเรื่องพื้นฐาน

เรื่องที่ 9 คือ Disposability นั่นคือ start ให้เร็ว และ จบการแบบสมบูรณ์

เรื่องที่สำคัญมาก ๆ คือ gracefull shutdown หรือ การจบการทำงานแบบสมบูรณ์
ยกตัวอย่างเช่น
เราต้องการลบ process การทำงาน จะเกิดอะไรขึ้นกับระบบงานของเรา ?

สิ่งที่ระบบงานควรต้องทำคือ

  • ไม่รับ request ใหม่ ๆ
  • ตรวจสอบว่า มีการทำงานค้างไหม ถ้าใช่ ก็ทำให้เสร็จทั้งหมด
  • จบการทำงานแบบสมบูรณ์

ส่วนที่มักมีปัญหาคือ ระบบงานของเรารับสัญญาณของการ kill/terminate process จาก OS ไหม ?
เช่น SIGTERM และ SIGKILL เป็นต้น
จากนั้นก็ทำการสั่งให้รอก่อน เช่นการ waiting หรือ delay
หรือถ้าเป็น process ที่สำคัญมาก ๆ อาจจะต้องมีการจัดการ task/job
ใน database หรือ messaging ไว้ก่อน process อีกด้วย
เพื่อทำให้มั่นใจว่า task/job จะทำงานได้ครบและสมบูรณ์จริง ๆ

ตัวอย่างของการรับ SIGTERM และ SIGKILL ในภาษา Go เป็นดังนี้

เท่าที่เห็นมา ไม่ค่อยมีใครทำเท่าไรนัก
ทำการรอ 5 วินาที ก่อนที่จะ shutdown

[gist id="a5e5c04c1cf3bc0be1fe812fa6d14c94" file="server2.go"]

แต่ในการทำงานจริง ๆ ควรมีระบบ monitoring ที่ดี
เพื่อ monitor การทำงานของแต่ละ feature ด้วย ว่ามีทำงานเป็นปกติด้วยหรือไม่

อีกเรื่องที่ยังไม่พูดถึงคือ เรื่องที่ 11 คือ Logs

ตามจริงต้องพูดรวมเรื่องของ observability เลย ทั้ง

  • Alert system
  • Application metric
  • Distributed tracing
  • Log aggregation

แต่ในข้อนี้เน้นที่เรื่อง logs
ก่อนอื่นต้องทำการออกแบบของ log format ก่อน
ให้มีรูปแบบที่เป็นมาตรฐาน ว่าจะเก็บอะไรบ้าง
จากนั้นจึงมาสร้าง log ด้วย library ต่าง ๆ อีกที
โดยแนะนำควรมี format เป็น JSON เช่น logrus, zap และ log/slog เป็นต้น

แนวทางนั้นบ้างก็ใช้งาน middleware เพื่อ log ทุก ๆ request ที่เข้ามา
หรือเขียน log ในแต่ละส่วนของ function ที่ต้องการไปได้เลย

จากนั้นให้สร้างระบบ Centralized log ขึ้นมา เช่น ELK หรือ OpenSearch และ LGTM ของ Grafana เป็นต้น
สำหรับจัดเก็บ ค้นหา visualization และ alert system ต่อไป

น่าจะเขียนยาวเกินไปละครับ
จบเพียงเท่านี้ดีกว่า เป็น note สำหรับการแบ่งปันเรื่อง 12 factor ที่ผ่านมา

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


Viewing all articles
Browse latest Browse all 1997

Trending Articles