Panorama general¶
Un solo programa, varios trabajos¶
rendimiento es un solo programa en Go (cmd/rendimiento) que hace cuatro trabajos a la vez. Cada trabajo es un conjunto de gorrutinas dentro del mismo proceso:
flowchart TB
subgraph proc[proceso de rendimiento]
direction TB
http[servidor HTTP :8080<br/>API REST · SSE · avisos web · interfaz]
subgraph mgr[administrador de controller-runtime]
appc[controlador de aplicaciones]
addc[controlador de complementos]
end
subgraph leader[arranca al ser elegido]
worker[trabajador de integración continua<br/>toma ejecuciones de la cola]
reno[programador de Renovate]
sync[sincronización de complementos desde git]
end
ddns[ciclo de DNS dinámico]
end
browser([Navegador]) --> http
github([GitHub]) -- avisos web --> http
http --> pg[(Postgres)]
worker --> pg
worker -- pods de construcción --> k8s[(API de Kubernetes)]
worker -- "buildctl (en pods)" --> bk[grupo de BuildKit]
appc --> k8s
addc -- suplantando a rendimiento-addons --> k8s
sync -- lee --> gitops[(p0dxD/gitops)]
ddns --> cf([Cloudflare])
appc --> cf
| Trabajo | Paquete | Qué hace |
|---|---|---|
| API e interfaz | internal/api |
Sirve la interfaz de React (incrustada en el programa), la API JSON, las actualizaciones en vivo con eventos enviados por el servidor, los avisos web de GitHub, el inicio de sesión y la configuración de la aplicación de GitHub. |
| Orquestación | internal/platform |
Convierte los avisos web en ejecuciones de integración continua, las ejecuciones en versiones y las versiones en objetos App. Aquí viven la incorporación, la reversión, la eliminación y la desconexión. |
| Integración continua | internal/pipeline |
Planea una ejecución como un grafo de pasos y ejecuta cada paso como un pod; las construcciones van al grupo de BuildKit. |
| Controlador de aplicaciones | internal/controller |
Concilia cada App en espacios de nombres, Deployments, Services, Ingresses, volúmenes, CronJobs, registros DNS y estado. |
| Controlador y sincronización de complementos | internal/controller, internal/addon |
Mantiene los objetos Addon al día con p0dxD/gitops/addons/*.yaml y concilia cada uno en los objetos que describen su paquete de Helm o sus manifiestos. |
| Complementos integrados | internal/renovate |
Renovate se ejecuta según una programación. |
| Vistas | internal/catalog, internal/environment |
La página Servicios y la página Entorno. |
¿Por qué "arranca al ser elegido"?¶
Algunos trabajos deben ocurrir exactamente una vez en el clúster: tomar ejecuciones de integración continua, programar Renovate, sincronizar complementos. La elección de líder de controller-runtime garantiza que solo una réplica los haga. Hoy hay una sola réplica con la elección de líder desactivada (en un plano de control de Raspberry Pi ocupado, las renovaciones del arrendamiento vencían y mataban el proceso a media construcción), así que mgr.Elected() se dispara de inmediato. La estructura ya está lista para más réplicas; vea Escalar.
Cómo los conecta main.go¶
cmd/rendimiento/main.go es la raíz de composición: el único lugar que lee la configuración y crea las implementaciones concretas. Todo lo demás recibe lo que necesita como campos de estructura o como interfaces. En orden:
- lee los ajustes del entorno (todos);
- abre Postgres, ejecuta las migraciones y vuelve a poner en cola las ejecuciones que interrumpió el último reinicio;
- crea el administrador de controller-runtime (cliente de Kubernetes, caché, métricas en
:9090); - elige el proveedor de DNS: Cloudflare si hay un token (y arranca el DNS dinámico con
DNS_TARGET=auto), si no,dns.Noop; - arma las opciones de generación (clase de entrada, emisor, clase de almacenamiento, perfil de GPU) y registra el controlador de aplicaciones;
- carga las credenciales de la aplicación de GitHub en un
github.Holder(vacío hasta la configuración); - crea el cliente con la identidad de los complementos (que suplanta a
rendimiento-addons), el generador y el sincronizador, y registra el controlador de complementos; - crea la plataforma, el ejecutor de Kubernetes (espacio de nombres de construcción, grupo de BuildKit, Railpack, nodos excluidos), el ejecutor de integración continua y el ejecutor de Renovate;
- crea el servidor de la API con todo lo anterior;
- arranca el administrador, los ciclos exclusivos del líder y el servidor HTTP, y espera una señal o un error.
baseURL := strings.TrimRight(env("BASE_URL", "http://localhost:8080"), "/")
ns := env("NAMESPACE", "rendimiento-system")
dsn := os.Getenv("DATABASE_URL")
if dsn == "" {
return errors.New("DATABASE_URL is required")
}
users := strings.FieldsFunc(os.Getenv("ALLOWED_USERS"), func(r rune) bool { return r == ',' || r == ' ' })
if len(users) == 0 {
return errors.New("ALLOWED_USERS is required (comma-separated GitHub logins)")
}
st, err := store.Open(ctx, dsn)
if err != nil {
return fmt.Errorf("database: %w", err)
}
defer st.Close()
if err := st.Migrate(ctx); err != nil {
return fmt.Errorf("migrate: %w", err)
}
go problemLog.Run(ctx, st, plain)
if requeued, failed, err := st.RequeueOrphans(ctx); err != nil {
return fmt.Errorf("recover interrupted runs: %w", err)
} else if requeued+failed > 0 {
log.Warn("recovered runs interrupted by the last restart", "requeued", requeued, "failed", failed)
}
Los paquetes, en capas¶
Las flechas significan "importa". Las capas de abajo nunca importan a las de arriba, lo que permite probar cada paquete por separado.
flowchart TB
cmd[cmd/rendimiento] --> api
api[internal/api] --> platform & controller & catalog & environment & renovate & addon
platform[internal/platform] --> pipeline & controller & generate & store & github & events & render
controller[internal/controller] --> render & addon & dns
renovate[internal/renovate] --> store & github
catalog[internal/catalog] --> render
environment[internal/environment] --> github & dns
generate[internal/generate] --> detect & templates[templates]
store[internal/store] --> pipeline
addon[internal/addon] --> github & crd
render[internal/render] --> spec
pipeline[internal/pipeline] --> spec
crd[api/v1alpha1] --> spec[internal/spec]
- Hasta abajo,
internal/spec(los tipos derendimiento.yaml) y paquetes hoja pequeños (dns,detect,events,github) no dependen de nada nuestro. api/v1alpha1define los recursos personalizados (App,Addon), reutilizando los tipos de la especificación para que unApplleve exactamente lo que dicerendimiento.yaml.renderes puro: entran la especificación y las imágenes, salen objetos de Kubernetes. Sin entrada ni salida, lo que lo hace fácil de probar con archivos de referencia.controllerypipelinehacen la entrada y salida contra Kubernetes.platformune la integración continua, el almacén y GitHub.apies la capa más externa.
Todo lo que está bajo internal/ es privado de este módulo (una regla de Go): otros proyectos no lo pueden importar. Eso nos deja libres de cambiarlo. Si algún día unas partes deben volverse una biblioteca pública, saldrían de internal/.
Dónde vive el estado¶
| Qué | Dónde | Por qué ahí |
|---|---|---|
| Lo que debe ejecutarse por aplicación (servicios, tareas programadas, imágenes publicadas) | Objetos App en Kubernetes |
Los controladores los vigilan; kubectl puede verlos y corregirlos; el clúster funciona sin la base de datos. |
| La configuración de cada aplicación | rendimiento.yaml en cada repositorio |
GitOps: revisada, versionada, y los cambios se despliegan al integrarlos. |
| Las definiciones de los complementos | p0dxD/gitops/addons/*.yaml → objetos Addon |
Las mismas razones, para los programas del clúster. |
| Ejecuciones, pasos, registros, versiones, sesiones, ajustes y ejecuciones de complementos | Postgres | Historial relacional, una cola de trabajo, texto grande (los registros). |
| Secretos | Secrets de Kubernetes (secretos sellados en git para las aplicaciones que los usan) | Nunca en git en texto plano, nunca en Postgres. |
Más en Datos y estado.
La interfaz¶
Una aplicación de una sola página en React + TypeScript (web/), construida con Vite en web/dist e incrustada en el programa de Go con //go:embed. No hay un servidor web aparte: el programa sirve los archivos y responde con index.html a las rutas del lado del cliente. Vea API, autenticación e interfaz.