学习如何使用OPA Gatekeeper在Kubernetes集群中编写并执行策略,以确保环境的安全和资源的有效管理。
有了像 Kubernetes 这样的工具,微服务环境的日常工作流程管理和自动化来说变得更加容易。
在 Kubernetes 中使用策略将帮助您获得最大的控制权和灵活性,例如:
读了这篇文章,你会学到
开放策略代理 (OPA) 是什么?
Open Policy Agent (OPA) 帮助我们来使用专为编写策略设计的声明性语言Rego编写策略。
通过OPA,我们可以定义并强制执行从Kubernetes到底层的微服务等各个层级的堆栈的策略。
这种方法有助于提升在管理 Kubernetes 集群中的策略时的一致性、可扩展性和敏捷性。
此外,通过使用表达力强的语法,您可以高效地表示组织做出的访问控制规则和复杂政策决策。
这样一来,这在很大程度上保证了你的 K8s 环境符合规范且安全。
本文的重点在于编写 Kubernetes 配置中的配置策略。
如果你不熟悉OPA,你可以快速了解它的工作方式和实现细节:这篇文章
在这部分,我们来详细了解一下如何让 OPA 运行起来吧。
还不错,OPA 对 Kubernetes 的支持也很好,正如其文档中所述,所以我们可以看看如何将其集成到您的 Kubernetes 环境中。
但首先,让我们来谈谈一些重要的组件,并理解它背后的运作方式。
Kubernetes 自带一个叫做准入控制器的组件,它就像 Kubernetes API 和每个请求之间的中间人,用于处理这些请求。
准入控制器可以实施策略并确保安全措施得以执行,确保只有经过授权且配置正确的工作负载才能进入集群。
他们可能修改请求中的细节,以确保请求格式正确且可接受,然后再进行处理。
所以,准入控制器可以分为两种类型:mutating(突变型)或 validating(验证型)。理解这一点非常重要,因为这就是 Open Policy Agent (OPA) 发挥作用的地方。
为什么我们需要在集群中使用准入控制器,官方 Kubernetes 文档 如下解释:
“…一个没有正确配置准入控制器的Kubernetes API服务器来说,是不完整的,这样的服务器将无法支持你所期望的所有功能…”
实际上,你可以选择使用 Open Policy Agent (OPA) 或者编写你自己的准入控制器,这取决于你打算在 Kubernetes 环境中自定义的程度。
什么是OPA Gatekeeper?
Kubernetes 集群通常需要准入控制器才能完整运行。OPA Gatekeeper 就是一个这样的控制器,会检查所有发送到 Kubernetes API 的请求。
守门人拦截请求并根据预设的策略进行检查。根据这项检查,请求可以被拒绝或批准。
从上面的图示中,我们可以看到OPA Gatekeeper是如何处理进入Kubernetes API服务器的任何请求的流程。
它也一直在观察 API 服务器中的 pod 和服务这些资源是否有任何变化。
所以实际上,当你在 Kubernetes 环境中装了 Gatekeeper 时,你可以直接在这里制定并实施策略。下面我们会继续讨论更多相关内容。
为进一步更好地理解OPA守门人在Kubernetes中的好处和集成范围,我们将通过以下案例来说明:
本两部分的教程将一步步指导你完成整个过程,教你如何使用 OPA 在你的 Kubernetes 集群中编写和测试策略。
在第一部分,我们将使用OPA守门人准入控制器来实施我们编写的策略,然后,在第二部分,我们将动手编写我们自己的自定义验证控制程序。
到本指南结束时,你就能做到如下:
要充分利用这份实用指南,你需要在本地电脑上准备好以下项目:
kubectl
可以与你的集群进行交互。咱们直接切入正题。
在这个用例中,让我们编写一个OPA策略规则,规定每个命名空间创建请求都应添加一个注解。
第一步:创建一个约束模板文件。约束模板定义了策略的结构和逻辑规则,这。此模板强制要求每个命名空间都必须包含 team
注解。
apiVersion: templates.gatekeeper.sh/v1beta1 kind: ConstraintTemplate metadata: name: k8srequiredannotations spec: crd: spec: names: kind: K8sRequiredAnnotations targets: - target: admission.k8s.gatekeeper.sh rego: | package k8srequiredannotations
违规[{"msg": msg}] { input.review.kind.kind == "Namespace" input.review.object.metadata.annotations 中没有 'team' msg := "Namespace 必须有一个 'team' 注解" }
将此 YAML 保存到文件 constrainttemplate.yaml
中,然后应用到您的集群中:
运行以下命令来应用约束模板:
kubectl apply -f constrainttemplate.yaml
第二步:创建约束文件
约束通过约束模板在特定资源上实施策略——在这种情况下,指的是在命名空间上。
版本号:constraints.gatekeeper.sh/v1beta1 类型:K8sRequiredAnnotations 元数据: 名称:require-team-annotation 规范: 匹配: 类型: - API组:[""] 类型:["Namespace"]
注解:需要团队注解
注解:Namespace:命名空间
将此 YAML 保存到名为 constraint.yaml
的文件里,并应用到您的集群上。
kubectl apply -f constraint.yaml
# 这条命令用于应用constraint.yaml文件中的配置。
步骤三:核实一下政策内容
为了验证策略是否起作用,可以尝试创建一个未标注团队的命名空间。这应该会被拒绝。
apiVersion: v1 kind: Namespace metadata: name: demo-namespace # 创建一个名为 demo-namespace 的命名空间
将这段 YAML 代码保存到名为 demo-namespace.yaml
的文件中,并执行这个配置。
运行以下命令来应用yaml文件:
kubectl apply -f demo-namespace.yaml
果然遇到了错误
现在,让我们通过创建一个带有 team
标签的命名空间来按照政策行事。
apiVersion: v1 kind: Namespace # 这是一个命名空间 metadata: name: test-namespace annotations: # 注释部分 team: "devops"
保存上述 YAML 文件为 test-namespace-with-annotation.yaml
,然后在集群中应用它。
这次它终于成功地创建了命名空间。
接下来,我们想要编写一个稍微更复杂的策略,带有 env:production
标签的命名空间必须设置资源配额。
这样的政策在你想要管理和监控资源使用的情况时会很有帮助,使事情更高效。我们开始吧。
第一步,创建模板文件
这个模板会检查标有 env: production
的命名空间(namespace)是否有资源配额(resource quota)。
apiVersion: templates.gatekeeper.sh/v1beta1 kind: ConstraintTemplate metadata: name: k8srequiredresourcequotas spec: crd: spec: names: kind: K8sRequiredResourceQuotas targets: - target: admission.k8s.gatekeeper.sh rego: | package k8srequiredresourcequotas
违规[{"msg": msg}] { input.review.kind.kind == "Namespace" namespace := input.review.object; 某些 label_key namespace.metadata.labels[label_key] == "production" 没有 has_resource_quota(namespace.metadata.name) msg := sprintf("带有 'env: production' 标签的命名空间必须有资源配额", []) } has_resource_quota(namespace_name) { 某个索引 i input.review.context.related[i].kind == "ResourceQuota" input.review.context.related[i].metadata.namespace == namespace_name }
将此 YAML 保存到名为 constrainttemplate.yaml
的文件中,然后将其应用到您的集群中:
kubectl apply -f constrainttemplate.yaml
该命令用于将constrainttemplate.yaml文件应用到Kubernetes集群中。
第二步:创建一个限制条件文件。
apiVersion: constraints.gatekeeper.sh/v1beta1 # 定义使用的API版本 kind: K8sRequiredResourceQuotas # 定义资源类型,这里表示K8s所需资源配额 metadata: name: require-resource-quota-for-production # 元数据名称,表示需要为生产环境设置资源配额 spec: # 规范部分,定义了资源配额的具体规则 match: # 匹配部分,定义了哪些资源需要遵守这些规则 kinds: - apiGroups: [""] # 指定API组为空,表示核心API组 kinds: ["Namespace"] # 指定资源类型为Namespace
将此 YAML 保存到名为 constraint.yaml
的文件中,并将其应用到您的集群上。
kubectl apply -f constraint.yaml
第三步:验证策略是否生效,为了验证策略是否生效,让我们创建一个带有生产标签但没有设置资源配额的简单测试命名空间。它应当被拒绝。
# 类型:Namespace,标签:环境=生产环境 apiVersion: v1 # API版本为v1 kind: Namespace # 类型:Namespace metadata: # 元数据部分 name: test-namespace-production-no-quota # 名称:test-namespace-production-no-quota labels: # 标签部分,env标签值为production env: production # 环境标签:env=production
将以下 YAML 保存到文件 test-namespace-production-no-quota.yaml
,并应用该文件。
kubectl apply -f test-namespace-production-no-quota.yaml
此命令用于创建或更新名为 test-namespace-production-no-quota 的命名空间,该命名空间没有配额。
正如预期的那样,当我们尝试创建命名空间时,它会告诉我们,我们需要先分配配额,正如我们在策略文件里提到的。
太棒了。我们给命名空间设定一个额度吧。
apiVersion: v1 kind: 资源配额限制 元数据: 名称: 资源配额限制 命名空间: default spec: 硬限制: 请求的CPU: "1" 请求的内存: 1Gi 限制的CPU: "2" 限制的内存: 2Gi
将此 YAML 保存为名为 resource-quota.yaml
的文件并应用这个配置。
运行以下命令来应用资源配额文件: kubectl apply -f resource-quota.yaml 这个命令会应用名为 resource-quota.yaml 的文件里的资源限制。
按照上述步骤申请后,命名空间的创建和配额申请都成功了。
你可以更进一步地使用命令查看命名空间的配额,如下所示:
kubectl get resourcequotas -n default
,运行该命令来获取默认命名空间中的资源配额。
到现在为止,我们已经看到了OPA gatekeeper在这些情况下的作用,它在我们想要在Kubernetes中执行特定操作时充当中间人或拦截特定操作的角色。
我们现在来进入最后部分,即创建我们自己的自定义验证 webhook(网络钩子)。
首先,创建并切换到一个专门为此过程创建的单独的文件夹,你可以命名为 webhook_server
。
minikube start
2. 编写 webhook 验证逻辑。我们将用 Python 编写这个逻辑,但也可以用任何其他语言。在文件 app.py
中添加以下代码。
from flask import Flask, request, jsonify import logging app = Flask(__name__) logger = logging.getLogger(__name__) @app.route('/validate', methods=['POST']) def validate_pod(): try: admission_review = request.get_json() pod_spec = admission_review['request']['object']['spec'] # 在这里加入你的验证逻辑 # 例如:检查Pod是否有特定标签 labels = pod_spec.get('labels', {}) if 'app' not in labels: logger.error("Pod验证失败:缺少'app'标签") return jsonify({"response": {"allowed": False, "status": {"reason": "MissingAppLabel"}}}), 200 # 如果所有验证检查都通过了 logger.info("Pod验证成功") return jsonify({"response": {"allowed": True}}), 200 except Exception as e: logger.exception("在验证Pod期间发生错误") return jsonify({"response": {"allowed": False, "status": {"reason": "InternalServerError"}}}), 500 if __name__ == '__main__': # 配置日志记录 logging.basicConfig(level=logging.INFO) # 启动Flask应用程序 app.run(host='0.0.0.0', port=8080, debug=False)
3. 我们需要将该 webhook 服务器打包成容器,并将其构建并推送到 DockerHub 仓库。步骤 I. 在 Dockerhub 上创建一个仓库。步骤 II. 在文件夹中创建一个包含以下内容的 Dockerfile:
# 示例内容:FROM python:3.8-slim # 示例内容:WORKDIR /app # 示例内容:COPY . /app # 示例内容:CMD ["python", "./server.py"]
请根据实际情况填写具体内容。
运行 Dockerfile
# 从Python 3.9 slim镜像开始 FROM python:3.9-slim # 设置工作目录为 /app WORKDIR /app # 复制 requirements.txt 文件到当前目录 COPY requirements.txt . # 运行 pip 安装 requirements.txt 文件中列出的包,不使用缓存 RUN pip install --no-cache-dir -r requirements.txt # 复制 app.py 文件到当前目录 COPY app.py . # 设置容器启动时运行的命令为 python app.py CMD [ "python", "app.py" ]
III. 构建 Docker 镜像 docker build -t your-username/repository-name:latest .
注:此命令用于从当前目录构建一个 Docker 镜像,并将其标记为 your-username/repository-name:latest
。
四. 推送到DockerHub
docker 登录
请将标签改为 `docker tag <repository-name>:latest your-username/repository-name:latest` 然后推送镜像 `docker push your-username/repository-name:latest`
注意:这里的 <repository-name>
和 :latest
是占位符,请根据实际情况替换为具体的仓库名和标签。这些步骤将帮助你更新并推送最新的 Docker 镜像。
现在,该镜像应该已经在Dockerhub上可以实时获取了。这意味着我们现在可以在部署时使用它了。
💡小提示:如果您在将 Docker 镜像推送到 DockerHub 时遇到请求被拒绝,确保在终端登录 Docker,并仔细核对镜像名、仓库名和标签是否一致,是否有拼写错误或不匹配的地方。
4. 生成 TLS 证书这一操作。这有助于确保我们 webhook 服务器与 Kubernetes 之间的通信安全。这一步骤至关重要,需要特别注意。如果配置不当,可能会遇到 TLS 错误。
openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes -subj "/CN=pod-validation-webhook.default.svc"
kubectl create secret tls pod-validation-webhook-tls --cert=server.crt --key=server.key -n default
运行 webhook-deployment.yaml 文件
apiVersion: apps/v1 kind: Deployment metadata: name: pod-validation-webhook namespace: default spec: replicas: 1 selector: matchLabels: app: pod-validation-webhook template: metadata: labels: app: pod-validation-webhook spec: containers: - name: cweb image: cannarron/cweb:latest ports: - containerPort: 8080 volumeMounts: - name: tls-certs mountPath: /etc/webhook/certs readOnly: true volumes: - name: tls-certs secret: secretName: pod-validation-webhook-tls
6. 创建一个 kubectl 服务,用于指向我们的 Python webhook 服务器 touch webhook-service.yaml
apiVersion: v1 kind: Service metadata: name: pod-validation-webhook spec: # 规范说明 selector: # 选择器 app: pod-validation-webhook ports: # 定义端口 - protocol: TCP port: 8080 targetPort: 8080
运行 validating-webhook-config.yaml:
apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: name: pod-validation-webhook webhooks: - name: pod.validation.example.com clientConfig: service: name: pod-validation-webhook namespace: default path: "/validate" caBundle: <base64 encoded ca.crt> admissionReviewVersions: - v1 sideEffects: None timeoutSeconds: 5 failurePolicy: Fail matchPolicy: Equivalent rules: - apiGroups: [""] apiVersions: ["v1"] operations: ["CREATE", "UPDATE"] resources: ["pods"]
8. 应用清单文件:我们来应用之前步骤中创建的部署文件、服务文件和验证文件,这些文件都是 YAML 格式的。
kubectl apply -f webhook-deployment.yaml // 部署 webhook 部署配置 kubectl apply -f webhook-service.yaml // 部署 webhook 服务配置 kubectl apply -f validating-webhook-config.yaml // 部署验证 webhook 配置
9. 验证步骤。如果部署顺利,你应该能看到 pod 运行正常。验证 webhook 也应该在工作。为了确认我们的验证钩子是否已经启动,根据我们在 webhook 中设置的验证规则,我们只需创建一个没有标签的测试 pod,这个请求会被拒绝。
在这份全面的指南中,我们讨论了如何使用 Kubernetes 策略来扩展 Kubernetes 的功能,并根据需求添加自定义增强或团队偏好。我们还逐步讲解了如何在 Kubernetes 中实施策略,并通过实际案例进行说明。
在你的 Kubernetes 配置中使用策略是一种创新的方法,可以帮助你充分发掘容器化部署中的潜力,并使部署更加安全。