---
# System prepended metadata

title: Chapter 4. Working with storage and volumes
tags: [Docker, docker in action 2ed]

---

# 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 大小。



