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通信

你可能感兴趣的:(Docker相关的概念和原理)