sudo or gosu

太長不看:若是須要在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 runCMD指定的命令會做爲ENTRYPOINT的參數執行。舉個例子,docker run ubuntu:latest ls就是執行/bin/sh -c ls。有些時候咱們須要指定ENTRYPOINT的值,好比換成本身的包裝腳本。ubuntu

默認docker中的命令都是以root身份啓動的(由於默認只有root用戶)。不過你也能夠經過USER指令設置當前使用的用戶。某些時候,你可能須要在docker build中使用多個用戶,好比上面例子中,安裝依賴須要root,運行程序時使用的是postgres。這時候就須要動態指定一個用戶身份。安全

docker文檔中建議,若是須要動態指定一個用戶身份,須要使用gosu而非日常的sudobash

然而文檔中並無解釋爲何。gosu項目主頁中也只提到gosu避免了strange and often annoying TTY and signal-forwarding behavior。(而後順便黑了下sudo太過於複雜)。不過gosu的測試用例透露了些蛛絲馬跡,能夠看出它認爲sudo至少有兩點很差:ide

  1. sudo會做爲被受權的命令的父進程一直存在,直到該命令退出。post

  2. 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。我的認爲,sudosu第二大不一樣,在於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 stopdocker killdocker stop會發兩撥信號,一個是SIGTERM,另外一個是SIGKILL。而docker kill則是kill的翻版。這兩個命令有個奇怪的地方,就是它們發送信號,歷來都只發給所謂的main process進程,也即ENTRYPOINT進程。若是該進程不會轉發信號(好比默認的/bin/sh -c),目標進程就收不到信號,這個功能便廢了。而當咱們用sudo啓動某個命令時,最終收到信號的會是sudo進程,而不是那個命令。

那麼sudo是否會轉發信號?答案是,若是能夠的話,sudo會盡量地轉發信號。即便遇到了SIGTERM這樣默認行爲是終止進程的信號,sudo也不會直接終止,而會轉發出去。因此儘管多了個sudo攔在路上,大多數狀況下,想要發送給目標進程的信號仍是能到達的。可是,SIGSTOPSIGKILL兩個信號是沒法捕獲的,sudo對此也無能爲力。SIGKILL的話狀況還好,由於main process進程(這裏的sudo)退出後,整個docker進程都會退出,無心中也達到了同樣的結果。不過SIGSTOP只會讓sudo停下來,結果該停的沒停,不應停的卻停了。

gosu的實現很簡單。它包括如下幾個步驟:

  • setgroup

  • setuid

  • setgid

  • 設置$HOME

  • exec 目標命令

除了最後關鍵的兩步,其它跟sudo差很少。

相關文章
相關標籤/搜索