\n // Implementation details...\n return true\n}\n\nfunc (h *ValidatingWebhook) hasResourceLimits(obj *appsv1.MyAppDeployment) bool {\n return obj.Spec.Resources.Limits != nil\n}\n\n// admission review response with uid and status\nfunc admissionResponse(uid string, allowed bool, message string) admission.Response {\n return admission.Response{\n UID: uid,\n Allowed: allowed,\n Result: &metav1.Status{\n Message: message,\n },\n }\n}\n```\n\n**Deploy webhook with service and certificate:**\n```bash\n# Create TLS secret for webhook\nopenssl req -x509 -newkey rsa:4096 -keyout webhook-key.pem -out webhook-cert.pem -days 365 -nodes \\\n -subj \"/CN=myapp-validating-webhook.default.svc\"\n\nkubectl create secret generic webhook-tls --from-file=cert.pem=webhook-cert.pem --from-file=key.pem=webhook-key.pem\n\n# Apply webhook configuration\nkubectl apply -f validating-webhook.yaml\n\n# Verify webhook is registered\nkubectl get validatingwebhookconfigurations\n\n# Test webhook manually\nkubectl apply -f test-deployment.yaml --dry-run=client\n\n# Check webhook logs\nkubectl logs -n default deploy/myapp-validating-webhook\n```\n\n---\n\n### Pattern 4: Client Library Usage (Go)\n\nThe Go client is the standard for Kubernetes controllers and operators.\n\n**BAD — Inefficient client usage:**\n```go\n// ❌ BAD — Creates new client for each operation\nfunc getDeployment(clientset *kubernetes.Clientset, name string) (*appsv1.Deployment, error) {\n // ❌ Creates new RESTClient each time\n config, _ := rest.InClusterConfig()\n client, _ := kubernetes.NewForConfig(config)\n \n return client.AppsV1().Deployments(\"default\").Get(context.TODO(), name, metav1.GetOptions{})\n}\n```\n\n**GOOD — Reusing client with proper error handling:**\n```go\n// ✅ GOOD — Single client instance with proper configuration\npackage client\n\nimport (\n \"context\"\n \"fmt\"\n \n appsv1 \"k8s.io/api/apps/v1\"\n metav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n \"k8s.io/client-go/kubernetes\"\n \"k8s.io/client-go/rest\"\n)\n\nvar clientset *kubernetes.Clientset\n\n// InitClient initializes the client once\nfunc InitClient() error {\n config, err := rest.InClusterConfig()\n if err != nil {\n return fmt.Errorf(\"failed to get in-cluster config: %w\", err)\n }\n \n clientset, err = kubernetes.NewForConfig(config)\n if err != nil {\n return fmt.Errorf(\"failed to create client: %w\", err)\n }\n \n return nil\n}\n\n// GetDeployment retrieves a deployment with proper error handling\nfunc GetDeployment(ctx context.Context, namespace, name string) (*appsv1.Deployment, error) {\n if clientset == nil {\n return nil, fmt.Errorf(\"client not initialized\")\n }\n \n deployment, err := clientset.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})\n if err != nil {\n return nil, fmt.Errorf(\"failed to get deployment %s/%s: %w\", namespace, name, err)\n }\n \n return deployment, nil\n}\n\n// CreateDeployment creates a new deployment\nfunc CreateDeployment(ctx context.Context, namespace string, deployment *appsv1.Deployment) (*appsv1.Deployment, error) {\n if clientset == nil {\n return nil, fmt.Errorf(\"client not initialized\")\n }\n \n result, err := clientset.AppsV1().Deployments(namespace).Create(ctx, deployment, metav1.CreateOptions{})\n if err != nil {\n return nil, fmt.Errorf(\"failed to create deployment: %w\", err)\n }\n \n return result, nil\n}\n\n// UpdateDeployment updates an existing deployment\nfunc UpdateDeployment(ctx context.Context, namespace string, deployment *appsv1.Deployment) (*appsv1.Deployment, error) {\n if clientset == nil {\n return nil, fmt.Errorf(\"client not initialized\")\n }\n \n result, err := clientset.AppsV1().Deployments(namespace).Update(ctx, deployment, metav1.UpdateOptions{})\n if err != nil {\n return nil, fmt.Errorf(\"failed to update deployment: %w\", err)\n }\n \n return result, nil\n}\n\n// DeleteDeployment deletes a deployment\nfunc DeleteDeployment(ctx context.Context, namespace, name string) error {\n if clientset == nil {\n return fmt.Errorf(\"client not initialized\")\n }\n \n err := clientset.AppsV1().Deployments(namespace).Delete(ctx, name, metav1.DeleteOptions{})\n if err != nil {\n return fmt.Errorf(\"failed to delete deployment %s/%s: %w\", namespace, name, err)\n }\n \n return nil\n}\n```\n\n**CRD client with dynamic client:**\n```go\n// ✅ GOOD — Dynamic client for unknown resource types\npackage client\n\nimport (\n \"context\"\n \"fmt\"\n \n apiextensionsv1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n \"k8s.io/apimachinery/pkg/api/meta\"\n \"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n \"k8s.io/apimachinery/pkg/runtime/schema\"\n \"k8s.io/client-go/dynamic\"\n \"k8s.io/client-go/rest\"\n)\n\nvar dynamicClient dynamic.Interface\n\n// InitDynamicClient initializes dynamic client for CRDs\nfunc InitDynamicClient() error {\n config, err := rest.InClusterConfig()\n if err != nil {\n return fmt.Errorf(\"failed to get in-cluster config: %w\", err)\n }\n \n dynamicClient, err = dynamic.NewForConfig(config)\n if err != nil {\n return fmt.Errorf(\"failed to create dynamic client: %w\", err)\n }\n \n return nil\n}\n\nfunc getCustomResource(client dynamic.Interface, gvr schema.GroupVersionResource, namespace, name string) (*unstructured.Unstructured, error) {\n resource, err := client.Resource(gvr).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{})\n if err != nil {\n return nil, fmt.Errorf(\"failed to get %s/%s: %w\", gvr.Resource, name, err)\n }\n \n return resource, nil\n}\n\n// ListCustomResources lists all instances of a CRD\nfunc ListCustomResources(gvr schema.GroupVersionResource, namespace string) (*unstructured.UnstructuredList, error) {\n if dynamicClient == nil {\n return nil, fmt.Errorf(\"dynamic client not initialized\")\n }\n \n list, err := dynamicClient.Resource(gvr).Namespace(namespace).List(context.TODO(), metav1.ListOptions{})\n if err != nil {\n return nil, fmt.Errorf(\"failed to list %s: %w\", gvr.Resource, err)\n }\n \n return list, nil\n}\n\n// UpdateCustomResourceStatus updates the status subresource\nfunc UpdateCustomResourceStatus(gvr schema.GroupVersionResource, namespace, name string, status interface{}) (*unstructured.Unstructured, error) {\n if dynamicClient == nil {\n return nil, fmt.Errorf(\"dynamic client not initialized\")\n }\n \n // Get current resource\n obj, err := dynamicClient.Resource(gvr).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{})\n if err != nil {\n return nil, err\n }\n \n // Update status\n err = obj.Object[\"status\"].(map[string]interface{})\n obj.Object[\"status\"] = status\n \n return dynamicClient.Resource(gvr).Namespace(namespace).UpdateStatus(context.TODO(), obj, metav1.UpdateOptions{})\n}\n```\n\n---\n\n### Pattern 5: Watch API with Resource Versions\n\nThe watch API efficiently streams resource changes.\n\n**BAD — Watch without resource version:**\n```go\n// ❌ BAD — Watch without resource version can miss events\nfunc watchDeployments(client appsv1client.AppsV1Interface) {\n // ❌ No resourceVersion - can miss events during restart\n watcher, err := client.Deployments(\"default\").Watch(context.TODO(), metav1.ListOptions{})\n if err != nil {\n panic(err)\n }\n \n for event := range watcher.ResultChan() {\n // Process event\n deployment := event.Object.(*appsv1.Deployment)\n fmt.Printf(\"Event: %s %s\\n\", event.Type, deployment.Name)\n }\n}\n```\n\n**GOOD — Watch with resource version tracking:**\n```go\n// ✅ GOOD — Watch with proper resource version handling\npackage watcher\n\nimport (\n \"context\"\n \"fmt\"\n \"time\"\n \n appsv1 \"k8s.io/api/apps/v1\"\n metav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n \"k8s.io/apimachinery/pkg/watch\"\n)\n\n// WatchDeployments watches deployments with resync capability\nfunc WatchDeployments(client appsv1client.AppsV1Interface, resyncPeriod time.Duration) error {\n // Get current resource version\n list, err := client.Deployments(\"default\").List(context.TODO(), metav1.ListOptions{Limit: 1})\n if err != nil {\n return fmt.Errorf(\"failed to list deployments: %w\", err)\n }\n \n var resourceVersion string\n if len(list.Items) > 0 {\n resourceVersion = list.ListMeta.ResourceVersion\n }\n \n // Start watch with resource version\n watcher, err := client.Deployments(\"default\").Watch(context.TODO(), metav1.ListOptions{\n ResourceVersion: resourceVersion,\n TimeoutSeconds: ptr[int64](300), // 5 minute timeout\n })\n if err != nil {\n return fmt.Errorf(\"failed to start watch: %w\", err)\n }\n \n // Handle events\n go func() {\n for event := range watcher.ResultChan() {\n deployment := event.Object.(*appsv1.Deployment)\n fmt.Printf(\"Event: %s %s (resourceVersion: %s)\\n\", \n event.Type, deployment.Name, deployment.ResourceVersion)\n }\n }()\n \n // Periodic resync to catch missed events\n go func() {\n ticker := time.NewTicker(resyncPeriod)\n defer ticker.Stop()\n \n for range ticker.C {\n performResync(client)\n }\n }()\n \n return nil\n}\n\n// RestartWatch restarts watch with latest resource version\nfunc restartWatch(client appsv1client.AppsV1Interface) error {\n list, err := client.Deployments(\"default\").List(context.TODO(), metav1.ListOptions{Limit: 1})\n if err != nil {\n return err\n }\n \n var resourceVersion string\n if len(list.Items) > 0 {\n resourceVersion = list.ListMeta.ResourceVersion\n }\n \n watcher, err := client.Deployments(\"default\").Watch(context.TODO(), metav1.ListOptions{\n ResourceVersion: resourceVersion,\n TimeoutSeconds: ptr[int64](300),\n })\n if err != nil {\n return err\n }\n \n // Continue processing...\n return nil\n}\n\nfunc ptr[T any](v T) *T {\n return &v\n}\n\nfunc performResync(client appsv1client.AppsV1Interface) {\n deployments, err := client.Deployments(\"default\").List(context.TODO(), metav1.ListOptions{})\n if err != nil {\n fmt.Printf(\"Resync failed: %v\\n\", err)\n return\n }\n \n for _, d := range deployments.Items {\n fmt.Printf(\"Resynced deployment: %s (replicas: %d)\\n\", d.Name, d.Spec.Replicas)\n }\n}\n```\n\n**Debug watch issues with kubectl:**\n```bash\n# Watch events in a namespace\nkubectl get events -n default --watch\n\n# Get resource version for debugging\nkubectl get deployments -o jsonpath='{.metadata.resourceVersion}'\n\n# Watch with specific resource version\nkubectl get deployments --watch --resource-version=\u003cversion>\n\n# Check API server logs for watch errors\nkubectl logs -n kube-system kube-apiserver-\u003cnode> | grep -i watch\n\n# Test watch with curl\nkubectl proxy &\ncurl -s --raw \"http://localhost:8001/api/v1/namespaces/default/pods?watch=true&resourceVersion=0\" &\n```\n\n---\n\n### Pattern 6: Subresource Implementation\n\nSubresources extend resources with additional endpoints (status, scale).\n\n**BAD — Incomplete subresource configuration:**\n```yaml\n# ❌ BAD — Missing subresource specification\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n name: myappdeployments.mycompany.com\nspec:\n group: mycompany.com\n names:\n kind: MyAppDeployment\n plural: myappdeployments\n scope: Namespaced\n versions:\n - name: v1\n served: true\n storage: true\n schema:\n openAPIV3Schema:\n type: object\n properties:\n spec:\n type: object\n properties:\n replicas:\n type: integer\n status:\n type: object\n # ❌ Status not defined as subresource\n```\n\n**GOOD — Complete subresource configuration:**\n```yaml\n# ✅ GOOD — Status and scale subresources properly defined\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n name: myappdeployments.mycompany.com\nspec:\n group: mycompany.com\n names:\n kind: MyAppDeployment\n plural: myappdeployments\n scope: Namespaced\n versions:\n - name: v1\n served: true\n storage: true\n schema:\n openAPIV3Schema:\n type: object\n properties:\n spec:\n type: object\n properties:\n replicas:\n type: integer\n minimum: 1\n maximum: 100\n status:\n type: object\n properties:\n replicas:\n type: integer\n availableReplicas:\n type: integer\n conditions:\n type: array\n items:\n type: object\n properties:\n type:\n type: string\n status:\n type: string\n reason:\n type: string\n message:\n type: string\n lastTransitionTime:\n type: string\n format: date-time\n subresources:\n status: {} # ✅ Enables /status endpoint\n scale:\n specReplicasPath: .spec.replicas # ✅ Enables /scale endpoint\n statusReplicasPath: .status.replicas\n additionalPrinterColumns:\n - name: Replicas\n type: integer\n description: Number of replicas\n jsonPath: .spec.replicas\n - name: Status\n type: string\n description: Current status\n jsonPath: .status.conditions[?(@.type==\"Ready\")].status\n - name: Age\n type: date\n jsonPath: .metadata.creationTimestamp\n```\n\n**Subresource operations:**\n```bash\n# Update status subresource\nkubectl patch myappdeployment myapp --type=merge -p '\n{\n \"status\": {\n \"replicas\": 3,\n \"availableReplicas\": 3,\n \"conditions\": [{\"type\": \"Ready\", \"status\": \"True\"}]\n }\n}'\n\n# Get scale subresource\nkubectl get myappdeployment myapp -o jsonpath='{.spec.replicas}'\n\n# Update scale subresource\nkubectl patch myappdeployment myapp --type=merge -p '{\"spec\":{\"replicas\":5}}'\n\n# Use kubectl scale command\nkubectl scale myappdeployment myapp --replicas=5\n\n# Get status subresource directly\nkubectl get myappdeployment myapp -o json | jq '.status'\n\n# Watch status changes\nkubectl get myappdeployment myapp --watch --field-selector=metadata.name=myapp\n```\n\n---\n\n### Pattern 7: API Conversion Webhooks\n\nConversion webhooks enable smooth API version transitions.\n\n**BAD — No conversion webhook for version migration:**\n```yaml\n# ❌ BAD — Missing conversion configuration\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n name: myappdeployments.mycompany.com\nspec:\n group: mycompany.com\n names:\n kind: MyAppDeployment\n plural: myappdeployments\n scope: Namespaced\n versions:\n - name: v1beta1\n served: true\n storage: false\n - name: v1\n served: true\n storage: true\n # ❌ No conversion webhook - manual migration required\n```\n\n**GOOD — Complete conversion webhook setup:**\n```yaml\n# ✅ GOOD — Conversion webhook for API version migration\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n name: myappdeployments.mycompany.com\nspec:\n group: mycompany.com\n names:\n kind: MyAppDeployment\n plural: myappdeployments\n scope: Namespaced\n versions:\n - name: v1beta1\n served: true\n storage: false\n - name: v1\n served: true\n storage: true\n conversion:\n strategy: Webhook\n webhook:\n clientConfig:\n service:\n name: myapp-conversion-webhook\n namespace: default\n path: /convert\n caBundle: \u003cbase64-encoded-ca-cert>\n conversionReviewVersions:\n - v1\n - v1beta1\n```\n\n**Conversion webhook implementation:**\n```go\n// ✅ GOOD — Webhook handles conversion between versions\npackage conversion\n\nimport (\n \"context\"\n \"fmt\"\n \n myappv1 \"mycompany.com/api/v1\"\n myappv1beta1 \"mycompany.com/api/v1beta1\"\n apiextensionsv1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n admission \"k8s.io/pod-security-admission/admission\"\n)\n\ntype ConversionWebhook struct {\n decoder *admission.Decoder\n}\n\nfunc (h *ConversionWebhook) Handle(ctx context.Context, req admission.Request) admission.Response {\n review := &apiextensionsv1.ConversionReview{}\n if err := h.decoder.Decode(req.AdmissionReview.Spec.Object, review); err != nil {\n return admission.Errored(http.StatusBadRequest, err)\n }\n\n response := &apiextensionsv1.ConversionResponse{\n Uid: review.Request.UID,\n }\n\n switch review.Request.DesiredAPIVersion {\n case \"mycompany.com/v1\":\n response.ConvertedObjects, response.Status = h.convertToV1(review.Request.Objects)\n case \"mycompany.com/v1beta1\":\n response.ConvertedObjects, response.Status = h.convertToV1Beta1(review.Request.Objects)\n default:\n response.Status = &metav1.Status{\n Status: metav1.StatusFailure,\n Code: http.StatusBadRequest,\n Message: fmt.Sprintf(\"unsupported API version: %s\", review.Request.DesiredAPIVersion),\n }\n return admission.Response{UID: review.Request.UID, Allowed: false, Result: response.Status}\n }\n\n return admission.Response{UID: review.Request.UID, Allowed: true, Result: response}\n}\n\nfunc (h *ConversionWebhook) convertToV1(objects []runtime.RawExtension) ([]runtime.RawExtension, *metav1.Status) {\n var converted []runtime.RawExtension\n \n for _, obj := range objects {\n v1beta1Obj := &myappv1beta1.MyAppDeployment{}\n if err := json.Unmarshal(obj.Raw, v1beta1Obj); err != nil {\n return nil, &metav1.Status{\n Status: metav1.StatusFailure,\n Code: http.StatusBadRequest,\n Message: fmt.Sprintf(\"failed to unmarshal v1beta1 object: %v\", err),\n }\n }\n \n v1Obj := convertV1Beta1ToV1(v1beta1Obj)\n data, err := json.Marshal(v1Obj)\n if err != nil {\n return nil, &metav1.Status{\n Status: metav1.StatusFailure,\n Code: http.StatusInternalServerError,\n Message: fmt.Sprintf(\"failed to marshal v1 object: %v\", err),\n }\n }\n \n converted = append(converted, runtime.RawExtension{Raw: data})\n }\n \n return converted, nil\n}\n\nfunc (h *ConversionWebhook) convertToV1Beta1(objects []runtime.RawExtension) ([]runtime.RawExtension, *metav1.Status) {\n var converted []runtime.RawExtension\n \n for _, obj := range objects {\n v1Obj := &myappv1.MyAppDeployment{}\n if err := json.Unmarshal(obj.Raw, v1Obj); err != nil {\n return nil, &metav1.Status{\n Status: metav1.StatusFailure,\n Code: http.StatusBadRequest,\n Message: fmt.Sprintf(\"failed to unmarshal v1 object: %v\", err),\n }\n }\n \n v1beta1Obj := convertV1ToV1Beta1(v1Obj)\n data, err := json.Marshal(v1beta1Obj)\n if err != nil {\n return nil, &metav1.Status{\n Status: metav1.StatusFailure,\n Code: http.StatusInternalServerError,\n Message: fmt.Sprintf(\"failed to marshal v1beta1 object: %v\", err),\n }\n }\n \n converted = append(converted, runtime.RawExtension{Raw: data})\n }\n \n return converted, nil\n}\n\nfunc convertV1Beta1ToV1(v1beta1Obj *myappv1beta1.MyAppDeployment) *myappv1.MyAppDeployment {\n return &myappv1.MyAppDeployment{\n TypeMeta: v1beta1Obj.TypeMeta,\n ObjectMeta: v1beta1Obj.ObjectMeta,\n Spec: myappv1.MyAppDeploymentSpec{\n Replicas: v1beta1Obj.Spec.Replicas,\n Image: v1beta1Obj.Spec.Image,\n Resources: convertResources(v1beta1Obj.Spec.Resources),\n },\n Status: myappv1.MyAppDeploymentStatus{\n Replicas: v1beta1Obj.Status.Replicas,\n AvailableReplicas: v1beta1Obj.Status.AvailableReplicas,\n Conditions: convertConditions(v1beta1Obj.Status.Conditions),\n },\n }\n}\n\nfunc convertV1ToV1Beta1(v1Obj *myappv1.MyAppDeployment) *myappv1beta1.MyAppDeployment {\n return &myappv1beta1.MyAppDeployment{\n TypeMeta: v1Obj.TypeMeta,\n ObjectMeta: v1Obj.ObjectMeta,\n Spec: myappv1beta1.MyAppDeploymentSpec{\n Replicas: v1Obj.Spec.Replicas,\n Image: v1Obj.Spec.Image,\n Resources: convertResourcesBack(v1Obj.Spec.Resources),\n },\n Status: myappv1beta1.MyAppDeploymentStatus{\n Replicas: v1Obj.Status.Replicas,\n AvailableReplicas: v1Obj.Status.AvailableReplicas,\n Conditions: convertConditionsBack(v1Obj.Status.Conditions),\n },\n }\n}\n```\n\n**Test conversion:**\n```bash\n# Get resource in v1beta1\nkubectl get myappdeployment myapp -o json --api-version=mycompany.com/v1beta1\n\n# Get same resource in v1 (triggers conversion webhook)\nkubectl get myappdeployment myapp -o json --api-version=mycompany.com/v1\n\n# Verify conversion in etcd\nkubectl get myappdeployment myapp -o yaml\n\n# Test conversion with curl\nkubectl proxy &\ncurl -s -X POST -H \"Content-Type: application/json\" \\\n --data '{\"apiVersion\":\"apiextensions.k8s.io/v1\",\"kind\":\"ConversionReview\",\"request\":{\"uid\":\"test\",\"desiredAPIVersion\":\"mycompany.com/v1\",\"objects\":[]}}' \\\n http://localhost:8001/apis/mycompany.com/v1/convert\n```\n\n---\n\n### Pattern 8: API Server Configuration\n\nProper API server configuration ensures custom resources work efficiently.\n\n**BAD — Default API server settings for high-volume CRs:**\n```yaml\n# ❌ BAD — No API server tuning for custom resources\napiVersion: v1\nkind: ConfigMap\nmetadata:\n name: kube-apiserver\n namespace: kube-system\ndata:\n kube-apiserver.conf: |\n # ❌ Using default settings\n kube-apiserver \\\n --etcd-servers=http://127.0.0.1:2379 \\\n --enable-admission-plugins=NamespaceLifecycle,LimitRanger\n```\n\n**GOOD — Optimized API server configuration:**\n```yaml\n# ✅ GOOD — Tuned for custom resource performance\napiVersion: v1\nkind: ConfigMap\nmetadata:\n name: kube-apiserver\n namespace: kube-system\ndata:\n kube-apiserver.conf: |\n kube-apiserver \\\n --etcd-servers=https://127.0.0.1:2379 \\\n --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt \\\n --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt \\\n --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key \\\n --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,ValidatingAdmissionWebhook,MutatingAdmissionWebhook \\\n --enable-bootstrap-token-auth=true \\\n --max-requests-inflight=2000 \\\n --max-requests-inflight=1500 \\\n --max-requests-inflight=1000 \\\n --max-requests-inflight=500 \\\n --request-timeout=300s \\\n --allow-privileged=true \\\n --authorization-mode=Node,RBAC \\\n --client-ca-file=/etc/kubernetes/pki/ca.crt \\\n --enable-swagger-ui=true \\\n --kubelet-certificate-authority=/etc/kubernetes/pki/ca.crt \\\n --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt \\\n --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key \\\n --runtime-config=api/all=true \\\n --service-account-issuer=https://kubernetes.default.svc.cluster.local \\\n --service-account-key-file=/etc/kubernetes/pki/sa.pub \\\n --service-account-signing-key-file=/etc/kubernetes/pki/sa.key \\\n --tls-cert-file=/etc/kubernetes/pki/apiserver.crt \\\n --tls-private-key-file=/etc/kubernetes/pki/apiserver.key\n\n# Optimized etcd configuration for CRDs\n etcd.conf: |\n # Optimize etcd for CRD storage\n --auto-compaction-mode=periodic\n --auto-compaction-retention=1h\n --quota-backend-bytes=8589934592 # 8GB\n --max-request-bytes=15728640 # 15MB\n --listen-client-urls=https://0.0.0.0:2379\n --listen-peer-urls=https://0.0.0.0:2380\n```\n\n**CRD controller configuration:**\n```go\n// ✅ GOOD — Controller with proper rate limiting and resync\npackage controller\n\nimport (\n \"context\"\n \"time\"\n \n \"github.com/go-logr/logr\"\n \"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n \"k8s.io/apimachinery/pkg/runtime/schema\"\n \"k8s.io/apimachinery/pkg/util/runtime\"\n \"k8s.io/apimachinery/pkg/util/wait\"\n \"k8s.io/client-go/tools/cache\"\n \"k8s.io/client-go/util/workqueue\"\n)\n\ntype Controller struct {\n client dynamic.Interface\n informer cache.SharedIndexInformer\n queue workqueue.RateLimitingInterface\n logger logr.Logger\n gvr schema.GroupVersionResource\n}\n\nfunc NewController(client dynamic.Interface, informer cache.SharedIndexInformer, gvr schema.GroupVersionResource, logger logr.Logger) *Controller {\n return &Controller{\n client: client,\n informer: informer,\n queue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),\n logger: logger,\n gvr: gvr,\n }\n}\n\nfunc (c *Controller) Start(stopCh \u003c-chan struct{}) error {\n // Set up event handlers\n c.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{\n AddFunc: c.onAdd,\n UpdateFunc: c.onUpdate,\n DeleteFunc: c.onDelete,\n })\n \n // Start informer\n go c.informer.Run(stopCh)\n \n // Process work items\n c.processWorkItems(stopCh)\n \n return nil\n}\n\nfunc (c *Controller) processWorkItems(stopCh \u003c-chan struct{}) {\n for {\n select {\n case \u003c-stopCh:\n return\n default:\n obj, shutdown := c.queue.Get()\n if shutdown {\n return\n }\n \n if err := c.reconcile(obj); err != nil {\n c.logger.Error(err, \"reconciliation failed\")\n if c.queue.NumRequeues(obj) \u003c 5 {\n c.queue.AddRateLimited(obj)\n } else {\n c.queue.Forget(obj)\n runtime.HandleError(err)\n }\n } else {\n c.queue.Forget(obj)\n }\n }\n }\n}\n\nfunc (c *Controller) reconcile(obj interface{}) error {\n defer c.queue.Done(obj)\n \n key, ok := obj.(string)\n if !ok {\n return nil\n }\n \n namespace, name, err := cache.SplitMetaNamespaceKey(key)\n if err != nil {\n return err\n }\n \n gvr := c.gvr\n resource, err := c.client.Resource(gvr).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{})\n if err != nil {\n if apierrors.IsNotFound(err) {\n return nil // Object deleted\n }\n return err\n }\n \n // Reconciliation logic here\n return nil\n}\n\nfunc (c *Controller) onAdd(obj interface{}) {\n key, err := cache.MetaNamespaceKeyFunc(obj)\n if err != nil {\n runtime.HandleError(err)\n return\n }\n c.queue.Add(key)\n}\n\nfunc (c *Controller) onUpdate(old, new interface{}) {\n key, err := cache.MetaNamespaceKeyFunc(new)\n if err != nil {\n runtime.HandleError(err)\n return\n }\n c.queue.Add(key)\n}\n\nfunc (c *Controller) onDelete(obj interface{}) {\n key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)\n if err != nil {\n runtime.HandleError(err)\n return\n }\n c.queue.Add(key)\n}\n```\n\n---\n\n## Constraints\n\n### MUST DO\n\n- Use DNS-style naming for API groups (e.g., `mycompany.com`, `k8s.io`)\n- Define strict schema validation to prevent invalid states (Parse, Don't Validate)\n- Implement admission webhooks with clear, actionable error messages\n- Use resource versions in watch API to avoid missing events\n- Include status subresource for controller-managed fields\n- Handle conversion webhooks gracefully during API version migrations\n- Set appropriate API server request limits based on CRD volume\n- Test CRD upgrades with existing resources before production deployment\n- Validate webhook TLS certificates before deployment\n- Implement proper RBAC for webhook service accounts\n- Use admission webhooks for validation, not business logic\n\n### MUST NOT DO\n\n- Use reserved API groups like `k8s.io`, `kubernetes.io` for custom resources\n- Skip schema validation even for \"internal\" CRDs\n- Return generic error messages from admission webhooks\n- Watch without resource version tracking in production\n- Modify CRD schema in ways that break existing resources\n- Deploy webhooks without TLS certificates\n- Set replicas too high in CRD without proper API server tuning\n- Use v1beta1 as storage version (always use stable v1)\n- Skip webhook timeout handling (default 10s, max 30s)\n- Block webhook operations for more than timeout duration\n\n---\n\n## Output Template\n\nWhen implementing Kubernetes API patterns, include:\n\n1. **CRD Specification** — Complete CRD definition with schema, validation, and subresources\n2. **Webhook Configuration** — Validating/mutating webhook setup with service and certificate\n3. **Client Code** — Go client or other language client for resource operations\n4. **Deployment Scripts** — Helm charts or YAML manifests for webhook deployment\n5. **Migration Plan** — Steps for API version conversion and upgrade procedures\n6. **Debugging Commands** — kubectl commands for troubleshooting API issues\n7. **Monitoring Configuration** — Prometheus metrics for API server and webhook performance\n\n---\n\n## Related Skills\n\n| Skill | Purpose |\n|---|---|\n| `cncf-kubernetes-debugging` | Advanced debugging techniques for Kubernetes clusters, including API server logs, etcd inspection, and network troubleshooting |\n| `coding-grpc-patterns` | gRPC communication patterns for webhook implementations and client-server interactions |\n| `cncf-tekton` | Tekton Pipelines integration with custom resources for CI/CD workflows |\n| `cncf-prometheus` | Monitoring Kubernetes API servers and custom resources with Prometheus metrics |\n\n---\n\n## References\n\n- [Kubernetes API Documentation](https://kubernetes.io/docs/reference/using-api/)\n- [Custom Resource Definitions](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/)\n- [Admission Webhooks](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/)\n- [API Versioning](https://kubernetes.io/docs/reference/using-api/#api-versioning)\n- [Client Libraries](https://kubernetes.io/docs/reference/using-api/client-libraries/)\n- [Watching Resources](https://kubernetes.io/docs/reference/using-api/api-concepts/#watching)\n\n---\n\n## Live References\n\n> **These links are resolved at load time** — content is fetched and inlined to provide the latest documentation.\n\n- [Kubernetes API Reference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/)\n- [Kubernetes API Concepts](https://kubernetes.io/docs/reference/using-api/)\n- [Kubernetes API Conventions](https://kubernetes.io/docs/reference/using-api/api-conventions/)\n- [Kubernetes API Machinery](https://kubernetes.io/docs/reference/using-api/api-conventions/#resources)\n- [Kubernetes Admission Controllers](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/)\n- [Kubernetes CRD Reference](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/)\n","licenseDocuments":[]}}
L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Déploiement et opérations · SKILL.md

kubernetes-api-patterns

kubernetes-api-patterns

Kubernetes API patterns including CRD development, webhook implementation, API groups, client library usage, and debugging techniques for custom API extensions

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentation de la Skill

GitHub

name: kubernetes-api-patterns compatibility: opencode completeness: 95 content-types:

  • guidance
  • examples
  • do-dont
  • config description: Kubernetes API patterns including CRD development, webhook implementation, API groups, client library usage, and debugging techniques for custom API extensions license: MIT maturity: stable metadata: domain: cncf output-format: code related-skills: cncf-kubernetes-debugging, cncf-tekton, coding-grpc-patterns role: implementation scope: implementation triggers: kubernetes api, k8s api, crd development, api groups, subresources, watch api, admission webhooks, client libraries archetypes:
    • tactical anti_triggers:
    • brainstorming
    • vague ideation
    • non-containerized architecture response_profile: verbosity: low directive_strength: high abstraction_level: operational version: "1.0.0"

Kubernetes API Patterns

Implement Kubernetes API patterns including Custom Resource Definition (CRD) development, webhook implementation, API groups, client library usage, and debugging techniques for custom API extensions.

TL;DR Checklist

  • Define CRD API groups, versions, and resources following Kubernetes naming conventions
  • Implement validation and conversion webhooks with proper admission review handling
  • Choose correct client library (Go, Python, Java) based on deployment environment
  • Use watch API with resource versions for efficient event streaming
  • Debug API issues with kubectl explain, kubectl get --raw, and api-resources
  • Migrate resources between API versions using conversion webhooks or sidecars
  • Configure API server settings for custom resource scalability
  • Implement subresources (status, scale) following Kubernetes patterns

When to Use

Use this skill when:

  • Developing Custom Resource Definitions (CRDs) for domain-specific APIs
  • Implementing admission webhooks (validating/mutating) for policy enforcement
  • Creating client applications that interact with Kubernetes API resources
  • Debugging API server issues or custom resource behavior
  • Migrating resources between API versions during API evolution
  • Extending Kubernetes with subresources (status, scale, scale status)
  • Building controllers or operators that watch and reconcile custom resources

When NOT to Use

Avoid this skill for:

  • Simple application deployment — Use Helm charts or kubectl apply instead of custom APIs
  • Basic RBAC configuration — Use Kubernetes built-in roles instead of custom admission webhooks
  • Logging or monitoring — Use dedicated operators like Prometheus Operator instead of custom APIs
  • Serverless function triggers — Use Knative or eventing frameworks instead of CRDs
  • Network policy management — Use CNI plugins with built-in policy support
  • Large-scale batch processing — Use Tekton pipelines or Spark operators instead of custom CRDs

Core Workflow

  1. Analyze API Requirements — Identify the domain objects, their relationships, and required operations. Checkpoint: Have you defined the API group, version, and resource names following Kubernetes conventions?

  2. Design CRD Specification — Define the schema, validation rules, and subresources. Checkpoint: Does the schema prevent invalid states through type definitions?

  3. Implement Webhooks (if needed) — Create admission webhooks for validation and mutation. Checkpoint: Do webhooks return clear admission review responses with proper error messages?

  4. Choose Client Library — Select Go client for controllers, Python/Java for external tools. Checkpoint: Is the client version compatible with the Kubernetes cluster version?

  5. Test API Interactions — Use kubectl and curl to verify API behavior. Checkpoint: Do all CRUD operations and watch functionality work as expected?

  6. Debug and Optimize — Identify performance bottlenecks and fix issues. Checkpoint: Is the watch API using resource versions correctly to avoid missed events?


Implementation Patterns

Pattern 1: API Group Structure

Kubernetes organizes APIs into groups, versions, and resources. This structure enables versioning and deprecation.

BAD — Inconsistent naming and versioning:

# ❌ BAD — Mixed snake_case and camelCase, unclear versioning
apiVersion: myapp.io/v1beta1
kind: MyAppDeployment
spec:
  replicas: 3

GOOD — Proper group naming and versioning:

# ✅ GOOD — DNS-style group name, clear versioning
apiVersion: mycompany.com/v1
kind: MyAppDeployment
spec:
  replicas: 3

API Resource Structure:

API Group: mycompany.com
  Version: v1
    Resource: myappdeployments
      Scope: Namespaced

kubectl commands for API discovery:

# List all API groups
kubectl api-groups

# List resources in a specific group
kubectl api-resources --api-group=mycompany.com

# Show API version for a resource
kubectl explain myappdeployment --api-version=mycompany.com/v1

# Check if CRD is established
kubectl get crd myappdeployments.mycompany.com -o jsonpath='{.status.conditions[?(@.type=="Established")].status}'

# List all resources across all groups
kubectl api-resources

# Get specific API version info
kubectl get --raw /apis/mycompany.com/v1

Pattern 2: CRD Definition with Validation

Custom Resource Definitions must include proper schema validation to prevent invalid states.

BAD — Missing schema validation:

# ❌ BAD — No validation allows invalid configurations
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: myappdeployments.mycompany.com
spec:
  group: mycompany.com
  names:
    kind: MyAppDeployment
    plural: myappdeployments
  scope: Namespaced
  versions:
    - name: v1
      served: true
      storage: true
      schema: {}  # ❌ Empty schema allows any field

GOOD — Complete schema with validation:

# ✅ GOOD — Schema enforces valid configurations
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: myappdeployments.mycompany.com
spec:
  group: mycompany.com
  names:
    kind: MyAppDeployment
    plural: myappdeployments
  scope: Namespaced
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              required:
                - replicas
                - image
              properties:
                replicas:
                  type: integer
                  minimum: 1
                  maximum: 100
                image:
                  type: string
                  pattern: "^[a-z0-9]+([._-][a-z0-9]+)*(/[a-z0-9]+([._-][a-z0-9]+)*)*(:[a-z0-9]+([._-][a-z0-9]+)*)?(@[a-f0-9]{64})?quot;
                resources:
                  type: object
                  properties:
                    limits:
                      type: object
                      properties:
                        memory:
                          type: string
                          pattern: "^[0-9]+(Mi|Gi|Ti)quot;
                        cpu:
                          type: string
                          pattern: "^[0-9]+(m|)quot;
                env:
                  type: array
                  items:
                    type: object
                    properties:
                      name:
                        type: string
                      value:
                        type: string

Create and verify CRD:

# Create CRD
kubectl apply -f myapp-crd.yaml

# Verify CRD is established
kubectl get crd myappdeployments.mycompany.com -o jsonpath='{.status.conditions[?(@.type=="Established")].status}'

# List instances
kubectl get myappdeployments

# Describe CRD to see schema
kubectl describe crd myappdeployments.mycompany.com

# Validate CRD YAML before applying
kubectl apply --dry-run=client -f myapp-crd.yaml

Pattern 3: Validating Webhook Implementation

Admission webhooks intercept API requests to enforce custom policies.

BAD — Webhook with no error handling:

// ❌ BAD — No proper admission review response
func (h *ValidatingWebhook) Handle(ctx context.Context, req admission.Request) admission.Response {
    obj := &myappv1.MyAppDeployment{}
    if err := h.decoder.Decode(req.AdmissionReview.Spec.Object, obj); err != nil {
        return admission.Errored(http.StatusBadRequest, err)
    }
    
    // ❌ Missing proper validation response
    if obj.Spec.Replicas > 100 {
        // No admission response with denial
        return admission.Allowed("allowed")
    }
    
    return admission.Allowed("validation passed")
}

GOOD — Complete webhook with proper responses:

// ✅ GOOD — Proper admission review with clear deny messages
package webhook

import (
    "context"
    "fmt"
    "net/http"
    
    appsv1 "mycompany.com/api/v1"
    admissionv1 "k8s.io/api/admission/v1"
    admission "k8s.io/pod-security-admission/admission"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type ValidatingWebhook struct {
    decoder *admission.Decoder
}

func (h *ValidatingWebhook) Handle(ctx context.Context, req admission.Request) admission.Response {
    obj := &appsv1.MyAppDeployment{}
    if err := h.decoder.Decode(req.AdmissionReview.Spec.Object, obj); err != nil {
        return admission.Errored(http.StatusBadRequest, err)
    }

    // Validate replicas
    if obj.Spec.Replicas > 100 {
        return admission.Denied("replicas must be between 1 and 100")
    }

    // Validate image format
    if !isValidImage(obj.Spec.Image) {
        return admission.Denied(fmt.Sprintf("invalid image format: %s", obj.Spec.Image))
    }

    // Validate resource constraints
    if !h.hasResourceLimits(obj) {
        return admission.Denied("resource limits are required")
    }

    return admission.Allowed("validation passed")
}

func isValidImage(image string) bool {
    // Regex pattern for valid container image names
    pattern := `^[a-z0-9]+([._-][a-z0-9]+)*(/[a-z0-9]+([._-][a-z0-9]+)*)*(:[a-z0-9]+([._-][a-z0-9]+)*)?(@[a-f0-9]{64})?

  
    
    
    kubernetes-api-patterns · Skills-SI Market
    
    
    
    
    
    
    
    
  
  
    
// Implementation details... return true } func (h *ValidatingWebhook) hasResourceLimits(obj *appsv1.MyAppDeployment) bool { return obj.Spec.Resources.Limits != nil } // admission review response with uid and status func admissionResponse(uid string, allowed bool, message string) admission.Response { return admission.Response{ UID: uid, Allowed: allowed, Result: &metav1.Status{ Message: message, }, } }

Deploy webhook with service and certificate:

# Create TLS secret for webhook
openssl req -x509 -newkey rsa:4096 -keyout webhook-key.pem -out webhook-cert.pem -days 365 -nodes \
  -subj "/CN=myapp-validating-webhook.default.svc"

kubectl create secret generic webhook-tls --from-file=cert.pem=webhook-cert.pem --from-file=key.pem=webhook-key.pem

# Apply webhook configuration
kubectl apply -f validating-webhook.yaml

# Verify webhook is registered
kubectl get validatingwebhookconfigurations

# Test webhook manually
kubectl apply -f test-deployment.yaml --dry-run=client

# Check webhook logs
kubectl logs -n default deploy/myapp-validating-webhook

Pattern 4: Client Library Usage (Go)

The Go client is the standard for Kubernetes controllers and operators.

BAD — Inefficient client usage:

// ❌ BAD — Creates new client for each operation
func getDeployment(clientset *kubernetes.Clientset, name string) (*appsv1.Deployment, error) {
    // ❌ Creates new RESTClient each time
    config, _ := rest.InClusterConfig()
    client, _ := kubernetes.NewForConfig(config)
    
    return client.AppsV1().Deployments("default").Get(context.TODO(), name, metav1.GetOptions{})
}

GOOD — Reusing client with proper error handling:

// ✅ GOOD — Single client instance with proper configuration
package client

import (
    "context"
    "fmt"
    
    appsv1 "k8s.io/api/apps/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/rest"
)

var clientset *kubernetes.Clientset

// InitClient initializes the client once
func InitClient() error {
    config, err := rest.InClusterConfig()
    if err != nil {
        return fmt.Errorf("failed to get in-cluster config: %w", err)
    }
    
    clientset, err = kubernetes.NewForConfig(config)
    if err != nil {
        return fmt.Errorf("failed to create client: %w", err)
    }
    
    return nil
}

// GetDeployment retrieves a deployment with proper error handling
func GetDeployment(ctx context.Context, namespace, name string) (*appsv1.Deployment, error) {
    if clientset == nil {
        return nil, fmt.Errorf("client not initialized")
    }
    
    deployment, err := clientset.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})
    if err != nil {
        return nil, fmt.Errorf("failed to get deployment %s/%s: %w", namespace, name, err)
    }
    
    return deployment, nil
}

// CreateDeployment creates a new deployment
func CreateDeployment(ctx context.Context, namespace string, deployment *appsv1.Deployment) (*appsv1.Deployment, error) {
    if clientset == nil {
        return nil, fmt.Errorf("client not initialized")
    }
    
    result, err := clientset.AppsV1().Deployments(namespace).Create(ctx, deployment, metav1.CreateOptions{})
    if err != nil {
        return nil, fmt.Errorf("failed to create deployment: %w", err)
    }
    
    return result, nil
}

// UpdateDeployment updates an existing deployment
func UpdateDeployment(ctx context.Context, namespace string, deployment *appsv1.Deployment) (*appsv1.Deployment, error) {
    if clientset == nil {
        return nil, fmt.Errorf("client not initialized")
    }
    
    result, err := clientset.AppsV1().Deployments(namespace).Update(ctx, deployment, metav1.UpdateOptions{})
    if err != nil {
        return nil, fmt.Errorf("failed to update deployment: %w", err)
    }
    
    return result, nil
}

// DeleteDeployment deletes a deployment
func DeleteDeployment(ctx context.Context, namespace, name string) error {
    if clientset == nil {
        return fmt.Errorf("client not initialized")
    }
    
    err := clientset.AppsV1().Deployments(namespace).Delete(ctx, name, metav1.DeleteOptions{})
    if err != nil {
        return fmt.Errorf("failed to delete deployment %s/%s: %w", namespace, name, err)
    }
    
    return nil
}

CRD client with dynamic client:

// ✅ GOOD — Dynamic client for unknown resource types
package client

import (
    "context"
    "fmt"
    
    apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    "k8s.io/apimachinery/pkg/api/meta"
    "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    "k8s.io/apimachinery/pkg/runtime/schema"
    "k8s.io/client-go/dynamic"
    "k8s.io/client-go/rest"
)

var dynamicClient dynamic.Interface

// InitDynamicClient initializes dynamic client for CRDs
func InitDynamicClient() error {
    config, err := rest.InClusterConfig()
    if err != nil {
        return fmt.Errorf("failed to get in-cluster config: %w", err)
    }
    
    dynamicClient, err = dynamic.NewForConfig(config)
    if err != nil {
        return fmt.Errorf("failed to create dynamic client: %w", err)
    }
    
    return nil
}

func getCustomResource(client dynamic.Interface, gvr schema.GroupVersionResource, namespace, name string) (*unstructured.Unstructured, error) {
    resource, err := client.Resource(gvr).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{})
    if err != nil {
        return nil, fmt.Errorf("failed to get %s/%s: %w", gvr.Resource, name, err)
    }
    
    return resource, nil
}

// ListCustomResources lists all instances of a CRD
func ListCustomResources(gvr schema.GroupVersionResource, namespace string) (*unstructured.UnstructuredList, error) {
    if dynamicClient == nil {
        return nil, fmt.Errorf("dynamic client not initialized")
    }
    
    list, err := dynamicClient.Resource(gvr).Namespace(namespace).List(context.TODO(), metav1.ListOptions{})
    if err != nil {
        return nil, fmt.Errorf("failed to list %s: %w", gvr.Resource, err)
    }
    
    return list, nil
}

// UpdateCustomResourceStatus updates the status subresource
func UpdateCustomResourceStatus(gvr schema.GroupVersionResource, namespace, name string, status interface{}) (*unstructured.Unstructured, error) {
    if dynamicClient == nil {
        return nil, fmt.Errorf("dynamic client not initialized")
    }
    
    // Get current resource
    obj, err := dynamicClient.Resource(gvr).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{})
    if err != nil {
        return nil, err
    }
    
    // Update status
    err = obj.Object["status"].(map[string]interface{})
    obj.Object["status"] = status
    
    return dynamicClient.Resource(gvr).Namespace(namespace).UpdateStatus(context.TODO(), obj, metav1.UpdateOptions{})
}

Pattern 5: Watch API with Resource Versions

The watch API efficiently streams resource changes.

BAD — Watch without resource version:

// ❌ BAD — Watch without resource version can miss events
func watchDeployments(client appsv1client.AppsV1Interface) {
    // ❌ No resourceVersion - can miss events during restart
    watcher, err := client.Deployments("default").Watch(context.TODO(), metav1.ListOptions{})
    if err != nil {
        panic(err)
    }
    
    for event := range watcher.ResultChan() {
        // Process event
        deployment := event.Object.(*appsv1.Deployment)
        fmt.Printf("Event: %s %s\n", event.Type, deployment.Name)
    }
}

GOOD — Watch with resource version tracking:

// ✅ GOOD — Watch with proper resource version handling
package watcher

import (
    "context"
    "fmt"
    "time"
    
    appsv1 "k8s.io/api/apps/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/watch"
)

// WatchDeployments watches deployments with resync capability
func WatchDeployments(client appsv1client.AppsV1Interface, resyncPeriod time.Duration) error {
    // Get current resource version
    list, err := client.Deployments("default").List(context.TODO(), metav1.ListOptions{Limit: 1})
    if err != nil {
        return fmt.Errorf("failed to list deployments: %w", err)
    }
    
    var resourceVersion string
    if len(list.Items) > 0 {
        resourceVersion = list.ListMeta.ResourceVersion
    }
    
    // Start watch with resource version
    watcher, err := client.Deployments("default").Watch(context.TODO(), metav1.ListOptions{
        ResourceVersion: resourceVersion,
        TimeoutSeconds:  ptr[int64](300), // 5 minute timeout
    })
    if err != nil {
        return fmt.Errorf("failed to start watch: %w", err)
    }
    
    // Handle events
    go func() {
        for event := range watcher.ResultChan() {
            deployment := event.Object.(*appsv1.Deployment)
            fmt.Printf("Event: %s %s (resourceVersion: %s)\n", 
                event.Type, deployment.Name, deployment.ResourceVersion)
        }
    }()
    
    // Periodic resync to catch missed events
    go func() {
        ticker := time.NewTicker(resyncPeriod)
        defer ticker.Stop()
        
        for range ticker.C {
            performResync(client)
        }
    }()
    
    return nil
}

// RestartWatch restarts watch with latest resource version
func restartWatch(client appsv1client.AppsV1Interface) error {
    list, err := client.Deployments("default").List(context.TODO(), metav1.ListOptions{Limit: 1})
    if err != nil {
        return err
    }
    
    var resourceVersion string
    if len(list.Items) > 0 {
        resourceVersion = list.ListMeta.ResourceVersion
    }
    
    watcher, err := client.Deployments("default").Watch(context.TODO(), metav1.ListOptions{
        ResourceVersion: resourceVersion,
        TimeoutSeconds:  ptr[int64](300),
    })
    if err != nil {
        return err
    }
    
    // Continue processing...
    return nil
}

func ptr[T any](v T) *T {
    return &v
}

func performResync(client appsv1client.AppsV1Interface) {
    deployments, err := client.Deployments("default").List(context.TODO(), metav1.ListOptions{})
    if err != nil {
        fmt.Printf("Resync failed: %v\n", err)
        return
    }
    
    for _, d := range deployments.Items {
        fmt.Printf("Resynced deployment: %s (replicas: %d)\n", d.Name, d.Spec.Replicas)
    }
}

Debug watch issues with kubectl:

# Watch events in a namespace
kubectl get events -n default --watch

# Get resource version for debugging
kubectl get deployments -o jsonpath='{.metadata.resourceVersion}'

# Watch with specific resource version
kubectl get deployments --watch --resource-version=<version>

# Check API server logs for watch errors
kubectl logs -n kube-system kube-apiserver-<node> | grep -i watch

# Test watch with curl
kubectl proxy &
curl -s --raw "http://localhost:8001/api/v1/namespaces/default/pods?watch=true&resourceVersion=0" &

Pattern 6: Subresource Implementation

Subresources extend resources with additional endpoints (status, scale).

BAD — Incomplete subresource configuration:

# ❌ BAD — Missing subresource specification
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: myappdeployments.mycompany.com
spec:
  group: mycompany.com
  names:
    kind: MyAppDeployment
    plural: myappdeployments
  scope: Namespaced
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                replicas:
                  type: integer
            status:
              type: object
              # ❌ Status not defined as subresource

GOOD — Complete subresource configuration:

# ✅ GOOD — Status and scale subresources properly defined
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: myappdeployments.mycompany.com
spec:
  group: mycompany.com
  names:
    kind: MyAppDeployment
    plural: myappdeployments
  scope: Namespaced
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                replicas:
                  type: integer
                  minimum: 1
                  maximum: 100
            status:
              type: object
              properties:
                replicas:
                  type: integer
                availableReplicas:
                  type: integer
                conditions:
                  type: array
                  items:
                    type: object
                    properties:
                      type:
                        type: string
                      status:
                        type: string
                      reason:
                        type: string
                      message:
                        type: string
                      lastTransitionTime:
                        type: string
                        format: date-time
      subresources:
        status: {}  # ✅ Enables /status endpoint
        scale:
          specReplicasPath: .spec.replicas  # ✅ Enables /scale endpoint
          statusReplicasPath: .status.replicas
  additionalPrinterColumns:
    - name: Replicas
      type: integer
      description: Number of replicas
      jsonPath: .spec.replicas
    - name: Status
      type: string
      description: Current status
      jsonPath: .status.conditions[?(@.type=="Ready")].status
    - name: Age
      type: date
      jsonPath: .metadata.creationTimestamp

Subresource operations:

# Update status subresource
kubectl patch myappdeployment myapp --type=merge -p '
{
  "status": {
    "replicas": 3,
    "availableReplicas": 3,
    "conditions": [{"type": "Ready", "status": "True"}]
  }
}'

# Get scale subresource
kubectl get myappdeployment myapp -o jsonpath='{.spec.replicas}'

# Update scale subresource
kubectl patch myappdeployment myapp --type=merge -p '{"spec":{"replicas":5}}'

# Use kubectl scale command
kubectl scale myappdeployment myapp --replicas=5

# Get status subresource directly
kubectl get myappdeployment myapp -o json | jq '.status'

# Watch status changes
kubectl get myappdeployment myapp --watch --field-selector=metadata.name=myapp

Pattern 7: API Conversion Webhooks

Conversion webhooks enable smooth API version transitions.

BAD — No conversion webhook for version migration:

# ❌ BAD — Missing conversion configuration
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: myappdeployments.mycompany.com
spec:
  group: mycompany.com
  names:
    kind: MyAppDeployment
    plural: myappdeployments
  scope: Namespaced
  versions:
    - name: v1beta1
      served: true
      storage: false
    - name: v1
      served: true
      storage: true
      # ❌ No conversion webhook - manual migration required

GOOD — Complete conversion webhook setup:

# ✅ GOOD — Conversion webhook for API version migration
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: myappdeployments.mycompany.com
spec:
  group: mycompany.com
  names:
    kind: MyAppDeployment
    plural: myappdeployments
  scope: Namespaced
  versions:
    - name: v1beta1
      served: true
      storage: false
    - name: v1
      served: true
      storage: true
  conversion:
    strategy: Webhook
    webhook:
      clientConfig:
        service:
          name: myapp-conversion-webhook
          namespace: default
          path: /convert
        caBundle: <base64-encoded-ca-cert>
      conversionReviewVersions:
        - v1
        - v1beta1

Conversion webhook implementation:

// ✅ GOOD — Webhook handles conversion between versions
package conversion

import (
    "context"
    "fmt"
    
    myappv1 "mycompany.com/api/v1"
    myappv1beta1 "mycompany.com/api/v1beta1"
    apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    admission "k8s.io/pod-security-admission/admission"
)

type ConversionWebhook struct {
    decoder *admission.Decoder
}

func (h *ConversionWebhook) Handle(ctx context.Context, req admission.Request) admission.Response {
    review := &apiextensionsv1.ConversionReview{}
    if err := h.decoder.Decode(req.AdmissionReview.Spec.Object, review); err != nil {
        return admission.Errored(http.StatusBadRequest, err)
    }

    response := &apiextensionsv1.ConversionResponse{
        Uid: review.Request.UID,
    }

    switch review.Request.DesiredAPIVersion {
    case "mycompany.com/v1":
        response.ConvertedObjects, response.Status = h.convertToV1(review.Request.Objects)
    case "mycompany.com/v1beta1":
        response.ConvertedObjects, response.Status = h.convertToV1Beta1(review.Request.Objects)
    default:
        response.Status = &metav1.Status{
            Status:  metav1.StatusFailure,
            Code:    http.StatusBadRequest,
            Message: fmt.Sprintf("unsupported API version: %s", review.Request.DesiredAPIVersion),
        }
        return admission.Response{UID: review.Request.UID, Allowed: false, Result: response.Status}
    }

    return admission.Response{UID: review.Request.UID, Allowed: true, Result: response}
}

func (h *ConversionWebhook) convertToV1(objects []runtime.RawExtension) ([]runtime.RawExtension, *metav1.Status) {
    var converted []runtime.RawExtension
    
    for _, obj := range objects {
        v1beta1Obj := &myappv1beta1.MyAppDeployment{}
        if err := json.Unmarshal(obj.Raw, v1beta1Obj); err != nil {
            return nil, &metav1.Status{
                Status:  metav1.StatusFailure,
                Code:    http.StatusBadRequest,
                Message: fmt.Sprintf("failed to unmarshal v1beta1 object: %v", err),
            }
        }
        
        v1Obj := convertV1Beta1ToV1(v1beta1Obj)
        data, err := json.Marshal(v1Obj)
        if err != nil {
            return nil, &metav1.Status{
                Status:  metav1.StatusFailure,
                Code:    http.StatusInternalServerError,
                Message: fmt.Sprintf("failed to marshal v1 object: %v", err),
            }
        }
        
        converted = append(converted, runtime.RawExtension{Raw: data})
    }
    
    return converted, nil
}

func (h *ConversionWebhook) convertToV1Beta1(objects []runtime.RawExtension) ([]runtime.RawExtension, *metav1.Status) {
    var converted []runtime.RawExtension
    
    for _, obj := range objects {
        v1Obj := &myappv1.MyAppDeployment{}
        if err := json.Unmarshal(obj.Raw, v1Obj); err != nil {
            return nil, &metav1.Status{
                Status:  metav1.StatusFailure,
                Code:    http.StatusBadRequest,
                Message: fmt.Sprintf("failed to unmarshal v1 object: %v", err),
            }
        }
        
        v1beta1Obj := convertV1ToV1Beta1(v1Obj)
        data, err := json.Marshal(v1beta1Obj)
        if err != nil {
            return nil, &metav1.Status{
                Status:  metav1.StatusFailure,
                Code:    http.StatusInternalServerError,
                Message: fmt.Sprintf("failed to marshal v1beta1 object: %v", err),
            }
        }
        
        converted = append(converted, runtime.RawExtension{Raw: data})
    }
    
    return converted, nil
}

func convertV1Beta1ToV1(v1beta1Obj *myappv1beta1.MyAppDeployment) *myappv1.MyAppDeployment {
    return &myappv1.MyAppDeployment{
        TypeMeta: v1beta1Obj.TypeMeta,
        ObjectMeta: v1beta1Obj.ObjectMeta,
        Spec: myappv1.MyAppDeploymentSpec{
            Replicas:  v1beta1Obj.Spec.Replicas,
            Image:     v1beta1Obj.Spec.Image,
            Resources: convertResources(v1beta1Obj.Spec.Resources),
        },
        Status: myappv1.MyAppDeploymentStatus{
            Replicas:            v1beta1Obj.Status.Replicas,
            AvailableReplicas:   v1beta1Obj.Status.AvailableReplicas,
            Conditions:          convertConditions(v1beta1Obj.Status.Conditions),
        },
    }
}

func convertV1ToV1Beta1(v1Obj *myappv1.MyAppDeployment) *myappv1beta1.MyAppDeployment {
    return &myappv1beta1.MyAppDeployment{
        TypeMeta: v1Obj.TypeMeta,
        ObjectMeta: v1Obj.ObjectMeta,
        Spec: myappv1beta1.MyAppDeploymentSpec{
            Replicas:  v1Obj.Spec.Replicas,
            Image:     v1Obj.Spec.Image,
            Resources: convertResourcesBack(v1Obj.Spec.Resources),
        },
        Status: myappv1beta1.MyAppDeploymentStatus{
            Replicas:            v1Obj.Status.Replicas,
            AvailableReplicas:   v1Obj.Status.AvailableReplicas,
            Conditions:          convertConditionsBack(v1Obj.Status.Conditions),
        },
    }
}

Test conversion:

# Get resource in v1beta1
kubectl get myappdeployment myapp -o json --api-version=mycompany.com/v1beta1

# Get same resource in v1 (triggers conversion webhook)
kubectl get myappdeployment myapp -o json --api-version=mycompany.com/v1

# Verify conversion in etcd
kubectl get myappdeployment myapp -o yaml

# Test conversion with curl
kubectl proxy &
curl -s -X POST -H "Content-Type: application/json" \
  --data '{"apiVersion":"apiextensions.k8s.io/v1","kind":"ConversionReview","request":{"uid":"test","desiredAPIVersion":"mycompany.com/v1","objects":[]}}' \
  http://localhost:8001/apis/mycompany.com/v1/convert

Pattern 8: API Server Configuration

Proper API server configuration ensures custom resources work efficiently.

BAD — Default API server settings for high-volume CRs:

# ❌ BAD — No API server tuning for custom resources
apiVersion: v1
kind: ConfigMap
metadata:
  name: kube-apiserver
  namespace: kube-system
data:
  kube-apiserver.conf: |
    # ❌ Using default settings
    kube-apiserver \
      --etcd-servers=http://127.0.0.1:2379 \
      --enable-admission-plugins=NamespaceLifecycle,LimitRanger

GOOD — Optimized API server configuration:

# ✅ GOOD — Tuned for custom resource performance
apiVersion: v1
kind: ConfigMap
metadata:
  name: kube-apiserver
  namespace: kube-system
data:
  kube-apiserver.conf: |
    kube-apiserver \
      --etcd-servers=https://127.0.0.1:2379 \
      --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt \
      --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt \
      --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key \
      --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,ValidatingAdmissionWebhook,MutatingAdmissionWebhook \
      --enable-bootstrap-token-auth=true \
      --max-requests-inflight=2000 \
      --max-requests-inflight=1500 \
      --max-requests-inflight=1000 \
      --max-requests-inflight=500 \
      --request-timeout=300s \
      --allow-privileged=true \
      --authorization-mode=Node,RBAC \
      --client-ca-file=/etc/kubernetes/pki/ca.crt \
      --enable-swagger-ui=true \
      --kubelet-certificate-authority=/etc/kubernetes/pki/ca.crt \
      --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt \
      --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key \
      --runtime-config=api/all=true \
      --service-account-issuer=https://kubernetes.default.svc.cluster.local \
      --service-account-key-file=/etc/kubernetes/pki/sa.pub \
      --service-account-signing-key-file=/etc/kubernetes/pki/sa.key \
      --tls-cert-file=/etc/kubernetes/pki/apiserver.crt \
      --tls-private-key-file=/etc/kubernetes/pki/apiserver.key

# Optimized etcd configuration for CRDs
  etcd.conf: |
    # Optimize etcd for CRD storage
    --auto-compaction-mode=periodic
    --auto-compaction-retention=1h
    --quota-backend-bytes=8589934592  # 8GB
    --max-request-bytes=15728640  # 15MB
    --listen-client-urls=https://0.0.0.0:2379
    --listen-peer-urls=https://0.0.0.0:2380

CRD controller configuration:

// ✅ GOOD — Controller with proper rate limiting and resync
package controller

import (
    "context"
    "time"
    
    "github.com/go-logr/logr"
    "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    "k8s.io/apimachinery/pkg/runtime/schema"
    "k8s.io/apimachinery/pkg/util/runtime"
    "k8s.io/apimachinery/pkg/util/wait"
    "k8s.io/client-go/tools/cache"
    "k8s.io/client-go/util/workqueue"
)

type Controller struct {
    client    dynamic.Interface
    informer  cache.SharedIndexInformer
    queue     workqueue.RateLimitingInterface
    logger    logr.Logger
    gvr       schema.GroupVersionResource
}

func NewController(client dynamic.Interface, informer cache.SharedIndexInformer, gvr schema.GroupVersionResource, logger logr.Logger) *Controller {
    return &Controller{
        client:  client,
        informer: informer,
        queue:   workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
        logger:  logger,
        gvr:     gvr,
    }
}

func (c *Controller) Start(stopCh <-chan struct{}) error {
    // Set up event handlers
    c.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
        AddFunc:    c.onAdd,
        UpdateFunc: c.onUpdate,
        DeleteFunc: c.onDelete,
    })
    
    // Start informer
    go c.informer.Run(stopCh)
    
    // Process work items
    c.processWorkItems(stopCh)
    
    return nil
}

func (c *Controller) processWorkItems(stopCh <-chan struct{}) {
    for {
        select {
        case <-stopCh:
            return
        default:
            obj, shutdown := c.queue.Get()
            if shutdown {
                return
            }
            
            if err := c.reconcile(obj); err != nil {
                c.logger.Error(err, "reconciliation failed")
                if c.queue.NumRequeues(obj) < 5 {
                    c.queue.AddRateLimited(obj)
                } else {
                    c.queue.Forget(obj)
                    runtime.HandleError(err)
                }
            } else {
                c.queue.Forget(obj)
            }
        }
    }
}

func (c *Controller) reconcile(obj interface{}) error {
    defer c.queue.Done(obj)
    
    key, ok := obj.(string)
    if !ok {
        return nil
    }
    
    namespace, name, err := cache.SplitMetaNamespaceKey(key)
    if err != nil {
        return err
    }
    
    gvr := c.gvr
    resource, err := c.client.Resource(gvr).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{})
    if err != nil {
        if apierrors.IsNotFound(err) {
            return nil // Object deleted
        }
        return err
    }
    
    // Reconciliation logic here
    return nil
}

func (c *Controller) onAdd(obj interface{}) {
    key, err := cache.MetaNamespaceKeyFunc(obj)
    if err != nil {
        runtime.HandleError(err)
        return
    }
    c.queue.Add(key)
}

func (c *Controller) onUpdate(old, new interface{}) {
    key, err := cache.MetaNamespaceKeyFunc(new)
    if err != nil {
        runtime.HandleError(err)
        return
    }
    c.queue.Add(key)
}

func (c *Controller) onDelete(obj interface{}) {
    key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
    if err != nil {
        runtime.HandleError(err)
        return
    }
    c.queue.Add(key)
}

Constraints

MUST DO

  • Use DNS-style naming for API groups (e.g., mycompany.com, k8s.io)
  • Define strict schema validation to prevent invalid states (Parse, Don't Validate)
  • Implement admission webhooks with clear, actionable error messages
  • Use resource versions in watch API to avoid missing events
  • Include status subresource for controller-managed fields
  • Handle conversion webhooks gracefully during API version migrations
  • Set appropriate API server request limits based on CRD volume
  • Test CRD upgrades with existing resources before production deployment
  • Validate webhook TLS certificates before deployment
  • Implement proper RBAC for webhook service accounts
  • Use admission webhooks for validation, not business logic

MUST NOT DO

  • Use reserved API groups like k8s.io, kubernetes.io for custom resources
  • Skip schema validation even for "internal" CRDs
  • Return generic error messages from admission webhooks
  • Watch without resource version tracking in production
  • Modify CRD schema in ways that break existing resources
  • Deploy webhooks without TLS certificates
  • Set replicas too high in CRD without proper API server tuning
  • Use v1beta1 as storage version (always use stable v1)
  • Skip webhook timeout handling (default 10s, max 30s)
  • Block webhook operations for more than timeout duration

Output Template

When implementing Kubernetes API patterns, include:

  1. CRD Specification — Complete CRD definition with schema, validation, and subresources
  2. Webhook Configuration — Validating/mutating webhook setup with service and certificate
  3. Client Code — Go client or other language client for resource operations
  4. Deployment Scripts — Helm charts or YAML manifests for webhook deployment
  5. Migration Plan — Steps for API version conversion and upgrade procedures
  6. Debugging Commands — kubectl commands for troubleshooting API issues
  7. Monitoring Configuration — Prometheus metrics for API server and webhook performance

Related Skills

SkillPurpose
cncf-kubernetes-debuggingAdvanced debugging techniques for Kubernetes clusters, including API server logs, etcd inspection, and network troubleshooting
coding-grpc-patternsgRPC communication patterns for webhook implementations and client-server interactions
cncf-tektonTekton Pipelines integration with custom resources for CI/CD workflows
cncf-prometheusMonitoring Kubernetes API servers and custom resources with Prometheus metrics

References


Live References

These links are resolved at load time — content is fetched and inlined to provide the latest documentation.

Skills enregistrées