使用Go語言編寫一個(gè)極簡版的容器Container
前置知識(shí)儲(chǔ)備:
- Linux 基礎(chǔ)知識(shí)
Docker 是基于 Linux 容器技術(shù)構(gòu)建的,因此了解 Linux 操作系統(tǒng)的基本原理、命令和文件系統(tǒng)等知識(shí)對(duì)于理解本文乃至于Docker 源碼非常重要。
- 容器技術(shù)基礎(chǔ)
了解容器技術(shù)的基本概念、原理和實(shí)現(xiàn)方式對(duì)于理解 Docker 源碼非常有幫助??梢詤⒖?Docker 官方文檔[1]中的容器概述部分,以及相關(guān)的教程和文章。
- Go 語言基礎(chǔ)
Docker 的源碼主要是用 Go 語言編寫的,具體可以參考Go 語言官方文檔[2]。

[圖片來源:Docker架構(gòu)概覽[3]]
什么是容器化
容器化是作為一種虛擬化技術(shù),允許應(yīng)用程序和其依賴的資源(如庫、環(huán)境變量等)被封裝在一個(gè)獨(dú)立的運(yùn)行環(huán)境中,稱為容器。其核心概念主要包括:
- 隔離性
容器使用操作系統(tǒng)級(jí)別的虛擬化技術(shù),如Linux的命名空間和控制組(cgroup),實(shí)現(xiàn)隔離。每個(gè)容器都有自己的進(jìn)程空間、文件系統(tǒng)、網(wǎng)絡(luò)和用戶空間,使得容器之間相互隔離,不會(huì)相互干擾。
- 輕量性
相比傳統(tǒng)的虛擬機(jī)(VM),容器更加輕量級(jí)。容器共享主機(jī)操作系統(tǒng)的內(nèi)核,因此啟動(dòng)更快、占用更少的資源。
- 可移植性
容器可以在不同的環(huán)境中運(yùn)行,包括開發(fā)、測試和生產(chǎn)環(huán)境。容器以相同的方式運(yùn)行,不受底層基礎(chǔ)設(shè)施的影響,提供了更好的可移植性。
- 可擴(kuò)展性
容器可以根據(jù)需求進(jìn)行擴(kuò)展和縮減。容器編排工具(如Kubernetes)可以自動(dòng)管理容器的部署、伸縮和負(fù)載均衡,提供彈性和可擴(kuò)展性。
"如果創(chuàng)建一個(gè)容器就像系統(tǒng)調(diào)用 create_container 一樣簡單就好了"[4]
Guideline
這里我們粗略的估算一下可能涉及到的步驟會(huì)有:導(dǎo)入必要的包、main函數(shù)、子進(jìn)程及其命名空間、掛載文件系統(tǒng)、運(yùn)行子進(jìn)程命令等。
我們知道真正的容器實(shí)現(xiàn)要復(fù)雜得多。它可能會(huì)涉及更多的命名空間設(shè)置、資源限制、文件系統(tǒng)掛載、網(wǎng)絡(luò)配置等方面的工作。
但是本文,“刪繁就簡”,主要是為了了解容器的基本原理。
按照這種實(shí)現(xiàn)的思路,我們開始一步步用代碼實(shí)現(xiàn):
package?main
import?(
?"fmt"
?"os"
?"os/exec"
?"syscall"
)
func?main()?{
?//?根據(jù)命令行參數(shù)選擇執(zhí)行不同的操作
?switch?os.Args[1]?{
?case?"run":
??parent()?//?執(zhí)行parent函數(shù)
?case?"child":
??child()?//?執(zhí)行child函數(shù)
?default:
??panic("wat?should?I?do")?//?拋出異常,程序無法繼續(xù)執(zhí)行
?}
}
func?parent()?{
?cmd?:=?exec.Command("/proc/self/exe",?append([]string{"child"},?os.Args[2:]...)...)
?cmd.Stdin?=?os.Stdin
?cmd.Stdout?=?os.Stdout
?cmd.Stderr?=?os.Stderr
?//?運(yùn)行命令并檢查錯(cuò)誤
?if?err?:=?cmd.Run();?err?!=?nil?{
??fmt.Println("ERROR",?err)
??os.Exit(1)
?}
}
func?child()?{
?cmd?:=?exec.Command(os.Args[2],?os.Args[3:]...)
?cmd.Stdin?=?os.Stdin
?cmd.Stdout?=?os.Stdout
?cmd.Stderr?=?os.Stderr
?//?運(yùn)行命令并檢查錯(cuò)誤
?if?err?:=?cmd.Run();?err?!=?nil?{
??fmt.Println("ERROR",?err)
??os.Exit(1)
?}
}
func?must(err?error)?{
?//?如果錯(cuò)誤不為空,拋出panic異常
?if?err?!=?nil?{
??panic(err)
?}
}我們從 main.go 開始,讀取第一個(gè)參數(shù)。如果是 "run",我們就運(yùn)行Parent函數(shù),如果是 "child",我們就運(yùn)行子方法。父方法運(yùn)行"/proc/self/exe",這是一個(gè)包含當(dāng)前可執(zhí)行文件內(nèi)存映像的特殊文件。
換句話說,我們重新運(yùn)行自己,但將 child 作為第一個(gè)參數(shù)傳遞。
我們可以借此執(zhí)行另外一個(gè)執(zhí)行用戶請(qǐng)求的程序(在 os.Args[2:] 中提供)。有了這個(gè)簡單的腳手架,我們就可以創(chuàng)建一個(gè)容器了。
命名空間
在 Linux 中,命名空間(Namespace)[5]是一種內(nèi)核功能,用于隔離進(jìn)程的資源視圖。它允許在同一系統(tǒng)上運(yùn)行的進(jìn)程具有獨(dú)立的資源副本,如進(jìn)程 ID、網(wǎng)絡(luò)接口、文件系統(tǒng)掛載點(diǎn)等。這種隔離性可以提供更好的安全性和資源管理。 以下是一些常見的 Linux 命名空間類型:
- PID命名空間:每個(gè)進(jìn)程在 PID 命名空間中都有一個(gè)唯一的進(jìn)程 ID。不同的 PID 命名空間中的進(jìn)程 ID 可以重復(fù),因此進(jìn)程在其所屬的命名空間中可以認(rèn)為是唯一的。
- 網(wǎng)絡(luò)命名空間:每個(gè)網(wǎng)絡(luò)命名空間都有自己的網(wǎng)絡(luò)設(shè)備、IP 地址、路由表和防火墻規(guī)則。這使得在不同的網(wǎng)絡(luò)命名空間中可以進(jìn)行網(wǎng)絡(luò)隔離和配置。
- 文件系統(tǒng)命名空間:文件系統(tǒng)命名空間允許在不同的命名空間中使用不同的文件系統(tǒng)視圖。這意味著一個(gè)進(jìn)程可以在一個(gè)命名空間中看到的文件和目錄,在另一個(gè)命名空間中可能是不可見的。
- UTS 命名空間:UTS 命名空間用于隔離主機(jī)名和域名。每個(gè) UTS 命名空間可以有自己獨(dú)立的主機(jī)名,這在容器化環(huán)境中非常有用。
- IPC 命名空間:IPC 命名空間用于隔離不同進(jìn)程之間的進(jìn)程間通信(IPC)機(jī)制,如信號(hào)量、消息隊(duì)列和共享內(nèi)存等。
- 用戶命名空間:用戶命名空間允許在不同命名空間中重新映射用戶和組 ID。這提供了更好的用戶隔離和權(quán)限管理。 通過使用這些命名空間,可以創(chuàng)建獨(dú)立的容器環(huán)境,每個(gè)容器都有自己的資源副本,從而實(shí)現(xiàn)更好的隔離和資源管理。
UTS命名空間
Linux UTS Namespace[6]。在 UTS 命名空間中,每個(gè)命名空間都有自己的主機(jī)名和域名。UTS 命名空間的使用場景包括:容器化和網(wǎng)絡(luò)隔離等。
要在程序中添加命名空間,我們只需在 parent() 方法的第二行,添加下面的這幾行代碼,以便于在Go運(yùn)行子進(jìn)程時(shí)傳遞給其一些額外的標(biāo)識(shí)。
cmd.SysProcAttr?=?&syscall.SysProcAttr{
?Cloneflags:?syscall.CLONE_NEWUTS?|?syscall.CLONE_NEWPID?|?syscall.CLONE_NEWNS、
}如果現(xiàn)在運(yùn)行程序,程序?qū)⒃?UTS、PID 和 MNT 命名空間內(nèi)運(yùn)行。
在 Docker 中,根文件系統(tǒng)是由 Docker 鏡像提供的,并且在容器啟動(dòng)時(shí)被掛載到容器的根目錄上。Docker 根文件系統(tǒng)一般具有分層結(jié)構(gòu)、只讀性和寫時(shí)復(fù)制等特性。
現(xiàn)在,雖然我們的進(jìn)程處于一組孤立的命名空間中,但文件系統(tǒng)看起來與主機(jī)相同。為了解決這個(gè)問題,我們需要以下四行代碼來實(shí)現(xiàn)根文件系統(tǒng):
must(syscall.Mount("rootfs",?"rootfs",?"",?syscall.MS_BIND,?""))
?must(os.MkdirAll("rootfs/oldrootfs",?0700))
????//?將當(dāng)前目錄?`/`?移到?`rootfs/oldrootfs`?并將新的?rootfs?目錄交換到?`/`
?must(syscall.PivotRoot("rootfs",?"rootfs/oldrootfs"))
?must(os.Chdir("/"))所以完整代碼如下:
package?main
import?(
?"fmt"
?"os"
?"os/exec"
?"syscall"
)
func?main()?{
?//?根據(jù)命令行參數(shù)選擇執(zhí)行不同的操作
?switch?os.Args[1]?{
?case?"run":
??parent()?//?執(zhí)行parent函數(shù)
?case?"child":
??child()?//?執(zhí)行child函數(shù)
?default:
??panic("wat?should?I?do")?//?拋出異常,程序無法繼續(xù)執(zhí)行
?}
}
func?parent()?{
?cmd?:=?exec.Command("/proc/self/exe",?append([]string{"child"},?os.Args[2:]...)...)
?//?設(shè)置子進(jìn)程的命名空間
?cmd.SysProcAttr?=?&syscall.SysProcAttr{
??Cloneflags:?syscall.CLONE_NEWUTS?|?syscall.CLONE_NEWPID?|?syscall.CLONE_NEWNS,
?}
?cmd.Stdin?=?os.Stdin
?cmd.Stdout?=?os.Stdout
?cmd.Stderr?=?os.Stderr
?//?運(yùn)行命令并檢查錯(cuò)誤
?if?err?:=?cmd.Run();?err?!=?nil?{
??fmt.Println("ERROR",?err)
??os.Exit(1)
?}
}
func?child()?{
?//?掛載文件系統(tǒng)
?must(syscall.Mount("rootfs",?"rootfs",?"",?syscall.MS_BIND,?""))
?must(os.MkdirAll("rootfs/oldrootfs",?0700))
?must(syscall.PivotRoot("rootfs",?"rootfs/oldrootfs"))
?must(os.Chdir("/"))
?cmd?:=?exec.Command(os.Args[2],?os.Args[3:]...)
?cmd.Stdin?=?os.Stdin
?cmd.Stdout?=?os.Stdout
?cmd.Stderr?=?os.Stderr
?//?運(yùn)行命令并檢查錯(cuò)誤
?if?err?:=?cmd.Run();?err?!=?nil?{
??fmt.Println("ERROR",?err)
??os.Exit(1)
?}
}
func?must(err?error)?{
?//?如果錯(cuò)誤不為空,拋出panic異常
?if?err?!=?nil?{
??panic(err)
?}
}是的,至此,基于golang實(shí)現(xiàn)的極簡版的容器代碼已經(jīng)有了基本骨架。
Cgroups
Linux Cgroups[7] 在 Docker 容器化中起著重要的作用,它提供了對(duì)容器的資源限制和隔離,使得容器可以在共享的宿主機(jī)上運(yùn)行而不會(huì)相互干擾:
- 資源限制
通過 Cgroups,Docker 可以對(duì)容器的資源使用進(jìn)行限制,如 CPU、內(nèi)存、磁盤和網(wǎng)絡(luò)等。這樣可以避免容器過度占用宿主機(jī)資源,保證系統(tǒng)的穩(wěn)定性和公平性。
- 隔離性
Cgroups 提供了容器級(jí)別的資源隔離,每個(gè)容器都可以被分配和限制其使用的資源。這樣,容器之間的資源使用不會(huì)互相干擾,一個(gè)容器的問題也不會(huì)影響其他容器或宿主機(jī)。
- 容器管理
Docker 使用 Cgroups 對(duì)容器進(jìn)行管理和監(jiān)控。通過讀取和設(shè)置 Cgroups 的屬性,Docker 可以實(shí)時(shí)了解容器的資源使用情況,并可以調(diào)整資源限制以滿足需求。
在cgroup(控制組)這部分,需要注意Cgroup 的掛載和層級(jí)結(jié)構(gòu)等限制。
所以我們將Cgrous這一部分加入到代碼實(shí)現(xiàn)中來如下:
package?main
import?(
????"fmt"
????"io/ioutil"
????"os"
????"os/exec"
????"strconv"
????"syscall"
)
func?main()?{
????//?創(chuàng)建?cgroup
????err?:=?createCgroup("mycontainer")
????if?err?!=?nil?{
????????fmt.Println("Failed?to?create?cgroup:",?err)
????????return
????}
????defer?func()?{
????????//?退出時(shí)刪除?cgroup
????????err?:=?deleteCgroup("mycontainer")
????????if?err?!=?nil?{
????????????fmt.Println("Failed?to?delete?cgroup:",?err)
????????}
????}()
????//?限制?CPU?使用率為?50%
????err?=?setCPULimit("mycontainer",?50)
????if?err?!=?nil?{
????????fmt.Println("Failed?to?set?CPU?limit:",?err)
????????return
????}
????//?在容器中運(yùn)行命令
????cmd?:=?exec.Command("/bin/bash")
????cmd.Stdin?=?os.Stdin
????cmd.Stdout?=?os.Stdout
????cmd.Stderr?=?os.Stderr
????cmd.SysProcAttr?=?&syscall.SysProcAttr{
????????Cloneflags:?syscall.CLONE_NEWNS?|?syscall.CLONE_NEWPID?|?syscall.CLONE_NEWUTS?|?syscall.CLONE_NEWIPC?|?syscall.CLONE_NEWNET,
????????Cgroup:?????"mycontainer",
????}
????err?=?cmd.Run()
????if?err?!=?nil?{
????????fmt.Println("Failed?to?run?command?in?container:",?err)
????}
}
func?createCgroup(name?string)?error?{
????cgroupPath?:=?"/sys/fs/cgroup/cpu/"?+?name
????err?:=?os.Mkdir(cgroupPath,?0755)
????if?err?!=?nil?{
????????return?err
????}
????//?將當(dāng)前進(jìn)程加入到?cgroup?中
????err?=?ioutil.WriteFile(cgroupPath+"/tasks",?[]byte(strconv.Itoa(os.Getpid())),?0644)
????if?err?!=?nil?{
????????return?err
????}
????return?nil
}
func?deleteCgroup(name?string)?error?{
????cgroupPath?:=?"/sys/fs/cgroup/cpu/"?+?name
????err?:=?os.Remove(cgroupPath)
????if?err?!=?nil?{
????????return?err
????}
????return?nil
}
func?setCPULimit(name?string,?limit?int)?error?{
????cgroupPath?:=?"/sys/fs/cgroup/cpu/"?+?name
????err?:=?ioutil.WriteFile(cgroupPath+"/cpu.cfs_quota_us",?[]byte(strconv.Itoa(limit*1000)),?0644)
????if?err?!=?nil?{
????????return?err
????}
????return?nil
}在上面,我們將當(dāng)前進(jìn)程加入到新創(chuàng)建的"mycontainer" 的 cgroup,然后,設(shè)置該 cgroup 的 CPU 使用率限制為 50%。繼而實(shí)現(xiàn)在容器中運(yùn)行一個(gè)交互式的 shell。
結(jié)語
編寫一個(gè)容器(container)是一個(gè)相當(dāng)復(fù)雜的任務(wù),涉及到許多底層的概念和技術(shù)?;仡櫛疚模褂胓olang一步步“還原”一個(gè)mini版的container所需步驟基本如下:
- 了解容器技術(shù)和相關(guān)概念:在開始編寫mini容器之前,強(qiáng)烈建議先了解一些容器技術(shù)的基本原理,如命名空間(namespaces)、控制組(cgroups)、文件系統(tǒng)隔離等。
- 選擇編程語言和庫:之所以選擇使用 Golang 進(jìn)行容器的編寫,因?yàn)樗峁┝藦?qiáng)大的并發(fā)和系統(tǒng)編程能力。同時(shí),還可以使用一些相關(guān)的庫,如
os/exec和syscall。 - 創(chuàng)建容器的基本結(jié)構(gòu):首先創(chuàng)建出一個(gè)基本的容器結(jié)構(gòu),該結(jié)構(gòu)將包含容器的信息,如 ID、進(jìn)程 ID、文件系統(tǒng)等。
- 設(shè)置容器的命名空間:使用 Golang 的
syscall包,設(shè)置容器的命名空間,如 PID 命名空間、網(wǎng)絡(luò)命名空間等。這樣可以將容器中的進(jìn)程與主機(jī)系統(tǒng)的進(jìn)程隔離開來。 - 設(shè)置容器的文件系統(tǒng):創(chuàng)建一個(gè)文件系統(tǒng),可以是一個(gè)文件夾或鏡像文件,用于存儲(chǔ)容器內(nèi)的文件和目錄。這里我們可以借助于 Golang 的
os和io/ioutil包來操作文件系統(tǒng)。 - 啟動(dòng)容器中的進(jìn)程:使用
os/exec包,在容器的命名空間中啟動(dòng)一個(gè)新的進(jìn)程, 并指定要運(yùn)行的可執(zhí)行文件和參數(shù)。 - 設(shè)置容器的網(wǎng)絡(luò):如果想讓容器具有網(wǎng)絡(luò)連接能力,我們還需要設(shè)置容器的網(wǎng)絡(luò)命名空間,并進(jìn)行相關(guān)網(wǎng)絡(luò)配置。這可能涉及到創(chuàng)建虛擬網(wǎng)絡(luò)設(shè)備、配置 IP 地址等。
- 處理容器的生命周期:需要考慮到容器的創(chuàng)建、啟動(dòng)、停止和銷毀等生命周期事件。這可能涉及到信號(hào)處理、資源清理等操作。
除此之外,還需要考慮到安全性、權(quán)限管理、資源限制等多方面因素。
當(dāng)然,實(shí)際的容器實(shí)現(xiàn)要更加復(fù)雜和完善。在實(shí)際項(xiàng)目應(yīng)用中,我們可能還需要考慮到如文件系統(tǒng)隔離、網(wǎng)絡(luò)隔離等遠(yuǎn)比這些復(fù)雜的場景。
以上就是使用Go語言編寫一個(gè)極簡版的容器Container的詳細(xì)內(nèi)容,更多關(guān)于Go編寫容器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
VScode下配置Go語言開發(fā)環(huán)境(2023最新)
在VSCode中配置Golang開發(fā)環(huán)境是非常簡單的,本文主要記錄了Go的安裝,以及給vscode配置Go的環(huán)境,具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10
golang數(shù)據(jù)結(jié)構(gòu)之golang稀疏數(shù)組sparsearray詳解
這篇文章主要介紹了golang數(shù)據(jù)結(jié)構(gòu)之golang稀疏數(shù)組sparsearray的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09
Go語言中緩沖bufio的原理解讀與應(yīng)用實(shí)戰(zhàn)
Go語言標(biāo)準(zhǔn)庫中的bufio包提供了帶緩沖的I/O操作,它通過封裝io.Reader和io.Writer接口,減少頻繁的I/O操作,提高讀寫效率,本文就來詳細(xì)的介紹一下,感興趣的可以學(xué)習(xí)2024-10-10
go編程中g(shù)o-sql-driver的離奇bug解決記錄分析
這篇文章主要為大家介紹了go編程中g(shù)o-sql-driver的離奇bug解決記錄分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05

