Files
egress-proxies-operator/docs/gcp-in-specific-project.md
Jan Novak 19d6a8dfba 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>
2026-08-11 19:17:58 +02:00

7.3 KiB

Project egress-proxy

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

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

# 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