Docker的實(shí)現(xiàn)原理—NameSpace慌烧、Cgroup

容器其實(shí)是一種沙盒技術(shù)令境。顧名思義,沙盒就是能夠像一個(gè)集裝箱一樣昨稼,把你的應(yīng)用“裝”起來的技術(shù)回还。這樣双戳,應(yīng)用與應(yīng)用之間抛人,因?yàn)橛辛诉吔缍恢劣谙嗷ジ蓴_弛姜;而被裝進(jìn)集裝箱的應(yīng)用,也可以被方便地搬來搬去函匕,這也是 PaaS 最理想的狀態(tài)娱据。

計(jì)算機(jī)的程序和進(jìn)程初解

在將Docker的實(shí)現(xiàn)原理之前我們先來聊一聊計(jì)算機(jī)的程序蚪黑。

計(jì)算機(jī)的程序歸根結(jié)底來說就是一組計(jì)算機(jī)能識(shí)別和執(zhí)行的指令盅惜,運(yùn)行于電子計(jì)算機(jī)上,滿足人們某種需求的信息化工具忌穿。

由于計(jì)算機(jī)只認(rèn)識(shí) 0 和 1抒寂,所以無論用哪種語言編寫這段代碼,最后都需要通過某種方式翻譯成二進(jìn)制文件掠剑,才能在計(jì)算機(jī)操作系統(tǒng)中運(yùn)行起來屈芜。而為了能夠讓這些代碼正常運(yùn)行,我們往往還要給它提供數(shù)據(jù)朴译,比如一個(gè)加法程序需要提供一個(gè)輸入或輸入文件井佑。這些數(shù)據(jù)加上代碼本身的二進(jìn)制文件,放在磁盤上眠寿,就是我們平常所說的一個(gè)“程序”躬翁,也叫代碼的可執(zhí)行鏡像(executable image)

執(zhí)行程序時(shí)盯拱,首先盒发,操作系統(tǒng)從“程序”中發(fā)現(xiàn)輸入數(shù)據(jù)保存在一個(gè)文件中例嘱,所以這些數(shù)據(jù)就會(huì)被加載到內(nèi)存中待命。同時(shí)宁舰,操作系統(tǒng)又讀取到了計(jì)算加法的指令拼卵,這時(shí),它就需要指示 CPU 完成加法操作蛮艰。而 CPU 與內(nèi)存協(xié)作進(jìn)行加法計(jì)算腋腮,又會(huì)使用寄存器存放數(shù)值、內(nèi)存堆棧保存執(zhí)行的命令和變量壤蚜。同時(shí)低葫,計(jì)算機(jī)里還有被打開的文件,以及各種各樣的 I/O 設(shè)備在不斷地調(diào)用中修改自己的狀態(tài)仍律。

一旦“程序”被執(zhí)行起來嘿悬,它就從磁盤上的二進(jìn)制文件,變成了計(jì)算機(jī)內(nèi)存中的數(shù)據(jù)水泉、寄存器里的值善涨、堆棧中的指令、被打開的文件草则,以及各種設(shè)備的狀態(tài)信息的一個(gè)集合钢拧。這樣一個(gè)程序運(yùn)行起來后的計(jì)算機(jī)執(zhí)行環(huán)境的總和,我們稱之為:進(jìn)程

Docker的實(shí)現(xiàn)

回過頭來我們來看炕横,虛擬機(jī)和容器源内,無論是虛擬機(jī)還是容器都可以理解是在做虛擬化(也就是一臺(tái)機(jī)器當(dāng)做多臺(tái)來使用),虛擬化核心需要解決的問題:資源隔離與資源限制

  • 虛擬機(jī)硬件虛擬化技術(shù)份殿, 通過一個(gè) hypervisor 層和獨(dú)立的Guest OS實(shí)現(xiàn)對(duì)資源的徹底隔離膜钓。

  • 容器技術(shù)的核心功能,就是通過約束和修改進(jìn)程的動(dòng)態(tài)表現(xiàn)卿嘲,從而制造一個(gè)邊界颂斜。

    • 邊界的實(shí)現(xiàn)方式是利用內(nèi)核的 Cgroup 和 Namespace 特性,此功能完全通過軟件實(shí)現(xiàn)拾枣。

這里雖然容器繼續(xù)也通過Namesapce和Cgroup實(shí)現(xiàn)了隔離沃疮,但它也有其弊端:隔離的不徹底

盡管可以在容器里通過 Mount Namespace 單獨(dú)掛載其他不同版本的操作系統(tǒng)文件,比如 CentOS 或者 Ubuntu梅肤,但這并不能改變共享宿主機(jī)內(nèi)核的事實(shí)司蔬。

隔離機(jī)制

首先,我這里使用centos7系統(tǒng)先嘗試啟動(dòng)一個(gè)容器姨蝴,去觀察它一下:

[root@cluster1 ~]$ docker run -it busybox /bin/sh
/ $ ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/sh
    7 root      0:00 ps aux

這里可以看到一個(gè)比較有趣的事情俊啼,我們?nèi)ゲ榭催M(jìn)程時(shí),我在容器中運(yùn)行的/bin/sh進(jìn)程ID變?yōu)榱恕?”似扔,熟悉centos的人都知道進(jìn)程為“1”的應(yīng)該是systemd程序吨些,而不是我運(yùn)行的/bin/sh搓谆,這其實(shí)就是Docker對(duì)我們運(yùn)行的進(jìn)程使用了障眼法,對(duì)被隔離的應(yīng)用的進(jìn)程空間做了手腳豪墅,使得這個(gè)進(jìn)程只能看到重新計(jì)算過的進(jìn)程編號(hào)泉手,比如 PID=1。這其實(shí)就是Linux當(dāng)中的Namespace機(jī)制偶器,這里的PID namespace被隔離后就會(huì)呈現(xiàn)這樣的效果斩萌。

除此以外,Linux還提供了Mount屏轰、UTS颊郎、IPC、Network 和 User 這些 Namespace霎苗,用來對(duì)各種不同的進(jìn)程上下文進(jìn)行“障眼法”操作姆吭。

這就是Docker對(duì)于隔離機(jī)制的實(shí)現(xiàn)。下面我來深入的了解一下

namesapce 名稱 隔離的資源
Mount Linux 內(nèi)核實(shí)現(xiàn)的第一個(gè) Namespace唁盏,Mount points(文件系統(tǒng)掛載點(diǎn))
IPC System V IPC(信號(hào)量内狸、消息隊(duì)列、共享內(nèi)存) 和POSIX MESSAGE QUEUES
Network Network devices厘擂、stacks昆淡、ports(網(wǎng)絡(luò)設(shè)備、網(wǎng)絡(luò)棧刽严、端口等)
PID Process IDs(進(jìn)程編號(hào))
User User and Groups IDs(用戶和用戶組)
UTS Hostname and NIS domain name(主機(jī)名與NIS域名)

同樣的道理昂灵,我們?nèi)ビ^察一個(gè)運(yùn)行的docker容器,然后手動(dòng)的去模擬實(shí)現(xiàn)它的隔離舞萄,為了方便理解眨补,先確認(rèn)宿主機(jī)存在的內(nèi)容,并且制造一些可以用來區(qū)別的數(shù)據(jù)

[root@cluster1 ~]$ touch /root/host.txt
# 宿主機(jī)上制作一個(gè)掛載點(diǎn)
[root@cluster1 ~]$ mkdir /tmp/tmpfs
[root@cluster1 ~]$ mount -t tmpfs -o size=20m tmpfs /tmp/tmpfs
[root@cluster1 ~]$ df -h /tmp/tmpfs/
Filesystem      Size  Used Avail Use% Mounted on
tmpfs            20M     0   20M   0% /tmp/tmpfs
# 查看宿主機(jī)主機(jī)名
[root@cluster1 ~]$ hostname
cluster1

# 查看宿主機(jī)進(jìn)程
[root@cluster1 ~]$ ps aux  | head
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          1  0.0  0.2 191056  3960 ?        Ss   21:30   0:01 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
root          2  0.0  0.0      0     0 ?        S    21:30   0:00 [kthreadd]
root          4  0.0  0.0      0     0 ?        S<   21:30   0:00 [kworker/0:0H]
root          6  0.0  0.0      0     0 ?        S    21:30   0:00 [ksoftirqd/0]
root          7  0.0  0.0      0     0 ?        S    21:30   0:00 [migration/0]
....

# 為了驗(yàn)證IPC namespace鹏氧,需要?jiǎng)?chuàng)建一個(gè)系統(tǒng)間通信隊(duì)列
# ipcmk -Q 命令:用來創(chuàng)建系統(tǒng)間通信隊(duì)列渤涌。
# ipcs -q 命令:用來查看系統(tǒng)間通信隊(duì)列列表佩谣。
[root@cluster1 ~]$ ipcmk -Q 
[root@cluster1 ~]$ ipcs -q
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x48d07ddf 0          root       644        0            0 

# 查看宿主機(jī)用戶數(shù)量個(gè)數(shù)
[root@cluster1 ~]$ cat /etc/passwd | wc -l
20

# 查看宿主機(jī)網(wǎng)絡(luò)
[root@cluster1 ~]$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    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
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:88:77:6c brd ff:ff:ff:ff:ff:ff
    inet 10.10.1.100/24 brd 10.10.1.255 scope global noprefixroute ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe88:776c/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:b7:30:58:e4 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:b7ff:fe30:58e4/64 scope link 
       valid_lft forever preferred_lft forever
       
# 觀察宿主機(jī)內(nèi)核
[root@cluster1 ~]$ uname -a
Linux cluster1 3.10.0-1127.el7.x86_64 #1 SMP Tue Mar 31 23:36:51 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

下面開始觀察docker容器

# 啟動(dòng)一個(gè)容器
[root@cluster1 ~]$ docker run -it busybox /bin/sh

# 完全獨(dú)立的目錄(非宿主機(jī)目錄)
/ $ ls /
bin    dev    etc    home   lib    lib64  proc   root   sys    tmp    usr    var
/ $ ls /root
/ $ df -h /tmp/tmpfs/
Filesystem                Size      Used Available Use% Mounted on
df: /tmp/tmpfs/: can't find mount point

# 獨(dú)立的主機(jī)名
/# hostname 
d87b4acb91fc

# 獨(dú)立的進(jìn)程
/$ ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/sh
    7 root      0:00 ps aux
    
# 獨(dú)立的系統(tǒng)通信隊(duì)列
/$ ipcs -q
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

# 獨(dú)立的用戶
/$ cat /etc/passwd | wc -l
9

# 獨(dú)立的網(wǎng)絡(luò)
/$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    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
8: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

# 相同的宿主機(jī)內(nèi)核
/$ uname -a
Linux d87b4acb91fc 3.10.0-1127.el7.x86_64 #1 SMP Tue Mar 31 23:36:51 UTC 2020 x86_64 GNU/Linux

OK把还,現(xiàn)在驗(yàn)證了我們上面的觀點(diǎn),容器是被沙河隔離的茸俭,下面我們?cè)賮砜纯词侨绾螌?shí)現(xiàn)的吊履?

我們進(jìn)行一下,用namespace模擬這個(gè)過程调鬓,看看我們能否達(dá)到docker的效果

首先我們先來了解一個(gè)命令—unshare

作用:一個(gè)用來取消與父進(jìn)程共享指定的命名空間

[root@cluster1 ~]# 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

# 我們通過unshare模擬容器的創(chuàng)建
# CentOS7 默認(rèn)允許創(chuàng)建的 User Namespace 為 0
# 所以需要先echo 65535 > /proc/sys/user/max_user_namespaces 打開限制
[root@cluster1 ~]$ echo 65535 > /proc/sys/user/max_user_namespaces
[root@cluster1 ~]$ unshare --mount --pid --mount-proc --uts --ipc --user -r --net --fork /bin/bash

# 沒有獨(dú)立的目錄(獨(dú)立思考一下艇炎,后面會(huì)講)
[root@cluster1 ~]$ ls /root/host.txt 
/root/host.txt

# 沒有獨(dú)立的mount掛載(獨(dú)立思考一下,后面會(huì)講)
[root@cluster1 ~]$ df -h /tmp/tmpfs/
Filesystem      Size  Used Avail Use% Mounted on
tmpfs            20M     0   20M   0% /tmp/tmpfs

# 看起來沒有獨(dú)立的主機(jī)名
[root@cluster1 ~]$ hostname
cluster1

# 獨(dú)立的進(jìn)程
[root@cluster1 ~]$ ps aux
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          1  0.0  0.1 116204  2912 pts/1    S    22:58   0:00 /bin/bash
root         25  0.0  0.0 155472  1852 pts/1    R+   23:00   0:00 ps aux

# 獨(dú)立的系統(tǒng)通信隊(duì)列
[root@cluster1 ~]$ ipcs -q
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages

# 獨(dú)立的網(wǎng)絡(luò)
[root@cluster1 ~]$ ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

# 看起來沒有獨(dú)立的用戶
[root@cluster1 ~]$ cat /etc/passwd | wc -l
20

從上邊論證腾窝,我們還缺乏幾個(gè)沒有隔離的掛載點(diǎn)缀踪、主機(jī)名居砖、目錄和用戶,我們接著分析

# 我們?cè)趫?zhí)行'unshare --mount --pid --mount-proc --uts --ipc --user -r --net --fork /bin/bash' 之后的終端繼續(xù)驗(yàn)證

# 嘗試修改主機(jī)名
$ hostname -b yijiuweishu
$ hostname
yijiuweishu

# 新增掛載點(diǎn)
$ mkdir /tmp/containerfs
$ mount -t tmpfs -o size=20m tmpfs /tmp/containerfs
$ df -h /tmp/containerfs/
Filesystem      Size  Used Avail Use% Mounted on
tmpfs            20M     0   20M   0% /tmp/containerfs

在新開啟一個(gè)終端,去查看宿主機(jī)的主機(jī)名和掛載點(diǎn)

[root@cluster1 ~]$ hostname
cluster1

[root@cluster1 ~]$  df -h /tmp/containerfs/
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda2        49G  2.4G   47G   5% /

可以看到實(shí)際上驴娃,主機(jī)名和掛載點(diǎn)也是獨(dú)立的奏候,只不過剛?cè)∠蚕砟且豢蹋覀儾蛔霾僮鞔匠ǎ鳈C(jī)名和掛載點(diǎn)是和宿主機(jī)一致的蔗草。

再去驗(yàn)證user namespace

  • User Namespace 主要是用來隔離用戶和用戶組的。

  • 一個(gè)比較典型的應(yīng)用場(chǎng)景就是在主機(jī)上以非 root 用戶運(yùn)行的進(jìn)程可以在一個(gè)單獨(dú)的 User Namespace 中映射成 root 用戶疆柔。使用 User Namespace 可以實(shí)現(xiàn)進(jìn)程在容器內(nèi)擁有 root 權(quán)限咒精,而在主機(jī)上卻只是普通用戶。

  • 而不是以root用戶映射成root用戶旷档,這樣映射后還是有root的權(quán)限

# 我們體驗(yàn)下用普通用戶去取消共享user namespace
[root@cluster1 ~]$ useradd test
[root@cluster1 ~]$ su - test
Last login: Fri Mar 17 23:08:56 CST 2023 on pts/2

[test@cluster1 ~]$ unshare --user -r --fork /bin/bash
# 可以看到模叙,雖然我們變成了root用戶,但是并不能調(diào)用一些root能執(zhí)行的命令鞋屈,由此可以推斷我們是進(jìn)行了用戶隔離
[root@cluster1 ~]$ reboot 
Failed to open /dev/initctl: Permission denied
Failed to talk to init daemon.

再來說一下我們看見的還是宿主機(jī)目錄向楼,這里會(huì)引申出一個(gè)新的概念

根文件系統(tǒng)rootfs

根文件系統(tǒng)首先是內(nèi)核啟動(dòng)時(shí)所mount(掛載)的第一個(gè)文件系統(tǒng),內(nèi)核代碼映像文件保存在根文件系統(tǒng)中谐区,而系統(tǒng)引導(dǎo)啟動(dòng)程序會(huì)在根文件系統(tǒng)掛載之后從中把一些基本的初始化腳本和服務(wù)等加載到內(nèi)存中去運(yùn)行湖蜕。

根文件系統(tǒng)一般叫做rootfs,這里所謂的文件系統(tǒng)并不是指FAT宋列、FAT32昭抒、NTFS、XFS這類的文件系統(tǒng)炼杖,熟悉Linux的人都知道灭返,Linux一切皆文件,且目錄結(jié)構(gòu)都在 /下開始坤邪,這里的根文件系統(tǒng)指的就是/熙含。

namespace雖然解決了共享問題,但沒有改變?nèi)萜鞯母夸浲Х模匀萜髦惺峭ㄟ^rootfs來實(shí)現(xiàn)的怎静。

切換新的根目錄,我們需要一個(gè)根文件系統(tǒng)(有自己的/bin黔衡、/dev蚓聘、/sys/proc 等)

# 獲取一個(gè)根文件系統(tǒng)
# 方式一
$ wget https://github.com/ericchiang/containers-from-scratch/releases/download/v0.1.0/rootfs.tar.gz

# 方式二(docker每個(gè)容器也有自己的根文件系統(tǒng)盟劫,我們拷貝一份)
$ docker run -d -i  busybox /bin/sh
$ docker ps
CONTAINER ID   IMAGE     COMMAND     CREATED         STATUS         PORTS     NAMES
6bd3747bc339   busybox   "/bin/sh"   3 minutes ago   Up 3 minutes             heuristic_napier
$ docker export 6bd3747bc339 -o busybox.tar
$ tar xf busybox.tar 
$ ls
bin  busybox.tar  dev  etc  home  lib  lib64  proc  root  sys  tmp  usr  var

chroot 隔離

chroot是在 Unix 和 Linux 系統(tǒng)的一個(gè)操作夜牡,針對(duì)正在運(yùn)作的軟件行程和它的子進(jìn)程,改變的根目錄侣签。使它不能對(duì)改變后的目錄之外的訪問(讀寫塘装、查看)

  • chroot 是通過指定 新的根目錄 和運(yùn)行的命令組成急迂,所以這意味著新的根目錄中也要有可執(zhí)行的命令和層次結(jié)構(gòu)
# chroot NEWROOT [COMMAND [ARG]...]
# 注意 此處的COMMAND需要是新的根目錄中存在的
[root@cluster1 ~]$ pwd
/root
[root@cluster1 ~]$ mkdir rootfs
[root@cluster1 ~]$ docker ps
CONTAINER ID   IMAGE     COMMAND     CREATED         STATUS         PORTS     NAMES
6bd3747bc339   busybox   "/bin/sh"   3 minutes ago   Up 3 minutes             heuristic_napier
[root@cluster1 ~]$ docker export 6bd3747bc339 -o busybox.tar

# 將busybox的根文件系統(tǒng) 放到/root/rootfs/下
[root@cluster1 ~]$ tar xf busybox.tar -C rootfs/



# 切換根目錄
[root@cluster1 ~]$ chroot rootfs/ /bin/bash
# 這表示busybox中,不存在bash終端
chroot: failed to run command ‘/bin/bash’: No such file or director

[root@cluster1 ~]$ chroot rootfs/ /bin/sh
/ # /bin/ls
bin    dev    etc    home   lib    lib64  proc   root   sys    tmp    usr    var
/ # /bin/ls /root
/ #

pivot_root隔離

pivot_root把當(dāng)前進(jìn)程的root文件系統(tǒng)放在put_old目錄蹦肴,而使new_root成為新的root文件系統(tǒng)

  • new_root 與 put_old 必須是文件夾

  • new_root文件夾必須是一個(gè)掛載點(diǎn) 袋毙,并且new_root文件夾里面有完整rootfs的各種文件

  • new_root 文件夾掛載應(yīng)該是一個(gè)與主機(jī)不同的namespace

  • put_old文件夾必須在new_root文件夾內(nèi)

  • 這從某方面也解釋了,為什么有時(shí)候docker run一個(gè)容器后冗尤,宿主機(jī)會(huì)多一個(gè)掛載點(diǎn)(/var/lib/docker/overlay2/xxx/merged)

  • pviot_root主要是把整個(gè)系統(tǒng)切換到一個(gè)新的root目錄听盖,然后去掉對(duì)之前root文件系統(tǒng)的依賴,以便于可以u(píng)mount 之前的文件系統(tǒng)(pivot_root需要root權(quán)限)
  • chroot是只改變即將運(yùn)行的某進(jìn)程的根目錄裂七,而系統(tǒng)的其他部分依舊依賴于老的root文件系統(tǒng)

在Docker中皆看,會(huì)優(yōu)先調(diào)用pivot_root,如果系統(tǒng)不支持才會(huì)使用 chroot

# 宿主機(jī)/root 目錄
[root@cluster1 ~]$ ls
anaconda-ks.cfg  busybox.tar  host.txt rootfs

[root@cluster1 ~]$ unshare --mount --fork /bin/bash         #需要有獨(dú)立的命名空間
[root@cluster1 ~]$ mkdir /new_root
[root@cluster1 ~]$ mount -t tmpfs mytmpfs  /new_root        #new_root是一個(gè)獨(dú)立的掛載點(diǎn)
[root@cluster1 ~]$ mkdir /new_root/old_root                 # 可以是任意名字背零,
[root@cluster1 ~]$ tar xf busybox.tar -C /new_root

# busybox文件系統(tǒng)的/root
[root@cluster1 ~]$ ls /new_root/root
[root@cluster1 ~]$ touch /new_root/root/container.txt
[root@cluster1 ~]$ ls /new_root/root/
container.txt

# pivot_root 切換根文件系統(tǒng)
[root@cluster1 ~]$ pivot_root /new_root /new_root/old_root
[root@cluster1 ~]$ ls   
bash: /usr/bin/ls: No such file or directory                # 沒有環(huán)境變量了
[root@cluster1 ~]$ /bin/ls /root/
container.txt

從另一個(gè)角度驗(yàn)證

Linux中每個(gè)進(jìn)程都會(huì)記錄其所使用的namespace(一串id)腰吟,下面我們來找一找

我們要知道一個(gè)環(huán)境變量, $$ 用于表示當(dāng)前的終端所在的進(jìn)程ID

# 首先查看當(dāng)前系統(tǒng)所用的namespace
[root@cluster1 ~]$ echo $$
5210
[root@cluster1 ~]$ ls -l /proc/$$/ns 
total 0
lrwxrwxrwx 1 root root 0 Mar 18 00:22 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Mar 18 00:22 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Mar 18 00:22 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Mar 18 00:22 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Mar 18 00:22 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Mar 18 00:22 uts -> uts:[4026531838]

# 查看一個(gè)容器所用的namesapce
[root@cluster1 ~]$ docker run -it busybox /bin/sh
/ # echo $$
1
/ # ls -l /proc/1/ns/
total 0
lrwxrwxrwx    1 root     root             0 Mar 17 16:23 ipc -> ipc:[4026532700]
lrwxrwxrwx    1 root     root             0 Mar 17 16:23 mnt -> mnt:[4026532698]
lrwxrwxrwx    1 root     root             0 Mar 17 16:23 net -> net:[4026532703]
lrwxrwxrwx    1 root     root             0 Mar 17 16:23 pid -> pid:[4026532701]
lrwxrwxrwx    1 root     root             0 Mar 17 16:23 user -> user:[4026531837]
lrwxrwxrwx    1 root     root             0 Mar 17 16:23 uts -> uts:[4026532699]


# 再來試試我們手動(dòng)實(shí)現(xiàn)的namesapce 隔離徙瓶,是否生效
[root@cluster1 ~]$ unshare --mount --pid --mount-proc --uts --ipc --user -r --net --fork /bin/bash
[root@cluster1 ~]$ ls -l /proc/$$/ns
total 0
lrwxrwxrwx 1 root root 0 Mar 18 00:25 ipc -> ipc:[4026532699]
lrwxrwxrwx 1 root root 0 Mar 18 00:25 mnt -> mnt:[4026532697]
lrwxrwxrwx 1 root root 0 Mar 18 00:25 net -> net:[4026532702]
lrwxrwxrwx 1 root root 0 Mar 18 00:25 pid -> pid:[4026532700]
lrwxrwxrwx 1 root root 0 Mar 18 00:25 user -> user:[4026532633]
lrwxrwxrwx 1 root root 0 Mar 18 00:25 uts -> uts:[4026532698]

從這個(gè)角度看毛雇,我們手動(dòng)unshare的也都是獨(dú)立的namespace,也論證了我們的觀點(diǎn)侦镇。

當(dāng)然Docker可不僅僅只是創(chuàng)建了namesapce灵疮,實(shí)際上還做了很多細(xì)節(jié)上的處理

Cgroup資源限制

通過namespace可以保證容器之間的隔離,但是無法控制每個(gè)容器可以占用多少資源壳繁, 如果其中的某一個(gè)容器正在執(zhí)行 CPU 密集型的任務(wù)震捣,那么就會(huì)影響其他容器中任務(wù)的性能與執(zhí)行效率,導(dǎo)致多個(gè)容器相互影響并且搶占資源闹炉。如何對(duì)多個(gè)容器的資源使用進(jìn)行限制就成了解決進(jìn)程虛擬資源隔離之后的主要問題蒿赢。

什么是Cgroup?

Control Groups(簡(jiǎn)稱 CGroups)就是能夠隔離宿主機(jī)器上的物理資源渣触,例如 CPU羡棵、內(nèi)存、磁盤 I/O 和網(wǎng)絡(luò)帶寬嗅钻。每一個(gè) CGroup 都是一組被相同的標(biāo)準(zhǔn)和參數(shù)限制的進(jìn)程皂冰。而我們需要做的,其實(shí)就是把容器這個(gè)進(jìn)程加入到指定的Cgroup中啊犬。

類似如上的圖片表示灼擂,創(chuàng)建了 2 個(gè) cgroup(每個(gè) cgroup 有 4 個(gè)進(jìn)程),并且限制它們各自最多只能使用 2GB 的內(nèi)存觉至。如果使用超過 2GB 的內(nèi)存,那么將會(huì)觸發(fā) OOM(Out Of Memory) 睡腿。

cgroup原理和用法并不復(fù)雜语御,但其內(nèi)核數(shù)據(jù)結(jié)構(gòu)特別復(fù)雜峻贮,錯(cuò)綜復(fù)雜的數(shù)據(jù)結(jié)構(gòu)感覺才是cgroup真正的難點(diǎn),了解即可应闯。

感興趣可以看看:https://github.com/dongzhiyan-stack/kernel-code-comment/blob/master/linux-3.10.96/kernel/cgroup.c

Cgroup的特點(diǎn)

  • cgroups的API以一個(gè)偽文件系統(tǒng)的方式實(shí)現(xiàn)纤控,用戶態(tài)的程序可以通過文件操作實(shí)現(xiàn)cgroups 的組織

  • cgroups的組織管理操作單元可以細(xì)粒到線程級(jí)別,用戶可以創(chuàng)建銷毀cgroups碉纺,從而實(shí)現(xiàn)資源再分配管理

  • 所有資源管理的功能都以子系統(tǒng)方式實(shí)現(xiàn)船万,接口統(tǒng)一

  • 子任務(wù)創(chuàng)建之初與其父進(jìn)程處于同一個(gè)cgroups的控制組

Cgroup的作用

作用 說明
資源限制 cgroups可以對(duì)任務(wù)使用的資源總額進(jìn)行限制,如設(shè)定應(yīng)用運(yùn)行時(shí)使用的內(nèi)存上限骨田,一但超過這個(gè)配額就發(fā)出OOM提示
優(yōu)先級(jí)分配 通過分配的CPU時(shí)間片數(shù)量及磁盤IO帶寬大小耿导,實(shí)際就相當(dāng)于控制了任務(wù)運(yùn)行的優(yōu)先級(jí)
資源統(tǒng)計(jì) cgroups 可以統(tǒng)計(jì)系統(tǒng)的資源使用量,如CPU時(shí)長(zhǎng)态贤,內(nèi)存使用量舱呻,這個(gè)功能非常適用于計(jì)費(fèi)
任務(wù)控制 cgroups可以對(duì)任務(wù)執(zhí)行掛起

Cgroup的術(shù)語

術(shù)語 說明
task(任務(wù)) 在cgroups的術(shù)語中,任務(wù)表示系統(tǒng)的一個(gè)進(jìn)程或者線程
cgroups(控制組) cgroups中的資源控制都以cgroups以單位實(shí)現(xiàn)悠汽,cgroups表示按某種資源控制標(biāo)準(zhǔn)劃分而成的任務(wù)組箱吕,包含一個(gè)或多個(gè)子系統(tǒng)茬高,一個(gè)任務(wù)加入某個(gè)cgroups雅采,也可以從某個(gè)cgroups簽到另一cgroups
subsystem(子系統(tǒng)) cgroups中的子系統(tǒng)就是一個(gè)資源調(diào)度控制器。CPU子系統(tǒng)可以控制從CPU時(shí)間分配,內(nèi)存子系統(tǒng)可以設(shè)置cgroups的內(nèi)存使用量
hierarchy(層級(jí)) 層級(jí)由一系列cgroups以一個(gè)樹狀結(jié)構(gòu)排列而成胡陪,每個(gè)層級(jí)通過綁定對(duì)應(yīng)的子系統(tǒng)進(jìn)行資源控制。層級(jí)中cgroups的節(jié)點(diǎn)可以包含零個(gè)節(jié)點(diǎn)或多個(gè)子節(jié)點(diǎn),子節(jié)點(diǎn)繼承父節(jié)點(diǎn)掛載的子系統(tǒng)吹泡。整個(gè)操作系統(tǒng)可以有多個(gè)層級(jí)

Cgroup子系統(tǒng)

一個(gè)子系統(tǒng)代表一類資源調(diào)度控制器爆哑。例如內(nèi)存子系統(tǒng)可以限制內(nèi)存的使用量揭朝,CPU 子系統(tǒng)可以限制 CPU 的使用時(shí)間潭袱。子系統(tǒng)是真正實(shí)現(xiàn)某類資源的限制的基礎(chǔ)敌卓。

Subsystem(子系統(tǒng)) cgroups 中的子系統(tǒng)就是一個(gè)資源調(diào)度控制器(又叫 controllers)

最終在Linux表現(xiàn)其實(shí)是一個(gè)文件系統(tǒng)

下面我們?cè)贑entos7上查看一下Cgroup的子系統(tǒng)(Centos7 默認(rèn)systemd也會(huì)使用cgroup)

# 控制cgroup可以使用命令工具行,lssubsys
[root@cluster1 ~]$ yum install -y libcgroup-tools

[root@cluster1 ~]$ lssubsys -m 
cpuset /sys/fs/cgroup/cpuset
cpu,cpuacct /sys/fs/cgroup/cpu,cpuacct
memory /sys/fs/cgroup/memory
devices /sys/fs/cgroup/devices
freezer /sys/fs/cgroup/freezer
net_cls,net_prio /sys/fs/cgroup/net_cls,net_prio
blkio /sys/fs/cgroup/blkio
perf_event /sys/fs/cgroup/perf_event
hugetlb /sys/fs/cgroup/hugetlb
pids /sys/fs/cgroup/pids

# 通過查看文件也是一樣
[root@cluster1 ~]$ ll /sys/fs/cgroup/
total 0
drwxr-xr-x 2 root root  0 Mar 18 11:57 blkio
lrwxrwxrwx 1 root root 11 Mar 18 11:57 cpu -> cpu,cpuacct
lrwxrwxrwx 1 root root 11 Mar 18 11:57 cpuacct -> cpu,cpuacct
drwxr-xr-x 2 root root  0 Mar 18 11:57 cpu,cpuacct
drwxr-xr-x 2 root root  0 Mar 18 11:57 cpuset
drwxr-xr-x 3 root root  0 Mar 18 12:01 devices
drwxr-xr-x 2 root root  0 Mar 18 11:57 freezer
drwxr-xr-x 2 root root  0 Mar 18 11:57 hugetlb
drwxr-xr-x 2 root root  0 Mar 18 11:57 memory
lrwxrwxrwx 1 root root 16 Mar 18 11:57 net_cls -> net_cls,net_prio
drwxr-xr-x 2 root root  0 Mar 18 11:57 net_cls,net_prio
lrwxrwxrwx 1 root root 16 Mar 18 11:57 net_prio -> net_cls,net_prio
drwxr-xr-x 2 root root  0 Mar 18 11:57 perf_event
drwxr-xr-x 2 root root  0 Mar 18 11:57 pids
drwxr-xr-x 4 root root  0 Mar 18 11:57 systemd

# 通過掛載點(diǎn)查看關(guān)聯(lián)的cgroup子系統(tǒng)
[root@cluster1 ~]$ 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/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
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/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
  • cpu:使用調(diào)度程序控制任務(wù)對(duì)cpu的使用

  • cpuacct:自動(dòng)生成cgroup中任務(wù)對(duì)cpu資源使用情況的報(bào)告

  • cpuset:主要用于設(shè)置CPU和內(nèi)存的親和性

  • blkio:塊設(shè)備 I/O 限制

  • devices:可以開啟或關(guān)閉cgroup中任務(wù)對(duì)設(shè)備的訪問

  • freezer: 可以掛起或恢復(fù)cgroup中的任務(wù)

  • pids:限制任務(wù)數(shù)量

  • memory:可以設(shè)定cgroup中任務(wù)對(duì)內(nèi)存使用量的限定,并且自動(dòng)生成這些任務(wù)對(duì)內(nèi)存資源使用情況的報(bào)告

  • perf_event:增加了對(duì)每 group 的監(jiān)測(cè)跟蹤的能力,可以監(jiān)測(cè)屬于某個(gè)特定的 group 的所有線程以及運(yùn)行在特定CPU上的線程

  • net_cls:docker沒有直接使用它鸥跟,它通過使用等級(jí)識(shí)別符標(biāo)記網(wǎng)絡(luò)數(shù)據(jù)包医咨,從而允許linux流量控制程序識(shí)別從具體cgroup中生成的數(shù)據(jù)包

Cgroup控制組

控制組 說白了就是一組進(jìn)程(進(jìn)程組),cgroup 就是用來限制 控制組 的資源使用拟淮。為了能夠方便地向一個(gè) 控制組 添加或者移除進(jìn)程(在命令行也能操作)谴忧,內(nèi)核使用了 虛擬文件系統(tǒng) 來進(jìn)行管理 控制組很泊。

控制組可以類比成Linux的目錄樹結(jié)構(gòu),由于目錄有層級(jí)關(guān)系撑蚌,所以 控制組 也有層級(jí)關(guān)系

每個(gè)控制組目錄中搏屑,都有一個(gè)名為 tasks 的文件辣恋,用于保存當(dāng)前 控制組 包含的進(jìn)程列表。如果我們想向某個(gè) 控制組 添加一個(gè)進(jìn)程時(shí)携狭,可以把進(jìn)程的 PID 寫入到 tasks 文件中即可稀并。

在 Linux 內(nèi)核中,可以存在多個(gè) 層級(jí)(控制組樹)单默,每個(gè)層級(jí)可以關(guān)聯(lián)一個(gè)或多個(gè) 資源控制子系統(tǒng)碘举,但同一個(gè) 資源控制子系統(tǒng) 不能關(guān)聯(lián)到多個(gè)層級(jí)中。如下圖所示:

直白來說搁廓,即/cgrp4關(guān)聯(lián)在/cgrp1下引颈,所以不能再直接關(guān)聯(lián)/sys/fs/cgroup/的某個(gè)子系統(tǒng)了

# 如果用戶想把資源控制子系統(tǒng)關(guān)聯(lián)到其他層級(jí),那么可以使用 mount 命令來進(jìn)行掛載

# 將內(nèi)存子系統(tǒng)重新關(guān)聯(lián)到 /sys/fs/cgroup/memory 這個(gè)層級(jí)
mount -t cgroup -o memory memory /sys/fs/cgroup/memory

動(dòng)手操作

為了不被冗余的篇幅影響閱讀體驗(yàn)境蜕,這里以cpuset設(shè)置獨(dú)占cpu舉例

其他的子系統(tǒng)請(qǐng)參考我的另一篇文章:

cpuset子系統(tǒng)

先來看下cpuset子系統(tǒng)下的文件作用

文件 說明
cpuset.cpus 允許cgroup中的進(jìn)程使用的CPU列表蝙场。如0-2,16代表 0,1,2,16這4個(gè)CPU
cpuset.cpu_exclusive cgroup是否獨(dú)占cpuset.cpus 中分配的cpu 。(默認(rèn)值0粱年,共享售滤;1,獨(dú)占)逼泣,如果設(shè)置為1趴泌,其他cgroup內(nèi)的cpuset.cpus值不能包含有該cpuset.cpus內(nèi)的值
cpuset.mems 允許cgroup中的進(jìn)程使用的內(nèi)存節(jié)點(diǎn)列表。如0-2,16代表 0,1,2,16這4個(gè)可用節(jié)點(diǎn)
cpuset.mem_exclusive 是否獨(dú)占memory拉庶,(默認(rèn)值0嗜憔,共享;1氏仗,獨(dú)占)
cpuset.mem_hardwall cgroup中任務(wù)的內(nèi)存是否隔離吉捶,(默認(rèn)值0夺鲜,不隔離;1呐舔,隔離币励,每個(gè)用戶的任務(wù)將擁有獨(dú)立的空間)
cpuset.memory_pressure 衡量cpuset中內(nèi)存分頁(yè)壓力的大小
cpuset.memory_spread_page 是否在允許的節(jié)點(diǎn)上均勻分布頁(yè)面緩存
cpuset.memory_spread_slab 是否將 slab 緩存均勻分布在允許的節(jié)點(diǎn)上
cpuset.sched_load_balance 是否在該 cpuset 上的 CPU 內(nèi)進(jìn)行負(fù)載平衡
cpuset.sched_relax_domain_level 遷移任務(wù)時(shí)的搜索范圍
cpuset.memory_pressure_enabled 僅在root cgroup中存在,表示是否計(jì)算內(nèi)存壓力

簡(jiǎn)單了解一下stress命令

stress --cpu 2 --timeout 600

表示使用stress命令模擬將2個(gè)CPU使用到百分之百珊拼,一會(huì)我們只綁定一個(gè)CPU食呻,用stress壓一下,看看會(huì)有幾個(gè)CPU被占滿

# 由于centos7已經(jīng)使用了cgroup澎现,所以不需要我們額外掛載
# mount -t cgroup -ocpuset cpuset /sys/fs/cgroup/cpuset

cd /sys/fs/cgroup/cpuset
mkdir wzp
cd wzp
echo 1 > cpuset.cpus
echo 0 > cpuset.mems
/bin/echo $$ > tasks
bash
# 這里執(zhí)行之后仅胞,我們新執(zhí)行的bash終端是在wzp這個(gè)cgroup的限制中的,也就是說剑辫,他僅可以使用1號(hào)CPU
cat /proc/self/cpuset


# 執(zhí)行命令干旧,模擬讓兩個(gè)CPU 100%
stress --cpu 2 --timeout 600

# 另開一個(gè)終端,top看看實(shí)際情況

PS:要?jiǎng)h除/sys/fs/cgroup/xxx/下 自己創(chuàng)建的目錄妹蔽,不能用 rm -rf 只能用rmdir

觀察Docker容器

docker的cgroup存放在/sys/fs/cgroups/某子系統(tǒng)/docker/<container-ID>

下面我們啟動(dòng)一個(gè)docker椎眯,并設(shè)置上cpuset、cpu和memory限制來驗(yàn)證我們的觀點(diǎn)

# 設(shè)置nginx容器使用CPU0-1胳岂,共享CPU512编整,內(nèi)存大小500m
[root@cluster1 ~]$ docker run -d --cpuset-cpus="0-1" --cpu-shares=512 --memory=500m nginx:alpine
4431d5a9c9ff657dc826a947c8c556e96b2e66722bd0e65932f77ed8357769e6

# 查看cgroup配置
[root@cluster1 ~]$ cat /sys/fs/cgroup/cpuset/docker/4431d5a9c9ff657dc826a947c8c556e96b2e66722bd0e65932f77ed8357769e6/cpuset.cpus 
0-1
[root@cluster1 ~]$ cat /sys/fs/cgroup/cpu/docker/4431d5a9c9ff657dc826a947c8c556e96b2e66722bd0e65932f77ed8357769e6/cpu.shares 
512
[root@cluster1 ~]$ cat /sys/fs/cgroup/memory/docker/4431d5a9c9ff657dc826a947c8c556e96b2e66722bd0e65932f77ed8357769e6/memory.limit_in_bytes 
524288000

下一篇,Docker的實(shí)現(xiàn)原理——網(wǎng)絡(luò)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旦万,一起剝皮案震驚了整個(gè)濱河市闹击,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌成艘,老刑警劉巖赏半,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異淆两,居然都是意外死亡断箫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門秋冰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仲义,“玉大人,你說我怎么就攤上這事剑勾“D欤” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵虽另,是天一觀的道長(zhǎng)暂刘。 經(jīng)常有香客問我,道長(zhǎng)捂刺,這世上最難降的妖魔是什么谣拣? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任募寨,我火速辦了婚禮,結(jié)果婚禮上森缠,老公的妹妹穿的比我還像新娘拔鹰。我一直安慰自己,他們只是感情好贵涵,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布列肢。 她就那樣靜靜地躺著,像睡著了一般独悴。 火紅的嫁衣襯著肌膚如雪例书。 梳的紋絲不亂的頭發(fā)上锣尉,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天刻炒,我揣著相機(jī)與錄音,去河邊找鬼自沧。 笑死坟奥,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拇厢。 我是一名探鬼主播爱谁,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼孝偎!你這毒婦竟也來了访敌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤衣盾,失蹤者是張志新(化名)和其女友劉穎寺旺,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體势决,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡阻塑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了果复。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陈莽。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖虽抄,靈堂內(nèi)的尸體忽然破棺而出走搁,到底是詐尸還是另有隱情,我是刑警寧澤迈窟,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布私植,位于F島的核電站,受9級(jí)特大地震影響菠隆,放射性物質(zhì)發(fā)生泄漏兵琳。R本人自食惡果不足惜狂秘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望躯肌。 院中可真熱鬧者春,春花似錦、人聲如沸清女。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嫡丙。三九已至拴袭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間曙博,已是汗流浹背拥刻。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留父泳,地道東北人般哼。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像惠窄,于是被迫代替她去往敵國(guó)和親蒸眠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容