Month: October 2021

  • วิธีแก้ปัญหา docker container ไม่ start cron อัตโนมัติ

    ปัญหา

    เวลาสร้าง docker container เพื่อ run งานแบบอัตโนมัติ เราก็จำเป็นต้องใช้ cron แต่ว่า เจ้า docker container images เนี่ย จะเป็นรุ่นตัดทุกอย่างที่ไม่จำเป็นออกหมด เอาไว้ให้ start – run – stop แต่ถึงกระนั้น เราก็ยังอยากได้การทำงานแบบ cron อยู่ดี

    Reproduce

    สร้าง docker container จาก ubuntu:20.04

    docker run -d -it  --name kxtest ubuntu:20.04

    ตรวจสอบว่ามี crontab หรือไม่ ก็ไม่มี

    docker exec -it kxtest bash
    
    root@93c827f2f9fc:/# crontab -l
    bash: crontab: command not found
    root@93c827f2f9fc:/# 

    /var/log/syslog มีไม๊ ก็ไม่มี

    root@93c827f2f9fc:/# ls -l /var/log/syslog
    ls: cannot access '/var/log/syslog': No such file or directory

    systemctl มีไม๊ ก็ไม่มี

    root@93c827f2f9fc:/# systemctl
    bash: systemctl: command not found

    แก้ไข

    ติดตั้ง 3 อย่าง cron, systemctl, rsyslog แล้ว ขอแถม vim ด้วย

    apt update ; apt install -y cron systemctl rsyslog vim

    ติดตั้งแล้ว ลองตรวจสอบ ก็พบว่า crontab มาแล้ว, systemctl มาแล้ว ส่วน /var/log/syslog ยังไม่ได้สร้าง

    root@93c827f2f9fc:/# crontab -l
    no crontab for root
    root@93c827f2f9fc:/# systemctl      
    apt-daily-upgrade.service	loaded inactive dead	Daily apt upgrade and clean activities
    apt-daily-upgrade.timer	loaded inactive dead	Daily apt upgrade and clean activities
    apt-daily.service	loaded inactive dead	Daily apt download activities
    apt-daily.timer	loaded inactive dead	Daily apt download activities
    cron.service	loaded inactive dead	Regular background program processing daemon
    dmesg.service	loaded inactive dead	Save initial kernel messages after boot
    e2scrub@.service	loaded inactive dead	Online ext4 Metadata Check for 
    e2scrub_all.service	loaded inactive dead	Online ext4 Metadata Check for All Filesystems
    e2scrub_all.timer	loaded inactive dead	Periodic ext4 Online Metadata Check for All Filesystems
    e2scrub_fail@.service	loaded inactive dead	Online ext4 Metadata Check Failure Reporting for 
    e2scrub_reap.service	loaded inactive dead	Remove Stale Online ext4 Metadata Check Snapshots
    fstrim.service	loaded inactive dead	Discard unused blocks on filesystems from /etc/fstab
    fstrim.timer	loaded inactive dead	Discard unused blocks once a week
    hwclock.sh.service	loaded inactive dead	
    logrotate.service	loaded inactive dead	Rotate log files
    logrotate.timer	loaded inactive dead	Daily rotation of log files
    motd-news.service	loaded inactive dead	Message of the Day
    motd-news.timer	loaded inactive dead	Message of the Day
    procps.service	loaded inactive dead	Loads kernel parameters that are specified in /etc/sysctl.conf
    rsyslog.service	loaded inactive dead	System Logging Service
    
    20 loaded units listed.
    To show all installed unit files use 'systemctl list-unit-files'.
    root@93c827f2f9fc:/# ls -l /var/log/syslog
    ls: cannot access '/var/log/syslog': No such file or directory

    ลอง start cron และ rsyslog ดู ก็พบว่า /var/log/syslog มาแล้ว

    root@93c827f2f9fc:/# systemctl start cron
    root@93c827f2f9fc:/# systemctl status cron
    cron.service - Regular background program processing daemon
        Loaded: loaded (/usr/lib/systemd/system/cron.service, disabled)
        Active: active (running)
    root@93c827f2f9fc:/# systemctl start rsyslog
    rsyslog.service - System Logging Service
        Loaded: loaded (/usr/lib/systemd/system/rsyslog.service, disabled)
        Active: active (running)
    root@93c827f2f9fc:/# ls -l /var/log/syslog
    -rw-r----- 1 syslog adm 517 Oct 10 01:36 /var/log/syslog

    ทดสอบสร้าง cron

    root@93c827f2f9fc:/# crontab -e

    ทำความเข้าใจก่อน crontab เอาไว้สร้าง cron ส่วนตัวของแต่ละ user

    แต่ที่ /etc/cron.d/ นั้น เอาไว้สร้างสำหรับ system

    2 วิธีการสร้าง cron นี้แตกต่างกันที่ crontab ไม่สามารถระบุผู้ใช้ได้ แต่ใน /etc/cron.d/ นั้น สามารถระบุ user ที่จะ run ได้

    ต่อไป จะสร้าง cron ง่าย ๆ ให้ echo เวลา ทุก 1 นาทีลงไฟล์

    root@93c827f2f9fc:~# echo 'echo $(date "+%Y%m%d-%H%M%S") >> test.log' > ~/testcron.sh ; chmod +x ~/testcron.sh
    root@93c827f2f9fc:~# crontab -e

    แล้วใส่ บรรทัดนี้ลงไป

    * * * * * /usr/bin/bash ~/testcron.sh

    ดูใน /var/log/syslog แล้ว ก็พบว่า ทำงานได้

    Oct 10 01:56:39 93c827f2f9fc crontab[1631]: (root) BEGIN EDIT (root)
    Oct 10 01:56:42 93c827f2f9fc crontab[1631]: (root) END EDIT (root)
    Oct 10 01:57:01 93c827f2f9fc CRON[1638]: (root) CMD (/usr/bin/bash ~/testcron.sh)

    ดูผลใน -/test.log

    root@93c827f2f9fc:~# cat ~/test.log 
    20211010-015701
    20211010-015801
    20211010-015901

    เป็นอันว่า OK ต่อไป ดูว่า ถ้า restart docker แล้ว cron จะทำงานไม๊ ?

    ทดสอบ restart docker container

    ออกจาก bash ของ docker แล้วใช้คำสั่ง

    docker restart kxtest

    รอสัก 3 นาที แล้วใช้คำสั่ง

    docker exec -it kxtest bash -c "date ; cat ~/test.log"

    ผลที่ได้คือ อ้าว cron ไม่ทำงานต่อ

    Sun Oct 10 02:02:33 UTC 2021
    20211010-015701
    20211010-015801
    20211010-015901

    มาดูเหตุกันก่อน

    เราต้องไปดู รากเหง้าของ ubuntu:20.04 image

    https://hub.docker.com/_/ubuntu

    Docker ทำงานโดยจะทำเพียงคำสั่งเดียว การสร้าง docker image จะสร้างจาก Dockerfile ซึ่ง ubuntu:20.04 มี Dockerfile ดังนี้

    FROM scratch
    ADD ubuntu-focal-oci-amd64-root.tar.gz /
    CMD ["bash"]

    แสดงว่า docker container ที่ใช้ ubuntu:20.04 (ตัวอื่น ๆ ก็ต้องตามไปดูกัน) ก็จะเรียก Bash เป็นคำสั่งสุดท้าย ก่อนจะยืนเป็น Service ให้ใช้งาน

    ดังนั้น … เราก็ต้องไปทำให้ start cron service ใน .bashrc กัน

    ประเด็นคือ มัน start ด้วย user ไหน ? ลองใช้คำสั่งนี้

    docker exec -it kxtest bash -c "id"
    
    uid=0(root) gid=0(root) groups=0(root)

    ก็แสดงว่า สำหรับ image ตัวนี้ run ด้วย root นั่นเอง

    วิธีแก้ไข ให้ cron ทำงานทุกครั้งที่ restart docker

    เข้าไปใน docker container ด้วยคำสั่ง

    docker exec -it kxtest bash

    จากนั้น แก้ไขไฟล์ ~/.bashrc เพิ่มบรรทัดนี้ลงไปที่ท้ายไฟล์

    systemctl start cron.service

    save แล้วออกมาจาก container

    แล้ว docker restart

    docker restart kxtest

    ดูผลลัพธ์

    ดูว่า cron ทำงานเลยไม๊

    docker exec -it kxtest bash -c "date ; systemctl status cron"
    Sun Oct 10 03:01:17 UTC 2021
    cron.service - Regular background program processing daemon
        Loaded: loaded (/usr/lib/systemd/system/cron.service, enabled)
        Active: active (running)
    

    ดูว่า ผลการทำงานเป็นอย่างไร

    docker exec -it kxtest bash -c "date ; cat ~/test.log"
    
    Sun Oct 10 03:02:05 UTC 2021
    20211010-015701
    20211010-015801
    20211010-015901
    20211010-030101
    20211010-030201

    เย้ ทำงานแล้ว

    UPDATE

    สำหรับ กรณีใช้ docker image อย่าง JupyterHub หรือ มันก็คือ Jupyter Notebook แบบที่ทำงานได้พร้อมกันหลายคน (แยก Login) ผมลองแล้ว สามารถใช้วิธีนี้ครับ

    อ้างอิงจาก Dockerfile ที่ https://hub.docker.com/r/jupyterhub/jupyterhub/dockerfile

    จะเห็นได้ว่า CMD ตัวสุดท้ายคือ [“jupyterhub”]

    เราสามารถไปสั่งให้ start cron ได้โดย

    docker exec -it myjupyterhub bash
    vi $(which jupyterhub)

    แล้วแก้ไข Code เพิ่มนิดเดียว จากของเดิม

    import re
    import sys
    from jupyterhub.app import main
    if __name__ == '__main__':
        sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
        sys.exit(main())

    เป็น

    import re
    import sys
    from jupyterhub.app import main
    if __name__ == '__main__':
        sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
        import subprocess
        subprocess.run(["service", "cron", "start"])
        sys.exit(main())

    จากนั้น restart ได้เลยครับ

    หวังว่าจะเป็นประโยชน์ครับ