ActualBudget sur GKE Autopilot
Actual Budget est une application de finances personnelles centrée sur la confidentialité et
le fonctionnement en local (local-first), fondée sur la budgétisation par enveloppes à base
zéro. Le composant actual-server est un serveur de synchronisation Node.js léger qui stocke
chaque budget dans un fichier SQLite et le synchronise entre l'interface web et les clients
de bureau et mobiles. Ce module déploie ActualBudget sur GKE Autopilot sous la forme d'un
StatefulSet doté d'un PVC de type bloc par pod, au-dessus du socle
App_GKE, qui provisionne et gère l'infrastructure Google Cloud et Kubernetes
partagée.
Ce guide se concentre sur les services cloud qu'utilise ActualBudget et sur la manière de les explorer et de les exploiter depuis la console Google Cloud et la ligne de commande. Pour les mécanismes communs à toutes les applications GKE — Workload Identity, ingress, autoscaling, CI/CD, Cloud Armor, IAP, Binary Authorization, VPC Service Controls, sauvegardes et cycle de vie du déploiement — reportez-vous au guide du socle App_GKE plutôt que de les répéter ici.
1. Vue d'ensemble
ActualBudget s'exécute comme une charge de travail Node.js actual-server unique. Comme il
gère son propre stockage SQLite, le déploiement assemble un ensemble volontairement restreint
de services Google Cloud :
| Fonctionnalité | Service Google Cloud | Remarques |
|---|---|---|
| Calcul | GKE Autopilot | Pods actual-server, 1 vCPU / 1 GiB par défaut, réplica unique (min = max = 1) exécuté en tant que StatefulSet |
| Base de données | Aucune | Les budgets sont des fichiers SQLite sous /data — database_type = "NONE" est imposé par ActualBudget_Common ; aucune instance Cloud SQL n'est créée |
| Stockage persistant | PVC Kubernetes (volume bloc par pod) | stateful_pvc_enabled = true par défaut — un PVC standard-rwo de 20Gi monté sur /data (SQLite a besoin d'un stockage bloc, pas de GCS FUSE) |
| Stockage d'objets | Cloud Storage | Un bucket storage est toujours déclaré par ActualBudget_Common ; il n'est monté sur /data (via GCS FUSE) que lorsque le PVC bloc est désactivé |
| Secrets | Secret Manager | Facultatif — un jeton d'API de 32 caractères (enable_api_key, par défaut false) injecté en tant que ACTUAL_TOKEN via un Secret Kubernetes natif |
| Ingress | Service Kubernetes / Gateway API | Par défaut service_type = ClusterIP avec enable_custom_domain = true mais aucun domaine configuré — accès interne uniquement tant que vous n'ajoutez pas un LoadBalancer ou un domaine |
Valeurs par défaut judicieuses à connaître d'emblée :
- StatefulSet + PVC bloc par défaut.
stateful_pvc_enabled = truerésout automatiquementworkload_typeenStatefulSet(inutile de définir les deux) et monte un PVCstandard-rwode 20Gi sur/dataavecfsGroup = 3000, conformément à la convention du chart Helm d'ActualBudget, afin que le conteneur (UID 1000/GID 2000) puisse écrire sur le volume. Ce choix est délibérément préféré à GCS FUSE, car SQLite supporte mal un répertoire monté via FUSE. - Aucune base de données.
database_typeest fixé àNONEparActualBudget_Common; il n'y a ni instance Cloud SQL, ni jobdb-init, etenable_cloudsql_volumevautfalsepar défaut (pas de sidecar Cloud SQL Auth Proxy). - Redis est désactivé en dur, pas seulement désactivé par défaut. Le
main.tfde la variante transmetenable_redis = falseau socle App_GKE sans condition — il ne transmet pasvar.enable_redis— de sorte qu'ActualBudget ne reçoit jamais deREDIS_HOSTsur GKE, quelle que soit la valeur de cette variable. - Réplica unique par conception.
min_instance_count = 1etmax_instance_count = 1— le serveur suppose un accès exclusif à ses fichiers SQLite sur le PVC partagé. container_portest fixé à5006parActualBudget_Common, quelle que soit la valeur de la variablecontainer_portelle-même — cette variable est inerte (sa description et son texte de validation font référence à un6333obsolète, artefact de copier-coller inoffensif provenant d'un autre module).- Aucune génération d'identifiants administrateur ni de secret. Le mot de passe du serveur
est défini de manière interactive sur l'écran d'intégration du premier lancement — il n'y a
aucun identifiant pré-créé à récupérer, sauf si
enable_api_key = truegénère unACTUAL_TOKEN. - Les sondes de santé ciblent
/— HTTPGET /renvoie 200 dès que le serveur Node est à l'écoute, sans authentification. - Exposition interne uniquement par défaut.
service_typevaut par défautClusterIP(et nonLoadBalancer, contrairement à la plupart des modules d'application GKE) etenable_custom_domainvauttruepar défaut, maisapplication_domainsvaut[]par défaut — un nouveau déploiement n'a donc aucun point de terminaison externe accessible tant que vous n'avez pas définiservice_type = LoadBalancerou fourniapplication_domains(ainsi que le DNS).
2. Services Google Cloud et comment les explorer
Toutes les commandes supposent que vous avez exécuté
gcloud container clusters get-credentials <cluster> --region <region> --project <project>
et que PROJECT, REGION et NAMESPACE sont définis. L'espace de noms et les autres
identifiants figurent dans les sorties du déploiement.
A. GKE Autopilot — le StatefulSet ActualBudget
ActualBudget s'exécute comme un StatefulSet à réplica unique, ce qui donne à son pod une
identité stable (<statefulset-name>-0) et rattache le même PVC lors des redémarrages et des
mises à jour.
- Console : Kubernetes Engine → Workloads → sélectionnez la charge de travail ActualBudget pour voir les pods, les révisions et les événements.
- CLI :
kubectl get statefulset,pods,svc -n "$NAMESPACE"
kubectl logs -n "$NAMESPACE" statefulset/<service-name> --tail=100
kubectl describe pod -n "$NAMESPACE" -l app=<service-name>
Consultez App_GKE pour savoir comment sont gérés Autopilot, la mise à l'échelle et le type de charge de travail (Deployment ou StatefulSet).
B. Volumes persistants — le PVC bloc /data
Avec stateful_pvc_enabled = true (la valeur par défaut), un PersistentVolumeClaim par pod
est provisionné et monté sur /data, où actual-server écrit ses bases de données SQLite de
budget, ses fichiers serveur et ses fichiers utilisateur. La StorageClass standard-rwo par
défaut repose sur du SSD (Balanced PD) et consomme le quota régional SSD_TOTAL_GB (souvent
serré).
- Console : Kubernetes Engine → Storage → PersistentVolumeClaims.
- CLI :
kubectl get pvc -n "$NAMESPACE"
kubectl describe pvc -n "$NAMESPACE"
kubectl exec -n "$NAMESPACE" statefulset/<service-name> -- df -h /data
C. Cloud Storage — le bucket storage (solution de repli GCS FUSE)
ActualBudget_Common déclare toujours un bucket Cloud Storage (storage), mais celui-ci
n'est monté sur /data via GCS FUSE que lorsque le PVC bloc est désactivé
(stateful_pvc_enabled = false). Avec le PVC du StatefulSet par défaut en place, ce bucket
existe mais n'est pas monté.
- Console : Cloud Storage → Buckets.
- CLI :
gcloud storage buckets list --project "$PROJECT" --filter="name~actualbudget"
Consultez App_GKE pour le comportement des montages GCS Fuse et les options CMEK.
D. Secret Manager — jeton d'API facultatif
Par défaut, aucun secret n'est créé. Lorsque enable_api_key = true, un jeton aléatoire de 32
caractères est généré, stocké dans Secret Manager sous le nom secret-<prefix>-<app>-api-key
et injecté dans le pod en tant que variable d'environnement ACTUAL_TOKEN via un Secret
Kubernetes natif — utile pour les automatisations qui doivent appeler le serveur avant que
l'interface ne soit configurée.
- Console : Security → Secret Manager.
- CLI :
gcloud secrets list --project "$PROJECT" --filter="name~api-key"
gcloud secrets versions access latest --secret=<secret-name> --project "$PROJECT"
Consultez App_GKE pour l'intégration du Secret Store CSI et la rotation.
E. Réseau et entrée
La charge de travail utilise par défaut service_type = ClusterIP — aucune IP externe n'est
créée d'emblée. enable_custom_domain = true provisionne une ressource Kubernetes Gateway
API, mais avec application_domains = [] par défaut, il n'y a aucun nom d'hôte à router.
Définissez service_type = LoadBalancer pour obtenir une IP externe directe, ou renseignez
application_domains (avec un DNS pointant vers l'IP obtenue) pour une Gateway sur domaine
personnalisé avec un certificat géré.
- Console : Kubernetes Engine → Services & Ingress ; Network services → Load balancing.
- CLI :
kubectl get svc,gateway -n "$NAMESPACE"
gcloud compute addresses list --project "$PROJECT"
Consultez App_GKE pour les domaines personnalisés, Cloud CDN et les détails sur les IP statiques.
F. Cloud Logging et Monitoring
Les flux stdout/stderr des pods sont envoyés à Cloud Logging ; les métriques GKE sont envoyées
à Cloud Monitoring. Un test de disponibilité facultatif (uptime_check_config, désactivé par
défaut) nécessite un point de terminaison accessible publiquement, ce que la valeur par défaut
ClusterIP ne fournit pas.
- Console : Logging → Logs Explorer ; Monitoring → Dashboards / Alerting.
- CLI :
gcloud logging read 'resource.type="k8s_container" AND resource.labels.namespace_name="'"$NAMESPACE"'"' \
--project "$PROJECT" --limit 50
3. Comportement de l'application ActualBudget
- Aucun job d'initialisation. Il n'y a aucune base de données à amorcer ; le serveur crée
ses fichiers SQLite sous
/dataau premier démarrage. Desinitialization_jobspersonnalisés sont acceptés pour des tâches de chargement ou de migration de données, mais aucun n'est fourni par défaut. - Configuration du premier lancement. Au premier accès, l'interface web affiche un écran d'intégration où vous définissez le mot de passe du serveur — il n'existe aucun identifiant pré-créé à récupérer. Faites-le immédiatement après avoir rendu le service accessible ; tant qu'aucun mot de passe n'est défini, toute personne pouvant atteindre l'URL peut s'approprier le serveur.
- Organisation des données.
ACTUAL_SERVER_FILES = /data/server-files(métadonnées du serveur et base de données des comptes) etACTUAL_USER_FILES = /data/user-files(données de synchronisation par budget), tous deux sur le montage persistant/data(PVC par défaut). - Modèle de synchronisation local-first. Les clients (web, bureau, mobile) conservent une copie locale complète du budget et n'utilisent le serveur que pour synchroniser les modifications chiffrées entre appareils — une brève indisponibilité du serveur n'empêche pas de travailler dans un client.
- Contrainte d'écrivain unique, mises à jour sûres du StatefulSet. Le serveur suppose un
accès exclusif à ses fichiers SQLite ; conservez
max_instance_count = 1. Comme la charge de travail est un StatefulSet doté d'une identité stable par pod rattachée au même PVC, la stratégieRollingUpdatepar défaut remplace le pod unique sur place au lieu de démarrer un second pod sur le volume partagé — contrairement aux combinaisons Deployment+NFS utilisées par d'autres modules de ce dépôt, il n'y a ici aucun risque d'interblocage dû à un double montage. - Priorité des sondes.
startup_probe_configethealth_check_config(les variables de sonde génériques de premier niveau d'App_GKE) sont inertes pour ActualBudget — la configuration effective des sondes provient toujours des variablesstartup_probe/liveness_probepropres à ActualBudget (transmises via la sortieconfigd'ActualBudget_Common), qui ciblent toutes deux HTTPGET /sans authentification. - Mises à jour de version. Modifiez
application_versionet réappliquez — Cloud Build produit une nouvelle image et le StatefulSet déploie la nouvelle révision.latestconstruit la version épinglée25.7.1via l'ARG de build spécifique à l'applicationACTUALBUDGET_VERSION(et non l'APP_VERSIONgénérique que le socle injecte). - Chemin de santé. Sonde de démarrage : HTTP
GET /, délai initial de 15s, timeout de 10s, période de 10s, 10 échecs tolérés. Sonde de vivacité : HTTPGET /, délai initial de 30s, timeout de 5s, période de 30s, 3 échecs tolérés. Les deux renvoient 200 sans authentification dès que le serveur HTTP est à l'écoute. - Vérification :
kubectl get statefulset,pods,svc -n "$NAMESPACE"
POD=$(kubectl get pods -n "$NAMESPACE" -l app=<service-name> -o jsonpath='{.items[0].metadata.name}')
kubectl port-forward -n "$NAMESPACE" "$POD" 5006:5006 &
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:5006/ # expect 200
4. Variables de configuration
Les variables sont regroupées exactement comme elles apparaissent sur la plateforme de déploiement. Seuls les paramètres propres à ActualBudget ou notables pour lui sont listés ; toutes les autres entrées sont héritées d'App_GKE avec leur comportement et leurs valeurs par défaut standard.
Groupe 3 — Identité de l'application
| Variable | Valeur par défaut | Description |
|---|---|---|
application_name | actualbudget | Nom de base des ressources. Ne pas modifier après le premier déploiement. |
application_version | latest | Tag de version de l'image ; latest construit la version épinglée 25.7.1. |
enable_api_key | false | Génère un jeton d'API de 32 caractères dans Secret Manager et l'injecte en tant que ACTUAL_TOKEN. Recommandé pour tout déploiement accessible en dehors du pod/de l'espace de noms. |
Groupe 4 — Exécution et mise à l'échelle
| Variable | Valeur par défaut | Description |
|---|---|---|
cpu_limit | 1000m | actual-server est un processus Node.js léger ; 1 vCPU suffit. |
memory_limit | 1Gi | Une mémoire modeste suffit pour des fichiers de budget typiques. |
min_instance_count | 1 | Garde l'instance unique active et évite les démarrages à froid. |
max_instance_count | 1 | Conservez 1 — un seul volume SQLite partagé, un seul écrivain. |
container_port | 5006 | Inerte — ActualBudget_Common fixe toujours le port du conteneur à 5006. |
enable_cloudsql_volume | false | Pas de Cloud SQL — laissez false. |
enable_image_mirroring | true | Réplica actualbudget/actual-server dans Artifact Registry pour éviter les limites de débit de Docker Hub. |
Groupe 6 — Backend GKE et cluster
| Variable | Valeur par défaut | Description |
|---|---|---|
service_type | LoadBalancer | Accessible depuis l'extérieur par défaut, comme tous les autres modules d'application GKE destinés au navigateur. Définissez ClusterIP pour le garder interne uniquement. |
workload_type | null → StatefulSet | Résolu automatiquement car stateful_pvc_enabled = true par défaut. |
session_affinity | None | Aucun routage persistant configuré (le réplica unique rend ce point largement sans objet). |
Groupe 7 — Configuration du StatefulSet
| Variable | Valeur par défaut | Description |
|---|---|---|
stateful_pvc_enabled | true | Activé par défaut — la base SQLite de budget et les fichiers utilisateur d'actual-server nécessitent un stockage bloc, pas GCS FUSE. |
stateful_pvc_size | 20Gi | Taille du PVC par pod ; dimensionnez-la pour les bases de données et fichiers de budget, marge comprise. |
stateful_pvc_mount_path | /data | Emplacement où actual-server conserve sa base SQLite, ses fichiers serveur et ses fichiers utilisateur. |
stateful_pvc_storage_class | standard-rwo | Balanced PD sur SSD ; consomme le quota SSD_TOTAL_GB — remplacez par standard (HDD) si ce quota est serré. |
stateful_fs_group | 3000 | Correspond à la convention fsGroup du chart Helm d'ActualBudget afin que le conteneur (UID 1000/GID 2000) puisse écrire sur le PVC. |
Groupe 9 — Règles de fiabilité
| Variable | Valeur par défaut | Description |
|---|---|---|
enable_pod_disruption_budget | true | Activé par défaut (contrairement à la plupart des modules) — protège le réplica unique du StatefulSet lors des interruptions volontaires de nœud. |
pdb_min_available | 1 | Nombre minimal de pods disponibles lors des interruptions volontaires. |
Groupe 10 — Observabilité et santé
| Variable | Valeur par défaut | Description |
|---|---|---|
startup_probe | HTTP /, délai initial de 15s, 10 échecs | La sonde de démarrage effective (voir §3 « Priorité des sondes »). |
liveness_probe | HTTP /, délai initial de 30s, 3 échecs | La sonde de vivacité effective. |
startup_probe_config / health_check_config | HTTP / | Déclarées pour refléter les variables du socle, mais inertes pour ActualBudget — utilisez plutôt startup_probe / liveness_probe ci-dessus. |
uptime_check_config | désactivé | À activer uniquement une fois le point de terminaison accessible publiquement (LoadBalancer ou domaine personnalisé). |
Groupe 14 — Cloud Storage et Artifact Registry
| Variable | Valeur par défaut | Description |
|---|---|---|
create_cloud_storage | true | Crée toujours le bucket storage que déclare ActualBudget_Common. |
gcs_volumes | [] | Montages GCS FUSE supplémentaires ; le bucket storage n'est monté automatiquement sur /data que lorsque stateful_pvc_enabled = false. |
Groupe 15 — Cache Redis
| Variable | Valeur par défaut | Description |
|---|---|---|
enable_redis | true (valeur par défaut de la variable) | Sans effet — main.tf impose enable_redis = false au socle, quelle que soit la valeur de cette variable. |
redis_host / redis_port / redis_auth | inertes | Sans objet — ActualBudget n'a pas d'intégration Redis sur GKE. |
Groupe 16 — Backend de base de données
| Variable | Valeur par défaut | Description |
|---|---|---|
database_type | NONE (fixe) | ActualBudget_Common le fixe à NONE ; aucune instance Cloud SQL n'est créée, quelle que soit cette variable. |
application_database_name / application_database_user | actualbudgetdb / actualbudgetuser | Transmises uniquement pour la compatibilité avec le socle — non référencées (il n'existe aucune base de données). |
Groupe 19 — Domaine personnalisé, IP statique et réseau
| Variable | Valeur par défaut | Description |
|---|---|---|
enable_custom_domain | true | Activé par défaut, mais avec application_domains = [], la Gateway n'a aucun nom d'hôte à router tant que vous n'en fournissez pas un. |
application_domains | [] | À renseigner pour exposer ActualBudget via un domaine personnalisé + certificat géré. |
reserve_static_ip | true | Réserve une IP statique même si service_type vaut par défaut ClusterIP (aucune IP de LoadBalancer n'est allouée par défaut). |
Toutes les autres entrées suivent le comportement standard d'App_GKE.
5. Sorties
Ces valeurs sont renvoyées lors d'un déploiement réussi et constituent le moyen le plus rapide de localiser et d'explorer les ressources en cours d'exécution.
| Sortie | Description |
|---|---|
service_name | Nom du Service Kubernetes. |
namespace | Espace de noms dans lequel s'exécute la charge de travail. |
service_cluster_ip | ClusterIP interne au cluster. |
stage_service_cluster_ips | Table des ClusterIP des services propres à chaque étape. |
service_external_ip | IP externe du LoadBalancer (lorsque service_type = LoadBalancer et qu'une IP statique est réservée). |
service_url | URL pour accéder à ActualBudget. |
actualbudget_api_key_secret_id | ID du secret Secret Manager de la clé d'API. Vide lorsque enable_api_key = false. |
statefulset_name | Nom du StatefulSet. |
storage_buckets | Buckets Cloud Storage créés (y compris le bucket storage). |
network_name / network_exists / regions | Réseau VPC, présence, régions disponibles. |
container_image / container_registry | Image déployée et dépôt Artifact Registry. |
monitoring_enabled / monitoring_notification_channels | État de la surveillance et canaux. |
initialization_jobs | Noms des éventuels jobs de configuration personnalisés (vide par défaut). |
deployment_id / tenant_id / resource_prefix | Identifiants de nommage. |
project_id / project_number | Identifiants du projet. |
cicd_enabled / cicd_configuration | État et détails du CI/CD (dépôt, déclencheur, registre). |
github_repository_url / github_repository_owner / github_repository_name | Détails GitHub du CI/CD. |
artifact_registry_repository / cloudbuild_trigger_name / cloudbuild_trigger_id | Registre et déclencheur de build. |
kubernetes_ready | Indique si le cluster/la charge de travail est prêt. |
vpc_sc_enabled / vpc_sc_perimeter_name / vpc_sc_dry_run_mode | État de VPC-SC. |
audit_logging_enabled / artifact_registry_cmek_enabled | État de la journalisation d'audit et de CMEK. |
6. Pièges de configuration et valeurs par défaut judicieuses
Risque : Critique (perte de données / panne / sécurité) — Élevé (service dégradé) — Moyen (coût ou dégradation partielle) — Faible (mineur).
Validation héritée au moment du plan. Ce module fait passer sa configuration par le moteur du socle App_GKE, qui valide les valeurs et leurs combinaisons au moment du plan — un
StatefulSetimposé avec un paramètre sans état, IAP sans identités autorisées, desquota_memory_*fournis sous forme d'entiers bruts, uncontainer_port/backup_retention_dayshors plage. Une configuration invalide fait échouer le plan avec une erreur claire et nommée avant toute création de ressource ; la plupart des erreurs ci-dessous sont donc détectées en amont plutôt qu'à l'application ou à l'exécution.
| Paramètre | Valeur judicieuse | Risque | Conséquence en cas d'erreur |
|---|---|---|---|
max_instance_count | 1 | Critique | Plusieurs pods écrivant dans les mêmes fichiers SQLite sur un même volume partagé exposent à une corruption ou à des conflits d'écriture. |
| Mot de passe du serveur au premier lancement | à définir immédiatement | Critique | Tant qu'aucun mot de passe n'est défini, toute personne pouvant atteindre le service peut s'approprier le serveur et ses données de budget. |
Contenu du PVC /data | ne jamais supprimer manuellement | Critique | Le PVC bloc est la seule copie des bases de données de budget ; le supprimer efface tous les budgets. |
stateful_pvc_enabled | true | Critique | Le désactiver entraîne un repli sur GCS FUSE pour /data, qui ne supporte pas SQLite sous de fortes écritures concurrentes. |
quota_memory_requests / _limits | unités binaires (4Gi, 8192Mi) | Critique | Les entiers bruts sont interprétés comme des octets et bloquent toute planification de pods. |
service_type / application_domains | en définir un pour exposer en externe | Élevé | Avec les valeurs par défaut (ClusterIP + aucun domaine), le service n'est accessible que depuis l'intérieur du cluster. |
stateful_pvc_storage_class | standard-rwo (SSD) | Moyen | Consomme le quota serré SSD_TOTAL_GB ; remplacez par standard (HDD) sur un projet limité en quota — SQLite n'a pas besoin des IOPS d'un SSD. |
enable_redis | n'importe quelle valeur — inerte | Faible | Tenter d'activer Redis via cette variable n'a aucun effet sur GKE ; main.tf le force toujours à désactivé. |
container_port | 5006 (fixe) | Faible | La variable est inerte ; sa propre description et son texte de validation font référence à un numéro de port obsolète et sans rapport. |
startup_probe_config / health_check_config | n'importe quelle valeur — inertes | Faible | Utilisez plutôt startup_probe / liveness_probe pour modifier le timing des sondes ; ces deux variables sont ignorées pour ActualBudget. |
enable_api_key | true pour l'automatisation sur un point de terminaison accessible | Moyen | Sans ACTUAL_TOKEN, l'accès programmatique à l'API repose uniquement sur le mot de passe du serveur. |
backup_retention_days | 7 (à augmenter en production) | Moyen | Trop court pour une conservation conforme aux exigences réglementaires. |
Pour le comportement du socle évoqué tout au long de ce guide — IAM et Workload Identity, autoscaling, ingress et certificats, CI/CD, Cloud Armor, IAP, Binary Authorization, VPC-SC, sauvegardes et mise en miroir des images — consultez App_GKE. La configuration applicative propre à ActualBudget, partagée avec la variante Cloud Run, est décrite dans ActualBudget_Common.
Guides associés
- Lab pratique : ActualBudget sur GKE Autopilot — déployez-le pas à pas, avec les écrans de la console et les commandes à chaque étape.
- ActualBudget sur Google Cloud Run — la même application sur Cloud Run, lorsque vous avez besoin de l'autre cible de déploiement.
- ActualBudget Common — Configuration applicative partagée — la configuration partagée par les deux cibles de déploiement.
Need RAD to do something it does not do yet? Request it on the roadmap, or vote on what is already there.