天天看點

docker鏡像建構檔案系統鏡像建構鏡像建構容器生命周期docker logsdocker 啟停

docker鏡像建構

  • 檔案系統
  • 鏡像建構
    • 容器層操作細節
  • 鏡像建構
    • docker commit
    • docker export
    • docker save
    • docker build
    • docker history
  • 容器生命周期
    • docker RUN
    • docker CMD
    • docker ENTRYPOINT
    • docker exec
    • docker attach
  • docker logs
  • docker 啟停
    • docker start
    • docker stop
      • 使用docker stop myredis_exec指令停止exec模式啟動的myredis2容器。
      • docker stop指令停止shell指令啟動的myredis容器。
      • 孤兒程序和僵屍程序
      • 總結:
    • docker kill

檔案系統

linux作業系統由核心空間和使用者空間組成。其中核心空間是kernel,對應的檔案系統時bootfs,linux在剛啟動的時候會加載bootfs檔案系統,在啟動完成以後會解除安裝bootfs。使用者空間是rootfs檔案系統,包括我們常用/etc、/proc、/bin、/dev等,如下圖所示:

docker鏡像建構檔案系統鏡像建構鏡像建構容器生命周期docker logsdocker 啟停

一般鏡像結構如下:

docker鏡像建構檔案系統鏡像建構鏡像建構容器生命周期docker logsdocker 啟停

在Image上還會有很多的Image。隻有bootfs上面一層那個Image是base Image。(rootfs)

對于任何docker鏡像,其檔案系統都是與主控端共用kernel,共用bootfs,但是不共用rootfs。容器的rootfs由容器自己提供。

對于一個鏡像,如果其base鏡像(bootfs上面一層的那個鏡像層)不是作業系統鏡像,而是自己寫的一個鏡像,那麼該鏡像的檔案系統就是自己拷貝過去的檔案目錄。

比如,自己寫了一個helloworld的代碼,使用dockerfile建構docker鏡像。

FROM scratch		白手起家從0建構一個鏡像
COPY hello /		将檔案"hello"複制到鏡像的根目錄,鏡像根目錄是/
CMD ["hello"]		容器啟動時,執行/hello
           

base鏡像一般時底層,同時也能被其他鏡像将其作為基礎進行擴充。

鏡像建構

Docker Daemon在建構dockerfile的過程中,對dockerfile中的每一條指令(FROM指令除外)都會建構一個新的image。

例如用如下指令建構鏡像:

docker鏡像建構檔案系統鏡像建構鏡像建構容器生命周期docker logsdocker 啟停

建構過程如下:

docker鏡像建構檔案系統鏡像建構鏡像建構容器生命周期docker logsdocker 啟停

建構出來的鏡像層,在容器中都隻是可讀,不可寫,也就是說,在容器運作過程中,對鏡像進行修改操作時不會修改鏡像的,隻會修改鏡像層以上的容器層。

當容器啟動時,會在鏡像層上建立一個新的容器層,該層是可讀可寫層,容器啟動以後的結構如下所示:

docker鏡像建構檔案系統鏡像建構鏡像建構容器生命周期docker logsdocker 啟停

鏡像層的數量可能有很多,所有的鏡像層聯合在一起組成了鏡像的檔案系統。如果在不同層中,有一個相同路徑的檔案,那麼在容器中,使用者隻能通路到在上層的檔案,不能通路到在下層的該檔案。相當于曆史層無法通路到。

容器層操作細節

1) 添加檔案。在容器中添加檔案時,是将該檔案添加到容器層中。

2) 讀取檔案。在容器中讀取檔案時,docker從檔案系統頂端往下依次每層查找此檔案,一旦找到該檔案,則打開并讀取到記憶體中。是以不會讀取到下層的同目錄檔案。

3) 修改檔案。在容器中讀取檔案時,docker從檔案系統頂端往下依次每層查找此檔案,一旦找到該檔案,則将其複制到容器層中,然後對其進行修改。

4) 删除檔案。在容器中删除檔案時,docker從檔案系統頂端往下依次每層查找此檔案,一旦找到該檔案,容器則記錄下删除操作,并不會真的去删除鏡像層的檔案。

可以看到,容器在運作過程中,隻有當需要修改的時候,才會去複制一份資料,這種特性稱為Copy-on-Write。

鏡像建構

docker commit

docker commit操作時儲存容器層檔案系統以及容器層檔案系統以下的鏡像層檔案系統,但是該方式不會儲存具體的修改步驟,通過這種方式儲存的鏡像,無法知道容器層到底進行了哪些修改。

docker export

docker export操作則是隻儲存最新版的容器層檔案系統,并且清除容器層之前的層,也就是清除之前的所有底層鏡像,将這個最新的容器層作為新的base鏡像。

docker save

docker save方式則是儲存容器層以下的鏡像層檔案系統,相當于修改沒有奏效。

docker build

docker build -t ImageName:TagName Dir
           

-t

—— 給鏡像加一個tag

ImageName

—— 給鏡像起的名字

TagName

—— 給鏡像起的标簽名

Dir

—— build context目錄

Docker預設從build context中查找dockerfile檔案。也可以通過-f參數指定dockerfile檔案目錄。

需要注意的是,dockerfile中的ADD、COPY等指令可以将build context中的檔案添加到鏡像中。(會将build context目錄中的所有檔案都發送給docker daemon)。

如下指令的執行順序:

docker鏡像建構檔案系統鏡像建構鏡像建構容器生命周期docker logsdocker 啟停

執行RUN,将Ubuntu作為base鏡像

執行RUN,安裝vim

啟動一個臨時容器,在該容器中通過apt-get

vim安裝vim

将該容器儲存為鏡像(docker commit)

删除容器

鏡像建構成功

在整個過程中,實際上的儲存容器的方法是docker commit。

dockerfile建構過程:

1) 從base鏡像運作一個容器

2) 執行一條指令,對容器進行修改

3) 執行docker commit操作,生成一個新的容器鏡像層

4) docker再基于剛剛送出的鏡像,運作一個新容器

5) 重複2~4步,直到dockerfile中的所有指令全部執行完畢。in

docker history

docker history ImageName
           

ImageName

—— 鏡像名

docker history指令可以檢視鏡像的曆史資訊,可以看到每一層鏡像是通過什麼指令來對鏡像進行修改的。

輸出結果中的

missing

表示無法擷取image id,通常從docker hub下載下傳的鏡像會有這個問題。

容器生命周期

docker RUN

docker RUN指令在目前鏡像的頂部執行指令,并建立新的層。docker RUN指令常常用于鏡像修改。

docker CMD

docker CMD指令是用來設定docker啟動時的CMD預設指令。

docker CMD指令會被啟動docker時的docker run指令覆寫掉,如果docker run指令後面有執行指令,則不會執行docker CMD指令。

docker ENTRYPOINT

docker ENTRYPOINT指令不會被覆寫掉,不會被docker run指令覆寫,始終都會執行。

容器的生命周期取決于啟動時運作的指令,隻要該指令不結束,容器就不會退出。

docker鏡像建構檔案系統鏡像建構鏡像建構容器生命周期docker logsdocker 啟停

如上所示的指令中,while死循環,指令不會結束,該容器永不退出。

docker exec

docker exec在容器中打開新的終端,并且可以啟動新的程序。

docker exec

進入的容器,建立的終端屬于daemon的子程序。

例如:

docker鏡像建構檔案系統鏡像建構鏡像建構容器生命周期docker logsdocker 啟停

這裡,docker exec進入到的則是bash終端,不是容器啟動時建立的/bin/bash終端。

docker attach

docker attach直接進入容器啟動指令建立的終端中,不會啟動新的程序。

例如:使用docker attach指令則會進入容器建立時的/bin/bash終端。

docker logs

docker logs -f +容器id

可以檢視/bin/bash終端的輸出。

docker 啟停

docker鏡像的建構使用CMD指令時,有兩種方式,一種時shell方式,一種是exec方式。如下所示:

shell方式

dockerfile

建構如下,利用shell方式建構容器。

FROM ubuntu:14.04
RUN apt-get update && apt-get -y install redis-server && rm -rf /var/lib/apt/lists/*
EXPOSE 6379
CMD "/usr/bin/redis-server"
           
exec方式

dockerfile

建構如下,利用exec方式建構容器。
FROM ubuntu:14.04
RUN apt-get update && apt-get -y install redis-server && rm -rf /var/lib/apt/lists/*
EXPOSE 6379
CMD ["/usr/bin/redis-server"]
           

然後基于它們建構兩個鏡像

myredis:shell

myredis:exec

docker build -t myredis:shell -f Dockerfile_shell .
docker build -t myredis:exec -f Dockerfile_exec .
           

運作

myredis:shell

鏡像,我們可以發現它的啟動程序(PID1)是

/bin/sh -c "/usr/bin/redis-server"

并且它建立了一個子程序

/usr/bin/redis-server *:6379

[email protected]:~# docker run -d --name myredis_shell myredis:shell
128fdc38cdccc222234ee2c2bc3a4c84355bc97a33e66ddf53c580b86cfe298e
[email protected]:~# docker exec myredis_shell ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 14:23 ?        00:00:00 /bin/sh -c "/usr/bin/redis-server"
root           7       1  0 14:23 ?        00:00:00 /usr/bin/redis-server *:6379
root          10       0  1 14:23 ?        00:00:00 ps -ef

           

下面運作

myredis:exec

鏡像,我們可以發現它的啟動程序是

/usr/bin/redis-server *:6379

,并沒有其他子程序存在。

[email protected]:~# docker run -d --name myredis_exec myredis:exec
ffd77fef180740ef199281747e372c33d6e2df06d9edbf11a03c0d30caf61fa3
[email protected]:~# docker exec myredis_exec ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 14:27 ?        00:00:00 /usr/bin/redis-server *:6379
root           9       0  0 14:27 ?        00:00:00 ps -ef
           

docker start

docker stop

docker stop指令是向容器發送一個SIGTERM信号。

docker每個container都是docker daemon的子程序。

當建立一個docker容器的時候,就會建立一個PID命名空間,容器啟動程序在該命名空間内PID=1。

當PID1的程序結束以後,容器會銷毀對應的的PID命名空間,并向容器内其他子程序發送SIGKILL信号。

使用docker stop myredis_exec指令停止exec模式啟動的myredis2容器。

[email protected]:~# docker stop myredis_exec
myredis_exec
[email protected]:~# docker logs myredis_exec 
[1] 09 Sep 14:27:17.175 # Warning: no config file specified, using the default config. In order to specify a config file use /usr/bin/redis-server /path/to/redis.conf
[1] 09 Sep 14:27:17.175 * Max number of open files set to 10032
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 2.8.4 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in stand alone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 1
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

[1] 09 Sep 14:27:17.175 # Server started, Redis version 2.8.4
[1] 09 Sep 14:27:17.175 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
[1] 09 Sep 14:27:17.175 * The server is now ready to accept connections on port 6379
[1 | signal handler] (1631197679) Received SIGTERM, scheduling shutdown...
[1] 09 Sep 14:27:59.545 # User requested shutdown...
[1] 09 Sep 14:27:59.545 * Saving the final RDB snapshot before exiting.
[1] 09 Sep 14:27:59.547 * DB saved on disk
[1] 09 Sep 14:27:59.547 # Redis is now ready to exit, bye bye...
           

docker stop指令生效,pid1程序收到了SIGTERM信号,随後退出了。

docker stop指令停止shell指令啟動的myredis容器。

[email protected]:~# docker stop myredis_shell 
myredis_shell
[email protected]:~# docker logs myredis_shell 
[7] 09 Sep 14:23:50.211 # Warning: no config file specified, using the default config. In order to specify a config file use /usr/bin/redis-server /path/to/redis.conf
[7] 09 Sep 14:23:50.211 * Max number of open files set to 10032
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 2.8.4 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in stand alone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 7
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

[7] 09 Sep 14:23:50.211 # Server started, Redis version 2.8.4
[7] 09 Sep 14:23:50.211 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
[7] 09 Sep 14:23:50.211 * The server is now ready to accept connections on port 6379
           

在myredis容器中,pid1程序沒有收到SIGTERM信号。這是因為pid1的程序

/bin/sh

沒有對SIGTERM信号的處理邏輯,是以它忽略了SIGTERM信号。當docker等待stop指令執行超過10s以後,docker daemon發送SIGKILL信号強制殺死sh程序。

孤兒程序和僵屍程序

如果子程序退出了,但是父程序沒有進行

wait()

系統調用來回收子程序,那麼子程序就會成為僵屍程序。

在myredis2中。exec方式,沒有建立/bin/sh

首先在

myredis2

容器中啟動一個bash程序,并建立子程序

sleep 1000

[email protected]:~# docker restart myredis_exec 
myredis_exec
[email protected]:~# docker exec -it myredis_exec bash
[email protected]:/# sleep 1000
           

在另一個終端視窗,檢視目前程序,我們可以發現一個

sleep

程序是

bash

程序的子程序。

[email protected]:~# docker exec myredis_exec ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 14:30 ?        00:00:00 /usr/bin/redis-server *:6379
root           9       0  0 14:30 pts/0    00:00:00 bash
root          25       9  0 14:30 pts/0    00:00:00 sleep 1000
root          26       0  0 14:31 ?        00:00:00 ps -ef
           

我們殺死

bash

程序之後檢視程序清單,這時候

bash

程序已經被殺死。這時候

sleep

程序(PID為21),雖然已經結束,而且被PID1程序

redis-server

接管,但是其沒有被父程序回收,成為僵屍狀态。

[email protected]:~# docker exec myredis_exec kill -9 9
[email protected]:~# docker exec myredis_exec ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 14:30 ?        00:00:00 /usr/bin/redis-server *:6379
root          25       1  0 14:30 ?        00:00:00 [sleep] <defunct>
root          39       0  0 14:31 ?        00:00:00 ps -ef
           

這是因為PID1程序

redis-server

沒有考慮過作為

init

對僵屍子程序的回收的場景。

在myredis中。shell方式,建立了/bin/sh

在用

/bin/sh

作為PID1程序的

myredis

容器中,再啟動一個

bash

程序,并建立子程序

sleep 1000

[email protected]:~# docker restart myredis_shell
myredis_shell
[email protected]:~# docker exec -ti myredis_shell bash
[email protected]:/# sleep 1000
           

檢視容器中程序情況

[email protected]:~# docker exec myredis_shell ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 14:32 ?        00:00:00 /bin/sh -c "/usr/bin/redis-server"
root           7       1  0 14:32 ?        00:00:00 /usr/bin/redis-server *:6379
root          10       0  0 14:32 pts/0    00:00:00 bash
root          25      10  0 14:32 pts/0    00:00:00 sleep 1000
root          26       0  0 14:32 ?        00:00:00 ps -ef
           

我們殺死

bash

程序之後檢視程序清單,發現

bash

sleep 1000

程序都已經被殺死和回收

[email protected]:~# docker exec myredis_shell kill -9 10
[email protected]:~# docker exec myredis_shell ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 14:32 ?        00:00:00 /bin/sh -c "/usr/bin/redis-server"
root           7       1  0 14:32 ?        00:00:00 /usr/bin/redis-server *:6379
root          39       0  0 14:33 ?        00:00:00 ps -ef
           

這是因為

sh/bash

等應用可以自動清理僵屍程序。

總結:

在建立容器的時候,PID1程序最好要能支援自動清理僵屍/孤兒程序,要能支援SIGTERM信号。

可以在建立PID1程序的時候,添加一個init系統。

例如:

FROM alpine:3.7
...
RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--", "COMMAND"]
           

添加tini系統,這樣,tini就是PID1程序。擁有SIGTERM信号支援和自動清理僵屍/孤兒程序的能力。

docker kill

docker kill指令是向容器發送一個SIGKILL信号。

繼續閱讀