Docker 学习笔记(三):数据、网络、系统权限、docker-compose
个人笔记,不保证正确,也并非介绍得事无巨细。。
一、Docker 数据管理
Docker 持久化数据有两种方式:
- 使用数据卷(volume):更安全(只有挂载了该数据卷的容器可读写),和主机耦合度低,但是用起来总感觉隔着些东西...
- 如果你要在多个容器间共享数据,那最佳选择是 volume
- 通过使用 NFS 等 volume 驱动,还可以实现将数据保存到远程存储(NAS)中。
- 但是缺点是,不熟悉 docker 的人基本不会使用 volume,它不够直观,可能一不小心跑个
docker volume prune
就把 volume 清掉了,而新来的运维小白还云里雾里。。
- 文件夹映射(bind):非常直观(一个很纯粹的文件夹,有权限的人都可以读写、查看),主机和容器可以很方便地交换数据(比如使用 rsync 定时备份),但是安全性差。
- 非 root 容器使用文件夹映射时,一定会遇到权限问题,因为 docker 默认创建的文件夹用户是
root/root
- bind 权限问题目前比较好的解决方案,是提前手动创建文件夹并设定
uid
/gid
,如mkdir xxx && chown 1000:1000 xxx
- 因为 bind(文件夹映射)很直观,我更多的时候都偏向于使用文件夹映射。
- 启用 selinux 很可能导致 bind 权限问题,通常建议关闭复杂的 selinux...
- 非 root 容器使用文件夹映射时,一定会遇到权限问题,因为 docker 默认创建的文件夹用户是
volume 和 bind 的详细对比参见:
- docker - volumes vs mount binds. what are the use cases?
- docker data volume vs mounted host directory
数据卷相关的命令:
docker volume create <volume name> # 新建数据卷
docker volume ls # 列出所有数据卷
docker volume inspect <volume name> # 查看数据卷信息
docker volume rm <volume name> # 删除数据卷
docker volume prune # 清除所有未挂载的数据卷
数据卷/文件夹的挂载命令:
# 1. 挂载数据卷
docker run -d -P \
--name web \
# -v my-vol:/wepapp \
--mount source=my-vol,target=/webapp \
training/webapp \
python app.py
# 2. 挂载宿主机的文件夹
docker run -d -P \
--name web \
# -v /src/webapp:/opt/webapp:ro \
--mount type=bind,source=/src/webapp,target=/opt/webapp,readonly \
training/webapp \
python app.py
# P.S. 以前是使用 -v 参数,现在推荐使用 --mount
数据卷的备份恢复
数据卷的备份、恢复与内容查看,都必须通过容器进行!
官方的推荐方法是,启动一个中间容器,挂载上对应的数据卷。
然后进入该容器查看数据卷内容,使用 tar
命令进行数据卷内容的备份(打包成 tar.gz)/恢复(将 tar.gz 解压到数据卷中)。
二、网络
1. 外部访问容器
使用端口映射 -p <宿主机端口>:<容器端口>
(该参数可重复多次使用),或者 -P
将容器 EXPOSE
出来的端口随机映射到宿主机。
2. 容器之间的互联
比较方便的方案,是将容器加入自定义的 Docker 网络以实现多容器互联。
-
新建自定义网络
docker network create --driver=bridge <net name> # 新建自定义网络,使用 bridge 模式
-
在运行容器时,通过
docker run --network=<net name> --name <container name> ...
将容器添加到该自定义网络
然后在其中一个容器中ping <container name>
,该名称会直接被解析到对应的容器 ip。这样,容器之间就能以容器名称为 hostname 互联。 -
通过
docker container inspect
可获知容器在 docker0 上的 ip 地址,容器之间可以通过该 ip 通信。- 缺点:ip 是动态分配的!每次都要手动配。。
如果容器较多,推荐使用 docker-compose 进行编排。
3. 容器访问宿主机网络
从 Docker 对这种连接方式的支持来看,显然是很不推荐用户这么干的。
但是对个人用户而言,从容器访问宿主机,我觉得还是个挺常见的需求。
可用方法如下:
- 通过宿主机的公网 ip 访问宿主机
- 缺点:宿主机需要向公网开放对应的端口。
- 在运行容器时指定
--network=host
,容器与宿主机共用网络- 缺点:容器与宿主机耦合度过高。而且可能出现端口冲突。
- 通过
ip addr show docker0
查看宿主机在网桥docker0
上的 ip 地址(linux 下一般为172.17.0.1
),容器可通过该 ip 访问宿主机。- 缺点:不同宿主机在
docker0
上可能具有不同的 ip,需要运行时手动配置。
- 缺点:不同宿主机在
- 通过 shell 脚本获取 host ip:参见 Docker for Linux - Support host.docker.internal DNS name to host
- 等 special DNS record for host.docker.internal + gateway.docker.internal 被 merged,就能直接使用
host.docker.internal
访问宿主机了。
三、使用 nsenter 进入容器的网络 namespace
查看 Docker 容器的连接数:
docker inspect -f '{{.State.Pid}}' <containerid> # 查找到容器的 pid
sudo nsenter -t <pid> -n netstat | grep ESTABLISHED # 通过 pid 检索所有连接
三、系统权限
一个系统上的 docker 容器,默认情况下是无法修改系统的公共内核参数的。这是一种隔离机制。
如果某个容器抽风把系统参数改得一团糟,那所有容器都会崩溃,没有人会希望碰到这种情况。
但是有的时候,我们确实需要在容器内修改整个系统的某些参数。比如在测试系统的时候,我们系统直接修改整个系统的时间,然后查看我们的应用能否应对这种问题。
为了满足这种需求,Docker 提供了一组参数用来给容器设置额外的权限。
Docker 容器的隔离是基于 Linux 的 Capability 机制实现的, Linux 的Capability 机制允许你将超级用户相关的高级权限划分成为不同的小单元. 目前 Docker 容器默认只用到了以下的 Capability:
CHOWN,
DAC_OVERRIDE,
FSETID,
FOWNER,
MKNOD,
NET_RAW,
SETGID,
SETUID,
SETFCAP,
SETPCAP,
NET_BIND_SERVICE,
SYS_CHROOT,
KILL,
AUDIT_WRITE
使用 --cap-add, --cap-drop 可以添加或禁用特定的权限。
比如容器若要修改系统时间,就要求它拥有 SYS_TIME 权限,所以我们在运行容器时需要添加参数 --cap-add SYS_TIME
。
然后在容器里运行 date --set "time-string"
,就能修改掉整个系统的时间。
P.S.
--privileged
参数也可以达到开放权限的作用, 与--cap-add
的区别就是,--privileged
是将所有权限给容器,这相当危险!
四、docker-compose
用于编排多个容器,管理容器集群之间的协作关系。
国内安装镜像:docker: install-compose
常用命令
docker-compose up
:- (推荐在测试时使用)新建并启动容器,聚合输出容器组中所有容器的 logs,命令结束时(ctrl+c),会 stop 掉整个容器组。
- (在生产环境下要添加该选项!)在
-d
模式(detached,分离模式)下运行。与默认行为的差别在与,命令结束后,容器组中的容器仍然会继续运行。
docker-compose start
: 启动终止的容器组。
docker-compose 大部分命令都和 docker 完全类似,区别在于 docker 命令只管理单个容器,而 docker-compose 则是编排多个容器。
docker-compose 本质上是一个单机的容器编排工具。而跨主机的容器集群管理,主流是 kubernetes,另外 docker 的 swarm 模式也提供简单的容器集群功能。
docker-compose.yml
模板
我就不详细介绍 docker-compose.yml 的内容了,建议直接看 Docker Compose 模板文件,写得非常好。