วันนี้เราจะมาสร้าง Task scheduler กันครับ ยกตัวอย่างโจทย์ที่ผมได้รับมาคือ “การที่ระบบจะต้องสามารถอัพเดทข้อมูลของตัวสถิติเว็บไซต์ทุก ๆ 1 ชั่วโมง เนื่องจากมีการคำนวณข้อมูลที่มากจึงไม่ได้ใช้การคำนวณใหม่แบบ Real-time ครับ” เราจะเห็นว่า Task แนวนี้มีเยอะครับ อาทิ
- การ Backup database ในทุก ๆ วัน
- การต้องอัพเดท หรือ Generate report ในทุก ๆ x ชั่วโมง
- และอีกมากมาย ที่ต้องเป็นงานที่ทำซ้ำ ๆ และ อยู่ใน Backgroud process ด้วย
สำหรับใครที่ยังอ่านแล้ว งง ๆ อยู่ ผมแนะนำว่าลองศึกษาเพิ่มเติมในส่วนของเรื่อง Task scheduler โดยการใช้งาน cron ได้ที่นี่น่ะครับ คุณจะได้สามารถอ่านที่ผมเขียนและเข้าใจได้ไม่ยากนัก
วิธีไหนดี?
วิธีการตั้ง Cron ในการทำให้ Task ใน container สามารถรันได้ (โปรเจกต์ที่เราพัฒนาอยู่ตอนนี้ใช้งาน Docker container เป็นหลักครับ) คร่าว ๆ ที่พอนึกออกตอนนี้มี 4 วิธีด้วยกันครับ
- ตั้งค่า Cron แบบทั่วไป ใน crontab ของเครื่องที่เป็น Host ของตัว Docker container อันนี้ก็สามารถหาวิธีทำได้ทั่วไปเลยครับ แต่วิธีนี้จะทำให้เวลานำเอางานชิ้นนี้ไป Deploy ที่ใหม่ เราก็ต้องเสียเวลาในการไป Config crontab ของเครื่องใหม่เอง อันนี้ทำให้เกิดการผิดพลาดได้สูงและเสี่ยงพอตัว
- ตั้งเครื่องมาใหม่อีกเครื่องนึง หรือ หา Cron service ที่สามารถยิง API เข้ามาหาในระบบเราได้ วิธีนี้ยังไม่ตอบโจทย์กับเราเนื่องจากเราไม่ต้องการให้ API endpoint นี้ public ออกไป อีกทั้งเรามองว่า Task ตัวนี้จะถูก Execute ด้วย command ครับ ดังนั้นวิธีนี้เลยตกไปก่อน
- ใช้การตั้งค่า Cron ภายใน container ของตัว Process application เลย ตอนแรกผมก็กะว่าจะใช้วิธีนี้ครับ เพราะ ตัวอย่างคือตอนนี้ผมใช้ php-fpm บน Alpine อยู่ ซึ่งตัวมันเองก็มี cron มาให้เราใช้ได้เลย แต่วิธีนี้ก็จะทำให้ Container นี้มันทำงานหลายหน้าที่เกินไปครับ ทั้งต้องคอย Serve ข้อมูลของระบบเรา อีกทั้งยังต้องทำงานในส่วนของ crontask อีกด้วย
- แยก Container ใหม่ขึ้นมาอีกอันทำหน้าที่เดียวในการตั้งค่าเกี่ยวกับ Crontask และลง Dependencies ต่าง ๆ แค่เท่าที่ใช้ในการรัน Task นั้น ๆ ขึ้นมา ซึ่งวิธีนี้ดีที่สุด ณ ตอนนี้ครับ มันทำให้เราจัดการง่ายและดูแลได้ หากเกิดปัญหาก็สามารถไปแก้ได้ในเฉพาะจุดนี้ หรือ หาก Container นี้มีปัญหามันก็ไม่ลากใครพังไปด้วย
จริง ๆ ก็อาจจะมีวิธีการแก้ไขเรื่องนี้ได้มากกว่านี้น่ะครับ เพื่อน ๆ คนไหนมีเทคนิคที่น่าสนใจลองแชร์กันได้ครับ สำหรับผมวันนี้ผมจะเลือกวิธีที่ 4 มาใช้ และ จะพาทุกคนทำไปด้วยกันครับ
สำหรับตัว alpine หรือ linux os ขนาดกระทัดรัดไซส์น่ารักกำลังดี ตอนนี้แทบจะทุกภาษามีคนทำไว้อยู่บน alpine แล้วทั้งสิ้นครับ ลองค้นหาดูได้เลยครับ ในวันนี้เราจะใช้เป็นตัว php-fpm:alpine กันครับ
เริ่มกันเลย
- ก่อนเริ่มครับ ทำการติดตั้ง Docker ให้เรียบร้อย สำหรับคนที่ยังไม่มีก็ติดตั้งเลยครับ จะได้ทดลองทำไปพร้อม ๆ กันเลย
- สั่งคำสั่ง clone repo นี้ ซึ่งผมได้เตรียมไฟล์ไว้แล้ว จะได้รวดเร็วในการทดลองยิ่งขึ้นครับ
git clone https://github.com/nitipatl/crontask-php-alpine
3. เมื่อคุณเข้าไปยังโฟลเดอร์นั้นจะพบกันกับไฟล์และโฟลเดอร์ย่อยดังนี้
.
├── Dockerfile
├── docker-compose.yml
├── readme.md
├── src
│ ├── log.txt
│ └── time.php
└── update_time.sh
update_time.sh
shell script ที่ทำหน้าที่ในการเรียกใช้งานสคริปต์ของ php
src/time.php
script php ซึ่งจะเขียนเวลาเป็น unixtime ออกมา
src/log.txt
คือไฟล์ที่เราต้องการนำเอาผลที่ได้จากการรัน shell script มาใส่ไว้
ให้ลองไล่เปิดดู Code แต่ละอันกันครับ สิ่งที่เราจะทำคือการสั่งให้ทุก ๆ 15 นาที ระบบจะต้องสั่ง update_time.sh
ให้ทำงานขึ้นมา
สำหรับ Dockerfile
คือการเพิ่มเติม command จาก based image ตามนี้
FROM php:7.2-fpm-alpine
WORKDIR /app
COPY update_time.sh /etc/periodic/15min/update_time
RUN chmod +x /etc/periodic/15min/update_time
COPY src .
ซึ่งนำเอา update_time.sh
ย้ายไปไว้ที่ /etc/periodic/15min
ครับ โดยสังเกตว่าชื่อไฟล์ script ที่เอาไปวางต้องไม่มี . (ดอท) ของนามสกุลอยู่น่ะครับ
ส่วน docker-compose.yml
มีการใส่ไว้แบบนี้ในส่วนของ command หลังจาก start containr ไว้แบบนี้ครับ เพื่อให้สั่งรัน crond และ -f คือ การทำงานแบบ foreground
command: crond -f
4. เราจะเริ่มการทดสอบได้โดย สั่ง
docker-compose up -d
เพื่อสั่งให้ Container ทำงานขึ้นมา
5. ทดลองสั่งรันดูว่าจะสามารถทำงานผ่าน cron ได้ไหม
docker-compose exec cron-php run-parts /etc/periodic/15min
ให้ทดลองเปิดไฟล์ src/log.txt
จะมีหน้าตาออกมาประมาณนี้ (คือมี unix time แสดงผลในบรรทัดใหม่ในทุก ๆ ครั้งที่เราสั่งรัน command ด้านบน)
แล้วแบบนี้มันมีการตั้งค่าเวลาแบบอื่น ๆ ไหม ถ้าไม่อยากได้ 15 นาที แต่เป็น 1 ชั่วโมงล่ะ?
$ docker-compose exec cron-php ls /etc/periodic
15min daily hourly monthly weekly
จะพบว่ามีโฟลเดอร์ของเวลาเบื้องต้นที่เราสามารถใช้งานได้เลยอยู่ตามนี้ครับ แต่ถ้ายังอยากจะตั้งเองแบบละเอียดขึ้น ผมแนะนำว่าลองดูจากตัวอย่างของที่นี่ ลองปรับจากสิ่งที่ผมเตรียมไว้แล้วลองเล่นดูเลยครับ
อ้างอิง
Running cron tasks on a Docker Alpine container
Running cron tasks on a Docker Alpine containerdevopsheaven.com