k8s环境下如何优雅地构建镜像
前言
随着各企业上云进程的加速,用容器的方式来交付软件产品也变得越来越普遍,如何以更安全的方式来构建容器镜像,也就成了大家关注的话题。 docker 构建镜像
docker是最近几年非常火热的容器技术,用docker来构建容器镜像也是常用的方法,在具备构建容器镜像所需的两个要素(Dockerfile & 上下文)的前提下,用下述命令就能构建一个容器镜像出来
$ docker build -t your_registry/your_repository:tag .
然后用 docker push 将镜像推送到镜像仓库
$ docker push your_registry/your_repository:tag
现在DevOps 的CI/CD环境大多数都会运行在容器内,镜像的构成也是在容器内完成的。这时候,通常用以下两种方式来完成容器内构建镜像的工作:
挂载宿主机的socket文件
$ docker run -it -v /var/run/docker.sock:/var/run/docker.sock docker
然后在容器内部用 docker build 构建镜像
$ docker build -f <dockerfile> -t test/test:v0.1 .
由于docker依赖于 docker daemon 进程, docker daemon 进程是一个 Unixsocket 连接,且 /var/run/docker.sock 文件是root权限,
$ ls -ltr /var/run/docker.sock lrwxr-xr-x 1 root daemon 69 Nov 26 15:13 /var/run/docker.sock
说明只有root权限才能访问 docker daemon 进程,在 docker daemon 无法暴露或者用户没有权限获取 docker daemon 进程的前提下,用 docker build 来构建镜像就变的非常困难了。
远程调用docker socket
docker daemon端开启远程调用
cat /etc/docker/daemon.json
{
"hosts": ["tcp://0.0.0.0:2375","unix:///var/run/docker.sock"]
}
可以在容器里面使用docker client根据dockerfile远程调用docker socket执行构建
$ cat dockerfile FROM busybox CMD ["echo","hello world !"]
dind(docker-in-docker)
这种方式不需要挂载宿主机的socket文件,但是需要以 –privileged 权限来以dind镜像创建一个容器:
$ docker run --rm -it --privileged docker:18.06-dind
然后在容器里面构建容器镜像并推送至远端仓库。
dind能够满足构建容器镜像的需求,但是从上面的命令看,有一个参数:–privileged 。意味这这个容器具有一些特权,他可能会看到宿主机上的一些设备,而且能够执行mount命令。
dind还有很多问题,只是方便docker开发人员来测试docker,所以官方也说running Docker inside Docker is generally not recommended。具体的可以看看这篇博文: https://jpetazzo.github.io/20…
上述两种方法,都能满足在容器内构建容器镜像且推送镜像至远端仓库的需求,但是从security角度来讲,需要root 权限(第一种方式),提供特权(第二种方式) 都使得风险增大,在Kubernetes 多租户的场景下,这种风险是不能接受的。那是否有一种不需要特殊权限,还能快速构建容器镜像的方法呢?答案就是下面讲的Kaniko。
Kaniko构建
Kaniko是谷歌开源的一款用来构建容器镜像的工具。与docker不同,Kaniko 并不依赖于Docker daemon进程,完全是在用户空间根据Dockerfile的内容逐行执行命令来构建镜像,这就使得在一些无法获取 docker daemon 进程的环境下也能够构建镜像,比如在标准的Kubernetes Cluster上。
Kaniko 以容器镜像的方式来运行的,同时需要三个参数: Dockerfile,上下文,以及远端镜像仓库的地址。
Kaniko会先提取基础镜像(Dockerfile FROM 之后的镜像)的文件系统,然后根据Dockerfile中所描述的,一条条执行命令,每一条命令执行完以后会在用户空间下面创建一个snapshot,并与存储与内存中的上一个状态进行比对,如果有变化,就将新的修改生成一个镜像层添加在基础镜像上,并且将相关的修改信息写入镜像元数据中。等所有命令执行完,kaniko会将最终镜像推送到指定的远端镜像仓库。
前置条件
- 标准的kubernetes集群 (e.g. using GKE)
- Kubernetes Secret
- 构建上下文目录(Context)
Kubernetes secret
要在 Kubernetes 集群中运行 kaniko,您需要一个标准的运行 Kubernetes 集群和一个 Kubernetes secret,其中包含推送最终映像所需的身份验证。推送至指定远端镜像仓库须要credential的支持,因此须要将credential以secret的方式挂载到/kaniko/.docker/这个目录下,文件名称为kaniko_config.json,内容以下:
{
"auths": {
"ghost.harbor.com": {
"auth": "YWRtaW46SGFyYm9yMTIzNDUK"
}
}
}
其中auth字段是认证信息,它是从用户名和密码通过base64编码而来:
echo "<harbor的登录用户名>:<harbor的密码>"|base64
运行创建secret
$ kubectl create secret generic kaniko-secret --from-file=kaniko_config.json secret/kaniko-secret created $ kubectl get secret kaniko-secret NAME TYPE DATA AGE kaniko-secret Opaque 1 23s
Pod示例
Kubernetes kaniko Pod 示例, args 参数按需修改:
apiVersion: v1
kind: Pod
metadata:
name: kaniko
spec:
containers:
- name: kaniko
image: gcr.io/kaniko-project/executor:latest
args:
- "--dockerfile=<指定Dockerfile>"
- "--context=<context 定义位置获取编排位置,即上下文>"
- "--destination=<远端镜像仓库>"
volumeMounts:
- name: kaniko-secret
mountPath: /secret
# 如果需要挂载dockerfile和必要的文件这里补充必要的挂载,跟下面volumes要对应就行
env:
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /secret/kaniko-secret.json
restartPolicy: Never
volumes:
- name: kaniko-secret
secret:
secretName: kaniko-secret # 这就是我们定义的harbor认证secret
# 如果需要挂载dockerfile和必要的文件这里补充必要的挂载
执行构建
$ kubectl -n kaniko apply -f kaniko.yaml pod/kaniko created $ kubectl -n kaniko get pods NAME READY STATUS RESTARTS AGE kaniko 1/1 Running 0 21s $ kubectl -n kaniko logs -f kaniko
浙公网安备 33010602011771号