在CSDN学Golang云原生(Docker镜像)

一,镜像分层机制

在 Docker 中,一个镜像可以由多个分层(Layer)组成。每个分层都表示一些修改或添加到上一个分层的文件系统差异。

Golang 在构建 Docker 镜像时也支持类似的机制,通过 docker build 命令来创建一个包含多个分层的镜像。

具体实现方式是在 Dockerfile 中使用 RUNADDCOPY 等指令来安装软件包、下载文件等操作,并且使用 -o 选项设置编译输出目录:

FROM golang:1.16

WORKDIR /go/src/app

COPY . .

RUN go get -d -v ./...
RUN go install -v ./...

CMD ["app"]

上面的例子中,我们从 Golang 官方镜像开始构建一个新镜像,并设置工作目录为 /go/src/app。然后复制当前目录下所有文件到容器中,并执行 go get 和 go install 命令安装和编译 Go 代码。最后设置容器启动命令为 app

在执行这个 Dockerfile 的过程中,Docker 会将每个指令生成的结果保存为一个新的分层,并将它们合并成最终的镜像。这种机制有助于减小镜像大小,避免重复数据存储。

如果你想查看某个 Docker 镜像的所有分层信息,可以使用 docker history 命令:

$ docker history my-image
IMAGE          CREATED        CREATED BY                                      SIZE      COMMENT
d34e5b1a58b3   5 days ago     /bin/sh -c #(nop)  CMD ["app"]                  0B        
      5 days ago     /bin/sh -c go install -v ./...                   4.57MB    
      5 days ago     /bin/sh -c go get -d -v ./...                    43.9MB   
      5 days ago     /bin/sh -c #(nop) COPY dir:86fd420f94bef8f09...   2.61kB    
      6 weeks ago    /bin/sh -c #(nop) WORKDIR /go/src/app            0B        
      6 weeks ago    /bin/sh -c #(nop) COPY file:d75a3e0d6401fcdb7...   116B      
      6 weeks ago    /bin/sh -c #(nop)  CMD ["bash"]                  0B        
      6 weeks ago    /bin/sh -c #(nop) ADD file:e0da07f59373bac823...   811MB     

上面的结果显示了该镜像中每个分层所包含的文件、大小以及生成方式。

需要注意的是,由于 Docker 镜像的设计原理,某些操作(例如 apt-get update)可能会导致多次创建分层,从而增加镜像大小。因此在编写 Dockerfile 的时候要尽量避免重复或无效的操作,以减小镜像大小。

二,容器写时复制机制

Golang 容器写时复制(Copy-on-write) 的实现细节,以下是一个简单的代码示例:

func CopyContainer(rootfs string) (string, error) {
    // 创建容器镜像的只读层
    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))

    // 挂载容器镜像到一个临时目录
    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)
    }
    
	// 以只读方式挂载容器层到临时目录
	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)
	}

    // 创建容器镜像的读写层
    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)
	}

	// 卸载临时目录下挂载的文件系统
	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
}

此函数使用 Golang 的 os/exec 包来执行操作系统级别的命令(如挂载和卸载文件系统)。它首先创建一个只读的容器镜像,并将其挂载到一个临时目录。然后,它将容器镜像根文件系统以只读方式挂载到该临时目录中,然后再将读写层作为 OverlayFS 挂载到只读层上。最后,它返回容器的根路径。

需要注意的是,此示例仅适用于 Linux 系统,并且使用了一些操作系统特定的命令和选项。在不同的操作系统和环境中可能需要进行修改以实现类似的功能。

三,容器联合挂载机制

在Golang中,可以通过使用os/exec包和容器运行时接口(CRI)来实现容器的联合挂载机制。

具体步骤如下:

  1. 使用os/exec包创建一个新的进程,并设置其命令参数为需要执行的容器程序。
  2. 在命令参数中指定需要挂载的文件系统类型及其路径,例如:--mount type=bind,source=/host/path,target=/container/path
  3. 使用CRI将该进程作为容器启动起来。
  4. 容器内部可以访问到宿主机上已经挂载好的目录,即实现了联合挂载。

示例代码如下:

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

以上代码会启动一个Alpine Linux容器,并且将宿主机上的/etc目录以只读方式挂载到容器内部。然后在容器内部执行cat /host/etc/hostname命令,输出宿主机上的主机名信息

四,镜像内容寻址机制

在golang中,镜像内容寻址机制指的是程序在运行时如何查找和访问已经编译好的包文件。golang使用了一种基于包名的寻址机制。

当我们在代码中引用一个外部包时,例如import "fmt",编译器会首先查找系统上的标准库路径,如果找到了就直接使用系统自带的fmt包;如果没有找到,则会继续在$GOPATH环境变量所指向的目录下查找,并将其编译成二进制文件进行链接。若还未找到,则会报错提示无法找到对应包。

Golang对于不同操作系统采用了不同的命名规范和后缀名。例如,在Windows平台下生成的可执行文件为.exe格式,在Linux或Unix平台下则是二进制可执行文件。

golang通过基于包名和路径来确定应该从哪个位置加载相应的包,这种方式使得依赖管理更加简单易懂,同时也方便了跨平台开发。

五,镜像构建

Golang 镜像构建通常可以分为以下几个步骤:

  1. 编写Dockerfile文件:Dockerfile是构建镜像的配置文件,其中包含了一系列命令和指令,用于指定如何将代码打包进镜像中。例如,可以通过FROM指令指定基础镜像(例如golang:latest);通过WORKDIR指令设置工作目录;通过COPY或者 ADD 指令将代码拷贝到镜像中等。
  2. 构建Docker镜像:在终端窗口中使用docker build命令来执行构建过程。例如:
docker build -t my-golang-app .

这条命令会在当前目录下寻找名为“Dockerfile”的文件,并以此为依据构建新的my-golang-app镜像。

  1. 运行容器:在终端窗口中使用docker run命令运行容器。例如:
docker run -p 8080:8080 my-golang-app

这样就成功运行了一个 Golang 应用程序的 Docker 容器,并且可以通过浏览器访问 http://localhost:8080 来查看应用程序的输出。

需要注意的是,在编写 Dockerfile 文件时,应该尽量遵循最佳实践,避免一些常见问题,如不必要的安装软件包、不规范的文件权限等。

六,镜像共享

Golang 镜像共享一般有两种方式:

  1. 通过 Docker Hub 共享:Docker Hub 是官方的 Docker 镜像仓库,开发者可以在其中创建自己的镜像并分享给其他人。要实现这种方式,需要在 Docker Hub 上注册账号,并将自己的 Golang 镜像 push 到 Docker Hub 上去,然后其他用户就可以 pull 下来使用。
  2. 通过私有镜像仓库共享:如果不想将自己的 Golang 镜像公开分享到 Docker Hub 上,也可以搭建自己的私有镜像仓库(如 Harbor、Nexus等),将其作为公司或团队内部的镜像管理中心,以便于内部协同开发和部署应用程序。在私有镜像仓库中上传和下载 Golang 镜像与在 Docker Hub 中类似。

无论是哪种方式,都需要遵循最佳实践制作好 Golang 镜像,并确保该镜像具有较高的可重复性和稳定性,在生产环境中能够正常运行。

七,私有注册中心构建

在 Golang 中,可以使用 Docker 镜像仓库或者自己搭建私有注册中心来进行镜像的管理和共享。下面介绍一种基于 Harbor 的私有注册中心构建方式。

  1. 下载安装并启动 Harbor
    在官网下载 Harbor 并解压缩到任意目录,然后运行 ./install.sh 脚本安装。安装完毕后执行 docker-compose up -d 启动 Harbor。默认情况下 Harbor 运行在 80 和 443 端口上,并使用自签名证书加密通信。
  2. 创建项目并添加用户
    登录到 Harbor 控制台,在左侧导航栏选择“项目”,点击“新建项目”按钮创建一个新的项目。然后在该项目中创建用户并分配权限,以便于其他人上传、下载和管理镜像。
  3. 构建 Golang 镜像并 push 到 Harbor
    编写一个简单的 Golang 应用程序,并编译成可执行文件(例如 hello-world),然后写好 Dockerfile 文件并将其与可执行文件放在同一目录下。接着在该目录下执行以下命令:
# 使用 Dockerfile 构建镜像
docker build -t harbor.example.com/myproject/hello-world:v1 .

# 将镜像推送到 Harbor 私有注册中心
docker push harbor.example.com/myproject/hello-world:v1

其中 harbor.example.com 是你的私有注册中心地址,myproject 是你创建的项目名称,hello-world:v1 是镜像的名称和版本号。

  1. 下载并使用 Golang 镜像
    在其他机器上登录到 Harbor 控制台或者运行以下命令下载镜像:
docker login harbor.example.com
docker pull harbor.example.com/myproject/hello-world:v1

然后就可以使用该镜像了。

注意:为了保证安全性和可靠性,在实际生产环境中,需要对私有注册中心进行更多的配置和管理,例如设置访问控制、加密通信、备份恢复等。

你可能感兴趣的:(golang,云原生,docker)