Kubernetes / OpenShift Manifest Generator
Generate production-ready YAML manifests following security best practices and operational excellence.
Current Versions & CLI (January 2026)
| Platform | Current Version | CLI | Documentation |
|---|---|---|---|
| Kubernetes | 1.31.x | kubectl |
https://kubernetes.io/docs/ |
| OpenShift | 4.17.x | oc |
https://docs.openshift.com/ |
| EKS | 1.31 | eksctl |
https://docs.aws.amazon.com/eks/ |
| AKS | 1.31 | az aks |
https://learn.microsoft.com/azure/aks/ |
| GKE | 1.31 | gcloud container |
https://cloud.google.com/kubernetes-engine/docs |
| ARO | 4.17.x | az aro, oc |
https://learn.microsoft.com/azure/openshift/ |
| ROSA | 4.17.x | rosa, oc |
https://docs.redhat.com/en/documentation/red_hat_openshift_service_on_aws/ |
Command Usage Convention
IMPORTANT: This skill uses kubectl in examples. When working with:
- OpenShift/ARO clusters: Replace
kubectlwithoc - Standard Kubernetes (AKS, EKS, GKE): Use
kubectlas shown - ROSA clusters: Use
rosaCLI for cluster ops,ocfor workload management
Core Principles
- Security by Default: Always include security contexts, never run as root
- Resource Management: Always specify resource requests/limits
- High Availability: Multiple replicas with anti-affinity for production
- Observability: Include health probes, annotations for monitoring
- GitOps Ready: Generate manifests suitable for version control
Manifest Generation Workflow
- Identify resource type and target platform
- Gather requirements (replicas, resources, networking, storage)
- Apply security best practices
- Generate YAML with appropriate labels and annotations
- Validate against best practices checklist
Resource Templates
Deployment (Production-Ready)
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${APP_NAME}
namespace: ${NAMESPACE}
labels:
app.kubernetes.io/name: ${APP_NAME}
app.kubernetes.io/instance: ${INSTANCE}
app.kubernetes.io/version: "${VERSION}"
app.kubernetes.io/component: ${COMPONENT}
app.kubernetes.io/part-of: ${PART_OF}
app.kubernetes.io/managed-by: cluster-skills
spec:
replicas: ${REPLICAS:-3}
revisionHistoryLimit: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app.kubernetes.io/name: ${APP_NAME}
app.kubernetes.io/instance: ${INSTANCE}
template:
metadata:
labels:
app.kubernetes.io/name: ${APP_NAME}
app.kubernetes.io/instance: ${INSTANCE}
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "${METRICS_PORT:-8080}"
spec:
serviceAccountName: ${SERVICE_ACCOUNT:-default}
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: ${APP_NAME}
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/name: ${APP_NAME}
containers:
- name: ${APP_NAME}
image: ${IMAGE}:${TAG}
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
ports:
- name: http
containerPort: ${PORT:-8080}
protocol: TCP
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
envFrom:
- configMapRef:
name: ${APP_NAME}-config
optional: true
- secretRef:
name: ${APP_NAME}-secrets
optional: true
resources:
requests:
cpu: ${CPU_REQUEST:-100m}
memory: ${MEMORY_REQUEST:-128Mi}
limits:
cpu: ${CPU_LIMIT:-500m}
memory: ${MEMORY_LIMIT:-512Mi}
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
startupProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 30
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
terminationGracePeriodSeconds: 30
Service
apiVersion: v1
kind: Service
metadata:
name: ${APP_NAME}
namespace: ${NAMESPACE}
labels:
app.kubernetes.io/name: ${APP_NAME}
spec:
type: ${SERVICE_TYPE:-ClusterIP}
ports:
- name: http
port: ${SERVICE_PORT:-80}
targetPort: http
protocol: TCP
selector:
app.kubernetes.io/name: ${APP_NAME}
app.kubernetes.io/instance: ${INSTANCE}
Ingress (Kubernetes)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ${APP_NAME}
namespace: ${NAMESPACE}
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: ${CLUSTER_ISSUER:-letsencrypt-prod}
spec:
ingressClassName: ${INGRESS_CLASS:-nginx}
tls:
- hosts:
- ${HOST}
secretName: ${APP_NAME}-tls
rules:
- host: ${HOST}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ${APP_NAME}
port:
name: http
OpenShift Route
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: ${APP_NAME}
namespace: ${NAMESPACE}
annotations:
haproxy.router.openshift.io/timeout: 60s
spec:
host: ${HOST}
to:
kind: Service
name: ${APP_NAME}
weight: 100
port:
targetPort: http
tls:
termination: edge
insecureEdgeTerminationPolicy: Redirect
ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: ${APP_NAME}-config
namespace: ${NAMESPACE}
data:
APP_ENV: "${ENVIRONMENT:-production}"
LOG_LEVEL: "${LOG_LEVEL:-info}"
Secret
apiVersion: v1
kind: Secret
metadata:
name: ${APP_NAME}-secrets
namespace: ${NAMESPACE}
type: Opaque
stringData:
DATABASE_URL: "${DATABASE_URL}"
PersistentVolumeClaim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ${APP_NAME}-data
namespace: ${NAMESPACE}
spec:
accessModes:
- ${ACCESS_MODE:-ReadWriteOnce}
storageClassName: ${STORAGE_CLASS:-standard}
resources:
requests:
storage: ${STORAGE_SIZE:-10Gi}
StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: ${APP_NAME}
namespace: ${NAMESPACE}
spec:
serviceName: ${APP_NAME}-headless
replicas: ${REPLICAS:-3}
podManagementPolicy: Parallel
updateStrategy:
type: RollingUpdate
selector:
matchLabels:
app.kubernetes.io/name: ${APP_NAME}
template:
metadata:
labels:
app.kubernetes.io/name: ${APP_NAME}
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: ${APP_NAME}
image: ${IMAGE}:${TAG}
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
ports:
- name: http
containerPort: ${PORT:-8080}
resources:
requests:
cpu: ${CPU_REQUEST:-100m}
memory: ${MEMORY_REQUEST:-256Mi}
limits:
cpu: ${CPU_LIMIT:-1000m}
memory: ${MEMORY_LIMIT:-1Gi}
volumeMounts:
- name: data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
storageClassName: ${STORAGE_CLASS:-standard}
resources:
requests:
storage: ${STORAGE_SIZE:-10Gi}
NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ${APP_NAME}-netpol
namespace: ${NAMESPACE}
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: ${APP_NAME}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ${INGRESS_NAMESPACE:-ingress-nginx}
ports:
- protocol: TCP
port: ${PORT:-8080}
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53 # DNS
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: ${EGRESS_TARGET}
ports:
- protocol: TCP
port: ${EGRESS_PORT}
HorizontalPodAutoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: ${APP_NAME}
namespace: ${NAMESPACE}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: ${APP_NAME}
minReplicas: ${MIN_REPLICAS:-2}
maxReplicas: ${MAX_REPLICAS:-10}
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: ${CPU_TARGET:-70}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: ${MEMORY_TARGET:-80}
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
ServiceAccount with RBAC
apiVersion: v1
kind: ServiceAccount
metadata:
name: ${APP_NAME}
namespace: ${NAMESPACE}
automountServiceAccountToken: false
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ${APP_NAME}
namespace: ${NAMESPACE}
rules:
- apiGroups: [""]
resources: ["configmaps", "secrets"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ${APP_NAME}
namespace: ${NAMESPACE}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ${APP_NAME}
subjects:
- kind: ServiceAccount
name: ${APP_NAME}
namespace: ${NAMESPACE}
CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: ${JOB_NAME}
namespace: ${NAMESPACE}
spec:
schedule: "${SCHEDULE}"
concurrencyPolicy: ${CONCURRENCY:-Forbid}
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
startingDeadlineSeconds: 300
jobTemplate:
spec:
backoffLimit: 3
activeDeadlineSeconds: ${TIMEOUT:-3600}
template:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
restartPolicy: OnFailure
containers:
- name: ${JOB_NAME}
image: ${IMAGE}:${TAG}
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
requests:
cpu: ${CPU_REQUEST:-100m}
memory: ${MEMORY_REQUEST:-128Mi}
limits:
cpu: ${CPU_LIMIT:-500m}
memory: ${MEMORY_LIMIT:-512Mi}
PodDisruptionBudget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: ${APP_NAME}-pdb
namespace: ${NAMESPACE}
spec:
minAvailable: ${MIN_AVAILABLE:-1}
# OR use maxUnavailable
# maxUnavailable: 1
selector:
matchLabels:
app.kubernetes.io/name: ${APP_NAME}
Best Practices Checklist
Security
-
runAsNonRoot: true -
runAsUserandrunAsGroupset (non-zero) -
allowPrivilegeEscalation: false -
readOnlyRootFilesystem: true -
capabilities.drop: ["ALL"] -
seccompProfile.type: RuntimeDefault -
automountServiceAccountToken: false(unless needed)
Reliability
- Resource requests and limits defined
- Liveness probe configured
- Readiness probe configured
- Startup probe for slow-starting apps
- PodDisruptionBudget defined
- Multiple replicas for production
Observability
- Standard labels applied (
app.kubernetes.io/*) - Prometheus annotations for metrics
- Structured logging configured
Networking
- NetworkPolicy defined (zero-trust)
- Service defined for pod exposure
- Ingress/Route for external access
ARO-Specific Manifests
ARO Deployment with Azure Identity
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${APP_NAME}
namespace: ${NAMESPACE}
spec:
replicas: ${REPLICAS:-3}
selector:
matchLabels:
app: ${APP_NAME}
template:
spec:
serviceAccountName: ${SERVICE_ACCOUNT}
containers:
- name: ${APP_NAME}
image: ${IMAGE}:${TAG}
env:
- name: AZURE_CLIENT_ID
valueFrom:
fieldRef:
fieldPath: spec.serviceAccount.annotations.azure.workloadidentity.io/client-id
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: ${SERVICE_ACCOUNT}
namespace: ${NAMESPACE}
annotations:
azure.workload.identity/use: "true"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ${APP_NAME}-azure-identity-binding
namespace: ${NAMESPACE}
subjects:
- kind: ServiceAccount
name: ${SERVICE_ACCOUNT}
namespace: ${NAMESPACE}
roleRef:
kind: ClusterRole
name: system:pod-identity
apiGroup: rbac.authorization.k8s.io
ARO Route with TLS
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: ${APP_NAME}
namespace: ${NAMESPACE}
annotations:
haproxy.router.openshift.io/timeout: 60s
spec:
host: ${HOST}
to:
kind: Service
name: ${APP_NAME}
weight: 100
port:
targetPort: http
tls:
termination: reencrypt
insecureEdgeTerminationPolicy: Redirect
wildcardPolicy: None
ROSA-Specific Manifests
ROSA Deployment with AWS IAM Role
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${APP_NAME}
namespace: ${NAMESPACE}
spec:
replicas: ${REPLICAS:-3}
selector:
matchLabels:
app: ${APP_NAME}
template:
spec:
serviceAccountName: ${SERVICE_ACCOUNT}
containers:
- name: ${APP_NAME}
image: ${IMAGE}:${TAG}
env:
- name: AWS_REGION
value: ${AWS_REGION}
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: ${SERVICE_ACCOUNT}
namespace: ${NAMESPACE}
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::${ACCOUNT_ID}:role/${ROLE_NAME}
ROSA NetworkPolicy (OVN-Kubernetes)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ${APP_NAME}-netpol
namespace: ${NAMESPACE}
spec:
podSelector:
matchLabels:
app: ${APP_NAME}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- protocol: TCP
port: 8080
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 443
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
ROSA PodDisruptionBudget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: ${APP_NAME}-pdb
namespace: ${NAMESPACE}
spec:
minAvailable: ${MIN_AVAILABLE:-2}
selector:
matchLabels:
app: ${APP_NAME}
ROSA HPA with PodDisruptionBudget
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: ${APP_NAME}
namespace: ${NAMESPACE}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: ${APP_NAME}
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: ${APP_NAME}-pdb
namespace: ${NAMESPACE}
spec:
minAvailable: 1
selector:
matchLabels:
app: ${APP_NAME}