Docker相关的概念和原理
chroot
chroot就是可以改变某进程的根目录,使这个程序不能访问目录之外的其他目录。
Docker是利用Linux的Namespace、Cgroups和联合文件系统三大机制来保证实现的,它的原理是使用Namespace做主机名、网络、pid等资源的隔离,
使用Cgroups对进程或者进程组做资源的限制,联合文件系统用于镜像构建和容器运行环境。
Docker的命名空间
Docker主要用到一下五种命名空间:
- pid namespace:用于隔离进程id。
- net namespace:隔离网络接口。
- mnt namespace:文件系统挂在点隔离。
- ipc namespace:信号量,消息队列和共享内存的隔离。
- ust namespace:主机名和域名的隔离。
Cgroups是一种unix内核功能,可以限制和隔离进程的资源使用情况。在容器的实现中,Cgroups通常用来限制容器的CPU和内存等资源的使用。
Docker有两个至关重要的组件:runC和containerd。
- runC是Docker官方按照OCI容器运行时标准得到的一个实现。runC是一个用来运行容器的轻量级工具,是真正用来运行容器的。
- containerd是从Docker中剥离出来的,containerd通过containerd-shim启动并管理runC,可以说containerd真正管理了容器的生命周期。
可以通过sudo ps aux|grep dockerd,查找到dockerd的pid,然后使用sudo pstree -l -a -A pid查看 docker组件之间的调用关系。
docker启动时,containerd就随之启动,dockerd与containerd一直存在。containerd会创建container-shim充当垫片进程,然后启动容器的真正进程。
镜像相关操作
镜像操作:
- 拉取镜像,docker pull从本地拉取镜像获取从远程拉取镜像到本地;
- 重命名镜像,docker tag重命名镜像
- 查看镜像,docker images /docker image ls 查看现有镜像。
- 删除镜像,docker rmi 镜像编号;
- 构建镜像,docker build基于Dockerfile构建镜像,docker commit基于运行的容器提交为镜像。
Dockerfile操作
Dockerfile常用指令:
Dockerfile指令 | 指令介绍 |
---|---|
FROM | Dockerfile除了注释行,第一行必须是FROM,FROM后面跟镜像名称,作为基础镜像 |
RUN | 跟具体命令,类似于Linux命令行的执行命令 |
ADD | 拷贝本地文件或者远程文件到镜像内 |
COPY | 拷贝本地文件到镜像内 |
USER | 指定容器启动的用户 |
ENDPOINT | 容器的启动命令 |
CMD | CMD为ENDPOINT提供默认参数,也可以单独使用CMD指定容器启动参数 |
ENV | 指定容器运行时的环境变量,格式为key=value |
ARG | 定义外部变量,构建镜像时可以使用build-arg=的格式传递参数用于构建 |
EXPOSE | 指定容器监听的端口,格式为[port]/tcp或者[port]/udp |
WORKDIR | 为Dockerfile中跟在其后的所有RUN、CMD、ENDPOINT、COPY和ADD命令设置工作目录,只在当前有效 |
Docker清除数据的命令:
- docker image prune -af 仅仅清除没有被容器使用的镜像文件
- docker system prune -f 清除多余的数据,包括包含的容器、多余的镜像、未使用的volume等等。
Dockerfile书写原则:
- 单一指责:一个Dockerfile只完成一个功能
- 提供备注信息:提供注释信息
- 保持容器最小化:只保留必要的内容
- 合理选择基础镜像
- 使用.dockerignore文件
- 尽量使用构建缓存
- 正确设置时区
- Ubuntu和Debian系统: RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
&& RUN echo "Asia/Shanghai" >> /etc/timezone- Centos系统:RUN ln -sd /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
- 使用国内软件源增加构建镜像构建速度
- 最小化镜像层数。
Dockerfile书写建议:
- Run指令在构建时会生成一个新的镜像层并且执行RUN指令后面的内容。
- RUN指令后面跟内容复杂时,建议使用反斜杠结尾且换行;
- RUN后面的内容尽量按照字母顺序排列,提高可读性。
- CMD和ENDPOINT
- CMD/ENDPOINT指令方式:CMD/ENDPOINT ["command","param"](exec模式,Docker 官方推荐的方式);
CMD/ENDPOINT command param ,称为shell模式。- exec模式,容器的1号进程就是CMD/ENDPOINT指定的命令,shell模式启动的进程在容器中实际上并不是1号进程。
- ENDPOINT可以和CMD指令结合使用,也可以单独使用,CMD只能单独使用。
- Dockerfile中如果使用了ENDPOINT指令,启动Docker容器时需要使用--entrypoint参数才能覆盖Dockerfile中的ENDPOINT指令,
使用CMD设置的命令,可以被docker run后面的参数直接覆盖。- ADD和COPY
- COPY指令只支持基本的文件和文件拷贝功能,ADD则支持更多文件来源类型,比如自动提取tar包,并且可以支持源文件为URL格式。
基于内核的弱隔离系统如何保证安全
Docker与虚拟机的区别:
- 虚拟机是通过管理系统(hypervisor)模拟出CPU、内存、网络等硬件,这样做的好处是虚拟机有自己的内核和操作系统,
并且硬件是通过虚拟机管理系统模拟出来的,用户程序无法直接使用到主机的操作系统和硬件资源,虚拟机也对隔离性和安全性有着更好的保证。- Docker容器则是通过Linux内核的Namespace技术实现了文件系统、进程、设备以及网络的隔离,然后通过cgroups对CPU、内存等资源进行限制,
最终实现容器之间互相不受影响,由于容器的隔离性仅仅依靠内核来提供,因此容器的隔离型也远弱于虚拟机。
Docker容器的安全问题 | 解决方法 |
---|---|
Docker作为一款容器引擎, 本身也会存在一些安全漏洞 |
使用最新的版本,有相应的命名空间 |
镜像安全 | 私有镜像仓库自己进行扫描 |
Namespace隔离不够,然后部分关键 内容没有被安全隔离 |
宿主机内核应该尽量安装最新补丁; 使用capabilities划分权限;使用SELinux、 AppArmor、GRSecurity等安全组件加强安全; 每个容器都要限制资源使用 |
所有容器共享主机内核 | 使用安全容器来代替runC |
cadvisor相关
cadvisor可以监控物理机和容器的状态;可以展示监控历史数据。
cadvisor的监控是基于cgroupsde。cgroups的工作目录是/sys/fs/cgroup,/sys/fs/cgroup目录,容器启动之后,可以在
/sys/fs/cgroup/memory/docker目录下查看内存相关的数据。memory.limit_in_bytes(限制总量)、memory.usage_in_bytes(使用量)。
如果监控网络信息,在/proc/{pid}/net/dev下面读取。
为什么容器需要Namespace
Namespace产生时间
命名空间与版本的关系:
Namespace名称 | 作用 | 内核版本 |
---|---|---|
Mount(mnt) | 隔离挂载点 | 2.4.19 |
Process(pid) | 隔离进程id | 2.6.24 |
Network(net) | 隔离网络设备,端口号 | 2.6.29 |
Interprocess Communication(ipc) | 隔离System IPC和POSIX Message Queue | 2.6.19 |
UTS Namespace(uts) | 隔离主机名和域名 | 2.6.19 |
User Namespace(user) | 隔离用户和用户组 | 3.8 |
Control group Namespace | 隔离cgroups根目录 | 4.6 |
Time Namespace | 隔离系统时间 | 5.6 |
各个Namespace的作用
Mount Namespace
Mount Namespace用来隔离不同的进程或进程组看到的挂载点。
使用unshare命令可以新建Mount Namespace,并且在新建的Mount Namespace内Mount是和外部完全隔离的。
[root@centos7 centos]# sudo unshare --mount --fork /bin/bash
[root@centos7 centos]# mkdir /tmp/tmpfs
[root@centos7 centos]# mount -t tmpfs -o size=20m tmpfs /tmp/tmpfs
[root@centos7 centos]# ls -l /proc/self/ns
PID Namespace
PID Namespace的作用是用来隔离进程。
[root@centos7 centos]# sudo unshare --pid --fork --mount-proc /bin/bash
[root@centos7 centos]# ps aux # 查看进程
UTS Namespace
UTS Namespace主要是用来隔离主机名的,允许每个UTS Namespace拥有一个独立的主机名。
[root@centos7 centos]# sudo unshare --uts --fork /bin/bash
[root@centos7 centos]# hostname -b docker
[root@centos7 centos]# hostname
IPC Namespace
IPC Namespace主要是用来隔离进程间通信。
[root@centos7 centos]# sudo unshare --ipc --fork /bin/bash
[centos@centos7 ~]$ ipcs -q
[root@centos7 centos]# ipcmk -Q
ipcs -q命令:用来查看系统间通信队列列表。
ipcmk -Q命令:用来创建系统间通信队列。
User Namespace
User Namespace 主要是用来隔离用户和用户组的。
[centos@centos7 ~]$ unshare --user -r /bin/bash # 如果没有权限,执行echo 65535 > /proc/sys/user/max_user_namespaces,然后再次尝试创建 User Namespace。
Net Namespace
Net Namespace用来隔离网络设备、IP地址和端口等信息。
[root@centos7 centos]# sudo unshare --net --fork /bin/bash
如何通过Cgroups机制实现资源限制
cgroups主要功能:
- 资源限制:限制资源的使用量,设置上限。
- 优先级控制:不同的组可以有不同的资源使用优先级。
- 审计:计算控制组的资源使用情况。
- 控制:控制进程的挂起或恢复。
Cgroups功能的实现依赖与三个核心概念:子系统、控制组、层级树。
- 子系统:是一个内核的组件,一个子系统代表一类资源调度控制器。
- 控制组:一组进程和一组带有参数的子系统的关联关系。
- 层级树:一系列的控制组按照树状结构排列组成的。
sudo mount -t cgroup # 查看当前系统已经挂载的cgroups信息
CPU子系统
在cpu子系统下创建cgroup
[root@centos7 centos]# mkdir /sys/fs/cgroup/cpu/mydocker
创建进程,加入cgroup
[root@centos7 centos]# cd /sys/fs/cgroup/cpu/mydocker
[root@centos7 centos]# echo $$ > tasks
执行CPU耗时任务,验证cgroup是否可以限制cpu使用时间
[root@centos7 centos]# cd /sys/fs/cgroup/cpu/mydocker
[root@centos7 centos]# echo 50000 > cpu.cfs_quota_us
memory 子系统
在memory子系统下创建cgroup
[root@centos7 centos]# mkdir /sys/fs/cgroup/memory/mydocker
创建进程,加入cgroup
[root@centos7 centos]# cd /sys/fs/cgroup/cpu/mydocker
[root@centos7 centos]# echo $$ > tasks
删除cgroups
[root@centos7 centos]# rmdir /sys/fs/cgroup/memory/mydocker
Docker组件机器原理
docker相关的组件:docker、dockerd、docker-init和docker-proxy。
containerd相关的组件:containerd、container-shim和ctr。
容器运行时相关的组件:runC。
dockerd
dockerd是Docker服务端的后台常驻进程,用来接收客户端发送的请求,执行具体的处理任务,处理完成后将结果返回给客户端。
Docker客户端与dockerd的交互方式:
- unix套接字与服务端通信:配置格式为,unix://socket_path,默认为/var/run/docker.sock,该文件只有root或者docker用户可以访问。
- TCP与服务端通信:可以配置为tcp://host:port。
- 文件描述符的方式与服务端通信:配置格式为: fd:// 这种格式一般用于systemd管理的系统中。
docker-init
init进程是1号进程,在docker run启动容器时可以添加--init参数,此时docker会使用docker-init作为1号进程。
docker-proxy
docker-proxy主要是用来做端口映射的。docker-proxy组件就会把容器内先赢的端口映射到主机上来,底层是依赖iptables实现的。
[root@centos7 centos]# sudo iptables -L -nv -t nat # iptables nat表的规则。
docker是官方实现的标准客户端,dockerd是Docker服务端的入口,负责接收客户端发送的指令并返回相应结果,docker-init在业务主进程没有进程回收功能,
docker-proxy组件是实现Docker网络访问的主要组件。
containerd
containerd不仅负责容器生命周期的管理,同时负责一些其他的功能:
- 镜像的管理
- 接收dockerd的请求,通过适当的参数调用runC启动容器;
- 管理存储相关资源;
- 管理网络相关资源;
containerd包含一个后台常驻进程,默认的socket路径为/run/containerd/containerd.sock,dockerd通过unix套接字向containerd发送请求,
containerd接收到请求后负责执行相关的动作并把执行结果返回给dockerd。
container-shim
container-shim的主要作用是将containerd和真正的容器进程解耦,使用container-shim作为容器进程的父进程,
从而实现containerd不影响已经启动的容器进程。
ctr
ctr是containerd-ctr,它是containerd的客户端,主要用来开发和调试。
runC
[root@centos7 centos]# cd /home/centos
[root@centos7 centos]# mkdir runc # 创建runc运行根目录
[root@centos7 centos]# mkdir rootfs &&& docker export $(docker create busybox) | tar -C rootfs -xvf - # 导入rootfs镜像文件
[root@centos7 centos]# runc spec
[root@centos7 centos]# cat config.json
[root@centos7 centos]# runc run busybox # 启动容器
[root@centos7 centos]# cd /home/centos/runc
[root@centos7 centos]# runc list
组件分类 | 组件名称 | 作用剖析 |
---|---|---|
Docker相关组件 | docker | Docker客户端,负责发送Docker操作请求 |
Docker相关组件 | dockerd | Docker服务端入口,负责接收客户端请求并返回结果 |
Docker相关组件 | docker-init | 业务主进程没有回收能力时,docker-init可以作为容器的1号进程,负责管理容器的内子进程 |
Docker相关组件 | docker-proxy | 用于Docker的网络实现,通过设置iptables规则使得访问到主机的流量可以转发到容器中 |
containerd相关组件 | containerd | 负责管理容器的生命周期,接收dockerd的请求,执行启动或者销毁容器操作 |
containerd相关组件 | containerd-shim | 将containerd和真正的容器进行解解耦,使用containerd-shim作为容器进程的父进程,可以实现重启containerd不影响已经启动的容器进程 |
containerd相关组件 | ctr | containerd的客户端,可以直接向containerd发送容器操作请求,主要用来测试和开发 |
容器运行时组件 | runc | 通过Namespace、Cgroups等系统接口,实现容器的创建和销毁 |
Docker网络模型
libnetwork常见的网络模式 | 作用 | 业务场景 |
---|---|---|
null空间模式 | 不提供任何容器网络 | 处理一些保密数据,需要一个隔离的网络环境执行一些纯计算任务 |
bridge桥接模式 | 使得容器与容器之间网络可以互通 | 容器需要实现网络通信或者提供网络服务 |
host主机模式 | 让容器内的程序可以使用到主机的网络 | 容器需要控制主机网络或者使用主机网络提供服务 |
container网络模式 | 将两个容器放到同一个网络空间中,可以直接通过localhost本地访问 | 两个容器之间需要通过localhost通信 |