太長不看:若是須要在Dockerfile的ENTRYPONNT中指定運行命令的用戶,用gosu
代替sudo
能夠避免某些信號處理上的邊界條件。不過這些邊界條件比較罕見,就算不用也沒多大關係git
docker官方文檔的Dockerfile部分,有一節講的是ENTRYPOINT。在這一節中,提到了若是在啓動腳本中須要指定運行命令的用戶,建議用gosu
代替sudo
,並給出了一個例子:github
#!/bin/bash set -e if [ "$1" = 'postgres' ]; then chown -R postgres "$PGDATA" if [ -z "$(ls -A "$PGDATA")" ]; then gosu postgres initdb fi exec gosu postgres "$@" fi exec "$@"
上面的腳本中,docker run
指定的命令會以postgres
用戶的身份執行。docker
所謂的ENTRYPOINT,正如其名,就是該鏡像的根命令。默認的ENTRYPOINT爲/bin/sh -c
,經過docker run
或CMD
指定的命令會做爲ENTRYPOINT的參數執行。舉個例子,docker run ubuntu:latest ls
就是執行/bin/sh -c ls
。有些時候咱們須要指定ENTRYPOINT的值,好比換成本身的包裝腳本。ubuntu
默認docker中的命令都是以root身份啓動的(由於默認只有root用戶)。不過你也能夠經過USER指令設置當前使用的用戶。某些時候,你可能須要在docker build
中使用多個用戶,好比上面例子中,安裝依賴須要root,運行程序時使用的是postgres。這時候就須要動態指定一個用戶身份。安全
docker文檔中建議,若是須要動態指定一個用戶身份,須要使用gosu
而非日常的sudo
。bash
然而文檔中並無解釋爲何。gosu
的項目主頁中也只提到gosu
避免了strange and often annoying TTY and signal-forwarding behavior
。(而後順便黑了下sudo
太過於複雜)。不過gosu
的測試用例透露了些蛛絲馬跡,能夠看出它認爲sudo
至少有兩點很差:ide
sudo
會做爲被受權的命令的父進程一直存在,直到該命令退出。post
sudo
模式下的HOME
環境變量還是用sudo
者原來的值。測試
能夠實證下這兩個指責:ui
~ sudo ps -o pid,ppid,cmd PID PPID CMD 12599 4281 sudo ps -o pid,ppid,cmd 12600 12599 ps -o pid,ppid,cmd ~ sudo env | grep HOME HOME=/home/lzx
這兩個現象確實存在,不過會形成什麼危害呢?若是真有鬼,夜路走多了天然會遇見。然而平時都是用着sudo
,也沒遇到什麼事呀。
咱們先來看看第二點,sudo
模式下HOME環境變量保存不變的事情。
這個事情涉及到sudo
的應用場景。sudo
用於扮演某個用戶來執行給定的命令,這一點相似於su
。我的認爲,sudo
跟su
第二大不一樣,在於sudo
是對使用者鑑權,而su
是對目標權限進行鑑權。假定你是sudoer,運行sudo
時你要輸入本身的密碼,也即證實本身有扮演的權限;而運行su
時,你要輸入的是要扮演的用戶的密碼,也即證實你有扮演的那個用戶的權限。因此sudo
會認爲,那你使用sudo
只是想臨時使用某一身份。既然如此,sudo
下HOME環境變量仍是原來的樣子,也不是什麼bug,而是個feature。若是你不認同這個feature,可使用sudo -H
。
再來看看第一點,sudo
做爲命令的父進程會一直存在。sudo
之因此退而不休,是由於它須要監控命令的輸入輸出。做爲一個很是關注安全性的程序,sudo
會重置本身的環境變量,儘可能以乾淨的環境來執行命令。不止如此,它還容許用戶定義安全策略,來處理命令的輸入輸出。不過有種狀況下,sudo
會直接exec給定的命令。那就是當用戶沒有指定安全策略,且執行的命令不須要佔用僞終端的時候。舉個例子,sudo sh -c 'sleep 20 &'
時,sudo
就真的再也不做爲父進程一直存在了(注意這裏我用了個sh來分割整條命令.若是直接輸入sudo sleep 20 &
,會被解析成後臺運行sudo sleep 20
)。不過這種狀況很是特殊,基本上能夠忽略。這一點跟上面那條不一樣,不存在一個改變該默認行爲的選項。
看來所謂的「annoying behavior」就是指這個了。不過平時用的時候從沒考慮過這個呀,爲何到了docker裏就不建議用呢?
緣由在於docker中處理signal的方式。不少程序,好比Apache和Nginx,容許用戶經過發信號的方式來控制程序的生命週期(重啓、關閉、中止,等等)。因爲docker把進程封裝了一層,若是想要給這些程序發信號,直接發給docker進程是不行的。那隻會影響docker自己的行爲。並且這些程序在docker裏面運行時,不可能意識到本身在一個獨立的容器裏。它們所報告的pid,跟外界的pid是不符合的。
爲了跟UNIX的信號機制和諧相處,docker另外提供了發送信號的接口:docker stop
和docker kill
。docker stop
會發兩撥信號,一個是SIGTERM
,另外一個是SIGKILL
。而docker kill
則是kill
的翻版。這兩個命令有個奇怪的地方,就是它們發送信號,歷來都只發給所謂的main process進程,也即ENTRYPOINT進程。若是該進程不會轉發信號(好比默認的/bin/sh -c
),目標進程就收不到信號,這個功能便廢了。而當咱們用sudo
啓動某個命令時,最終收到信號的會是sudo
進程,而不是那個命令。
那麼sudo
是否會轉發信號?答案是,若是能夠的話,sudo
會盡量地轉發信號。即便遇到了SIGTERM
這樣默認行爲是終止進程的信號,sudo
也不會直接終止,而會轉發出去。因此儘管多了個sudo
攔在路上,大多數狀況下,想要發送給目標進程的信號仍是能到達的。可是,SIGSTOP
和SIGKILL
兩個信號是沒法捕獲的,sudo
對此也無能爲力。SIGKILL
的話狀況還好,由於main process進程(這裏的sudo
)退出後,整個docker進程都會退出,無心中也達到了同樣的結果。不過SIGSTOP
只會讓sudo
停下來,結果該停的沒停,不應停的卻停了。
gosu的實現很簡單。它包括如下幾個步驟:
setgroup
setuid
setgid
設置$HOME
exec 目標命令
除了最後關鍵的兩步,其它跟sudo
差很少。