fix: fixup multi problem
ci-api / test (push) Successful in 25m0s
ci-web / test (push) Failing after 7m59s

This commit is contained in:
Nuxgrid
2026-07-30 19:29:57 +02:00
parent 0848d3c0a0
commit ac8bdbcab7
14 changed files with 492 additions and 163 deletions
+9 -1
View File
@@ -104,7 +104,15 @@ func main() {
log.Fatalf("k8s client: %v", err)
}
helmProv, err := demos.NewHelmProvisioner(&cfg, k8sClient, "/charts", "helm")
// Client metrics.k8s.io (usage CPU/mémoire live) : optionnel, ne bloque
// pas le démarrage si metrics-server n'est pas déployé sur le cluster.
metricsClient, err := k8s.NewMetricsClient(&cfg)
if err != nil {
log.Printf("metrics client indisponible (usage CPU/mémoire désactivé): %v", err)
metricsClient = nil
}
helmProv, err := demos.NewHelmProvisioner(&cfg, k8sClient, metricsClient, "/charts", "helm")
if err != nil {
log.Fatalf("helm provisioner: %v", err)
}
+1
View File
@@ -72,6 +72,7 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.140.0 // indirect
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
k8s.io/metrics v0.36.3 // indirect
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
+2
View File
@@ -181,6 +181,8 @@ k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
k8s.io/metrics v0.36.3 h1:NDKceAgWS8CJCdDtM5kFACkBOa9Lxia1jUiibJfvUgQ=
k8s.io/metrics v0.36.3/go.mod h1:NTLS8ybwn+zYGwKqYublWPvmnNp8N4pV3etjtx7XWaM=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
@@ -17,6 +17,7 @@ import (
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
metricsclient "k8s.io/metrics/pkg/client/clientset/versioned"
)
// HelmProvisioner : implémente Provisioner en appelant l'exécutable Helm.
@@ -24,7 +25,8 @@ import (
type HelmProvisioner struct {
cfg *config.Config
k8sClient *kubernetes.Clientset
chartsDir string // chemin vers le dossier des charts (ex: /charts)
metricsClient *metricsclient.Clientset // optionnel : nil si metrics-server indisponible
chartsDir string // chemin vers le dossier des charts (ex: /charts)
frontendImage string
backendImage string
baseDomain string
@@ -34,9 +36,11 @@ type HelmProvisioner struct {
// NewHelmProvisioner crée un nouveau provisioner Helm.
// chartsDir : chemin absolu vers le dossier contenant les charts (backend/, frontend/)
// helmPath : chemin vers l'exécutable helm (optionnel, default: "helm")
// metricsClient : client metrics.k8s.io pour l'usage CPU/mémoire live (optionnel, peut être nil).
func NewHelmProvisioner(
cfg *config.Config,
k8sClient *kubernetes.Clientset,
metricsClient *metricsclient.Clientset,
chartsDir string,
helmPath string,
) (*HelmProvisioner, error) {
@@ -55,6 +59,7 @@ func NewHelmProvisioner(
return &HelmProvisioner{
cfg: cfg,
k8sClient: k8sClient,
metricsClient: metricsClient,
chartsDir: chartsDir,
frontendImage: cfg.FrontendImage,
backendImage: cfg.BackendImage,
@@ -336,6 +341,38 @@ func (h *HelmProvisioner) waitForRollout(namespace string) error {
}
}
// componentResourceLimits : limites CPU/mémoire configurées dans chaque chart
// (deploy/chart-gestion/*/values.yaml), utilisées pour calculer un % d'usage.
var componentResourceLimits = map[string]struct {
cpuMilli int64
memMi int64
}{
"backend": {cpuMilli: 500, memMi: 256},
"frontend": {cpuMilli: 200, memMi: 128},
"postgresql": {cpuMilli: 1000, memMi: 1024},
"redis": {cpuMilli: 500, memMi: 512},
}
// componentKey identifie le composant (backend/frontend/postgresql/redis) à
// partir du nom de pod généré par Helm (ex: "demo-xxx-backend-...-6d59f4-abcde").
func componentKey(podName string) string {
switch {
case strings.Contains(podName, "backend"):
return "backend"
case strings.Contains(podName, "frontend"):
return "frontend"
case strings.Contains(podName, "postgresql"):
return "postgresql"
case strings.Contains(podName, "redis"):
return "redis"
default:
return ""
}
}
// GetResourceState : phase + usage CPU/mémoire live de chaque pod d'une démo.
// L'usage (metrics-server) est best-effort : s'il est indisponible, seule la
// phase du pod est renseignée plutôt que de faire échouer tout l'appel.
func (h *HelmProvisioner) GetResourceState(
ctx context.Context,
namespace string,
@@ -349,23 +386,47 @@ func (h *HelmProvisioner) GetResourceState(
return ResourceState{}, err
}
usage := map[string][2]int64{} // pod name -> [cpuMilli, memMi]
if h.metricsClient != nil {
if list, err := h.metricsClient.MetricsV1beta1().PodMetricses(namespace).List(ctx, metav1.ListOptions{}); err == nil {
for _, m := range list.Items {
var cpu, mem int64
for _, c := range m.Containers {
cpu += c.Usage.Cpu().MilliValue()
mem += c.Usage.Memory().Value() / (1024 * 1024)
}
usage[m.Name] = [2]int64{cpu, mem}
}
}
}
var state ResourceState
for _, pod := range pods.Items {
status := string(pod.Status.Phase)
key := componentKey(pod.Name)
if key == "" {
continue
}
switch {
case strings.Contains(pod.Name, "backend"):
state.APIState = status
cs := ComponentState{Phase: string(pod.Status.Phase)}
if limits, ok := componentResourceLimits[key]; ok {
cs.CPULimitMilli = limits.cpuMilli
cs.MemoryLimitMi = limits.memMi
}
if u, ok := usage[pod.Name]; ok {
cs.CPUMilli = u[0]
cs.MemoryMi = u[1]
}
case strings.Contains(pod.Name, "frontend"):
state.WebState = status
case strings.Contains(pod.Name, "postgresql"):
state.DBState = status
case strings.Contains(pod.Name, "redis"):
state.DBMemoryState = status
switch key {
case "backend":
state.API = cs
case "frontend":
state.Web = cs
case "postgresql":
state.DB = cs
case "redis":
state.DBM = cs
}
}
+16 -6
View File
@@ -27,11 +27,21 @@ type ExternalResource struct {
func (ExternalResource) TableName() string { return "external_pool" }
type ResourceState struct {
APIState string `gorm:"size:20;not null" json:"api_state"`
WebState string `gorm:"size:20;not null" json:"web_state"`
DBState string `gorm:"size:20;not null" json:"db_state"`
DBMemoryState string `gorm:"size:20;not null" json:"dbm_state"`
// ComponentState : état live d'un composant (pod) d'une démo — phase k8s +
// usage CPU/mémoire actuel rapporté par metrics-server, avec les limites
// configurées dans le chart Helm correspondant (pour calculer un pourcentage).
type ComponentState struct {
Phase string `json:"phase"` // Running, Pending, Failed, Unknown, "" si pod introuvable
CPUMilli int64 `json:"cpu_milli"` // usage CPU actuel, en millicores
CPULimitMilli int64 `json:"cpu_limit_milli"` // limite CPU configurée, en millicores
MemoryMi int64 `json:"memory_mi"` // usage mémoire actuel, en Mi
MemoryLimitMi int64 `json:"memory_limit_mi"` // limite mémoire configurée, en Mi
}
func (ResourceState) TableName() string { return "ressource_state" }
// ResourceState : état live des 4 composants d'une démo.
type ResourceState struct {
API ComponentState `json:"api"`
Web ComponentState `json:"web"`
DB ComponentState `json:"db"`
DBM ComponentState `json:"dbm"`
}
+24 -9
View File
@@ -8,19 +8,18 @@ import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
metricsclient "k8s.io/metrics/pkg/client/clientset/versioned"
)
func NewClient(cfg *config.Config) (*kubernetes.Clientset, error) {
var (
k8sCfg *rest.Config
err error
)
func restConfig(cfg *config.Config) (*rest.Config, error) {
if cfg.Kubeconfig != "" {
k8sCfg, err = clientcmd.BuildConfigFromFlags("", cfg.Kubeconfig)
} else {
k8sCfg, err = rest.InClusterConfig()
return clientcmd.BuildConfigFromFlags("", cfg.Kubeconfig)
}
return rest.InClusterConfig()
}
func NewClient(cfg *config.Config) (*kubernetes.Clientset, error) {
k8sCfg, err := restConfig(cfg)
if err != nil {
return nil, fmt.Errorf("k8s config: %w", err)
}
@@ -32,3 +31,19 @@ func NewClient(cfg *config.Config) (*kubernetes.Clientset, error) {
return client, nil
}
// NewMetricsClient crée un client pour l'API metrics.k8s.io (metrics-server),
// utilisée pour lire l'usage CPU/mémoire en direct des pods.
func NewMetricsClient(cfg *config.Config) (*metricsclient.Clientset, error) {
k8sCfg, err := restConfig(cfg)
if err != nil {
return nil, fmt.Errorf("k8s config: %w", err)
}
client, err := metricsclient.NewForConfig(k8sCfg)
if err != nil {
return nil, fmt.Errorf("metrics clientset: %w", err)
}
return client, nil
}