分类 docker 下的文章

概念

1.镜像(Image):类似于虚拟机中的镜像,是一个包含有文件系统的面向Docker引擎的只读模板。
2.容器(Container):类似于一个轻量级的沙盒,可以将其看作一个极简的Linux系统环境(包括root权限、进程空间、用户空间和网络空间等),以及运行在其中的应用程序。
3.仓库(Repository):类似于代码仓库,这里是镜像仓库,是Docker用来集中存放镜像文件的地方。

Docker镜像的分类

1.基础镜像,可以去docker.io上下载,也可以在国内的Docker仓库下载
2.自定义镜像,可以基于基础镜像实现镜像的自定义
Docker镜像的相关命令
1.docker images
2.docker search centos
3.docker pull centos:7
Docker仓库的配置/etc/docker/daemon.json,重启Docker
{
"registry-mirrors": ["http://hub-mirror.c.163.com/"]
}

国内仓库pull镜像

docker pull hub-mirror.c.163.com/library/centos:7
docker images
镜像导出备份,删除和导入
重命名:docker tag hub-mirror.c.163.com/library/centos:7 centos:7
导出镜像:docker save hub-mirror.c.163.com/library/centos:7 >/tmp/centos.tar
删除镜像:docker rmi centos:7
镜像导入:docker load < /tmp/centos7.tar

Docker 容器

可以使用镜像生成对应运行的容器,一个镜像可以生成多个容器
Centos7镜像生成的容器系统是Centos7,Ubuntu镜像生成的容器系统是Ubuntu的
使用Centos7镜像启动容器
docker run -it centos:7 /bin/bash
i表示交互式,t表示打开一个Shell窗口
宿主机上查看容器的相关操作
1.docker ps:查看运行的容器。docker ps -a:查看所有容器
2.docker inspect:查看容器详细信息
3.docker logs:查看容器日志
4.docker rm:删除容器,docker rm -f:强制删除容器

容器后台运行的特点
如果没有永久运行的程序,终端一退出容器就会马上退出
容器永久运行的条件:需要有永久运行的程序,并且使用run -d后台启动容器
启动后台容器:docker run -d centos:7 /bin/bash -c "while true;do echo helloworld; sleep 5; done"
进入后台容器:docker exec -it xxx /bin/bash
后台容器的停止
停止后台容器:docker stop xxx 或者 docker kill xxx
批量删除容器:docker rm -f xxx xxx

进入容器后ctrl+p+q可以退出终端而不关闭容器,也可以直接关闭终端,容器中pid为1的进程只要不被杀死,容器就不会被关闭。

自定义镜像

容器的临时性
1.容器里的操作当容器删除了就没了
2.最好不要把数据存储在容器里

可将宿主机文件挂载到docker中,或者使用docker数据卷操作 参见docker数据卷

Docker自定义镜像
一般自带的镜像无法满足特定的要求,需要基于基础镜像来自定义Docker镜像
Docker镜像制作的两种方法
1.基于Docker Commit制作镜像
2.基于Dockerfile制作镜像,Dockerfile方式为主流制作镜像的方式

Docker Commit

给基础镜像新增ifconfig命令
docker run --name helloworld -it centos /bin/bash
yum install net-tools -y

保存为镜像

docker commit xx(容器名称) myimage:v1.0
docker images

参考51 CTO 2019Docker入门实战视频

一个docker容器启动之后,我们进入可以看到一个操作系统的所有文件。

我们可以通过chroot这个命令可以更改一个程序运行时的根目录。

假设,我们现在有一个 $HOME/test 目录,想要把它作为一个 /bin/bash 进程的根目录。首先,创建一个 test 目录和几个 lib 文件夹:

$ mkdir -p $HOME/test
$ mkdir -p $HOME/test/{bin,lib64,lib}
$ cd $T

然后,把 bash 命令拷贝到 test 目录对应的 bin 路径下:

$ cp -v /bin/{bash,ls} $HOME/test/bin

接下来,把 bash 命令需要的所有 so 文件,也拷贝到 test 目录对应的 lib 路径下。

$ T=$HOME/test
$ list="$(ldd /bin/ls | egrep -o '/lib.*\.[0-9]')"
$ for i in $list; do cp -v "$i" "${T}${i}"; done

最后,执行 chroot 命令,告诉操作系统,我们将使用 $HOME/test 目录作为 /bin/bash 进程的根目录:

$ chroot $HOME/test /bin/bash

这时,你如果执行 "ls /",就会看到,它返回的都是 $HOME/test 目录下面的内容,而不是宿主机的内容。

这个挂载在容器根目录上、用来为容器进程提供隔离后执行环境的文件系统,就是所谓的“容器镜像”。它还有一个更为专业的名字,叫作:rootfs(根文件系统)。

Docker容器运行过程

  1. 启用 Linux Namespace 配置;
  2. 设置指定的 Cgroups 参数;
  3. 切换进程的根目录(Change Root)。

Cgroup和Namespace类似,也是将进程进行分组,但它的目的和namespace不一样,Namespace是为了隔离进程组之间的资源,而Cgroup是为了对一组进程进行统一的资源监控和限制。

它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。

在Linux中,Cgroups给用户暴露出来的接口是文件系统,以文件与目录形式组织在/sys/fs/cgroup路径之下。

root@herefree:/sys/fs/cgroup# ls
blkio    cpu,cpuacct  freezer  net_cls           perf_event  systemd
cpu      cpuset       hugetlb  net_cls,net_prio  pids        unified
cpuacct  devices      memory   net_prio          rdma
  1. cpu 子系统,主要限制进程的 cpu 使用率。
  2. cpuacct 子系统,可以统计 cgroups 中的进程的 cpu 使用报告。
  3. cpuset 子系统,可以为 cgroups 中的进程分配单独的 cpu 节点或者内存节点。
  4. memory 子系统,可以限制进程的 memory 使用量。
  5. blkio 子系统,可以限制进程的块设备 io。
  6. devices 子系统,可以控制进程能够访问某些设备。
  7. net_cls 子系统,可以标记 cgroups 中进程的网络数据包,然后可以使用 tc 模块(traffic control)对数据包进行控制。
  8. net_prio — 这个子系统用来设计网络流量的优先级
  9. freezer 子系统,可以挂起或者恢复 cgroups 中的进程。
  10. ns 子系统,可以使不同 cgroups 下面的进程使用不同的 namespace
  11. hugetlb — 这个子系统主要针对于HugeTLB系统进行限制,这是一个大页文件系统。
如何使用?

在对应子系统下面创建一个目录。

root@herefree:/sys/fs/cgroup/cpu# mkdir container
root@herefree:/sys/fs/cgroup/cpu# cd container/
root@herefree:/sys/fs/cgroup/cpu/container# ls
cgroup.clone_children  cpuacct.usage_percpu_sys   cpu.shares
cgroup.procs           cpuacct.usage_percpu_user  cpu.stat
cpuacct.stat           cpuacct.usage_sys          notify_on_release
cpuacct.usage          cpuacct.usage_user         tasks
cpuacct.usage_all      cpu.cfs_period_us
cpuacct.usage_percpu   cpu.cfs_quota_us

后台写一个死循环脚本,运行起来

 while : ; do : ; done &

查看cpu占用

jiaoben.png

使被该进程组限制的进程每100ms时间只能使用20ms的CPU时间。

echo 20000 > /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us

将被限制进程的PID写入到container组里的task文件。

echo 11036 > /sys/fs/cgroup/cpu/container/tasks 

再次查看

jiaoben2.png

对于 Docker 等 Linux 容器项目来说,它们只需要在每个子系统下面,为每个容器创建一个控制组(即创建一个新目录),然后在启动容器进程之后,把这个进程的 PID 填写到对应控制组的 tasks 文件中就可以了。

假设我们有了一个Docker项目,我们创建一个容器。

$ docker run -it ubuntu /bin/bash

-it 参数告诉了 Docker 项目在启动容器后,需要给我们分配一个文本输入 / 输出环境,也就是 TTY,跟容器的标准输入相关联,这样我们就可以和这个 Docker 容器进行交互了。而 /bin/bash 就是我们要在 Docker 容器里运行的程序。

所以上面这条指令的意思就是:

请帮我启动一个容器,在容器里执行 /bin/sh,并且给我分配一个命令行终端跟这个容器交互。

我们在容器中输入ps指令:

/ # ps
PID  USER   TIME COMMAND
  1 root   0:00 /bin/sh
  2 root   0:00 ps

在这里可以看到,我们在 Docker 里最开始执行的 /bin/sh,就是这个容器内部的第 1 号进程(PID=1),而这个容器里一共只有两个进程在运行。这就意味着,前面执行的 /bin/sh,以及我们刚刚执行的 ps,已经被 Docker 隔离在了一个跟宿主机完全不同的世界当中。

我们在宿主机上运行/bin/bash程序时,操作系统会给它分配一个进程编号pid。这个进程编号就是进程的唯一标识,我们在docker中运行/bin/bash时,给它施展了一个“障眼法”,让它看不到其他的进程,这样它就会自己认为自己的pid=1。

这种机制,其实就是对被隔离应用的进程空间做了手脚,使得这些进程只能看到重新计算过的进程编号,这就是Linux里面的Namespace机制。

Namespace是对全局系统资源的一种封装隔离,使得处于不同namespace的进程拥有独立的全局系统资源,改变一个namespace中的系统资源只会影响当前namespace里的进程,对其他namespace中的进程没有影响。

目前,Linux内核里面实现了7种不同类型的namespace。

名称        宏定义             隔离内容
Cgroup      CLONE_NEWCGROUP   Cgroup root directory (since Linux 4.6)
IPC         CLONE_NEWIPC      System V IPC, POSIX message queues (since Linux 2.6.19)
Network     CLONE_NEWNET      Network devices, stacks, ports, etc. (since Linux 2.6.24)
Mount       CLONE_NEWNS       Mount points (since Linux 2.4.19)
PID         CLONE_NEWPID      Process IDs (since Linux 2.6.24)
User        CLONE_NEWUSER     User and group IDs (started in Linux 2.6.23 and completed in Linux 3.8)
UTS         CLONE_NEWUTS      Hostname and NIS domain name (since Linux 2.6.19)

PID Namespace

在 Linux 系统中创建进程的系统调用是 clone()

int pid = clone(main_function, stack_size, SIGCHLD, NULL);

这个系统调用就会为我们创建一个新的进程,并且返回它的进程号 pid。

我们用 clone() 系统调用创建一个新进程时,就可以在参数中指定 CLONE_NEWPID 参数,比如:

int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);  

这时,新创建的这个进程将会“看到”一个全新的进程空间,在这个进程空间里,它的 PID 是 1。

https://segmentfault.com/a/1190000006908272

容器是一种沙盒技术,它可以像集装箱一样,将应用“装”起来,这样应用与应用之间就有了边界不相互干扰。也可以被方便的搬来搬去。

这个边界是如何实现的呢?

假如,现在你要写一个计算加法的小程序,这个程序需要的输入来自于一个文件,计算完成后的结果则输出到另一个文件中。由于计算机只认识 0 和 1,所以无论用哪种语言编写这段代码,最后都需要通过某种方式翻译成二进制文件,才能在计算机操作系统中运行起来。而为了能够让这些代码正常运行,我们往往还要给它提供数据,比如我们这个加法程序所需要的输入文件。这些数据加上代码本身的二进制文件,放在磁盘上,就是我们平常所说的一个“程序”,也叫代码的可执行镜像(executable image)。

首先,操作系统从“程序”中发现输入数据保存在一个文件中,所以这些数据就被会加载到内存中待命。同时,操作系统又读取到了计算加法的指令,这时,它就需要指示 CPU 完成加法操作。而 CPU 与内存协作进行加法计算,又会使用寄存器存放数值、内存堆栈保存执行的命令和变量。同时,计算机里还有被打开的文件,以及各种各样的 I/O 设备在不断地调用中修改自己的状态。

一旦“程序”被执行起来,它就从磁盘上的二进制文件,变成了计算机内存中的数据、寄存器里的值、堆栈中的指令、被打开的文件,以及各种设备的状态信息的一个集合。像这样一个程序运起来后的计算机执行环境的总和,就是进程。

容器技术的核心功能,就是通过约束和修改进程的动态表现,从而为其创造出一个“边界”。

对于 Docker 等大多数 Linux 容器来说,Cgroups 技术是用来制造约束的主要手段,而 Namespace 技术则是用来修改进程视图的主要方法。