Add GCP deployment docs, PR review notes, and Claude tooling updates
docs/gcp-in-specific-project.md: SA + firewall setup for the egress-proxy project, in-kube secret, and apply-ready ConfigMap/Deployment/Proxy manifests (Ubuntu image — debian-cloud lacks cloud-init). docs/gcp-vm-validation.md: end-to-end GCP VM validation walkthrough. docs/reviews/: proxy-operator PR review notes from 2026-08-10. .claude/: operator-reviewer agent, accumulated permission allowlist. .gitignore: never commit sa_key.json (live SA key stays untracked). Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
158
docs/gcp-vm-validation.md
Normal file
158
docs/gcp-vm-validation.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# Validating real VM creation on GCP
|
||||
|
||||
Recipe for wiring the GCP provider into a live cluster and watching a
|
||||
`Proxy` CR create a real Compute Engine VM. Angle brackets mark values you
|
||||
supply: `<PROJECT_ID>`, `<SA_KEY_PATH>`, `<ZONE>`, `<CLUSTER_EGRESS_IP>`,
|
||||
`<REGISTRY_IMAGE>`.
|
||||
|
||||
The one important fact up front: **the operator takes no GCP credentials
|
||||
through its own config.** The client is built with Application Default
|
||||
Credentials (`internal/provider/gcp/gcp.go`, `New()`); there is no
|
||||
key-file field in the providers config. The only secret to prepare is a
|
||||
service-account JSON key, injected via the standard
|
||||
`GOOGLE_APPLICATION_CREDENTIALS` mechanism. On GKE you would use workload
|
||||
identity instead and skip the key entirely.
|
||||
|
||||
## 1. GCP-side prerequisites (prepared outside the cluster)
|
||||
|
||||
1. A project — `<PROJECT_ID>` — with the **Compute Engine API enabled**.
|
||||
2. A **service account** with `roles/compute.instanceAdmin.v1` on the
|
||||
project. The operator only calls instances
|
||||
`Insert`/`Get`/`Delete`/`AggregatedList` and does not attach a service
|
||||
account to the VMs it creates, so no `iam.serviceAccountUser` is
|
||||
needed.
|
||||
3. A **JSON key** for that service account, saved at `<SA_KEY_PATH>`.
|
||||
4. A **firewall rule**: created VMs get network tag `proxy-operator` (the
|
||||
default; configurable as `gcp.networkTag`), an ephemeral external IP,
|
||||
and Squid listening on 3128.
|
||||
|
||||
```sh
|
||||
gcloud compute firewall-rules create allow-proxy-operator \
|
||||
--project <PROJECT_ID> \
|
||||
--network default \
|
||||
--allow tcp:3128 \
|
||||
--target-tags proxy-operator \
|
||||
--source-ranges <CLUSTER_EGRESS_IP>/32
|
||||
```
|
||||
|
||||
The source range must cover the cluster's egress IP — the operator's
|
||||
CONNECT health probes originate there, and without the rule the Proxy
|
||||
hangs at `Running`/unhealthy instead of reaching `Ready`. ⚠️ The
|
||||
sample cloud-init configures `http_access allow all`, so on a public
|
||||
IP this is an open proxy — keep the source ranges tight.
|
||||
|
||||
## 2. Create the credentials Secret
|
||||
|
||||
Namespace is `egress-proxies-operator-system` after kustomize prefixing:
|
||||
|
||||
```sh
|
||||
kubectl -n egress-proxies-operator-system create secret generic gcp-credentials \
|
||||
--from-file=key.json=<SA_KEY_PATH>
|
||||
```
|
||||
|
||||
## 3. Add a GCP entry to the providers ConfigMap
|
||||
|
||||
Edit `config/manager/providers_config.yaml` (mounted at
|
||||
`/etc/proxy-operator/providers.yaml`):
|
||||
|
||||
```yaml
|
||||
providers:
|
||||
- name: kubernetes
|
||||
type: kubernetes
|
||||
- name: gcp-eu # spec.provider on a Proxy refers to this NAME, not the type
|
||||
type: gcp
|
||||
gcp:
|
||||
project: <PROJECT_ID>
|
||||
# network: default # these three default as shown
|
||||
# networkTag: proxy-operator
|
||||
# diskSizeGb: 10
|
||||
```
|
||||
|
||||
The config is validated fail-fast at startup — a typo shows up
|
||||
immediately in the manager log, not on first use.
|
||||
|
||||
## 4. Mount the Secret and point ADC at it
|
||||
|
||||
In `config/manager/manager.yaml`, add to the manager container:
|
||||
|
||||
```yaml
|
||||
env:
|
||||
- name: GOOGLE_APPLICATION_CREDENTIALS
|
||||
value: /var/secrets/gcp/key.json
|
||||
volumeMounts:
|
||||
- name: gcp-credentials
|
||||
mountPath: /var/secrets/gcp
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: gcp-credentials
|
||||
secret:
|
||||
secretName: gcp-credentials
|
||||
```
|
||||
|
||||
(`volumeMounts` merges into the existing container list; `volumes` into
|
||||
the existing pod-level list.)
|
||||
|
||||
## 5. Deploy and create the Proxy
|
||||
|
||||
```sh
|
||||
make deploy IMG=<REGISTRY_IMAGE>
|
||||
```
|
||||
|
||||
`config/samples/proxy_gcp.yaml` is usable as-is once `spec.provider`
|
||||
matches the name from step 3. All three placement fields are mandatory
|
||||
for GCP — a missing one sets the Proxy to `Failed` with a message naming
|
||||
it:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
mode: Managed
|
||||
provider: gcp-eu
|
||||
placement:
|
||||
zone: <ZONE> # e.g. europe-west1-b
|
||||
machineType: e2-micro
|
||||
image: projects/debian-cloud/global/images/family/debian-12
|
||||
```
|
||||
|
||||
```sh
|
||||
kubectl apply -f config/samples/proxy_gcp.yaml
|
||||
```
|
||||
|
||||
## 6. What you should see
|
||||
|
||||
```sh
|
||||
kubectl get proxy -w
|
||||
```
|
||||
|
||||
`Provisioning` → `Running` (VM's external IP published in status) →
|
||||
`Ready` (CONNECT health probe succeeded through the public IP). Then:
|
||||
|
||||
```sh
|
||||
# the VM exists and carries the GC labels
|
||||
gcloud compute instances list --project <PROJECT_ID> \
|
||||
--filter 'labels.proxy-operator-managed=yes'
|
||||
|
||||
# the proxy actually tunnels — should print the VM's external IP
|
||||
curl -x http://<EXTERNAL_IP>:3128 https://ifconfig.me
|
||||
```
|
||||
|
||||
Cleanup — the finalizer deletes the VM:
|
||||
|
||||
```sh
|
||||
kubectl delete proxy proxy-gcp-sample
|
||||
gcloud compute instances list --project <PROJECT_ID> # should be empty again
|
||||
```
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **The orphan GC sweeps the whole project**: any VM labeled
|
||||
`proxy-operator-managed=yes` whose UID does not match a live Proxy CR
|
||||
in *this* cluster is deleted once past the age threshold. Do not point
|
||||
two operator installs at the same project, and do not hand-create VMs
|
||||
with that label.
|
||||
- **VM creation is fire-and-forget** — the provider never waits on the
|
||||
insert operation; progress is discovered by polling `Get`. A quota
|
||||
error or bad image name surfaces on the Proxy's status/conditions a
|
||||
reconcile later, not synchronously. `kubectl describe proxy` is the
|
||||
place to look when something stalls.
|
||||
- **e2-micro costs pennies but is not free everywhere** — remember to
|
||||
delete the CR (or check `gcloud compute instances list`) when done.
|
||||
Reference in New Issue
Block a user