[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6D1VWHMMF5DCWMHTQYHDQT7":3,"skill-same:01M6D1VWHMMF5DCWMHTQYHDQT7":74},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":28,"publishedAt":46,"linkOnly":47,"markdownContent":48,"markdownBody":49,"frontMatter":50,"refName":51,"commitSha":52,"skillDirectory":53,"forksCount":54,"repositoryHtmlUrl":55,"repositoryHomepageUrl":56,"ownerHtmlUrl":57,"ownerBlogUrl":56,"ownerDescription":58,"updatedAt":59,"install":60,"resources":73},"01M6D1VWHMMF5DCWMHTQYHDQT7","devops-kubernetes",null,"Kubernetes when it is warranted: Deployments with requests, limits, probes and security contexts, Services, Ingress and the Gateway API, ConfigMaps and Secrets with an external secret store, autoscaling, disruption budgets, Helm versus Kustomize, namespaces and RBAC, network policies, and GitOps. Read before writing or changing Kubernetes manifests or charts.","enowdev\u002Fenowxcli","enowdev","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F179420349?v=4","01M6D1MVX2KC0X3Z9NESE7P22K","Enow Dev.","01M6D1MW1FMZGBDKWAJY4GQX81",67,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fdevops-kubernetes\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000011","运维与部署","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45],"kubernetes","devops kubernetes","devops","rbac","gateway","services","network","api","secrets","Charts","helm","kustomize","read","security","deployments","gitops","writing","2026-11-10 10:12:30",false,"---\nname: devops-kubernetes\ndescription: \"Kubernetes when it is warranted: Deployments with requests, limits, probes and security contexts, Services, Ingress and the Gateway API, ConfigMaps and Secrets with an external secret store, autoscaling, disruption budgets, Helm versus Kustomize, namespaces and RBAC, network policies, and GitOps. Read before writing or changing Kubernetes manifests or charts.\"\n---\n\n# Kubernetes\n\nThe generated manifest is a Deployment of `app:latest` with one replica, no\nrequests or limits, no probes, running as root on a writable filesystem, next\nto a committed Secret that is only base64, a `LoadBalancer` Service per app,\nand `kubectl apply` from a laptop. It works until the first node drain, noisy\nneighbour or leaked repository. This is what a workload needs to survive\nrollouts, node loss and review, and how to package and ship it. One part of\ndevops; the whole is in the `devops` skill.\n\n## 1. When, and which cluster\n\n- Not for a few services run by a small team: a PaaS or ECS costs a fraction\n  to operate (`devops-platforms`). It pays off with many services and teams,\n  or where the organisation already runs it. Use a managed control plane\n  (GKE Autopilot or EKS Auto Mode when nobody wants to run nodes); k3s or\n  Talos only when self-hosting is a requirement.\n- Upstream is at 1.37 (August 2026), three minors a year, about 14 months of\n  patches each; managed providers trail by one or two. Upgrade at least\n  yearly, one minor at a time, after `kubent` or `pluto` finds removed APIs.\n- Ingress NGINX (`kubernetes\u002Fingress-nginx`) was retired in March 2026, with\n  no more fixes. Use the Gateway API with a maintained implementation (Envoy\n  Gateway, Cilium, Istio, NGINX Gateway Fabric, Traefik, Kong, the cloud's).\n\n## 2. The Deployment\n\n```yaml\napiVersion: apps\u002Fv1\nkind: Deployment\nmetadata:\n  name: api\n  labels: { app.kubernetes.io\u002Fname: api }\nspec:\n  replicas: 3\n  revisionHistoryLimit: 5\n  selector:\n    matchLabels: { app.kubernetes.io\u002Fname: api }\n  strategy:\n    rollingUpdate: { maxSurge: 25%, maxUnavailable: 0 }\n  template:\n    metadata:\n      labels: { app.kubernetes.io\u002Fname: api }\n    spec:\n      serviceAccountName: api\n      automountServiceAccountToken: false\n      terminationGracePeriodSeconds: 30\n      securityContext:\n        runAsNonRoot: true\n        runAsUser: 10001\n        runAsGroup: 10001\n        seccompProfile: { type: RuntimeDefault }\n      containers:\n        - name: api\n          image: ghcr.io\u002Facme\u002Fapi:1.4.2@sha256:\u003Cdigest>\n          ports: [{ name: http, containerPort: 8080 }]\n          envFrom:\n            - configMapRef: { name: api-config }\n            - secretRef: { name: api-secrets }\n          resources: { requests: { cpu: 250m, memory: 256Mi }, limits: { memory: 512Mi } }\n          startupProbe: { httpGet: { path: \u002Fhealthz, port: http }, periodSeconds: 5, failureThreshold: 30 }\n          readinessProbe: { httpGet: { path: \u002Freadyz, port: http }, periodSeconds: 5, failureThreshold: 3 }\n          livenessProbe: { httpGet: { path: \u002Fhealthz, port: http }, periodSeconds: 10, failureThreshold: 3 }\n          lifecycle: { preStop: { sleep: { seconds: 5 } } }\n          securityContext: { allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities: { drop: [\"ALL\"] } }\n          volumeMounts: [{ name: tmp, mountPath: \u002Ftmp }]\n      volumes: [{ name: tmp, emptyDir: {} }]\n      topologySpreadConstraints:\n        - maxSkew: 1\n          topologyKey: topology.kubernetes.io\u002Fzone\n          whenUnsatisfiable: ScheduleAnyway\n          labelSelector: { matchLabels: { app.kubernetes.io\u002Fname: api } }\n```\n\n- Requests always: scheduling and the autoscaler's maths use them. A memory\n  limit always, 1 to 2 times the request, with the runtime sized below it\n  (`--max-old-space-size`, `-XX:MaxRAMPercentage=75`, `GOMEMLIMIT`), since the\n  kernel kills the process at the limit. CPU limits rarely: throttling adds\n  latency even on an idle node; keep them for batch work or where policy\n  demands (Go 1.25 and later read the CPU limit for `GOMAXPROCS`).\n- Size requests from measurement (p95 over a week, VPA recommendations,\n  `kubectl top`); in-place resize is stable since 1.35.\n- Probes: startup covers the slowest boot (30 x 5s here); readiness checks\n  what a request needs, with a short timeout; liveness checks only the\n  process (`backend-observability`). No heavy work in any of them.\n- The `preStop` sleep (stable since 1.34, no `sleep` binary needed) lets\n  endpoints drop the pod before `SIGTERM`; the grace period covers the sleep\n  plus the app's drain (`devops-deploy`).\n- At least 2 replicas (3 for anything that matters) spread over zones, and no\n  `replicas` field once an HPA owns it (GitOps and the HPA would fight). The\n  image by digest, a numeric user matching the image (`devops-containers`).\n\n## 3. Services and the Gateway API\n\n```yaml\napiVersion: v1\nkind: Service\nmetadata: { name: api }\nspec:\n  selector: { app.kubernetes.io\u002Fname: api }\n  ports: [{ name: http, port: 80, targetPort: http }]\n---\napiVersion: gateway.networking.k8s.io\u002Fv1\nkind: HTTPRoute\nmetadata: { name: api }\nspec:\n  parentRefs: [{ name: public, namespace: gateway }]\n  hostnames: [\"api.example.com\"]\n  rules:\n    - matches: [{ path: { type: PathPrefix, value: \u002F } }]\n      backendRefs: [{ name: api, port: 80 }]\n```\n\n- `ClusterIP` Services inside and one Gateway outside, not a `LoadBalancer`\n  per app. The platform owns the Gateway (its namespace, HTTPS listeners,\n  `allowedRoutes`); app teams own their HTTPRoutes.\n- TLS through cert-manager: set `config.gatewayAPI.enabled=true` in its Helm\n  values, annotate the Gateway with `cert-manager.io\u002Fcluster-issuer:\n  letsencrypt`, and each HTTPS listener's `certificateRefs` names the Secret\n  it fills. On an Ingress, the same annotation plus a `tls:` block.\n- Timeouts, body limits and rate limits are per implementation (policies or\n  filters); set them for uploads and long requests (`devops-networking`).\n\n## 4. Configuration and secrets\n\n- A ConfigMap change must roll the pods: Kustomize's `configMapGenerator`\n  hashes the name; Helm charts add a `checksum\u002Fconfig` pod annotation.\n- A Secret is base64, not encryption; plain Secret manifests never go into\n  git. Pull them from a secret manager with External Secrets Operator, or\n  keep them encrypted in git with Sealed Secrets or SOPS:\n\n```yaml\napiVersion: external-secrets.io\u002Fv1\nkind: ExternalSecret\nmetadata: { name: api-secrets }\nspec:\n  refreshInterval: 1h\n  secretStoreRef: { kind: ClusterSecretStore, name: aws-secrets-manager }\n  target: { name: api-secrets }\n  dataFrom:\n    - extract: { key: prod\u002Fapi }\n```\n\n- Secrets encrypted at rest (KMS on managed clusters), readable by few\n  subjects. Cloud access through workload identity (EKS Pod Identity or IRSA,\n  GKE Workload Identity Federation, Azure Workload Identity), never keys.\n\n## 5. Scaling and disruption\n\n```yaml\napiVersion: autoscaling\u002Fv2\nkind: HorizontalPodAutoscaler\nmetadata: { name: api }\nspec:\n  scaleTargetRef: { apiVersion: apps\u002Fv1, kind: Deployment, name: api }\n  minReplicas: 3\n  maxReplicas: 20\n  metrics:\n    - type: Resource\n      resource: { name: cpu, target: { type: Utilization, averageUtilization: 70 } }\n---\napiVersion: policy\u002Fv1\nkind: PodDisruptionBudget\nmetadata: { name: api }\nspec:\n  maxUnavailable: 1\n  selector: { matchLabels: { app.kubernetes.io\u002Fname: api } }\n```\n\n- CPU utilisation is measured against the request, so requests must be real;\n  60 to 75% is the usual target. Request rate or queue depth through KEDA or\n  a metrics adapter; KEDA also scales workers to zero on an empty queue. The\n  default 300s scale-down window stops flapping; keep it.\n- A PDB with `minAvailable` equal to the replica count blocks every drain and\n  upgrade; `maxUnavailable: 1` does not. Nodes follow pending pods (Cluster\n  Autoscaler or Karpenter); a `PriorityClass` protects what matters.\n\n## 6. Packaging: Kustomize or Helm\n\n| | Kustomize | Helm |\n|---|---|---|\n| Best for | Your own apps: plain YAML, a base and overlays | Configurable software for others; third-party components |\n| Per environment | `overlays\u002Fstaging`, `overlays\u002Fprod` (image digest, replicas, config) | `values-staging.yaml`, `values-prod.yaml` |\n| Render | `kubectl kustomize deploy\u002Foverlays\u002Fprod` | `helm template api .\u002Fchart -f values-prod.yaml` |\n| Lint | kubeconform on the output | `helm lint --strict`, kubeconform on the output |\n\n- Helm 4 is current (server-side apply for new releases). Pin chart versions,\n  prefer OCI charts and the upstream project's chart (Bitnami's point at\n  images without free updates); label with the `app.kubernetes.io\u002F*` set.\n\n## 7. Namespaces, RBAC, network policies\n\n- A namespace per application or team and environment; production in its own\n  cluster when the blast radius matters. RBAC: a ServiceAccount per workload,\n  Roles per namespace, no `cluster-admin` for apps or CI; check with\n  `kubectl auth can-i --list --as=system:serviceaccount:shop:api -n shop`.\n- Pod Security Admission: label namespaces\n  `pod-security.kubernetes.io\u002Fenforce: restricted`; the pod above passes.\n- Network policies: deny by default, allow DNS, then each needed path (from\n  the gateway's namespace in, to the database out). They need an enforcing\n  CNI (Cilium, Calico, GKE Dataplane V2, the EKS VPC CNI with policies on);\n  without one they are silently ignored.\n\n```yaml\napiVersion: networking.k8s.io\u002Fv1\nkind: NetworkPolicy\nmetadata: { name: default-deny }\nspec:\n  podSelector: {}\n  policyTypes: [Ingress, Egress]\n  egress:\n    - to: [{ namespaceSelector: {}, podSelector: { matchLabels: { k8s-app: kube-dns } } }]\n      ports: [{ port: 53, protocol: UDP }, { port: 53, protocol: TCP }]\n```\n\n## 8. Jobs, CronJobs, migrations\n\n- CronJobs: `concurrencyPolicy: Forbid`, an explicit `timeZone`,\n  `startingDeadlineSeconds`, and `backoffLimit`, `activeDeadlineSeconds` and\n  `ttlSecondsAfterFinished` on the job, so stuck runs end and old ones go.\n- Migrations run as a Job before the rollout: an Argo CD `PreSync` hook, a\n  Helm `pre-upgrade` hook, or a CI step that waits\n  (`kubectl wait --for=condition=complete job\u002Fmigrate --timeout=10m`). Never\n  an init container: it runs in every pod, racing (`devops-deploy`).\n\n## 9. GitOps\n\n```yaml\napiVersion: argoproj.io\u002Fv1alpha1\nkind: Application\nmetadata: { name: api-prod, namespace: argocd }\nspec:\n  project: shop\n  source:\n    repoURL: https:\u002F\u002Fgithub.com\u002Facme\u002Fdeploy.git\n    targetRevision: main\n    path: apps\u002Fapi\u002Foverlays\u002Fprod\n  destination: { server: https:\u002F\u002Fkubernetes.default.svc, namespace: shop }\n  syncPolicy:\n    automated: { prune: true, selfHeal: true }\n```\n\n- The cluster pulls its state from git (Argo CD, or Flux `GitRepository` and\n  `Kustomization`). CI pushes the image and commits its digest; CI holds no\n  cluster credentials, a rollback is a `git revert`, and self-heal undoes\n  hand edits. Logs to stdout as JSON, metrics by ServiceMonitor or OTLP,\n  alerts on restarts and `OOMKilled` (`devops-observability`).\n\n## Check it\n\n- Offline, with your cluster's version (the second schema location covers\n  CRDs such as HTTPRoute and ExternalSecret), plus `helm lint --strict` and\n  `kube-linter lint deploy\u002F` or `trivy config deploy\u002F`:\n\n```sh\nkubectl kustomize deploy\u002Foverlays\u002Fprod | kubeconform -strict -summary \\\n  -kubernetes-version 1.35.0 -schema-location default \\\n  -schema-location 'https:\u002F\u002Fraw.githubusercontent.com\u002Fdatreeio\u002FCRDs-catalog\u002Fmain\u002F{{.Group}}\u002F{{.ResourceKind}}_{{.ResourceAPIVersion}}.json'\n```\n\n- With read access: `kubectl diff -k deploy\u002Foverlays\u002Fprod` and\n  `kubectl apply --dry-run=server -k deploy\u002Foverlays\u002Fprod`; a real apply only\n  when the task says so.\n- In staging: `kubectl rollout status deployment\u002Fapi --timeout=5m`, pods\n  over zones, no probe failures in `kubectl get events --sort-by=.lastTimestamp`.\n\n## Avoid\n\n`latest` or tag-only images; no requests, no memory limit, or tight CPU\nlimits on latency-sensitive services; liveness probes that check the\ndatabase; no `preStop` sleep; root users and writable filesystems; Secrets in\ngit as base64; one `LoadBalancer` per service; new work on Ingress NGINX;\n`replicas` fighting an HPA; a PDB that blocks every drain; migrations in init\ncontainers; `cluster-admin` for CI; network policies on a CNI that ignores\nthem; `kubectl apply` to production from a laptop; Kubernetes for one small\nservice.\n","# Kubernetes\n\nThe generated manifest is a Deployment of `app:latest` with one replica, no\nrequests or limits, no probes, running as root on a writable filesystem, next\nto a committed Secret that is only base64, a `LoadBalancer` Service per app,\nand `kubectl apply` from a laptop. It works until the first node drain, noisy\nneighbour or leaked repository. This is what a workload needs to survive\nrollouts, node loss and review, and how to package and ship it. One part of\ndevops; the whole is in the `devops` skill.\n\n## 1. When, and which cluster\n\n- Not for a few services run by a small team: a PaaS or ECS costs a fraction\n  to operate (`devops-platforms`). It pays off with many services and teams,\n  or where the organisation already runs it. Use a managed control plane\n  (GKE Autopilot or EKS Auto Mode when nobody wants to run nodes); k3s or\n  Talos only when self-hosting is a requirement.\n- Upstream is at 1.37 (August 2026), three minors a year, about 14 months of\n  patches each; managed providers trail by one or two. Upgrade at least\n  yearly, one minor at a time, after `kubent` or `pluto` finds removed APIs.\n- Ingress NGINX (`kubernetes\u002Fingress-nginx`) was retired in March 2026, with\n  no more fixes. Use the Gateway API with a maintained implementation (Envoy\n  Gateway, Cilium, Istio, NGINX Gateway Fabric, Traefik, Kong, the cloud's).\n\n## 2. The Deployment\n\n```yaml\napiVersion: apps\u002Fv1\nkind: Deployment\nmetadata:\n  name: api\n  labels: { app.kubernetes.io\u002Fname: api }\nspec:\n  replicas: 3\n  revisionHistoryLimit: 5\n  selector:\n    matchLabels: { app.kubernetes.io\u002Fname: api }\n  strategy:\n    rollingUpdate: { maxSurge: 25%, maxUnavailable: 0 }\n  template:\n    metadata:\n      labels: { app.kubernetes.io\u002Fname: api }\n    spec:\n      serviceAccountName: api\n      automountServiceAccountToken: false\n      terminationGracePeriodSeconds: 30\n      securityContext:\n        runAsNonRoot: true\n        runAsUser: 10001\n        runAsGroup: 10001\n        seccompProfile: { type: RuntimeDefault }\n      containers:\n        - name: api\n          image: ghcr.io\u002Facme\u002Fapi:1.4.2@sha256:\u003Cdigest>\n          ports: [{ name: http, containerPort: 8080 }]\n          envFrom:\n            - configMapRef: { name: api-config }\n            - secretRef: { name: api-secrets }\n          resources: { requests: { cpu: 250m, memory: 256Mi }, limits: { memory: 512Mi } }\n          startupProbe: { httpGet: { path: \u002Fhealthz, port: http }, periodSeconds: 5, failureThreshold: 30 }\n          readinessProbe: { httpGet: { path: \u002Freadyz, port: http }, periodSeconds: 5, failureThreshold: 3 }\n          livenessProbe: { httpGet: { path: \u002Fhealthz, port: http }, periodSeconds: 10, failureThreshold: 3 }\n          lifecycle: { preStop: { sleep: { seconds: 5 } } }\n          securityContext: { allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities: { drop: [\"ALL\"] } }\n          volumeMounts: [{ name: tmp, mountPath: \u002Ftmp }]\n      volumes: [{ name: tmp, emptyDir: {} }]\n      topologySpreadConstraints:\n        - maxSkew: 1\n          topologyKey: topology.kubernetes.io\u002Fzone\n          whenUnsatisfiable: ScheduleAnyway\n          labelSelector: { matchLabels: { app.kubernetes.io\u002Fname: api } }\n```\n\n- Requests always: scheduling and the autoscaler's maths use them. A memory\n  limit always, 1 to 2 times the request, with the runtime sized below it\n  (`--max-old-space-size`, `-XX:MaxRAMPercentage=75`, `GOMEMLIMIT`), since the\n  kernel kills the process at the limit. CPU limits rarely: throttling adds\n  latency even on an idle node; keep them for batch work or where policy\n  demands (Go 1.25 and later read the CPU limit for `GOMAXPROCS`).\n- Size requests from measurement (p95 over a week, VPA recommendations,\n  `kubectl top`); in-place resize is stable since 1.35.\n- Probes: startup covers the slowest boot (30 x 5s here); readiness checks\n  what a request needs, with a short timeout; liveness checks only the\n  process (`backend-observability`). No heavy work in any of them.\n- The `preStop` sleep (stable since 1.34, no `sleep` binary needed) lets\n  endpoints drop the pod before `SIGTERM`; the grace period covers the sleep\n  plus the app's drain (`devops-deploy`).\n- At least 2 replicas (3 for anything that matters) spread over zones, and no\n  `replicas` field once an HPA owns it (GitOps and the HPA would fight). The\n  image by digest, a numeric user matching the image (`devops-containers`).\n\n## 3. Services and the Gateway API\n\n```yaml\napiVersion: v1\nkind: Service\nmetadata: { name: api }\nspec:\n  selector: { app.kubernetes.io\u002Fname: api }\n  ports: [{ name: http, port: 80, targetPort: http }]\n---\napiVersion: gateway.networking.k8s.io\u002Fv1\nkind: HTTPRoute\nmetadata: { name: api }\nspec:\n  parentRefs: [{ name: public, namespace: gateway }]\n  hostnames: [\"api.example.com\"]\n  rules:\n    - matches: [{ path: { type: PathPrefix, value: \u002F } }]\n      backendRefs: [{ name: api, port: 80 }]\n```\n\n- `ClusterIP` Services inside and one Gateway outside, not a `LoadBalancer`\n  per app. The platform owns the Gateway (its namespace, HTTPS listeners,\n  `allowedRoutes`); app teams own their HTTPRoutes.\n- TLS through cert-manager: set `config.gatewayAPI.enabled=true` in its Helm\n  values, annotate the Gateway with `cert-manager.io\u002Fcluster-issuer:\n  letsencrypt`, and each HTTPS listener's `certificateRefs` names the Secret\n  it fills. On an Ingress, the same annotation plus a `tls:` block.\n- Timeouts, body limits and rate limits are per implementation (policies or\n  filters); set them for uploads and long requests (`devops-networking`).\n\n## 4. Configuration and secrets\n\n- A ConfigMap change must roll the pods: Kustomize's `configMapGenerator`\n  hashes the name; Helm charts add a `checksum\u002Fconfig` pod annotation.\n- A Secret is base64, not encryption; plain Secret manifests never go into\n  git. Pull them from a secret manager with External Secrets Operator, or\n  keep them encrypted in git with Sealed Secrets or SOPS:\n\n```yaml\napiVersion: external-secrets.io\u002Fv1\nkind: ExternalSecret\nmetadata: { name: api-secrets }\nspec:\n  refreshInterval: 1h\n  secretStoreRef: { kind: ClusterSecretStore, name: aws-secrets-manager }\n  target: { name: api-secrets }\n  dataFrom:\n    - extract: { key: prod\u002Fapi }\n```\n\n- Secrets encrypted at rest (KMS on managed clusters), readable by few\n  subjects. Cloud access through workload identity (EKS Pod Identity or IRSA,\n  GKE Workload Identity Federation, Azure Workload Identity), never keys.\n\n## 5. Scaling and disruption\n\n```yaml\napiVersion: autoscaling\u002Fv2\nkind: HorizontalPodAutoscaler\nmetadata: { name: api }\nspec:\n  scaleTargetRef: { apiVersion: apps\u002Fv1, kind: Deployment, name: api }\n  minReplicas: 3\n  maxReplicas: 20\n  metrics:\n    - type: Resource\n      resource: { name: cpu, target: { type: Utilization, averageUtilization: 70 } }\n---\napiVersion: policy\u002Fv1\nkind: PodDisruptionBudget\nmetadata: { name: api }\nspec:\n  maxUnavailable: 1\n  selector: { matchLabels: { app.kubernetes.io\u002Fname: api } }\n```\n\n- CPU utilisation is measured against the request, so requests must be real;\n  60 to 75% is the usual target. Request rate or queue depth through KEDA or\n  a metrics adapter; KEDA also scales workers to zero on an empty queue. The\n  default 300s scale-down window stops flapping; keep it.\n- A PDB with `minAvailable` equal to the replica count blocks every drain and\n  upgrade; `maxUnavailable: 1` does not. Nodes follow pending pods (Cluster\n  Autoscaler or Karpenter); a `PriorityClass` protects what matters.\n\n## 6. Packaging: Kustomize or Helm\n\n| | Kustomize | Helm |\n|---|---|---|\n| Best for | Your own apps: plain YAML, a base and overlays | Configurable software for others; third-party components |\n| Per environment | `overlays\u002Fstaging`, `overlays\u002Fprod` (image digest, replicas, config) | `values-staging.yaml`, `values-prod.yaml` |\n| Render | `kubectl kustomize deploy\u002Foverlays\u002Fprod` | `helm template api .\u002Fchart -f values-prod.yaml` |\n| Lint | kubeconform on the output | `helm lint --strict`, kubeconform on the output |\n\n- Helm 4 is current (server-side apply for new releases). Pin chart versions,\n  prefer OCI charts and the upstream project's chart (Bitnami's point at\n  images without free updates); label with the `app.kubernetes.io\u002F*` set.\n\n## 7. Namespaces, RBAC, network policies\n\n- A namespace per application or team and environment; production in its own\n  cluster when the blast radius matters. RBAC: a ServiceAccount per workload,\n  Roles per namespace, no `cluster-admin` for apps or CI; check with\n  `kubectl auth can-i --list --as=system:serviceaccount:shop:api -n shop`.\n- Pod Security Admission: label namespaces\n  `pod-security.kubernetes.io\u002Fenforce: restricted`; the pod above passes.\n- Network policies: deny by default, allow DNS, then each needed path (from\n  the gateway's namespace in, to the database out). They need an enforcing\n  CNI (Cilium, Calico, GKE Dataplane V2, the EKS VPC CNI with policies on);\n  without one they are silently ignored.\n\n```yaml\napiVersion: networking.k8s.io\u002Fv1\nkind: NetworkPolicy\nmetadata: { name: default-deny }\nspec:\n  podSelector: {}\n  policyTypes: [Ingress, Egress]\n  egress:\n    - to: [{ namespaceSelector: {}, podSelector: { matchLabels: { k8s-app: kube-dns } } }]\n      ports: [{ port: 53, protocol: UDP }, { port: 53, protocol: TCP }]\n```\n\n## 8. Jobs, CronJobs, migrations\n\n- CronJobs: `concurrencyPolicy: Forbid`, an explicit `timeZone`,\n  `startingDeadlineSeconds`, and `backoffLimit`, `activeDeadlineSeconds` and\n  `ttlSecondsAfterFinished` on the job, so stuck runs end and old ones go.\n- Migrations run as a Job before the rollout: an Argo CD `PreSync` hook, a\n  Helm `pre-upgrade` hook, or a CI step that waits\n  (`kubectl wait --for=condition=complete job\u002Fmigrate --timeout=10m`). Never\n  an init container: it runs in every pod, racing (`devops-deploy`).\n\n## 9. GitOps\n\n```yaml\napiVersion: argoproj.io\u002Fv1alpha1\nkind: Application\nmetadata: { name: api-prod, namespace: argocd }\nspec:\n  project: shop\n  source:\n    repoURL: https:\u002F\u002Fgithub.com\u002Facme\u002Fdeploy.git\n    targetRevision: main\n    path: apps\u002Fapi\u002Foverlays\u002Fprod\n  destination: { server: https:\u002F\u002Fkubernetes.default.svc, namespace: shop }\n  syncPolicy:\n    automated: { prune: true, selfHeal: true }\n```\n\n- The cluster pulls its state from git (Argo CD, or Flux `GitRepository` and\n  `Kustomization`). CI pushes the image and commits its digest; CI holds no\n  cluster credentials, a rollback is a `git revert`, and self-heal undoes\n  hand edits. Logs to stdout as JSON, metrics by ServiceMonitor or OTLP,\n  alerts on restarts and `OOMKilled` (`devops-observability`).\n\n## Check it\n\n- Offline, with your cluster's version (the second schema location covers\n  CRDs such as HTTPRoute and ExternalSecret), plus `helm lint --strict` and\n  `kube-linter lint deploy\u002F` or `trivy config deploy\u002F`:\n\n```sh\nkubectl kustomize deploy\u002Foverlays\u002Fprod | kubeconform -strict -summary \\\n  -kubernetes-version 1.35.0 -schema-location default \\\n  -schema-location 'https:\u002F\u002Fraw.githubusercontent.com\u002Fdatreeio\u002FCRDs-catalog\u002Fmain\u002F{{.Group}}\u002F{{.ResourceKind}}_{{.ResourceAPIVersion}}.json'\n```\n\n- With read access: `kubectl diff -k deploy\u002Foverlays\u002Fprod` and\n  `kubectl apply --dry-run=server -k deploy\u002Foverlays\u002Fprod`; a real apply only\n  when the task says so.\n- In staging: `kubectl rollout status deployment\u002Fapi --timeout=5m`, pods\n  over zones, no probe failures in `kubectl get events --sort-by=.lastTimestamp`.\n\n## Avoid\n\n`latest` or tag-only images; no requests, no memory limit, or tight CPU\nlimits on latency-sensitive services; liveness probes that check the\ndatabase; no `preStop` sleep; root users and writable filesystems; Secrets in\ngit as base64; one `LoadBalancer` per service; new work on Ingress NGINX;\n`replicas` fighting an HPA; a PDB that blocks every drain; migrations in init\ncontainers; `cluster-admin` for CI; network policies on a CNI that ignores\nthem; `kubectl apply` to production from a laptop; Kubernetes for one small\nservice.\n",{"name":5,"description":7},"main","38293fe1b096e6bf3646c7344c38786ed688f464","crates\u002Fenowx-core\u002Fskills\u002Fdevops-kubernetes",21,"https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli","https:\u002F\u002Fenowx.ai","https:\u002F\u002Fgithub.com\u002Fenowdev","Pretending to be a Developer","2026-10-06 02:05:35",{"source":8,"skillName":5,"commands":61,"prompt":71,"zipAvailable":72},[62,65,68],{"tool":63,"command":64},"npx","npx skills add enowdev\u002Fenowxcli --skill devops-kubernetes",{"tool":66,"command":67},"bunx","bunx skills add enowdev\u002Fenowxcli --skill devops-kubernetes",{"tool":69,"command":70},"pnpm","pnpm dlx skills add enowdev\u002Fenowxcli --skill devops-kubernetes","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fdevops-kubernetes\u002FSKILL.md",true,[],[75,89,111,127,151,173],{"skillNo":76,"name":77,"nameZh":6,"description":78,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":79,"categories":80,"keywords":84,"publishedAt":46},"01M6D2HQYSQD9N7MTNDT4A7CBM","demo-skill","Project-scope demo skill for tests.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fworkspace\u002F.agents\u002Fskills\u002Fdemo-skill\u002FSKILL.md",[81],{"categoryNo":82,"categoryName":83,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[85,86,87,88,41],"demo skill","demo","tests","project",{"skillNo":90,"name":45,"nameZh":6,"description":91,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":92,"categories":93,"keywords":100,"publishedAt":46},"01M6D2HM0GCPM0TM2HPG9HHR2R","Writing words people read: the specific thing instead of claims, words to drop, sentences, interface copy (buttons, labels, errors, empty states, confirmations), page copy, notifications and emails, alt text and link text, numbers and dates, words that translate well, documentation and voice. Read before writing a page's copy, an interface's text or a document.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fwriting\u002FSKILL.md",[94,97],{"categoryNo":95,"categoryName":96,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":98,"categoryName":99,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000036","内容创作者",[45,101,102,103,104,105,41,106,107,108,109,110],"text","emails","empty","drop","document","page","voice","notifications","documentation","translate",{"skillNo":112,"name":113,"nameZh":6,"description":114,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":115,"categories":116,"keywords":120,"publishedAt":46},"01M6D2HG07YS49NM196Q0R3AZ8","ui","Designing or restyling an interface: direction, layout, type, colour, icons, components, states, responsive behaviour. Read before building a page, a screen or a set of components.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui\u002FSKILL.md",[117],{"categoryNo":118,"categoryName":119,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000032","设计师",[113,121,122,123,124,106,125,41,126],"icons","components","SET","responsive","layout","before building",{"skillNo":128,"name":129,"nameZh":6,"description":130,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":131,"categories":132,"keywords":137,"publishedAt":46},"01M6D2HB59HZHWG1RYZDV4SJ91","ui-themes","Light and dark themes, and colour that holds in both: tokens with a value per theme, and what each kind of content needs to stay visible (icons, SVG drawings and line art, logos, photos, screenshots, charts, code, maps, borders, shadows, focus, form controls), switching without a flash, and checking both. Read before building a second theme or a theme toggle, and before any drawing, logo or chart on a page that has two themes.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-themes\u002FSKILL.md",[133,136],{"categoryNo":134,"categoryName":135,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},[113,138,139,140,141,142,143,144,145,41,126,146,106,147,38,148,121,149,150],"ui themes","switching","photos","tokens","drawing","focus","maps","Art","flash","code","content","screenshots","logo",{"skillNo":152,"name":153,"nameZh":6,"description":154,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":155,"categories":156,"keywords":160,"publishedAt":46},"01M6D2H5R5KRAE524C8QX0T7Q0","ui-structure","Structure before styling: the blueprint written before any interface code (the screen's job, the regions its kind of page needs, their order and why, the layout skeleton in numbers, the components and their states), the anatomy and order of a homepage, a dashboard, a list, a detail page, settings, a form, sign-in, pricing, docs, an article and a checkout, and how to turn the blueprint into code (tokens, layout primitives, components, sections, page). Read first, before building or rebuilding any page or screen.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-structure\u002FSKILL.md",[157,158,159],{"categoryNo":95,"categoryName":96,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},{"categoryNo":98,"categoryName":99,"taxonomyType":26,"taxonomyTypeDesc":27},[161,113,162,122,163,164,147,165,166,167,125,141,168,169,170,41,126,106,171,172],"structure","ui structure","pricing","dashboard","Blueprint","article","anatomy","styling","primitives","list","why","how",{"skillNo":174,"name":175,"nameZh":6,"description":176,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":177,"categories":178,"keywords":186,"publishedAt":46},"01M6D2H0ZZBJG77MBGPZHA6MWD","ui-stack-vue","Building interfaces in Vue 3 and Nuxt: project layout, script setup with typed defineProps, defineEmits and defineModel, attribute fallthrough, slots and scoped slots, template refs, ref, reactive and computed, watch versus watchEffect, composables and VueUse, provide and inject, Pinia, keyed v-for, transitions, Nuxt 4 (app directory, pages, layouts, useFetch and useAsyncData keys, $fetch in handlers, server routes, runtime config, useSeoMeta, NuxtImg, middleware), VeeValidate forms, Reka UI and Nuxt UI, testing, vue-tsc, and the traps. Read when the project uses Vue or Nuxt.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-stack-vue\u002FSKILL.md",[179,182,185],{"categoryNo":180,"categoryName":181,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":183,"categoryName":184,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",{"categoryNo":118,"categoryName":119,"taxonomyType":26,"taxonomyTypeDesc":27},[113,187,188,189,190,191,192,193,125,88,194,195,41,196,197,198,199,200,201,202],"vue","stack","ui stack vue","forms","pages","watch","runtime","transitions","fetch","pinia","nuxt","template","server","config","directory","testing"]