# Chapter 4. Working with storage and volumes ###### tags: `Docker`, `docker in action 2ed` ## 範圍 - Introducing mount points - How to share data between the host and a container - How to share data between containers - Using temporary, in-memory filesystems - Managing data with volumes - Advanced storage with volume plugins ## 前言 ### 使用情境 - Application with database - 資料會存在哪? - 當 Container 關掉的時候,資料的走向會是如何? - 要如何保留在 Container 上的資料? - Log files - 要如何去寫 log file? - 要如何 access log file for trouble shooting ## 4.1 File trees and mount points - Linux 的整個 storage 就是一個 single file tree `/` - Mount point - Storage devices (disk partitions or USB disk partitions) are attached to specific locations in that tree. - ![](https://i.imgur.com/WSjfvSE.png) - 透過 mount point 可以掛載我們需要儲存的位置,而 Container 同時也是這種 file tree,因此類似的概念也可以套用在 Container 上來儲存所需的資料 - common types of storage mounted into containers - Bind mounts - In memory storage - Docker volumes ![](https://i.imgur.com/bkXcEpx.png) using the `--mount` flag on the `docker run` and `docker create` sub-commands ## 4.2 Bind mounts - 在 `docker run` 的同時,告訴 container 某些檔案的路徑要 bind 到 host 上的哪裡 - 範例 - 假設我們起一組 Nginx container,並將 Nginx 的 default.conf bind 到 host 端上的設定檔,同時將 Nginx 的 access log 也 bind 到 host 端上的 log file - ![](https://i.imgur.com/ikhrpqn.png) ```bash= # 起一個 Nginx 並將設定檔與 log file 用 bind mount 的方式掛載 $ CONF_SRC=~/example.conf; \ CONF_DST=/etc/nginx/conf.d/default.conf; \ LOG_SRC=~/example.log; \ LOG_DST=/var/log/nginx/custom.host.access.log; $ docker run -d --name diaweb \ --mount type=bind,src=${CONF_SRC},dst=${CONF_DST} \ --mount type=bind,src=${LOG_SRC},dst=${LOG_DST} \ -p 8889:80 \ nginx:latest $ curl localhost:8889,Nginx 會把 request 寫到 access log 裡 # 確認掛載在 host 上的 example.log 有沒有資料 ``` - 可以加上 `readonly=true`,以防 container 修改到 host 上的設定檔 - bind mount 的缺點 - 如果使用 bind mount,會讓 container 不這麼 portable(要確保每台 host 都要有這些路徑) - 容易與其他 container 造成 conflict(都設定相同的路徑) - 盡量避免使用 bind mount ## 4.3 In memory storage - 當 container 有某些較為敏感的資料需要被儲存時(不需要被寫入 Container 內),通常會選用此方法 - `--mount type=tmpfs,dst=/tmp` - 當 container 停止時,`tmpfs` 也會被移除 - 範例 ```bash= $ docker run --rm \ --mount type=tmpfs,dst=/tmp \ --entrypoint mount \ alpine:latest -v ``` - output: `tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noexec,relatime)` - read write privilege - ignore suid(suid 為僅檔案擁有者在執行檔案時才有權限,只適合 binary file) - http://linux.vbird.org/linux_basic_train/unit05.php#5.3 - no device can interrupt - no device can execute - File access times will be updated - 也可以加入 `tmpfs-size=16k`, `tmpfs-mode=1770` 來限制 tmpfs 的 size or tmpfs 在 container 內的權限(default 是 world-writable 1777) ```bash= $ docker run -d \ -it \ --name tmptest \ --mount type=tmpfs,destination=/app,tmpfs-mode=1770,tmpfs-size=16k \ nginx:latest ``` - 透過 `docker container inspect tmptest` 來查看 mount 狀況 ```jsonc= "Mounts": [ { "Type": "tmpfs", "Target": "/app", "TmpfsOptions": { "SizeBytes": 16384, "Mode": 1016 } } ] // By 逸🐟大大 // 1770 // -> 1 111 111 000 // -> 1016 ``` ## 4.4 Docker volumes - Volume 是由 Docker 建立與管理的 - 可用 `docker volume create` 指令直接建立,或可以在建立 Container 時一併建立 - Volume 建立時存放在主機的目錄中 - 範例 - 建立一個名為 location-example 的 volume ```bash= $ docker volume create \ --driver local \ --label example=location \ location-example ``` - 透過 inspect 可以查看 volume 的資訊 ```bash= $ docker volume inspect location-example ``` ### 4.4.1 Volumes provide container-independent data management - 適合使用 Volumes 的情境 - Database software versus database data - Web application versus log data - Data processing application versus input and output data - Web server versus static content - Products versus support tools - >images are appropriate for packaging and distributing relatively static files such as programs; volumes hold dynamic data or specializations - polymorphic(多型態的) and composable(可組成的) tools 都很適合使用 Volumes ### 4.4.2 Using volumes with a NoSQL database - 以 Apache Cassandra project 為範例,測試 Volume 可以被重複利用,掛載到另一個 Container ![](https://i.imgur.com/NpOMQRC.png) ```bash= # 先建立需要的 volume $ docker volume create \ --driver local \ --label example=cassandra \ cass-shared # 建立 cass1 $ docker run -d \ --volume cass-shared:/var/lib/cassandra/data \ --name cass1 \ cassandra:2.2 # 建立 CQLSH(Cassandra Client) $ docker run -it --rm \ --link cass1:cass \ cassandra:2.2 cqlsh cass ## 新增一筆資料 $ create keyspace docker_hello_world with replication = { 'class' : 'SimpleStrategy', 'replication_factor': 1 }; ## 確定資料存在 $ select * from system.schema_keyspaces where keyspace_name = 'docker_hello_world'; ## 離開 $ quit # 刪除 cass1 並建立 cass2 將同樣的 Volume 也 mount 進去,同時使用同樣的方式去檢查資料是否存在 $ docker stop cass1 $ docker rm -vf cass1 $ docker run -d \ --volume cass-shared:/var/lib/cassandra/data \ --name cass2 \ cassandra:2.2 $ docker run -it --rm \ --link cass2:cass \ cassandra:2.2 \ cqlsh cass $ select * from system.schema_keyspaces where keyspace_name = 'docker_hello_world'; $ quit $ docker rm -vf cass2 cass-shared ``` ## 4.5 Shared mount point and sharing files - 比較 bind mount 和 Volume ```bash= # Bind Mount ## 建立 host 上要 mount 進去 Container 的資料夾 $ LOG_SRC=~/web-logs-example $ mkdir ${LOG_SRC} ## 起一個 Container 可以寫入 logA $ docker run --name plath -d \ --mount type=bind,src=${LOG_SRC},dst=/data \ dockerinaction/ch4_writer_a ## 透過 Container 查看該 log $ docker run --rm \ --mount type=bind,src=${LOG_SRC},dst=/data \ alpine:latest \ head /data/logA ## 透過 host 端查看該 log $ cat ${LOG_SRC}/logA $ docker rm -f plath # Volume ## 建立 Volume named logging-example $ docker volume create \ --driver local \ logging-example ## 與上面步驟一樣,換成 Volume mount 方式 $ docker run --name plath -d \ --mount type=volume,src=logging-example,dst=/data \ dockerinaction/ch4_writer_a $ docker run --rm \ --mount type=volume,src=logging-example,dst=/data \ alpine:latest \ head /data/logA ## 透過 host 查看 $ cat /var/lib/docker/volumes/logging-example/_data/logA $ docker rm -f plath ``` - 差別在於 Volume 不需要知道任何 host 端上的路徑 ### 4.5.1 Anonymous volumes and the volumes-from flag - Volume 可以是匿名的 - it is assigned a unique identifier - 在 `docker run` 同時,會自動幫我們建立起 Volume ```bash= $ docker run --name fowler \ --mount type=volume,dst=/library/PoEAA \ --mount type=bind,src=/tmp,dst=/library/DSL \ alpine:latest \ echo "Fowler collection created." $ docker volume ls ``` - 也可以透過 `--volumes-from` 來複製 mount destinations ```bash= $ docker run --name knuth \ --mount type=volume,dst=/library/TAoCP.vol1 \ --mount type=volume,dst=/library/TAoCP.vol2 \ --mount type=volume,dst=/library/TAoCP.vol3 \ --mount type=volume,dst=/library/TAoCP.vol4.a \ alpine:latest \ echo "Knuth collection created" $ docker run --name reader \ --volumes-from fowler \ --volumes-from knuth \ alpine:latest ls -l /library/ docker inspect --format "{{json .Mounts}}" reader ``` - 三種情境下,不能使用 `--volumes-form` tag - a shared volume mounted to a different location - the volume sources conflict with each other ```bash= $ docker run --name chomsky --volume /library/ss \ alpine:latest echo "Chomsky collection created." $ docker run --name lamport --volume /library/ss \ alpine:latest echo "Lamport collection created." $ docker run --name student \ --volumes-from chomsky --volumes-from lamport \ alpine:latest ls -l /library/ $ docker inspect -f "{{json .Mounts}}" student # => 發現只會有一個被 mount 進去 ``` - need to change the write permission of a volume ## 4.6 Cleaning up volumes - Anonymous volumes can be deleted via the `docker run --rm` or `docker rm -v` flags - or `docker volume remove f9db93b59ceb01927ed5f954f9170c3aa2699b4975a0bbfb0a7803df6f2c302c` - `docker rm -v` Remove the volumes associated with the container - named volumes must always be deleted manually - This behavior can be helpful when containers are running jobs that collect partitioned or periodic data ```bash= $ for i in amazon google microsoft; \ do \ docker run --rm \ --mount type=volume,src=$i,dst=/tmp \ --entrypoint /bin/sh \ alpine:latest -c "nslookup $i.com > /tmp/results.txt"; \ done $ docker volume list $ docker volume remove amazon google microsoft ``` - 唯一的限制:沒在被使用的 Volume 才能被移除 - 也可以透過 `docker volume prune` 來清除沒在使用的 Volume ## 4.7 Advanced storage with volume plugins - 可以透過一些 plugin 來 enhance docker volume - REX-Ray (https://github.com/rexray/rexray) - provides volumes on several cloud and on-premises storage platforms ## Summary ![](https://i.imgur.com/kZuddl1.png) - Bind mount 可存放在 host 上的任何地方,任何人都可以隨時修改。 - tmpfs mount 只存放在 memory 中,不會寫入 host 的 fs。 - Volume 存放在 host 中由 Docker 管理的地方(`/var/lib/docker/volumes/`)。非 Docker 者不應該修改檔案系統中的這一部分。 ## Docker Layers - 使用 storage drivers 儲存及管理 layers,如: - overlay2 - overlay - aufs - 一個 image 由一至多個 layer 組成。 - Every instruction that modify the filesystem in Dockerfile adds up a layer. ```dockerfile= # syntax=docker/dockerfile:1 FROM ubuntu:18.04 LABEL org.opencontainers.image.authors="org@example.com" COPY . /app RUN make /app RUN rm -r $HOME/.cache CMD python /app/app.py ``` - 每個 Layer 紀錄 data 差異變化 (like Git commit) - `docker pull` 時,各 layer 各自獨立拉取 ![](https://i.imgur.com/VySuSfK.png) - 每個 container 都基於 image 原有之唯讀 layers ,建立一層「薄薄的」可寫 layer: - Container size on disk (`docker ps -s`) ![](https://i.imgur.com/UPksAce.png) - 圖示: ![](https://i.imgur.com/7lOyaog.png) ![](https://i.imgur.com/S3YeCld.png) - 某個 container 需讀寫 image 內有之檔案時: - 需讀取,會從最上層往下尋找,並讀取有檔案最新版本的 layer 裡面的檔案,各 container 共同讀取同一檔案。 - 需寫入,會採用 copy-on-write (CoW) strategy,各 container 找到檔案後複製一份來當前 layer 使用。 ### Summary - image 由 Dockerfile 生成,每行有 I/O 指令的結果儲存於一個 layer。 - layer 可達到孤立環境效果,image 內原有的檔案不會有任何改變,各 container 間亦互不影響,若 container 消滅,則其自有之 layer 亦消滅,其改動亦不復存在。 - 使用 stroage driver 使 I/O 效率比原生差,但 CoW 使唯讀檔案時能較有效使用。 - 若有大量寫入需求,應考慮使用 volume 。 - 同樣的 layer 在不同 image 之間可共用,善用其 cache 特性復用 layer 可增進 pull 效率。 - 各 layer 紀錄的 filesystem 變化都會保留,有存在過就會影響 image 大小。