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>
255 lines
7.3 KiB
Markdown
255 lines
7.3 KiB
Markdown
## Project egress-proxy
|
|
|
|
```bash
|
|
PROJECT_ID=egress-proxy
|
|
|
|
# 1. Create the service account
|
|
gcloud iam service-accounts create proxy-operator \
|
|
--project ${PROJECT_ID} \
|
|
--display-name "egress-proxies-operator"
|
|
|
|
# Output:
|
|
# Created service account [proxy-operator].
|
|
# Service account email: proxy-operator@egress-proxy.iam.gserviceaccount.com
|
|
|
|
# 2. Grant compute.instanceAdmin.v1 on the project
|
|
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
|
|
--member "serviceAccount:proxy-operator@${PROJECT_ID}.iam.gserviceaccount.com" \
|
|
--role roles/compute.instanceAdmin.v1
|
|
|
|
# Output:
|
|
# ---------
|
|
# Updated IAM policy for project [egress-proxy].
|
|
# bindings:
|
|
# - members:
|
|
# - serviceAccount:proxy-operator@egress-proxy.iam.gserviceaccount.com
|
|
# role: roles/compute.instanceAdmin.v1
|
|
# - members:
|
|
# - serviceAccount:541231138892@cloudservices.gserviceaccount.com
|
|
# role: roles/compute.instanceGroupManagerServiceAgent
|
|
# - members:
|
|
# - serviceAccount:service-541231138892@compute-system.iam.gserviceaccount.com
|
|
# role: roles/compute.serviceAgent
|
|
# - members:
|
|
# - user:admin@fujultimate.cz
|
|
# role: roles/owner
|
|
# etag: BwZYuFXko24=
|
|
# version: 1
|
|
|
|
# 3. Create the JSON key (this is what goes into the Secret)
|
|
SA_KEY_PATH=sa_key.json
|
|
gcloud iam service-accounts keys create $SA_KEY_PATH \
|
|
--iam-account proxy-operator@${PROJECT_ID}.iam.gserviceaccount.com
|
|
|
|
# output:
|
|
# created key [fdff85174a8e80bbd684e76c4d9fe28e2f4b2ddf] of type [json] as [sa_key.json] for [proxy-operator@egress-proxy.iam.gserviceaccount.com]
|
|
|
|
# 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.
|
|
|
|
gcloud compute firewall-rules create allow-proxy-operator \
|
|
--project $PROJECT_ID \
|
|
--network default \
|
|
--allow tcp:3128 \
|
|
--target-tags proxy-operator \
|
|
--source-ranges 94.230.145.216/32
|
|
```
|
|
|
|
## Phase 2 - resources in kube
|
|
|
|
```bash
|
|
SA_KEY_PATH=sa_key.json
|
|
kubectl -n egress-proxies-operator-system create secret generic gcp-credentials \
|
|
--from-file=key.json=$SA_KEY_PATH
|
|
```
|
|
|
|
|
|
## Appendix - full manifests
|
|
|
|
```bash
|
|
# crawl CR
|
|
kubectl apply -f - <<'EOF'
|
|
apiVersion: crawl.example.com/v1alpha1
|
|
kind: Proxy
|
|
metadata:
|
|
name: proxy-gcp-sample
|
|
spec:
|
|
mode: Managed
|
|
provider: gcp-eu # must match a provider NAME in providers.yaml
|
|
placement:
|
|
zone: europe-west1-b
|
|
machineType: e2-micro
|
|
# debian-cloud images have no cloud-init, so spec.cloudInit (passed as
|
|
# user-data metadata) would be silently ignored there. Ubuntu images do.
|
|
image: projects/ubuntu-os-cloud/global/images/family/ubuntu-2404-lts-amd64
|
|
port: 3128
|
|
cloudInit:
|
|
inline: |
|
|
#cloud-config
|
|
package_update: true
|
|
packages:
|
|
- squid
|
|
write_files:
|
|
- path: /etc/squid/conf.d/proxy-operator.conf
|
|
content: |
|
|
http_access allow all
|
|
via off
|
|
forwarded_for off
|
|
runcmd:
|
|
- systemctl restart squid
|
|
attributes:
|
|
geo: eu
|
|
purpose: crawl
|
|
EOF
|
|
|
|
|
|
|
|
# configmap
|
|
kubectl apply -f - <<'EOF'
|
|
apiVersion: v1
|
|
data:
|
|
providers.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: egress-proxy
|
|
# network: default # these three default as shown
|
|
# networkTag: proxy-operator
|
|
# diskSizeGb: 10
|
|
kind: ConfigMap
|
|
metadata:
|
|
labels:
|
|
app.kubernetes.io/managed-by: kustomize
|
|
app.kubernetes.io/name: egress-proxies-operator
|
|
name: egress-proxies-operator-providers-config
|
|
namespace: egress-proxies-operator-system
|
|
EOF
|
|
|
|
# operator deployment
|
|
kubectl apply -f - <<'EOF'
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
annotations:
|
|
deployment.kubernetes.io/revision: "2"
|
|
labels:
|
|
app.kubernetes.io/managed-by: kustomize
|
|
app.kubernetes.io/name: egress-proxies-operator
|
|
control-plane: controller-manager
|
|
name: egress-proxies-operator-controller-manager
|
|
namespace: egress-proxies-operator-system
|
|
spec:
|
|
progressDeadlineSeconds: 600
|
|
replicas: 1
|
|
revisionHistoryLimit: 10
|
|
selector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: egress-proxies-operator
|
|
control-plane: controller-manager
|
|
strategy:
|
|
rollingUpdate:
|
|
maxSurge: 25%
|
|
maxUnavailable: 25%
|
|
type: RollingUpdate
|
|
template:
|
|
metadata:
|
|
annotations:
|
|
kubectl.kubernetes.io/default-container: manager
|
|
labels:
|
|
app.kubernetes.io/name: egress-proxies-operator
|
|
control-plane: controller-manager
|
|
spec:
|
|
containers:
|
|
- args:
|
|
- --metrics-bind-address=:8443
|
|
- --leader-elect
|
|
- --health-probe-bind-address=:8081
|
|
- --providers-config=/etc/proxy-operator/providers.yaml
|
|
command:
|
|
- /manager
|
|
env:
|
|
- name: DISCOVERY_TOKEN
|
|
valueFrom:
|
|
secretKeyRef:
|
|
key: token
|
|
name: discovery-token
|
|
optional: true
|
|
- name: GOOGLE_APPLICATION_CREDENTIALS
|
|
value: /var/secrets/gcp/key.json
|
|
image: egress-proxies-operator:dev
|
|
imagePullPolicy: IfNotPresent
|
|
livenessProbe:
|
|
failureThreshold: 3
|
|
httpGet:
|
|
path: /healthz
|
|
port: 8081
|
|
scheme: HTTP
|
|
initialDelaySeconds: 15
|
|
periodSeconds: 20
|
|
successThreshold: 1
|
|
timeoutSeconds: 1
|
|
name: manager
|
|
ports:
|
|
- containerPort: 8081
|
|
name: health
|
|
protocol: TCP
|
|
- containerPort: 8090
|
|
name: discovery
|
|
protocol: TCP
|
|
readinessProbe:
|
|
failureThreshold: 3
|
|
httpGet:
|
|
path: /readyz
|
|
port: 8081
|
|
scheme: HTTP
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 10
|
|
successThreshold: 1
|
|
timeoutSeconds: 1
|
|
resources:
|
|
limits:
|
|
cpu: 500m
|
|
memory: 128Mi
|
|
requests:
|
|
cpu: 10m
|
|
memory: 64Mi
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop:
|
|
- ALL
|
|
readOnlyRootFilesystem: true
|
|
terminationMessagePath: /dev/termination-log
|
|
terminationMessagePolicy: File
|
|
volumeMounts:
|
|
- mountPath: /etc/proxy-operator
|
|
name: providers-config
|
|
readOnly: true
|
|
- mountPath: /var/secrets/gcp
|
|
name: gcp-credentials
|
|
readOnly: true
|
|
dnsPolicy: ClusterFirst
|
|
restartPolicy: Always
|
|
schedulerName: default-scheduler
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
serviceAccount: egress-proxies-operator-controller-manager
|
|
serviceAccountName: egress-proxies-operator-controller-manager
|
|
terminationGracePeriodSeconds: 10
|
|
volumes:
|
|
- configMap:
|
|
defaultMode: 420
|
|
name: egress-proxies-operator-providers-config
|
|
name: providers-config
|
|
- name: gcp-credentials
|
|
secret:
|
|
defaultMode: 420
|
|
secretName: gcp-credentials
|
|
EOF
|
|
``` |