Kubernetes是一款开源的容器编排平台,可以帮助用户轻松地部署、扩展和管理容器化应用程序。在Kubernetes中,容器镜像是非常重要的一部分,因为它们包含了应用程序所需的所有依赖项和代码。在这篇文章中,我们将介绍如何在Kubernetes中保存容器镜像,并使用Docker Go SDK编写代码自动化这个过程。
Docker in Docker
Docker in Docker(DinD)是一个镜像,它允许在Docker容器内运行Docker。这是一个非常方便的解决方案,因为它允许您在没有安装Docker的主机上运行Docker容器。在Kubernetes中,我们可以使用Docker in Docker镜像来在容器中执行docker命令,操作主机docker,制作镜像并推送到远程仓库。
首先,我们需要在Kubernetes集群中创建一个Docker in Docker容器。我们可以使用Docker命令来创建容器:
docker run --privileged --name dind -d docker:dind
这个命令将在后台启动一个Docker in Docker容器,并为它分配一个名称“dind”。在创建容器时,我们需要使用“--privileged”选项,这将允许容器访问主机的Docker守护程序。这个选项通常是不安全的,因为它允许容器绕过主机的安全限制。因此,我们需要确保只有受信任的用户可以访问这个容器。
接下来,我们需要将主机的“/var/run/docker.sock”文件挂载到容器中。这个文件是Docker守护程序的UNIX套接字,可以让容器与主机的Docker守护程序通信。我们可以使用以下命令来挂载文件:
docker run --privileged --name dind -v /var/run/docker.sock:/var/run/docker.sock -d docker:dind
现在,我们已经准备好在Docker in Docker容器中运行Docker命令。我们测试一下docker命令是否正常
这里可以看到docker命令执行正常,也就说明可以顺利连接到主机上的docker上。
过滤并筛选出执行镜像
我们知道,每个pod都会创建出至少两个容器,一个是pause容器,一个是作业主容器。
主容器容器名称规格大概为:k8s_{podname}_{namespace}_{random_str},pause容器的名称开头是: k8s_POD_{podname}_{namespace}...
所以可以通过这样的规则过滤出指定的容器。
制作镜像
新版的docker新增了commit 命令,可以用来直接把运行中的容器制作为镜像:
可以看到已经成功保存镜像了。
提交镜像
使用docker login登录到镜像仓库,然后通过docker push推送镜像,这里没有大的问题,现在再k8s中保存容器镜像的可行性验证了,接下来就是通过编码实现程序打包提交镜像。
Docker Go SDK
Docker Go SDK是一个用于与Docker守护程序通信的Go语言库。它允许我们编写代码来拉取、构建和推送Docker镜像,我们可以使用Docker Go SDK编写代码来保存容器镜像。
首先,我们需要安装Docker Go SDK。我们可以使用以下命令来安装:
go get github.com/docker/docker/client
接下来,我们可以使用以下代码来连接到Docker守护程序,并拉取、构建和推送容器镜像:
ctx := context.Background()
dockerOpt := client.FromEnv
cli, err := client.NewClientWithOpts(dockerOpt, client.WithAPIVersionNegotiation())
if err != nil {
logrus.Errorf("getClientError:%+v", err)
return
}
//查询容器列表
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{Filters: filters.Args{}})
if err != nil {
logrus.Errorf("ContainerListError:%+v", err)
return
}
targetContainer := types.Container{}
for _, oneContainer := range containers {
for _, name := range oneContainer.Names {
if strings.Contains(name, podName) && strings.Contains(name, namespace) && !strings.HasPrefix(name, "/k8s_POD_") {
targetContainer = oneContainer
break
}
}
}
logrus.Infof("过滤出容器:%v", targetContainer.Names)
// 制作镜像
imageId, err := cli.ContainerCommit(ctx, targetContainer.ID, types.ContainerCommitOptions{
Pause: false,
Config: &container.Config{Image: imageName},
})
if err != nil {
logrus.Errorf("ContainerCommitError:%+v", err)
return
}
fmt.Println(imageId)
//给镜像打tag
err = cli.ImageTag(ctx, imageId.ID, imageName)
if err != nil {
logrus.Errorf("ImageTagError:%+v", err)
return
}
登录镜像仓库&提交镜像
//登录registry
authConfig := registry.AuthConfig{
Username: loginUserName,
Password: loginPassword,
ServerAddress: parsedUrl.Host,
}
logrus.Info("登录中,请稍后")
_, err = cli.RegistryLogin(context.Background(), authConfig)
if err != nil {
logrus.Errorf("RegistryLoginError:%+v", err)
return
}
logrus.Info("登录成功。")
encodedJSON, err := json.Marshal(authConfig)
if err != nil {
logrus.Errorf("AuthInfoMarshalError:%+v", err)
return
}
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
logrus.Infof("镜像: %s 推送中......", imageName)
// 推送镜像
pushResp, err := cli.ImagePush(ctx, imageName, types.ImagePushOptions{
All: false,
RegistryAuth: authStr,
})
//TODO 打印镜像上传进度
if err != nil {
logrus.Errorf("ImagePushError:%+v", err)
return
}
按需部署image-committer
上面我们已经跑通流程了,在需要保存指定容器为镜像的时候,查询出指定pod运行在哪个机器节点上,然后部署一个k8s原生的job到相同的节点上,让程序运行起来就可以,编排示例如下
apiVersion: batch/v1
kind: Job
metadata:
name: image-committer-0032
namespace: ns-145
spec:
parallelism: 1
completions: 1
activeDeadlineSeconds: 600
backoffLimit: 600
template:
spec:
volumes:
- name: docker-secret
secret:
secretName: cyl-docker-secret
defaultMode: 420
- name: docker-socket
hostPath:
path: /var/run/docker.sock
containers:
- name: committer
image: harbor.ctyuncdn.cn/bc-test/image-committer:v0.5
command:
- /opt/bc-srv/bin/image-committer
- --pod-name="nginx-demo01-7bb8596b59-b2dc6"
- --namespace=ns-145
- --new-image-name="harbor.xxx.cn/bc-test/nginx-commiter:v0.3"
resources: {}
volumeMounts:
- name: docker-secret
mountPath: /etc/docker/image-committer
- name: docker-socket
mountPath: /var/run/docker.sock
imagePullPolicy: Always
restartPolicy: OnFailure
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirst
nodeName: node-2-0-2
securityContext: {}
schedulerName: default-scheduler
结论
在这篇文章中,我们介绍了如何在Kubernetes中保存容器镜像,并使用Docker Go SDK编写代码自动化这个过程。通过使用Docker in Docker镜像,我们可以在Kubernetes集群中轻松地拉取、构建和推送容器镜像。同时,使用Docker Go SDK编写代码可以帮助我们实现自动化的容器镜像保存功能,提高开发和部署效率。