:::

在Docker中定期執行指令 / Run a Scheduled Job Inside a Docker Container

6月 19, 2023 , , 0 Comments Edit Copy Download

2023-0409-204459.png

總不會全天下只有我不能在Docker容器裡面執行crontab吧?


cron不能運作 / cron is not working

2023-0409-200614.png

https://forums.docker.com/t/cron-does-not-run-in-a-php-docker-container/103897 

根據大部分網路上說法,如果要在Docker容器裡面用cron做排程,主要是在Dockerfile裡面寫入以下指令:

# 複製crontab檔案到cron.d目錄
COPY ./docker/php-server/crontab /etc/cron.d/crontab

# 設定執行權限
RUN chmod 0644 /etc/cron.d/crontab

# 啟用cron排程任務
RUN crontab /etc/cron.d/crontab

而crontab的寫法可以參考下的寫法:

* * * * * root php /var/www/artisan schedule:run >> /var/log/cron.log 2>&1

然而,我在Debian bullseye版本裡,這個方法並不能正常運作。我有確認cron的確有載入我的設定,但cron就是不能在我設定的時間內正常運作。

https://pypi.org/project/python-crontab/

https://pypi.org/project/python-crontab/ 

如果系統上的cron無法正常運作,那我能不能用其他程式語言來做排程呢?

我嘗試了Python的python-crontab。它有種寫法可以在Windows環境運作,這可能表示它不需要仰賴Linux的cron,所以比較可能可以成功?但結果也是不行。

https://www.npmjs.com/package/node-cron

https://www.npmjs.com/package/node-cron 

我之前試過Node.js的Node Cron套件,它的確能照我所想的運作。不過只是為了跑個排程,還要額外安裝Node.js,好像有點太過複雜了。有沒有更簡單的方法呢?

有,而且還是基本程式設計就會學到的做法:等待與無限迴圈。


用Bash實作的排程腳本 / Create a scheduled job with Bash

要完成這個任務,我們有兩階段設定需要進行。

腳本 / Script 

第一個階段是是建立我們的排程腳本cron.sh。請將以下程式碼儲存到/cron.sh中,記得開啟執行權限(chmod +x cron.sh),程式碼內容如下:

#!/bin/bash
while :
do # Seconds
  sleep 30 # Your Scheduled job
  echo `date` > /tmp/date.txt
done

cron.sh的原理很簡單,首先用while執行無限迴圈。迴圈開頭會先用sleep等待30秒。30秒之後再輸出現在的時間(echo `date`)到/tmp/date.txt檔案中。

儘管認真的話也可以寫的像是crontab那樣,每分鐘都檢查一次分鐘、小時、天、月、週,但大多時候我只是要一段時間後執行某個腳本而已。沒錯,我就是在說certbot更新SSL憑證的這件事情。Let's Encrypt的憑證期限只有90天,我建議每30天就檢查一次憑證看需不需要更新。

背景程序 / Background process

有了腳本之後,接下來要讓它能夠執行,而且必須要讓它在背景執行,不能擋住原本Docker容器要執行的工作。

Docker容器必須要以一個特定的執行程序為主。當該程序中斷的時候,Docker容器就隨之關機。很多時候執行程序只是一個簡單的指令,但大多時候我們要在Docker容器裡面執行的工作都相當複雜,於是很多容器會建立docker-entrypoint.sh,或是鼓勵開發者將要執行的腳本放到 /docker-entrypoint.d/ 的目錄中。

https://stackoverflow.com/a/64205695

https://stackoverflow.com/a/64205695

NGINX就是一個很好的例子。我們可以在/docker-entrypoint.d放入我們希望在NGINX正式啟動之前所執行的腳本,而這裡就是啟動排程腳本cron.sh的位置。

請建立/docker-entrypoint.d/startup.sh,給予它執行權限(chmod +x /docker-entrypoint.d/startup.sh)。startup.sh的內容如下:

#!/bin/bash
/cron.sh &

/cron.sh後面的&表示是以背景程序執行。這樣Docker容器就不會被卡在/cron.sh的無限迴圈中,而能夠繼續啟動NGINX。

這樣我們就算是完成了。


小結 / In closing

用無限迴圈(while)跟等待(sleep)的做法簡單粗暴,但同時也存在著兩個排程時需要注意的隱憂:

  1. 排程執行的指令如果發生意外,整個排程會完全終止。最好加入「||」來避免這個問題。詳情請看Jayesh Bhoi的說明
  2. 排程結果缺乏明確的記錄機制,讓人難以確定排程是否有順利完成,還是是否還在進行中。這也只能自行手動設定。

https://blog.longwin.com.tw/2020/07/docker-crontab-how-to-set-2020/

https://blog.longwin.com.tw/2020/07/docker-crontab-how-to-set-2020/ 

許多人認為排程任務不應該寫在Docker容器內,而是要由主機伺服器(host)自己的排程來啟動Docker容器。這點我並不認同,因為這必須修改主機伺服器,進而提高整個設定門檻。除非是需要使用者客製化調整的設定,不然能包在Docker容器裡就能完成的工作,我會希望它盡可能就在Docker容器裡完成就好。


最後要來問大家的問題是:為什麼我的Docker容器就是不能跑cron啊?

請求高手解惑QQ