一,鏡像分層機(jī)制
在 Docker 中,一個(gè)鏡像可以由多個(gè)分層(Layer)組成。每個(gè)分層都表示一些修改或添加到上一個(gè)分層的文件系統(tǒng)差異。
Golang 在構(gòu)建 Docker 鏡像時(shí)也支持類似的機(jī)制,通過(guò)?docker build
?命令來(lái)創(chuàng)建一個(gè)包含多個(gè)分層的鏡像。
具體實(shí)現(xiàn)方式是在 Dockerfile 中使用?RUN
、ADD
、COPY
?等指令來(lái)安裝軟件包、下載文件等操作,并且使用?-o
?選項(xiàng)設(shè)置編譯輸出目錄:
FROM golang:1.16
WORKDIR /go/src/app
COPY . .
RUN go get -d -v ./...
RUN go install -v ./...
CMD ["app"]
上面的例子中,我們從 Golang 官方鏡像開始構(gòu)建一個(gè)新鏡像,并設(shè)置工作目錄為?/go/src/app
。然后復(fù)制當(dāng)前目錄下所有文件到容器中,并執(zhí)行?go get
?和?go install
?命令安裝和編譯 Go 代碼。最后設(shè)置容器啟動(dòng)命令為?app
。
在執(zhí)行這個(gè) Dockerfile 的過(guò)程中,Docker 會(huì)將每個(gè)指令生成的結(jié)果保存為一個(gè)新的分層,并將它們合并成最終的鏡像。這種機(jī)制有助于減小鏡像大小,避免重復(fù)數(shù)據(jù)存儲(chǔ)。
如果你想查看某個(gè) Docker 鏡像的所有分層信息,可以使用?docker history
?命令:
$ docker history my-image
IMAGE CREATED CREATED BY SIZE COMMENT
d34e5b1a58b3 5 days ago /bin/sh -c #(nop) CMD ["app"] 0B
<missing> 5 days ago /bin/sh -c go install -v ./... 4.57MB
<missing> 5 days ago /bin/sh -c go get -d -v ./... 43.9MB
<missing> 5 days ago /bin/sh -c #(nop) COPY dir:86fd420f94bef8f09... 2.61kB
<missing> 6 weeks ago /bin/sh -c #(nop) WORKDIR /go/src/app 0B
<missing> 6 weeks ago /bin/sh -c #(nop) COPY file:d75a3e0d6401fcdb7... 116B
<missing> 6 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 6 weeks ago /bin/sh -c #(nop) ADD file:e0da07f59373bac823... 811MB
上面的結(jié)果顯示了該鏡像中每個(gè)分層所包含的文件、大小以及生成方式。
需要注意的是,由于 Docker 鏡像的設(shè)計(jì)原理,某些操作(例如?apt-get update
)可能會(huì)導(dǎo)致多次創(chuàng)建分層,從而增加鏡像大小。因此在編寫 Dockerfile 的時(shí)候要盡量避免重復(fù)或無(wú)效的操作,以減小鏡像大小。
二,容器寫時(shí)復(fù)制機(jī)制
Golang 容器寫時(shí)復(fù)制(Copy-on-write) 的實(shí)現(xiàn)細(xì)節(jié),以下是一個(gè)簡(jiǎn)單的代碼示例:
func CopyContainer(rootfs string) (string, error) {
// 創(chuàng)建容器鏡像的只讀層
runcmd := exec.Command("docker", "create", rootfs)
output, err := runcmd.Output()
if err != nil {
return "", fmt.Errorf("failed to create container: %v", err)
}
containerID := strings.TrimSpace(string(output))
// 掛載容器鏡像到一個(gè)臨時(shí)目錄
tmpdir, err := ioutil.TempDir("", "container")
if err != nil {
return "", fmt.Errorf("failed to create temporary directory: %v", err)
}
defer os.RemoveAll(tmpdir)
mountcmd := exec.Command("mount", "-o", "bind", "/proc/self/mounts", filepath.Join(tmpdir, "mounts"))
if err := mountcmd.Run(); err != nil {
return "", fmt.Errorf("failed to mount /proc/self/mounts: %v", err)
}
// 以只讀方式掛載容器層到臨時(shí)目錄
rootfsPath := filepath.Join(tmpdir, "rootfs")
if err := os.MkdirAll(rootfsPath, 0755); err != nil {
return "", fmt.Errorf("failed to create rootfs path: %v", err)
}
mountcmd = exec.Command("mount", "-o", "ro,noatime,nodiratime,noexec,nodev,nosuid", "--bind", rootfs, rootfsPath)
if err := mountcmd.Run(); err != nil {
return "", fmt.Errorf("failed to bind mount rootfs: %v", err)
}
// 創(chuàng)建容器鏡像的讀寫層
layerPath := filepath.Join(tmpdir, "layer")
if err := os.MkdirAll(layerPath, 0755); err != nil {
return "", fmt.Errorf("failed to create layer path: %v", err)
}
// 掛載讀寫層到容器鏡像只讀層上
mountcmd = exec.Command("mount", "-o", "rw,noatime,nodiratime,noexec,nodev,nosuid,lowerdir="+rootfsPath+",upperdir="+layerPath+",workdir="+filepath.Join(tmpdir, "work"), "none", filepath.Join(tmpdir, "overlay"))
if err := mountcmd.Run(); err != nil {
return "", fmt.Errorf("failed to create overlay mount: %v", err)
}
// 卸載臨時(shí)目錄下掛載的文件系統(tǒng)
defer func() {
exec.Command("umount", "-l", filepath.Join(tmpdir, "overlay")).Run()
exec.Command("umount", "-l", rootfsPath).Run()
exec.Command("umount", "-l", filepath.Join(tmpdir, "mounts")).Run()
}()
containerRoot := filepath.Join(layerPath, "root")
return containerRoot, nil
}
此函數(shù)使用 Golang 的?os/exec
?包來(lái)執(zhí)行操作系統(tǒng)級(jí)別的命令(如掛載和卸載文件系統(tǒng))。它首先創(chuàng)建一個(gè)只讀的容器鏡像,并將其掛載到一個(gè)臨時(shí)目錄。然后,它將容器鏡像根文件系統(tǒng)以只讀方式掛載到該臨時(shí)目錄中,然后再將讀寫層作為 OverlayFS 掛載到只讀層上。最后,它返回容器的根路徑。
需要注意的是,此示例僅適用于 Linux 系統(tǒng),并且使用了一些操作系統(tǒng)特定的命令和選項(xiàng)。在不同的操作系統(tǒng)和環(huán)境中可能需要進(jìn)行修改以實(shí)現(xiàn)類似的功能。
三,容器聯(lián)合掛載機(jī)制
在Golang中,可以通過(guò)使用os/exec包和容器運(yùn)行時(shí)接口(CRI)來(lái)實(shí)現(xiàn)容器的聯(lián)合掛載機(jī)制。
具體步驟如下:
- 使用os/exec包創(chuàng)建一個(gè)新的進(jìn)程,并設(shè)置其命令參數(shù)為需要執(zhí)行的容器程序。
- 在命令參數(shù)中指定需要掛載的文件系統(tǒng)類型及其路徑,例如:--mount type=bind,source=/host/path,target=/container/path
- 使用CRI將該進(jìn)程作為容器啟動(dòng)起來(lái)。
- 容器內(nèi)部可以訪問(wèn)到宿主機(jī)上已經(jīng)掛載好的目錄,即實(shí)現(xiàn)了聯(lián)合掛載。
示例代碼如下:
package main
import (
"os"
"os/exec"
)
func main() {
cmd := exec.Command("docker", "run", "--rm",
"-v", "/etc:/host/etc:ro",
"alpine", "cat", "/host/etc/hostname")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
panic(err)
}
}
以上代碼會(huì)啟動(dòng)一個(gè)Alpine Linux容器,并且將宿主機(jī)上的/etc目錄以只讀方式掛載到容器內(nèi)部。然后在容器內(nèi)部執(zhí)行cat /host/etc/hostname命令,輸出宿主機(jī)上的主機(jī)名信息
四,鏡像內(nèi)容尋址機(jī)制
在golang中,鏡像內(nèi)容尋址機(jī)制指的是程序在運(yùn)行時(shí)如何查找和訪問(wèn)已經(jīng)編譯好的包文件。golang使用了一種基于包名的尋址機(jī)制。
當(dāng)我們?cè)诖a中引用一個(gè)外部包時(shí),例如import "fmt"
,編譯器會(huì)首先查找系統(tǒng)上的標(biāo)準(zhǔn)庫(kù)路徑,如果找到了就直接使用系統(tǒng)自帶的fmt包;如果沒(méi)有找到,則會(huì)繼續(xù)在$GOPATH環(huán)境變量所指向的目錄下查找,并將其編譯成二進(jìn)制文件進(jìn)行鏈接。若還未找到,則會(huì)報(bào)錯(cuò)提示無(wú)法找到對(duì)應(yīng)包。
Golang對(duì)于不同操作系統(tǒng)采用了不同的命名規(guī)范和后綴名。例如,在Windows平臺(tái)下生成的可執(zhí)行文件為.exe格式,在Linux或Unix平臺(tái)下則是二進(jìn)制可執(zhí)行文件。
golang通過(guò)基于包名和路徑來(lái)確定應(yīng)該從哪個(gè)位置加載相應(yīng)的包,這種方式使得依賴管理更加簡(jiǎn)單易懂,同時(shí)也方便了跨平臺(tái)開發(fā)。
五,鏡像構(gòu)建
Golang 鏡像構(gòu)建通??梢苑譃橐韵聨讉€(gè)步驟:
- 編寫Dockerfile文件:Dockerfile是構(gòu)建鏡像的配置文件,其中包含了一系列命令和指令,用于指定如何將代碼打包進(jìn)鏡像中。例如,可以通過(guò)
FROM
指令指定基礎(chǔ)鏡像(例如golang:latest
);通過(guò)WORKDIR
指令設(shè)置工作目錄;通過(guò)COPY
或者?ADD
?指令將代碼拷貝到鏡像中等。 - 構(gòu)建Docker鏡像:在終端窗口中使用docker build命令來(lái)執(zhí)行構(gòu)建過(guò)程。例如:
docker build -t my-golang-app .
這條命令會(huì)在當(dāng)前目錄下尋找名為“Dockerfile”的文件,并以此為依據(jù)構(gòu)建新的my-golang-app鏡像。
- 運(yùn)行容器:在終端窗口中使用docker run命令運(yùn)行容器。例如:
docker run -p 8080:8080 my-golang-app
這樣就成功運(yùn)行了一個(gè) Golang 應(yīng)用程序的 Docker 容器,并且可以通過(guò)瀏覽器訪問(wèn)?http://localhost:8080?來(lái)查看應(yīng)用程序的輸出。
需要注意的是,在編寫 Dockerfile 文件時(shí),應(yīng)該盡量遵循最佳實(shí)踐,避免一些常見問(wèn)題,如不必要的安裝軟件包、不規(guī)范的文件權(quán)限等。
六,鏡像共享
Golang 鏡像共享一般有兩種方式:
- 通過(guò) Docker Hub 共享:Docker Hub 是官方的 Docker 鏡像倉(cāng)庫(kù),開發(fā)者可以在其中創(chuàng)建自己的鏡像并分享給其他人。要實(shí)現(xiàn)這種方式,需要在 Docker Hub 上注冊(cè)賬號(hào),并將自己的 Golang 鏡像 push 到 Docker Hub 上去,然后其他用戶就可以 pull 下來(lái)使用。
- 通過(guò)私有鏡像倉(cāng)庫(kù)共享:如果不想將自己的 Golang 鏡像公開分享到 Docker Hub 上,也可以搭建自己的私有鏡像倉(cāng)庫(kù)(如 Harbor、Nexus等),將其作為公司或團(tuán)隊(duì)內(nèi)部的鏡像管理中心,以便于內(nèi)部協(xié)同開發(fā)和部署應(yīng)用程序。在私有鏡像倉(cāng)庫(kù)中上傳和下載 Golang 鏡像與在 Docker Hub 中類似。
無(wú)論是哪種方式,都需要遵循最佳實(shí)踐制作好 Golang 鏡像,并確保該鏡像具有較高的可重復(fù)性和穩(wěn)定性,在生產(chǎn)環(huán)境中能夠正常運(yùn)行。
七,私有注冊(cè)中心構(gòu)建
在 Golang 中,可以使用 Docker 鏡像倉(cāng)庫(kù)或者自己搭建私有注冊(cè)中心來(lái)進(jìn)行鏡像的管理和共享。下面介紹一種基于 Harbor 的私有注冊(cè)中心構(gòu)建方式。
- 下載安裝并啟動(dòng) Harbor
在官網(wǎng)下載 Harbor 并解壓縮到任意目錄,然后運(yùn)行?./install.sh
?腳本安裝。安裝完畢后執(zhí)行?docker-compose up -d
?啟動(dòng) Harbor。默認(rèn)情況下 Harbor 運(yùn)行在 80 和 443 端口上,并使用自簽名證書加密通信。 - 創(chuàng)建項(xiàng)目并添加用戶
登錄到 Harbor 控制臺(tái),在左側(cè)導(dǎo)航欄選擇“項(xiàng)目”,點(diǎn)擊“新建項(xiàng)目”按鈕創(chuàng)建一個(gè)新的項(xiàng)目。然后在該項(xiàng)目中創(chuàng)建用戶并分配權(quán)限,以便于其他人上傳、下載和管理鏡像。 - 構(gòu)建 Golang 鏡像并 push 到 Harbor
編寫一個(gè)簡(jiǎn)單的 Golang 應(yīng)用程序,并編譯成可執(zhí)行文件(例如 hello-world),然后寫好 Dockerfile 文件并將其與可執(zhí)行文件放在同一目錄下。接著在該目錄下執(zhí)行以下命令:
# 使用 Dockerfile 構(gòu)建鏡像
docker build -t harbor.example.com/myproject/hello-world:v1 .
# 將鏡像推送到 Harbor 私有注冊(cè)中心
docker push harbor.example.com/myproject/hello-world:v1
其中?harbor.example.com
?是你的私有注冊(cè)中心地址,myproject
?是你創(chuàng)建的項(xiàng)目名稱,hello-world:v1
?是鏡像的名稱和版本號(hào)。
- 下載并使用 Golang 鏡像
在其他機(jī)器上登錄到 Harbor 控制臺(tái)或者運(yùn)行以下命令下載鏡像:
docker login harbor.example.com
docker pull harbor.example.com/myproject/hello-world:v1
然后就可以使用該鏡像了。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-609723.html
注意:為了保證安全性和可靠性,在實(shí)際生產(chǎn)環(huán)境中,需要對(duì)私有注冊(cè)中心進(jìn)行更多的配置和管理,例如設(shè)置訪問(wèn)控制、加密通信、備份恢復(fù)等。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-609723.html
到了這里,關(guān)于在CSDN學(xué)Golang云原生(Docker鏡像)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!