Docker 容器入门(编写自己的容器和镜像)
图片
Docker容器的优势
容器是一系列操作系统技术的集合,它允许你运行进程。容器本身是一项非常古老的技术,但自从 Docker 及其用于创建、部署和分发容器的软件系统问世以来,容器技术得到了广泛应用。我们将探讨在不使用 Docker 的情况下构建容器所需的基本单元,以便你能够摆脱诸如“轻量级虚拟机”、“与 Docker 有关”、“穷人的虚拟化”之类的定义。
那么,我们需要哪些技术来创建我们自己的容器呢?
首先,我们来看看操作系统(OS)的基本功能。操作系统运行进程,进程是系统中执行的基本工作单元。操作系统维护着一个进程控制块(PCB)表,其中每个进程都通过一个称为进程 ID(PID)的特殊编号进行标识。PCB 表还包含进程的状态、权限、内存信息、环境变量、路径、子进程等信息。每个进程都有一个特定的根目录,进程在该目录中执行。您可以在/proc文件系统中找到名为 `/etc/` 的文件夹,或者运行以下命令来查找这些信息:
ps aux
这是它在我的机器上的样子:
这其中还有很多细节,但我们将重点放在 Linux 系统的高级概述上。
现在,如果我们能够创建一个进程并将其隔离,使其能够在其他地方运行而无需安装整个操作系统,我们就可以称其为容器。为了隔离这个进程,即使其无法访问自身文件夹之外的资源,我们需要将其“囚禁”起来。我们可以使用以下命令来实现:
chroot /path/to/new/root command
它会更改此进程及其子进程的根文件夹,因此该进程将无法访问此文件夹之外的任何内容。让我们以超级用户身份执行以下步骤:
mkdir my-new-root
chroot my-new-root bash
这里我们创建了一个新文件夹,然后使用chroot命令“更改根目录”并bash在其中运行命令。
您应该会看到类似“找不到 bash”或“命令无法识别”的错误信息。请参考以下屏幕截图:

由于 bash 命令运行在 my-new-root 目录下,无法访问该目录之外的任何资源,因此它找不到运行 bash shell 的程序。
要解决此问题,请使用 'ldd' 命令。ldd
会打印程序运行所需的共享其他对象。
ldd /bin/bash
此命令会输出运行特定程序所需的依赖项:

让我们把这些文件复制到 my-new-root 目录下的相应文件夹中。
mkdir my-new-root/{bin,lib64,lib}
cp /bin/bash my-new-root/bin
cp /lib/x86_64-linux-gnu/libtinfo.so.5 /lib/x86_64-linux-gnu/libdl.so.2 /lib/x86_64-linux-gnu/libc.so.6 my-new-root/lib
cp /lib64/ld-linux-x86-64.so.2 my-new-root/lib64
这里我们创建了三个文件夹,用于存放 bash 所需的共享库(分别位于 lib 和 lib64 目录下)。然后我们将这些对象复制到相应的文件夹中。
现在运行chroot my-new-root bash它,它会在 my-new-root 目录下打开一个 bash shell。你可以通过pwd它的输出来验证这一点。/
为什么不试试
ls在这里也启用该命令呢?
即使我们的新 root 用户无法访问外部文件,它仍然可以看到主机容器上正在运行的进程。如果我们想在同一主机上运行多个容器,这种方法就行不通了。为了实现真正的隔离,我们还需要对其他进程隐藏进程。否则,一个容器可以终止其他容器的进程 ID (PID)、卸载文件系统或更改其他容器的网络设置。每个进程都位于 UNIX 系统中定义的 7 个命名空间之一。我们可以使用以下命令unshare来查看这些命名空间:
我们可以看到上面列出的7个重要的命名空间。我们可以使用 unshare 命令来限制这些命名空间。
unshare --mount --uts --ipc --net --pid --fork --user --map-root-user chroot my-new-root bash
现在,我们的新根用户已限制了对这些命名空间中任何进程的访问。它们现在也可以获得重复的进程 ID 了!这才是真正的隔离。
最后一点,命名空间并不能帮助我们限制物理资源,例如内存限制。对于这些资源,我们需要使用容器配置cgroups文件,它本质上是一个文件,我们可以在其中收集进程 ID (PID) 并定义 CPU、内存或网络带宽的限制。这很重要,因为一个容器可能会耗尽宿主机环境的资源(例如通过fork bomb 攻击),导致其他容器无法使用。
注意:Windows 操作系统不易受到传统的 fork bomb 攻击,因为它们无法创建其他进程。
以下是具体做法(不用担心命令,我们只是在学习容器的构成要素)。
# outside of unshare'd environment get the tools we'll need here
apt-get install -y cgroup-tools htop
# create new cgroups
cgcreate -g cpu,memory,blkio,devices,freezer:/sandbox
# add our unshare'd env to our cgroup
ps aux # grab the bash PID that's right after the unshare one
cgclassify -g cpu,memory,blkio,devices,freezer:sandbox <PID>
# Set a limit of 80M
cgset -r memory.limit_in_bytes=80M sandbox
就这样!我们现在已经创建了自己的容器。
让我们在不使用 Docker 的情况下创建镜像。
图片
镜像本质上是将预先打包好的容器打包成的文件对象。
我们可以使用以下命令将此容器打包成压缩文件:
tar cvf dockercontainer.tar my-new-root
现在我们可以把它寄到其他地方,然后创建一个文件夹来解压缩它:
# make container-root directory, export contents of container into it
mkdir container-root
tar xf dockercontainer.tar -C container-root/
# make a contained user, mount in name spaces
unshare --mount --uts --ipc --net --pid --fork --user --map-root-user chroot $PWD/container-root ash # change root to it
# mount needed things
# change cgroup settings
# etc
就这些?意思是我们可以像这样到处使用容器吗?其实不然,Docker 的功能远不止这些。它提供了一个强大的镜像仓库,里面包含预编译的镜像,还支持网络功能、导入、导出、运行、标记、列出、销毁这些镜像等等。
Docker容器的优势
- 运行时:Docker 引擎允许我们使用编译后的不同软件包,并在各种操作系统上运行相同的程序。它的运行时也带来了良好的工作流程优势。
- 镜像:极佳的便携性、镜像注册表、镜像差异比较
- 自动化:您的容器可以通过包含配置的文件从本地计算机流向 Jenkins。它还支持容器缓存和多阶段构建。因此,镜像构建速度非常快。
这里提供了一个用 Go 语言编写的上述流程示例及其配套视频。
我们将在后续文章中详细探讨这些内容。感谢您阅读到最后。
如果你觉得这篇文章对你有所帮助,学到了新知识,请分享。你也可以在推特上跟我打个招呼。保重 :)
文章来源:https://dev.to/dpkahuja/a-docker-free-intro-to-containers-write-your-own-containers-and-images-3pk4



