(三)Docker高级应用

Docker中的文件和数据

Docker镜像通过UnionFS进行分层存储(可以通过docker history查看),一个Docker镜像本质上是一组文件,默认存储在/var/lib/docker/中。

而Docker 容器其实是在镜像的最上层加了一层读写层,通常也称为容器层。在运行中的容器里所做的改动,如写新文件、修改已有文件、删除文件等操作其实都写到了容器层。容器层删除了,最上层的读写层跟着也删除了,改动自然也丢失了。

若要持久化这些改动,一是通过 docker commit [repository[:tag]]将当前容器保存成为一个新镜像。若想将持久化数据,或是多个容器间共享数据,需将数据存储主机的文件系统中。

(三)Docker高级应用_第1张图片
types-of-mounts.png

数据卷(Volume) - 为什么是特殊的目录?

  • 处于UFS(Union File System)之外
  • 主机文件系统中的普通目录
  • 在卷上的I/O性能应与主机上的完全相同
  • 卷的内容不包含在Docker镜像中
  • 任何对卷内容的修改不是镜像的一部分
  • 可被多个容器共享和重用
  • 持久化数据(即使容器已被删除)

如何初始化卷?

(三)Docker高级应用_第2张图片
volume-data-container.png
  • 从主机上挂载卷: 与容器共享主机目录

    • Docker卷允许在主机操作系统和容器之间共享任意目录/文件
    • 需要确保在主机操作系统上外部配置文件是可访问的
    • 动态伸缩的约束
  • 从其他容器挂载卷(数据容器):数据容器会作为一个接口, 向其他容器提供访问卷的途径

    • 容器可处理一组卷
    • 能限制访问权限,如只读或读写: docker run --volume-from some-container
    • 在多个容器间共享卷
    • 卷内容会保持同步
    • 底层使用相同的目录

使用数据容器的场景?

  • 存储持久化数据库

    • 启动数据容器
    • 启动基于数据库镜像的数据库容器
    • 外部化数据目录 - 通过从数据容器挂载卷
    • 安排数据容器的备份计划
  • 配置文件

  • 数据文件

实践操作

  1. 访问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
  1. 在"主机"上挂载一个卷
# 创建匿名卷
$ docker volume create
4997a11f62aff64766a2ce490debf1770a0c3f29dae57326579fefd0c3668673
# 创建卷
$ docker volume create my-volume
my-volume

这样创建的卷会默认在/var/lib/docker/volumes/下,可以使用docker volume进行统一管理

  1. 使用卷
# 启动容器,挂载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
  1. 向卷中写入数据
  • 宿主机(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
  1. 使用数据容器
  • 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
  1. 运行时添加文件
# 复制文件到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:由多个键值对组成,由逗号分隔,每一个由 = 元祖组成。键值对没有顺序。
    • 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模式挂载目录
  1. 直接挂载主机目录
# 启动容器,挂载本机目录(绝对路径)到容器的/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高级应用_第3张图片
docker-networks.png

Docker 为我们提供了四种不同的网络模式

  • bridge:未指定网络时, 容器默认在bridge网络下
  • container:CONTAINER_NAME:重用某容器的网络配置(ip/mac),等同于在同一容器内
  • host:(唯一)共享了宿主机的网络, 使用相同的ip
  • none:(唯一)关闭所有网络连接

docker network ls中包含了默认的host、none和一个bridge网络,当启动的容器没有指定网络时,会默认加入到bridge网络中

Bridge 网桥模式

(三)Docker高级应用_第4张图片
net-bridge.png
  • 启动Docker时会在主机上创建一个名为docker0的虚拟网桥接口
  • docker0 会为每一个容器分配一个新的 IP 地址并将 docker0 的 IP 地址设置为默认的网关
  • docker0 通过 iptables 中的配置与宿主机器上的网卡相连,所有符合条件的请求都会通过 iptables 转发到 docker0 并由网桥分发给对应的机器
  • 端口映射通过修改 iptables 再将对端口的访问重定向到 docker0,实现对容器的访问


    (三)Docker高级应用_第5张图片
    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
    • Links只能对同一主机上的容器生效
    • 容器在重新部署时会断开与其他容器的连接
    • 容器在创建后才能被相互Link

容器网络模型 - 组件

(三)Docker高级应用_第6张图片
container-network-model.png
  • 沙箱
    • 包含容器网络堆栈配置信息:容器的接口、路由表和 DNS 设置
    • 可能包含多网络的多个端点:连接的每一个网络都有一个不同的端点
  • 网络
    • 一组端点之间能够相互直接交流
    • 实现可以是Linux网桥或重叠网(Overlay)
  • 容器
    • 容器能够作为任意一个或多个网络的一部分
    • 能够同时对接桥接网络和重叠网络
  • 在某个特定网络下的所有容器都能够自由地互相通信
  • 多个网络有助于分散容器之间的流量传输
  • 多个端点允许一个容器加入到多个网络中

用户可创建的网络有2种类型:

  • bridge: 用于同一主机的不同容器进行连接
  • overlay: 跨主机网络连接
    • Docker 网络特性

网络实战

  1. 默认的bridge网络也是网桥网络,应用场景相同。
  1. 创建新的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
  1. 容器进行网络访问
# 同网络容器Ping:成功
$ docker exec service0 ping service1 # 172.18.0.2
# 不同网络容器Ping:失败
$ docker exec service1 ping service2
$ docker exec service1 ping 172.19.0.2 # 无连接
  1. 为容器配置新的网络
# 将容器连接到另一个网络
$ 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,对应不同的网络。访问时会根据网络配置决定应该走哪一个网络进行传输。

  1. 使用Host网络
$ docker run -itd --net=host --name service-host busybox
$ docker inspect service-host

这时候容器就和本机运行的进程相同了,只要暴露了端口就能通过localhost:port进行访问。

  1. 使用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通信

你可能感兴趣的:((三)Docker高级应用)