最近使用docker commit碰到了一個小坑,簡單記錄在此。
事情是這樣的,我們組的一個同學想要修改某個image中的程序啟動參數,首先他使用某個鏡像運行了一個容器(docker run image:tag /bin/bash),然后在容器內修改了程序的啟動參數,最后使用這個容器生成它需要的image(docker commit containerID),通常來說這套操作是沒有任何問題的,可是呢這個image一發布立刻就退出了… 根據日志能夠觀察到的現象是發布的pod起來后立即退出并且沒有打印任何日志,但是在我們內部容器平臺每個pod中都會有一個引導程序負責啟動用戶程序同時打印一些日志到stdout和stderr,開始分析問題時我們把關注點主要集中在用戶程序的修改上(誤以為改錯了某些參數),忽略了stdout沒有輸出任何日志的這個關鍵信息。經推測沒有日志輸出很大的可能性是執行命令退出了,順著沒有日志輸出這個關鍵信息排查,很快也定位到了問題所在。驗證:分別啟動兩個新舊image,通過docker ps查看發現這兩個容器的CMD指令不一致,老image的CMD=‘/bin/sh -c /bin/init’,新image的CMD=‘/bin/bash’,很明顯修改后的CMD覆蓋掉了修改前的。
那么接下來簡單分析下CMD是如何被覆蓋的,修改前image的cmd=‘/bin/sh -c /bin/init’,然后docker run -dit image:tag /bin/bash啟動了容器并且進入到容器內部修改了應用程序相關參數,緊接著執行docker commit & docker push。起初以為docker commit會給新創建的image設置一個默認cmd (/bin/bash),通過閱讀代碼了解到commit會將容器當前使用的cmd設置給新創建的image。也就是原來image的cmd被剛才commit時指定的容器cmd覆蓋了。
知道了問題我們就來想想解決辦法吧,目前想到了兩種方式:
1. 如果希望cmd不變,那么就需要保證運行的容器cmd正確,針對上邊提到的case可以在docker run一個容器時不指定cmd(docker run -dit image:tag),這樣就會使用默認的cmd運行容器,此時再commit就不會出現上邊提到的問題。
2. docker commit時可以指定--change參數替換image中的cmd,比如:--change=‘CMD ["/bin/sh", "-c", "/bin/init"]'?
這部分代碼邏輯相對比較簡單,具體處理邏輯大家感興趣可以自行閱讀:
docker commit 處理邏輯:
1. docker client拼裝請求發送到docker daemon的”/commit”接口
2. docker daemon接收到請求以后在postCommit()中調用CreateImageFromContainer(),在CreateImageFromContainer()函數中可以看到,首先根據容器ID找到容器的運行時信息,然后根據運行時的config信息生成一個用于創建image的config,最后根據這個newConfig生成一個新的image(config結構中包含了CMD、ENTRYPOINT等)
3. 另外也可以看到docker commit時如果參數中指定了changes那么在函數BuildFromConfig()中會優先使用changes指定的指令構建image的config(也就是覆蓋了上一個容器的相關指令)
Docker Client:
|— NewCommitCommand (components/cli/cli/command/container/commit.go)
????|— runCommit (components/cli/cli/command/container/commit.go)
????????|— ContainerCommit (components/engine/client/container_commit.go)
Docker Daemon:
|— initRoutes (components/engine/api/server/router/container/container.go)? ?
????|— postCommit (components/engine/api/server/router/container/container_routes.go)
????????|— CreateImageFromContainer (components/engine/daemon/commit.go)?
? ??????????? ? ?|— daemon.GetContainer
? ? ? ? ? ? ? ? ?|— dockerfile.BuildFromConfig
?????????????????|— daemon.imageService.CommitImage