Docker原理剖析--基本介紹

何爲Docker?

  • Docker 是一個開源工具,它能夠將你的應用打包成一個標準格式的鏡像(Image),並以容器的方式運行
  • Docker 將程序運行的所須要的一切:Code,ENV,System等等包裝在一個完整的文件系統中
  • 全部容器會共享一個Kernel
|----------------|
|  CODE - Image  |
|----------------|
|  JRE - Image   |
|----------------|
|      KERNEL    |
|----------------|
複製代碼

Linux Namespace 介紹

Linux NamespaceKernel的一個功能,它提供了內核級別隔離系統資源的方法,經過將系統的全局資源放到不一樣的Namespace來實現隔離資源的目的.目前用到Namespace有:node

名稱 宏定義 隔離內容
IPC CLONE_NEWIPC 實現容器與宿主機、容器與容器之間的IPC隔離。IPC資源包括信號量、消息隊列和共享內存
Network CLONE_NEWNET 提供了關於網絡資源的隔離,包括網絡設備、IPv4和IPv6協議棧、IP路由表、防火牆,套接字等
Mount CLONE_NEWNS 實現隔離文件系統掛載點,使容器內有獨立的掛載文件系統
PID CLONE_NEWPID 實現容器內有獨立的進程樹 (也就意味着每一個容器都有本身的PID爲1的進程)
User CLONE_NEWUSER 實現用戶能夠將不一樣的主機用戶映射到容器,好比user用戶映射到容器內的root用戶上
UTS CLONE_NEWUTS 實現容器能夠擁有獨立的主機名和域名,在網絡上能夠視爲獨立的節點
Cgroup CLONE_NEWCGROUP 實現資源的限制(CPU,Memory等等)

NamespaceAPI主要用到下面3個系統調用:docker

  • CLONE 建立新進程,而且系統調用參數會判斷哪一個類型的Namespace被建立(如上表格中的CLONE_NEWUSER),而且子進程也會包含到這些Namespace
  • UNSHARE 將進程移出某個Namespace
  • SETNS 將進程切換/加入到某個Namespace

Linux Namespace


UTS Namespace

UTS Namespace 主要用來隔離nodenamedomainname兩個系統標識,每一個Namespace容許有本身的hostnameshell

下面使用將使用Go來作一個UTS Namespace的例子.bash

// UTS/clone.go

package main

import (
	"log"
	"os"
	"os/exec"
	"syscall"
)

func main() {
	cmd := exec.Command("sh")
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags:syscall.CLONE_NEWUTS,
	}

	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run();err != nil{
		log.Fatal(err)
	}
}
複製代碼

解釋下代碼, exec.Command("sh")是來指定被fork出來的新進程內的初始命令,默認用sh來執行(固然能夠換成bashzsh之類的)網絡

下面就是設置系統調用參數,使用CLONE_NEWUTS這個標識符來建立一個UTS Namespacedom

syscall庫封裝好對clone()函數的調用,這段代碼被執行後就會進入到一個sh運行環境中函數

執行go run clone.go的命令後,在這個交互式環境裏面,使用pstree -pl查看下系統中進程之間的關係,以下:工具

init(1)───init(537)───init(538)───sh(539)───sh(540)───sh(545)───node(547)───node(597)──zsh(612)───go(2005)───clone(2098)───sh(2103)───pstree(2104)
複製代碼

而後輸出一下當前的PIDoop

# echo $$
2103
複製代碼

驗證父進程和子進程是否不在同一個UTS Namespaceui

# readlink /proc/2098/ns/uts
uts:[4026532185]

# readlink /proc/2103/ns/uts
uts:[4026532195]
複製代碼

能夠看到它們確實不在同一個UTS Namespace中,因爲咱們對進程的CLONE進了一個新的UTS Namespace內,因此這個環境下修改hostname/NIS domain name 對宿主主機沒有任何影響,下面進行下實驗

## 在clone出來的sh執行
# hostname -b air
# hostname
air
複製代碼

另外在宿主機另啓動一個shell

root@DESKTOP-UMENNVI:~# hostname
DESKTOP-UMENNVI
複製代碼

能夠看到,外部的hostname並無被內部的修改所影響,由此能夠了解到UTS Namespeace的做用


IPC Namespace

IPC Namespace用來隔離System V IPCPOSIX message queues.每個IPC Namespace都有本身的System V IPCPOSIX message queue

建立IPC Namespace跟以前的方法相似,只須要把CLONE_NEWUTS替換成CLONE_NEWIPC

// IPC/clone.go
package main

import (
	"log"
	"os"
	"os/exec"
	"syscall"
)

func main() {
	cmd := exec.Command("sh")
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags:syscall.CLONE_NEWIPC,
	}

	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		log.Fatal(err)
	}
}
複製代碼
下面來演示如下隔離效果
  1. 在宿主機上打開一個shell
root@DESKTOP-UMENNVI:~# ipcs -q

--------- 消息隊列 -----------
鍵        msqid      擁有者  權限     已用字節數 消息
複製代碼
  1. 在宿主機建立一個message queue
root@DESKTOP-UMENNVI:~# ipcmk -Q
消息隊列 id:0

# 查看消息隊列
root@DESKTOP-UMENNVI:~# ipcs -q

--------- 消息隊列 -----------
鍵        msqid      擁有者  權限     已用字節數 消息      
0x9dce743a 0          root       644        0            0 
複製代碼
  1. 這裏咱們已經在宿主機上建立好一個queue了,下面將使用另外一個shell去運行IPC/clone.go新建IPC Namespace
root@DESKTOP-UMENNVI:# go run clone.go 
# ipcs -q 

--------- 消息隊列 -----------
鍵        msqid      擁有者  權限     已用字節數 消息
複製代碼

能夠發現,在新建立的Namespace裏,看不到宿主機上已經建立的message queue,說明IPC Namespace建立成功,已經被隔離了


PID Namespace

PID Namespace是用來隔離進程ID(PID).一樣在不一樣的PID Namespace裏能夠擁有不一樣的進程樹.在docker container裏面,使用ps -ef就會發現前臺運行的進程PID1,那是由於每一個容器都建立了獨自的PID Namespace

基於上面的基礎,把CLONE_NEWIPC替換成CLONE_NEWPID

// PID/clone.go
package main

import (
	"log"
	"os"
	"os/exec"
	"syscall"
)

func main() {
	cmd := exec.Command("sh")
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags:syscall.CLONE_NEWPID,
	}


	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run();err != nil{
		log.Fatal(err)
	}
}
複製代碼

咱們須要分別在宿主機和go run PID/clone.go內運行shell

  1. 首先運行go run PID/clone.go查看shell的PID
root@DESKTOP-UMENNVI:# go run PID/clone.go 
# echo $$
1
複製代碼
  1. 在宿主機上查看進程樹,查看一下進程的真實PID
init(1)─┬─init(11)─┬─at-spi-bus-laun(294)─┬─dbus-daemon(307)
        ├─init(537)───init(538)───sh(539)───sh(540)───sh(545)───node(547)─┬─node(597)─┬─node(722)─┬─{node}(723)
        │                                                                 │           │           ├─{node}(724)
        │                                                                 │           │           ├─{node}(725)
        │                                                                 │           │           ├─{node}(726)
        │                                                                 │           │           ├─{node}(727)
        │                                                                 │           │           └─{node}(728)
        │                                                                 │           ├─zsh(610)───bash(3571)───pstree(4090)
        │                                                                 │           ├─zsh(612)───bash(3601)───go(3965)─┬─clone(4074)─┬─sh(4079)
        │                                                                 │           │                                  │             ├─{clone}(4075)
        │                                                                 │           │                                  │             ├─{clone}(4076)
        │                                                                 │           │                                  │             ├─{clone}(4077)
        │                                                                 │           │                                  │             └─{clone}(4078)
        │                                                                 │           │                                  ├─{go}(3966)
複製代碼

能夠看到 go run PID/clone.go的真實PID應該是4074,也就是說這個4074被映射到PID Namespace裏後爲1. 固然這裏還不能用ps/top來查看,由於會依賴/proc文件系統,還須要掛載/proc文件系統後才行.


Mount Namespace

Mount Namespace 用來隔離各個容器的掛載節點. 在不一樣的Namespace的容器中看到的文件系統層次是不同的. 在Mount Namespace中調用mount()umount()僅僅只會影響當前Namespace內的文件系統,對全局文件系統沒有影響

Mount NamespaceUnix & Linux第一個實現的Namespace類型,因此它的系統調用參數是NEWNS(New Namespace的縮寫)

基於上面的基礎,添加syscall.CLONE_NEWNS

// MOUNT/clone.go
package main

import (
	"log"
	"os"
	"os/exec"
	"syscall"
)

func main() {
	cmd := exec.Command("sh")
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags:syscall.CLONE_NEWNS | syscall.CLONE_NEWPID,
	}


	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run();err != nil{
		log.Fatal(err)
	}
}
複製代碼
  1. 運行代碼go run MOUNT/clone.go 後,查看/proc的文件內容

proc 是一個(僞)文件系統,提供訪問系統內核數據的操做提供接口

root@DESKTOP-UMENNVI:# go run MOUNT/clone.go 

# ls /proc
1    307   4553  545  acpi       crypto       interrupts  kmsg         modules       self         tty
11   309   4651  547  buddyinfo  devices      iomem       kpagecgroup  mounts        softirqs     uptime
192  339   4656  597  bus        diskstats    ioports     kpagecount   mtrr          stat         version
248  3571  4657  610  cgroups    dma          irq         kpageflags   net           swaps        vmallocinfo
272  4079  537   679  cmdline    driver       kallsyms    loadavg      pagetypeinfo  sys          vmstat
273  420   538   68   config.gz  execdomains  kcore       locks        partitions    sysvipc      zoneinfo
294  4315  539   69   consoles   filesystems  keys        meminfo      sched_debug   thread-self
299  4374  540   722  cpuinfo    fs           key-users   misc         schedstat     timer_list
複製代碼

能夠看到這裏的/proc仍是宿主機的,下面將/proc mount到新建的Namespace中來

# mount -t proc proc /proc
# ls /proc
1          cmdline    diskstats    interrupts  keys         loadavg  mtrr          self      thread-self  vmstat
5          config.gz  dma          iomem       key-users    locks    net           softirqs  timer_list   zoneinfo
acpi       consoles   driver       ioports     kmsg         meminfo  pagetypeinfo  stat      tty
buddyinfo  cpuinfo    execdomains  irq         kpagecgroup  misc     partitions    swaps     uptime
bus        crypto     filesystems  kallsyms    kpagecount   modules  sched_debug   sys       version
cgroups    devices    fs           kcore       kpageflags   mounts   schedstat     sysvipc   vmallocinfo
複製代碼

能夠看到少了不少文件,如今用top/ps來查看系統進程

# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 14:47 pts/5    00:00:00 sh
root         6     1  0 14:58 pts/5    00:00:00 ps -ef
複製代碼

在當前的Namespace中,sh進程的PID1的進程,說明Mount && PID Namespace和宿主機是隔離的,mount操做沒有影響到宿主機 Docker Volume 也是利用了這個特性(還有USER Namespace)


User Namespace

User Namespace 主要用來隔離用戶&用戶組ID. 一個進程的User IDGroup IDUser Namespace內外能夠是不一樣的,比較常見的是在宿主機上以一個非root用戶建立一個User Namespace,而後在User Namespace裏面卻映射成root用戶 從Linux Kernel 3.8開始,非root進程也能夠建立User Namespace,而且此用戶在建立的Namespace裏面能夠被映射成root而且擁有root權限

具體代碼以下:

// USER/clone.go
package main

import (
	"log"
	"os"
	"os/exec"
	"syscall"
)

func main() {
	cmd := exec.Command("sh")
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWUSER,
	}

	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run();err != nil{
		log.Fatal(err)
	}
}
複製代碼
  1. root用戶在宿主機上看一下當前的用戶和用戶組
root@DESKTOP-UMENNVI:# id
uid=0(root) gid=0(root) 組=0(root)
複製代碼

能夠看到咱們是root用戶,接下來運行程序

root@DESKTOP-UMENNVI:# go run USER/clone.go 
$ id
uid=65534(nobody) gid=65534(nogroup) 組=65534(nogroup)
複製代碼

能夠看到它們的UID是不一樣的說明User Namespace建立&&隔離完成了


Network Namespace

Network Namespace 是用來隔離網絡設備,IPV4/IPV6等網絡棧的Namespace Network Namespace可讓每一個容器擁有獨立的虛擬網絡設備,而且容器內的應用能夠綁定到容器內的端口,每一個Namespace內的端口都不會互相沖突 在宿主機上搭建網橋後,就能很方便地實現容器之間的通訊

在上面的基礎上改爲syscall.CLONE_NEWNET

package main

import (
	"log"
	"os"
	"os/exec"
	"syscall"
)

func main() {
	cmd := exec.Command("sh")
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags:syscall.CLONE_NEWNET,
	}


	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run();err != nil{
		log.Fatal(err)
	}
}
複製代碼
  1. 首先在宿主機上查看一下本身的網絡設備
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.25.50.16  netmask 255.255.240.0  broadcast 172.25.63.255
        inet6 fe80::215:5dff:fe50:5608  prefixlen 64  scopeid 0x20<link>
        ether 00:15:5d:50:56:08  txqueuelen 1000  (以太網)
        RX packets 766496  bytes 148694705 (148.6 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 688919  bytes 3273950212 (3.2 GB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (本地環回)
        RX packets 2  bytes 100 (100.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2  bytes 100 (100.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
複製代碼

能夠宿主機上有eth0,lo等網絡設備

  1. 運行程序查看網絡設備 go run NET/clone.go
root@DESKTOP-UMENNVI:# go run NET/clone.go 
# ifconfig
# 
複製代碼

能夠看到網絡設備什麼都沒有,由於Network Namespace與宿主機之間的網絡是處於隔離狀態了

相關文章
相關標籤/搜索