Managed resources
Inventory of GCP resources reconciled by Config Connector, grouped by managed GCP project
Google Cloud Config Connector (KCC) is the operator we use to manage GCP resources as Kubernetes Custom Resources. Every GCP resource that has a corresponding KCC CRD is created, updated, and (occasionally) deleted by KCC controllers reconciling YAML in this repository against the live GCP API.
Plain GitOps applies: the desired state lives in main, ArgoCD syncs it into the cluster, KCC controllers actuate it onto GCP. There are no out-of-band gcloud or terraform apply steps for the projects KCC owns.
| Property | Value |
|---|---|
| Operator manifest | k8s/infra-services/gcp-config-connector/autopilot-configconnector-operator.yaml |
| Operator version | 1.148.0 (GKE Autopilot variant) |
| Operator namespace | configconnector-operator-system |
| Controller mode | namespaced (one Google Service Account per namespace) |
| Authentication | GKE Workload Identity |
stateIntoSpec | Absent (unspecified spec fields stay unspecified after reconciliation) |
| Hub cluster | Infra Management Cluster |
| Spoke clusters | TB Platform Dev / QA / Prod (run only the operator; resources for those projects are reconciled from the hub) |
| GitOps tool | ArgoCD (Managed Services → Config Connector) |
The cluster-level ConfigConnector CR runs the operator in namespaced mode:
# k8s/infra-services/gcp-config-connector/configconnector.yaml
apiVersion: core.cnrm.cloud.google.com/v1beta1
kind: ConfigConnector
metadata:
name: configconnector.core.cnrm.cloud.google.com
spec:
mode: namespaced
stateIntoSpec: Absent
In namespaced mode, each Kubernetes namespace that wants to manage GCP resources must declare its own ConfigConnectorContext resource pointing at the Google Service Account it should impersonate. This gives us:
KCC runs as a hub-and-spoke topology. The Infra Management Cluster is the GitOps hub - it hosts the namespaces that reconcile GCP resources for the central platform infra projects and for every tb-platform environment. The tb-platform spoke clusters run the operator too, but only for the small slice of resources that must live inside their own cluster.
graph TD
subgraph hub["Infra Management Cluster (hub)"]
op["KCC Operator<br/>v1.148.0<br/>namespaced mode"]
ns_mgmt["ns: tb-infra-mgmt-project<br/>→ tb-infra-mgmt"]
ns_vpc["ns: tb-infra-mgmt-vpc-project<br/>→ tb-infra-mgmt-vpc"]
ns_sec["ns: tb-infra-security-project<br/>→ tb-infra-security"]
ns_plat["ns: tb-platform-infra<br/>→ shared platform infra"]
ns_env["ns: tb-platform-{dev,qa,prod}<br/>→ tb-platform GCP projects"]
ns_vpcenv["ns: tb-platform-vpc-{dev,qa,prod}<br/>→ tb-platform VPC projects"]
op --> ns_mgmt & ns_vpc & ns_sec & ns_plat & ns_env & ns_vpcenv
end
subgraph spokes["TB Platform Clusters (dev / qa / prod)"]
op_spoke["KCC Operator<br/>(ApplicationSet:<br/>tb-platform-config-connector-operator)"]
end
ns_mgmt -.reconciles.-> gcp_mgmt[("GCP: tb-infra-mgmt")]
ns_vpc -.reconciles.-> gcp_vpc[("GCP: tb-infra-mgmt-vpc")]
ns_sec -.reconciles.-> gcp_sec[("GCP: tb-infra-security")]
ns_plat -.reconciles.-> gcp_plat[("GCP: platform org/folders")]
ns_env -.reconciles.-> gcp_env[("GCP: tb-platform-{env}")]
ns_vpcenv -.reconciles.-> gcp_vpcenv[("GCP: tb-platform-vpc-{env}")]The single workload-identity service account that backs every hub namespace is:
gke-platform-infra@tb-infra-mgmt-gke-prod-uk-40fd.iam.gserviceaccount.com
This GSA holds the IAM grants on each target GCP project / folder that allow it to act as a fleet-wide infrastructure administrator. Per-namespace blast-radius is enforced by:
AppProject (which namespaces and clusters each Application may target).ConfigConnectorContext (which GSA, and therefore which GCP scope, KCC may use in that namespace).KCC content lives in two top-level trees:
k8s/
├── infra-services/
│ ├── gcp-config-connector/ # Operator install (CRDs + ConfigConnector CR)
│ ├── tb-infra-mgmt-project/ # KCC resources for the tb-infra-mgmt GCP project
│ ├── tb-infra-mgmt-vpc-project/ # KCC resources for the tb-infra-mgmt-vpc GCP project
│ └── tb-infra-security-project/ # KCC resources for the tb-infra-security GCP project
└── tb-platform-infra/ # KCC resources for the tb-platform GCP projects (per-env)
├── core/ # Shared ConfigConnectorContext + cluster mgmt SA
├── env/ # Per-env workload projects (dev/qa/prod) + base
├── vpc/ # Per-env VPC host projects (dev/qa/prod) + base
└── iam/ # Shared IAM resources
Inside each tree, child directories are named after the Config Connector API group they correspond to (compute/, iam/, dns/, kms/, sql/, storage/, resourcemanager/, pubsub/, firestore/, bigquery/, serviceusage/, privateca/, networkconnectivity/, monitoring/, run/, redis/, artifactregistry/). This convention is enforced by k8s/tb-platform-infra/README.md.
secretmanager//secretsmanager/directories are an exception: they are named after the GCP Secret Manager API but contain External Secrets Operator resources (SecretStore,ExternalSecret), not KCC resources. See Managed resources for details.
A core/config-connector-context.yaml at the root of each tree (or each env overlay) wires the namespace up to KCC.
The operator is installed by ArgoCD from k8s/infra-services/gcp-config-connector/:
# k8s/infra-services/gcp-config-connector/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
#- gcp-config-connector-operator.yaml # legacy non-Autopilot manifest (v1.127.0)
- autopilot-configconnector-operator.yaml # active, v1.148.0
- configconnector.yaml
What the active manifest installs:
Namespace/configconnector-operator-system (annotated cnrm.cloud.google.com/operator-version: 1.148.0).core.cnrm.cloud.google.com and customize.core.cnrm.cloud.google.com API groups (ConfigConnector, ConfigConnectorContext, ControllerReconciler, ControllerResource, MutatingWebhookConfigurationCustomization, NamespacedControllerReconciler, NamespacedControllerResource).ConfigConnector resource that selects namespaced mode.The same Kustomization is reused for every cluster that runs KCC:
| Cluster | Delivered by | Source |
|---|---|---|
| Infra Management | Application/config-connector-operator | apps/config-connector.yaml |
| TB Platform Dev / QA / Prod | ApplicationSet/tb-platform-config-connector-operator (list generator) | application-sets/tb-platform-config-connector.yaml |
The non-Autopilot manifest (
gcp-config-connector-operator.yaml, pinned atv1.127.0) is kept in-tree for reference only - it is commented out ofkustomization.yamland is not applied to any cluster.
The cluster-level ConfigConnector CR intentionally has no googleServiceAccount and no credentialSecretName. In namespaced mode each namespace supplies its own GSA via a ConfigConnectorContext:
# k8s/tb-platform-infra/env/dev/core/config-connector-context.yaml
apiVersion: core.cnrm.cloud.google.com/v1beta1
kind: ConfigConnectorContext
metadata:
name: configconnectorcontext.core.cnrm.cloud.google.com
namespace: tb-platform-dev
spec:
googleServiceAccount: "gke-platform-infra@tb-infra-mgmt-gke-prod-uk-40fd.iam.gserviceaccount.com"
stateIntoSpec: Absent
The GKE node identity binds to this GSA via Workload Identity - the cnrm-system per-namespace controller manager is granted roles/iam.workloadIdentityUser on gke-platform-infra and runs as it when calling Google Cloud APIs. No JSON keys are stored anywhere in the cluster, so credential rotation is handled entirely by GCP IAM.
Every KCC namespace that the hub cluster reconciles is wired to the same workload-identity GSA, but each namespace targets a different GCP project via the resources it contains:
| Namespace | Reconciles GCP project | KCC source path |
|---|---|---|
tb-infra-mgmt-project | tb-infra-mgmt | k8s/infra-services/tb-infra-mgmt-project/ |
tb-infra-mgmt-vpc-project | tb-infra-mgmt-vpc (and its host-project IAM) | k8s/infra-services/tb-infra-mgmt-vpc-project/ |
tb-infra-security-project | tb-infra-security + parent folder | k8s/infra-services/tb-infra-security-project/ |
tb-platform-infra | Shared platform infra (cross-env) | k8s/tb-platform-infra/ (root kustomization) |
tb-platform-{dev,qa,prod} | tb-platform-{env} workload project | k8s/tb-platform-infra/env/{env}/ |
tb-platform-vpc-{dev,qa,prod} | tb-platform-vpc-{env} host project | k8s/tb-platform-infra/vpc/{env}/ |
ArgoCD delivers four classes of KCC content to the hub cluster:
graph LR
git[(infra-services<br/>main)]
git --> opApp[Application:<br/>config-connector-operator]
git --> mgmtApp[Application:<br/>infra-mgmt-project]
git --> vpcApp[Application:<br/>infra-mgmt-vpc-project]
git --> secApp[Application:<br/>infra-security-project]
git --> platApp[Application:<br/>tb-platform-infra]
git --> envSet[ApplicationSet:<br/>tb-platform-environments]
git --> vpcSet[ApplicationSet:<br/>tb-platform-vpc-config]
git --> opSet[ApplicationSet:<br/>tb-platform-config-connector-operator]
opApp -->|infra-services| operator((Operator CRDs<br/>+ ConfigConnector CR))
mgmtApp -->|infra-mgmt| nsMgmt((ns: tb-infra-mgmt-project))
vpcApp -->|infra-mgmt| nsVpc((ns: tb-infra-mgmt-vpc-project))
secApp -->|infra-mgmt| nsSec((ns: tb-infra-security-project))
platApp -->|infra-services| nsPlat((ns: tb-platform-infra))
envSet -->|tb-platform-infra| nsEnv((ns: tb-platform-{env}))
vpcSet -->|tb-platform-infra| nsVpcEnv((ns: tb-platform-vpc-{env}))
opSet --> opSpoke((Operator on<br/>tb-platform clusters))AppProject | Owns | Allowed source repos | Notable |
|---|---|---|---|
infra-services | Operator install + shared tb-platform-infra | This repo + Helm registries used by other infra services | prune: false for the operator app to avoid accidentally tearing out CRDs |
infra-mgmt | tb-infra-mgmt-project, tb-infra-mgmt-vpc-project, tb-infra-security-project | Only https://github.com/Titanbay/infra-services | Destinations restricted to the three tb-infra-* namespaces on the hub |
tb-platform-infra | All tb-platform-{env} + tb-platform-vpc-{env} apps and the per-env operator install | This repo + Helm registries used across tb-platform clusters | Destinations cover all six tb-platform-* namespaces on the hub and every namespace the platform stack uses on each spoke cluster |
See Managed Services → Config Connector for the full per-Application sync policy and Slack notification wiring.
Every KCC Application uses ServerSideApply=true. KCC resources are large, the controller updates them out-of-band as it reconciles state, and a client-side three-way merge will fight with the controller. With SSA, Kubernetes tracks field ownership and Argo only owns the fields it actually writes.
prune is set per-Application based on blast radius:
| Application | automated.prune | Why |
|---|---|---|
config-connector-operator | false | Operator removal would tear down CRDs and orphan every KCC resource cluster-wide. |
infra-mgmt-project, infra-security-project | unset (manual sync) | High-risk core infra; deletions reviewed by hand. |
infra-mgmt-vpc-project, tb-platform-infra, tb-platform-environments, tb-platform-vpc-config, tb-platform-config-connector-operator | true | Bounded blast radius; Git is the single source of truth. |
The footprint covers the full set of GCP services we use - IAM, networking, compute, GKE, KMS, DNS, SQL, storage, pub/sub, secrets, BigQuery, Firestore, Artifact Registry, Private CA, Network Connectivity, Cloud Run, Redis, Monitoring, and Service Usage. The full inventory by GCP project, including kind counts and the relevant API groups, lives in Managed resources. Day-to-day workflows (adding resources, debugging stuck reconciles, recovering from drift) are in Operations.
Application / ApplicationSet reference.Inventory of GCP resources reconciled by Config Connector, grouped by managed GCP project
Day-to-day workflows for working with Config Connector resources: adding, updating, troubleshooting