如果你从未接触过Docker或Go,但却很感兴趣,这篇文章或许可以帮助你
这篇文章可能需要你
- 使用过Docker、Dockerfile,或阅读过Docker官方文档
最近在使用docker的SDK做一个项目,但官方的文档遵循了Go社区的经典原则,只给出了几个example
如果你想做的事情不在这个列表内,那么很遗憾,你就没有代码可以抄了,自己研究吧。
这片文章会描述我在这段时间内对docker SDK for Go 的探索,不涉及你能在官方文档中找到的基础知识
先来从简单的例子开始(不包含在官方的example中的例子)
- 像平常那样,用DockerFile完成一次简单的部署
func DeployWithDockerFile(username, repoName string) {
f, err := NewTarArchiveFromPath(download.GetRepoDir(username, repoName))
if err != nil {
panic(err)
}
build, err := cli.ImageBuild(ctx, f, types.ImageBuildOptions{
Dockerfile: "Dockerfile",
})
if err != nil {
panic(err)
}
all, err := io.ReadAll(build.Body)
if err != nil {
panic(err)
}
fmt.Println("resp:", string(all))
}
复制代码
在上面这段代码中,做了如下几件事
- 将要部署的项目压缩成一个tar文件
- 调用
cli.ImageBuild
- 处理
cli.ImageBuild
的返回值,他是一个ImageBuildResponse
cli
首先,如果你阅读过了官方文档,你一定知道这个cli
是什么,以及他是怎么运作的:
Go社区很喜欢用这种缩写作为变量的名字,如果在Java社区中,我想它可能会叫做DockerClient
cli, _ = client.NewClientWithOpts(client.FromEnv,client.WithAPIVersionNegotiation())
复制代码
使用这种方式对其进行初始化,点开源码发现
client, err := defaultHTTPClient(DefaultDockerHost)
...
func defaultHTTPClient(host string) (*http.Client, error) {
...
return &http.Client{
Transport: transport,
CheckRedirect: CheckRedirect,
}, nil
}
复制代码
这居然就是个普通的HTTPClient,用于和docker的daemon线程进行交互,所以这个go sdk实际上就是帮你封装了一层,具体做的事情和自己封装http.Client去使用API没有任何区别,不过有了这层封装,代码写起来还是方便了很多
ImageBuild()
这个函数需要传入三个参数,这个SDK中的大部分函数都是按照这个模式设计的
首先是恒古不变的context
,文档中没有特殊要求,给他传一个emptyContext
就可以了,如果你对context
感到陌生,也不要紧,他只是Go中对于协程控制的一部分,可以很方便的在父子协程中通信,使用这个SDK对于它基本上是无感知的,只需要把它传进去就好
第二个参数通常用来定位你的项目/容器/镜像,在这里他接受一个tar文件,作为你的当前操作目录,在其他的函数中,这一项可能要传入容器id/镜像id等等。在Docker中涉及文件传输的部分,经常会使用tar这种格式
第三个参数通常是当前操作的options,这个options中的选项即是官方文档中API reference的各种参数,比如这里的dockerfile参数,他需要指定Dockerfile在你项目中的相对路径
这个函数返回一个Response,可以将其理解为精简的httpResponse,里面包含着docker返回的信息,但根据返回结果来看它应该是http2.0的stream
将项目打包成Image后,需要将其Run成Container,在这个过程中绑定端口
docker run -p 8000:9000 demo
复制代码
他的实现如下
func StartFromImage(username, repoName string) {
var resp, err = cli.ContainerCreate(ctx, &container.Config{
Image: imageName,
}, &container.HostConfig{
PortBindings: nat.PortMap{"9000/tcp": []nat.PortBinding{{
HostIP: "0.0.0.0",
HostPort: "8000",
}}},
}, nil, nil, "name")
cID := resp.ID
if err := cli.ContainerStart(ctx, cID, types.ContainerStartOptions{}); err != nil {
...
}
...
}
复制代码
ContainerCreate
在Docker API中,创建容器和启动容器是两个操作,其中需要注意在调用ContainerCreate
绑定端口时,需设置PortBindings
他是一个nat.PortMap
类型
// PortMap is a collection of PortBinding indexed by Port
type PortMap map[Port][]PortBinding
复制代码
这里的Port是string类型,map的value是一个PortBinding的数组
特别注意:这里的port是端口号/协议的组合,形如8080/tcp,9000/udp,而后面PortBinding中则是普通的端口号和Host地址
ContainerStart
相当于docker start
,但只接受容器的id,不支持tag作为参数
至此,完成了基本的由Dockerfile创建镜像,再创建容器,运行容器的过程