OPA Storage Class Templates¶
OPA/Gatekeeper constraint templates for storage governance. Enforce approved storage classes, prevent expensive storage tiers, and control PVC sizes.
Storage Costs Add Up Fast
Uncontrolled PVC creation with premium storage classes can drive up cloud costs. Enforce storage class governance to control expenses.
Template 5: Storage Class Restrictions¶
Enforces usage of approved storage classes in PersistentVolumeClaims. Prevents expensive or deprecated storage classes from being used.
Complete Policy¶
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8sallowedstorageclass
spec:
crd:
spec:
names:
kind: K8sAllowedStorageClass
validation:
openAPIV3Schema:
properties:
allowedStorageClasses:
type: array
items:
type: string
description: "List of approved storage class names"
blockedStorageClasses:
type: array
items:
type: string
description: "List of blocked storage class names (e.g., deprecated)"
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedstorageclass
violation[{"msg": msg, "details": {}}] {
input.review.kind.kind == "PersistentVolumeClaim"
storage_class := input.review.object.spec.storageClassName
count(input.parameters.allowedStorageClasses) > 0
not allowed_storage_class(storage_class)
msg := sprintf("Storage class %v is not in the allowed list: %v",
[storage_class, input.parameters.allowedStorageClasses])
}
violation[{"msg": msg, "details": {}}] {
input.review.kind.kind == "PersistentVolumeClaim"
storage_class := input.review.object.spec.storageClassName
count(input.parameters.blockedStorageClasses) > 0
blocked_storage_class(storage_class)
msg := sprintf("Storage class %v is blocked. Use an approved storage class instead.",
[storage_class])
}
violation[{"msg": msg, "details": {}}] {
input.review.kind.kind == "PersistentVolumeClaim"
not input.review.object.spec.storageClassName
count(input.parameters.allowedStorageClasses) > 0
msg := "PersistentVolumeClaim must specify a storageClassName"
}
allowed_storage_class(class) {
allowed := input.parameters.allowedStorageClasses[_]
class == allowed
}
blocked_storage_class(class) {
blocked := input.parameters.blockedStorageClasses[_]
class == blocked
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedStorageClass
metadata:
name: allowed-storage-classes
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: [""]
kinds: ["PersistentVolumeClaim"]
parameters:
allowedStorageClasses:
- "gp3" # AWS EBS gp3 (general purpose)
- "standard-rwo" # GKE standard persistent disk
- "managed-csi" # Azure managed disk
blockedStorageClasses:
- "io2" # AWS provisioned IOPS (expensive)
- "premium-rwo" # GKE premium SSD (expensive)
- "gp2" # AWS EBS gp2 (deprecated, use gp3)
Customization Variables¶
| Variable | Default | Purpose |
|---|---|---|
allowedStorageClasses |
Cloud-specific defaults | Approved storage classes (cost-effective) |
blockedStorageClasses |
Expensive/deprecated | Explicitly blocked storage classes |
Validation Commands¶
# Apply policy
kubectl apply -f opa-allowed-storage-class.yaml
# Verify installation
kubectl get constrainttemplates k8sallowedstorageclass
kubectl get k8sallowedstorageclass
# Test with blocked storage class (should fail)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: io2 # Blocked expensive storage
resources:
requests:
storage: 10Gi
EOF
# Test with allowed storage class (should pass)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: gp3 # Allowed cost-effective storage
resources:
requests:
storage: 10Gi
EOF
# Check violations
kubectl get k8sallowedstorageclass allowed-storage-classes -o yaml
# Audit PVCs with unapproved storage classes
kubectl get pvc -A -o json | jq -r '
.items[] |
"\(.metadata.namespace)/\(.metadata.name): \(.spec.storageClassName)"
' | grep -v -E '(gp3|standard-rwo|managed-csi)$'
Use Cases¶
- Cost Control: Block expensive premium SSD storage classes
- Migration Enforcement: Deprecate old storage classes (gp2 → gp3)
- Performance Guarantees: Ensure production uses appropriate storage tiers
- Vendor Lock-in Prevention: Standardize on CSI storage classes
- Compliance: Enforce encrypted storage classes for sensitive data
Advanced Example: PVC Size Constraints¶
Extended policy enforcing minimum and maximum PVC sizes to prevent cost overruns and tiny inefficient volumes.
Complete Policy¶
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8spvcsizeconstraints
spec:
crd:
spec:
names:
kind: K8sPVCSizeConstraints
validation:
openAPIV3Schema:
properties:
minSize:
type: string
description: "Minimum PVC size (e.g., '1Gi')"
maxSize:
type: string
description: "Maximum PVC size (e.g., '100Gi')"
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8spvcsizeconstraints
violation[{"msg": msg, "details": {}}] {
input.review.kind.kind == "PersistentVolumeClaim"
requested := input.review.object.spec.resources.requests.storage
min_size := input.parameters.minSize
to_bytes(requested) < to_bytes(min_size)
msg := sprintf("PVC size %v is below minimum %v", [requested, min_size])
}
violation[{"msg": msg, "details": {}}] {
input.review.kind.kind == "PersistentVolumeClaim"
requested := input.review.object.spec.resources.requests.storage
max_size := input.parameters.maxSize
to_bytes(requested) > to_bytes(max_size)
msg := sprintf("PVC size %v exceeds maximum %v. Request approval for larger volumes.",
[requested, max_size])
}
# Convert storage units to bytes
to_bytes(str) = num {
endswith(str, "Ti")
num := to_number(trim_suffix(str, "Ti")) * 1024 * 1024 * 1024 * 1024
}
to_bytes(str) = num {
endswith(str, "Gi")
num := to_number(trim_suffix(str, "Gi")) * 1024 * 1024 * 1024
}
to_bytes(str) = num {
endswith(str, "Mi")
num := to_number(trim_suffix(str, "Mi")) * 1024 * 1024
}
to_bytes(str) = num {
endswith(str, "Ki")
num := to_number(trim_suffix(str, "Ki")) * 1024
}
to_bytes(str) = num {
not contains(str, "i")
num := to_number(str)
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPVCSizeConstraints
metadata:
name: pvc-size-constraints
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: [""]
kinds: ["PersistentVolumeClaim"]
parameters:
minSize: "1Gi" # Prevent tiny volumes (inefficient)
maxSize: "100Gi" # Require approval for large volumes
Customization Variables¶
| Variable | Default | Purpose |
|---|---|---|
minSize |
1Gi |
Minimum PVC size (prevent inefficient tiny volumes) |
maxSize |
100Gi |
Maximum PVC size (require approval for larger) |
Validation Commands¶
# Apply policy
kubectl apply -f opa-pvc-size-constraints.yaml
# Verify installation
kubectl get constrainttemplates k8spvcsizeconstraints
kubectl get k8spvcsizeconstraints
# Test with oversized PVC (should fail)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: large-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: gp3
resources:
requests:
storage: 500Gi # Exceeds maximum
EOF
# Test with valid size (should pass)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: normal-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: gp3
resources:
requests:
storage: 20Gi # Within limits
EOF
# Audit PVCs exceeding size limits
kubectl get pvc -A -o json | jq -r '
.items[] |
select(.spec.resources.requests.storage | tonumber > 100) |
"\(.metadata.namespace)/\(.metadata.name): \(.spec.resources.requests.storage)"
'
Use Cases¶
- Cost Control: Prevent accidental creation of multi-terabyte volumes
- Resource Optimization: Discourage tiny inefficient volumes under 1Gi
- Approval Workflow: Force teams to request approval for large storage
- Capacity Planning: Ensure cluster has sufficient storage capacity
- Best Practices: Encourage right-sized storage allocation
Storage Class Selection Guide¶
Choosing the right storage class for your workload:
| Use Case | AWS | GCP | Azure | Characteristics |
|---|---|---|---|---|
| Development | gp3 | standard-rwo | managed-csi | Cost-effective, moderate performance |
| Production Databases | io2 | pd-ssd | premium-ssd-v2 | High IOPS, low latency |
| Logs & Backups | st1 | standard-rwo | standard-lrs | Throughput-optimized, low cost |
| Ephemeral | gp3 | balanced-pd | managed-csi | Fast ephemeral volumes |
| Compliance | gp3-encrypted | pd-ssd-encrypted | managed-csi-encrypted | Encryption at rest required |
Migration Path: gp2 → gp3 (AWS)¶
AWS gp3 offers better performance at lower cost than gp2:
# Find PVCs using deprecated gp2
kubectl get pvc -A -o json | jq -r '
.items[] |
select(.spec.storageClassName == "gp2") |
"\(.metadata.namespace)/\(.metadata.name)"
'
# Update storage class allowlist to block gp2
kubectl patch k8sallowedstorageclass allowed-storage-classes --type='json' -p='[
{"op": "add", "path": "/spec/parameters/blockedStorageClasses/-", "value": "gp2"}
]'
Related Resources¶
- OPA Resource Governance Templates → - Resource limits and quota enforcement
- OPA LimitRange Templates → - Default resource limits and ephemeral storage
- OPA Pod Security Templates → - Privileged containers and host namespaces
- Kyverno Storage Templates → - Kubernetes-native alternative
- Decision Guide → - OPA vs Kyverno selection
- Template Library Overview → - Back to main page