claude-vault/skills/skill-creator/examples/k3s-deployment-skill.md
2026-02-04 15:29:11 +01:00

8.3 KiB

Example: K3s Deployment Skill

This is a complete example of a DevOps skill for K3s/Kubernetes deployments.


---
name: k3s-deploy
description: Generates K3s deployment manifests with proper resource limits, probes, and HPA. Use when deploying applications to Kubernetes.
argument-hint: [app-name] [replicas] [image]
allowed-tools: Read, Write, Edit, Glob, Grep, Bash
disable-model-invocation: true
---

# K3s Deployment Generator

Generate production-ready Kubernetes manifests for K3s clusters with proper resource limits, health probes, and autoscaling.

## When to Use

- Deploying new applications to K3s
- Creating or updating Deployment manifests
- Setting up Services and Ingress
- Configuring Horizontal Pod Autoscaling

## Prerequisites

- K3s cluster running and accessible
- kubectl configured with cluster access
- Container image available in registry

## Instructions

### Step 1: Parse Arguments

Extract from `$ARGUMENTS`:
- **app-name**: Name for the deployment (kebab-case)
- **replicas**: Initial replica count (default: 2)
- **image**: Container image with tag

### Step 2: Generate Deployment

Create `deployment.yaml`:

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ${APP_NAME}
  namespace: default
  labels:
    app: ${APP_NAME}
    version: v1
spec:
  replicas: ${REPLICAS}
  revisionHistoryLimit: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: ${APP_NAME}
  template:
    metadata:
      labels:
        app: ${APP_NAME}
        version: v1
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8000"
        prometheus.io/path: "/metrics"
    spec:
      serviceAccountName: ${APP_NAME}
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 1000
      containers:
        - name: ${APP_NAME}
          image: ${IMAGE}
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 8000
              protocol: TCP
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          envFrom:
            - configMapRef:
                name: ${APP_NAME}-config
            - secretRef:
                name: ${APP_NAME}-secrets
          resources:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "256Mi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /health/live/
              port: http
            initialDelaySeconds: 30
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /health/ready/
              port: http
            initialDelaySeconds: 5
            periodSeconds: 5
            timeoutSeconds: 3
            failureThreshold: 3
          startupProbe:
            httpGet:
              path: /health/live/
              port: http
            initialDelaySeconds: 10
            periodSeconds: 5
            failureThreshold: 30
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL
          volumeMounts:
            - name: tmp
              mountPath: /tmp
      volumes:
        - name: tmp
          emptyDir: {}
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchLabels:
                    app: ${APP_NAME}
                topologyKey: kubernetes.io/hostname
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels:
              app: ${APP_NAME}

Step 3: Generate Service

Create service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: ${APP_NAME}
  namespace: default
  labels:
    app: ${APP_NAME}
spec:
  type: ClusterIP
  ports:
    - name: http
      port: 80
      targetPort: http
      protocol: TCP
  selector:
    app: ${APP_NAME}

Step 4: Generate ConfigMap

Create configmap.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: ${APP_NAME}-config
  namespace: default
data:
  LOG_LEVEL: "INFO"
  ENVIRONMENT: "production"

Step 5: Generate HPA

Create hpa.yaml:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: ${APP_NAME}
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: ${APP_NAME}
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 10
          periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
        - type: Percent
          value: 100
          periodSeconds: 15
        - type: Pods
          value: 4
          periodSeconds: 15
      selectPolicy: Max

Step 6: Generate ServiceAccount

Create serviceaccount.yaml:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: ${APP_NAME}
  namespace: default

Step 7: Generate Ingress (Traefik)

Create ingress.yaml:

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: ${APP_NAME}
  namespace: default
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(\`${APP_NAME}.example.com\`)
      kind: Rule
      services:
        - name: ${APP_NAME}
          port: 80
      middlewares:
        - name: ${APP_NAME}-ratelimit
  tls:
    certResolver: letsencrypt

---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: ${APP_NAME}-ratelimit
  namespace: default
spec:
  rateLimit:
    average: 100
    burst: 50

Patterns & Best Practices

Resource Sizing

Start conservative and adjust based on metrics:

Workload Type CPU Request CPU Limit Memory Request Memory Limit
Web App 100m 500m 128Mi 256Mi
API Server 200m 1000m 256Mi 512Mi
Worker 500m 2000m 512Mi 1Gi
Database 1000m 4000m 1Gi 4Gi

Probe Configuration

  • startupProbe: Use for slow-starting apps (Django, Java)
  • livenessProbe: Detect deadlocks, restart if failed
  • readinessProbe: Detect temporary unavailability, remove from LB

Labels Convention

labels:
  app: my-app           # Required: app name
  version: v1.2.3       # Recommended: version
  component: api        # Optional: component type
  part-of: platform     # Optional: parent application
  managed-by: kubectl   # Optional: management tool

Validation

# Dry run to validate manifests
kubectl apply --dry-run=client -f .

# Show diff before applying
kubectl diff -f .

# Apply with record
kubectl apply -f . --record

# Watch rollout
kubectl rollout status deployment/${APP_NAME}

# Check events
kubectl get events --field-selector involvedObject.name=${APP_NAME}

Common Pitfalls

  • No Resource Limits: Can cause node resource exhaustion
  • Missing Probes: Leads to traffic to unhealthy pods
  • Wrong Probe Paths: Causes constant restarts
  • No Anti-Affinity: All pods on same node = single point of failure
  • Aggressive HPA: Causes thrashing; use stabilization windows

Rollback Procedure

# View rollout history
kubectl rollout history deployment/${APP_NAME}

# Rollback to previous version
kubectl rollout undo deployment/${APP_NAME}

# Rollback to specific revision
kubectl rollout undo deployment/${APP_NAME} --to-revision=2