Docker中的文件和數(shù)據(jù)
Docker鏡像通過UnionFS進(jìn)行分層存儲(可以通過docker history查看),一個Docker鏡像本質(zhì)上是一組文件,默認(rèn)存儲在/var/lib/docker/<storage-driver>中。
而Docker 容器其實(shí)是在鏡像的最上層加了一層讀寫層,通常也稱為容器層。在運(yùn)行中的容器里所做的改動,如寫新文件、修改已有文件、刪除文件等操作其實(shí)都寫到了容器層。容器層刪除了,最上層的讀寫層跟著也刪除了,改動自然也丟失了。
若要持久化這些改動,一是通過 docker commit <containerId> [repository[:tag]]將當(dāng)前容器保存成為一個新鏡像。若想將持久化數(shù)據(jù),或是多個容器間共享數(shù)據(jù),需將數(shù)據(jù)存儲主機(jī)的文件系統(tǒng)中。

數(shù)據(jù)卷(Volume) - 為什么是特殊的目錄?
- 處于UFS(Union File System)之外
- 主機(jī)文件系統(tǒng)中的普通目錄
- 在卷上的I/O性能應(yīng)與主機(jī)上的完全相同
- 卷的內(nèi)容不包含在Docker鏡像中
- 任何對卷內(nèi)容的修改不是鏡像的一部分
- 可被多個容器共享和重用
- 持久化數(shù)據(jù)(即使容器已被刪除)
如何初始化卷?

-
從主機(jī)上掛載卷: 與容器共享主機(jī)目錄
- Docker卷允許在主機(jī)操作系統(tǒng)和容器之間共享任意目錄/文件
- 需要確保在主機(jī)操作系統(tǒng)上外部配置文件是可訪問的
- 動態(tài)伸縮的約束
-
從其他容器掛載卷(數(shù)據(jù)容器):數(shù)據(jù)容器會作為一個接口, 向其他容器提供訪問卷的途徑
- 容器可處理一組卷
- 能限制訪問權(quán)限,如只讀或讀寫:
docker run --volume-from some-container - 在多個容器間共享卷
- 卷內(nèi)容會保持同步
- 底層使用相同的目錄
使用數(shù)據(jù)容器的場景?
-
存儲持久化數(shù)據(jù)庫
- 啟動數(shù)據(jù)容器
- 啟動基于數(shù)據(jù)庫鏡像的數(shù)據(jù)庫容器
- 外部化數(shù)據(jù)目錄 - 通過從數(shù)據(jù)容器掛載卷
- 安排數(shù)據(jù)容器的備份計劃
配置文件
數(shù)據(jù)文件
實(shí)踐操作
- 訪問Docker Host虛擬機(jī)
因?yàn)樵贛acOS上的Docker實(shí)際是運(yùn)行在一個虛擬環(huán)境下的,所以
/var/lib/docker路徑無法在Mac上找到。我們需要連接到Docker Host虛擬機(jī),才能查看到Docker的默認(rèn)目錄。
$ screen ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/tty
linuxkit-025000000001:~# ls /var/lib/docker/
builder containers network plugins swarm trust
containerd image overlay2 runtimes tmp volumes
linuxkit-025000000001:~# ls /var/lib/docker/volumes/
metadata.db
- 在"主機(jī)"上掛載一個卷
# 創(chuàng)建匿名卷
$ docker volume create
4997a11f62aff64766a2ce490debf1770a0c3f29dae57326579fefd0c3668673
# 創(chuàng)建卷
$ docker volume create my-volume
my-volume
這樣創(chuàng)建的卷會默認(rèn)在
/var/lib/docker/volumes/下,可以使用docker volume進(jìn)行統(tǒng)一管理
- 使用卷
# 啟動容器,掛載my-volume卷到容器的/data;
$ docker run -dit --name c-my-volume -v my-volume:/data busybox
a9ee3a9c98009a64865eb98da92f7bdd052634e44861f59d42051fe7015dbcd0
# 查看是否掛載成功;
$ docker exec c-my-volume ls | grep data
data
# 啟動容器,自動生成匿名卷并掛載到容器的/data;
$ docker run -dit --name c-anonymous -v :/data busybox
ede60b7d7c514ef3b8d5f4d94cfa994b19da7680f13a5539ac64948fe65deb0e
# 查看匿名卷對應(yīng)的目錄;
$ docker inspect -f {{.Mounts}} c-anonymous
- 向卷中寫入數(shù)據(jù)
- 宿主機(jī)(Docker Host)向卷寫入數(shù)據(jù)
linuxkit-025000000001:/var/lib/docker/volumes/my-volume/_data# touch test
linuxkit-025000000001:/var/lib/docker/volumes/my-volume/_data# echo "Hello" > test
- 容器卷中檢查修改文件
$ docker exec c-my-volume cat /data/test
Hello
$ docker exec -it c-my-volume sh
/ # echo "Hi" > /data/test
- 宿主機(jī)(Docker Host)檢查
linuxkit-025000000001:/var/lib/docker/volumes/my-volume/_data# cat test
Hi
- 使用數(shù)據(jù)容器
- Dockerfile
FROM busybox
VOLUME /config # 聲明卷的掛載點(diǎn)
CMD ls /config
- Shell
$ docker build -t data-container -f Dockerfile-2
# 創(chuàng)建數(shù)據(jù)容器
$ docker create --name data-container data-container
610dc7f0df53adec4a8ee02e7f8f2a4856a29f5be3007c1fb69e9863d7960af1
$ docker inspect -f {{.Mounts}} data-container
[{volume e715b7a35b3f70b536a5eab136815d0deb4926b7b782b99168471c648ffc5ffa /var/lib/docker/volumes/e715b7a35b3f70b536a5eab136815d0deb4926b7b782b99168471c648ffc5ffa/_data /config local true }]
# 啟動服務(wù)容器,引用數(shù)據(jù)容器
$ docker run -dit --name service-container --volumes-from data-container busybox
# 文件系統(tǒng)中修改
linuxkit-025000000001:/var/lib/docker/volumes/e715b7a35b3f70b536a5eab136815d0deb4926b7b782b99168471c648ffc5ffa/_data# touch test
# 查看服務(wù)容器
$ docker exec service-container ls /config
- 運(yùn)行時添加文件
# 復(fù)制文件到docker容器中
$ touch test-2
$ docker cp test-2 service-container:/config/
# 文件系統(tǒng)中查看
linuxkit-025000000001:/var/lib/docker/volumes/e715b7a35b3f70b536a5eab136815d0deb4926b7b782b99168471c648ffc5ffa/_data# ls
test test-2
Mount掛載
文件掛載有多種方式,Volume卷只是提供了統(tǒng)一的管理方式。我們可以使用mount進(jìn)行自定義配置,包括綁定任意文件目錄,設(shè)置讀寫規(guī)則等。
-
--mount:由多個鍵值對組成,由逗號分隔,每一個由 <key>=<value> 元祖組成。鍵值對沒有順序。-
type,可以是 bind,volume,tmpfs。 -
source,主機(jī)上的文件或目錄的路徑。可能用 src,source 指定。 -
destination,容器中的文件或目錄的路徑。可能用 destination,dst,target 指定。 -
readonly,如果存在,將更改 Propagation,可以是一個 rprivate。 -
consistency,如果存在,可以是 consistent,delegated 或 cached,只在 Mac 版有效。 -
--mount標(biāo)志不支持 z 或 Z 修改 selinux。
-
-
-v 絕對路徑:容器內(nèi)路徑:默認(rèn)使用bind模式掛載目錄
- 直接掛載主機(jī)目錄
# 啟動容器,掛載本機(jī)目錄(絕對路徑)到容器的/data;
$ docker run -dit --name c-temp-path -v /temp:/data busybox
a9ee3a9c98009a64865eb98da92f7bdd052634e44861f59d42051fe7015dbcd0
# 查看是否掛載成功;
$ docker exec c-temp-path ls /data
Shared
username
$ docker inspect c-temp-path
...
"Mounts": [
{
"Type": "bind",
"Source": "/Users",
"Destination": "/data",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
...
可以掛載任意路徑,但是無法用volume進(jìn)行管理。
卷插件
卷插件允許第三方數(shù)據(jù)管理解決方案接入
在不修改應(yīng)用的情況下,可以用另一個插件替換當(dāng)前插件
- Flocker
- Convoy
- Blockbridge
- GlusterFS
- Netshare
- Openstorage
卷的應(yīng)用場景
配置文件: 一次構(gòu)建,多處部署
- 將配置文件植入到容器
- 在 Dockerfile 中使用 'COPY' 指令
- 在鏡像構(gòu)建時使用 'RUN' 指令修改配置
- 最簡方案(靜態(tài)配置的情況下)
- 任何對配置文件的修改都需要重新構(gòu)建鏡像
- 使用環(huán)境變量, 動態(tài)傳入到容器
- 當(dāng)啟動容器時傳入環(huán)境變量:
docker run -e USERNAME=zzz PASSWORD=... - 簡單配置情況下運(yùn)行良好
- 當(dāng)啟動容器時傳入環(huán)境變量:
- 查詢鍵-值存儲
- 利用鍵-值存儲獲取配置信息
- 很多可選項(xiàng),如 consul, etcd, zookeeper
- 使得配置更加動態(tài)化
- 引入外部依賴,可用性更加重要
- 從主機(jī)上掛載卷
- 從其他容器掛載卷
Tips
- Volumes!=Persistance(卷并不意味著持久化)
- 卷并不會被垃圾回收
- 針對有狀態(tài)的容器(如數(shù)據(jù)庫)和相應(yīng)的數(shù)據(jù)容器,可以使用相同的鏡像
- 為數(shù)據(jù)容器制定備份計劃
- 將帶有敏感數(shù)據(jù)的容器放在的當(dāng)前主機(jī)上
- 盡可能緩存鏡像,因?yàn)橄螺d很耗時
網(wǎng)絡(luò)管理

Docker 為我們提供了四種不同的網(wǎng)絡(luò)模式
bridge:未指定網(wǎng)絡(luò)時, 容器默認(rèn)在bridge網(wǎng)絡(luò)下container:CONTAINER_NAME:重用某容器的網(wǎng)絡(luò)配置(ip/mac),等同于在同一容器內(nèi)host:(唯一)共享了宿主機(jī)的網(wǎng)絡(luò), 使用相同的ipnone:(唯一)關(guān)閉所有網(wǎng)絡(luò)連接
docker network ls中包含了默認(rèn)的host、none和一個bridge網(wǎng)絡(luò),當(dāng)啟動的容器沒有指定網(wǎng)絡(luò)時,會默認(rèn)加入到bridge網(wǎng)絡(luò)中
Bridge 網(wǎng)橋模式

- 啟動Docker時會在主機(jī)上創(chuàng)建一個名為
docker0的虛擬網(wǎng)橋接口 - docker0 會為每一個容器分配一個新的 IP 地址并將 docker0 的 IP 地址設(shè)置為默認(rèn)的網(wǎng)關(guān)
- docker0 通過 iptables 中的配置與宿主機(jī)器上的網(wǎng)卡相連,所有符合條件的請求都會通過 iptables 轉(zhuǎn)發(fā)到 docker0 并由網(wǎng)橋分發(fā)給對應(yīng)的機(jī)器
-
端口映射通過修改 iptables 再將對端口的訪問重定向到 docker0,實(shí)現(xiàn)對容器的訪問
network-bridge-forward.png
# 啟動容器并映射端口
$ docker run -dit --name bridge-container -p 5000 busybox
cb52033e0b8f9c5a8e4c18e5a1fe0ac6d01809393575c1c85b3cdd5aacea437f
# 查看容器服務(wù)的端口
$ docker port bridge-container
5000/tcp -> 0.0.0.0:32771
# 查看容器的IP地址
$ docker inspect --format '{{ .NetworkSettings.IPAddress }}' bridge-container
172.17.0.2
# 查看默認(rèn)網(wǎng)絡(luò)配置和其中的容器
$ docker network inspect bridge
...
"IPAM": {
...
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Containers": {
"cb52033e0b8f9c5a8e4c18e5a1fe0ac6d01809393575c1c85b3cdd5aacea437f": {
"Name": "bridge-container",
"EndpointID": "b4af63f885a783563aeabdb70806983cb519a497835209686954a599fb81bc7a",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
},
...
網(wǎng)絡(luò)和Links
-
docker run --link <target container> <image>- Links只能對同一主機(jī)上的容器生效
- 容器在重新部署時會斷開與其他容器的連接
- 容器在創(chuàng)建后才能被相互Link
容器網(wǎng)絡(luò)模型 - 組件

- 沙箱
- 包含容器網(wǎng)絡(luò)堆棧配置信息:容器的接口、路由表和 DNS 設(shè)置
- 可能包含多網(wǎng)絡(luò)的多個端點(diǎn):連接的每一個網(wǎng)絡(luò)都有一個不同的端點(diǎn)
- 網(wǎng)絡(luò)
- 一組端點(diǎn)之間能夠相互直接交流
- 實(shí)現(xiàn)可以是Linux網(wǎng)橋或重疊網(wǎng)(Overlay)
- 容器
- 容器能夠作為任意一個或多個網(wǎng)絡(luò)的一部分
- 能夠同時對接橋接網(wǎng)絡(luò)和重疊網(wǎng)絡(luò)
- 在某個特定網(wǎng)絡(luò)下的所有容器都能夠自由地互相通信
- 多個網(wǎng)絡(luò)有助于分散容器之間的流量傳輸
- 多個端點(diǎn)允許一個容器加入到多個網(wǎng)絡(luò)中
用戶可創(chuàng)建的網(wǎng)絡(luò)有2種類型:
- bridge: 用于同一主機(jī)的不同容器進(jìn)行連接
- overlay: 跨主機(jī)網(wǎng)絡(luò)連接
網(wǎng)絡(luò)實(shí)戰(zhàn)
- 默認(rèn)的
bridge網(wǎng)絡(luò)也是網(wǎng)橋網(wǎng)絡(luò),應(yīng)用場景相同。
- 創(chuàng)建新的Bridge網(wǎng)絡(luò)
# 創(chuàng)建2個Bridge網(wǎng)絡(luò)并創(chuàng)建容器連接網(wǎng)絡(luò)
$ docker network create my-net1
$ docker run -itd --net=my-net1 --name service0 busybox
$ docker run -itd --net=my-net1 --name service1 busybox
$ docker network create my-net2
$ docker run -itd --net=my-net2 --name service2 busybox
# 查看網(wǎng)絡(luò), 獲取容器ip地址等信息
$ docker network ls
$ docker network inspect my-net1
$ docker network inspect my-net2
- 容器進(jìn)行網(wǎng)絡(luò)訪問
# 同網(wǎng)絡(luò)容器Ping:成功
$ docker exec service0 ping service1 # 172.18.0.2
# 不同網(wǎng)絡(luò)容器Ping:失敗
$ docker exec service1 ping service2
$ docker exec service1 ping 172.19.0.2 # 無連接
- 為容器配置新的網(wǎng)絡(luò)
# 將容器連接到另一個網(wǎng)絡(luò)
$ docker network connect my-net2 service1
$ docker network inspect my-net2
# 此時service1在2個網(wǎng)絡(luò)各自有一個ip
$ docker exec service0 ping service1 # 172.18.0.3
$ docker exec service1 ping service0 # 172.18.0.2
$ docker exec service1 ping service2 # 172.19.0.2
$ docker exec service2 ping service1 # 172.19.0.3
此時,
service1容器中的沙箱就具有2個Endpoints,對應(yīng)不同的網(wǎng)絡(luò)。訪問時會根據(jù)網(wǎng)絡(luò)配置決定應(yīng)該走哪一個網(wǎng)絡(luò)進(jìn)行傳輸。
- 使用Host網(wǎng)絡(luò)
$ docker run -itd --net=host --name service-host busybox
$ docker inspect service-host
這時候容器就和本機(jī)運(yùn)行的進(jìn)程相同了,只要暴露了端口就能通過localhost:port進(jìn)行訪問。
- 使用None網(wǎng)絡(luò):即關(guān)閉所有網(wǎng)絡(luò)連接
網(wǎng)絡(luò)插件
- 允許第三方的容器網(wǎng)絡(luò)方案連接到容器網(wǎng)絡(luò)中
- 降低了不同類型主機(jī)上容器通信的難度
- 擴(kuò)展由Docker提供的核心網(wǎng)絡(luò)功能
可用的網(wǎng)絡(luò)插件來自于:
- Weave
- Project Calico
- Nuage Networks
- Cisco
- VMware
- Microsoft
- Midokura
容器安全
在容器中的root權(quán)限對應(yīng)宿主機(jī)的一個普通用戶, 但當(dāng)容器開啟--privileged特權(quán)后, 就可以使用root對宿主機(jī)進(jìn)行操作
# 重要文件映射到容器中
$ docker run -v /:/hostfs busybox cat /hostfs/etc/paths
# 部分文件不可見
# 容器中修改主機(jī)重要文件
$ docker run -it -v /:/hostfs busybox touch /hostfs/threat-on-the-way
# 無法創(chuàng)建
安全最佳實(shí)踐
- 主機(jī):
- 保持內(nèi)核及時更新
- 增強(qiáng)主機(jī)保護(hù)
- 保持Docker及時更新
- Docker守護(hù)進(jìn)程:
- 只允許受信用戶控制Docker守護(hù)進(jìn)程
- 不使用不受信的鏡像倉庫
- 必要時請為Docker守護(hù)進(jìn)程應(yīng)用TLS認(rèn)證
- 限制容器之間的網(wǎng)絡(luò)通信
- 鏡像:
- 在Dockerfile中為容器創(chuàng)建一個非root用戶
- 以非root用戶運(yùn)行容器進(jìn)程
- 只使用受信的基礎(chǔ)鏡像
- 僅安裝必要的包
- 重新構(gòu)建鏡像時需要包含安全補(bǔ)丁
- 容器運(yùn)行時
- 限制容器使用Linux內(nèi)核能力
- 不要使用privileged容器
- 限制容器上的資源使用
- 指定容器重啟策略為on-failure
- 使用AppArmor/SELinux保證額外的安全層
- 其他
- 為Docker掛載點(diǎn)創(chuàng)建單獨(dú)的分區(qū)
- 不要到產(chǎn)品環(huán)境中使用任何開發(fā)者工具(boot2docker, kinematic)
- 建立本地倉庫鏡像
- 使用供應(yīng)商最支持的存儲驅(qū)動程序
- aufs是唯一的允許容器共享執(zhí)行文件的存儲驅(qū)動,但可能會導(dǎo)致嚴(yán)重的內(nèi)核崩潰
- 為Docker守護(hù)進(jìn)程設(shè)置受限的控制資源權(quán)限(ulimit)
- 由最小基礎(chǔ)鏡像開始(Busybox, Alpine)
多主機(jī)部署和管理
Docker本身只關(guān)注單主機(jī)(Docker Host),對鏡像、容器進(jìn)行管理。在多主機(jī)部署時,主要是利用其它組件、工具進(jìn)行服務(wù)發(fā)現(xiàn)、服務(wù)注冊、網(wǎng)絡(luò)傳輸。只是由外部程序來接管多主機(jī)的協(xié)調(diào)工作,對于Docker來說是透明的。例如:
- 將Docker容器放在Host網(wǎng)絡(luò)/映射Docker容器的端口到Host上
- 利用服務(wù)發(fā)現(xiàn)(Consul/Eureka)自動發(fā)布、獲取IP:PORT
- 利用Http通信
