高效运维公众号由萧田国及朋友们维护,经常发布各种广为传播的优秀原创技术文章,关注运维转型,陪伴您的运维职业生涯,一起愉快滴发展。 |
本文主要介绍使用 shell 实现一个简易的 Docker。
Linux Namespace 是 Linux 提供的一种内核级别环境隔离的方法。学习过 Linux 的同学应该对 chroot 命令比较熟悉(通过修改根目录把用户限制在一个特定目录下),chroot 提供了一种简单的隔离模式:chroot 内部的文件系统无法访问外部的内容。Linux Namespace 在此基础上,提供了对 UTS、IPC、mount、PID、network、User 等的隔离机制。
Namespace 是对全局系统资源的一种封装隔离,使得处于不同 namespace 的进程拥有独立的全局系统资源,改变一个 namespace 中的系统资源只会影响当前 namespace 里的进程,对其他 namespace 中的进程没有影响。
Linux Namespace 有如下种类:
namespace 相关的系统调用有3个,分别是 clone()、setns()、unshare()。
clone: 创建一个新的进程并把这个新进程放到新的 namespace 中
setns: 将当前进程加入到已有的 namespace 中
unshare: 使当前进程退出指定类型的 namespace,并加入到新创建的 namespace 中
系统中的每个进程都有 /proc/[pid]/ns/这样一个目录,里面包含了这个进程所属 namespace 的信息,里面每个文件的描述符都可以用来作为s etns 函数(2.1.2)的 fd 参数。
#查看当前bash进程关联的Namespace
# ls -l /proc/$$/ns
total 0
lrwxrwxrwx 1 root root 0 Jan 17 21:43 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Jan 17 21:43 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Jan 17 21:43 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Jan 17 21:43 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Jan 17 21:43 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Jan 17 21:43 uts -> uts:[4026531838]
#这些 namespace 文件都是链接文件。链接文件的内容的格式为 xxx:[inode number]。
其中的 xxx 为 namespace 的类型,inode number 则用来标识一个 namespace,我们也可以把它理解为 namespace 的 ID。
如果两个进程的某个 namespace 文件指向同一个链接文件,说明其相关资源在同一个 namespace 中。以ipc:[4026531839]例,
ipc是namespace的类型,4026531839是inode number,如果两个进程的ipc namespace的inode number一样,说明他们属于同一个namespace。
这条规则对其他类型的namespace也同样适用。
#从上面的输出可以看出,对于每种类型的namespace,进程都会与一个namespace ID关联。
#当一个namespace中的所有进程都退出时,该namespace将会被销毁。在 /proc/[pid]/ns 里放置这些链接文件的作用就是,一旦这些链接文件被打开,
只要打开的文件描述符(fd)存在,那么就算该 namespace 下的所有进程都结束了,但这个 namespace 也会一直存在,后续的进程还可以再加入进来。
2.1.4.1 IPC Namespace
IPC namespace 用来隔离 System V IPC objects 和 POSIX message queues。其中 System V IPC objects 包含消息列表 Message queues、信号量 Semaphore sets 和共享内存 Shared memory segments。为了展现区分 IPC Namespace 我们这里会使用到 ipc 相关命令:
# nsenter: 加入指定进程的指定类型的namespace中,然后执行参数中指定的命令。# 命令格式:nsenter [options] [program [arguments]]
# 示例:nsenter –t 27668 –u –I /bin/bash
#
# unshare: 离开当前指定类型的namespace,创建且加入新的namesapce,然后执行参数中执行的命令。# 命令格式:unshare [options] program [arguments]
# 示例:unshare --fork --pid --mount-proc readlink /proc/self
#
# ipcmk:创建shared memory segments, message queues, 和semaphore arrays
# 参数-Q:创建message queues
# ipcs:查看shared memory segments, message queues, 和semaphore arrays的相关信息
# 参数-a:显示全部可显示的信息
# 参数-q:显示活动的消息队列信息
shell A
#查看当前shell的uts / ipc namespace number
# readlink /proc/$$/ns/uts /proc/$$/ns/ipc
uts:[4026531838]
ipc:[4026531839]
#查看当前主机名
# hostname
myCentos
#查看ipc message queues,默认情况下没有message queue
# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
#创建一个message queue
# ipcmk -Q
Message queue id: 131072
# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x82a1d963 131072 root 644 0 0
-----> 切换至shell B执行
------------------------------------------------------------------
#回到shell A之后我们可以看下hostname、ipc等有没有收到影响
# hostname
myCentos
# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x82a1d963 131072 root 644 0 0
#接下来我们尝试加入shell B中新的Namespace
# nsenter -t 30372 -u -i /bin/bash
[root@shell-B:/root]
# hostname
shell-B
# readlink /proc/$$/ns/uts /proc/$$/ns/ipc
uts:[4026532382]
ipc:[4026532383]
# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
#可以看到我们已经成功的加入到了新的Namespace中
shell B
#确认当前shell和shell A属于相同Namespace
# readlink /proc/$$/ns/uts /proc/$$/ns/ipc
uts:[4026531838]
ipc:[4026531839]
# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x82a1d963 131072 root 644 0 0
#使用unshare创建新的uts和ipc Namespace,并在新的Namespace中启动bash
# unshare -iu /bin/bash
#确认新的bash uts/ipc Namespace Number
# readlink /proc/$$/ns/uts /proc/$$/ns/ipc
uts:[4026532382]
ipc:[4026532383]
#设置新的hostname与shell A做区分
# hostname shell-B
# hostname
shell-B
#查看之前的ipc message queue
# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
#查看当前bash进程的PID
# echo $$
30372
切换回shell A
2.1.4.2 Net Namespace
相关命令:ip netns: 管理网络namespace
用法:
ip netns list
ip netns add NAME
ip netns set NAME NETNSID
ip [-all] netns delete [NAME]
shell A
#创建一对网卡,分别命名为veth0_11/veth1_11
# ip link add veth0_11 type veth peer name veth1_11
#查看已经创建的网卡
#ip a
1: lo:
mtu 65536 qdisc noqueue state UNKNOWN qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: eth0:
mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 5e:75:97:0d:54:17 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.1/24 brd 192.168.1.255 scope global eth0
valid_lft forever preferred_lft forever
3: br1:
mtu 1500 qdisc noqueue state DOWN qlen 1000 link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
inet 172.18.0.1/24 scope global br1
valid_lft forever preferred_lft forever
96: veth1_11@veth0_11:
mtu 1500 qdisc noop state DOWN qlen 1000 link/ether 5e:75:97:0d:54:0e brd ff:ff:ff:ff:ff:ff
97: veth0_11@veth1_11:
mtu 1500 qdisc noop state DOWN qlen 1000 link/ether a6:c7:1f:79:a6:a6 brd ff:ff:ff:ff:ff:ff
#使用ip netns创建两个net namespace
# ip netns add r1
# ip netns add r2
# ip netns list
r2
r1 (id: 0)
#将两个网卡分别加入到对应的netns中
# ip link set veth0_11 netns r1
# ip link set veth1_11 netns r2
#再次查看网卡,在bash当前的namespace中已经看不到veth0_11和veth1_11了
# ip a
1: lo:
mtu 65536 qdisc noqueue state UNKNOWN qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: eth0:
mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 5e:75:97:0d:54:17 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.1/24 brd 192.168.1.255 scope global eth0
valid_lft forever preferred_lft forever
3: br1:
mtu 1500 qdisc noqueue state DOWN qlen 1000 link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
inet 172.18.0.1/24 scope global br1
valid_lft forever preferred_lft forever
#接下来我们切换到对应的netns中对网卡进行配置
#通过nsenter --net可以切换到对应的netns中,ip a展示了我们上面加入到r1中的网卡
# nsenter --net=/var/run/netns/r1 /bin/bash
# ip a
1: lo:
mtu 65536 qdisc noop state DOWN qlen 1 link/loopback 00:00:00:00:00 :00 brd 00:00:00:00:00:00
97: veth0_11@if96:
mtu 1500 qdisc noop state DOWN qlen 1000 link/ether a6:c7:1f:79:a6:a6 brd ff:ff:ff:ff:ff:ff link-netnsid 1
#对网卡配置ip并启动
# ip addr add 172.18.0.11/24 dev veth0_11
# ip link set veth0_11 up
# ip a
1: lo:
mtu 65536 qdisc noop state DOWN qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
97: veth0_11@if96:
mtu 1500 qdisc noqueue state LOWERLAYERDOWN qlen 1000 link/ether a6:c7:1f:79:a6:a6 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet 172.18.0.11/24 scope global veth0_11
valid_lft forever preferred_lft forever
-----> 切换至shell B执行
------------------------------------------------------------------
#在r1中ping veth1_11
# ping 172.18.0.12
PING 172.18.0.12 (172.18.0.12) 56(84) bytes of data.
64 bytes from 172.18.0.12: icmp_seq=1 ttl=64 time=0.033 ms
64 bytes from 172.18.0.12: icmp_seq=2 ttl=64 time=0.049 ms
...
#至此我们通过netns完成了创建net Namespace的小实验
shell B
#在shell B中我们同样切换到netns r2中进行配置
#通过nsenter --net可以切换到r2,ip a展示了我们上面加入到r2中的网卡
# nsenter --net=/var/run/netns/r2 /bin/bash
# ip a
1: lo:
mtu 65536 qdisc noop state DOWN qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
96: veth1_11@if97:
mtu 1500 qdisc noop state DOWN qlen 1000 link/ether 5e:75:97:0d:54:0e brd ff:ff:ff:ff:ff:ff link-netnsid 0
#对网卡配置ip并启动
# ip addr add 172.18.0.12/24 dev veth1_11
# ip link set veth1_11 up
# ip a
1: lo:
mtu 65536 qdisc noop state DOWN qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
96: veth1_11@if97:
mtu 1500 qdisc noqueue state UP qlen 1000 link/ether 5e:75:97:0d:54:0e brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.18.0.12/24 scope global veth1_11
valid_lft forever preferred_lft forever
inet6 fe80::5c75:97ff:fe0d:540e/64 scope link
valid_lft forever preferred_lft forever
#尝试ping r1中的网卡
# ping 172.18.0.11
PING 172.18.0.11 (172.18.0.11) 56(84) bytes of data.
64 bytes from 172.18.0.11: icmp_seq=1 ttl=64 time=0.046 ms
64 bytes from 172.18.0.11: icmp_seq=2 ttl=64 time=0.040 ms
...
#可以完成通信
切换至shell A执行
示意图
资源限制(Resource limiting): Cgroups可以对进程组使用的资源总额进行限制。如对特定的进程进行内存使用上限限制,当超出上限时,会触发OOM。
优先级分配(Prioritization): 通过分配的CPU时间片数量及硬盘IO带宽大小,实际上就相当于控制了进程运行的优先级。
资源统计(Accounting): Cgroups可以统计系统的资源使用量,如CPU使用时长、内存用量等等,这个功能非常适用于计费。
进程控制(Control):Cgroups可以对进程组执行挂起、恢复等操作。
Cgroups的组成:
task : 在 Cgroups 中,task 就是系统的一个进程。
cgroup : Cgroups 中的资源控制都以 cgroup 为单位实现的。cgroup 表示按照某种资源控制标准划分而成的任务组,包含一个或多个子系统。一个任务可以加入某个 cgroup,也可以从某个 cgroup 迁移到另外一个 cgroup。
查看当前系统支持的 subsystem
#通过/proc/cgroups查看当前系统支持哪些subsystem
# cat /proc/cgroups
#subsys_name hierarchy num_cgroups enabled
cpuset 11 1 1
cpu 4 67 1
cpuacct 4 67 1
memory 5 69 1
devices 7 62 1
freezer 8 1 1
net_cls 6 1 1
blkio 9 62 1
perf_event 3 1 1
hugetlb 2 1 1
pids 10 62 1
net_prio 6 1 1
#字段含义
#subsys_name: subsystem的名称
#hierarchy:subsystem所关联到的cgroup树的ID,如果多个subsystem关联到同一颗cgroup树,那么他们的这个字段将一样,比如这里的cpu和cpuacct就一样,表示他们绑定到了同一颗树。如果出现下面的情况,这个字段将为0:当前subsystem没有和任何cgroup树绑定
当前subsystem已经和cgroup v2的树绑定
当前subsystem没有被内核开启
#num_cgroups: subsystem所关联的cgroup树中进程组的个数,也即树上节点的个数
#enabled: 1表示开启,0表示没有被开启(可以通过设置内核的启动参数“cgroup_disable”来控制subsystem的开启).
查看进程所属 cgroup
#查看当前shell进程所属的cgroup
# cat /proc/$$/cgroup
11:cpuset:/
10:pids:/system.slice/sshd.service
9:blkio:/system.slice/sshd.service
8:freezer:/
7:devices:/system.slice/sshd.service
6:net_prio,net_cls:/
5:memory:/system.slice/sshd.service
4:cpuacct,cpu:/system.slice/sshd.service
3:perf_event:/
2:hugetlb:/
1:name=systemd:/system.slice/sshd.service
#字段含义(以冒号分为3列):# 1. cgroup树ID,对应/proc/cgroups中的hierachy
# 2. cgroup所绑定的subsystem,多个subsystem使用逗号分隔。name=systemd表示没有和任何subsystem绑定,只是给他起了个名字叫systemd。# 3. 进程在cgroup树中的路径,即进程所属的cgroup,这个路径是相对于挂载点的相对路径。
使用cgroup
查看下当前系统 cgroup 挂载情况。
#过滤系统挂载可以查看cgroup
# mount |grep cgroup
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio,net_cls)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
#如果系统中没有挂载cgroup,可以使用mount命令创建cgroup
#挂载根cgroup
# mkdir /sys/fs/cgroup
# mount -t tmpfs cgroup_root /sys/fs/cgroup
#将cpuset subsystem关联到/sys/fs/cgroup/cpu_memory
# mkdir /sys/fs/cgroup/cpuset
# sudo mount -t cgroup cpuset -o cgroup /sys/fs/cgroup/cpuset/
#将cpu和memory subsystem关联到/sys/fs/cgroup/cpu_memory
# mkdir /sys/fs/cgroup/cpu_memory
# sudo mount -n -t cgroup -o cpu,memory cgroup /sys/fs/cgroup/cpu_memory
# Centos操作系统可以通过yum install cgroup-tools 来安装以下命令
cgcreate: 在层级中创建新cgroup。用法: cgcreate [-h] [-t <tuid>:<tgid>] [-a <agid>:<auid>] [-f mode] [-d mode] [-s mode]
-g <controllers>:<path> [-g ...]
示例: cgcreate -g *:student -g devices:teacher //在所有的挂载hierarchy中创建student cgroup,在devices
hierarchy挂载点创建teacher cgroup
cgset: 设置指定cgroup(s)的参数
用法: cgset [-r <name=value>] <cgroup_path> ...
示例: cgset -r cpuset.cpus=0-1 student //将student cgroup的cpuset控制器中的cpus限制为0-1
cgexec: 在指定的cgroup中运行任务
用法: cgexec [-h] [-g <controllers>:<path>] [--sticky] command [arguments]
示例: cgexec -g cpu,memory:test1 ls -l //在cpu和memory控制器下的test1 cgroup中运行ls -l命令
lower :可以是多个,是处于最底层的目录,作为只读层。
upper :只有一个,作为读写层。
work :为工作基础目录,挂载后内容会被清空,且在使用过程中其内容用户不可见。
读:
如果文件在容器层(upperdir),直接读取文件;
shell
# 创建所需的目录
# mkdir upper lower merged work
# echo "lower" > lower/in_lower.txt
# echo "upper" > upper/in_upper.txt
# 在lower和upper中都创建 in_both文件
# echo "lower" > lower/in_both.txt
# echo "upper" > upper/in_both.txt
#查看下我们当前的目录及文件结构
# tree .
.
|-- lower
| |-- in_both.txt
| `-- in_lower.txt
|-- merged
|-- upper
| |-- in_both.txt
| `-- in_upper.txt
`-- work
#使用mount命令将创建的目录联合挂载起来
# mount -t overlay overlay -o lowerdir=lower,upperdir=upper,workdir=work merged
#查看mount结果可以看到已经成功挂载了
# mount |grep overlay
overlay on /data/overlay_demo/merged type overlay (rw,relatime,lowerdir=lower,upperdir=upper,workdir=work)
#此时再查看文件目录结构
# tree .
.
|-- lower
| |-- in_both.txt
| `-- in_lower.txt
|-- merged
| |-- in_both.txt
| |-- in_lower.txt
| `-- in_upper.txt
|-- upper
| |-- in_both.txt
| `-- in_upper.txt
`-- work
`-- work
#可以看到merged中包含了lower和upper中的文件
#然后我查看merge中的in_both文件,验证了上层目录覆盖下层的结论
# cat merged/in_both.txt
upper
#上面我们验证了挂载后overlayfs的读,接下来我们去验证下写
#我们在merged中创建一个新文件,并查看
# touch merged/new_file
# tree .
.
|-- lower
| |-- in_both.txt
| `-- in_lower.txt
|-- merged
| |-- in_both.txt
| |-- in_lower.txt
| |-- in_upper.txt
| `-- new_file
|-- upper
| |-- in_both.txt
| |-- in_upper.txt
| `-- new_file
`-- work
`-- work
#可以看到新文件实际是放在了upper目录中
#下面我们看下如果删除了lower和upper中都有的文件会怎样
# rm -f merged/in_both.txt
# tree .
.
|-- lower
| |-- in_both.txt
| `-- in_lower.txt
|-- merged
| |-- in_lower.txt
| |-- in_upper.txt
| `-- new_file
|-- upper
| |-- in_both.txt
| |-- in_upper.txt
| `-- new_file
`-- work
`-- work
#从文件目录上看只有merge中没有了in_both文件,但是upper中的文件已经发生了变化
# ll upper/in_both.txt
c--------- 1 root root 0, 0 Jan 21 19:33 upper/in_both.txt
#upper/in_both.txt已经变成了一个空的字符文件,且覆盖了lower层的内容
#!/usr/bin/env bash
set -o errexit -o nounset -o pipefail; shopt -s nullglob
overlay_path='/var/lib/bocker/overlay' && container_path='/var/lib/bocker/containers' && cgroups='cpu,cpuacct,memory';
[[ $# -gt 0 ]] && while [ "${1:0:2}" == '--' ]; do OPTION=${1:2}; [[ $OPTION =~ = ]] && declare "BOCKER_${OPTION/=*/}=${OPTION/*=/}" || declare "BOCKER_${OPTION}=x"; shift; done
function bocker_check() {
case ${1:0:3} in
img) ls "$overlay_path" | grep -qw "$1" && echo 0 || echo 1;;
ps_) ls "$container_path" | grep -qw "$1" && echo 2 || echo 3;;
esac
}
function bocker_init() { #HELP Create an image from a directory:\nBOCKER init
uuid="img_$(shuf -i 42002-42254 -n 1)"
if [[ -d "$1" ]]; then
[[ "$(bocker_check "$uuid")" == 0 ]] && bocker_run "$@"
mkdir "$overlay_path/$uuid" > /dev/null
cp -rf --reflink=auto "$1"/* "$overlay_path/$uuid" > /dev/null
[[ ! -f "$overlay_path/$uuid"/img.source ]] && echo "$1" > "$overlay_path /$uuid"/img.source
[[ ! -d "$overlay_path/$uuid"/proc ]] && mkdir "$overlay_path/$uuid"/proc
echo "Created: $uuid"
else
echo "No directory named '$1' exists"
fi
}
function bocker_pull() { #HELP Pull an image from Docker Hub:\nBOCKER pull
tmp_uuid="$(uuidgen)" && mkdir /tmp/"$tmp_uuid"
download-frozen-image-v2 /tmp/"$tmp_uuid" "$1:$2" > /dev/null
rm -rf /tmp/"$tmp_uuid"/repositories
for tar in $(jq '.[].Layers[]' --raw-output < /tmp/$tmp_uuid/manifest.json); do
tar xf /tmp/$tmp_uuid/$tar -C /tmp/$tmp_uuid && rm -rf /tmp/$tmp_uuid/$tar
done
for config in $(jq '.[].Config' --raw-output < /tmp/$tmp_uuid/manifest.json); do
rm -f /tmp/$tmp_uuid/$config
done
echo "$1:$2" > /tmp/$tmp_uuid/img.source
bocker_init /tmp/$tmp_uuid && rm -rf /tmp/$tmp_uuid
}
function bocker_rm() { #HELP Delete an image or container:\nBOCKER rm
[[ "$(bocker_check "$1")" == 3 ]] && echo "No container named '$1' exists" && exit 1
[[ "$(bocker_check "$1")" == 1 ]] && echo "No image named '$1' exists" && exit 1
if [[ -d "$overlay_path/$1" ]];then
rm -rf "$overlay_path/$1" && echo "Removed: $1"
else
umount "$container_path/$1"/merged && rm -rf "$container_path/$1" && ip netns del netns_"$1" && ip link del dev veth0_"$1" && echo "Removed: $1"
cgdelete -g "$cgroups:/$1" &> /dev/null
fi
}
function bocker_images() { #HELP List images:\nBOCKER images
echo -e "IMAGE_ID\t\tSOURCE"
for img in "$overlay_path"/img_*; do
img=$(basename "$img")
echo -e "$img\t\t$(cat "$overlay_path/$img/img.source")"
done
}
function bocker_ps() { #HELP List containers:\nBOCKER ps
echo -e "CONTAINER_ID\t\tCOMMAND"
for ps in "$container_path"/ps_*; do
ps=$(basename "$ps")
echo -e "$ps\t\t$(cat "$container_path/$ps/$ps.cmd")"
done
}
function bocker_run() { #HELP Create a container:\nBOCKER run
uuid="ps_$(shuf -i 42002-42254 -n 1)"
[[ "$(bocker_check "$1")" == 1 ]] && echo "No image named '$1' exists" && exit 1
[[ "$(bocker_check "$uuid")" == 2 ]] && echo "UUID conflict, retrying..." && bocker_run "$@" && return
cmd="${@:2}" && ip="$(echo "${uuid: -3}" | sed 's/0//g')" && mac="${uuid: -3:1}:${uuid: -2}"
ip link add dev veth0_"$uuid" type veth peer name veth1_"$uuid"
ip link set dev veth0_"$uuid" up
ip link set veth0_"$uuid" master br1
ip netns add netns_"$uuid"
ip link set veth1_"$uuid" netns netns_"$uuid"
ip netns exec netns_"$uuid" ip link set dev lo up
ip netns exec netns_"$uuid" ip link set veth1_"$uuid" address 02:42:ac:11:00"$mac"
ip netns exec netns_"$uuid" ip addr add 172.18.0."$ip"/24 dev veth1_"$uuid"
ip netns exec netns_"$uuid" ip link set dev veth1_"$uuid" up
ip netns exec netns_"$uuid" ip route add default via 172.18.0.1
mkdir -p "$container_path/$uuid"/{lower,upper,work,merged} && cp -rf --reflink=auto "$overlay_path/$1"/* "$container_path/$uuid"/lower > /dev/null && \
mount -t overlay overlay \
-o lowerdir="$container_path/$uuid"/lower,upperdir="$container_path/$uuid"/upper,workdir="$container_path/$uuid"/work \
"$container_path/$uuid"/merged
echo 'nameserver 114.114.114.114' > "$container_path/$uuid"/merged/etc/resolv.conf
echo "$cmd" > "$container_path/$uuid/$uuid.cmd"
cgcreate -g "$cgroups:/$uuid"
: "${BOCKER_CPU_SHARE:=512}" && cgset -r cpu.shares="$BOCKER_CPU_SHARE" "$uuid"
: "${BOCKER_MEM_LIMIT:=512}" && cgset -r memory.limit_in_bytes="$((BOCKER_MEM_LIMIT * 1000000))" "$uuid "
cgexec -g "$cgroups:$uuid" \
ip netns exec netns_"$uuid" \
unshare -fmuip --mount-proc \
chroot "$container_path/$uuid"/merged \
/bin/sh -c "/bin/mount -t proc proc /proc && $cmd" \
2>&1 | tee "$container_path/$uuid/$uuid.log" || true
ip link del dev veth0_"$uuid"
ip netns del netns_"$uuid"
}
function bocker_exec() { #HELP Execute a command in a running container:\nBOCKER exec
[[ "$(bocker_check "$1")" == 3 ]] && echo "No container named '$1' exists" && exit 1
cid="$(ps o ppid,pid | grep "^$(ps o pid,cmd | grep -E "^\ *[0-9]+ unshare.*$1" | awk '{print $1}')" | awk '{print $2}')"
[[ ! "$cid" =~ ^\ *[0-9]+$ ]] && echo "Container '$1' exists but is not running" && exit 1
nsenter -t "$cid" -m -u -i -n -p chroot "$container_path/$1"/merged "${@:2}"
}
function bocker_logs() { #HELP View logs from a container:\nBOCKER logs
[[ "$(bocker_check "$1")" == 3 ]] && echo "No container named '$1' exists" && exit 1
cat "$container_path/$1/$1.log"
}
function bocker_commit() { #HELP Commit a container to an image:\nBOCKER commit
[[ "$(bocker_check "$1")" == 3 ]] && echo "No container named '$1' exists" && exit 1
[[ "$(bocker_check "$2")" == 0 ]] && echo "Image named '$2' exists" && exit 1
mkdir "$overlay_path/$2" && cp -rf --reflink=auto "$container_path/$1"/merged/* "$overlay_path/$2" && sed -i "s/:.*$/:$(date +%Y%m%d-%H%M%S)/g" "$overlay_path/$2"/img.source
echo "Created: $2"
}
function bocker_help() { #HELP Display this message:\nBOCKER help
sed -n "s/^.*#HELP\\s//p;" < "$1" | sed "s/\\\\n/\n\t/g;s/$/\n/;s!BOCKER!${1/!/\\!}!g"
}
[[ -z "${1-}" ]] && bocker_help "$0" && exit 1
case $1 in
pull|init|rm|images|ps|run|exec|logs|commit) bocker_"$1" "${@:2}" ;;
*) bocker_help "$0" ;;
esac
README
Bocker
使用100行bash实现一个docker,本脚本是依据bocker实现,更换了存储驱动,完善了pull等功能。
前置条件
为了脚本能够正常运行,机器上需要具备以下组件:
iproute2
iptables
libcgroup-tools
util-linux >= 2.25.2
coreutils >= 7.5
另外你可能还要做以下设置:
实现的功能
来源:本文转自公众号“vivo互联网技术”, 点击查看原文 。
多项首批评估结果揭晓!2023年12月15日,中国信通院 DevOps、AIOps 系列标准最新评估结果重磅发布!
“高效运维”公众号诚邀广大技术人员投稿
投稿邮箱:[email protected],或添加联系人微信:greatops1118。
点个“在看”,一年不宕机
|
意林 · 《 我的画风不太对① 》|连载一 呵呵,地球人好脆弱哦! 7 年前 |
|
叙拉古之惑 · 罗马人的故事 7 年前 |
|
下厨房 · 满满一大罐的健康坚果,抱着吃好满足 7 年前 |
|
开智学堂 · 工作中的最小阻力原则 7 年前 |
|
只说冬夏 · 房思琪的悲剧里,我看到了什么? 7 年前 |