Docker 鏡像管理

運行容器示例

這是前一篇的內容,可以先來練習回顧一下容器的操作。

Nginx

直接下載鏡像並啓動容器,這裏選擇alpine版的:

$ docker run --name web1 -p 8001:80 -d nginx:alpine
Unable to find image 'nginx:alpine' locally
alpine: Pulling from library/nginx
e7c96db7181b: Downloading 
3fb6217217ef: Download complete 
alpine: Pulling from library/nginx
e7c96db7181b: Pull complete 
3fb6217217ef: Pull complete 
Digest: sha256:17bd1698318e9c0f9ba2c5ed49f53d690684dab7fe3e8019b855c352528d57be
Status: Downloaded newer image for nginx:alpine
01c17a72e943e93d71b56b433bea7a3d6ffa1f848dc3947f2adaf2bb2e3e7fee
$

啓動參數說明:

  • -d,表示啓動容器後在宿主機的後臺運行。
  • -p,端口映射,將宿主機的8001端口,映射到容器內部的80端口。端口映射是network的內容,之後會詳細說明。

查看啓動的容器:

$ docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
f57cd5f9d50f        nginx:alpine        "nginx -g 'daemon of…"   13 minutes ago      Up 12 minutes       0.0.0.0:8001->80/tcp   web1
$ 

可以看到端口和端口映射的情況。
這裏主要關注COMMAND,上面的顯示被截斷了:

$ docker container ls --no-trunc --format '{{.Command}}'
"nginx -g 'daemon off;'"
$ 

這裏啓動nginx加了參數,daemon off 字面的意思就是關閉守護進程。這是爲了讓nginx在前臺運行。
如果是nginx默認的啓動方式,那麼nginx程序將在後臺運行,一旦nginx啓動完就沒有任何程序了,結果容器也就退出了。

在容器中執行任何程序或服務,一定不能在容器中運行在後臺。只要運行在後臺,一啓動就會終止。

既然Nginx已經啓動,就可以直接用瀏覽器訪問了。並且做了端口映射,所以可以直接通過宿主機的端口來進行訪問:http://[宿主機的IP地址]:8001

容器的日誌
每一個容器的目的只是爲了運行一個程序,這個程序就是容器的主進程PID=1。傳統的程序的日誌一般是保存在日誌文件中的,但是容器中沒有這個必要。因爲現在整個容器就只爲了運行一個進程,日誌就可以直接打印在控制檯上了,就是程序直接在前臺運行的效果。
使用下面的命令可以查看日誌:

$ docker container logs web1

查看後,訪問幾次頁面再看下是否有訪問日誌刷新。

redis

首先,直接啓動一個redis:

$ docker container run --name redis -d redis:alpine

容器啓動後,依然是停留在宿主機的命令行界面。

進入容器內部
現在需要進入到容器內部進行操作,就像之前的busybox那樣。但是,這次容量內部運行的是一個 redis-server 的程序,並且一個容器內部一般只運行一個程序。所以容器裏並沒有shell。
這裏和之前的busybox容器的情況不同,在busybox容器內部就有一個shell。所以直接進入是沒有任何終端界面的。這裏需要啓動一個shell然後進入:

$ docker container exec -it redis /bin/sh
/data # ps
PID   USER     TIME  COMMAND
    1 redis     0:00 redis-server
   12 root      0:00 /bin/sh
   23 root      0:00 ps
/data # 

進入並且執行命令查看當前容器內的進程。
這裏看到,除了ps命令,還有原本的 redis-server 以及進入容器時啓動的shell。所以在容器內部運行多個進程也是可以的,現在就是這個情況。不過一般也就這在這種場景下需要在容器中運行多個進程。

執行其他操作
既然都進來了,就運行些命令。看下系統的時間:

/data # date
Tue Jul 16 13:03:05 UTC 2019
/data #

時間沒問題,不過時區不對,這個略過。

查看端口監聽情況:

/data # netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       
tcp        0      0 0.0.0.0:6379            0.0.0.0:*               LISTEN      
tcp        0      0 :::6379                 :::*                    LISTEN      
/data # 

使用 redis-cli 命令:

/data # redis-cli
127.0.0.1:6379> set age 23
OK
127.0.0.1:6379> set name Adam
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> exit
/data # exit
$ 

這裏看到 redis-cli 自帶用戶界面,所以不用啓動 /bin/sh 也能直接進來:

$ docker container exec -it redis redis-cli
127.0.0.1:6379> exit
$ 

Docker 鏡像

Docker鏡像的基礎知識。

鏡像啓動

docker鏡像含有啓動容器所需要的文件系統及其內容,因此,其用於創建並啓動docker容器。
採用分層構建機制,最底層爲bootfs,次之爲rootfs:

  • bootfs: 用於系統引導的文件系統,包括bootloader和kernel,容器啓動完成後會被卸載以節約內存資源
  • rootfs: 位於bootfs之上,表現爲docker容器的根文件系統:
    • 傳統模式中,系統啓動之時,內核掛載rootfs時會首先將其掛載爲“只讀”模式,完整性自檢完成後將其重新掛載爲讀寫模式
    • docker中,rootfs由內核掛載爲“只讀”模式,而後通過"聯合掛載“技術額外掛載一個”可寫“層

Docker 鏡像管理
啓動一個busybox容器,命令ls查看容器內部,擁有完整意義上的文件系統:

$ docker container run --name shell -it busybox
/ # ls
bin   dev   etc   home  proc  root  sys   tmp   usr   var
/ # exit
$ 

分層構建

分層構建的鏡像:

  • 位於下層的鏡像爲父鏡像(parent image),最底層的稱爲基礎鏡像(base image)
  • 最上層爲"可讀寫”層,其下的均爲"只讀“層

Docker 鏡像管理
如圖,是一個Apache鏡像。最底層是一個 Debian 的基礎鏡像,一個純淨的操作系統。在系統之上,添加了一個 emacs,這是一個代碼編輯器。再然後添加了一個 Apache。這裏每添加一個軟件都是一個獨立的層次。
最最下面的bootfs,在容器啓動時,一旦引導完rootfs就會被卸載並移除(從內存中移除)。
對於一個容器,所有寫操作,只能在最上層的可讀寫層進行。如果容器刪除了,這個最上面的可讀寫層也會一起被刪除。

操作系統基礎鏡像

關於Linux操作系統的基礎鏡像,可以參考下表來選擇合適的基礎鏡像:

  • busybox: 臨時測試用
  • alpine: 主要用於測試,也可用於生產環境
  • centos: 主要用於生產環境,支持CentOS/Red Hat,常用於追求穩定性的企業應用
  • ubuntu: 主要用於生產環境,常用於人工智能計算和企業應用
  • debian: 主要用於生產環境

推薦使用Alpine鏡像,因爲它被嚴格控制並保持最小尺寸(目前小於5MB),但它仍然是一個完整的發行版。
alpine的好處主要是小,並且基本功能全。用於測試是非常方便的,而且生產上也是可以用。雖然不建議這麼做,主要是因爲缺少調試工具。
busybox的鏡像比alpine更小,它並不是一個系統發行版。最初這個工具是爲了在一張軟盤上創建一個可引導的 GNU/Linux 系統,這可以用作安裝盤和急救盤。它是一個集成了三百多個最常用Linux命令和工具的軟件。所以如果是需要啓動一個容器並運行一些系統的工具和命令,那麼可以使用這個作爲基礎鏡像。
另外3個就是常用的Linux發行版,推薦在生產系統上用。鏡像大也不是什麼問題,因爲容器是分層構建的,所以本地的多個鏡像理論上是共用同一個基礎鏡像。

文件系統

Docker鏡像的分層構建和聯合掛載依賴於它的專有文件系統。
aufs
在早期這個文件系統是aufs(advanced multi-layered unification filesystem), 高級多層統一文件系統。
overlayfs
aufs的競爭產品是overlayfs,overlayfs在3.18版本開始被合併到Linux內核。使用docker info命令可以找到當前使用的文件系統:

Storage Driver: overlay2
 Backing Filesystem: xfs
 Supports d_tpe: true
 Native Overlay Diff: true

overlay2是一種抽象的二級文件系統,它需要建構在本地文件系統之上。上面的信息顯示,這裏作爲基礎的本地文件系統是xfs。
其他文件系統
docker的分層鏡像,除了aufs,還支持btrfs,devicemapper和vfs等。早期默認支持的文件系統:

  • Ubuntu系統,默認使用aufs
  • CentOS系統,默認使用devicemapper

Device Mapper 是 Linux2.6 內核中支持邏輯卷管理的通用設備映射機制,它爲實現用於存儲資源管理的塊設備驅動提供了一個高度模塊化的內核構架。

Docker Registry

最著名的 Registry 就是Docker Hub: https://hub.docker.com/
其他的和有比如這個Quay: https://quay.io/

啓動容器時,會先試圖從本地獲取相關的鏡像。如果本地鏡像不存在,再從Registry中下載鏡像並保存到本地。
一個Registry通常由2部分組成:

  • Repository
  • Index

Repository(倉庫)

Repository,由特定的docker鏡像的所有迭代版本組成的鏡像倉庫。一個Registry中可以存在多個Repository。每個倉庫可以包含多個Tag(標籤),每個標籤對應一個鏡像。
Repository可分爲頂層倉庫用戶倉庫,用戶倉庫名稱格式爲“用戶名/倉庫名”。使用docker search命令看一下:

$ docker search --limit 3 nginx
NAME                  DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
nginx                 Official build of Nginx.                        11704               [OK]                
jwilder/nginx-proxy   Automated Nginx reverse proxy for docker con…   1628                                    [OK]
bitnami/nginx         Bitnami nginx Docker Image                      69                                      [OK]
$ 

這裏顯示了3個,第一個是沒有用戶名的屬於頂層倉庫。後面是用戶倉庫,可以看到分別屬於的用戶名。

Index

Idxex的作用:

  • 維護用戶賬戶、鏡像的校驗以及公共命名空間的信息
  • 相當於爲Registry提供了一個完成用戶認證功能的檢索接口

雲原生

這裏只是簡單的提一下這個概念,主要是程序配置文件的問題。
鏡像的使用有一個問題,就是鏡像內部使用的配置信息。配置信息可以直接注入在鏡像裏,但是這樣就要爲不同的配置生成好多個不同版本的鏡像。
雲原生是一種爲了雲計算環境運行而生的應用程序,並且可以解決不同配置的信息的問題。
以Nginx爲例,傳統的開發運行在服務器上的程序,使用配置文件來管理配置。如果把它託管到容器雲上運行,就會有諸多不便之處,最大的問題就是修改配置文件。
而那些雲原生開發的程序,會使用對於雲計算場景方便的接口來提供配置邏輯。具體到容器,相當於爲應用程序加了一層外殼,再去操作裏面的數據是不方便的。有一種做法是向容器傳入環境變量來傳遞配置信息,而配置則可以從環境變量加載自動注入到配置中。
雲原生的大量配置都可以直接通過環境變量來獲取。

基於容器製作鏡像

使用命令docker commint會把容器最上面的可寫層,單獨創建爲一個鏡像層,生成一個新的鏡像。

其他製作鏡像的方法,並且是製作鏡像的最主要的方法是,基於Dockerfile製作鏡像。這部分內容很重要也很多,需要單獨再寫一篇。

修改基礎鏡像的內容

基於busybox,添加一個httpd的服務。

$ docker run --name httpd -it busybox
/ # echo "<h1>Hello world. Busybox httpd.</h1>" > /var/www/index.html
/ # cat /var/www/index.html 
<h1>Hello world. Busybox httpd.</h1>
/ # 

在容器內部進行修改
現在創建好了一個html文件,但是下次docker再啓動這個容器時這個文件是不會有的。現在需要做的是將之前做的改變保存好。

保存對容器的修改,生成新的鏡像
要保持這個容器的運行狀態,那就再另外開一個會話執行commit命令:

$ docker commit -p httpd
sha256:5bd093efd84001a2f7412292431ead5c760acef8f4e3a2298abf9f28aa7b3cd7
$ 

這裏的-p參數是將容器處於暫停狀態,這樣可以防止鏡像製作過程中可能會有操作來改變容器的內容,建議-p參數都加上。

修改鏡像標籤

查看鏡像信息,新制作完成的鏡像信息如下:

$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
<none>              <none>              5bd093efd840        2 minutes ago       1.22MB
busybox             latest              e4db68de4ff2        4 weeks ago         1.22MB
$ 

由於製作的時候沒有指明倉庫名和標籤名,所以都是空。這兩個字段是允許爲空的,這樣只能通過鏡像的ID來指明這個鏡像。

添加標籤信息
爲了引用時方便,還是把倉庫名和標籤名加上吧:

$ docker image tag 5bd093efd840 myimg/httpd:v1
$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg/httpd         v1                  5bd093efd840        9 minutes ago       1.22MB
busybox             latest              e4db68de4ff2        4 weeks ago         1.22MB
$ 

一個鏡像可以有多個標籤,再加一個latest標籤:

$ docker image tag myimg/httpd:v1 myimg/httpd:latest
$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg/httpd         latest              5bd093efd840        11 minutes ago      1.22MB
myimg/httpd         v1                  5bd093efd840        11 minutes ago      1.22MB
busybox             latest              e4db68de4ff2        4 weeks ago         1.22MB
$ 

這裏可以確認下,2個標籤的鏡像的ID是一樣的,所以這裏是一個鏡像,只是爲這個鏡像添加了2個標籤。

刪除標籤
沒有刪除標籤的命令,要刪除某個標籤,就直接用刪除鏡像的命令:

$ docker image tag myimg/httpd:v1 myimg/httpd:tmp1
$ docker image rm myimg/httpd:tmp1
Untagged: myimg/httpd:tmp1
$

這裏又添加了一個標籤,然後再把這個標籤給刪除,命令執行結果顯示只是把指定的標籤去掉了。所以同一個鏡像打了多個標籤,本地存的只有一份。刪除某個標籤的鏡像,只是把這個標籤從鏡像標籤的列表裏去除。之後刪除最後一個標籤的時候纔是真正的刪除了一個鏡像。

修改鏡像默認啓動命令

使用 inspect 命令可以查看docker對象的底層信息。這裏要找的是鏡像的底層信息中的默認啓動的命令,具體如下:

$ docker image inspect busybox
            "Cmd": [
                "sh"
            ],
$

啓動時運行的命令是sh,這個也是busybox鏡像默認啓動時運行的命令,因爲製作新鏡像的時候沒有指定這個內容。
重新制作一版新的鏡像,這次要指定默認啓動時運行的命令:

$ docker commit -c 'CMD ["httpd", "-f", "-h", "/var/www/"]' -p httpd myimg/httpd:v2
sha256:850da6d87c65a2c6084cdbfcabbeeeaf6c13ddbb9fbb984fec5ca05cab38830d
$ 

參數-c不是用來指定命令的,而是指定所有要做的修改,當然這裏只要修改啓動的命令。
httpd命令參數說明
關於啓動命令httpd -f -h /var/www/,這個具體可以去看httpd的參數說明。-f表示作爲守護進程也就是在前臺運行,而-h參數則是指定首頁的路徑。

啓動驗證鏡像

帶參數啓動鏡像:

$ docker container run --name httpd2 -d -p 8002:80 myimg/httpd:v2
80522bb422e16dae4ea052bcb36e51203f4d7b023fefdf3de4114598b3e95b29
$ 

鏡像啓動後,可以使用瀏覽器訪問宿主機的IP地址加上映射的端口號來打開這個頁面,比如:http://192.168.24.170:8002/

鏡像導入和導出

可以在已有鏡像的主機上把鏡像打包,將打包的文件複製到另外一臺主機上再把鏡像導入,就可以在主機之間傳遞鏡像了。這種方法不需要連接鏡像倉庫。

導出鏡像

導出鏡像就是將鏡像導出到一個tar包:

$ docker image save -o httpd.tar myimg/httpd

這條命令省略了Tag標籤,這樣就會把整個倉庫打包,就是打包所有的版本。
save命令僅有一個參數-o,就是指定導出的位置。如果沒有-o參數,那就是輸出到終端。不過也不能直接輸出到終端,這樣的做法是再通過輸出重定向來把內容保存起來。所以這條命令的效果是一樣的:

$ docker image save myimg/httpd > httpd2.tar

可以加上標籤信息,就可以指定打包對應的Tag的鏡像。鏡像的參數可以傳入多個,就打包多個鏡像:

$ docker image save -o httpd3.tar myimg/httpd:v1 myimg/httpd:v2

導出爲tar文件
導入的文件名可以任意指定,不過建議使用tar擴展名。這確實是一個tar包,使用tar命令來查看tar包內部的文件列表:

$ tar -tvf httpd.tar 
-rw-r--r-- 0/0            1491 2019-07-18 15:10 25079c1e47bf896a028e55d715dc06e251f3efe53ca655ad63f6085ce6a465a8.json
-rw-r--r-- 0/0            1464 2019-07-18 15:05 7f36d8e3488df22381081d68c7f2215750167250114abd0b2f31d99e81a7bfd7.json
drwxr-xr-x 0/0               0 2019-07-18 15:05 957ac2430f81aaa485efe07e872a460156d73e48f53f31a9743ed0d5f0fa44d7/
-rw-r--r-- 0/0               3 2019-07-18 15:05 957ac2430f81aaa485efe07e872a460156d73e48f53f31a9743ed0d5f0fa44d7/VERSION
-rw-r--r-- 0/0            1081 2019-07-18 15:05 957ac2430f81aaa485efe07e872a460156d73e48f53f31a9743ed0d5f0fa44d7/json
-rw-r--r-- 0/0            4608 2019-07-18 15:05 957ac2430f81aaa485efe07e872a460156d73e48f53f31a9743ed0d5f0fa44d7/layer.tar
drwxr-xr-x 0/0               0 2019-07-18 15:10 a24e93a2c2b0548055a10d18f0c88dc138c57ee6f13020538bf80da2bfefc59f/
-rw-r--r-- 0/0               3 2019-07-18 15:10 a24e93a2c2b0548055a10d18f0c88dc138c57ee6f13020538bf80da2bfefc59f/VERSION
-rw-r--r-- 0/0            1107 2019-07-18 15:10 a24e93a2c2b0548055a10d18f0c88dc138c57ee6f13020538bf80da2bfefc59f/json
-rw-r--r-- 0/0            4608 2019-07-18 15:10 a24e93a2c2b0548055a10d18f0c88dc138c57ee6f13020538bf80da2bfefc59f/layer.tar
drwxr-xr-x 0/0               0 2019-07-18 15:05 dea411b43d1b59da62f22c37c8507e7757c2dd9a5467a523f92e612d88e83ae8/
-rw-r--r-- 0/0               3 2019-07-18 15:05 dea411b43d1b59da62f22c37c8507e7757c2dd9a5467a523f92e612d88e83ae8/VERSION
-rw-r--r-- 0/0             406 2019-07-18 15:05 dea411b43d1b59da62f22c37c8507e7757c2dd9a5467a523f92e612d88e83ae8/json
-rw-r--r-- 0/0         1441280 2019-07-18 15:05 dea411b43d1b59da62f22c37c8507e7757c2dd9a5467a523f92e612d88e83ae8/layer.tar
-rw-r--r-- 0/0             579 1970-01-01 08:00 manifest.json
-rw-r--r-- 0/0             238 1970-01-01 08:00 repositories
$ 

從IMAGE ID可以看出,這裏確實是將2個版本的鏡像到打包了。

導入鏡像

使用load命令可以方便的將鏡像導入:

$ docker image load -i httpd3.tar 
6194458b07fc: Loading layer [==================================================>]  1.441MB/1.441MB
dd0dd7cb79c9: Loading layer [==================================================>]  4.608kB/4.608kB
Loaded image: myimg/httpd:latest
Loaded image: myimg/httpd:v1
698704828883: Loading layer [==================================================>]  4.608kB/4.608kB
Loaded image: myimg/httpd:v2
$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg/httpd         v2                  25079c1e47bf        30 minutes ago      1.22MB
myimg/httpd         latest              7f36d8e3488d        35 minutes ago      1.22MB
myimg/httpd         v1                  7f36d8e3488d        35 minutes ago      1.22MB
$ 

上面最後一次打包的文件是 httpd3.tar。執行打包命令的時候指定了v1和v2標籤,並沒有指定latest標籤。不過這裏能看到所有的3個標籤。所以標籤只是一個標籤,一個鏡像可以有多個標籤,但是不同標籤的鏡像是同一個鏡像。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章