Go 實現容器技術的一個硬傷

我們都知道 Go runtime 是一個多線程的運行環境,線程是一個被屏蔽的底層,對『應用層』透明。然而,有不少 Linux 系統調用卻不能在這樣的環境下正常工作。

比如,容器技術中需要用到的 setns

Docker 可以通過 exec 命令在一個存在的容器中運行一個進程,那麼這個進程就需要通過 setns 系統調用加入到容器對應的 namespace 中,然而 setns 並不能正確的在 Go runtime 這樣的多線程環境下工作,因此在實現一個容器的時候,這方面 Go 語言就遠沒有 C 語言來得直接、簡潔。

Docker 實現 setns 的原理

第一步就是需要用 os/exec 啟動一個新的進程。

cmd := &exec.Cmd{ Path: "/proc/self/exe", Args: []string{"setns"},}cmd.Start()

第二步最為關鍵,必須讓這個新的進程在啟動 runtime 多線程環境之前完成 setns 相關操作, Go 語言並沒有直接提供在一個程序啟動前執行某段代碼的機制,但 C 語言卻可以通過 gcc 的 擴展 __attribute__((constructor)) 來實現程序啟動前執行特定代碼,因此 Go 就可以通過 cgo 嵌入 這樣的一段 C 代碼來完成 runtime 啟動前執行特定的 C 代碼。

Docker 實現如下:

// +build linux,!gccgopackage nsenter/*#cgo CFLAGS: -Wallextern void nsexec();void __attribute__((constructor)) init(void) { nsexec();}*/import "C"

這段代碼就會在 Go 程序真正啟動前執行這裡定義的 init() 函數,然後執行 nsexec(), nsexec 函數里就可以干我們想乾的所有事情了,有興趣的看這裡 void nsexec() 。注意這裡定義的 nsenter 包並不需要被顯示使用,只需要 import 被編譯進去即可。

有人給 Go 提的 issue: proposal: runtime: add RunWithMainThreadOnly 和 討論 。

推薦閱讀:

什麼是容器?為什麼我們關注它?
Kubernetes 是什麼?
CRI-O 1.0 簡介
如何在 Windows 上運行 Linux 容器
黃允松:雲計算的謊言與野心

TAG:Go語言 | 容器 | Docker |