关于《Docker容器与容器云》的读书笔记,主要用于自己强化知识记忆,理解错误之处还望指出,有兴趣的同学也可以阅读原书。
(略)
子命令分类 | 子命令 |
---|---|
Docker环境信息 | info, version |
容器生命周期管理 | create, exec, kill, pause, unpause, restart, start, stop, rm, run |
镜像仓库命令 | login, logout, pull, push, search |
镜像管理 | build, images, rmi, import, load, save, tag, commit |
容器运维操作 | attach, export, inspect, port, ps, rename, stats, top, wait, cp, diff, update |
容器资源管理 | volume, network |
系统日志信息 | events, history, logs |
# 环境信息
docker info
# 版本信息
docker version
# 创建容器
docker run
options:
-i: 使用交互模式,始终保持输入流开放
-t: 分配一个伪终端,配合-i使用,进行交互操作
--name: 指定容器名
-c: 给运行中的容器分配cpu的share值(相对权重,与宿主机相关)
-m: 限制容器所有进程的内存总量
-v: 挂载volume,[host-dir]:[container-dir]:[rw|ro]
-p: 将容器端口暴露给宿主机,hostPort:containerPort
# 容器的启动
docker start
options:
-i: 使用交互模式,始终保持输入流开放
-a: 附加标准输入,标准输出或错误输出
# 容器的停止
docker stop
options:
-t: 容器停止前的等待时间
# 容器的重启
docker restart
options:
-t: 容器停止前的等待时间
# 镜像拉取
docker pull [OPTIONS] NAME:[TAGS|@DIGEST]
# 镜像推送
docker push [OPTIONS] NAME[:TAG]
# 显示镜像列表
docker images [OPTIONS] [REPOSITORY[:TAG]]
# 镜像删除
docker rmi [OPTIONS] IMAGE [IMAGE...]
# 容器删除
docker rm [OPTIONS] CONTAINER [CONTAINER...]
# 将容器保存为镜像
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
## 只能选择正在运行的容器(官方推荐使用build和Dockerfile创建镜像)
# 连接容器进行交互
docker attach [OPTIONS] CONTAINER
# 查看容器和镜像的详细信息
docker inspect [OPTIONS] NAME|ID [NAME|ID...]
## 查看容器IP地址
docker inspect --format='{{.NetworkSettings.IPAddress}}' NAME
## 批量查看IP地址
for i in `docker ps -q`; do echo -n "$i "; docker inspect $i --format='{{.NetworkSettings.IPAddress}}'; done
# 查看容器的相关信息
docker ps [OPTIONS]
options:
-a: 全部显示
-l: 只查看最新创建的容器
# 打印实时系统事件
docker events [OPTIONS]
# 打印镜像的历史版本信息
docker history [OPTIONS] IMAGE
# 打印容器中的进程日志
docker logs [OPTIONS] CONTAINER
namespace的六项隔离
namespace | 系统调用参数 | 隔离内容 |
---|---|---|
UTS | CLONE_NEWUTS | 主机与域名 |
IPC | CLONE_NEWIPC | 信号量、消息队列和共享内存 |
PID | CLONE_NEWPID | 进程编号 |
Network | CLONE_NEWNET | 网络设备、网络栈、端口等 |
Mount | CLONE_NEWNS | 挂载点(文件系统) |
User | CLONE_NEWUSER | 用户和用户组 |
# 常见方式(Docker使用namespace最基本的方式)
int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);
# 与namespace相关的4个参数:
- child_func: 传入子进程运行的程序主函数
- child_stack: 传入子进程使用的栈空间
- flags: 表示使用哪些CLONE_*标志位。与namespace相关的见上表。
- args: 则可用于传入用户参数
@@备注@@
1.clone是linux系统调用fork()的一种最通用的方式,通过flags参数来控制多少功能。
2.flags有20多种CLONE_*参数,来控制clone的方方面面
# 最后一列为namespace编号
[root@sherlockV7 ns]# ll /proc/$$/ns <<-- $$shell中表示当前运行的进程ID号
总用量 0
lrwxrwxrwx. 1 root root 0 3月 15 21:20 ipc -> ipc:[4026531839]
lrwxrwxrwx. 1 root root 0 3月 15 21:20 mnt -> mnt:[4026531840]
lrwxrwxrwx. 1 root root 0 3月 15 21:20 net -> net:[4026531956]
lrwxrwxrwx. 1 root root 0 3月 15 21:20 pid -> pid:[4026531836]
lrwxrwxrwx. 1 root root 0 3月 15 21:20 user -> user:[4026531837]
lrwxrwxrwx. 1 root root 0 3月 15 21:20 uts -> uts:[4026531838]
- 若两个进程的namespace编号相同,则在同一namespace下。
- 上述link表示,若该文件被打开,只要打开的文件描述符(fd)存在,则namespace下所有进程都结束也一直存在,后续进程可以再加进来
- Docker中,通过文件描述符定位和加入是常见的方式。
# 通过setns()函数加入已存在的namespace
int setns(int fd, int nstype);
- fd: 表示要加入的namespace的文件描述符
- nstype: 检测fd指向的namespace文件描述符是否符合要求。参数0为不检查
@@备注@@
1.通过setns()系统调用,进程从原有NS加入已存在NS中。
2.为不影响进程调用者,也为使新加入的pid NS生效。
3.setns()执行后,使用clone()创建子进程继续执行命令,结束原先进程。
4.Docker exec在已运行容器中执行新命令,就需要用到该方法
# 不启动新进程进行隔离NS操作
int unshare(int flags);
@@备注@@
1.功能类似clone,在原有的进程上运行,不需要启动新进程。
2.相当于跳出原有namespace进行操作。
3.Docker目前未用到该系统调用。
说明
UTS:UNIX Time-sharing System
作用
提供了主机名和域名的隔离,使Docker拥有独立的主机名和域名
说明
进程间通信(Inter-Process Communication, IPC)涉及的IPC资源包括:
申请IPC资源就申请了一个全局唯一的32位ID。
IPC namespace实际包括:
作用
说明
结论
引申作用
外部监控Docker内进程方法:监控Docker daemon所在的PID namespace下的所有进程以及子进程,再进行筛选。
PID namespace确保进程顺利进行的额外工作
容器中要实现类似init的资源监控与回收功能,维护后续启动进行的运行状态,确保最终回收子进程的资源。
在新的PID namespace中使用ps命令能够看到所有进程,原因为与PID相关的proc文件系统(procfs)未挂载到原/proc不同的地方。只想看PID namespace本身进程时,需要重新挂载/proc。
mount -t proc proc /proc
感觉跟之前解释的有出入,没看懂,先跳过后续补充。
说明
mount namespace通过隔离文件系统挂载点对文件系统的隔离提供支持
作用
不同mount namespace下的文件结构发生变化也互不影响。
查看挂载到当前ns中的文件系统
挂载传播
进程创建mount namespace时,会把当前文件结构复制给新的namespace。新的ns中mount操作都只影响自身的文件系统,不外界不产生影响。但当父节点挂载外部设备后,子节点复制的目录结构却无法自动挂载,因为这种操作会影响父节点的文件系统。
引入挂载传播概念,定义挂载对象之间的关系:
系统会用这些关系来决定将任何挂载对象中的挂载事件如何传播到其他挂载对象。
挂载状态分类:
作用
主要提供关于网络资源的隔离,包括:
虚拟网络设备对(veth pair)
由于一个物理网络设备最多存在于一个namespace中,可以通过创建veth pair(有两端,类似管道,如果数据从一端传入另一端也能接收到,反之亦然)在不同的namespace间创建通道,已达到通信的目的。
容器实现网络隔离的过程
veth pair创建前,新旧namespace如何通信
物理设备分配给新建namespace时的注意点
作用
user namespace主要隔离了安全相关的标识符(identifier)和属性(attribute),包括:
简单理解
一个普通用户的进程通过clone()创建的新进程在新user namespace中可以拥有不同的用户和用户组。也就意味,一个进程在容器外属于没特权的普通用户,但它创建的容器进程却属于拥有所有权限的超级用户。
验证结论
Docker方面的应用
强大的内核工具 —— cgroups
cgroups不仅可以限制被namespace隔离起来的资源,还可以为资源设置权重、计算使用量、操控(进程或线程)任务启停等。
官方定义
简单理解
cgroups可以限制、记录任务组所使用的物理资源(包括CPU、Memory、IO等),为容器实现虚拟化提供了基本保证,是构建Docker等一系列虚拟化工具的基石。
对于开发者,cgroups有以下4个特点
主要目的是为了不同用户层面的资源管理,提供一个统一化的接口。主要提供以下四大功能:
组织结构
基本规则
子系统就是cgroups的资源控制,每种子系统独立控制一种资源。目前Docker中,使用了9种子系统。net_cls子系统在内核中已经广泛使用,但是Docker中没有使用。
cgroups实现形式
表现为一个文件系统,通过mount挂载文件系统,就可以查看到下面的各类子系统。
以cpu为例:
# cpu子系统挂载路径
[root@sherlockV7 cpu]# pwd
/sys/fs/cgroup/cpu
# 挂载该子系统的控制组下的文件
[root@sherlockV7 cpu]# ls
cgroup.clone_children cgroup.sane_behavior cpuacct.usage_percpu cpu.rt_period_us cpu.stat release_agent user.slice/
cgroup.event_control cpuacct.stat cpu.cfs_period_us cpu.rt_runtime_us docker/ system.slice/
cgroup.procs cpuacct.usage cpu.cfs_quota_us cpu.shares notify_on_release tasks
# Docker daemon会在每个子系统控制组目录下创建名为docker的控制组,
# 容器则以ID为名称在docker控制组目录下分别创建控制组,
# 并将容器中的所有进程号写入控制组的tasks中,
# 再在控制文件(cpu.cfs_quota_us)中写入预设的限制参数值。
# Docker组的层级目录如下:
[root@sherlockV7 docker]# tree .
.
├── cgroup.clone_children
├── cgroup.event_control
├── cgroup.procs
├── cpuacct.stat
├── cpuacct.usage
├── cpuacct.usage_percpu
├── cpu.cfs_period_us
├── cpu.cfs_quota_us
├── cpu.rt_period_us
├── cpu.rt_runtime_us
├── cpu.shares
├── cpu.stat
├── d314400633f9281e8d028b4dfb41e82ecc6ed67211bc604ad0054bd747ad7d41
│ ├── cgroup.clone_children
│ ├── cgroup.event_control
│ ├── cgroup.procs
│ ├── cpuacct.stat
│ ├── cpuacct.usage
│ ├── cpuacct.usage_percpu
│ ├── cpu.cfs_period_us
│ ├── cpu.cfs_quota_us
│ ├── cpu.rt_period_us
│ ├── cpu.rt_runtime_us
│ ├── cpu.shares
│ ├── cpu.stat
│ ├── notify_on_release
│ └── tasks
├── notify_on_release
└── tasks
cgroups的实际本质是给任务挂上钩子,当任务运行过程中涉及某种资源时,就会触发钩子上附带的子系统进行检测,根据资源类型不同,使用对应的技术进行资源限制和优先级分配。
对于不同系统资源,cgroups提供统一的接口对资源进行控制和统计,但限制的具体方式不尽相同。
以memory子系统为例:
判断资源超限
在"mm_struct"结构体(描述内存状态)记录所属cgroup。
当进程需申请更多内存时,触发cgroup用量检测。
用量超过cgroup限额时,拒绝用户的内存申请。
否则,给予相应内存并在cgroup统计信息中记录。
超限之后
内存超出cgroup最大限额之后
若设置了OOM Control(内存超限控制),进程会收到OOM信号并结束
否则进程会被挂起,进入睡眠状态,直至cgroup中其他进程释放足够内存资源为止。
Docker默认开启OOM Control
并不直接关联,cgroup与任务之间是多对多关系。
通过中间结构把双方关联信息记录起来。
每个任务结构体task_struct都包含一个指针,
可查询到对应cgroup情况
也可查询各个子系统状态
同时子系统状态中也包含找到任务的指针。
不同类型的子系统按需定义自身的控制信息结构体,最终将子系统状态指针包含进去。
最后内核通过container_of(通过结构体成员找到结构体本身)等宏定义来获取相应的结构体,关联到任务,以达到资源限制的目的。
内核开发者通过VFS(虚拟文件系统转换器)接口实现cgroup的文件系统,并将各个子系统的实现都封装到文件系统的各项操作中。
实际使用过程中,Docker需通过挂载cgroup文件系统新建一个层级结构,挂载时指定要绑定的子系统。把cgroup文件系统挂载上以后,就可以像操作文件一样对cgroups的层级进行浏览和操作管理(包括权限管理和子文件管理等)。
注意事项
以资源开头的文件都是用来限制这个cgroup下任务的可用配置文件,一个cgroup创建完成,不管绑定何种子系统,其目录下都会生成以下几个文件用来描述cgroup相应信息。同样,把相应信息写入配置文件就可以生效。
tasks:
cgroup.procs:
notify_on_release:
release_agent:
挂载状态分类中并没有提到master slave方式,这其实是上述从属挂载概念中隐藏的信息。下面引用外部资料说明:MS_SLAVE – This propagation type sits midway between shared and private. A slave mount has a master—a shared peer group whose members propagate mount and unmount events to the slave mount. However, the slave mount does not propagate events to the master peer group.原文地址:http://hustcat.github.io/mount-namespace-and-mount-propagation/ ↩︎
规则2中的解释说明没有理解明白,明显感觉与规则1说明上有冲突的地方,同一层级可以被附加多个子系统,但是规则2却说,子系统附加多个层级时,必须保证目标层级只有这个唯一一个子系统。难道是说,子系统与其他子系统附加到一个层级,或者是这子系统附加到多个层级,这两种情况只能选择一种吗?先跳过,后面有解答后再回来标注。 ↩︎