Pipeline de integración y despliegue continuo que corre íntegramente dentro de un
cluster de Kubernetes, sin depender de GitHub Actions, Jenkins ni ninguna herramienta
externa. Detecta commits nuevos en un repositorio Git, ejecuta los tests, construye la
imagen de la aplicación y actualiza el despliegue — todo mediante recursos nativos de
Kubernetes (Job, CronJob, RBAC) y Kaniko para el build de imágenes sin Docker daemon.
CronJob (polling Git) → Job (tests) → Job (build con Kaniko) → Job (deploy) → Deployment
│
▼
Controller (FastAPI + Redis)
— estado de cada run del pipeline
- Fase 1 — Controller: API de estado del pipeline (FastAPI + Redis)
- Fase 2 — CronJob de polling de Git
- Fase 3 — Job de tests con initContainer
- Fase 4 — Job de build con Kaniko
- Fase 5 — Job de deploy
- Fase 6 — RBAC del pipeline (ServiceAccounts, Role por etapa)
Las 6 fases están completas y verificadas de punta a punta.
Detalle de lo verificado hasta ahora:
- Fase 2:
git-poller(CronJob cada minuto,k8s/git-poller-cronjob.yaml) consulta el último commit del repo vigilado (flowpipe-configConfigMap) congit ls-remote, pregunta al controller si ese SHA ya fue procesado y, si es nuevo, lanza unJoba partir de la plantilla del CronJobtest-runner(kubectl create job ... --from=cronjob/test-runner). Corre bajo su propio ServiceAccount (flowpipe-poller-sa, ver Fase 6) con permisos mínimos definidos enk8s/flowpipe-rbac.yaml. - Fase 3:
test-runner(CronJob suspendido, sólo se dispara vía el poller,k8s/test-runner-cronjob.yaml) clona el repo en uninitContainer, instala dependencias y correpytestreal contra el código clonado, reportandosuccess/failedal controller víaPOST /runs/update. - Fase 4: si los tests pasan, ese mismo
Jobencadenakaniko-build(CronJob suspendido,k8s/kaniko-build-cronjob.yaml) víakubectl create job ... --from=cronjob/kaniko-build. Kaniko clona el repo, construye la imagen a partir de suDockerfilesin depender de un Docker daemon, y la publica en el registry local de minikube con el SHA del commit como tag — trazabilidad completa entre commit → imagen. - Fase 5: como Kaniko no tiene shell (no puede encadenar nada por sí mismo),
test-runneractúa como orquestador del resto del pipeline: espera conkubectl wait --for=condition=completea que termine elJobde build, reportastage:"build"al controller, y si tuvo éxito encadenaflowpipe-deploy(CronJob suspendido,k8s/flowpipe-sample-deploy-cronjob.yaml), que aplica de forma idempotente unDeploymentparaflowpipe-samplecon la imagen recién construida — actualizándolo (rolling update) en cada commit nuevo. - Fase 6: se reemplazó la
ServiceAccountúnica compartida por tres — una por etapa (flowpipe-poller-sa,flowpipe-test-sa,flowpipe-deploy-sa), cada una con su propioRoleacotado solo a los verbos y recursos que esa etapa usa de verdad (p. ej.flowpipe-deploy-sano puede crearJobs, soloDeployments; el acceso a las plantillas deCronJobestá además acotado porresourceNamesa las que cada etapa realmente encadena).kaniko-buildno tieneServiceAccountpropia — usa eldefaultdel namespace porque no llama a la API de Kubernetes en absoluto.
Requiere Docker Desktop, minikube y kubectl.
minikube start
minikube addons enable registry
minikube docker-env | Invoke-Expression
docker build -t flowpipe-controller:latest ./controller
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/flowpipe-config.yaml
kubectl apply -f k8s/flowpipe-rbac.yaml
kubectl apply -f k8s/redis-deployment.yaml
kubectl apply -f k8s/controller-deployment.yaml
kubectl apply -f k8s/git-poller-cronjob.yaml
kubectl apply -f k8s/test-runner-cronjob.yaml
kubectl apply -f k8s/kaniko-build-cronjob.yaml
kubectl apply -f k8s/flowpipe-sample-deploy-cronjob.yaml
kubectl get pods -n flowpipeProbar el controller:
kubectl port-forward -n flowpipe svc/flowpipe-controller 8000:80
# en otra terminal
Invoke-RestMethod -Uri http://localhost:8000/runs/update -Method Post `
-ContentType "application/json" `
-Body '{"commit_sha":"abc123","stage":"test","status":"success"}'
Invoke-RestMethod -Uri http://localhost:8000/runs/abc123- Kaniko en vez de Docker-in-Docker (Fase 4): construir imágenes dentro de un Pod
normalmente requeriría montar
/var/run/docker.socko correr el contenedor en modoprivileged, lo que da acceso efectivo al nodo. Kaniko construye la imagen en espacio de usuario, sin socket ni privilegios especiales — el enfoque recomendado para CI/CD nativo de Kubernetes. - Registry local sin TLS (Fase 4): el addon
registryde minikube no trae certificado, por lo que Kaniko usa--insecure --skip-tls-verifypara poder publicar en él. Válido únicamente para este entorno de aprendizaje/local — en un clúster real este sería un registry con TLS yimagePullSecrets, nunca una conexión sin cifrar. - Imagen del Deployment por IP del Service, no por su DNS interno (Fase 5): el
perfil de minikube usado en este proyecto corre con
--container-runtime=docker. Con ese runtime, el pull de imágenes lo hace el daemon de Docker del nodo directamente, fuera de la red/DNS del clúster — por eso no resuelve nombres*.svc.cluster.local, aunque Kaniko sí pudo publicar con ese mismo nombre (su push corre dentro de un Pod, donde el DNS del clúster sí aplica). La solución fue resolver la IP del Serviceregistryen tiempo de ejecución (kubectl get svc ... -o jsonpath='{.spec.clusterIP}') en vez de hardcodearla, con unRoleacotado porresourceNamespara queflowpipe-deploy-sasolo pueda leer ese Service puntual enkube-system. En un clúster real, concontainerdcomo runtime y un registry externo con DNS público, este problema no existiría. - RBAC de mínimo privilegio por etapa (Fase 6): antes de esta fase, las tres
CronJobque usankubectlcompartían una únicaServiceAccountcon la unión de todos los permisos — cualquiera de ellas podía, en teoría, crearJobso modificarDeploymentsaunque nunca lo hiciera. Separar unaServiceAccount/Rolepor etapa acota el radio de daño si alguna llegara a verse comprometida: cada una solo puede hacer exactamente lo que su propio script necesita, nada más.