diff --git a/docs/content/getting-started/build-the-platform.md b/docs/content/getting-started/build-the-platform.md index 25e3b08c..f239bd15 100644 --- a/docs/content/getting-started/build-the-platform.md +++ b/docs/content/getting-started/build-the-platform.md @@ -19,8 +19,21 @@ against this capacity without knowing which cluster it runs on. - AWS access key ID and secret access key {{< /tab >}} {{< tab "GKE" >}} -- A GCP account with permissions to create GKE clusters, VPCs, and IAM roles -- A GCP service account JSON key +- A GCP service account JSON key, granted these roles on the project: + + | Role | Needed for | + |---|---| + | `roles/container.admin` | the cluster and its node pools | + | `roles/compute.admin` | the VPC network and subnet | + | `roles/serviceusage.serviceUsageAdmin` | enabling the APIs the cluster needs | + | `roles/iam.serviceAccountAdmin` | the node service account | + | `roles/iam.serviceAccountKeyAdmin` | the node service account's key | + | `roles/iam.serviceAccountUser` | attaching that account to the nodes | + | `roles/resourcemanager.projectIamAdmin` | granting the node account `container.admin` | + + The last one is worth a look before you hand the key over. Modelplane grants + the node service account `roles/container.admin`, so the credential doing the + provisioning has to be able to set project IAM policy. {{< /tab >}} {{< tab "AKS" >}} - An Azure account with permissions to create AKS clusters and managed identities diff --git a/e2e/README.md b/e2e/README.md index d915e428..c7aff122 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -51,6 +51,26 @@ server exposes both, so the pod goes Ready without a real model or GPU. | Control-plane `InferenceGateway` + cross-cluster routing to the replica | | | Status propagation and foreground-deletion ordering | | +### Why cloud provisioning cannot be tested here + +`lean-control-plane.yaml` trims the control plane to what a BYO cluster needs, +and both trims stop a cloud `InferenceCluster` from reconciling at all. Neither +announces itself, so this is what to expect if you point this control plane at a +real cloud: + +- The MRAP activates only `*.kubernetes.m.crossplane.io` and + `*.helm.m.crossplane.io`. A cloud provider's managed resources are then never + activated, so they sit with **no status conditions at all** — which reads as + nothing happening rather than as an error. +- The `dormant-cloud-providers` `ImageConfig` maps every + `xpkg.upbound.io/upbound/provider-*` to a zero-replica runtime config. Editing + that `ImageConfig` is not enough on its own: it is resolved when a package + revision reconciles, so existing Deployments keep their replica count until + something scales them. + +Undoing both is possible but leaves a control plane that is no longer the one CI +runs, so prefer a separate control plane for cloud work. + ## Prerequisites - **Docker** with real headroom — **≥ 16 GB memory** and **plenty of disk** diff --git a/functions/compose-gke-cluster/function/fn.py b/functions/compose-gke-cluster/function/fn.py index 9357df4b..b7dd834e 100644 --- a/functions/compose-gke-cluster/function/fn.py +++ b/functions/compose-gke-cluster/function/fn.py @@ -175,9 +175,11 @@ def _cred_name(self) -> str: def resolve_project(self) -> str | None: """Fetch the GCP provider config and return its projectID. - The GCP provider uses projectID from the ProviderConfig/ClusterProviderConfig - as the default for all managed resources, so we only need the value - explicitly for the workloadPool string in the GKE cluster spec. + The GCP provider late-initializes projectID from the + ProviderConfig/ClusterProviderConfig into most managed resources, so the + value is needed explicitly only where that does not happen: the + workloadPool string in the GKE cluster spec, and the ProjectIAMMember, + which creates with an empty project unless it is set. When the ProviderConfig is transiently gone but the cluster already exists, falls back to the project embedded in the observed cluster's @@ -506,6 +508,12 @@ def compose_iam_binding(self) -> None: forProvider=iamv1beta1.ForProvider( role="roles/container.admin", member=f"serviceAccount:{sa_email}", + # Set explicitly. The provider late-initializes `project` + # from the ProviderConfig for the Network, Subnetwork and + # ServiceAccount, but not for this resource: it goes to + # create with an empty project and the GCP call 404s on + # "Error retrieving IAM policy for project ''". + project=self.project, ), ), ), diff --git a/functions/compose-gke-cluster/tests/test_fn.py b/functions/compose-gke-cluster/tests/test_fn.py index 09813e66..a351bd16 100644 --- a/functions/compose-gke-cluster/tests/test_fn.py +++ b/functions/compose-gke-cluster/tests/test_fn.py @@ -274,6 +274,7 @@ def _iam_binding( "forProvider": { "role": "roles/container.admin", "member": f"serviceAccount:{sa_email}", + "project": "my-gcp-project", }, }, }