无需Docker, 5分钟徒手DIY 一个Linux容器

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器镜像服务 ACR,镜像仓库100个 不限时长
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介:

容器技术,其优势以及应用场景想必大家都不陌生。本文旨在用DIY一个简陋的Linux容器作为例子,让感兴趣的同学大致体会容器是如何建成的。

什么是容器?

提到容器最容易想到的大概就是Docker了, 然而Docker只是容器的一种实现以及提供丰富的周边工具,本质上也是利用了Linux primitives构建Linux container。如果要求我只能用一句话来定义容器,那么我会说:容器是 一个隔离的进程工作室 (an isolated process workspace)。这么定义当然太笼统,展开细节可以轻松讲几篇文章,我们这里聚焦最浅显的概念:隔离 (isolation),普遍的观点是将隔离分为两类:

  1. Namespace isolation: 容器内的进程看到的pid, 文件系统,hostname, IP, 网卡都是独立于host的,在namespace内无法修改其他namespaces的内容。
  2. Resource isolation: 容器内的进程系统资源的使用可以限制,例如容器内的进程们CPU/Memory使用不能超过quota等

DIY一个Linux容器

接下来我们将step by step讲解如何用Linux自带的工具将进程 (processes)放入容器 (an isolated workspace), 注: 整个流程参考了Eric Chiang的containers from scratch, 原文中有几个地方我没有直接走通,这里加了一些个人的剪裁和修改。我的环境是Ali Linux 4.4.114-1.al7.x86_64, 接下来的步骤无需安装Docker也不需要安装其他工具

Namespace隔离

1. 准备容器文件系统 (类似Docker image)

container filesystem本质上就是一个tarball, 有兴趣的话可以看一下Redbeard的minimal containers 视频,核心是一句话:A CONTAINER FILESYSTEM ISN'T MAGIC, IT'S JUST A TARBALL

# 下载+解压container filesystem tarball
[root@iZ2zea1bcc0lrqq26qw1esZ ~]# wget https://github.com/ericchiang/containers-from-scratch/releases/download/v0.1.0/rootfs.tar.gz
[root@iZ2zea1bcc0lrqq26qw1esZ ~]# tar -xzf rootfs.tar.gz

[root@iZ2zea1bcc0lrqq26qw1esZ ~]# ls rootfs
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

可以看到 rootfs下面看到的文件结构和Liunx / 下看到的很相似。

2. 创建一个简陋的容器

我们的目的是进入rootfs,并且以rootfs为新的root (/), 这样进程只能看到rootfs中的文件, Linux自带的chroot工具可以很容易达到这种效果

# Mount rootfs/proc,这样在rootfs子文件系统中的进程可以做`ps aux`这样的命令
[root@iZ2zea1bcc0lrqq26qw1esZ ~]# mount -t proc proc rootfs/proc

# 用chroot将 /bin/bash 进程看到的root限制在dir rootfs, 恭喜你进入了你自建的隔离非常简陋的容器!
[root@iZ2zea1bcc0lrqq26qw1esZ ~]# chroot rootfs /bin/bash

# 现在bash shell中的root (/) 已经变成了 host上面的“/root/rootfs”
root@iZ2zea1bcc0lrqq26qw1esZ:/# bin/ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  test.py  tmp  usr  var

# 这时文件系统隔离有了,但是进程隔离还没有, 下面的命令可以看到机器上所有的进程,我们容器里的shell甚至可以去kill其他进程,不理想!
root@iZ2zea1bcc0lrqq26qw1esZ:/# bin/ps aux

# 退出该容器
exit

3. 隔离PID

为了限制/bin/bash进程能看到的pids,我们需要为bash进程创建pid namespace。用 Linux unshare command 可以方便地为进程创造不同类型的namespace

# 下面的命令为 /bin/bash进程创建了一个新的process namespace
[root@iZ2zea1bcc0lrqq26qw1esZ ~]# unshare -p -f --mount-proc=$PWD/rootfs/proc chroot rootfs /bin/bash

# 现在shell里面只能看到本pid namespace的两个进程了,注意shell进程是pid 1, 这里和Docker的主进程为pid 1完全一样
root@iZ2zea1bcc0lrqq26qw1esZ:/# bin/ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0  20272  3284 ?        S    19:08   0:00 /bin/bash
root         2  0.0  0.0  17500  2084 ?        R+   19:08   0:00 bin/ps aux

上面几步之后,我们基本有了一个对filesystm和pid有隔离的容器,这个容器距离严谨的namespace隔离还有很长的路要走,例如创建Network namespace, 修改hostname, username space (不允许容器用户为root), 这里假设我们的隔离的需求很简单只是mount + pid。

Resource 隔离

CGroup (control group) 对于CPU, memory, 文件系统资源的隔离的技术已经很成熟,大多数容器也都选择用cgroup来做资源限制,对cgroup的细节不在这里介绍,下面的步骤会为上面创建好的container加上内存使用的限制

# 下面这些命令在host shell内执行,而不是刚才创建的container shell
# 创建一个叫做mygroup的memory cgroup
[root@iZ2zea1bcc0lrqq26qw1esZ ~]# mkdir /sys/fs/cgroup/memory/mygroup

# 限制在这个cgroup里面所有的进程内存使用和不能超过100MB
[root@iZ2zea1bcc0lrqq26qw1esZ ~]# echo "100000000" > /sys/fs/cgroup/memory/mygroup/memory.limit_in_bytes

# 找到我们的container在host上面的pid,这里是10028 (这里需要注意有两个bash进程,我们的容器进程是从unshare命令里面fork出来的,因此是10028而不是unshare 10027本身)
[root@iZ2zea1bcc0lrqq26qw1esZ ~]# ps aux|grep bin/bash
root     10027  0.0  0.0 107904   704 pts/0    S    01:49   0:00 unshare -p -f --mount-proc=/root/rootfs/proc chroot rootfs /bin/bash
root     10028  0.0  0.0  20264  3068 pts/0    S+   01:49   0:00 /bin/bash
root     10031  0.0  0.0 112668  2216 pts/1    S+   01:49   0:00 grep --color=auto bin/bash

# 根据你找到pid替换$MY_PID
[root@iZ2zea1bcc0lrqq26qw1esZ ~]# export MY_PID=10028

# 将shell pid加入mygroup, 加入之后,容器内所有的进程 (bash以及其子进程)都会受到内存使用100M的
[root@iZ2zea1bcc0lrqq26qw1esZ ~]# echo $MY_PID > /sys/fs/cgroup/memory/mygroup/tasks

# 下面这些命令在container shell内执行
# 测试一下效果,下面的python脚本会不断消耗内存
bin/cat <<EOF > ./test.py
a = ''
i=0
while True:
i += 1
print "%dmb" % (i*10,)
some_str = ' ' * 10000000
a = a + some_str
EOF

# 执行脚本,可以看到内存接近100M的时候脚本会被kill, 现在我们再也不用担心这个容器内的进程会用光host的内存啦, nice!
root@iZ2zea1bcc0lrqq26qw1esZ:/# /usr/bin/python test.py 
10mb
20mb
30mb
40mb
50mb
60mb
70mb
80mb
Killed

总结

到这里,我们DIY了一个文件系统,pid隔离,内存限制为100M的容器。通用的容器需要对网络,user namespace, 以及其他系统资源(CPU, disk等)做了更完整的隔离,这篇文章的目的不是鼓励大家去自建容器,而是通过一个简单的动手教程让大家熟悉Linux容器的构成和实现。Docker以及其他容器runtime都提供了强大的功能,但是归根溯源都还是将Linux已有的namespace工具,cgroup结合在一起搭建出了易用容器。欢迎对容器技术感兴趣的同学拍砖交流。

目录
相关文章
|
10天前
|
Kubernetes Cloud Native Docker
云原生时代的容器化实践:Docker和Kubernetes入门
【10月更文挑战第37天】在数字化转型的浪潮中,云原生技术成为企业提升敏捷性和效率的关键。本篇文章将引导读者了解如何利用Docker进行容器化打包及部署,以及Kubernetes集群管理的基础操作,帮助初学者快速入门云原生的世界。通过实际案例分析,我们将深入探讨这些技术在现代IT架构中的应用与影响。
44 2
|
8天前
|
运维 Cloud Native 虚拟化
一文吃透云原生 Docker 容器,建议收藏!
本文深入解析云原生Docker容器技术,涵盖容器与Docker的概念、优势、架构设计及应用场景等,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
一文吃透云原生 Docker 容器,建议收藏!
|
4天前
|
运维 Cloud Native 云计算
云原生之旅:Docker容器化实战
本文将带你走进云原生的世界,深入理解Docker技术如何改变应用部署与运维。我们将通过实际案例,展示如何利用Docker简化开发流程,提升应用的可移植性和伸缩性。文章不仅介绍基础概念,还提供操作指南和最佳实践,帮助你快速上手Docker,开启云原生的第一步。
|
2天前
|
关系型数据库 MySQL Java
【Docker最新版教程】一文带你快速入门Docker常见用法,实现容器编排和自动化部署上线项目
Docker快速入门到项目部署,MySQL部署+Nginx部署+docker自定义镜像+docker网络+DockerCompose项目实战一文搞定!
|
2天前
|
开发者 Docker Python
从零开始:使用Docker容器化你的Python Web应用
从零开始:使用Docker容器化你的Python Web应用
9 1
|
6天前
|
运维 持续交付 虚拟化
深入解析Docker容器化技术的核心原理
深入解析Docker容器化技术的核心原理
22 1
|
7天前
|
机器学习/深度学习 数据采集 Docker
Docker容器化实战:构建并部署一个简单的Web应用
Docker容器化实战:构建并部署一个简单的Web应用
|
7天前
|
JavaScript 开发者 Docker
Docker容器化实战:构建并部署一个简单的Web应用
Docker容器化实战:构建并部署一个简单的Web应用
|
7天前
|
持续交付 开发者 Docker
Docker容器化技术实战指南
Docker容器化技术实战指南
24 1
|
7天前
|
安全 Docker 微服务
深入理解Docker容器技术:从基础到实践
深入理解Docker容器技术:从基础到实践