不曉得該怎麼替本篇內容命名文章標題,所以最後索性直接拿關鍵字當作標題,就讓我偷懶一回吧。
最後還是改了一個標題,因為之後寫的文章可有能會提到這一篇。
公司新添購了一台 Synology NAS 型號為 DS1515+,明眼人一看這型號應該就明白,這是一台可以運行 Docker 的 Synology NAS。Synology NAS 其實原本就已經可以安裝各種套件,讓使用者享受一機多用的好處。現在變本加厲能直接運行 Docker,這根本是意圖叫人不要把 NAS 當成純粹的 NAS 來使用。 (若想要查詢哪些 Synology NAS 可以運行 Docker 可以參閱 Synology 網站。)
既然廠商如此用心良苦,意圖叫人惡搞善用他們家的產品,我們當然恭敬不如從命。基本上既然可以運行 Docker,那麼其實大部分 Server 能做的事情都有機會可以轉移至 NAS 上,所以首先我便打算在上面運行一個 Container 來取代既有的 Raspberry pi。
預定的使用情境大概如下,我們有一檯 Raspberry pi 在運行 cron,它會替我們定時定期執行一些工作,工作的程式是用 php 編寫,透過 php cli 來執行。由此若要轉移這個工作,所需最簡單的環境就是一個有 php cli + cron 的 Container 即可。
因為已經有許多人分享如何在 Synology NAS 上安裝 Docker,因此本文就不再重述,只能說安裝流程非常簡單,只要滑鼠點幾下即可,沒有任何難度可言。使用 Docker 也與安裝一樣簡單,只要透過管理界面點幾下,很容易就能下載 Images,並運行 Container。
如此一來剩下的麻煩事就只有該如何做出一個有 php cli + cron 的 Container 呢?
其實要取得 php cli 的 Container 很容易,在 Docker Hub 的 php official repository 即可找到官方做好的各種本版 php cli,只要透過指令 docker pull php:5.6.14-cli
就能輕易取得。但這次的狀況不同,我們是要在 NAS 上運行 Docker,而 NAS 主要是透過內建的圖形界面來管理 Docker,因此決定改變做法,先自行建立 Dockerfile,再讓 Docker Hub 幫我 Automated Build,最後我只要在 NAS 的界面上簡單操作,直接下載並運行所需的 Container。
首先我建立了一個基本的 Dockerfile 如下,直接引用 php:5.6.14-cli 當作 base image,解決 php cli 這項需求。
FROM php:5.6.14-cli
MAINTAINER ChengWei <chengwei@theqwan.com>
CMD ["/bin/bash"]
接著處理 cron 的部分,在這個 php:5.6.14-cli 的 image 中本身是沒有安裝 cron 的,只好自行安裝,當然為了追求讓 image 容量不要過大,所以不安裝其他多餘的 recommends package。
RUN apt-get install -y --no-install-recommends cron
下一個問題是,即便安裝了 cron,但 Container 在啟動之後,並不會自己驅動 cron 開始運行,我們需要有另外幫手來確保 cron 會被自動地啟動,而這個幫手就是 runit 啦!
其實如果你有思考過「該如何讓 Container 可以像 Server 一樣的使用,讓它可以同時運行多個 Service」這個問題,那麼你應該有看過這篇文章《YOUR DOCKER IMAGE MIGHT BE BROKEN without you knowing it》,同時應該也注意過這個特別的 Image - phusion/baseimage。細節不多說文章中都有說明,總之他們也有運用到 runit 讓 Container 上可以同時運行多個 Service。所以這裡我也要安裝 runit 來幫我啟動 cron。
RUN apt-get install -y --no-install-recommends runit
除了安裝之外當然還要設定 runit,讓它知道要如何幫我啟動 cron。
RUN echo '#!/bin/sh' > /etc/service/cron/run
RUN echo 'exec /usr/sbin/cron -f' >> /etc/service/cron/run
RUN chmod -R 700 /etc/service/cron/
CMD ["runsv", "/etc/service/cron"]
上面的前三行是建立一個 runit 所需的路徑與該 service 的 run file,最後一行 CMD 則是當此 Container 啟動時,就去運行 runit 來啟動 cron。
到此基本上 php cli + cron 這兩項需求已經滿足,剩下就針對一些小地方進行微調,首先是「時區」設定,要將時區調整成 Asia/Taipei,這樣我才能用最簡單的方式來設定工作執行的時間,難道有人明明身處台灣,但在設定 cron 的時候卻要先自己換算一下現在是美國時間幾點嗎?
RUN rm /etc/timezone
RUN echo "Asia/Taipei" > /etc/timezone
RUN chmod 644 /etc/timezone
RUN dpkg-reconfigure --frontend noninteractive tzdata
「時區」處理完畢後,再來清除一些多餘的檔案,像是 cron 安裝之後會產生一些基本的檔案,像是 cron.daily 等,這些檔案是我不需要的,因為我要執行的工作我會通通都寫入 /etc/crontab 之中,所以要將這些多餘的檔案刪除。
RUN rm -f /etc/cron.daily/standard
RUN rm -f /etc/cron.daily/upstart
RUN rm -f /etc/cron.daily/dpkg
RUN rm -f /etc/cron.daily/password
RUN rm -f /etc/cron.weekly/fstrim
到此所有的微調都完成了,在自己手動 docker build 之後,確認這個 Container 沒有問題可以正常運行。最後再來追求 Docker Image 的 Size 最小化,因此將整個 Dockerfile 修正成下面的模樣。
FROM php:5.6.14-cli
MAINTAINER ChengWei <chengwei@theqwan.com>
RUN rm /etc/timezone \
&& echo "Asia/Taipei" > /etc/timezone \
&& chmod 644 /etc/timezone \
&& dpkg-reconfigure --frontend noninteractive tzdata \
&& apt-get update \
&& apt-get install -y --no-install-recommends runit \
&& apt-get install -y --no-install-recommends cron \
&& mkdir /etc/service/cron \
&& echo '#!/bin/sh' > /etc/service/cron/run \
&& echo 'exec /usr/sbin/cron -f' >> /etc/service/cron/run \
&& chmod -R 700 /etc/service/cron/ \
&& chmod 600 /etc/crontab \
&& rm -f /etc/cron.daily/standard \
&& rm -f /etc/cron.daily/upstart \
&& rm -f /etc/cron.daily/dpkg \
&& rm -f /etc/cron.daily/password \
&& rm -f /etc/cron.weekly/fstrim \
&& apt-get purge -y --auto-remove \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
CMD ["runsv", "/etc/service/cron"]
(其實還可以追求 Size 更小,例如不要使用 php:5.6.14-cli 當 base image,而是改用其他較小的 image。)
接著就把 Dockerfile 送上 GitHub 並且與 Docker Hub 建立 Services 串連。
然後在 Docker Hub 建立一個 Automated Build 的 Repository 。
完成上述所有的步驟後,我就能輕輕鬆鬆的在 NAS 中下載並運行所需的 Container,附帶一提在 NAS 中我設定要將 NAS 裡的特定 file 映射至 Container 的 /etc/crontab,如此一來即可直接修改此 file 來管理 cron 要執行的工作啦。
文末附上此 Image 的 GitHub (只有一個檔案啦) 與 Docker Hub。