七天用 Go 寫個 docker(第一天)

pibigstar發表於2020-03-18

很多人剛接觸docker的時候就會感覺非常神奇,感覺這個技術非常新穎,其實並不然,docker使用到的技術都是之前已經存在過的,只不過舊酒換了新瓶罷了。簡單來說docker本質其實是一個特殊的程式,這個程式特殊在它被NamespaceCgroup 技術做了裝飾,Namespace將該程式與Linux系統進行隔離開來,讓該程式處於一個虛擬的沙盒中,而Cgroup則對該程式做了一系列的資源限制,兩者配合模擬出來一個沙盒的環境。

Linux對執行緒提供了六種隔離機制,分別為:uts pid user mount network ipc ,它們的作用如下:

  • uts: 用來隔離主機名
  • pid:用來隔離程式PID號的
  • user: 用來隔離使用者的
  • mount:用來隔離各個程式看到的掛載點檢視
  • network: 用來隔離網路
  • ipc:用來隔離System V IPC 和 POSIX message queues

因為我們是在Windows裡面寫程式碼,然後將程式碼編譯好,放到Linux中執行,所以這裡我們要更改下我們goland的環境,因為在不同的環境中,go匯入的檔案也是不同,如果我們的環境使用的Windows,那麼使用 os/exec 包時,匯入的將是 exec_windows.go,而如果我們的環境是Linux,那麼將會匯入exec_linux.go檔案,因為只有Linux才會給建立程式時提供這個隔離引數,所以我們需要把環境改成Linux。

在這裡插入圖片描述

4.1 隔離uts

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)
    }
}

我們編譯一下,放到Linux中測試一下,windows中編譯需要先把GOOS改成Linux,然後再執行go build,編譯指令碼如下

SET CGO_ENABLED=0
SET GOOS=linux
SET GOARCH=amd64
go build main.go


在這裡插入圖片描述

main 放到Linux環境中執行,這裡我用的是 Centos

4.2 測試能否隔離主機名

  1. 給main檔案新增可執行許可權
    chmod +x main
  2. 檢視當前主機名
  3. 執行 main 檔案
    ./main
  4. 修改主機名
    hostname -b 新主機名

    再次檢視主機名,我們看到已經將主機名修改為 test
  5. 退出 shell,再次檢視主機名

    這時我們發現,外部的主機名,並沒有被改變,說明該程式成功的將自己的hostname與外部的hostname進行隔離了。這也證明我們使用 uts namespace成功了。

4.2 其他的隔離

我們想對此程式進行那種隔離,只需要在Cloneflags中新增引數即可

package main

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

func main() {
    cmd := exec.Command("sh")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        // 隔離 uts,ipc,pid,mount,user,network
        Cloneflags: syscall.CLONE_NEWUTS |
            syscall.CLONE_NEWIPC |
            syscall.CLONE_NEWPID |
            syscall.CLONE_NEWNS |
            syscall.CLONE_NEWUSER |
            syscall.CLONE_NEWNET,
        // 設定容器的UID和GID
        UidMappings: []syscall.SysProcIDMap{
            {
                // 容器的UID
                ContainerID: 1,
                // 宿主機的UID
                HostID: 0,
                Size:   1,
            },
        },
        GidMappings: []syscall.SysProcIDMap{
            {
                // 容器的GID
                ContainerID: 1,
                // 宿主機的GID
                HostID: 0,
                Size:   1,
            },
        },
    }
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

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

文章會首發於我微信公眾號上,掃碼關注,及時獲取最新內容

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章