Architecture¶
This page explains how the operator works so you can reason about its behaviour when things go wrong, and decide which configuration belongs where.
If you want to use the operator, start with the Quick Start. Come back here when you need to understand why something is happening.
High-level picture¶
flowchart LR
U([hq / kubectl apply]) -->|GitlabInstance CR| K8s[(Kubernetes API)]
K8s --> Op[bnerd-gitlab-operator\nreconcile loop]
Op -->|provisions| PG[PerconaPGCluster\nPercona PG Operator]
Op -->|provisions| Redis[Redis CRs\nOT-Container-Kit]
Op -->|provisions EE| ES[Elasticsearch\nECK]
Op -->|creates/updates| Sec[Operator Secrets]
Op -->|emits| HR[Flux HelmRelease\n+ HelmRepository]
HR -->|Flux drives| Chart[GitLab Helm Chart\ncharts.gitlab.io]
Chart --> Pods[GitLab Pods]
PG --> Pods
Redis --> Pods
ES --> Pods
Sec --> Pods
style Op fill:#6366f1,stroke:#4f46e5,color:#fff
style HR fill:#16a34a,stroke:#15803d,color:#fff
The operator does not install GitLab itself — it generates Helm values and emits a Flux HelmRelease that causes Flux's helm-controller to install the upstream GitLab Helm chart. This means any upstream chart feature is available via spec.helm.values as an escape hatch.
Reconcile sequence¶
When you kubectl apply a GitlabInstance, the operator runs this sequence on every reconcile:
sequenceDiagram
participant U as kubectl
participant K as Kubernetes API
participant O as Operator
participant F as Flux helm-controller
U->>K: kubectl apply GitlabInstance
K->>O: reconcile triggered
O->>O: 1. Validate spec
O->>O: 2. Resolve version via GitlabVersionMap/default
O->>O: 3. Resolve GitlabProfile defaults
O->>K: 4. Provision/check managed Postgres (PerconaPGCluster)
Note over O,K: Requeue every 15s until PG ready
O->>K: 4b. Provision/check managed Redis
Note over O,K: Requeue every 15s until Redis ready
O->>K: 4c. EE: Provision/check managed Elasticsearch
O->>K: 5. Read & validate S3 credentials Secret
O->>K: 5b. Ensure registry-storage Secret
O->>O: 6. Compose Helm values (4-layer merge)
O->>K: 7. Apply HelmRepository + HelmRelease
O->>K: 8. Synthesise status (phase, host, conditions)
F->>K: helm upgrade/install GitLab chart
Step details¶
1. Validate spec — checks that spec.domains.gitlab is present, edition is ce or ee, and that licenseSecret is set for EE. A validation failure sets phase: Failed permanently (no requeue).
2. Version resolution — if spec.helm.version is set, that chart version is used directly (escape hatch). Otherwise the operator fetches the cluster-scoped GitlabVersionMap/default and resolves spec.version through it (aliases → prefix matching → exact match). A missing version map is transient (requeue with back-off); an unknown version string is permanent.
3. Profile defaults — if spec.profile is set, the operator fetches the named GitlabProfile and merges its spec.defaults into the composition input as the lowest-priority layer.
4. Backend provisioning — for each backend (postgres, redis, elasticsearch):
managed: true— the operator first checks that the backend CRD is present in the cluster (capability gating). If not, the instance goes permanentlyFailedwith a clear message. If present, the operator applies the backend CR (PerconaPGCluster,Redis/RedisReplication/RedisSentinel, or ECKElasticsearch) without an owner reference (so it survives instance deletion), then requeues every 15 seconds until the backend reports ready.managed: false— the operator reads the namedcredentialsSecretfrom the instance namespace. If absent, it setsphase: Pendingand requeues after 30 seconds.
5. Object storage — if spec.objectStorage.credentialsSecret is set, the operator reads the Secret, validates that all required bucket-class keys are present, and synthesises an operator-owned {name}-gitlab-registry-storage Secret containing the docker-registry S3 driver config. A missing bucket key sets phase: Failed with S3BucketsIncomplete.
6. Value composition — the operator merges four layers (last wins):
| Layer | Source |
|---|---|
| 1 (lowest) | GitlabProfile defaults |
| 2 | Version-map resolved values |
| 3 | Derived values from GitlabInstanceSpec (domains, edition, backends, S3) |
| 4 (highest) | spec.helm.values (untyped deep-merge) |
7. HelmRelease emission — the operator applies a HelmRepository pointing at https://charts.gitlab.io and a HelmRelease (helm.toolkit.fluxcd.io/v2beta1) with the composed values and the resolved chart version. The HelmRelease is owned by the GitlabInstance (deleted when the instance is deleted).
8. Status synthesis — the operator sets status.phase to Deploying immediately after emitting the HelmRelease. If the live HelmRelease already has Ready=True, the phase is set to Ready. The host, observedVersion, and chartVersion fields are updated on every reconcile.
Deletion¶
When a GitlabInstance is deleted, the operator's finalizer (k8s.bnerd.com/gitlab-finalizer) runs before the object is removed. The finalizer:
- Deletes all operator-owned Secrets listed in
status.secrets. - Removes the finalizer, allowing the object to be garbage-collected.
The Flux HelmRelease is owned by the GitlabInstance via a Kubernetes owner reference and is deleted automatically by the API server when the instance is removed.
Managed backends (Postgres, Redis, Elasticsearch) are NOT deleted. They carry no owner reference to the GitlabInstance and are retained on deletion by design. See Deletion & Retention.
What the operator does not do¶
- Create RGW users or S3 buckets — that is hq's responsibility.
- Install GitLab itself — Flux's helm-controller does that from the upstream chart.
- Manage GitLab configuration post-deploy — use GitLab's admin UI or the Rails console.
- Perform in-place GitLab data migrations — the chart's
gitlab:db:migrateruns automatically during chart upgrades.