SiamCafe.net Blog
Technology

OPA Gatekeeper Internal Developer Platform

opa gatekeeper internal developer platform
OPA Gatekeeper Internal Developer Platform | SiamCafe Blog
2025-07-17· อ. บอม — SiamCafe.net· 9,809 คำ

OPA Gatekeeper คืออะไร

OPA Gatekeeper เป็น Policy Engine สำหรับ Kubernetes ที่ทำหน้าที่เป็น Admission Controller ตรวจสอบทุก Request ที่เข้ามาที่ Kubernetes API Server ว่าเป็นไปตาม Policy ที่กำหนดหรือไม่ ถ้าไม่ผ่าน Request จะถูก Reject ทันที

เมื่อใช้ร่วมกับ Internal Developer Platform (IDP) Gatekeeper ทำหน้าที่เป็น Guardrails ให้ Developer สามารถ Deploy Application ได้ด้วยตัวเองอย่างปลอดภัย โดยไม่ต้องรอ Platform Team Review ทุก Manifest เพราะ Policy จะตรวจสอบอัตโนมัติ

ติดตั้ง OPA Gatekeeper

# ติดตั้ง Gatekeeper ด้วย Helm
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update

helm install gatekeeper gatekeeper/gatekeeper \
  --namespace gatekeeper-system \
  --create-namespace \
  --set replicas=3 \
  --set audit.replicas=1 \
  --set audit.logLevel=INFO \
  --set controllerManager.logLevel=INFO

# ตรวจสอบ
kubectl get pods -n gatekeeper-system
kubectl get crd | grep gatekeeper

# === Constraint Template: ห้ามใช้ Latest Tag ===
# k8s-required-image-tag.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequiredimagetag
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredImageTag
      validation:
        openAPIV3Schema:
          type: object
          properties:
            exemptImages:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiredimagetag
        
        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not valid_tag(container.image)
          msg := sprintf("Container '%v' uses invalid image '%v'. Must specify a tag (not 'latest')", [container.name, container.image])
        }
        
        violation[{"msg": msg}] {
          container := input.review.object.spec.initContainers[_]
          not valid_tag(container.image)
          msg := sprintf("Init container '%v' uses invalid image '%v'", [container.name, container.image])
        }
        
        valid_tag(image) {
          # ตรวจสอบว่ามี tag และไม่ใช่ latest
          contains(image, ":")
          not endswith(image, ":latest")
        }
        
        valid_tag(image) {
          # ใช้ SHA digest
          contains(image, "@sha256:")
        }
        
        valid_tag(image) {
          # Exempt images
          exempt := input.parameters.exemptImages[_]
          glob.match(exempt, ["/"], image)
        }

---
# Constraint: บังคับใช้กับทุก Namespace ยกเว้น kube-system
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredImageTag
metadata:
  name: require-image-tag
spec:
  enforcementAction: deny
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
      - apiGroups: ["apps"]
        kinds: ["Deployment", "StatefulSet", "DaemonSet"]
    excludedNamespaces:
      - kube-system
      - gatekeeper-system
  parameters:
    exemptImages:
      - "gcr.io/distroless/*"

# Apply
kubectl apply -f k8s-required-image-tag.yaml

Policy Templates สำหรับ IDP

# === Policy: ต้องมี Resource Limits ===
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequiredresources
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredResources
      validation:
        openAPIV3Schema:
          type: object
          properties:
            maxCpuLimit:
              type: string
            maxMemoryLimit:
              type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiredresources
        
        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not container.resources.limits
          msg := sprintf("Container '%v' must have resource limits", [container.name])
        }
        
        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not container.resources.limits.cpu
          msg := sprintf("Container '%v' must have CPU limit", [container.name])
        }
        
        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not container.resources.limits.memory
          msg := sprintf("Container '%v' must have memory limit", [container.name])
        }
        
        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not container.resources.requests
          msg := sprintf("Container '%v' must have resource requests", [container.name])
        }

---
# === Policy: ต้องมี Required Labels ===
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequiredlabels
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredLabels
      validation:
        openAPIV3Schema:
          type: object
          properties:
            labels:
              type: array
              items:
                type: object
                properties:
                  key:
                    type: string
                  allowedRegex:
                    type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiredlabels
        
        violation[{"msg": msg}] {
          provided := {label | input.review.object.metadata.labels[label]}
          required := {label | label := input.parameters.labels[_].key}
          missing := required - provided
          count(missing) > 0
          msg := sprintf("Missing required labels: %v", [missing])
        }
        
        violation[{"msg": msg}] {
          label := input.parameters.labels[_]
          value := input.review.object.metadata.labels[label.key]
          label.allowedRegex != ""
          not re_match(label.allowedRegex, value)
          msg := sprintf("Label '%v' value '%v' does not match regex '%v'", [label.key, value, label.allowedRegex])
        }

---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: require-team-labels
spec:
  enforcementAction: deny
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["Deployment"]
    excludedNamespaces: ["kube-system"]
  parameters:
    labels:
      - key: "app.kubernetes.io/name"
      - key: "app.kubernetes.io/team"
        allowedRegex: "^(platform|backend|frontend|data|ml)$"
      - key: "app.kubernetes.io/environment"
        allowedRegex: "^(dev|staging|production)$"

---
# === Policy: ห้ามใช้ Root Container ===
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8snoroot
spec:
  crd:
    spec:
      names:
        kind: K8sNoRoot
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8snoroot
        
        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          container.securityContext.runAsUser == 0
          msg := sprintf("Container '%v' must not run as root", [container.name])
        }
        
        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not container.securityContext.runAsNonRoot
          not container.securityContext.runAsUser
          msg := sprintf("Container '%v' must set securityContext.runAsNonRoot=true", [container.name])
        }

Python Script ทดสอบ Policy

# test_policies.py — ทดสอบ OPA Policies ด้วย Python
import subprocess
import json
import yaml
import sys

class PolicyTester:
    """ทดสอบ OPA Gatekeeper Policies"""

    def __init__(self):
        self.results = []

    def test_manifest(self, manifest_path, expected_result="pass"):
        """ทดสอบ Manifest กับ Gatekeeper"""
        # Dry-run ด้วย kubectl
        cmd = [
            "kubectl", "apply", "-f", manifest_path,
            "--dry-run=server", "-o", "json"
        ]
        result = subprocess.run(cmd, capture_output=True, text=True)

        passed = result.returncode == 0
        test_passed = (passed and expected_result == "pass") or \
                      (not passed and expected_result == "fail")

        self.results.append({
            "manifest": manifest_path,
            "expected": expected_result,
            "actual": "pass" if passed else "fail",
            "test_passed": test_passed,
            "error": result.stderr if not passed else "",
        })

        status = "PASS" if test_passed else "FAIL"
        print(f"  [{status}] {manifest_path} (expected: {expected_result})")
        if not test_passed:
            print(f"    Error: {result.stderr[:200]}")

    def run_suite(self, test_cases):
        """รัน Test Suite"""
        print("=== Policy Test Suite ===\n")
        for tc in test_cases:
            self.test_manifest(tc["manifest"], tc["expected"])

        passed = sum(1 for r in self.results if r["test_passed"])
        total = len(self.results)
        print(f"\nResults: {passed}/{total} passed")

        if passed < total:
            print("\nFailed tests:")
            for r in self.results:
                if not r["test_passed"]:
                    print(f"  - {r['manifest']}: expected {r['expected']}, "
                          f"got {r['actual']}")
            sys.exit(1)

# Test Cases
tests = [
    # ควร PASS: มี Tag, Resource Limits, Labels
    {"manifest": "tests/valid-deployment.yaml", "expected": "pass"},
    # ควร FAIL: ใช้ Latest Tag
    {"manifest": "tests/latest-tag.yaml", "expected": "fail"},
    # ควร FAIL: ไม่มี Resource Limits
    {"manifest": "tests/no-limits.yaml", "expected": "fail"},
    # ควร FAIL: ไม่มี Required Labels
    {"manifest": "tests/no-labels.yaml", "expected": "fail"},
    # ควร FAIL: Run as Root
    {"manifest": "tests/root-container.yaml", "expected": "fail"},
]

tester = PolicyTester()
tester.run_suite(tests)

IDP Integration Workflow

Best Practices

OPA Gatekeeper คืออะไร

OPA Gatekeeper เป็น Kubernetes Admission Controller ที่ใช้ OPA บังคับ Policy ตรวจสอบทุก Resource ที่สร้างหรือแก้ไขว่าเป็นไปตาม Policy เช่น ห้ามใช้ Latest Tag ต้องมี Resource Limits ต้องมี Labels ถ้าไม่ผ่าน Request ถูก Reject

Internal Developer Platform คืออะไร

IDP เป็นแพลตฟอร์มภายในองค์กรให้ Developer สร้าง Deploy และจัดการ Application ได้ด้วยตัวเอง มี Self-service Portal, Automated Pipelines และ Guardrails ด้วย Policy ลด Cognitive Load และเพิ่ม Developer Productivity

ทำไมต้องใช้ OPA Gatekeeper ใน IDP

เป็น Guardrails ให้ Developer ใช้ Kubernetes ปลอดภัยโดยไม่ต้องรู้ทุก Best Practice ป้องกัน Misconfiguration อัตโนมัติ ไม่ต้องรอ Platform Team Review ทำให้ Developer มี Self-service ที่ปลอดภัย

Constraint Template คืออะไร

Constraint Template เป็น CRD ที่กำหนด Policy Logic ด้วยภาษา Rego สร้าง Constraint จาก Template เพื่อบังคับ Policy บน Cluster Template กำหนดว่าตรวจสอบอะไร Constraint กำหนดว่าบังคับกับ Resource ไหนและพารามิเตอร์อะไร

สรุป

OPA Gatekeeper เป็นเครื่องมือสำคัญสำหรับ Internal Developer Platform ช่วยให้ Developer มี Self-service ที่ปลอดภัยบน Kubernetes สิ่งสำคัญคือเริ่มด้วย Audit Mode ใช้ Constraint Library สำเร็จรูป ทดสอบ Policy ใน CI ด้วย conftest เขียน Error Message ที่ชัดเจน และเก็บ Policy ใน Git ใช้ GitOps Deploy เพื่อ Version Control และ Auditability

📖 บทความที่เกี่ยวข้อง

OPA Gatekeeper Observability Stackอ่านบทความ → OPA Gatekeeper Pod Schedulingอ่านบทความ → OPA Gatekeeper Certification Pathอ่านบทความ → OPA Gatekeeper Developer Experience DXอ่านบทความ → OPA Gatekeeper Learning Path Roadmapอ่านบทความ →

📚 ดูบทความทั้งหมด →