<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>policy | 伪架构师</title>
    <link>/tags/policy/</link>
      <atom:link href="/tags/policy/index.xml" rel="self" type="application/rss+xml" />
    <description>policy</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>zh</language><lastBuildDate>Thu, 16 Jul 2020 08:35:47 +0800</lastBuildDate>
    <image>
      <url>/img/logo-wide.png</url>
      <title>policy</title>
      <link>/tags/policy/</link>
    </image>
    
    <item>
      <title>针对 Kubernetes 工作负载的策略工具</title>
      <link>/post/kubernetes-policies/</link>
      <pubDate>Thu, 16 Jul 2020 08:35:47 +0800</pubDate>
      <guid>/post/kubernetes-policies/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://learnk8s.io/kubernetes-policies&#34; target=&#34;_blank&#34;&gt;Enforcing policies and governance for Kubernetes workloads&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://echorand.me/&#34; target=&#34;_blank&#34;&gt;Amit Saha&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;本文将会讲述使用 &lt;a href=&#34;https://github.com/open-policy-agent/conftest&#34; target=&#34;_blank&#34;&gt;conftest&lt;/a&gt; 这样的静态工具以及 &lt;a href=&#34;https://github.com/open-policy-agent/gatekeeper&#34; target=&#34;_blank&#34;&gt;Gatekeeper&lt;/a&gt; 之类的集群内 Operator 为 Kubernetes 工作负载提供策略支持的方法。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;本文所讲的策略，指的是在 Kubernetes 中，阻止特定工作负载进行部署的方法。&lt;/p&gt;

&lt;p&gt;这种要求通常是出于合规的考虑，有一些最佳实践可以推荐给集群管理员：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;不要运行特权 Pod。&lt;/li&gt;
&lt;li&gt;不要用 root 运行 Pod。&lt;/li&gt;
&lt;li&gt;指定资源限制。&lt;/li&gt;
&lt;li&gt;不要使用 &lt;code&gt;latest&lt;/code&gt; 标签的镜像。&lt;/li&gt;
&lt;li&gt;限制 Linux capability 的使用。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;除去上述安全要求，可能还会有一些应用管理方面的需要，例如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;所有工作负载都应该有 &lt;code&gt;project&lt;/code&gt; 和 &lt;code&gt;app&lt;/code&gt; 标签。&lt;/li&gt;
&lt;li&gt;所有工作负载都应该从特定镜像库获取（例如 &lt;code&gt;my-company.com&lt;/code&gt;）。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最后还有一类需求，防止工作负载之间的冲突，例如多个服务不应使用同样的 Ingress 主机名。&lt;/p&gt;

&lt;p&gt;下面会分别讲述集群内外进行策略实施的方法。&lt;/p&gt;

&lt;p&gt;不符合策略规定的工作负载将被拒绝部署。&lt;/p&gt;

&lt;p&gt;集群外方式是通过对 YAML 文件进行静态检查之后，根据检查结果决定是否放行的。&lt;/p&gt;

&lt;p&gt;有&lt;a href=&#34;https://deploy-preview-317--learnk8s.netlify.app/validating-kubernetes-yaml&#34; target=&#34;_blank&#34;&gt;多种工具&lt;/a&gt;能够完成这一任务。&lt;/p&gt;

&lt;p&gt;集群内方式是使用 Validating admission controller，这些控制器会在工作负载进入数据库之前进行调用。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;本文所涉的代码可以在 &lt;a href=&#34;https://github.com/amitsaha/kubernetes-policy-enforcement-demo&#34; target=&#34;_blank&#34;&gt;github&lt;/a&gt; 找到。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;不合规的-deployment&#34;&gt;不合规的 Deployment&lt;/h2&gt;

&lt;p&gt;假设我们有这样一个 YAML：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: http-echo
  labels:
    app: http-echo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: http-echo
  template:
    metadata:
      labels:
        app: http-echo
    spec:
      containers:
      - name: http-echo
        image: hashicorp/http-echo
        args: [&amp;quot;-text&amp;quot;, &amp;quot;hello-world&amp;quot;]
        ports:
        - containerPort: 5678

      - name: http-echo-1
        image: hashicorp/http-echo:latest
        args: [&amp;quot;-text&amp;quot;, &amp;quot;hello-world&amp;quot;]
        ports:
        - containerPort: 5678
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的清单会生成一个 Pod，其中包含两个容器，这两个容器使用的是同一个镜像。&lt;/p&gt;

&lt;p&gt;第一个容器没有指定镜像标签，另外一个用的是 &lt;code&gt;latest&lt;/code&gt;，最终他们使用的都是 &lt;code&gt;hashicorp/http-echo&lt;/code&gt; 镜像的 &lt;code&gt;latest&lt;/code&gt; 版本。&lt;/p&gt;

&lt;p&gt;这不符合前面说的最佳实践，应该阻止这种工作负载在我们的集群上运行。正确的指定镜像的方式是填写精确的标签，例如 &lt;code&gt;hashicorp/http-echo:0.2.3&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;那么就看看如何使用静态分析的方式，制定策略来制止这种工作负载的部署。&lt;/p&gt;

&lt;p&gt;要阻止这种资源到达集群，可能要在如下位置嵌入这个分析过程：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git 的 &lt;code&gt;pre-commit&lt;/code&gt;，在进入 GIT 之前进行检查。&lt;/li&gt;
&lt;li&gt;作为 CI/CD Pipeline 的一部分，在 Git 分支合并到主线之前进行检查。&lt;/li&gt;
&lt;li&gt;作为 CI/CD Pipeline 的一部分，在资源被提交到集群之间进行检查。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;使用-conftest-实时策略&#34;&gt;使用 Conftest 实时策略&lt;/h2&gt;

&lt;p&gt;&lt;a href=&#34;https://conftest.dev/&#34; target=&#34;_blank&#34;&gt;Conftest&lt;/a&gt; 是一个针对配置文件的测试框架，能够用于对 Kubernetes 清单文件进行检查和校验。&lt;/p&gt;

&lt;p&gt;Conftest 的测试使用一种叫 &lt;a href=&#34;https://www.openpolicyagent.org/docs/latest/policy-language/&#34; target=&#34;_blank&#34;&gt;Rego&lt;/a&gt; 的 DSL 编写。&lt;/p&gt;

&lt;p&gt;可以根据项目网站上的&lt;a href=&#34;https://www.conftest.dev/install/&#34; target=&#34;_blank&#34;&gt;安装指导&lt;/a&gt;进行安装。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;目前的最新版本为 0.19.0。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;接下来定义两条策略：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-rego&#34;&gt;package main

deny[msg] {
  input.kind == &amp;quot;Deployment&amp;quot;
  image := input.spec.template.spec.containers[_].image
  not count(split(image, &amp;quot;:&amp;quot;)) == 2
  msg := sprintf(&amp;quot;image &#39;%v&#39; doesn&#39;t specify a valid tag&amp;quot;, [image])
}

deny[msg] {
  input.kind == &amp;quot;Deployment&amp;quot;
  image := input.spec.template.spec.containers[_].image
  endswith(image, &amp;quot;latest&amp;quot;)
  msg := sprintf(&amp;quot;image &#39;%v&#39; uses latest tag&amp;quot;, [image])
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;猜猜这两条策略有什么用？&lt;/p&gt;

&lt;p&gt;两个策略都是用在 &lt;code&gt;Deployment&lt;/code&gt; 对象上的，他们会从 &lt;code&gt;spec.container&lt;/code&gt; 字段中获取内容。&lt;/p&gt;

&lt;p&gt;第一条规则用于检查镜像是否带有标签：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-rego&#34;&gt;  not count(split(image, &amp;quot;:&amp;quot;)) == 2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;第二条规则会检查，标签是否为 &lt;code&gt;latest&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-rego&#34;&gt;  endswith(image, &amp;quot;latest&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果条件为真，那么 &lt;code&gt;deny&lt;/code&gt; 块就会被判为非法。&lt;/p&gt;

&lt;p&gt;如果代码中的 &lt;code&gt;deny&lt;/code&gt; 超过一个，conftest 会分别进行检查，如果任意一个 &lt;code&gt;deny&lt;/code&gt; 生效，都会做出违规判断。&lt;/p&gt;

&lt;p&gt;把这段代码保存为 &lt;code&gt;check_image_tag.rego&lt;/code&gt;，并运行 conftest 对 &lt;code&gt;deployment.yaml&lt;/code&gt; 进行检查：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ conftest test -p conftest-checks test-data/deployment.yaml
FAIL - test-data/deployment.yaml - image &#39;hashicorp/http-echo&#39; doesn&#39;t specify a valid tag
FAIL - test-data/deployment.yaml - image &#39;hashicorp/http-echo:latest&#39; uses latest tag

2 tests, 0 passed, 0 warnings, 2 failures
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;两个测试的结果都是失败。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;conftest&lt;/code&gt; 是静态的，需要在把 YAML 提交给集群之前进行检查。&lt;/p&gt;

&lt;p&gt;如果已经在使用 CICD 工具向集群提交 YAML，就需要新增一个步骤，使用 conftest 策略对所有资源进行校验。&lt;/p&gt;

&lt;p&gt;但是这样就可以阻止用户向集群提交使用 &lt;code&gt;latest&lt;/code&gt; 标签的 &lt;code&gt;Deployment&lt;/code&gt; 对象吗？&lt;/p&gt;

&lt;p&gt;当然了，所有具备权限的人，只要跳过 CICD 就可以用 &lt;code&gt;kubectl apply -f deployment.yaml&lt;/code&gt; 中在集群中创建这种违规对象。&lt;/p&gt;

&lt;p&gt;所以要在集群中部署动态检查来弥补这种不足——在非法对象被发送给集群之后拒绝。&lt;/p&gt;

&lt;h2 id=&#34;kubernetes-api&#34;&gt;Kubernetes API&lt;/h2&gt;

&lt;p&gt;回顾一下创建下面 Pod 的过程：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
  - name: sise
    image: learnk8s/app:1.0.0
    ports:
    - containerPort: 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;kubectl apply -f pod.yaml&lt;/code&gt; 执行之后，对象定义被发送给 API Server：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;YAML 保存到 ETCD。&lt;/li&gt;
&lt;li&gt;调度器把 Pod 分配给 Node。&lt;/li&gt;
&lt;li&gt;Kubelet 收到 Pod 定义，并创建对象。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;只有这么一点么？&lt;/p&gt;

&lt;p&gt;如果 YAML 有拼写错误怎么办？&lt;/p&gt;

&lt;p&gt;如何阻止无效的 YAML 进入 ETCD？&lt;/p&gt;

&lt;p&gt;在 &lt;code&gt;kubectl apply&lt;/code&gt; 时候，首先是 Kubelet 做了一些事：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;在客户端对资源定义进行检查。&lt;/li&gt;
&lt;li&gt;把 YAML 转换为 JSON。&lt;/li&gt;
&lt;li&gt;从 &lt;code&gt;KUBECONFIG&lt;/code&gt; 读入配置。&lt;/li&gt;
&lt;li&gt;把对象报文发送给 &lt;code&gt;kube-apiserver&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;api-server&lt;/code&gt; 收到请求之后，也不会立即存入 etcd。&lt;/p&gt;

&lt;p&gt;首先他要检查请求者身份是否合法，也就是进行&lt;a href=&#34;https://kubernetes.io/docs/reference/access-authn-authz/authentication/&#34; target=&#34;_blank&#34;&gt;认证&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;通过认证之后，还要判断该用户是否有权创建资源？&lt;/p&gt;

&lt;p&gt;身份和权限不能混为一谈，能访问集群不代表能够读写所有对象。鉴权过程通常是使用 &lt;a href=&#34;https://kubernetes.io/docs/reference/access-authn-authz/rbac/&#34; target=&#34;_blank&#34;&gt;RBAC&lt;/a&gt; 机制实现。&lt;/p&gt;

&lt;p&gt;有了 RBAC，就可以通过适当的授权来限制用户的能力了。&lt;/p&gt;

&lt;p&gt;假设用户已经能够通过认证并且具备所需权限，是不是就能够把 Pod 定义保存到 Pod 之中了？并不是。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;api-server&lt;/code&gt; 自身也是一个 Pipeline。&lt;/p&gt;

&lt;p&gt;请求报文在保存到数据库之前，还要经过几个组件。认证和授权就是这些组件的一部分，还有其他组件。&lt;/p&gt;

&lt;p&gt;在对象进入数据库之前，首先会由 &lt;a href=&#34;https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/&#34; target=&#34;_blank&#34;&gt;Admission Controller&lt;/a&gt; 进行处理。&lt;/p&gt;

&lt;p&gt;这个步骤中，就有机会对当前资源进行更多检查。Kubernetes 缺省启用了几个 Admission Controller：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;kube-apiserver&lt;/code&gt; 的 &lt;code&gt;--enable-admission-plugins&lt;/code&gt; 中可以看到启用的项目。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;下面用 &lt;code&gt;NamespaceLifecycle&lt;/code&gt; 为例来看看 Admission Controller 的行为。&lt;/p&gt;

&lt;h2 id=&#34;validating-admission-controllers&#34;&gt;Validating admission controllers&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;NamespaceLifecycle&lt;/code&gt; 会阻止用户在不存在的 Namespace 中创建 Pod，例如下面的定义：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: my-pod
  namespace: does-not-exist
spec:
  containers:
  - name: sise
    image: learnk8s/app:1.0.0
    ports:
    - containerPort: 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;YAML 结构是有效的，所以它能通过 &lt;code&gt;kubectl&lt;/code&gt; 的校验，提交给集群。&lt;/p&gt;

&lt;p&gt;假设用户通过了认证和鉴权，这个请求就会进入 &lt;code&gt;NamespaceLifecycle&lt;/code&gt;。&lt;code&gt;does-not-exist&lt;/code&gt; 命名空间并不存在，请求被拒绝。&lt;/p&gt;

&lt;p&gt;另外 &lt;code&gt;NamespaceLifecycle&lt;/code&gt; 还会阻止删除 &lt;code&gt;default&lt;/code&gt;、&lt;code&gt;kube-system&lt;/code&gt; 和 &lt;code&gt;kube-public&lt;/code&gt; 命名空间的请求。&lt;/p&gt;

&lt;p&gt;用于对请求进行校验的控制器被集中在 &lt;code&gt;Validating&lt;/code&gt; 分类之中。&lt;/p&gt;

&lt;p&gt;除此之外还有另外一个分类，被称为 &lt;code&gt;Mutating&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&#34;mutating-admission-controllers&#34;&gt;Mutating admission controllers&lt;/h2&gt;

&lt;p&gt;从名字就看得出，Mutating Controller 控制器能够对请求报文做出变更。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DefaultStorageClass&lt;/code&gt; 就是一个很好的例子。&lt;/p&gt;

&lt;p&gt;假设要创建一个 PVC 对象：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个对象提交之后，如果使用 &lt;code&gt;kubectl get pvc&lt;/code&gt;，会发现存储卷状态为 &lt;code&gt;Bound&lt;/code&gt; 并且被赋予了一个 &lt;code&gt;standard&lt;/code&gt; 的 &lt;code&gt;StorageClass&lt;/code&gt;。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;NAME     STATUS   VOLUME         CAPACITY   ACCESS MODES   STORAGECLASS   AGE
my-pvc   Bound    pvc-059f2da2   3Gi        RWO            standard       3s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;很明显我们的 YAML 中并没有这些定义。用 &lt;code&gt;kubectl get pvc my-pvc -0 yaml&lt;/code&gt; 查看一下 YAML，会看到如下内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi
  storageClassName: standard
  volumeMode: Filesystem
  volumeName: pvc-059f2da2-a216-42b7-875e-e7da327605dd
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;多出了一行 &lt;code&gt;storageClassName: standard&lt;/code&gt;。这里的 &lt;code&gt;standard&lt;/code&gt; 并不是 API 中硬编码的，而是把缺省 &lt;code&gt;StorageClass&lt;/code&gt; 的名字注入到 &lt;code&gt;spec.storageClassName&lt;/code&gt; 之中。&lt;/p&gt;

&lt;p&gt;可以用命令读取缺省 &lt;code&gt;StorageClass&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get storageclass
NAME                 PROVISIONER                RECLAIMPOLICY   VOLUMEBINDINGMODE   AGE
standard (default)   k8s.io/minikube-hostpath   Delete          Immediate           8m
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果缺省 &lt;code&gt;StorageClass&lt;/code&gt; 名称为 &lt;code&gt;aws-ebs&lt;/code&gt;，&lt;code&gt;DefaultStorageClass&lt;/code&gt; Admission Controller 会把它注入到之前 &lt;code&gt;standard&lt;/code&gt; 所在的位置。&lt;/p&gt;

&lt;p&gt;Kubernetes 带有多个 Mutating 和 Validating Admission Controller，官方网站上有&lt;a href=&#34;https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/&#34; target=&#34;_blank&#34;&gt;完整的列表&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;请求在经过这些关卡之后，才能保存到数据库。&lt;/p&gt;

&lt;p&gt;如果想要自定义检查或变更过程，如何能加入自己的规则呢？&lt;/p&gt;

&lt;h3 id=&#34;admission-controller-的扩展性&#34;&gt;Admission Controller 的扩展性&lt;/h3&gt;

&lt;p&gt;除了 Kubernetes 内置之外，他也可以使用自己的 Admission Controller。&lt;/p&gt;

&lt;p&gt;有两个可编程的点：&lt;code&gt;MutationAdmissionWebhook&lt;/code&gt; 和 &lt;code&gt;ValidationAdmissionWebhook&lt;/code&gt;。可以在这两个 Webhook 上注册自己的组件，这样在 Admission 阶段就可以使用自定义的组件来处理对象了。&lt;/p&gt;

&lt;p&gt;因此可以编写一个组件，来检查当前 Pod 是否使用了来自特定私有镜像库的镜像。&lt;/p&gt;

&lt;p&gt;把这个组件注册到 &lt;code&gt;ValidationAdmissionWebhook&lt;/code&gt;，来对容器定义做出放行或阻拦的决策。&lt;/p&gt;

&lt;p&gt;这就是 Gatekeeper 的用途——它可以被注册到集群，对请求信息进行校验。&lt;/p&gt;

&lt;h2 id=&#34;用-gatekeeper-实施策略&#34;&gt;用 Gatekeeper 实施策略&lt;/h2&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/open-policy-agent/gatekeeper&#34; target=&#34;_blank&#34;&gt;Gatekeeper&lt;/a&gt; 让 Kubernetes 管理员可以定义策略来保证集群的合规性，并符合最佳实践的要求。&lt;/p&gt;

&lt;p&gt;Gatekeeper 会将自己注册到 Validation Webhook。&lt;/p&gt;

&lt;h3 id=&#34;提交到集群的任何资源都会被活动的策略进行检查&#34;&gt;提交到集群的任何资源都会被活动的策略进行检查。&lt;/h3&gt;

&lt;p&gt;同时 Gatekeeper 也符合 Kubernetes 的架构建议，使用 CRD 来管理策略，因此它的策略也是 Kubernetes 资源。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://cloud.google.com/anthos-config-management/docs/concepts/policy-controller&#34; target=&#34;_blank&#34;&gt;Google 云文档&lt;/a&gt;在这方面有很精彩的阐述。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;从内部看，Gatekeeper 使用 Open Policy Agent(OPA) 作为核心的策略引擎，策略使用 Rego 语言编写——和 &lt;code&gt;conftest&lt;/code&gt; 一样。&lt;/p&gt;

&lt;p&gt;在下面的内容里，会尝试使用 Gatekeeper。此处要求用户使用管理用户操作 Kubernetes，使用下面的指令部署 Gatekeeper：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;kubectl apply -f \
https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;检查运行情况：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl -n gatekeeper-system describe svc gatekeeper-webhook-service
Name:              gatekeeper-webhook-service
Namespace:         gatekeeper-system
Labels:            gatekeeper.sh/system=yes
Annotations:       ...
Type:              ClusterIP
IP:                10.102.199.165
Port:              &amp;lt;unset&amp;gt;  443/TCP
TargetPort:        8443/TCP
Endpoints:         172.18.0.4:8443
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个服务就是用于验证的的。所有的 Pod、Deployment、Service 等，都要受到 Gatekeeper 的监管&lt;/p&gt;

&lt;h3 id=&#34;使用-contstrainttemplate-定义可复用的策略&#34;&gt;使用 ContstraintTemplate 定义可复用的策略&lt;/h3&gt;

&lt;p&gt;在 Gatekeeper 中，首先需要使用 &lt;code&gt;ContstraintTemplate&lt;/code&gt; 创建策略。&lt;/p&gt;

&lt;p&gt;下面的 &lt;code&gt;ContstraintTemplate&lt;/code&gt; 定义会拒绝使用 &lt;code&gt;latest&lt;/code&gt; 标签的镜像。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8simagetagvalid
spec:
  crd:
    spec:
      names:
        kind: K8sImageTagValid
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8simagetagvalid

        violation[{&amp;quot;msg&amp;quot;: msg, &amp;quot;details&amp;quot;:{}}] {
          image := input.review.object.spec.template.spec.containers[_].image
          not count(split(image, &amp;quot;:&amp;quot;)) == 2
          msg := sprintf(&amp;quot;image &#39;%v&#39; doesn&#39;t specify a valid tag&amp;quot;, [image])
        }

        violation[{&amp;quot;msg&amp;quot;: msg, &amp;quot;details&amp;quot;:{}}] {
          image := input.review.object.spec.template.spec.containers[_].image
          endswith(image, &amp;quot;latest&amp;quot;)
          msg := sprintf(&amp;quot;image &#39;%v&#39; uses latest tag&amp;quot;, [image])
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个策略和前面 &lt;code&gt;conftest&lt;/code&gt; 的策略类似，但是也有些区别。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;输入对象名称为 &lt;code&gt;input.review.object&lt;/code&gt;，而不是 &lt;code&gt;input&lt;/code&gt;，这里也无需检查输入对象的 &lt;code&gt;kind&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;deny&lt;/code&gt; 规则改为 &lt;code&gt;violation&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;violation&lt;/code&gt; 块的签名是一个包含两个属性的对象。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;第一个是字符串类型的 &lt;code&gt;msg&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;第二个是 &lt;code&gt;details&lt;/code&gt; 对象，其中可以包含任意属性。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这两个属性都会用作返回值。&lt;/p&gt;

&lt;p&gt;接下来用 &lt;code&gt;kubectl&lt;/code&gt; 把这个定义提交到集群。然后就可以使用 &lt;code&gt;describe&lt;/code&gt; 命令查询模板情况：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl apply -f templates/check_image_tag.yaml
constrainttemplate.templates.gatekeeper.sh/k8simagetagvalid created

$ kubectl describe constrainttemplate.templates.gatekeeper.sh/k8simagetagvalid
Name:         k8simagetagvalid
Namespace:
Labels:       &amp;lt;none&amp;gt;
Annotations:  kubectl.kubernetes.io/last-applied-configuration:
                {&amp;quot;apiVersion&amp;quot;:&amp;quot;templates.gatekeeper.sh/v1beta1&amp;quot;,&amp;quot;kind&amp;quot;:&amp;quot;ConstraintTemplate&amp;quot;,&amp;quot;metadata&amp;quot;:
                {&amp;quot;annotations&amp;quot;:{},&amp;quot;name&amp;quot;:&amp;quot;k8simagetagvalid&amp;quot;},&amp;quot;spec&amp;quot;...
API Version:  templates.gatekeeper.sh/v1beta1
Kind:         ConstraintTemplate
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个对象并不能直接用于进行校验。它只是一个策略定义，要使用这个策略，还要创建一个 &lt;code&gt;Constraint&lt;/code&gt;。&lt;/p&gt;

&lt;h3 id=&#34;创建一个-constraint&#34;&gt;创建一个 Constraint&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Constraint&lt;/code&gt; 对象的含义是“在集群中使用这个策略”。&lt;/p&gt;

&lt;p&gt;可以把 &lt;code&gt;ConstraintTemplates&lt;/code&gt; 当做一本菜谱，其中包含虽然包含几百个菜式，但是菜谱本身是无法食用的。必须选择菜谱并按照菜谱要求提供相应的材料，进行合适的操作，才能烤出蛋糕。&lt;/p&gt;

&lt;p&gt;下面举个例子，用前面的 &lt;code&gt;K8sImageTagValid&lt;/code&gt; 创建一个 &lt;code&gt;Contraint&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sImageTagValid
metadata:
  name: valid-image-tag
spec:
  match:
    kinds:
      - apiGroups: [&amp;quot;apps&amp;quot;]
        kinds: [&amp;quot;Deployment&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个 &lt;code&gt;Constraint&lt;/code&gt; 引用了 &lt;code&gt;ConstraintTemplate&lt;/code&gt;，并用 &lt;code&gt;spec.match&lt;/code&gt; 字段规定了适用的资源类型。这里我们要求针对 &lt;code&gt;api&lt;/code&gt; 组下的 &lt;code&gt;Deployment&lt;/code&gt; 对象进行检查。&lt;/p&gt;

&lt;p&gt;这些字段是数组类型的，因此可以指定多个值，把检查范围扩展到 &lt;code&gt;StatefulSet&lt;/code&gt;、&lt;code&gt;DaemonSet&lt;/code&gt; 等。&lt;/p&gt;

&lt;p&gt;用 &lt;code&gt;kubectl apply&lt;/code&gt; 提交这个对象。&lt;/p&gt;

&lt;h3 id=&#34;测试策略&#34;&gt;测试策略&lt;/h3&gt;

&lt;p&gt;用带有两个镜像的 Deployment 进行测试：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl apply -f deployment.yaml
Error from server ([denied by valid-image-tag] image &#39;hashicorp/http-echo&#39; doesn&#39;t specify a valid tag
[denied by valid-image-tag] image &#39;hashicorp/http-echo:latest&#39; uses latest tag): error when creating
&amp;quot;test-data/deployment.yaml&amp;quot;: admission webhook &amp;quot;validation.gatekeeper.sh&amp;quot; denied the request:
[denied by valid-image-tag] image &#39;hashicorp/http-echo&#39; doesn&#39;t specify a valid tag
[denied by valid-image-tag] image &#39;hashicorp/http-echo:latest&#39; uses latest tag
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Gatekeeper 拒绝了输入内容，可以看出这个过程是不能跳过的。&lt;/p&gt;

&lt;p&gt;如果集群中正在运行工作负载，此时实施 Gatekeeper 策略可能会很有难度——这有因为合规问题导致业务中断的风险。&lt;/p&gt;

&lt;p&gt;Gatekeeper 还允许使用 &lt;code&gt;dry-run&lt;/code&gt; 模式运行 &lt;code&gt;Constraint&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sImageTagValid
metadata:
  name: valid-image-tag
spec:
  enforcementAction: dryrun
  match:
    kinds:
      - apiGroups: [&amp;quot;apps&amp;quot;]
        kinds: [&amp;quot;Deployment&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个模式中，策略不会阻止工作负载的部署，但是会在对象的 &lt;code&gt;Violation&lt;/code&gt; 字段中记录违规行为：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl describe k8simagetagvalid.constraints.gatekeeper.sh/valid-image-tag
Name:         valid-image-tag
Namespace:
Labels:       &amp;lt;none&amp;gt;
Annotations:  kubectl.kubernetes.io/last-applied-configuration:
....

  Total Violations:  2
  Violations:
    Enforcement Action:  dryrun
    Kind:                Deployment
    Message:             image &#39;hashicorp/http-echo&#39; doesn&#39;t specify a valid tag
    Name:                http-echo
    Namespace:           default
    Enforcement Action:  dryrun
    Kind:                Deployment
    Message:             image &#39;hashicorp/http-echo:latest&#39; uses latest tag
    Name:                http-echo
    Namespace:           default
Events:                  &amp;lt;none&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在确保所有工作负载都合规之后，就可以移除 &lt;code&gt;dry-run&lt;/code&gt;，正式启用策略了。&lt;/p&gt;

&lt;h3 id=&#34;标签检查&#34;&gt;标签检查&lt;/h3&gt;

&lt;p&gt;这个例子会检查 Deployment 对象，要求必须包含 &lt;code&gt;project&lt;/code&gt; 和 &lt;code&gt;app&lt;/code&gt; 两个标签。&lt;/p&gt;

&lt;p&gt;如果用 &lt;code&gt;conftest&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-rego&#34;&gt;package main

deny[msg] {
  input.kind == &amp;quot;Deployment&amp;quot;

  required := {&amp;quot;app&amp;quot;, &amp;quot;project&amp;quot;}
  provided := {label | input.metadata.labels[label]}
  missing := required - provided

  count(missing) &amp;gt; 0
  msg = sprintf(&amp;quot;you must provide labels: %v&amp;quot;, [missing])
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面代码中：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;required&lt;/code&gt; 是一个集合，其中包含了 &lt;code&gt;app&lt;/code&gt; 和 &lt;code&gt;project&lt;/code&gt; 两个元素。我们希望每个 &lt;code&gt;Deployment&lt;/code&gt; 都包含这两个标签。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;provided&lt;/code&gt; 会从输入中读取当前对象的标签。&lt;/li&gt;
&lt;li&gt;两个集合相减，得到缺失的标签的集合，赋值给 &lt;code&gt;missing&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;count()&lt;/code&gt; 函数获取 &lt;code&gt;missing&lt;/code&gt; 集合的元素数量，如果大于零，代表该输入不合规。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;测试一下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ conftest test -p conftest-checks/check_labels.rego test-data/deployment.yaml
FAIL - test-data/deployment.yaml - you must provide labels: {&amp;quot;project&amp;quot;}
1 test, 0 passed, 0 warnings, 1 failure
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在 YAML 中加入要求的标签，才能通过测试。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: http-echo
  labels:
    app: http-echo
    project: test
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;前面提到，这样的不合规对象还是可以提交给集群的，因此还是需要 Gatekeeper 来在集群之中使用策略。&lt;/p&gt;

&lt;p&gt;首先创建一个 &lt;code&gt;ConstraintTemplate&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8srequiredlabels
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredLabels
      validation:
        # Schema for the `parameters` field
        openAPIV3Schema:
          properties:
            labels:
              type: array
              items: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiredlabels

        violation[{&amp;quot;msg&amp;quot;: msg, &amp;quot;details&amp;quot;: {&amp;quot;missing_labels&amp;quot;: missing}}] {
          provided := {label | input.review.object.metadata.labels[label]}
          required := {label | label := input.parameters.labels[_]}
          missing := required - provided
          count(missing) &amp;gt; 0
          msg := sprintf(&amp;quot;you must provide labels: %v&amp;quot;, [missing])
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上述代码演示了如何从输入中抓取参数。&lt;/p&gt;

&lt;p&gt;并且这里使用 &lt;code&gt;openAPIV3Schema&lt;/code&gt; 对输入进行过滤，这一节代码表示要求输入对象有一个参数 &lt;code&gt;label&lt;/code&gt;，其数据类型为字符串数组。所有输入都通过 &lt;code&gt;input.parameters&lt;/code&gt; 属性传递给 &lt;code&gt;constraint&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;模板提交以后，就可以据此创建 &lt;code&gt;Contstraint&lt;/code&gt; 了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: deployment-must-have-labels
spec:
  match:
    kinds:
      - apiGroups: [&amp;quot;apps&amp;quot;]
        kinds: [&amp;quot;Deployment&amp;quot;]
  parameters:
    labels: [&amp;quot;app&amp;quot;, &amp;quot;project&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个对象提交以后，集群中就有了校验镜像和校验标签的两个 &lt;code&gt;Constraint&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;再次创建前面的 Deployment：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl apply -f deployment.yaml
Error from server ([denied by deployment-must-have-labels] you must provide labels: {&amp;quot;project&amp;quot;}
[denied by valid-image-tag] image &#39;hashicorp/http-echo&#39; doesn&#39;t specify a valid tag
[denied by valid-image-tag] image &#39;hashicorp/http-echo:latest&#39; uses latest tag): error when creating
&amp;quot;deployment.yaml&amp;quot;: admission webhook &amp;quot;validation.gatekeeper.sh&amp;quot; denied the request:
[denied by deployment-must-have-labels] you must provide labels: {&amp;quot;project&amp;quot;}
[denied by valid-image-tag] image &#39;hashicorp/http-echo&#39; doesn&#39;t specify a valid tag
[denied by valid-image-tag] image &#39;hashicorp/http-echo:latest&#39; uses latest tag
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;意料之中的创建失败。只有修改 YAML，合规之后才能通过。&lt;/p&gt;

&lt;h2 id=&#34;总结&#34;&gt;总结&lt;/h2&gt;

&lt;p&gt;Conftest 和 Gatekeeper 都是用 Rego 语言定义策略的，这两个工具结合起来就能覆盖集群内外的校验要求了。&lt;/p&gt;

&lt;p&gt;正如你所见，Conftest 的 Rego 策略需要做点修改才能用在 Gatekeeper 里。&lt;a href=&#34;https://github.com/plexsystems/konstraint&#34; target=&#34;_blank&#34;&gt;Konstraint&lt;/a&gt; 项目可以解决这个问题。该工具能把 &lt;code&gt;Conftest&lt;/code&gt; 策略转换为 Gatekeeper 的 &lt;code&gt;ConstraintTemplate&lt;/code&gt; 和 &lt;code&gt;Constraint&lt;/code&gt;，并且它还能方便地对策略进行测试。&lt;/p&gt;

&lt;p&gt;除了本文的两个工具之外，集群内外的策略检查都还有别的选择。它们的主要优势就是使用了通用的 Rego 语言。例如 &lt;a href=&#34;https://github.com/FairwindsOps/polaris&#34; target=&#34;_blank&#34;&gt;Polaris&lt;/a&gt; 同时提供了集群内外的校验功能。然而它使用的是基于 JSON 的策略描述方法，其表达能力远弱于 Rego。&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
