Docker从入门到实战

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Docker从入门到实战相关的知识,希望对你有一定的参考价值。

一步一步走,写小白都能看懂的文章,将持续更新中,敬请期待!

Docker从入门到实战(一)

一:容器技术与Docker概念

1 什么是容器

容器技术并不是一个全新的概念,它又称为容器虚拟化。虚拟化技术目前主要有硬件虚拟化、半虚拟化、操作系统虚拟化等。
1.1关于虚拟化
虚拟化技术的分类与定义在不同领域有不同的理解。对于计算机领域,虚拟化技术主要分为两大类:一类基于硬件虚拟化,另一类基于软件虚拟化。硬件虚拟化并不多见,大都是半虚拟化与软件结合,应用较为广泛的则是基于软件的虚拟化技术。
基于软件虚拟化又可分为应用虚拟化(如Wine)和平台虚拟化(如虚拟机),容器技术属于操作系统虚拟化,属于平台虚拟化的一种。
技术分享图片
1.2容器的定义
所谓容器,顾名思义就是来放东西的道具。在刚进入国内时,还有一段时间在讨论Container这个单词翻译为“容器合适”,还是翻译为“集装箱”合适。大可把容器理解为一个沙盒,每个容器是独立的,容器间可以相互通信。

2 容器的前世今生

如果说工业上的集装箱是从一个箱子开始的,那么软件行业的容器则是从文件系统隔离开始的。
2015年微软公司也在Windows Server上为其基于Windows的应用添加了容器支持,称之为Windows Containers,与Windows Server 2016一同发布,通过该实现,Docker可以原生的在Windows上运行Docker容器,而不需要再启动一个虚拟机来运行Docker(Windows上早期运行Docker需要使用Linux虚拟机)。同年,MacOS也原生支持运行Docker容器。

3 容器的原理

容器本质上是宿主机上的进程。容器技术通过namespace实现资源隔离,通过Cgroups(Google公司的Control Groups技术,2007年被合并到Linux2.6.24内核中)实现资源控制,通过rootfs实现文件系统隔离,再加上容器搜索引擎自身的特性来管理容器的生命周期。
3.1认识namespace
在分布式的环境下,容器必须要有独立的IP、端口和路由等,自然就有了网络隔离。同时,进程通信隔离、权限隔离等也要考虑到,因此基本上一个容器需要做到6項基本隔离。
技术分享图片
对namespace的操作主要通过clone(),setns(),unshare()这三个系统调用来完成的。
3.1.1查看当前的namespance
root用户:ls -l /proc/$$/ns
技术分享图片
这里$$指的是当前进程ID号,可以看到4026531839这样的数字,表示当前进程指向的namespace.当两个进程指向同一串数字时,表示他们处于同一个namespace下。
3.1.2使用clone()创建新的namespace
创建一个namespace的方法是使用clone()系统调用,它会创建一个新的进程。为了说明创建的过程,给出clone()的原型如下:
int clone (int(child_func) (void ) , void child_stack, int flags , voidarg) ;
如果调用clone()时设置了一个CLONE_NEW的标志,一个与之对应的新的命名空间将被创建,新的进程属于该命名空间,可以使用多个CLONE_NEW标志的组合。
3.1.3使用一个sents()关联一个已经存在namespace
当一个namespace没有进程时还保持打开,这么做是为了后续添加进程到该namesapce.而添加这个功能就是使用sents()系统调用来完成,这使得调用的进程能够和namespace关联,docker exec 就需要用到这个方法:
int setns (int fd, int nstyps);
fd参数指明了关联的namespace,其指向了\proc\PID\ns目录系一个符号连接的文件描述符。可以通过发开这些符号链接指向的文件或者打开一个绑定到符号链接的文件来获得文件描述符。
nstype参数运行调用者检查fd指向的命名空间的类型,如果这个参数等于数,将不会检查,当调用者已经知道namespace的类型时这会很有用。当nstype被赋值为CLONE_NEW*的常量时,内核会检查fd指向的namespace的类型。
要把namespace利用起来,还要使用execve()函数(或者其他的exec()函数),使得我们能够构建一个简单但是有用的工具,该函数可以执行用户命令。
3.1.4使用unshare()在已有进程上进行namespace隔离
unshare()和clone()有些像,不同的地方是前者运行在原有进程上,相当于跳出原来namespace操作,Linux自带的unshare()就是通过调用unshare()这个API来实现。
[[email protected]_110_98_centos ~]# unshare --help
Usage:
unshare [options] <program> [<argument>...]

Run a program with some namespaces unshared from the parent.

Options:
-m, --mount unshare mounts namespace
-u, --uts unshare UTS namespace (hostname etc)
-i, --ipc unshare System V IPC namespace
-n, --net unshare network namespace
-p, --pid unshare pid namespace
-U, --user unshare user namespace
-f, --fork fork before launching <program>
--mount-proc[=<dir>] mount proc filesystem first (implies --mount)
-r, --map-root-user map current user to root (implies --user)
--propagation <slave|shared|private|unchanged>
modify mount propagation in mount namespace
-s, --setgroups allow|deny control the setgroups syscall in user namespaces

-h, --help display this help and exit
-V, --version output version information and exit

For more details see unshare(1).
由于docker没有使用这个系统调用,所以不展开。
3.2 认识Cgroups
Cgroups是linux内核提供的一种可以限制、记录、隔离进程组(process groups)所使用的物理资源(如CPU,内存,I/O等)的机制。最初由Google公司的工程师提出看,后来被整合经linux内核。
这么说理解起来有点吃力,我们通过命令来挂载cgroupfs
提示已经挂载,这个动作一般情况下已经在linux启动的时候做了。
技术分享图片
在主流linux发行版下,都可以通过/etc/cgconfig.conf或者cgroup-bin的相关指令来配置Cgroups。
mount {
cpuset = /sys/fs/cgroup/cpuset;
momory = /sys/fs/cgroup/momory;
}
group cnsworder/test {
perm {
task {
uid = root;
gid = root;
}
admin {
uid = root;
gid = root;
}
}
cpu {
cpu.shares = 1000;
}
}
然后通过命令行把一个进程移动到这个Cgroups之中。
#mount -t group -o cpu cpu /sys/fs/cgroup/cpuset
#cgcreate -g cpu,momory:/cnsworder
#chown root:root /sys/fs/cgroup/cpuset/cnsworder/test/*
#chown root:root /sys/fs/cgroup/cpuset/cnsworder/test/task
#cgrun -g cpu,momory:/cnsworder/test bash
3.3容器的创建
3.3.1系统调用clone()创建新进程,拥有自己的namespace
该进程拥有自己的pid,mount,user,net,ipc和uts namespace。
#pid =clone(fun,stack,flags,clone_arg);
3.3.2将pid写入Cgroup子系统这样就受到Cgroups子系统控制
#echo$pid >/sys/fs/cgroup/cpu/tasks
#echo$pid >/sys/fs/cgroup/cpuset/tasks
#echo$pid >/sys/fs/cgroup/bikio/tasks
#echo$pid >/sys/fs/cgroup/memory/tasks
#echo$pid >/sys/fs/cgroup/devices/tasks
#echo$pid >/sys/fs/cgroup/feezer/tasks
3.3.3通过pivot_root系统调用,使进程进入一个新的rootfs,之后通过exec()系统调用,在新的namespace,Cgroups,rootfs中执行/bin/bash.
fun () {
pivot_root ("path_of_rootfs/", path);
exec ("/bin/bash");
}
通过上面的操作,成功的在一个容器中运行了/bin/bash。

以上是关于Docker从入门到实战的主要内容,如果未能解决你的问题,请参考以下文章

Docker从入门到实战

docker-9 supervisord 参考docker从入门到实战

docker容器从入门到实战0826

[52ABP实战课程系列]Docker&Ubuntu从入门到实战开课啦~

《OD Docker实战》Docker从入门到精通

docker容器从入门到实战0826(笔记整理)