K3s: La Guía Completa para Cada Caso de Uso
Domina K3s desde un nodo en desarrollo hasta producción multi-nodo: instalación, red, almacenamiento, GitOps, observabilidad, CI/CD, despliegues en edge y mantenimiento.
Kubernetes es el estándar para orquestación de contenedores a escala, pero su peso operativo — etcd, el servidor de API, el controller manager, el scheduler, el cloud-controller, el plugin CNI — lo hace costoso de ejecutar por debajo de la escala empresarial. Un clúster completo de Kubernetes en un VPS de $10 es como operar una logística de distribución para un solo paquete.
K3s resuelve esto reduciendo Kubernetes a su superficie de API esencial manteniendo compatibilidad completa. Se distribuye como un binario único de menos de 100MB, reemplaza etcd con SQLite para despliegues de un solo nodo, incluye Flannel como CNI, Traefik como controlador de ingress y local-path-provisioner para almacenamiento. Obtienes un clúster de Kubernetes listo para producción que arranca en menos de 30 segundos en una máquina con 512MB de RAM.
Esta guía cubre cada caso de uso significativo: desarrollo local, producción en VPS único, clústeres HA multi-nodo, Raspberry Pi y dispositivos edge, configuraciones homelab, pipelines GitOps, stacks de observabilidad y operaciones del día dos. Cada sección es autocontenida — lee las que correspondan a tu situación.
Cómo Difiere K3s de K8s
Entender qué eliminó y qué reemplazó K3s te ayuda a razonar sobre sus limitaciones y fortalezas antes de comprometerte con él.
Eliminado de Kubernetes upstream:
- Código de proveedor cloud integrado (controladores específicos de AWS, GCP, Azure)
- Características en fase Alpha y APIs deprecadas
- La mayoría de plugins y add-ons no esenciales
Reemplazado con alternativas más ligeras:
- etcd → SQLite (nodo único) o etcd integrado (clúster HA)
- kube-proxy → reemplazado por host-gw / VXLAN de Flannel
- CoreDNS → incluido, misma versión
- Ingress → Traefik v2 incluido por defecto
- Almacenamiento → local-path-provisioner incluido
Lo que permanece idéntico a K8s upstream:
- La API de Kubernetes — cada comando
kubectl, cada manifiesto, cada CRD - El modelo de programación de pods
- RBAC, secrets, configmaps, services, ingress
- Compatibilidad con charts de Helm
- Todos los tipos de carga de trabajo estándar (Deployment, StatefulSet, DaemonSet, Job, CronJob)
La implicación práctica: cualquier carga de trabajo que corre en Kubernetes upstream corre en K3s sin modificación. La diferencia está en la capa de infraestructura — cómo se ejecuta y gestiona el clúster en sí.
Caso 1: Desarrollo Local
El caso de uso más inmediato de K3s es reemplazar Docker Compose o Minikube para desarrollo local. K3s corre en Linux nativamente y en macOS/Windows via Multipass o Lima.
Linux (Nativo)
# instalar K3s como clúster de un solo nodo
curl -sfL https://get.k3s.io | sh -
# el script de instalación inicia K3s como servicio systemd
sudo systemctl status k3s
# verificar el nodo
sudo kubectl get nodes
# copiar kubeconfig para uso local
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config
chmod 600 ~/.kube/config
macOS / Windows (via Lima)
Lima ejecuta una VM Linux con compartición automática de archivos y reenvío de puertos:
# instalar Lima
brew install lima
# crear instancia K3s
limactl start --name k3s template://k3s
# usar kubectl de K3s desde Lima
limactl shell k3s kubectl get nodes
# o configurar kubectl local para usarlo
limactl shell k3s -- cat /etc/rancher/k3s/k3s.yaml \
| sed "s/127.0.0.1/$(limactl list k3s --format '{{.IP}}')/g" \
> ~/.kube/k3s-lima.yaml
export KUBECONFIG=~/.kube/k3s-lima.yaml
kubectl get nodes
Flujo de Trabajo de Desarrollo
# construir y cargar imagen en K3s sin registro
docker build -t myapp:dev .
# K3s usa containerd, no Docker — importar directamente
docker save myapp:dev | sudo k3s ctr images import -
# o usar un registro local
docker run -d -p 5000:5000 --name registry registry:2
docker tag myapp:dev localhost:5000/myapp:dev
docker push localhost:5000/myapp:dev
# configurar K3s para confiar en el registro local
sudo tee /etc/rancher/k3s/registries.yaml << 'EOF'
mirrors:
"localhost:5000":
endpoint:
- "http://localhost:5000"
EOF
sudo systemctl restart k3s
Caso 2: Producción en Nodo Único (VPS)
Un solo nodo K3s en un VPS de $10–20/mes maneja una cantidad sorprendente de carga de producción: una API Go con PostgreSQL, caché Redis, workers en segundo plano y Traefik manejando HTTPS — todo en 2 vCPUs y 4GB de RAM.
Requisitos del Servidor
| Carga de trabajo | Mínimo | Recomendado |
|---|---|---|
| Sistema K3s + Traefik | 512MB RAM, 1 vCPU | 1GB RAM, 1 vCPU |
| 3–5 servicios pequeños | 2GB RAM total, 2 vCPU | 4GB RAM, 2 vCPU |
| PostgreSQL + Redis | +1GB RAM | +2GB RAM |
| Stack de monitoreo | +512MB RAM | +1GB RAM |
Instalando K3s en un VPS
# como root en el VPS
# deshabilitar Traefik si quieres instalarlo manualmente con config personalizada
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable traefik" sh -
# o mantener Traefik incluido (válido para la mayoría de los casos)
curl -sfL https://get.k3s.io | sh -
# obtener el token del nodo (necesario para añadir nodos agente después)
sudo cat /var/lib/rancher/k3s/server/node-token
Desplegando una Aplicación en Producción
Un conjunto completo de manifiestos para una API Go con PostgreSQL:
# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: myapp
# postgres.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: myapp
spec:
accessModes: [ReadWriteOnce]
storageClassName: local-path # clase de almacenamiento por defecto de K3s
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: myapp
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:17-alpine
env:
- name: POSTGRES_DB
value: myapp
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: postgres-secret
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
ports:
- containerPort: 5432
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
readinessProbe:
exec:
command: ["pg_isready", "-U", "$(POSTGRES_USER)"]
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: data
persistentVolumeClaim:
claimName: postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: myapp
spec:
selector:
app: postgres
ports:
- port: 5432
# api.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: myapp
spec:
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: yourregistry/myapp-api:2.1.0
ports:
- containerPort: 8080
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: api-secret
key: database-url
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: api
namespace: myapp
spec:
selector:
app: api
ports:
- port: 80
targetPort: 8080
# ingress.yaml — IngressRoute de Traefik
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: api-ingress
namespace: myapp
spec:
entryPoints:
- websecure
routes:
- match: Host(`api.tudominio.com`)
kind: Rule
services:
- name: api
port: 80
tls:
certResolver: letsencrypt
Gestión de Secretos
# crear secretos de forma imperativa
kubectl create secret generic postgres-secret \
--namespace myapp \
--from-literal=username=myapp \
--from-literal=password=$(openssl rand -base64 32)
kubectl create secret generic api-secret \
--namespace myapp \
--from-literal=database-url="postgres://myapp:password@postgres:5432/myapp?sslmode=disable"
Caso 3: Clúster HA Multi-Nodo
Para cargas de trabajo de producción que necesitan alta disponibilidad, K3s soporta una configuración multi-servidor con etcd integrado. La configuración HA mínima es tres nodos servidor (para el quórum de etcd).
Arquitectura
┌──────────────────────────────┐
│ Balanceador de carga (nginx) │
│ TCP pass-through :6443 │
└──────┬──────┬──────┬───────────┘
│ │ │
┌──────▼──┐ ┌──▼─────▼┐ ┌──────────┐
│Server 1 │ │Server 2 │ │ Server 3 │
│ (etcd) │ │ (etcd) │ │ (etcd) │
└─────────┘ └─────────┘ └──────────┘
│
┌────────────┼────────────┐
┌──────▼──┐ ┌──────▼──┐ ┌──────▼──┐
│ Agent 1 │ │ Agent 2 │ │ Agent 3 │
│(worker) │ │(worker) │ │(worker) │
└─────────┘ └─────────┘ └──────────┘
Configurando el Clúster HA
# en Server 1 — inicializar el clúster con etcd integrado
curl -sfL https://get.k3s.io | sh -s - server \
--cluster-init \
--tls-san TU_LB_IP \
--tls-san server1.internal \
--disable traefik \
--node-taint CriticalAddonsOnly=true:NoExecute
# obtener el token del clúster
sudo cat /var/lib/rancher/k3s/server/node-token
# en Server 2 y Server 3 — unirse al clúster
curl -sfL https://get.k3s.io | sh -s - server \
--server https://SERVER1_IP:6443 \
--token K10abc...::server:xyz... \
--tls-san TU_LB_IP \
--disable traefik \
--node-taint CriticalAddonsOnly=true:NoExecute
# en cada nodo Agent
curl -sfL https://get.k3s.io | K3S_URL=https://TU_LB_IP:6443 \
K3S_TOKEN=K10abc...::server:xyz... sh -
El --node-taint CriticalAddonsOnly=true:NoExecute en los nodos servidor evita que las cargas de trabajo sean programadas en los nodos del plano de control. El --tls-san TU_LB_IP añade la IP del balanceador al certificado TLS.
Configuración del Balanceador de Carga (nginx)
# /etc/nginx/nginx.conf
stream {
upstream k3s_servers {
server server1.internal:6443;
server server2.internal:6443;
server server3.internal:6443;
}
server {
listen 6443;
proxy_pass k3s_servers;
proxy_timeout 10s;
proxy_connect_timeout 5s;
}
}
Caso 4: Raspberry Pi y Dispositivos Edge
K3s fue diseñado con ARM en mente. Corre en Raspberry Pi 4 (4GB), Raspberry Pi 5 y otras computadoras de placa única ARM.
Configuración de Raspberry Pi
# en Raspberry Pi OS (64-bit recomendado)
# habilitar cgroups — requerido para el runtime de contenedores
echo "cgroup_memory=1 cgroup_enable=memory" | sudo tee -a /boot/cmdline.txt
sudo reboot
# instalar K3s (detecta ARM64 automáticamente)
curl -sfL https://get.k3s.io | sh -
# verificar
sudo kubectl get nodes
Consideraciones específicas de ARM:
Usa imágenes de contenedor multi-arch (--platform linux/arm64). Las imágenes de solo amd64 fallarán con exec format error.
# construir imagen multi-arch con Docker Buildx
docker buildx create --name multiarch --use
docker buildx inspect --bootstrap
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag yourregistry/myapp:2.1.0 \
--push .
Mueve el directorio de datos de K3s a un SSD USB para mejor rendimiento:
sudo mkdir -p /mnt/ssd
sudo mount /dev/sda1 /mnt/ssd
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--data-dir /mnt/ssd/k3s" sh -
Despliegues Edge sin Conexión
# en una máquina con internet, descargar artefactos K3s
VERSION=v1.31.0+k3s1
wget https://github.com/k3s-io/k3s/releases/download/$VERSION/k3s-arm64
wget https://github.com/k3s-io/k3s/releases/download/$VERSION/k3s-airgap-images-arm64.tar.zst
# transferir al dispositivo edge
scp k3s-arm64 pi@edgedispositivo:/usr/local/bin/k3s
scp k3s-airgap-images-arm64.tar.zst pi@edgedispositivo:/var/lib/rancher/k3s/agent/images/
# en el dispositivo edge
chmod +x /usr/local/bin/k3s
INSTALL_K3S_SKIP_DOWNLOAD=true \
INSTALL_K3S_VERSION=$VERSION \
./install.sh
# pre-cargar imágenes de la aplicación
docker save yourregistry/myapp:2.1.0 > myapp.tar
sudo k3s ctr images import myapp.tar
Caso 5: Homelab
Un clúster K3s en hardware viejo o un grupo de mini-PCs es una excelente forma de aprender patrones de Kubernetes de producción sin costos de nube.
MetalLB: LoadBalancer en Bare Metal
En bare metal, los servicios LoadBalancer se quedan en estado <Pending> para siempre sin una herramienta que los maneje. MetalLB asigna IPs reales desde un pool que defines.
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml
kubectl wait --namespace metallb-system \
--for=condition=ready pod \
--selector=app=metallb \
--timeout=90s
# metallb-config.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: homelab-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.200-192.168.1.220 # reserva estas IPs en tu router
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: homelab-l2
namespace: metallb-system
spec:
ipAddressPools:
- homelab-pool
cert-manager para HTTPS
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.0/cert-manager.yaml
Para CA autofirmada (servicios internos):
# cluster-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned-issuer
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: homelab-ca
namespace: cert-manager
spec:
isCA: true
commonName: homelab-ca
secretName: homelab-ca-secret
privateKey:
algorithm: ECDSA
size: 256
issuerRef:
name: selfsigned-issuer
kind: ClusterIssuer
group: cert-manager.io
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: homelab-ca-issuer
spec:
ca:
secretName: homelab-ca-secret
Longhorn: Almacenamiento de Bloques Distribuido
El local-path incluido crea volúmenes locales al nodo — si el nodo muere, los datos se pierden. Longhorn provee almacenamiento de bloques replicado para clústeres multi-nodo.
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.8.0/deploy/prerequisite/longhorn-iscsi-installation.yaml
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.8.0/deploy/longhorn.yaml
# establecer Longhorn como clase de almacenamiento por defecto
kubectl patch storageclass local-path -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
kubectl patch storageclass longhorn -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
Caso 6: CI/CD — Desplegando en K3s desde GitHub Actions
Configurando el Acceso
# deploy-sa.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: github-deploy
namespace: myapp
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deploy-role
namespace: myapp
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "patch", "list"]
- apiGroups: [""]
resources: ["pods", "services"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: github-deploy-binding
namespace: myapp
subjects:
- kind: ServiceAccount
name: github-deploy
namespace: myapp
roleRef:
kind: Role
name: deploy-role
apiGroup: rbac.authorization.k8s.io
El Pipeline de Despliegue
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}/api
jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Iniciar sesión en GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Construir y publicar
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Desplegar en K3s
env:
KUBECONFIG_DATA: ${{ secrets.KUBECONFIG_B64 }}
run: |
echo "$KUBECONFIG_DATA" | base64 -d > /tmp/kubeconfig
export KUBECONFIG=/tmp/kubeconfig
kubectl set image deployment/api \
api=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
--namespace myapp
kubectl rollout status deployment/api \
--namespace myapp \
--timeout=5m
Caso 7: GitOps con Flux
GitOps invierte el modelo CI/CD de push: en lugar de que CI empuje cambios al clúster, un controlador en el clúster jala desde un repositorio Git y aplica los cambios. El repositorio Git se convierte en la fuente única de verdad del estado del clúster.
Instalando Flux
# instalar CLI de Flux
curl -s https://fluxcd.io/install.sh | sudo bash
# bootstrap Flux en K3s
flux bootstrap github \
--owner=tuorg \
--repository=k3s-gitops \
--branch=main \
--path=clusters/production \
--personal
GitRepository y Kustomization
# clusters/production/myapp/source.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: myapp
namespace: flux-system
spec:
interval: 1m
url: https://github.com/tuorg/myapp
ref:
branch: main
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: myapp
namespace: flux-system
spec:
interval: 5m
path: ./k8s/production
prune: true
sourceRef:
kind: GitRepository
name: myapp
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: api
namespace: myapp
Automatización de Imágenes
# image-policy.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: myapp-api
namespace: flux-system
spec:
image: ghcr.io/tuorg/myapp/api
interval: 5m
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: myapp-api
namespace: flux-system
spec:
imageRepositoryRef:
name: myapp-api
policy:
semver:
range: '>=2.0.0 <3.0.0'
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageUpdateAutomation
metadata:
name: myapp
namespace: flux-system
spec:
interval: 5m
sourceRef:
kind: GitRepository
name: myapp
git:
checkout:
ref:
branch: main
commit:
author:
email: fluxbot@tuorg.com
name: Flux Bot
messageTemplate: 'chore: actualizar {{range .Updated.Images}}{{println .}}{{end}}'
push:
branch: main
update:
path: ./k8s/production
strategy: Setters
En tu manifiesto de Deployment, marca el campo de imagen para automatización:
containers:
- name: api
image: ghcr.io/tuorg/myapp/api:2.1.0 # {"$imagepolicy": "flux-system:myapp-api"}
Caso 8: Stack de Observabilidad
kube-prometheus-stack (Helm)
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install monitoring prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
--set grafana.adminPassword=$(openssl rand -base64 24) \
--set prometheus.prometheusSpec.retention=7d \
--set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.storageClassName=local-path \
--set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage=10Gi
Exponiendo Grafana con Traefik
# grafana-ingress.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: grafana
namespace: monitoring
spec:
entryPoints:
- websecure
routes:
- match: Host(`grafana.tudominio.com`)
kind: Rule
services:
- name: monitoring-grafana
port: 80
tls:
certResolver: letsencrypt
ServiceMonitor para Métricas Personalizadas
# servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: myapp-api
namespace: myapp
labels:
release: monitoring
spec:
selector:
matchLabels:
app: api
endpoints:
- port: http
path: /metrics
interval: 30s
Loki para Logs
helm repo add grafana https://grafana.github.io/helm-charts
helm install loki grafana/loki-stack \
--namespace monitoring \
--set promtail.enabled=true \
--set loki.persistence.enabled=true \
--set loki.persistence.storageClassName=local-path \
--set loki.persistence.size=10Gi
Caso 9: Red en Profundidad
Reemplazando Flannel con Cilium
curl -sfL https://get.k3s.io | sh -s - \
--flannel-backend=none \
--disable-network-policy \
--disable traefik
helm repo add cilium https://helm.cilium.io/
helm install cilium cilium/cilium \
--namespace kube-system \
--set operator.replicas=1 \
--set kubeProxyReplacement=true \
--set k8sServiceHost=$(hostname -I | awk '{print $1}') \
--set k8sServicePort=6443
Middlewares de Traefik
# middlewares.yaml
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: rate-limit
namespace: myapp
spec:
rateLimit:
average: 100
burst: 50
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: security-headers
namespace: myapp
spec:
headers:
stsSeconds: 31536000
stsIncludeSubdomains: true
contentTypeNosniff: true
frameDeny: true
xssProtection: "1; mode=block"
referrerPolicy: "strict-origin-when-cross-origin"
Operaciones del Día Dos
Actualizando K3s
# instalar el controlador de actualización del sistema
kubectl apply -f https://github.com/rancher/system-upgrade-controller/releases/latest/download/system-upgrade-controller.yaml
# upgrade-plan.yaml
apiVersion: upgrade.cattle.io/v1
kind: Plan
metadata:
name: k3s-server
namespace: system-upgrade
spec:
concurrency: 1
cordon: true
nodeSelector:
matchExpressions:
- key: node-role.kubernetes.io/control-plane
operator: In
values: ["true"]
serviceAccountName: system-upgrade
upgrade:
image: rancher/k3s-upgrade
channel: https://update.k3s.io/v1-release/channels/stable
---
apiVersion: upgrade.cattle.io/v1
kind: Plan
metadata:
name: k3s-agent
namespace: system-upgrade
spec:
concurrency: 2
cordon: true
nodeSelector:
matchExpressions:
- key: node-role.kubernetes.io/control-plane
operator: DoesNotExist
serviceAccountName: system-upgrade
upgrade:
image: rancher/k3s-upgrade
prepare:
image: rancher/k3s-upgrade
args: ["prepare", "k3s-server"]
channel: https://update.k3s.io/v1-release/channels/stable
Respaldo y Restauración
# respaldo de SQLite (nodo único)
sudo systemctl stop k3s
sudo cp /var/lib/rancher/k3s/server/db/state.db /backup/k3s-state-$(date +%Y%m%d).db
sudo systemctl start k3s
# respaldo automático con timer de systemd
sudo tee /etc/systemd/system/k3s-backup.service << 'EOF'
[Unit]
Description=Respaldo de SQLite de K3s
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'cp /var/lib/rancher/k3s/server/db/state.db /backup/k3s-$(date +%Y%m%d-%H%M).db && find /backup -name "k3s-*.db" -mtime +7 -delete'
EOF
sudo systemctl enable --now k3s-backup.timer
# snapshot de etcd (clúster HA)
sudo k3s etcd-snapshot save --name pre-upgrade-snapshot
# restaurar
sudo k3s server \
--cluster-reset \
--cluster-reset-restore-path=/var/lib/rancher/k3s/server/db/snapshots/pre-upgrade-snapshot
Mantenimiento de Nodos
# drenar un nodo
kubectl drain node-name \
--ignore-daemonsets \
--delete-emptydir-data \
--grace-period=60
# realizar mantenimiento (actualizaciones de SO, trabajo de hardware)
# descordonar después del mantenimiento
kubectl uncordon node-name
# desinstalar el agente K3s del nodo
/usr/local/bin/k3s-agent-uninstall.sh
Cuotas de Recursos por Namespace
# resource-quota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: myapp-quota
namespace: myapp
spec:
hard:
requests.cpu: "4"
requests.memory: 4Gi
limits.cpu: "8"
limits.memory: 8Gi
pods: "20"
persistentvolumeclaims: "10"
---
apiVersion: v1
kind: LimitRange
metadata:
name: myapp-limits
namespace: myapp
spec:
limits:
- type: Container
default:
cpu: "500m"
memory: "256Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
Eligiendo la Configuración Correcta de K3s
| Decisión | Nodo único / dev | Producción HA | Homelab | Edge |
|---|---|---|---|---|
| Almacenamiento | local-path | Longhorn | Longhorn | local-path en SSD |
| CNI | Flannel | Flannel o Cilium | Flannel | Flannel |
| Ingress | Traefik (bundled) | Traefik personalizado | Traefik + MetalLB | Traefik |
| DB del clúster | SQLite | etcd integrado | SQLite o etcd | SQLite |
| Nodos servidor | 1 | 3+ | 1–3 | 1 |
K3s escala desde una sola Raspberry Pi hasta un clúster de cincuenta nodos bare metal, con la misma API de kubectl en cada configuración. La inversión en aprenderlo — los manifiestos, los charts de Helm, los patrones GitOps — se transfiere a cada destino de despliegue sin modificación.
K3s no es un Kubernetes simplificado. Es Kubernetes con el peso operativo eliminado — la misma API, la misma compatibilidad, el mismo ecosistema, corriendo en hardware que el Kubernetes completo no puede justificar.
Tags
Artículos relacionados
API Versioning Strategies: Cómo Evolucionar APIs sin Romper Clientes
Una guía exhaustiva sobre estrategias de versionado de APIs: URL versioning vs Header versioning, cómo deprecar endpoints sin shock, migration patterns reales, handling de cambios backwards-incompatibles, y decisiones arquitectónicas que importan. Con 50+ ejemplos de código en Go.
Arquitectura de software: Más allá del código
Una guía completa sobre arquitectura de software explicada en lenguaje humano: patrones, organización, estructura y cómo construir sistemas que escalen con tu negocio.
Automatizando tu vida con Go CLI: Guía profesional para crear herramientas de línea de comandos escalables
Una guía exhaustiva y paso a paso sobre cómo crear herramientas CLI escalables con Go 1.25.5: desde lo básico hasta proyectos empresariales complejos con flags, configuración, logging, y ejemplos prácticos para Windows y Linux.