lab de una mañana · 2026-07-04
No un resumen para creerte: un log para que lo veas, lo repliques desde cero y lo trastees. Cada comando de aquí es copiable y se ejecutó de verdad en tu nodo nobara-pc.
Verde = funciona y lo verificaste. Ámbar = matiz importante. Rojo = no entra hoy, y por qué no es deuda.
Salida literal de kubectl -n opsguard get all,ingress,secret,configmap. Esto sigue corriendo en tu máquina mientras lees — puedes abrir otra terminal y contrastarlo.
kubectl -n opsguard get all,ingress,secret,configmap
NAME READY STATUS RESTARTS AGE
pod/opsguard-runner-7ddc69655c-2kgts 1/1 Running 0 77m
NAME TYPE CLUSTER-IP PORT(S) AGE
service/opsguard ClusterIP 10.43.94.180 80/TCP 77m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/opsguard-runner 1/1 1 1 77m
NAME CLASS HOSTS ADDRESS PORTS
ingress.../opsguard traefik opsguard.localhost 192.168.1.51 80
NAME TYPE DATA AGE
secret/opsguard-secrets Opaque 1 77m
configmap/opsguard-scan-demo 1 74m
El Job/opsguard-scan ya no aparece: se autoborró con ttlSecondsAfterFinished: 3600 (1 h tras terminar). Es correcto — un Job es de usar y tirar. Más abajo tienes el comando para relanzarlo cuando quieras.
Esto es lo que el clúster te dijo y tu resumen mental no: OpsGuard no es un servicio.
El Ingress devuelve HTTP 502 — a propósito
OpsGuard es una CLI batch: escanea un diff, imprime el veredicto y termina. No escucha en ningún puerto. Así que el Service y el Ingress enrutan hacia un backend que no existe. Verificado:
curl -s -o /dev/null -w "HTTP %{http_code}\n" \
-H "Host: opsguard.localhost" http://192.168.1.51/
# respuesta:
HTTP 502 # Traefik y el Service resuelven; no hay nada escuchando detrás
Traducción para entrevista: el primitivo K8s correcto para OpsGuard es un Job / CronJob disparado por CI, no un Deployment con Service e Ingress. El Secret (API key aislada, consumida por envFrom) y el Job son defendibles al 100%. El Service e Ingress se dejaron escritos como práctica de manifiestos, con el 502 a la vista y anotado en los propios ficheros. Nada maquillado.
La bitácora del diario. Incluye los dos tropiezos, porque replicar sin ellos te haría chocar igual.
KUBECONFIG global que apunta ahí. Se copió a ~/.kube/config con permisos de usuario.docker build no es visible sin importarla con k3s ctr images import.git diff HEAD. Un repo recién iniciado sin commit no tiene HEAD → falla. Solución: commitear una línea base limpia y luego meter el secreto.Si borras todo (kubectl delete ns opsguard) y quieres reconstruirlo, esto es el camino completo. Ejecuta desde projects/ops_guard/.
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $USER:$USER ~/.kube/config && chmod 600 ~/.kube/config
export KUBECONFIG=$HOME/.kube/config # k3s exporta uno global que hay que pisar
kubectl get nodes
docker build -t opsguard:lab .
docker save opsguard:lab -o /tmp/opsguard-lab.tar
sudo k3s ctr images import /tmp/opsguard-lab.tar
sudo k3s ctr images ls | grep opsguard # verificar que está en containerd
kubectl apply -f deploy/k8s/00-namespace.yaml
# Secret real, fuera de git. Gate 1 no usa la key (valor dummy vale).
# Para usar Gate 2 (LLM), pon aquí tu OPENROUTER_API_KEY real.
kubectl -n opsguard create secret generic opsguard-secrets \
--from-literal=OPENROUTER_API_KEY='dummy-not-used-by-gate1'
kubectl apply -f deploy/k8s/20-deployment.yaml \
-f deploy/k8s/30-service.yaml \
-f deploy/k8s/40-ingress.yaml
kubectl -n opsguard rollout status deploy/opsguard-runner
kubectl -n opsguard get pods,svc,ingress
kubectl apply -f deploy/k8s/45-configmap-scan.yaml \
-f deploy/k8s/50-job-scan.yaml
# el Job termina en Failed a propósito (BLOCK = exit≠0 = fail-closed)
kubectl -n opsguard wait --for=condition=failed job/opsguard-scan --timeout=60s
kubectl -n opsguard logs job/opsguard-scan
🛡️ OpsGuard-AI — Security Gate Active
🚨 DETECTED 2 STATIC VIOLATIONS:
[AWS Access Key] AKIAIOSFODNN7EXAMPLE Diff
[AWS Secret Key] wJalrXUtnFEMI/K7MDENG/bPxRf... Diff
⛔ PIPELINE BLOCKED: SECURITY VIOLATION DETECTED
Cosas para meterle mano y entender cómo responde. Todo sobre lo que ya está desplegado.
kubectl -n opsguard exec -it deploy/opsguard-runner -- sh
# dentro del pod:
echo $OPENROUTER_API_KEY # viene del Secret por envFrom
cd /tmp && git init demo && cd demo
git config user.email t@t.co && git config user.name t
cp /app/opsguard.yml . && echo ok > a.py && git add . && git commit -qm base
echo 'AWS_KEY="AKIAIOSFODNN7EXAMPLE"' > leak.py && git add leak.py
opsguard scan # mira cómo bloquea
# edita el script del escaneo (prueba un fichero SIN secretos y mira que pase)
kubectl -n opsguard edit configmap opsguard-scan-demo
kubectl -n opsguard delete job opsguard-scan --ignore-not-found
kubectl apply -f deploy/k8s/50-job-scan.yaml
kubectl -n opsguard logs -f job/opsguard-scan
curl -s -o /dev/null -w "HTTP %{http_code}\n" \
-H "Host: opsguard.localhost" http://192.168.1.51/
# HTTP 502 → no hay backend HTTP. Ese es el punto.
kubectl -n opsguard describe deploy opsguard-runner
kubectl -n opsguard get events --sort-by=.lastTimestamp | tail -20
kubectl -n opsguard scale deploy/opsguard-runner --replicas=2 # y vuelve a 1
kubectl delete ns opsguard # se lleva pod, svc, ingress, secret, configmap
sudo k3s ctr images rm docker.io/library/opsguard:lab # opcional: la imagen
El namespace opsguard está aislado de tu homelab (Grafana, smartctl-exporter siguen intactos en Docker). Borrarlo no toca nada más.
El lab es materia prima. Estas dos cosas son tuyas — por eso no las toqué.
Escribir el bruto del primer post de la serie con tu voz. La historia fuerte ya está: “desplegué mi herramienta en k8s y el clúster me dijo la verdad — no es un servicio, es un Job, y el Ingress me devolvió un 502 honesto”. El diario en deploy/k8s/DIARIO_LAB.md es tu materia prima; el borrador sale luego de /linkedin-editor con tu texto.
Si OpsGuard vuelve al CV, mi propuesta es que sea como “desplegado en Kubernetes (k3s) como Job de CI con secreto gestionado”, nunca como microservicio (el 502 lo desmonta en entrevista). Es una propuesta: cualquier cambio pasa por _meta/cv_maestro/AGENT.md y por tu instrucción explícita en otra sesión.