Docker中的文件和数据
Docker镜像通过UnionFS进行分层存储(可以通过docker history
查看),一个Docker镜像本质上是一组文件,默认存储在/var/lib/docker/<storage-driver>
中。
而Docker 容器其实是在镜像的最上层加了一层读写层,通常也称为容器层。在运行中的容器里所做的改动,如写新文件、修改已有文件、删除文件等操作其实都写到了容器层。容器层删除了,最上层的读写层跟着也删除了,改动自然也丢失了。
若要持久化这些改动,一是通过 docker commit <containerId> [repository[:tag]]
将当前容器保存成为一个新镜像。若想将持久化数据,或是多个容器间共享数据,需将数据存储主机的文件系统中。
数据卷(Volume) - 为什么是特殊的目录?
- 处于UFS(Union File System)之外
- 主机文件系统中的普通目录
- 在卷上的I/O性能应与主机上的完全相同
- 卷的内容不包含在Docker镜像中
- 任何对卷内容的修改不是镜像的一部分
- 可被多个容器共享和重用
- 持久化数据(即使容器已被删除)
如何初始化卷?
volume-data-container.png-
从主机上挂载卷: 与容器共享主机目录
- Docker卷允许在主机操作系统和容器之间共享任意目录/文件
- 需要确保在主机操作系统上外部配置文件是可访问的
- 动态伸缩的约束
-
从其他容器挂载卷(数据容器):数据容器会作为一个接口, 向其他容器提供访问卷的途径
- 容器可处理一组卷
- 能限制访问权限,如只读或读写:
docker run --volume-from some-container
- 在多个容器间共享卷
- 卷内容会保持同步
- 底层使用相同的目录
使用数据容器的场景?
-
存储持久化数据库
- 启动数据容器
- 启动基于数据库镜像的数据库容器
- 外部化数据目录 - 通过从数据容器挂载卷
- 安排数据容器的备份计划
-
配置文件
-
数据文件
实践操作
- 访问Docker Host虚拟机
因为在MacOS上的Docker实际是运行在一个虚拟环境下的,所以
/var/lib/docker
路径无法在Mac上找到。我们需要连接到Docker Host虚拟机,才能查看到Docker的默认目录。
$ 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
- 在"主机"上挂载一个卷
# 创建匿名卷
$ docker volume create
4997a11f62aff64766a2ce490debf1770a0c3f29dae57326579fefd0c3668673
# 创建卷
$ docker volume create my-volume
my-volume
这样创建的卷会默认在
/var/lib/docker/volumes/
下,可以使用docker volume
进行统一管理
- 使用卷
# 启动容器,挂载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
# 查看匿名卷对应的目录;
$ docker inspect -f {{.Mounts}} c-anonymous
- 向卷中写入数据
- 宿主机(Docker Host)向卷写入数据
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
- 宿主机(Docker Host)检查
linuxkit-025000000001:/var/lib/docker/volumes/my-volume/_data# cat test
Hi
- 使用数据容器
- Dockerfile
FROM busybox
VOLUME /config # 声明卷的挂载点
CMD ls /config
- Shell
$ docker build -t data-container -f Dockerfile-2
# 创建数据容器
$ 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 }]
# 启动服务容器,引用数据容器
$ docker run -dit --name service-container --volumes-from data-container busybox
# 文件系统中修改
linuxkit-025000000001:/var/lib/docker/volumes/e715b7a35b3f70b536a5eab136815d0deb4926b7b782b99168471c648ffc5ffa/_data# touch test
# 查看服务容器
$ docker exec service-container ls /config
- 运行时添加文件
# 复制文件到docker容器中
$ touch test-2
$ docker cp test-2 service-container:/config/
# 文件系统中查看
linuxkit-025000000001:/var/lib/docker/volumes/e715b7a35b3f70b536a5eab136815d0deb4926b7b782b99168471c648ffc5ffa/_data# ls
test test-2
Mount挂载
文件挂载有多种方式,Volume卷只是提供了统一的管理方式。我们可以使用mount
进行自定义配置,包括绑定任意文件目录,设置读写规则等。
-
--mount
:由多个键值对组成,由逗号分隔,每一个由 <key>=<value> 元祖组成。键值对没有顺序。-
type
,可以是 bind,volume,tmpfs。 -
source
,主机上的文件或目录的路径。可能用 src,source 指定。 -
destination
,容器中的文件或目录的路径。可能用 destination,dst,target 指定。 -
readonly
,如果存在,将更改 Propagation,可以是一个 rprivate。 -
consistency
,如果存在,可以是 consistent,delegated 或 cached,只在 Mac 版有效。 -
--mount
标志不支持 z 或 Z 修改 selinux。
-
-
-v 绝对路径:容器内路径
:默认使用bind
模式挂载目录
- 直接挂载主机目录
# 启动容器,挂载本机目录(绝对路径)到容器的/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进行管理。
卷插件
卷插件允许第三方数据管理解决方案接入
在不修改应用的情况下,可以用另一个插件替换当前插件
- Flocker
- Convoy
- Blockbridge
- GlusterFS
- Netshare
- Openstorage
卷的应用场景
配置文件: 一次构建,多处部署
- 将配置文件植入到容器
- 在 Dockerfile 中使用 'COPY' 指令
- 在镜像构建时使用 'RUN' 指令修改配置
- 最简方案(静态配置的情况下)
- 任何对配置文件的修改都需要重新构建镜像
- 使用环境变量, 动态传入到容器
- 当启动容器时传入环境变量:
docker run -e USERNAME=zzz PASSWORD=...
- 简单配置情况下运行良好
- 当启动容器时传入环境变量:
- 查询键-值存储
- 利用键-值存储获取配置信息
- 很多可选项,如 consul, etcd, zookeeper
- 使得配置更加动态化
- 引入外部依赖,可用性更加重要
- 从主机上挂载卷
- 从其他容器挂载卷
Tips
- Volumes!=Persistance(卷并不意味着持久化)
- 卷并不会被垃圾回收
- 针对有状态的容器(如数据库)和相应的数据容器,可以使用相同的镜像
- 为数据容器制定备份计划
- 将带有敏感数据的容器放在的当前主机上
- 尽可能缓存镜像,因为下载很耗时
网络管理
docker-networks.pngDocker 为我们提供了四种不同的网络模式
bridge
:未指定网络时, 容器默认在bridge
网络下container:CONTAINER_NAME
:重用某容器的网络配置(ip/mac),等同于在同一容器内host
:(唯一)共享了宿主机的网络, 使用相同的ipnone
:(唯一)关闭所有网络连接
docker network ls
中包含了默认的host、none
和一个bridge
网络,当启动的容器没有指定网络时,会默认加入到bridge
网络中
Bridge 网桥模式
net-bridge.png- 启动Docker时会在主机上创建一个名为
docker0
的虚拟网桥接口 - docker0 会为每一个容器分配一个新的 IP 地址并将 docker0 的 IP 地址设置为默认的网关
- docker0 通过 iptables 中的配置与宿主机器上的网卡相连,所有符合条件的请求都会通过 iptables 转发到 docker0 并由网桥分发给对应的机器
-
端口映射通过修改 iptables 再将对端口的访问重定向到 docker0,实现对容器的访问
network-bridge-forward.png
# 启动容器并映射端口
$ docker run -dit --name bridge-container -p 5000 busybox
cb52033e0b8f9c5a8e4c18e5a1fe0ac6d01809393575c1c85b3cdd5aacea437f
# 查看容器服务的端口
$ docker port bridge-container
5000/tcp -> 0.0.0.0:32771
# 查看容器的IP地址
$ docker inspect --format '{{ .NetworkSettings.IPAddress }}' bridge-container
172.17.0.2
# 查看默认网络配置和其中的容器
$ 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": ""
}
},
...
网络和Links
-
docker run --link <target container> <image>
- Links只能对同一主机上的容器生效
- 容器在重新部署时会断开与其他容器的连接
- 容器在创建后才能被相互Link
容器网络模型 - 组件
container-network-model.png- 沙箱
- 包含容器网络堆栈配置信息:容器的接口、路由表和 DNS 设置
- 可能包含多网络的多个端点:连接的每一个网络都有一个不同的端点
- 网络
- 一组端点之间能够相互直接交流
- 实现可以是Linux网桥或重叠网(Overlay)
- 容器
- 容器能够作为任意一个或多个网络的一部分
- 能够同时对接桥接网络和重叠网络
- 在某个特定网络下的所有容器都能够自由地互相通信
- 多个网络有助于分散容器之间的流量传输
- 多个端点允许一个容器加入到多个网络中
用户可创建的网络有2种类型:
- bridge: 用于同一主机的不同容器进行连接
- overlay: 跨主机网络连接
网络实战
- 默认的
bridge
网络也是网桥网络,应用场景相同。
- 创建新的Bridge网络
# 创建2个Bridge网络并创建容器连接网络
$ 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
# 查看网络, 获取容器ip地址等信息
$ docker network ls
$ docker network inspect my-net1
$ docker network inspect my-net2
- 容器进行网络访问
# 同网络容器Ping:成功
$ docker exec service0 ping service1 # 172.18.0.2
# 不同网络容器Ping:失败
$ docker exec service1 ping service2
$ docker exec service1 ping 172.19.0.2 # 无连接
- 为容器配置新的网络
# 将容器连接到另一个网络
$ docker network connect my-net2 service1
$ docker network inspect my-net2
# 此时service1在2个网络各自有一个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,对应不同的网络。访问时会根据网络配置决定应该走哪一个网络进行传输。
- 使用Host网络
$ docker run -itd --net=host --name service-host busybox
$ docker inspect service-host
这时候容器就和本机运行的进程相同了,只要暴露了端口就能通过localhost:port进行访问。
- 使用None网络:即关闭所有网络连接
网络插件
- 允许第三方的容器网络方案连接到容器网络中
- 降低了不同类型主机上容器通信的难度
- 扩展由Docker提供的核心网络功能
可用的网络插件来自于:
- Weave
- Project Calico
- Nuage Networks
- Cisco
- VMware
- Microsoft
- Midokura
容器安全
在容器中的root权限对应宿主机的一个普通用户, 但当容器开启--privileged
特权后, 就可以使用root对宿主机进行操作
# 重要文件映射到容器中
$ docker run -v /:/hostfs busybox cat /hostfs/etc/paths
# 部分文件不可见
# 容器中修改主机重要文件
$ docker run -it -v /:/hostfs busybox touch /hostfs/threat-on-the-way
# 无法创建
安全最佳实践
- 主机:
- 保持内核及时更新
- 增强主机保护
- 保持Docker及时更新
- Docker守护进程:
- 只允许受信用户控制Docker守护进程
- 不使用不受信的镜像仓库
- 必要时请为Docker守护进程应用TLS认证
- 限制容器之间的网络通信
- 镜像:
- 在Dockerfile中为容器创建一个非root用户
- 以非root用户运行容器进程
- 只使用受信的基础镜像
- 仅安装必要的包
- 重新构建镜像时需要包含安全补丁
- 容器运行时
- 限制容器使用Linux内核能力
- 不要使用privileged容器
- 限制容器上的资源使用
- 指定容器重启策略为on-failure
- 使用AppArmor/SELinux保证额外的安全层
- 其他
- 为Docker挂载点创建单独的分区
- 不要到产品环境中使用任何开发者工具(boot2docker, kinematic)
- 建立本地仓库镜像
- 使用供应商最支持的存储驱动程序
- aufs是唯一的允许容器共享执行文件的存储驱动,但可能会导致严重的内核崩溃
- 为Docker守护进程设置受限的控制资源权限(ulimit)
- 由最小基础镜像开始(Busybox, Alpine)
多主机部署和管理
Docker本身只关注单主机(Docker Host),对镜像、容器进行管理。在多主机部署时,主要是利用其它组件、工具进行服务发现、服务注册、网络传输。只是由外部程序来接管多主机的协调工作,对于Docker来说是透明的。例如:
- 将Docker容器放在Host网络/映射Docker容器的端口到Host上
- 利用服务发现(Consul/Eureka)自动发布、获取IP:PORT
- 利用Http通信
网友评论