精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

干貨分享:用 Go 從頭實現(xiàn)一個迷你 Docker—Gocker

新聞 前端
在本文中,我們將關注 Linux 操作系統(tǒng)上的容器,并簡單地說明為什么Windows 上的容器[1]根本不存在。

 容器很受歡迎。容器已成為應用程序在服務器上打包和運行的默認方式,最初是由 Docker 普及的?,F(xiàn)在,Docker 是公司的名稱和一個命令(一組命令),使您可以輕松管理容器(創(chuàng)建,運行,刪除,網(wǎng)絡)。但是,容器本身是從一組操作系統(tǒng)原語創(chuàng)建的。在本文中,我們將關注 Linux 操作系統(tǒng)上的容器,并簡單地說明為什么Windows 上的容器[1]根本不存在。

Linux 下沒有創(chuàng)建容器的單個系統(tǒng)調用。它們是利用 Linux 命名空間和控制組或 cgroups 構成的松散構造。

Gocker 是什么?

Gocker[2] 是一個使用 Go 編程語言從頭開始實現(xiàn) Docker 核心功能的項目。它主要目的是提供對容器在 Linux 系統(tǒng)調用級別上如何工作的理解。Gocker 允許你創(chuàng)建容器,管理容器鏡像(Image),在容器中執(zhí)行進程等。

干貨分享:用 Go 從頭實現(xiàn)一個迷你 Docker—Gocker

Gocker 的功能

Gocker 可以模擬 Docker 的內核,讓你管理 Docker 鏡像(從 Docker Hub 獲取),運行容器,列出正在運行的容器或在已經(jīng)運行的容器中運行進程:

  • 在容器中運行進程
    • gocker run <--cpus=cpus-max> <--mem=mem-max> <--pids=pids-max> <image[:tag]> </path/to/command>
  • 列出正在運行的容器
    • gocker ps
  • 在運行的容器中執(zhí)行進程
    • gocker exec </path/to/command>
  • 列出本地可用的鏡像
    • gocker images
  • 刪除本地可用的鏡像
    • gocker rmi

其他功能

  • Gocker 使用 Overlay 文件系統(tǒng)快速創(chuàng)建容器,而無需復制整個文件系統(tǒng),同時還可以在多個容器實例之間共享同一容器鏡像。
  • Gocker 容器擁有自己的網(wǎng)絡命名空間,并且能夠訪問 Internet。請參閱下面的限制。
  • 您可以控制系統(tǒng)資源,例如 CPU 百分比,RAM 數(shù)量和進程數(shù)。Gocker 通過利用 cgroups 實現(xiàn)了這一目標。

Gocker 容器隔離性

用 Gocker 創(chuàng)建的容器擁有自己的以下命名空間(請參見 run.go 和 network.go):

  • 文件系統(tǒng) File system (via chroot)
  • PID
  • IPC
  • UTS (hostname)
  • Mount
  • Network

在創(chuàng)建用于限制以下內容的 cgroup 時,除非你在 gocker run 命令中指定了 --mem,--cpus 或 --pids 選項,否則容器將使用無限的資源。這些標志分別限制了容器可以使用的最大 RAM,CPU 內核和 PID。

  • CPU 核心數(shù)
  • RAM
  • PID 數(shù)量(限制進程)

命名空間(Namespaces)基礎

所有 Linux 計算機在啟動時都是 “default” 命名空間的一部分。在計算機上創(chuàng)建的進程也繼承默認命名空間。換句話說,因為所有對象也都存在于默認命名空間中,進程可以看到正在運行的其他進程,網(wǎng)絡接口,掛載點,名為 IPC 的對象或權限允許的文件。當創(chuàng)建一個進程時,我們可以告訴 Linux 為我們創(chuàng)建一個新的 PID 命名空間,在這種情況下,新進程及其任何后代形成一個新的層次結構或 PID,而新創(chuàng)建的初始進程為 PID 1,就像 Linux 機器上特殊的初始化進程一樣。假設使用新的 PID 命名空間創(chuàng)建了一個名為 “new_child” 的進程。當該進程或其后代使用諸如 getpid() 或 getppid() 之類的系統(tǒng)調用時,它們會在新命名空間中看到 PID。例如,對于這兩個系統(tǒng)調用,在新創(chuàng)建的 PID 命名空間中的 new_child 將獲得 1。而當您從默認命名空間查看 new_child 的 PID 時,當然不會為其分配 1(那是默認命名空間中的 init 了)。

Linux 操作系統(tǒng)提供了在創(chuàng)建進程時或與之關聯(lián)的正在運行的進程創(chuàng)建新命名空間的方法。所有命名空間,無論其類型如何,都被分配了內部 ID。命名空間是一種內核對象。一個進程只能屬于一個命名空間。例如,假設一個進程 new_child 的 PID 命名空間設置為內部 ID 為 0x87654321 的命名空間,它不能屬于另一個 PID 命名空間。但是,可能存在其他屬于同一 PID 命名空間 0x87654321 的其他進程。同樣,new_child 的后代將自動屬于相同的 PID 命名空間。命名空間是繼承的。

你可以使用 lsns 實用程序列出計算機中的各種命名空間。即使您的計算機上沒有運行任何容器,也很可能會看到與各種命名空間相關的其他進程。這表明,命名空間并不僅僅是在容器的上下文中使用。它們可以在任何地方使用。它們提供隔離。它們是一項強大的安全功能。在現(xiàn)代 Linux 系統(tǒng)上,您會看到 init,systemd,幾個系統(tǒng)守護程序,Chrome,Slack,當然還有使用各種命名空間的 Docker 容器。讓我們看一看我機器上的 lsns 實用程序的輸出:

  1.    NS TYPE   NPROCS   PID USER             COMMAND 
  2. 4026532281 mnt         1   313 root             /usr/lib/systemd/systemd-udevd 
  3. 4026532282 uts         1   313 root             /usr/lib/systemd/systemd-udevd 
  4. 4026532313 mnt         1   483 systemd-timesync /usr/lib/systemd/systemd-timesyncd 
  5. 4026532332 uts         1   483 systemd-timesync /usr/lib/systemd/systemd-timesyncd 
  6. 4026532334 mnt         1   502 root             /usr/bin/NetworkManager --no-daemon 
  7. 4026532335 mnt         1   503 root             /usr/lib/systemd/systemd-logind 
  8. 4026532336 uts         1   503 root             /usr/lib/systemd/systemd-logind 
  9. 4026532341 pid         1  1943 shuveb           /opt/google/chrome/nacl_helper 
  10. 4026532343 pid         2  1941 shuveb           /opt/google/chrome/chrome --type=zygote 
  11. 4026532345 net        50  1941 shuveb           /opt/google/chrome/chrome --type=zygote 
  12. 4026532449 mnt         1   547 root             /usr/lib/boltd 
  13. 4026532489 mnt         1   580 root             /usr/lib/bluetooth/bluetoothd 
  14. 4026532579 net         1  1943 shuveb           /opt/google/chrome/nacl_helper 
  15. 4026532661 mnt         1   766 root             /usr/lib/upowerd 
  16. 4026532664 user        1   766 root             /usr/lib/upowerd 
  17. 4026532665 pid         1  2521 shuveb           /opt/google/chrome/chrome --type=renderer 
  18. 4026532667 net         1   836 rtkit            /usr/lib/rtkit-daemon 
  19. 4026532753 mnt         1   943 colord           /usr/lib/colord 
  20. 4026532769 user        1  1943 shuveb           /opt/google/chrome/nacl_helper 
  21. 4026532770 user       50  1941 shuveb           /opt/google/chrome/chrome --type=zygote 
  22. 4026532771 pid         1  2010 shuveb           /opt/google/chrome/chrome --type=renderer 
  23. 4026532772 pid         1  2765 shuveb           /opt/google/chrome/chrome --type=renderer 
  24. 4026531835 cgroup    294     1 root             /sbin/init 
  25. 4026531836 pid       237     1 root             /sbin/init 
  26. 4026531837 user      238     1 root             /sbin/init 
  27. 4026531838 uts       289     1 root             /sbin/init 
  28. 4026531839 ipc       292     1 root             /sbin/init 
  29. 4026531840 mnt       283     1 root             /sbin/init 
  30. 4026531992 net       236     1 root             /sbin/init 
  31. 4026532912 pid         2  3249 shuveb           /usr/lib/slack/slack --type=zygote 
  32. 4026532914 net         2  3249 shuveb           /usr/lib/slack/slack --type=zygote 
  33. 4026533003 user        2  3249 shuveb           /usr/lib/slack/slack --type=zygote 

即使您沒有顯式創(chuàng)建命名空間,進程也將成為默認命名空間的一部分。所有命名空間的詳細信息都記錄在 /proc 文件系統(tǒng)中。您可以通過輸入 ls -l /proc/self/ns/來查看您的 Shell 進程所屬的命名空間。這是我電腦的結果。另外,這些大多是從 init 繼承的:

  1. ➜  ~ ls -l /proc/self/ns 
  2. total 0 
  3. lrwxrwxrwx 1 shuveb shuveb 0 Jun 13 11:44 cgroup -> 'cgroup:[4026531835]' 
  4. lrwxrwxrwx 1 shuveb shuveb 0 Jun 13 11:44 ipc -> 'ipc:[4026531839]' 
  5. lrwxrwxrwx 1 shuveb shuveb 0 Jun 13 11:44 mnt -> 'mnt:[4026531840]' 
  6. lrwxrwxrwx 1 shuveb shuveb 0 Jun 13 11:44 net -> 'net:[4026531992]' 
  7. lrwxrwxrwx 1 shuveb shuveb 0 Jun 13 11:44 pid -> 'pid:[4026531836]' 
  8. lrwxrwxrwx 1 shuveb shuveb 0 Jun 13 11:44 pid_for_children -> 'pid:[4026531836]' 
  9. lrwxrwxrwx 1 shuveb shuveb 0 Jun 13 11:44 user -> 'user:[4026531837]' 
  10. lrwxrwxrwx 1 shuveb shuveb 0 Jun 13 11:44 uts -> 'uts:[4026531838]' 

沒有容器的命名空間

從 lsns 的輸出中,我們看到容器并不是唯一使用命名空間的對象。為此,讓我們創(chuàng)建一個具有自己的 PID 命名空間的 shell 實例。我們將使用 unshare 實用程序來做到這一點。“unshare” 這個名字很明顯。還有一個同名的 Linux 系統(tǒng)調用[3],可讓您取消共享默認命名空間,從而使調用進程加入新創(chuàng)建的命名空間。

  1. ➜  ~ sudo unshare --fork --pid --mount-proc /bin/bash 
  2. [root@kodai shuveb]# ps aux 
  3. USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND 
  4. root           1  0.5  0.0   8296  4944 pts/1    S    08:59   0:00 /bin/bash 
  5. root           2  0.0  0.0   8816  3336 pts/1    R+   08:59   0:00 ps aux 
  6. [root@kodai shuveb]#  

在以上調用中,unshare 實用程序正在派生一個新進程,調用 unshare() 系統(tǒng)調用以創(chuàng)建一個新的 PID 命名空間,然后在其中執(zhí)行 /bin/bash。我們還告訴 unshare 實用程序在新進程中掛載 proc 文件系統(tǒng)。這是 ps 實用程序從其獲取信息的地方。從 ps 命令的輸出中,您確實可以看到該 shell 擁有一個新的 PID 命名空間(PID 為 1),并且由于 ps 是由具有新 PID 命名空間的 shell 啟動的,因此它繼承了該 Shell 并獲得 PID 為 2。作為練習,您可以弄清楚在此容器中運行的 Shell 進程在主機上的 PID 是什么。

命名空間的類型

了解 PID 命名空間后,讓我們嘗試了解其他命名空間以及它們的含義。命名空間手冊頁[4]討論了 8 種不同的命名空間。以下是帶有簡短說明的各種類型,以及指向相關手冊頁的鏈接:

NamespaceFlagIsolatesCgroup[5]CLONE_NEWCGROUPCgroup root directoryIPC[6]CLONE_NEWIPCSystem V IPC, POSIX message queuesNetwork[7]CLONE_NEWNETNetwork devices,stacks, ports, etc.Mount[8]CLONE_NEWNSMount pointsPID[9]CLONE_NEWPIDProcess IDsTime[10]CLONE_NEWTIMEBoot and monotonic clocksUser[11]CLONE_NEWUSERUser and group IDsUTS[12]CLONE_NEWUTSHostname and NIS domain name

您可以想象使用這些命名空間為新的或現(xiàn)有的流程做什么。當它們在同一臺計算機上運行時,您幾乎可以將它們隔離在一個虛擬機上運行。您可以將多個進程隔離在各自的命名空間中,并在同一主機內核上運行。這比運行多個虛擬機要有效得多。

創(chuàng)建新的命名空間或加入現(xiàn)有的命名空間

默認情況下,當您使用 fork() 創(chuàng)建進程時,子進程將繼承調用 fork() 的進程的命名空間。如果您希望創(chuàng)建的新進程成為新命名空間的一部分,該怎么辦?但 fork() 沒有參數(shù),不允許我們在創(chuàng)建子進程之前對其進行控制。然而,您可以使用 clone() 系統(tǒng)調用來施加這種控制,從而可以非常精細地控制它創(chuàng)建的新進程。

有關 clone() 的說明

在 Linux 下,雖然有不同的系統(tǒng)調用,例如 fork(),vfork() 和 clone() 來創(chuàng)建新進程。但是在內部,內核中的 fork() 和 vfork() 只是使用不同的參數(shù)調用 clone()。圍繞內核源代碼(為了更好的說明,我進行了一些編輯)非常容易理解。在文件kernel/fork.c[13] 中,您可以看到以下內容:

  1. SYSCALL_DEFINE0(fork) 
  2.   struct kernel_clone_args args = { 
  3.     .exit_signal = SIGCHLD, 
  4.   }; 
  5.  
  6.   return _do_fork(&args); 
  7.  
  8. SYSCALL_DEFINE0(vfork) 
  9.   struct kernel_clone_args args = { 
  10.     .flags    = CLONE_VFORK | CLONE_VM, 
  11.     .exit_signal  = SIGCHLD, 
  12.   }; 
  13.  
  14.   return _do_fork(&args); 
  15.  
  16.  
  17. SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp, 
  18.      int __user *, parent_tidptr, 
  19.      int __user *, child_tidptr, 
  20.      unsigned long, tls) 
  21.   struct kernel_clone_args args = { 
  22.     .flags    = (lower_32_bits(clone_flags) & ~CSIGNAL), 
  23.     .pidfd    = parent_tidptr, 
  24.     .child_tid  = child_tidptr, 
  25.     .parent_tid  = parent_tidptr, 
  26.     .exit_signal  = (lower_32_bits(clone_flags) & CSIGNAL), 
  27.     .stack    = newsp, 
  28.     .tls    = tls, 
  29.   }; 
  30.  
  31.   if (!legacy_clone_args_valid(&args)) 
  32.     return -EINVAL; 
  33.  
  34.   return _do_fork(&args); 

如您所見,這三個系統(tǒng)調用僅使用不同的參數(shù)調用 _do_fork()。_do_fork() 實現(xiàn)創(chuàng)建新進程的邏輯。

使用 clone() 創(chuàng)建具有新命名空間的進程

Gocker 通過 Go 的 “exec” 包使用 clone() 系統(tǒng)調用執(zhí)行以下操作。在處理與運行容器有關的內容的 run.go[14] 中,您可以看到以下內容:

  1. cmd = exec.Command("/proc/self/exe", args...) 
  2. cmd.Stdin = os.Stdin 
  3. cmd.Stdout = os.Stdout 
  4. cmd.Stderr = os.Stderr 
  5. cmd.SysProcAttr = &syscall.SysProcAttr{ 
  6.   Cloneflags: syscall.CLONE_NEWPID | 
  7.     syscall.CLONE_NEWNS | 
  8.     syscall.CLONE_NEWUTS | 
  9.     syscall.CLONE_NEWIPC, 
  10. doOrDie(cmd.Run()) 

在 syscall.SysProcAttr 中,我們可以傳入 Cloneflags,然后將其傳遞給對 clone() 系統(tǒng)調用。細心的讀者會注意到,我們不在這里設置單獨的網(wǎng)絡命名空間。在 Gocker 中,我們設置了一個虛擬以太網(wǎng)接口,將其添加到新的網(wǎng)絡命名空間,并使用另一個 Linux 系統(tǒng)調用使容器加入該命名空間。我們將在后面討論。

使用 unshare() 創(chuàng)建和加入新的命名空間

如果要為現(xiàn)有進程創(chuàng)建新的命名空間,則不必使用 clone() 創(chuàng)建新的子進程,Linux 提供了 unshare()[15] 系統(tǒng)調用。

加入其他進程所屬的命名空間

為了加入文件引用的命名空間或加入其他進程所屬的命名空間,Linux 提供了setns()[16] 系統(tǒng)調用。我們將很快看到,這非常有用。

Gocker 如何創(chuàng)建容器

由于 Gocker 的主要目的是幫助理解 Linux 容器,因此保留了一些來自 Gocker 的日志消息。從這個意義上講,它比運行 Docker 更為冗長。讓我們看一下日志,以指導我們執(zhí)行程序。然后,我們可以進行深入分析,看看實際情況如何:

  1. ➜  sudo ./gocker run alpine /bin/sh 
  2. 2020/06/13 12:37:53 Cmd args: [./gocker run alpine /bin/sh] 
  3. 2020/06/13 12:37:53 New container ID: 33c20f9ee600 
  4. 2020/06/13 12:37:53 Image already exists. Not downloading. 
  5. 2020/06/13 12:37:53 Image to overlay mount: a24bb4013296 
  6. 2020/06/13 12:37:53 Cmd args: [/proc/self/exe setup-netns 33c20f9ee600] 
  7. 2020/06/13 12:37:53 Cmd args: [/proc/self/exe setup-veth 33c20f9ee600] 
  8. 2020/06/13 12:37:53 Cmd args: [/proc/self/exe child-mode --img=a24bb4013296 33c20f9ee600 /bin/sh] 
  9. / #  

在這里,我們要求 Gocker 從 Alpine Linux 鏡像運行 shell。稍后我們將了解如何管理鏡像(Image)。現(xiàn)在,請注意以 “ Cmd args:” 開頭的日志行。此行表示產(chǎn)生了一個新進程。第一行日志向我們顯示了由于運行 Gocker 命令而使 shell 程序啟動的過程。但是,到最后,我們看到了另外三個進程。最后一個是帶有參數(shù) “child-mode” 的 /bin/sh,我們在 Alpine Linux 鏡像中使用它。在此之前,我們看到其他兩個進程分別帶有參數(shù) “setup-netns” 和 “setup-veth”。這些命令設置了一個新的網(wǎng)絡命名空間,并設置了一個虛擬以太網(wǎng)設備對的容器端,使容器分別與外界通信。

由于各種原因,Go 語言不直接支持 fork() 系統(tǒng)調用。我們通過創(chuàng)建一個新進程來解決此限制,但是要在其中再次執(zhí)行當前程序。/proc/self/exe 指向當前正在運行的可執(zhí)行文件的路徑。我們根據(jù)命令行傳遞不同的命令行參數(shù)來調用適當?shù)暮瘮?shù)(當在子進程中 fork() 返回時將調用該函數(shù))。

源代碼的組織

Gocker 源代碼通過命令(如參數(shù))組織在文件中。例如,主要服務于 gocker run 命令行參數(shù)的函數(shù)位于 run.go 文件中。類似地,gocker exec 主要需要的功能在 exec.go 文件中。這并不意味著這些文件是獨立的。它們從其他文件中自由調用函數(shù)。還有一些文件可以實現(xiàn)通用功能,例如 cgroups.go 和 utils.go。

運行容器

在 main.go[17] 中,您可以看到是否運行了 Gocker 命令,我們檢查以確保 gocker0 橋接器已啟動并正在運行。否則,我們通過調用完成工作的 setupGockerBridge() 來啟動它。最后,我們調用函數(shù) initContainer(),該函數(shù)在 run.go 中實現(xiàn)。讓我們仔細看看該函數(shù):

  1. func initContainer(mem int, swap int, pids int, cpus float64,  
  2.                                 src string, args []string) { 
  3.   containerID := createContainerID() 
  4.   log.Printf("New container ID: %s\n", containerID) 
  5.   imageShaHex := downloadImageIfRequired(src) 
  6.   log.Printf("Image to overlay mount: %s\n", imageShaHex) 
  7.   createContainerDirectories(containerID) 
  8.   mountOverlayFileSystem(containerID, imageShaHex) 
  9.   if err := setupVirtualEthOnHost(containerID); err != nil { 
  10.     log.Fatalf("Unable to setup Veth0 on host: %v", err) 
  11.   } 
  12.   prepareAndExecuteContainer(mem, swap, pids, cpus, containerID,  
  13.                                 imageShaHex, args) 
  14.   log.Printf("Container done.\n"
  15.   unmountNetworkNamespace(containerID) 
  16.   unmountContainerFs(containerID) 
  17.   removeCGroups(containerID) 
  18.   os.RemoveAll(getGockerContainersPath() + "/" + containerID) 

首先,我們通過調用 createContainerID() 創(chuàng)建唯一的容器 ID。然后,我們調用 downloadImageIfRequired(),以便可以從Docker Hub 下載容器鏡像(如果本地尚不可用)。Gocker 使用 /var/run/gocker/containers 中的子目錄來掛載容器根文件系統(tǒng)。createContainerDirectories() 會解決這個問題。mountOverlayFileSystem() 知道如何處理多層 Docker 鏡像,并在 /var/run/gocker/containers/<container-id>/fs/mnt 上為可用鏡像安裝合并的文件系統(tǒng)。盡管這看起來令人生畏,但如果您閱讀源代碼,這并不難理解。覆蓋(Overlay)文件系統(tǒng)允許您創(chuàng)建一個堆疊的文件系統(tǒng),其中較低的層(在這種情況下是 Docker 根文件系統(tǒng))是只讀的,而任何更改都將保存到 “upperdir”,而無需更改較低層中的任何文件。這允許許多容器共享一個 Docker 鏡像。當我們在虛擬機上下文中說“鏡像”時,它通常是指磁盤鏡像。但是在這里,它只是一個目錄或一組目錄(奇特的名字:layers),帶有構成 Docker “鏡像”根文件系統(tǒng)的文件,可以使用 Overlay 文件系統(tǒng)掛載該文件來創(chuàng)建根文件系統(tǒng)一個新的容器。

接下來,我們創(chuàng)建一個虛擬的以太網(wǎng)配對設備,它非常類似于調用 setupVirtualEthOnHost() 的管道。它們采用名稱 veth0_ <container-id> 和 veth1_ <container-id> 的形式。我們將一對中的 veth0 部分連接到主機上的網(wǎng)橋 gocker0。稍后,我們將在容器內部使用該對的 veth1 部分。它們就像管道一樣,是從具有自己的網(wǎng)絡命名空間的容器內部進行網(wǎng)絡通信的秘鑰。隨后,我們將介紹如何在容器內設置 veth1 部件。

最后,調用 prepareAndExecuteContainer(),它實際上在容器中執(zhí)行該過程。當此函數(shù)返回時,容器已完成執(zhí)行。最后,我們進行一些清理并退出。讓我們看看 prepareAndExecuteContainer() 的作用。它實際上創(chuàng)建了我們看到的日志的 3 個進程,并使用 setup-netns,setup-veth 和 child-mode 參數(shù)運行相同的 gocker 二進制文件。

設置可在容器內工作的網(wǎng)絡

設置新的網(wǎng)絡命名空間非常容易。您只需將 CLONE_NEWNET 包含在傳遞給 clone() 系統(tǒng)調用的標志位掩碼中即可。棘手的是確保容器內部可以具有網(wǎng)絡接口,通過該接口可以與外部進行通信。在 Gocker 中,我們創(chuàng)建的第一個新命名空間是網(wǎng)絡的命名空間。當使用 setup-ns 和 setup-veth 參數(shù)調用 gocker 時會發(fā)生這種情況。首先,我們設置一個新的網(wǎng)絡命名空間。setns() 系統(tǒng)調用可以將調用進程的命名空間設置為由文件描述符所引用的命名空間,該文件描述符指向 /proc/<pid>/ns 中的文件,該文件列出了進程所屬的所有命名空間。讓我們看一下 setupNewNetworkNamespace() 函數(shù),該函數(shù)是通過使用 setup-netns 作為參數(shù)調用 gocker 而被調用的。(譯注:即上文提到的 Cmd args: [/proc/self/exe setup-netns 33c20f9ee600] )

  1. func setupNewNetworkNamespace(containerID string) { 
  2.   _ = createDirsIfDontExist([]string{getGockerNetNsPath()}) 
  3.   nsMount := getGockerNetNsPath() + "/" + containerID 
  4.   if _, err := syscall.Open(nsMount,  
  5.                 syscall.O_RDONLY|syscall.O_CREAT|syscall.O_EXCL, 
  6.                 0644); err != nil { 
  7.     log.Fatalf("Unable to open bind mount file: :%v\n", err) 
  8.   } 
  9.  
  10.   fd, err := syscall.Open("/proc/self/ns/net", syscall.O_RDONLY, 0
  11.   defer syscall.Close(fd) 
  12.   if err != nil { 
  13.     log.Fatalf("Unable to open: %v\n", err) 
  14.   } 
  15.  
  16.   if err := syscall.Unshare(syscall.CLONE_NEWNET); err != nil { 
  17.     log.Fatalf("Unshare system call failed: %v\n", err) 
  18.   } 
  19.   if err := syscall.Mount("/proc/self/ns/net", nsMount,  
  20.                                 "bind", syscall.MS_BIND, ""); err != nil { 
  21.     log.Fatalf("Mount system call failed: %v\n", err) 
  22.   } 
  23.   if err := unix.Setns(fd, syscall.CLONE_NEWNET); err != nil { 
  24.     log.Fatalf("Setns system call failed: %v\n", err) 
  25.   } 

每當 Linux 內核中的最后一個進程終止時,它都會自動刪除該命名空間。但是,有一種技術可以通過綁定來保留命名空間,即使其中沒有任何進程。在 setupNewNetworkNamespace() 函數(shù)中,我們使用此技術。我們首先打開進程的網(wǎng)絡命名空間文件,該文件位于 /proc/self/ns/net 中。然后,我們使用 CLONE_NEWNET 參數(shù)調用 unshare() 系統(tǒng)調用。這會將與其所屬的命名空間解除關聯(lián),并創(chuàng)建一個新的新網(wǎng)絡命名空間,同時將其設置為該進程的網(wǎng)絡命名空間。然后,我們將此進程的網(wǎng)絡命名空間專用文件的綁定到一個已知的文件名,即 /var/run/gocker/net-ns/<container-id>。該文件可隨時用于引用該網(wǎng)絡命名空間?,F(xiàn)在,我們可以退出此進程,但是由于此進程的新網(wǎng)絡命名空間已綁定到新文件上,因此內核將保留此命名空間。

接下來,使用 setup-veth 參數(shù)調用 gocker。這將調用函數(shù) setupContainerNetworkInterfaceStep1() 和 setupContainerNetworkInterfaceStep2()。在第一個函數(shù)中,我們查找 veth1_<container-id> 接口,并將其命名空間設置為在上一步中創(chuàng)建的新網(wǎng)絡命名空間。原本該接口將在主機上不可見。但問題是:由于它與 veth0_<container-id> 接口配對,該接口在主機上仍然可見,因此加入此網(wǎng)絡命名空間的任何進程都可以與主機進行通信。第二個函數(shù)將 IP 地址添加到網(wǎng)絡接口,并將 gocker0 網(wǎng)橋設置為其默認網(wǎng)關設備。

現(xiàn)在,主機上有一個網(wǎng)絡接口,而新的網(wǎng)絡命名空間上有一個可以相互通信的接口。而且由于該網(wǎng)絡命名空間可以由文件引用,因此我們可以隨時使用 setns() 系統(tǒng)調用打開該文件并加入該網(wǎng)絡命名空間。這正是我們要做的。

此后,prepareAndExecuteContainer() 調用將設置一個新進程,該進程使用 child-mode 參數(shù)運行 gocker。這是最后一個進程,將產(chǎn)生我們要在容器中運行的命令。讓我們看一下運行 child-mode 的進程的新命名空間。我們之前已經(jīng)看過了這段代碼:

  1. cmd = exec.Command("/proc/self/exe", args...) 
  2. cmd.Stdin = os.Stdin 
  3. cmd.Stdout = os.Stdout 
  4. cmd.Stderr = os.Stderr 
  5. cmd.SysProcAttr = &syscall.SysProcAttr{ 
  6.   Cloneflags: syscall.CLONE_NEWPID | 
  7.     syscall.CLONE_NEWNS | 
  8.     syscall.CLONE_NEWUTS | 
  9.     syscall.CLONE_NEWIPC, 
  10. doOrDie(cmd.Run()) 

在這里,我們設置新的 PID,mount,UTS 和 IPC 命名空間。請記住,我們有一個通過文件可以引用的新網(wǎng)絡命名空間。我們只需要加入它。我們將很快完成。child-mode 進程將調用函數(shù) execContainerCommand()。這里代碼:

  1. func execContainerCommand(mem int, swap int, pids int, cpus float64, 
  2.   containerID string, imageShaHex string, args []string) { 
  3.   mntPath := getContainerFSHome(containerID) + "/mnt" 
  4.   cmd := exec.Command(args[0], args[1:]...) 
  5.   cmd.Stdin = os.Stdin 
  6.   cmd.Stdout = os.Stdout 
  7.   cmd.Stderr = os.Stderr 
  8.  
  9.   imgConfig := parseContainerConfig(imageShaHex) 
  10.   doOrDieWithMsg(syscall.Sethostname([]byte(containerID)), "Unable to set hostname"
  11.   doOrDieWithMsg(joinContainerNetworkNamespace(containerID), "Unable to join container network namespace"
  12.   createCGroups(containerID, true
  13.   configureCGroups(containerID, mem, swap, pids, cpus) 
  14.   doOrDieWithMsg(copyNameserverConfig(containerID), "Unable to copy resolve.conf"
  15.   doOrDieWithMsg(syscall.Chroot(mntPath), "Unable to chroot"
  16.   doOrDieWithMsg(os.Chdir("/"), "Unable to change directory"
  17.   createDirsIfDontExist([]string{"/proc""/sys"}) 
  18.   doOrDieWithMsg(syscall.Mount("proc""/proc""proc"0""), "Unable to mount proc"
  19.   doOrDieWithMsg(syscall.Mount("tmpfs""/tmp""tmpfs"0""), "Unable to mount tmpfs"
  20.   doOrDieWithMsg(syscall.Mount("tmpfs""/dev""tmpfs"0""), "Unable to mount tmpfs on /dev"
  21.   createDirsIfDontExist([]string{"/dev/pts"}) 
  22.   doOrDieWithMsg(syscall.Mount("devpts""/dev/pts""devpts"0""), "Unable to mount devpts"
  23.   doOrDieWithMsg(syscall.Mount("sysfs""/sys""sysfs"0""), "Unable to mount sysfs"
  24.   setupLocalInterface() 
  25.   cmd.Env = imgConfig.Config.Env 
  26.   cmd.Run() 
  27.   doOrDie(syscall.Unmount("/dev/pts"0)) 
  28.   doOrDie(syscall.Unmount("/dev"0)) 
  29.   doOrDie(syscall.Unmount("/sys"0)) 
  30.   doOrDie(syscall.Unmount("/proc"0)) 
  31.   doOrDie(syscall.Unmount("/tmp"0)) 

在這里,我們將容器的主機名設置為容器 ID,加入之前創(chuàng)建的新網(wǎng)絡命名空間,創(chuàng)建允許我們控制 CPU,PID 和 RAM 使用率的 Linux 控制組,并加入這些 Cgroup,然后復制主機的 DNS 解析文件進入容器的文件系統(tǒng),對已安裝的 Overlay 文件系統(tǒng)執(zhí)行 chroot(),掛載所需的文件系統(tǒng),以使容器能夠平穩(wěn)運行,設置本地網(wǎng)絡接口,根據(jù)容器鏡像的建議設置環(huán)境變量并最終運行用戶希望我們運行的命令。現(xiàn)在,此命令將在一組新的命名空間中運行,從而使它幾乎完全與主機隔離。

限制容器資源

除了使用命名空間實現(xiàn)隔離之外,容器的另一個重要特征:限制容器可以消耗的資源量的能力。Linux 下的 Cgroup 很簡單,通過它我們能夠做到這一點。雖然命名空間是通過諸如 unshare(),setns() 和 clone() 之類的系統(tǒng)調用來實現(xiàn)的,但 Cgroup 是通過創(chuàng)建目錄并將文件寫入虛擬文件系統(tǒng)(位于 /sys/fs/cgroup 下)來管理的。在 Cgroups 虛擬文件系統(tǒng)層次結構中,每個容器創(chuàng)建了 3 個目錄:

  • /sys/fs/cgroup/pids/gocker/<container-id>
  • /sys/fs/cgroup/cpu/gocker/<container-id>
  • /sys/fs/cgroup/mem/gocker/<container-id>

對于每個創(chuàng)建的目錄,內核都會添加各種文件,從而可以自動配置該 cgroup。

這是我們配置容器的方式:

  • 當容器啟動時,我們將創(chuàng)建 3 個目錄,每個目錄用于我們關心的三個 cgroup:CPU,PID 和 Memory。
  • 然后,我們通過寫入該目錄內的文件來設置 cgroup 的限制。例如,要設置容器中允許的最大 PID 數(shù)量,我們將該最大數(shù)量寫入 /sys/fs/cgroup/pids/gocker/<cont-id>/pids.max。這將配置此 Cgroup。
  • 現(xiàn)在,我們可以通過將其 PID 添加到 /sys/fs/cgroup/pids/gocker/<cont-id>/cgroup.procs 中來添加需要由該 Cgroup 控制的進程。

這就是全部。一旦添加了要由 Cgroup 控制的進程,內核將自動將所有進程后代的 PID 添加到適當?shù)?Cgroup 的 cgroup.procs 文件中。我們在容器(添加到了上面的 3 個 Cgroups 中)中啟動一個進程,并且該進程是容器啟動其他進程的祖先進程,所以所有限制也都會被繼承。

限制 CPU

讓我們嘗試將容器可以使用的 CPU 限制為主機系統(tǒng) 1 個 CPU 內核的 20%。讓我們開始一個受此限制的容器,安裝 Python 并運行一個 while 循環(huán)。我們通過向 gocker 傳遞 --cpu = 0.2 標志來實現(xiàn):

  1. sudo ./gocker run --cpus=0.2 alpine /bin/sh 
  2. 2020/06/13 18:14:09 Cmd args: [./gocker run --cpus=0.2 alpine /bin/sh] 
  3. 2020/06/13 18:14:09 New container ID: d87d44b4d823 
  4. 2020/06/13 18:14:09 Image already exists. Not downloading. 
  5. 2020/06/13 18:14:09 Image to overlay mount: a24bb4013296 
  6. 2020/06/13 18:14:09 Cmd args: [/proc/self/exe setup-netns d87d44b4d823] 
  7. 2020/06/13 18:14:09 Cmd args: [/proc/self/exe setup-veth d87d44b4d823] 
  8. 2020/06/13 18:14:09 Cmd args: [/proc/self/exe child-mode --cpus=0.2 --img=a24bb4013296 d87d44b4d823 /bin/sh] 
  9. / # apk add python3 
  10. fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz 
  11. fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz 
  12. (1/10) Installing libbz2 (1.0.8-r1) 
  13. (2/10) Installing expat (2.2.9-r1) 
  14. (3/10) Installing libffi (3.3-r2) 
  15. (4/10) Installing gdbm (1.13-r1) 
  16. (5/10) Installing xz-libs (5.2.5-r0) 
  17. (6/10) Installing ncurses-terminfo-base (6.2_p20200523-r0) 
  18. (7/10) Installing ncurses-libs (6.2_p20200523-r0) 
  19. (8/10) Installing readline (8.0.4-r0) 
  20. (9/10) Installing sqlite-libs (3.32.1-r0) 
  21. (10/10) Installing python3 (3.8.3-r0) 
  22. Executing busybox-1.31.1-r16.trigger 
  23. OK: 53 MiB in 24 packages 
  24. / # python3 
  25. Python 3.8.3 (default, May 15 202001:53:50)  
  26. [GCC 9.3.0] on linux 
  27. Type "help""copyright""credits" or "license" for more information. 
  28. >>> while True: 
  29. ...     pass 
  30. ...  

在宿主機器運行 top,查看在容器內部運行的 python 進程占用了多少 CPU。

干貨分享:用 Go 從頭實現(xiàn)一個迷你 Docker—Gocker

Cgroup將CPU限制為20%

從另一個終端,讓我們使用 gocker exec 命令在同一容器內啟動另一個 python 進程,并在其中運行 while 循環(huán)。

  1. ➜  sudo ./gocker ps                        
  2. 2020/06/13 18:21:10 Cmd args: [./gocker ps] 
  3. CONTAINER ID  IMAGE    COMMAND 
  4. d87d44b4d823  alpine:latest  /usr/bin/python3.8 
  5. ➜  sudo ./gocker exec d87d44b4d823 /bin/sh 
  6. 2020/06/13 18:21:24 Cmd args: [./gocker exec d87d44b4d823 /bin/sh] 
  7. / # python3 
  8. Python 3.8.3 (default, May 15 202001:53:50)  
  9. [GCC 9.3.0] on linux 
  10. Type "help""copyright""credits" or "license" for more information. 
  11. >>> while True: 
  12. ...     pass 
  13. ...  

現(xiàn)在有 2 個 python 進程,在不受 Cgroup 限制的情況下,不出意外的話,將消耗 2 個完整的 CPU 內核。現(xiàn)在,讓我們看一下主機上 top 命令的輸出:

干貨分享:用 Go 從頭實現(xiàn)一個迷你 Docker—Gocker

Cgroup通過2個進程將CPU限制為20%

從主機 top 命令的輸出中可以看到,兩個 python 進程(都運行循環(huán))都限制為每個 CPU 占用 10%。容器的 20% CPU 配額由調度程序公平分配給容器中的 2 個進程。請注意,也可以指定一個以上 CPU 內核的余量。例如,如果要允許一個容器最大使用 2 個半核心,請在標志中將其指定為 --cpu = 2.5。

限制 PID

在新的 PID 命名空間中運行 Shell 程序的容器似乎消耗 7 個 PID。這意味著,如果您啟動一個 PID 上限為 7 的新容器,則將無法在 Shell 上啟動其他進程。讓我們對此進行測試。(盡管容器中只有 2 個處于運行狀態(tài)的進程,但我不確定為什么要消耗 7 個 PID。這需要進一步研究。)

  1. ➜  sudo ./gocker run --pids=7 alpine /bin/sh 
  2. [sudo] password for shuveb:  
  3. 2020/06/13 18:28:00 Cmd args: [./gocker run --pids=7 alpine /bin/sh] 
  4. 2020/06/13 18:28:00 New container ID: 920a577165ef 
  5. 2020/06/13 18:28:00 Image already exists. Not downloading. 
  6. 2020/06/13 18:28:00 Image to overlay mount: a24bb4013296 
  7. 2020/06/13 18:28:00 Cmd args: [/proc/self/exe setup-netns 920a577165ef] 
  8. 2020/06/13 18:28:00 Cmd args: [/proc/self/exe setup-veth 920a577165ef] 
  9. 2020/06/13 18:28:00 Cmd args: [/proc/self/exe child-mode --pids=7 --img=a24bb4013296 920a577165ef /bin/sh] 
  10. / # ls -l 
  11. /bin/sh: can't fork: Resource temporarily unavailable 
  12. / #  

限制 RAM

開啟一個新容器,將最大允許內存設置為 128M?,F(xiàn)在,我們將在其中安裝 python,并分配大量 RAM。這應該會觸發(fā)內核的內存不足(OOM),使其殺死我們的 python 進程。讓我們看看實際情況:

  1. ➜ sudo ./gocker run --mem=128 --swap=0 alpine /bin/sh 
  2. 2020/06/13 18:30:30 Cmd args: [./gocker run --mem=128 --swap=0 alpine /bin/sh] 
  3. 2020/06/13 18:30:30 New container ID: b22bbc6ee478 
  4. 2020/06/13 18:30:30 Image already exists. Not downloading. 
  5. 2020/06/13 18:30:30 Image to overlay mount: a24bb4013296 
  6. 2020/06/13 18:30:30 Cmd args: [/proc/self/exe setup-netns b22bbc6ee478] 
  7. 2020/06/13 18:30:30 Cmd args: [/proc/self/exe setup-veth b22bbc6ee478] 
  8. 2020/06/13 18:30:30 Cmd args: [/proc/self/exe child-mode --mem=128 --swap=0 --img=a24bb4013296 b22bbc6ee478 /bin/sh] 
  9. / # apk add python3 
  10. fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz 
  11. fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz 
  12. (1/10) Installing libbz2 (1.0.8-r1) 
  13. (2/10) Installing expat (2.2.9-r1) 
  14. (3/10) Installing libffi (3.3-r2) 
  15. (4/10) Installing gdbm (1.13-r1) 
  16. (5/10) Installing xz-libs (5.2.5-r0) 
  17. (6/10) Installing ncurses-terminfo-base (6.2_p20200523-r0) 
  18. (7/10) Installing ncurses-libs (6.2_p20200523-r0) 
  19. (8/10) Installing readline (8.0.4-r0) 
  20. (9/10) Installing sqlite-libs (3.32.1-r0) 
  21. (10/10) Installing python3 (3.8.3-r0) 
  22. Executing busybox-1.31.1-r16.trigger 
  23. OK: 53 MiB in 24 packages 
  24. / # python3 
  25. Python 3.8.3 (default, May 15 202001:53:50)  
  26. [GCC 9.3.0] on linux 
  27. Type "help""copyright""credits" or "license" for more information. 
  28. >>> a1 = bytearray(100 * 1024 * 1024
  29. Killed 
  30. / #  

需要注意的一件事是,我們使用 --swap = 0 將分配給該容器的 swap 設置為零。否則,Cgroup 雖然限制 RAM 使用,但它將允許容器使用無限的交換空間。當 swap 設置為零時,容器將被完全限制為所允許的 RAM 值。

 

 

責任編輯:張燕妮 來源: Go語言中文網(wǎng)
相關推薦

2025-05-20 09:39:57

GogRPC微服務

2022-04-01 15:18:42

Web 框架網(wǎng)絡通信

2020-08-14 10:01:25

編程神經(jīng)網(wǎng)絡C語言

2022-11-14 08:01:48

2023-05-10 08:05:41

GoWeb應用

2024-01-08 08:36:29

HTTPGo代理服務器

2023-02-26 01:37:57

goORM代碼

2022-10-08 00:00:00

AdminUser數(shù)據(jù)庫鑒權

2023-02-01 08:04:07

測試flask網(wǎng)頁

2022-03-06 19:57:50

狀態(tài)機easyfsm項目

2019-03-29 15:34:39

Go框架Web

2024-01-02 13:58:04

GoREST API語言

2019-07-05 08:39:39

GoSQL解析器

2024-06-13 08:36:11

2021-04-25 08:58:00

Go拍照云盤

2021-04-15 08:55:51

Go struc代碼

2014-04-14 15:54:00

print()Web服務器

2014-07-08 09:27:24

SQLSERVER腳本

2021-06-25 10:38:05

JavaScript編譯器前端開發(fā)

2021-07-02 07:18:19

Goresults通道類型
點贊
收藏

51CTO技術棧公眾號

国产精品影视网| 私拍精品福利视频在线一区| 亚洲人成亚洲人成在线观看图片 | 日韩精品久久一区| 中文字幕在线看人| 视频在线不卡免费观看| 精品美女一区二区| 国产免费视频传媒| 中文字幕在线观看播放| 不卡一区二区中文字幕| 国产日韩欧美视频在线| 日本在线观看中文字幕| 欧美在线电影| 亚洲精品国产成人| 欧美一级视频在线| 成人欧美一区二区三区的电影| 国产精品亲子乱子伦xxxx裸| 黑人中文字幕一区二区三区| 97成人在线观看| 午夜在线一区二区| 欧美精品制服第一页| 人人妻人人澡人人爽人人精品| 亚洲视频资源| 色婷婷综合久久久久中文一区二区| 桥本有菜av在线| 黄色在线视频观看网站| 国产高清亚洲一区| 成人国产精品日本在线| 懂色av中文字幕| 国产偷自视频区视频一区二区| 欧美日韩xxx| 可以免费看av的网址| 亚洲va久久| 亚洲福利视频在线| 中文字幕无码毛片免费看| 久久精品 人人爱| 在线一区二区视频| 欧美日韩国产精品激情在线播放| 99热国产在线中文| 亚洲色图20p| 亚洲一区二区在线看| 邻家有女韩剧在线观看国语| 91蜜桃网址入口| 国产一区二区三区高清| 性中国xxx极品hd| 国产一区二区导航在线播放| 国产在线a不卡| 在线观看视频二区| 全部av―极品视觉盛宴亚洲| 日本一区二区三区四区视频| 1级黄色大片儿| 国产毛片久久| 欧美一级视频在线观看| 少妇一级淫片免费放中国 | 欧美激情精品久久久久| 亚洲色偷偷综合亚洲av伊人| 91视频综合| 日韩视频在线一区| 黄色片网站免费| 色喇叭免费久久综合网| 在线观看精品国产视频| 女人黄色一级片| 久久密一区二区三区| 日韩中文娱乐网| 日本一级片免费| 欧美一区二区三区另类| 欧美大荫蒂xxx| 日本亚洲色大成网站www久久| 亚洲伦理精品| 国产精品久久久久久久一区探花 | 日韩欧美在线观看一区二区三区| 手机av在线网站| 国产精品极品在线观看| 日韩av影院在线观看| 日韩丰满少妇无码内射| 久久性感美女视频| 欧美日韩国产91| 中文字幕一区二区三区精品| 视频一区在线视频| 成人国产精品久久久久久亚洲| 国产草草影院ccyycom| 成人avav影音| 日韩一区不卡| 国产区在线观看| 欧美日韩国产中文精品字幕自在自线 | 牛夜精品久久久久久久99黑人| 久久99精品视频一区97| 免费在线观看黄网站| 日本欧美久久久久免费播放网| 91夜夜未满十八勿入爽爽影院 | 日韩 欧美 视频| 黑人巨大精品欧美一区二区桃花岛| 欧美中文一区二区三区| 韩国三级hd中文字幕有哪些| 亚洲人成网77777色在线播放 | 欧美精品久久久久久久久46p| 国内精品嫩模av私拍在线观看| 日本a级片电影一区二区| 91中文字幕在线视频| 92精品国产成人观看免费 | 一级在线观看视频| 狠狠88综合久久久久综合网| 国产不卡在线观看| 亚洲精品久久久久久无码色欲四季| 久久综合狠狠综合| 日本三级中文字幕在线观看| 日韩影片中文字幕| 欧美电影精品一区二区| 男人的天堂av网| 99精品视频免费观看视频| 成人性生交大片免费看视频直播| 无码精品黑人一区二区三区| 亚洲品质自拍视频网站| 亚洲乱码国产一区三区| 99精品中文字幕在线不卡 | 变态调教一区二区三区| 欧美日韩aaaaaa| 成人午夜剧场视频网站| 亚洲精品麻豆| 5566av亚洲| 在线观看国产原创自拍视频| 欧美日韩在线影院| 日本50路肥熟bbw| 亚洲天天综合| 国产日产亚洲精品| 国产综合在线观看| 欧美日韩在线影院| 欲求不满的岳中文字幕| 黄色成人精品网站| 亚洲精品欧美极品| 免费观看在线午夜影视| 欧美视频日韩视频在线观看| 国产精品无码久久久久久| 怡红院精品视频在线观看极品| 91久热免费在线视频| 日本视频不卡| 欧美三级日韩三级| 亚洲天堂av中文字幕| 久久精品道一区二区三区| 久久riav二区三区| ririsao久久精品一区| 精品乱码亚洲一区二区不卡| 黄色一级免费视频| 懂色中文一区二区在线播放| 女女百合国产免费网站| 8848成人影院| 久久久久久久爱| 丰满人妻一区二区三区免费视频| 亚洲欧美电影院| 亚洲欧美日韩网站| 欧美日韩理论| 国产精品区免费视频| 国产丝袜视频在线播放| 亚洲电影第1页| 国产黄色片视频| 99久久精品免费精品国产| av免费观看大全| 久久成人av| 国产精品久久久久久av福利软件| eeuss影院www在线观看| 欧美日韩国产一二三| 国产精品三区在线观看| 国产精品18久久久| 少妇高潮喷水在线观看| 美女久久久久| 国产女人精品视频| av网站在线免费| 亚洲激情小视频| 日本久久综合网| 国产精品不卡视频| 国产乱淫av片| 美女久久一区| 中国一区二区三区| 综合中文字幕| 国产成人精品免高潮费视频| 男人影院在线观看| 亚洲成人黄色网址| 中文字幕第315页| 亚洲女人****多毛耸耸8| 久久国产劲爆∧v内射| 老妇喷水一区二区三区| 亚洲在线不卡| 欧美男男freegayvideosroom| 国产成人精品久久二区二区91| 精品欧美色视频网站在线观看| 亚洲国产成人精品久久久国产成人一区 | 久久99精品国产99久久| 欧美97人人模人人爽人人喊视频| 欧美多人爱爱视频网站| 日本a一级在线免费播放| 欧美狂野另类xxxxoooo| 日本一二三区不卡| 国产精品久久久久久久久动漫 | 国产精品久久久久久久久久白浆| 日韩av免费在线看| 羞羞网站在线看| 亚洲视频在线观看| 午夜免费福利视频| 91成人国产精品| 国产在线观看免费av| 中文字幕av一区 二区| 波多野结衣办公室双飞| 久久国内精品视频| 激情综合在线观看| 欧美日韩免费| 婷婷久久青草热一区二区| 国产精品调教视频| 91精品免费久久久久久久久| 午夜激情电影在线播放| 另类少妇人与禽zozz0性伦| 玖玖综合伊人| 欧美精品一区二区三区在线播放| 91精东传媒理伦片在线观看| 欧美日韩国产色视频| 久久久www成人免费毛片| 欧美国产一区在线| 欧美丰满少妇人妻精品| 国产精品自在在线| 亚洲人视频在线| 免费在线欧美黄色| 免费看黄在线看| 欧美另类视频| 不卡中文字幕在线| 欧美色蜜桃97| 欧美在线视频二区| 色婷婷综合久久久久久| 亚洲国产欧美一区| 久久久久xxxx| 日日夜夜免费精品| 国产精品一区二区免费在线观看| 一本到12不卡视频在线dvd| 日本成人黄色| 免费观看不卡av| 国产一区二区三区免费不卡| 中文字幕一区二区三区四区久久| 91久久精品在线| 伊人久久一区| 91精品久久久久久久久青青| 日韩黄色碟片| 成人在线一区二区| www 久久久| 成人欧美一区二区三区黑人孕妇| 国产精品xxx| 国产免费一区二区三区香蕉精| 69堂精品视频在线播放| 国产精品第3页| 日本一区二区电影| 国产精品美女久久| 日韩欧美激情| 成人免费在线视频网站| 国产免费av国片精品草莓男男| 成人美女av在线直播| 国产免费区一区二区三视频免费| 91亚洲国产成人精品性色| 国产精品欧美一区二区三区不卡| 亚洲iv一区二区三区| 哺乳挤奶一区二区三区免费看| 动漫精品视频| 免费看av成人| 亚洲欧美日韩精品在线| 亚洲久久久久| 国产xxxx振车| 一本色道久久精品| 国产福利一区视频| 精品伊人久久久久7777人| 能看毛片的网站| av电影在线观看完整版一区二区| 无码人妻精品一区二区三区温州 | 国产剧情在线| 欧美激情网友自拍| www.com.cn成人| 国产日韩欧美在线| 99亚洲乱人伦aⅴ精品| 久久青青草原| 久久免费大视频| 99在线免费视频观看| 久久久噜噜噜| 色姑娘综合天天| 91女厕偷拍女厕偷拍高清| 奇米网一区二区| 一区二区三区加勒比av| 日日夜夜综合网| 欧美日韩国产经典色站一区二区三区 | 国产欧美一级| 黄大色黄女片18第一次| 成人精品视频网站| 日本一二三不卡视频| 亚洲精品成人少妇| 日韩 国产 欧美| 欧美一区二区三区视频在线观看| 五月婷婷激情在线| 日韩在线视频免费观看| 成人黄色动漫| 成人h片在线播放免费网站| 开心激情综合| 日日噜噜噜夜夜爽爽| 国产精品综合色区在线观看| 亚洲热在线视频| 久久久青草青青国产亚洲免观| 三级黄色录像视频| 欧美视频一二三| 精品久久人妻av中文字幕| 亚洲系列中文字幕| 欧美高清另类hdvideosexjaⅴ| 国产精品久久久久久一区二区| 99久热这里只有精品视频免费观看| 五月天久久狠狠| 亚洲一区二区三区四区五区午夜 | 18岁网站在线观看| 国产一区欧美二区| 亚洲av成人无码久久精品| 午夜精品视频一区| 亚洲成人久久精品| 久久精品视频导航| 国产精品.xx视频.xxtv| 欧美久久综合性欧美| 亚洲高清网站| 午夜性福利视频| 亚洲视频一二三| 中文字幕av影视| 一本色道久久88综合亚洲精品ⅰ| 国产精品原创| 国产一区二区三区高清视频| 黄色免费成人| 无人码人妻一区二区三区免费| 一区在线中文字幕| 一本一道人人妻人人妻αv | 国产黄色在线网站| 国产精品一香蕉国产线看观看| 精品在线播放| 国产一区二区三区精彩视频| av在线不卡网| 亚洲精品视频在线观看免费视频| 精品欧美一区二区在线观看| 午夜影院免费在线| 5g国产欧美日韩视频| 欧美在线高清| 韩国三级hd中文字幕有哪些| 亚洲精品国产视频| av手机免费看| 欧美华人在线视频| 国产精品玖玖玖在线资源| 精品嫩模一区二区三区| 国产精品一二三区| 欧美成人片在线观看| 欧美电影免费观看完整版 | 国产精品99一区| 欧美日韩激情| 精品综合久久久久| 自拍偷拍亚洲综合| 亚洲经典一区二区| 国内精品模特av私拍在线观看| 精品福利一区| 成年人黄色片视频| 国产精品三级电影| 99国产精品久久久久99打野战| 久久夜色撩人精品| 伊色综合久久之综合久久| 欧美国产视频一区| 成人免费高清在线| 亚洲欧美综合自拍| 尤物99国产成人精品视频| 日本一区二区中文字幕| 视色,视色影院,视色影库,视色网| 国产宾馆实践打屁股91| 日本高清www免费视频| 婷婷激情图片久久| 欧洲日韩成人av| 国内黄色精品| 天天色天天综合网| 亚洲国产综合视频在线观看| 日本a一级在线免费播放| 国产精品久久电影观看| 亚洲自拍偷拍网| 污片免费在线观看| 欧美色综合网站| 日韩激情美女| 日本中文不卡| 国产裸体歌舞团一区二区| 日韩欧美亚洲一区二区三区| 亚洲色图色老头| 国产一区二区三区免费观看在线| 日本精品久久久久久久久久| 久久久久久久久97黄色工厂| 国产精品一级视频| 午夜精品久久久久久久白皮肤| 黄色不卡一区| 97超碰人人看| 色婷婷av一区二区三区之一色屋| 黄色成人在线观看| 看高清中日韩色视频| 精品在线免费观看| 日本高清不卡码| 久久国产精品视频| 久久av影视| 日本成人在线免费| 欧美性猛交一区二区三区精品 | 亚洲欧美自拍偷拍色图| 香蕉国产在线视频| 91久久精品久久国产性色也91|