資料卷的相關命令

花米徐發表於2017-11-27

資料卷的相關命令

資料卷背景

在介紹VOLUME指令之前,我們來看下如下場景需求:

1)容器是基於映象建立的,最後的容器檔案系統包括映象的只讀層+可寫層,容器中的程式操作的資料持久化都是儲存在容器的可寫層上。一旦容器刪除後,這些資料就沒了,除非我們人工備份下來(或者基於容器建立新的映象)。能否可以讓容器程式持久化的資料儲存在主機上呢?這樣即使容器刪除了,資料還在。

2)當我們在開發一個web應用時,開發環境是在主機本地,但執行測試環境是放在docker容器上。

這樣的話,我在主機上修改檔案(如htmljs等)後,需要再同步到容器中。這顯然比較麻煩。

3)多個容器執行一組相關聯的服務,如果他們要共享一些資料怎麼辦?

對於這些問題,我們當然能想到各種解決方案。而docker本身提供了一種機制,可以將主機上的某個目錄與容器的某個目錄(稱為掛載點、或者叫卷)關聯起來,容器上的掛載點下的內容就是主機的這個目錄下的內容,這類似linux系統下mount的機制。 這樣的話,我們修改主機上該目錄的內容時,不需要同步容器,對容器來說是立即生效的。 掛載點可以讓多個容器共享。

 

在日常的docker使用情況中,我們可能有這樣的一些需求:

  1. 是否可以在宿主機上,不啟動容器,便能夠直接訪問甚至修改容器中的某些檔案?
  2. 容器與容器之間是否可以資料共享?
  3. 可以使容器中的某些資料不隨著容器的刪除而隨之刪除嗎?

    在Docker中提供了資料卷(Volume)機制,通過它可以完美地解決上述的問題。資料卷是一個可供一個或多個容器使用的特殊目錄,它繞過 UFS,並且有一些有用的特性。

    資料卷有以下的一些有用的特性:

  4. 資料卷可以在容器之間共享和重用
  5. 對資料卷的修改會立馬生效
  6. 對資料卷的更新,不會影響映象
  7. 資料卷的生命週期獨立於容器的生命週期,它預設會一直存在,即使容器被刪除。

    資料卷的實現原理

    在介紹資料卷的原理之前,我們先介紹Linux的掛載與特殊的"繫結掛載"。

    在Linux中,掛載通常是指將一個裝置(通常是儲存裝置,例如光碟、U盤或者某個分割槽等)作為掛載點,掛載到某個目錄上去。在Linux中,如果想要訪問儲存裝置中的檔案,那麼必須先將儲存裝置掛載到Linux檔案系統的某個目錄上,然後才能通過訪問這個目錄來訪問儲存裝置。

    "繫結掛載"是一種特殊的掛載方式,首先它與掛載不同,"繫結掛載"是將一個目錄掛載到另外一個目錄上。然後繫結掛載後的兩個目錄會在內容上保持完全一致,也就是說,對其中一個目錄做出修改,也相當於對另外一個目錄看做出了同樣的修改。我們可以通過以下連結加深一下繫結掛載的理解:http://www.codeweblog.com/%E5%85%B3%E4%BA%8Emount-bind%E7%9A%84%E7%90%86%E8%A7%A3

    而Docker的資料卷機制其實是通過Linux系統的"繫結掛載"實現的。資料卷volume的本質是在容器的建立過程中,將宿主機的一個指定目錄("資料卷"的內容將會儲存在宿主機的這個目錄)掛載到容器中某個目錄(容器的這個目錄也稱之為"掛載點"),而這裡用到掛載方法就是"繫結掛載"。

    從資料卷的原理看資料卷的應用場景

    由於在容器的建立過程中,可以一個宿主機的指定目錄"繫結掛載"到指定容器的某個目錄。而且"繫結掛載"之後容器內的目錄會和宿主機的指定目錄始終保持完全一致,所以上面的應用場景就很容易實現了。

    如果想要不啟動容器,就訪問或者修改容器中的某個目錄,只需要建立一個資料卷,然後將資料卷掛載到容器的該目錄即可了。由於資料卷的內容會和容器中該目錄的內容始終保持一致,那麼就可以訪問或者修改"資料卷"其實也就是訪問或修改"容器的對應目錄"了。

    同理,如果想要實現容器間資料的共享,只要把"資料卷"掛載到不同容器的某個目錄就行了,這樣不同容器的指定目錄的內容就會保持完全一致了。

    容器在刪除的時候,只會將容器對應的可寫層刪除掉,而不會將"資料卷"刪除掉。所以如果想在容器中刪除時,某些資料能夠保留下來,那麼只需事先將"資料卷"掛載到容器的某個目錄,然後將需要保留的資料複製到該目錄中就可以了。這樣這些資料就不會隨著容器的刪除而隨之刪除,它會保留在"資料卷"之中了。

     

    建立一個資料卷

    建立一個名為vo1的資料卷,並將該資料卷掛載到container1容器的/dir1目錄。

    相關知識

    在閱讀相關知識之前,請先閱讀背景知識對資料卷有一定的瞭解。如果您能夠看明白資料卷的原理,那麼我相信接下來的學習會非常地輕鬆。如果看不懂也沒有關係,讓我們在學習中慢慢地加深理解。希望學習完這個實訓,能讓你對資料卷有一個全新的理解。

    建立一個資料卷

    Docker1.9版本以後,docker提供了一條新的命令:docker volume,用來建立、檢視、刪除資料卷。而傳統的docker run -v建立一個資料卷的方式也仍然保留了下來。

    可以使用docker volume create建立一個資料卷,並指定資料卷的名字,如下所示:下面這條命令將建立了一個名為vo1的資料卷。

    (1)docker volume create --name vo1

    除此以外,還可以在建立新容器的時候,通過新增-v標籤建立一個資料卷,如下所示:下面這條命令基於ubuntu進行建立並啟動了一個容器,然後建立了一個"隨機名字"的volume,並掛載到容器的/data目錄。

    (2)docker run -itd -v /data ubuntu /bin/bash

    當然也可以在建立新容器的時候,指定資料卷的名字,如下所示:這個命令就建立了一個名為vo2的資料卷,並掛載到了容器的/data目錄。

    (3)docker run -itd -v vo2:/data ubuntu /bin/bash

    之前我們說過,"資料卷"的內容會儲存在宿主機的一個指定的目錄上,預設情況下,在建立資料卷時,會在宿主機中的/var/lib/docker/volume/下建立一個以"資料卷名"為名的目錄,並將資料卷的內容儲存在該目錄下的/_data目錄下(也就是將資料卷的內容儲存在/var/lib/docker/volume/資料卷名/_data/中)。

    在日常的工作中,我們一般不會用第一種方式來建立一個資料卷,因為一個沒有被容器使用的資料卷顯然意義是不大的。如果需要指定資料卷的名字,用第三種方式來建立就比較好了。

    前面說過,資料卷的內容會與容器的掛載點內容完全保持一致,大家可以在右側命令列進行,加深對繫結掛載的理解。

    任務要求

    本關的程式設計任務是補全step1/createvolume.sh指令碼檔案的內容,要求實現建立一個資料卷。具體要求如下:

  • 建立一個名為vo1的資料卷,並將該資料卷掛載到container1容器的/dir1目錄。

    本關涉及的程式碼檔案step1/createvolume.sh的程式碼框架如下:

    #!/bin/bash

    #建立一個名為vo1的資料卷,並將該資料卷掛載到container1容器的/dir1目錄。

    #********** Begin *********#

     

    docker run --name container1 -itd -v vo1:/dir1 ubuntu /bin/bash

     

    詳細介紹

    一、通過docker run命令

    1、執行命令:docker run --name test -it -v /home/xqh/myimage:/data ubuntu /bin/bash

    其中的 -v 標記 在容器中設定了一個掛載點 /data(就是容器中的一個目錄),並將主機上的 /home/xqh/myimage 目錄中的內容關聯到 /data下。

    這樣在容器中對/data目錄下的操作,還是在主機上對/home/xqh/myimage的操作,都是完全實時同步的,因為這兩個目錄實際都是指向主機目錄。

    2、執行命令:docker run --name test1 -it -v /data ubuntu /bin/bash

    上面-v的標記只設定了容器的掛載點,並沒有指定關聯的主機目錄。這時docker會自動繫結主機上的一個目錄。通過docker inspect 命令可以檢視到。

    xqh@ubuntu:~/myimage$ docker inspect test1

    [

    {

    "Id": "1fd6c2c4bc545163d8c5c5b02d60052ea41900a781a82c20a8f02059cb82c30c",

    .............................

    "Mounts": [

    {

    "Name": "0ab0aaf0d6ef391cb68b72bd8c43216a8f8ae9205f0ae941ef16ebe32dc9fc01",

    "Source": "/var/lib/docker/volumes/0ab0aaf0d6ef391cb68b72bd8c43216a8f8ae9205f0ae941ef16ebe32dc9fc01/_data",

    "Destination": "/data",

    "Driver": "local",

    "Mode": "",

    "RW": true

    }

    ],

    ...........................

    上面 Mounts下的每條資訊記錄了容器上一個掛載點的資訊,"Destination" 值是容器的掛載點,"Source"值是對應的主機目錄。

    可以看出這種方式對應的主機目錄是自動建立的,其目的不是讓在主機上修改,而是讓多個容器共享。

    二、通過dockerfile建立掛載點

    上面介紹的通過docker run命令的-v標識建立的掛載點只能對建立的容器有效。

    通過dockerfile VOLUME 指令可以在映象中建立掛載點,這樣只要通過該映象建立的容器都有了掛載點。

    還有一個區別是,通過 VOLUME 指令建立的掛載點,無法指定主機上對應的目錄,是自動生成的。

    #test

    FROM ubuntu

    MAINTAINER hello1

    VOLUME ["/data1","/data2"]

    上面的dockfile檔案通過VOLUME指令指定了兩個掛載點 /data1 /data2.

    我們通過docker inspect 檢視通過該dockerfile建立的映象生成的容器,可以看到如下資訊

       

    "Mounts": [

    {

    "Name": "d411f6b8f17f4418629d4e5a1ab69679dee369b39e13bb68bed77aa4a0d12d21",

    "Source": "/var/lib/docker/volumes/d411f6b8f17f4418629d4e5a1ab69679dee369b39e13bb68bed77aa4a0d12d21/_data",

    "Destination": "/data1",

    "Driver": "local",

    "Mode": "",

    "RW": true

    },

    {

    "Name": "6d3badcf47c4ac5955deda6f6ae56f4aaf1037a871275f46220c14ebd762fc36",

    "Source": "/var/lib/docker/volumes/6d3badcf47c4ac5955deda6f6ae56f4aaf1037a871275f46220c14ebd762fc36/_data",

    "Destination": "/data2",

    "Driver": "local",

    "Mode": "",

    "RW": true

    }

    ],

    可以看到兩個掛載點的資訊。

    三、容器共享卷(掛載點)

    docker run --name test1 -it myimage /bin/bash

    上面命令中的 myimage是用前面的dockerfile檔案構建的映象。 這樣容器test1就有了 /data1 /data2兩個掛載點。

    下面我們建立另一個容器可以和test1共享 /data1 /data2 ,這是在 docker run中使用 --volumes-from標記,如:

    可以是來源不同映象,如:

    docker run --name test2 -it --volumes-from test1  ubuntu  /bin/bash

    也可以是同一映象,如:

    docker run --name test3 -it --volumes-from test1  myimage  /bin/bash

    上面的三個容器 test1 , test2 , test3 均有 /data1 /data2 兩個目錄,且目錄中內容是共享的,任何一個容器修改了內容,別的容器都能獲取到。

       

    四、最佳實踐:資料容器

    如果多個容器需要共享資料(如持久化資料庫、配置檔案或者資料檔案等),可以考慮建立一個特定的資料容器,該容器有1個或多個卷。

    其它容器通過--volumes-from 來共享這個資料容器的卷。

    因為容器的卷本質上對應主機上的目錄,所以這個資料容器也不需要啟動。

    如: docker run --name dbdata myimage echo "data container"

       

    說明:有個卷,容器之間的資料共享比較方便,但也有很多問題需要解決,如許可權控制、資料的備份、卷的刪除等。這些內容後續文章介紹。

    五、特別說明

    掛載宿主機目錄

    當然使用者也可以指定宿主機具體目錄作為資料卷的內容,掛載到容器的"掛載點"。如下所示:下面將宿主機的/host/dir掛載到了容器的/container/dir目錄。

  1. docker run --name vocotainer1 -v /host/dir:/container/dir ubuntu
    							

    但是需要注意的是,宿主機的目錄和容器的目錄必須使用絕對路徑。如果宿主機不存在/host/dir目錄,則會建立一個空資料夾。在/host/dir下的所有檔案和資料夾都可以在容器中在/container/dir下被訪問。如果映象中本來就存在/container/dir資料夾,那麼該資料夾下所有內容都會被刪除,保證與宿主機中資料夾一致

    同時建立多個資料卷

    當然在掛載的時候也可以同時建立多個資料卷,例如下面的命令就建立了兩個資料卷:
    docker run --name vocotainer2 -v co2vo1:/data -v co21vo2:/dir1 ubuntu。同理一次指定多個宿主機的目錄掛載到容器中也是可行的。

    與其他容器共享資料卷(--volumes-from)

    在使用docker run建立並啟動一個新容器時,也可以使用--volumes-from標籤使容器與已有的容器共享資料卷。

    如下所示,下面的命令建立了一個名為vocotainer3的容器,並與vocontainer1共享資料卷。因為vocontainer1的掛載點在/container/dir上,所以如果vocotainer3的掛載點也將會是/container/dir。

  2. docker run --name vocotainer3 -- volumes-from vocontainer1 ubuntu
    							

    通常如果有一些檔案如果需要被多個容器共享,一種常見的做法就是建立一個資料容器(該容器僅僅用來共享資料而不做其他用途),其他容器與之共享資料卷。

    檢視資料卷

    想象一下這樣的場景,假設事先執行了docker run -v /data --name vocontainer1 ubuntu,由於-v標籤沒有指定"資料卷名",那麼為資料卷會隨機生成一個"資料卷名"。那我們如何知道vocontainer1對應的資料卷名是什麼呢??

    本關任務是學習檢視一個資料卷的資訊,要求學習者參照示例,輸出容器container1建立的資料卷的名字。

    相關知識

    檢視資料卷的具體資訊

    在Docker中可以通過docker inspect檢視容器、映象、資料卷等的具體資訊,為了區分,所以最好指定具體型別為容器。通過--type引數可以指定具體型別,而--type container就是宣告具體型別為容器。

    執行docker inspect --type container vocontainer1,執行結果如下所示(已經省略很多無關資訊)

    從上圖中我們可以瞭解到,vocontainer1的資料卷名為b892ce74e55fe1672ec0af69b93661384bbb8334922e1a47c42b122b5885cf0e,資料卷的內容儲存在/var/lib/docker/volumes/b892ce74e55fe1672ec0af69b93661384bbb8334922e1a47c42b122b5885cf0e/_data中,掛載點為/data。資料卷內容可讀寫。

    僅檢視資料卷的名字

    當然如果僅僅只想檢視容器對應的volumeName可以通過以下命令獲得,其中--format用來解析docker inspect輸出的json串,具體在這就不展開了。有興趣的同學可以深入研究。

  3. docker inspect --type container --format='{{range .Mounts}}{{.Name}}{{end}}' containerName|containerId
    							

    例如,如果我想檢視vocontainer1的資料卷名字,我可以執行以下命令:

  4. docker inspect --type container --format='{{range .Mounts}}{{.Name}}{{end}}' vocontainer1
    							

    任務要求

    本關的程式設計任務是補全step3/infovolume.sh指令碼檔案的內容,要求實現檢視一個資料卷的資訊。具體要求如下:

  • 輸出容器container1建立的資料卷的名字

    本關涉及的程式碼檔案step3/infovolume.sh的程式碼框架如下:

    #建立一個容器,並建立一個隨機名字的資料卷掛載到容器的/data目錄

    docker run --name container1 -v /data ubuntu

    #輸出容器container1建立的資料卷的名字

    #********** Begin *********#

    docker inspect --type container --format='{{range .Mounts}}{{.Name}}{{end}}' container1

    #********** End **********#

     

    刪除資料卷

    資料卷是被設計用來持久化資料的,它的生命週期獨立於容器,如果在建立容器時掛載了資料卷,執行docker rm刪除容器時,並不會自動地將容器對應的資料卷刪除掉。在Docker中也不存在垃圾回收這樣的機制來處理沒有任何容器引用的資料卷。

    本關的任務是學習刪除資料卷,要求學習這參照示例,將container1容器對應的資料卷刪除掉。

    相關知識

    刪除資料卷的三種方式

    第一種方式:docker volume rm volumeName

    如果知道想要刪除的資料卷的名字,那麼可以直接使用這種方式去刪除一個資料卷,但是隻會嘗試地去刪除資料卷,如果該資料卷還被容器使用,那麼將刪除不成功,但是如果這個資料卷已經不被任何容器所使用了,那麼資料卷將會被刪除。

    第二種方式:docker rm -v containerId|containerName

    在刪除容器時如果想要將容器對應的資料卷也同時刪除掉,可以使用指定-v標籤,但是值得注意的是,這種方法也只會嘗試地去刪除容器對應的資料卷,如果該資料卷還被其他容器使用,那麼將刪除不成功,但是如果這個資料卷已經不被任何其他容器所使用了,那麼資料卷將會被刪除。

    如果是docker run -v /data --name container1 ubuntu建立的資料卷(沒有顯示指定資料卷名),如果該資料卷沒有被其他任何容器使用,那麼在使用docker rm -v container1嘗試刪除container1容器以及對應的資料卷時,會把資料卷刪除掉。

    但是如果用docker run -v vo1:/data --name container1 ubuntu建立的資料卷(顯示指定資料卷名),也就是建立時指定了"資料卷名",那麼在使用docker rm -v container1嘗試刪除容器以及對應的資料卷時,不會將資料卷刪除,只是解除了資料卷和容器的聯絡。如果要刪除資料卷,還得在上述基礎上,繼續使用docker volume rm vo1

    第三種方式:在建立容器時指定--rm標籤

    如果在建立容器時指定了--rm標籤,那麼在容器處於"終止狀態"時就會刪除容器以及嘗試刪除容器所對應的資料卷。當然在刪除容器對應的資料卷時,如果沒有指定了資料卷名,那麼將刪除對應的資料卷。如果指定了資料卷名,也只是解除了資料卷和容器的聯絡,真正要刪除,還得執行docker volume rm

    刪除無用的資料卷

    在我們的工作中難免在刪除容器時忘記刪除了資料卷,當然我們可以通過docker volume rm一條條地嘗試的去刪除。但是docker提供了更加簡便的方法。也就是:docker volume prune,如果執行這條命令,那麼會將所有沒有被容器使用的資料卷刪除掉。

    docker volume prune

    任務要求

    本關的程式設計任務是補全step4/rmvolume.sh指令碼檔案的內容,要求實現刪除一個資料卷。具體要求如下:

  • 將container1容器對應的資料卷刪除掉。

    本關涉及的程式碼檔案step4/rmvolume.sh的程式碼框架如下:

    #!/bin/bash

    #建立一個名為container1的容器,建立一個資料卷掛載到容器的/data目錄

    docker run -v vo4:/data --name container1 ubuntu

    #刪除container1對應的資料卷

    #********** Begin *********#

    docker rm -v container1

    docker volume rm vo4

     

    #********** End **********#

     

    備份與恢復資料卷

    備份一個資料卷

    首先建立一個容器vocontainer1,並建立了一個名為db1的資料卷,將資料卷掛在到容器的/dbdate目錄

  1. docker run -v db1:/dbdate --name vocontainer1 ubuntu
    							

    下面開始備份一個資料卷。首先進入一個空白目錄,使用--volumes-from建立一個新容器,這樣新容器與dbcontainer1容器共享dbdata掛載目錄,同時把主機上的當前目錄掛載到容器的 /backup 目錄。命令如下:

  2. docker run --volumes-from dbcontainer1 -v $(pwd):/backup ubuntu tar -cvf /backup/backup.tar /dbdata
    							

    容器啟動後,使用了tar 命令來將 dbdata目錄壓縮,並儲存在 /backup/backup.tar檔案中,由於主機的當前目錄掛載在容器的/backup目錄下,而繫結掛載的兩個目錄的內容完全保持一致,所以相當於將dbcontainer1資料卷的內容壓縮後備份到了宿主機的當前目錄了

    恢復一個資料卷

    假設一不小心名為db1的資料卷給刪除掉了,可以這麼恢復:

    首先建立一個帶有空資料卷的容器dbcontainer2,掛載目錄為/dbdata,資料卷名為db1。命令如下所示:

  3. docker run -v db1:/dbdata --name dbcontainer2 ubuntu /bin/bash
    							

    然後進入之前儲存backup.tar的宿主機目錄,在該目錄下執行下面命令,該命令建立一個新容器,新容器與dbcontainer2容器共享dbdata掛載目錄,同時將主機的當前目錄掛載的容器的/backup中。

  4. docker run --volumes-from dbcontainer2 -v $(pwd):/backup busybox tar -xvf /backup/backup.tar -C /dbdata
    							

    啟動容器時,使用tar命令將資料卷的備份檔案backup.tar解壓到/dbdata目錄,由於該容器與dbcontainer2容器共享一個資料卷,也就相當於將backup.tar解壓到了dbcontainer2/dbdata目錄。
    又因為
    dbcontainer2將名為db1的資料卷掛載到了/dbdata上,所以實質上就將db1的資料卷內容完全恢復了!

    任務要求

    本關的程式設計任務是補全step5/reback.sh指令碼檔案的內容,要求實現備份和恢復資料卷。具體要求如下:

  • 將名為vo1的資料卷備份
  • 使用備份檔案恢復vo1資料卷

    本關涉及的程式碼檔案step5/reback.sh的程式碼框架如下:

     

    #!/bin/bash

    # 建立一個vo1的資料卷,並在資料卷中新增1.txt檔案

    docker run --name vocontainer1 -v vo1:/dir1 ubuntu touch /dir1/1.txt

    #1.將vo1資料卷的資料備份到宿主機的/newback中,將容器的/backup路徑掛載上去,並將容器內/dir1資料夾打包至/backup/backup.tar

    #********** Begin *********#

    docker run --volumes-from vocontainer1 -v /newback:/backup ubuntu tar -cvf /backup/backup.tar /dir1

     

    #********** End **********#

    #刪除所有的容器以及它使用的資料卷

    docker rm -vf $(docker ps -aq)

    docker volume rm vo1

    #在次建立一個vo1的資料卷

    docker run -itd --name vocontainer2 -v vo1:/dir1 ubuntu /bin/bash

    #2.將儲存在宿主機中備份檔案的資料恢復到vocontainer2的/中

    #********** Begin *********#

    docker run --volumes-from vocontainer2 -v /newback:/backup ubuntu tar -xvf /backup/backup.tar -C /

     

     

    #********** End **********#

相關文章