Featured image of post k8s环境下如何优雅地构建镜像

k8s环境下如何优雅地构建镜像

前言

随着各企业上云进程的加速,用容器的方式来交付软件产品也变得越来越普遍,如何以更安全的方式来构建容器镜像,也就成了大家关注的话题。 docker 构建镜像

docker是最近几年非常火热的容器技术,用docker来构建容器镜像也是常用的方法,在具备构建容器镜像所需的两个要素(Dockerfile & 上下文)的前提下,用下述命令就能构建一个容器镜像出来

1
$ docker build -t your_registry/your_repository:tag .

然后用 docker push 将镜像推送到镜像仓库

1
$ docker push your_registry/your_repository:tag

现在DevOps 的CI/CD环境大多数都会运行在容器内,镜像的构成也是在容器内完成的。这时候,通常用以下两种方式来完成容器内构建镜像的工作:

挂载宿主机的socket文件

1
$ docker run -it -v /var/run/docker.sock:/var/run/docker.sock docker

然后在容器内部用 docker build 构建镜像

1
$ docker build -f <dockerfile> -t test/test:v0.1 .

由于docker依赖于 docker daemon 进程, docker daemon 进程是一个 Unixsocket 连接,且 /var/run/docker.sock 文件是root权限,

1
2
$ 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端开启远程调用

1
2
3
4
cat /etc/docker/daemon.json
{
  "hosts": ["tcp://0.0.0.0:2375","unix:///var/run/docker.sock"]
}

可以在容器里面使用docker client根据dockerfile远程调用docker socket执行构建

1
2
3
4
$ cat dockerfile
FROM busybox

CMD ["echo","hello world !"]
1
$ docker -H <宿主机ip>:2375 build -f <dockerfile> .

 

dind(docker-in-docker)

这种方式不需要挂载宿主机的socket文件,但是需要以 –privileged 权限来以dind镜像创建一个容器:

1
$ 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,内容以下:

1
2
3
4
5
6
7
{   
    "auths": {
        "ghost.harbor.com": {
            "auth": "YWRtaW46SGFyYm9yMTIzNDUK"
       }
    }
}

其中auth字段是认证信息,它是从用户名和密码通过base64编码而来:

1
echo "<harbor的登录用户名>:<harbor的密码>"|base64

运行创建secret

1
2
3
4
5
6
$ 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 参数按需修改:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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和必要的文件这里补充必要的挂载

执行构建

1
2
3
4
5
6
7
8
$ 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