
实现一个容器镜像白名单的准入控制器 | 视频文字稿
前面我们已经介绍了准入控制器的含义,了解可以通过有两个特殊的“动态”控制器 -ValidatingAdmissionWebhook 和 MutatingAdmissionWebhook 来让开发者自行实现自己的准入逻辑。这两个控制器没有实现任何固定逻辑,相反,它们使我们能够在每次在集群中创建、更新或删除Kubernetes 资源时通过 webhooks 灵活地实现和执行自定义逻辑。
示例
接下来我们将构建一个准入控制器示例,只允许使用来自白名单镜像仓库的资源创建 Pod,拒绝使用不受信任的镜像仓库中进行拉取镜像。
比如我们这里只允许使用来自 docker.io 或者 gcr.io 镜像仓库的镜像创建 Pod,其他不受信任的镜像创建的 Pod 将会被拒绝。
要实现这个需求,我们就需要构建一个 ValidatingAdmissionWebhook,并将其注册到 APIServer。在编写这个 Webhook 之前我们就需要先链接通过注册的 Webhook 从 APIServer 接收到的请求的结构,以及我们对 APIServer 的响应结构。
APIServer 实际上使用的是一个 AdmissionReview 类型的对象来向我们自定义的 Webhook 发送请求和接收响应。
对于每个请求,在 AdmissionReview 结构体内部都有一个 AdmissionRequest 类型的属性,该属性中封装了发送到 APIServer 的原始请求数据,我们主要关心的就是该对象内部包含的正在创建/更新或删除的 Kubernetes 对象(比如 Pod、Deployment 等) JSON payload 数据。下面是用于验证准入控制器的 AdmissionReview 请求对象示例:
对于验证准入控制器,我们的应用程序必须接收一个 AdmissionReview 对象,对其进行处理来决定是否允许/不允许该请求,并通过在 AdmissionReview 结构中填充一个类型为 AdmissionResponse 的 response 属性来返回我们的验证结果。在 response 中,我们使用一个名为 allowed 的布尔类型来表示是否允许/不允许,我们还可以选择包含一个 HTTP 状态码和一条 message 消息,将其传递回客户端。下面是用于验证准入控制器的 AdmissionReview 响应对象示例:
如果我们要构建一个 Mutating 准入控制器,我们将使用一个 JSONPatch 类型的对象作为 AdmissionReview 响应的 response 属性的一部分发送回变更的结果,原始请求将使用此JSON Patch 进行修改。下面是用于 Mutating 准入控制器的 AdmissionReview 响应对象示例:
关于 AdmissionReview 的完整结构定义可以查看文档:https://github.com/kubernetes/api/blob/master/admission/v1/types.go。
逻辑实现
这里我们要实现的是一个简单的带 TLS 认证的 HTTP 服务,用 Deployment 方式部署在我们的集群中。
首先新建项目:
然后在根目录下面新建一个 main.go 的入口文件,在该文件中定义 webhook server 的入口点,代码如下所示:
通过 flag 来获取传递的命令行参数,比如 TLS 证书,镜像仓库白名单等。然后使用标准库 http 来定义服务,通过一个 WebhookServer 结构体进行了简单的封装,虽然我们这里主要是实现 validate 校验功能,为了扩展支持 muate,这里我们分别定义两个端点来进行支持:
所以这里最重要的就是 serve 函数了,用来处理传入的 mutate 和 validating 函数的 HTTP 请求。该函数从请求中反序列化 AdmissionReview 对象,执行一些基本的内容校验,根据 URL 路径调用相应的 mutate 和 validate 函数,然后序列化 AdmissionReview 对象:
在上面的 serve 函数中会根据传入的 PATH 来决定调用的逻辑,这里我们主要是实现校验的功能,所以主要是实现 validate 函数的逻辑:
代码实现逻辑也很简单的,就是拿着传入的对象 Pod,循环里面的镜像,判断这些镜像是否都是白名单列表中的镜像,如果是则校验通过,否则校验失败,返回 allowed=false。
部署
证书
上面我们实现了最基本的业务逻辑,由于 webhook 要求是通过 HTTPS 暴露服务,所以我们还需要为其生成相关的证书。为了方便这里我们可以使用 cfssl 来生成相关证书。
安装 cfssl:
然后创建 CA 证书机构,执行下面的命令:
然后使用下面的命令生成 CA 证书和私钥:
然后接下来就可以创建 Server 端证书了:
其中最重要的就是 -hostname 的值,格式为 {service-name}.{service-namespace}.svc,其中 service-name 代表你 webhook 的 Service 名字,service-namespace 代表你 webhook 的命名空间。
然后使用生成的 server 证书和私钥创建一个 Secret 对象:
后面我们通过 Volumes 的形式将 Secret 挂载到 webhook 的容器中指定的位置给 webhook 使用即可。
Docker 镜像
然后接下来我们只需要将 webhook 打包成 Docker 镜像,并使用一个 Deployment 来运行这个容器应用即可,对应的 Dockerfile 文件如下所示:
这里我们使用了 Docker 的多阶段构建功能,先将项目构建打包成二进制文件,然后在 distrolesss 中运行该应用,执行项目的命令构建推送镜像即可:
部署 webhook
现在 webhook 的镜像已经准备好了,接下来我们就需要将其部署到 Kubernetes 集群中,这里我们使用 Deployment + Service 来提供服务即可,在 Pod 的规范中配置环境变量 WHITELIST_REGISTRIES 来定义白名单镜像仓库地址,然后将证书通过 Secret 的 Volumes 形式挂载到 Pod 容器中,对应的资源清单文件如下所示:
直接创建上面的资源清单即可:
注册 webhook
上面我们只是单纯将我们实现的 webhook 部署到了 Kubernetes 集群中,但是还并没有和 ValidatingWebhook 对接起来,要将我们上面实现的服务注册到 ValidatingWebhook 中只需要创建一个类型为 ValidatingWebhookConfiguration 的 Kubernetes 资源对象即可,在这个对象中就可以来配置我们的 webhook 这个服务。
如下所示,我们将 webhook 命名为 io.ydzs.admission-registry ,只需要保证在集群中名称唯一即可。然后在 rules 属性下面就是来指定在什么条件下使用该 webhook 的配置,这里我们只需要在创建 Pod 的时候才调用这个 webhook。此外在 ClientConfig 属性下我们还需要指定 Kubernetes APIServer 如何来找到我们的 webhook 服务,这里我们将通过一个在 default 命名空间下面的名为 admission-registry 的 Service 服务在 /validate 路径下面提供服务,此外还指定了一个 caBundle 的属性,这个属性通过指定一个 PEM 格式的 CA bundle 来表示 APIServer 作为客户端可以使用它来验证我们的 webhook 应用上的服务器证书。对应的注册 webhook 的资源清单如下所示:
上面的 CA_BUNDLE 值使用的是上面生成 ca.crt 文件内容的 base64 值:
然后将得到的值替换掉 validatingwebhook.yaml 文件中的 CA_BUNDLE ,然后就可以直接部署到集群中:
到这里我们的镜像白名单校验的 webhook 就部署完成了。
测试
接下来我们来测试下上面我们 webhook 是否生效了。首先创建一个如下所示的测试 Pod 清单:
由于 docker.io 是我们的镜像白名单,所以正常上面的应用是可以正常创建的:
然后创建另外一个 Pod,这次我们使用一个 ydzs.io 的镜像仓库的镜像:
由于 ydzs.io 并不在我们的镜像白名单中,所以正常部署后会被拒绝:
可以看到上面的 Pod 部署失败了,因为不在镜像白名单中,证明我们的校验准入控制器逻辑是正确的。
清理
要移除这个校验准入控制器比较简单,只需要移除上面的几个资源对象即可:
总结这里我们只是通过一个简单的示例来说明我们应该如何去开发一个校验的准入控制器,对于 Mutate 类型的控制器实现方式也基本一致。当然如果我们只是简单的想现在下镜像仓库,我们也可以不需要自己去编写代码来实现,毕竟这样效率并不是很高,我们可以通过 Open Policy Agent Gatekeeper 项目来实现,它提供了一种通过策略配置而不是编写代码来实现类似用例的方法。
本文转载自微信公众号「k8s技术圈」
原文链接:https://mp.weixin.qq.com/s/pzn2Nz-KNgcINNnGn9JGew.
