DOCKER FAQ系列(二) (基礎篇)

寫在前頭

docker已經逐漸成爲測試人員的基本技能了,在我們使用docker的時候,掌握一些原理,分析一些問題原因對我們能力提升有巨大的幫助。


docker裏目錄掛載是什麼意思,爲什麼要掛載

Docker容器啓動的時候,如果要掛載宿主機的一個目錄,可以用-v參數指定。

譬如我要啓動一個centos容器,宿主機的/data目錄掛載到容器的/home目錄,


可通過以下方式指定:


掛載目錄的意思,將宿主機的某個目錄掛到容器裏的某個位置(爲什麼用  這個詞語,是因爲 linux的文件系統是以 / 爲根,各級目錄爲子葉向下遞進的一個樹形結構;


如果我們有塊磁盤,一般是先格式化,然後將該磁盤  到某個位置。)

有些人理解的掛載,是說把容器裏的目錄掛出來,某方面講這種理解是有錯誤的,也許你懂了,可是也許你就理解篇了,因爲你顛倒了主次。


首先想,現有宿主機還是容器,當然是宿主機。

其次,宿主機的文件目錄先有,還是容器裏文件目錄先有? 當然宿主機。(有人可能疑問,-v /data:/home 如果 /data 在宿主機不存在也會自動創建一個,依然會在容器創建之前)


我們用命令驗證下:


看到 source 和destination應該明白了吧?

  • 特別注意:

掛載的時候兩個目錄都最好使用絕對路徑,如果使用了相對路徑發現不是自己想要的結果,可以通過·docker inspect yourcontainer id 檢查 Mounts內容


那麼爲什麼要掛? 理由很多

  • 容器被創建時候,宿主機給他分配了一塊存儲空間,這塊存儲空間由該容器自己擁有,當我們銷燬該容器的時候,該存儲空間也會被回收

  • 當我們需要查方便的檢查日誌的時候(容器的命令可能會限制,一些方便的命令容器可能不存在)

  • 當我們需要持久化某些容器內有狀態數據的時候

  • 當我們需要方便的修改某些容器內配置參數的時候


爲什麼綁定了宿主的文件到容器,宿主修改了文件,容器內看到的還是舊的內容啊(蝸牛語錄)


在綁定宿主內容的形式中,有一種特殊的形式,就是綁定宿主文件,既:


在 myapp.ini 文件不發生改變的情況下,這樣的綁定是和綁定宿主目錄性質一樣,同樣是將宿主文件綁定到容器內部,容器內可以看到這個文件。但是,一旦文件發生改變,情況則有不同。


簡單的文件修改,比如 echo "name = jessie" >> myapp.ini,這類修改依舊還是原來的文件,宿主(或容器)對文件進行的改動,另一方是可以看到的。


而複雜的文件操作,比如使用 vim,或者其它編輯器編輯文件,則很有可能會導致一方的修改,另一方看不到。


其原因是這類編輯器在保存文件的時候,經常會採用一種避免寫入過程中發生故障而導致文件丟失的策略,既先把內容寫到一個新的文件中去,寫好了後,再刪除舊的文件,然後把新文件改名爲舊的文件名,從而完成保存的操作。從這個操作流程可以看出,雖然修改後的文件的名字和過去一樣,但對於文件系統而言是一個新的文件了。換句話說,雖然是同名文件,但是舊的文件的 inode 和修改後的文件的 inode 不同。


如上面的例子可以看到,經過 vim 編輯文件後,inode 從 268541 變爲了 268716,這就是剛纔說的,名字還是那個名字,文件已不是原來的文件了。


而 Docker 的 綁定宿主文件,實際上在文件系統眼裏,針對的是 inode,而不是文件名。因此容器內所看到的,依舊是之前舊的 inode 對應的那個文件,也就是舊的內容。


這就出現了之前的那個問題,在宿主內修改綁定文件的內容,結果發現容器內看不到改變,其原因就在於宿主的那個文件已不是原來的文件了?


這類問題解決辦法很簡單,如果文件可能改變,那麼就不要綁定宿主文件,而是綁定一個宿主目錄,這樣只要目錄不跑,裏面文件愛咋改就咋改?。


Docker 日誌都在哪裏?怎麼收集(蝸牛語錄)

Docker 引擎日誌 一般是交給了 Upstart(Ubuntu 14.04) 或者 systemd (CentOS 7, Ubuntu 16.04)。前者一般位於 /var/log/upstart/docker.log 下,後者一般通過 jounarlctl -u docker 來讀取。不同系統的位置都不一樣,SO上有人總結了一份列表,我修正了一下,可以參考:


爲什麼我啓動容器的時候,容器沒多久就退出了?

比如下面一條命令:


很多人運行上面這個命令後,發現兩件事

  • 運行該命令後,命令行進入了容器,此時另起shell 運行 docker ps 會發現容器是up的

  • 此時如果輸入exit 會退出容器,然後在用docker ps ,容器不見了,docker ps -a 發現該容器exited了


爲什麼?

  • 容器的運行需要一個主進程,當主進程退出後,容器的生命週期完結 上述我們用 /bin/bash作爲入口命令,啓動了一個bash進程,由於該進程會進入交互式

  • 當處於交互式狀態時候,其實這個進程是不會主動退出的,所以容器會一直運行, 但是當你退出了這個交互進程(比如手動執行exit)容器也就down了

怎麼保證我啓動一個容器是常駐的,不會退出的

  • 首先你得有一個不會主動退出的前臺進程,一般是服務主進程

  • 配合 docker run -d 選項,d 是daemon的意思

如:


當按照上述的方式啓動ubuntu容器

  • 不會進入容器

  • 容器不會退出,因爲/bin/bash 交給了daemon託管


容器的 ENTRYPOINT 和 CMD 有啥區別,怎麼用

CMD

這兩個東西可能是大家最容易忽略的了,很多人會說這倆效果一樣,可出現問題的時候,你會發現沒明白這兩者區別的時候是會有多難受

如果大家會寫Dockerfile, 那麼這兩者你就一定要搞清楚


首先看CMD,官網解釋地址: https://docs.docker.com/engine/reference/builder/#cmd


一定要看官網(其它的總結,除非你有辨別能力,否則不要信)

這點cmd理解我不準備扯,我準備帶大家翻譯:


首先說了, CMD的形式結構大概有三種

  • CMD [“executable”,”param1”,”param2”] (exec 形式, 這是首選的)

  • CMD [“param1”,”param2”] (這樣寫是爲作爲 ENTRYPOINT的參數的)

  • CMD command param1 param2 (shell 形式)


然後下一句說了, 在Dockerfile裏只有一個CMD生效且是最後一個

再下一句說明了CMD 主要目的: 是提供正在執行的容器提供默認值,這些默認值包括可執行. 或者也可以忽略執行,在某些場景你同樣還必須配合指定一個ENTRYPOINT的聲明


這句翻譯就有點拗口了

那麼聯繫下我們的docker build過程,我們根據Dockerfile可以打成image , 再者,打好的image可以被新一個image from , 那麼舊者的image的 默認CMD 和新者的CMD 誰會執行? 你是否忽然明白上面說的 包括可執行,和忽略執行?

那麼爲什麼某些場景我們必須配合指定一個 ENTRYPOINT的聲明呢?一會我們介紹ENTRYPOINT再說


下面三個Note ,提醒我們要注意:

  • 假如CMD 被用來提供 ENTRYPOINT的默認參數時候, CMD 和 ENTRYPOINT 必須同時在Dockerfile裏指定,且必須是 JsonArray的形式

  • 當你提供JsonArray的時候 注意不要使用單引號,這是json規範

  • 不像Sehll形式, exec形式不會喚起一個 command shell. 這意味着正常的shell處理過程不會發生。 舉個例子: CMD [“echo”,”$HOME”] 不會做變量的代替(我們啓動shell 會自動加載一些環境變量,這是個代替的過程.$HOME代替/root這樣)。假如你想執行一個 shell形式的命令或者直接運行一個shell 使用 CMD [ “sh”, “-c”, “echo $HOME” ].即可。 當時用 exec形式的並且直接執行一個shell, 那麼這裏的環境變量的代替是shell做的不是 docker本身。


官網文檔接着結合實例來說了CMD的具體用法:(注意我們這裏說的 CMD 是指Dockerfile裏寫的CMD)


當我們使用 exec形式 和 shell形式時候,當我們運行鏡像啓動一個容器時候,如果不額外指定,是會默認執行CMD裏配置的。

當你在Dockerfile指定這樣的:


那麼這裏 CMD後面的指令將會默認作爲 /bin/sh -c的參數執行,即在shell中執行


如果你希望你的CMD 不使用shell執行, 那麼你必須如上所說,將你的命令表達成 json array形式,並且還要指定執行體的全路徑。這是CMD首選的使用形式。


任何執行體的參數應該獨立用字符串在json array中表示如下:


如果你想你的鏡像每次啓動成容器時執行一個相同的執行命令,你應該考慮使用 ENTRYPOINT 和CMD 結合使用

  • 注意: 如果用戶在使用docker run啓動容器的時候 指定了額外的命令參數, 它會覆蓋你 Dockerfile寫的CMD

  • 注意: 不要對CMD和RUN的區別感到困惑, RUN 實際執行了一個命令並且提交成結果;CMD在build image期間並沒有做任何執行操作,但是它爲了image 啓動容器時候提供了默認執行命令


ENTRYPOINT

ENTRYPOINT 有2種形式:

  • ENTRYPOINT [“executable”, “param1”, “param2”] (exec form, preferred)

  • ENTRYPOINT command param1 param2 (shell form)

ENTRYPOINT 允許你把你的容器啓動時候去執行某件事情,讓你的容器變成可執行的。


exec形式

舉個例子下面這條命令會默認啓動nginx,並監聽在80(注意命令並沒有指定額外的運行參數,只是運行了一個nginx image)


docker run <image> 後面若接了命令行參數, 他會默認追加到 exec 形式的ENTRYPOINT 之後,並且會覆蓋 CMD指定的參數。


這允許給 entrypoint傳遞參數。

比如 docker run <image> -d 將會把-d 傳給 entrypoint

你可以在命令行中使用 docker run --entrypoint 去覆蓋image本身的 ENTRYPOINT


shell形式

在Dockerfile 中指定shell 形式的 ENTRYPOINT


exec形式ENTRYPOINT 舉例

您可以使用exec形式ENTRYPOINT來設置相對不變的默認命令和參數,

然後使用其中任何一種形式CMD來設置更可能更改的其他默認值。


以上命令使用 docker build . --tag top

docker run -it --rm --name test top -H

官網doc這裏使用這裏的命令是點迷惑, 這裏的top是它的鏡像名,而不是入口命令。注意自己辨別下。


此時你可以另起shell 使用docker exec 查看上述容器進程:

執行: docker exec -it test ps aux


你會發現 首先: Dockerfile 裏的CMD 沒有生效 -H 被傳給了 ENTRYPOINT作爲參數,並且Dockerfile裏的exec形式的 ENTRYPOINT的啓動的容器,PID 爲1

Dockerfile裏的 ENTRYPOINT 要想在 docker run期間 重寫覆蓋,你可以使用docker run的 –entrypoint 來覆蓋,另外 exec form注意也要用json array,不能有單引號。


此外和shell命令不同,exec 也不會調用shell,那麼你執行一個命令時候,像shell那種環境變量的替代是不會發生的,如果你希望用shell 執行,你可以:


shell形式的ENTRYPOINT示例

當你在Dockerfile裏指定了 shell形式的ENTRYPOINT , 首先entrypoint會調用 /bin/sh -c . 這種形式會有環境變了的替代過程,並且會忽略 Dockerfile裏的CMD 指令,以及 docker run的命令行參數


我們看如下的例子:

Dockerfile裏:


當你執行這個鏡像時候,你會看到PID 1進程


這時候你可以乾淨的停止容器


假如你的ENTRYPOINT 沒有寫 exec ,如下Dockerfile


此時, top -b 將會作爲 /bin/sh -c 的參數執行, top -b 將不會是 PID爲1的主進程


首先看到 CMD裏的 -H 會被忽略,其次 主進程 PID=1是 /bin/sh -c

這時候我們停止容器(docker stop test)的時候,會是一個不乾淨的停止操作,即我們使用 docker stop test 其實是關閉了top -b的父進程 /bin/sh -c


即 top -b 進程是接受不到 docker stop 發出的 SIGTERM 信號的

所以 top命令還是會存在的,所以 stop不了容器, docker stop 在超時時會強制對容器發出 SIGKILL 信號幹掉容器。


說到這,總結下 ENTRYPOINT 和 CMD

  • 首先 一個Dockerfile 至少指定 CMD 和 ENTRYPOINT 中任何一個

  • 當你希望定義一個可執行的容器時候,你應該考慮使用ENTRYPOINT

  • CMD 應該被定義爲 ENTRYPOINT的默認參數。 或者是執行那種臨時性的命令

  • CMD 當你啓動容器提供了一些可選參數,CMD會被覆蓋


下表說明了 CMD/ENTRYPOINT的結合(Dockerfile裏):


從上表可以得出幾個重要結論:

  • 當Dockerfile裏 ENTRYPOINT 裏使用了shell形式的時候, CMD 無論在哪指定都會被忽略(包括從命令行docker run輸入 cmd)


  • 當image沒有指定默認的 ENTRYPOINT的時候, CMD將發揮作用,而一旦指定了ENTRYPOINT,CMD會作爲 ENTRYPOINT的參數或者完全被 ENTRYPOINT 覆蓋


  • 另外在kubernetes中,也有另外兩個和這裏的CMD/ENTRYPOINT對應,它們叫 command和args 詳情見 https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/ 後續在K8S的相關的介紹中會給大家整理。


發佈了116 篇原創文章 · 獲贊 60 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章