Multi-Tenant-Cluster mit Terraform erstellen

Ein Mehrmandanten-Cluster in Google Kubernetes Engine ist ein Kubernetes-Cluster, der von mehreren verschiedenen Teams oder Nutzern (Mandanten) gemeinsam genutzt wird. Jeder Mandant hat in der Regel eigene Ressourcen und Anwendungen im Cluster.

In diesem Terraform-Tutorial erfahren Sie, wie Sie schnell einen GKE-Cluster erstellen, der von zwei Teams, backend und frontend, gemeinsam genutzt werden kann und auf dem teamspezifische Arbeitslasten bereitgestellt werden können. In diesem Tutorial wird davon ausgegangen, dass Sie bereits mit Terraform vertraut sind. Wenn nicht, können Sie sich mit den folgenden Ressourcen mit den Grundlagen von Terraform vertraut machen:

Hinweis

Führen Sie folgende Schritte aus, um die Kubernetes Engine API zu aktivieren:

  1. Melden Sie sich in Ihrem Google Cloud -Konto an. Wenn Sie mit Google Cloudnoch nicht vertraut sind, erstellen Sie ein Konto, um die Leistungsfähigkeit unserer Produkte in der Praxis sehen und bewerten zu können. Neukunden erhalten außerdem ein Guthaben von 300 $, um Arbeitslasten auszuführen, zu testen und bereitzustellen.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  3. Verify that billing is enabled for your Google Cloud project.

  4. Enable the GKE, GKE GKE Fleet, Cloud SQL, Resource Manager, IAM, Connect gateway APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  5. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  6. Verify that billing is enabled for your Google Cloud project.

  7. Enable the GKE, GKE GKE Fleet, Cloud SQL, Resource Manager, IAM, Connect gateway APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  8. Prüfen Sie, ob Sie die folgenden Rollen für das Projekt haben: roles/owner, roles/iam.serviceAccountTokenCreator

    Rollen prüfen

    1. Rufen Sie in der Google Cloud Console die Seite IAM auf.

      IAM aufrufen
    2. Wählen Sie das Projekt aus.
    3. Suchen Sie in der Spalte Hauptkonto nach allen Zeilen, in denen Sie oder eine Gruppe, zu der Sie gehören, angegeben sind. Fragen Sie Ihren Administrator, zu welchen Gruppen Sie gehören.

    4. Prüfen Sie in allen Zeilen, in denen Sie angegeben oder enthalten sind, die Spalte Rolle, um zu sehen, ob die Liste der Rollen die erforderlichen Rollen enthält.

    Rollen zuweisen

    1. Rufen Sie in der Google Cloud Console die Seite IAM auf.

      IAM aufrufen
    2. Wählen Sie das Projekt aus.
    3. Klicken Sie auf Zugriffsrechte erteilen.
    4. Geben Sie im Feld Neue Hauptkonten Ihre Nutzer-ID ein. Das ist in der Regel die E‑Mail-Adresse eines Google-Kontos.

    5. Klicken Sie auf Rolle auswählen und suchen Sie nach der Rolle.
    6. Klicken Sie auf Weitere Rolle hinzufügen, wenn Sie weitere Rollen zuweisen möchten.
    7. Klicken Sie auf Speichern.

Umgebung vorbereiten

In dieser Anleitung verwenden Sie Cloud Shell zum Verwalten von Ressourcen, die inGoogle Cloudgehostet werden. Die Software, die Sie für diese Anleitung benötigen, ist in Cloud Shell vorinstalliert, einschließlich Terraform, kubectl und der Google Cloud CLI.

  1. Starten Sie eine Cloud Shell-Sitzung über die Google Cloud Console. Klicken Sie dazu auf das Symbol zum Aktivieren von Cloud Shell Cloud Shell aktivieren Button zum Aktivieren von Cloud Shell. Dadurch wird im unteren Bereich der Google Cloud Console eine Sitzung gestartet.

    Die Dienstanmeldedaten, die dieser virtuellen Maschine zugeordnet sind, werden automatisch verwendet. Daher müssen Sie keinen Dienstkontoschlüssel einrichten oder herunterladen.

  2. Bevor Sie Befehle ausführen, legen Sie Ihr Standardprojekt in der gcloud CLI mit dem folgenden Befehl fest:

    gcloud config set project PROJECT_ID
    

    Ersetzen Sie dabei PROJECT_ID durch Ihre Projekt-ID.

  3. Klonen Sie das GitHub-Repository:

    git clone https://github.com/terraform-google-modules/terraform-docs-samples.git --single-branch
    
  4. Wechseln Sie in das Arbeitsverzeichnis:

    cd terraform-docs-samples/gke/quickstart/multitenant
    

Terraform-Dateien prüfen

Der Google Cloud -Anbieter ist ein Plug-in, mit dem Sie Google Cloud -Ressourcen mit Terraform verwalten und bereitstellen können. Es dient als Bindeglied zwischen Terraform-Konfigurationen undGoogle Cloud -APIs, sodass Sie Infrastrukturressourcen wie virtuelle Maschinen und Netzwerke deklarativ definieren können.

  1. Prüfen Sie die Datei main.tf, in der eine GKE-Clusterressource beschrieben wird:

    cat main.tf
    

    Die Ausgabe sieht etwa so aus:

    resource "google_container_cluster" "default" {
      name               = "gke-enterprise-cluster"
      location           = "us-central1"
      initial_node_count = 3
      fleet {
        project = data.google_project.default.project_id
      }
      workload_identity_config {
        workload_pool = "${data.google_project.default.project_id}.svc.id.goog"
      }
      security_posture_config {
        mode               = "BASIC"
        vulnerability_mode = "VULNERABILITY_ENTERPRISE"
      }
      depends_on = [
        google_gke_hub_feature.policycontroller,
        google_gke_hub_namespace.default
      ]
      # Set `deletion_protection` to `true` will ensure that one cannot
      # accidentally delete this instance by use of Terraform.
      deletion_protection = false
    }
    
    resource "google_gke_hub_membership_binding" "default" {
      for_each = google_gke_hub_scope.default
    
      project               = data.google_project.default.project_id
      membership_binding_id = each.value.scope_id
      scope                 = each.value.name
      membership_id         = google_container_cluster.default.fleet[0].membership_id
      location              = google_container_cluster.default.fleet[0].membership_location
    }

Cluster und SQL-Datenbank erstellen

  1. Führen Sie in Cloud Shell diesen Befehl aus, um zu prüfen, ob Terraform verfügbar ist:

    terraform
    

    Die Ausgabe sollte in etwa so aussehen:

    Usage: terraform [global options] <subcommand> [args]
    
    The available commands for execution are listed below.
    The primary workflow commands are given first, followed by
    less common or more advanced commands.
    
    Main commands:
      init          Prepare your working directory for other commands
      validate      Check whether the configuration is valid
      plan          Show changes required by the current configuration
      apply         Create or update infrastructure
      destroy       Destroy previously-created infrastructure
    
  2. Initialisieren Sie Terraform:

    terraform init
    
  3. Optional: Terraform-Konfiguration planen:

    terraform plan
    
  4. Wenden Sie die Terraform-Konfiguration an:

    terraform apply
    

    Geben Sie bei Aufforderung zur Bestätigung von Aktionen yes ein. Die Ausführung dieses Befehls kann mehrere Minuten dauern. Die Ausgabe sieht etwa so aus:

    Apply complete! Resources: 23 added, 0 changed, 0 destroyed.
    

Backend-Teamanwendung bereitstellen

  1. Sehen Sie sich die folgende Terraform-Datei an:

    cat backend.yaml
    

    Die Ausgabe sollte in etwa so aussehen:

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: backend-configmap
      namespace: backend-team
      labels:
        app: backend
    data:
      go.mod: |
        module multitenant
    
        go 1.22
    
        require github.com/go-sql-driver/mysql v1.8.1
    
        require filippo.io/edwards25519 v1.1.0 // indirect
    
      go.sum: |
        filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
        filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
        github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
        github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
    
      backend.go: |
        package main
    
        import (
          "database/sql"
          "fmt"
          "log"
          "math/rand"
          "net/http"
          "os"
    
          _ "github.com/go-sql-driver/mysql"
        )
    
        func main() {
          mux := http.NewServeMux()
          mux.HandleFunc("/", frontend)
    
          port := "8080"
    
          log.Printf("Server listening on port %s", port)
          log.Fatal(http.ListenAndServe(":"+port, mux))
        }
    
        func frontend(w http.ResponseWriter, r *http.Request) {
          log.Printf("Serving request: %s", r.URL.Path)
    
          host, _ := os.Hostname()
          fmt.Fprintf(w, "Backend!\n")
          fmt.Fprintf(w, "Hostname: %s\n", host)
    
          // Open database using cloud-sql-proxy sidecar
          db, err := sql.Open("mysql", "multitenant-app@tcp/multitenant-app")
          if err != nil {
            fmt.Fprintf(w, "Error: %v\n", err)
            return
          }
    
          // Create metadata Table if not exists
          _, err = db.Exec("CREATE TABLE IF NOT EXISTS metadata (metadata_key varchar(255) NOT NULL, metadata_value varchar(255) NOT NULL, PRIMARY KEY (metadata_key))")
          if err != nil {
            fmt.Fprintf(w, "Error: %v\n", err)
            return
          }
    
          // Pick random primary color
          var color string
          randInt := rand.Intn(3) + 1
          switch {
          case randInt == 1:
            color = "red"
          case randInt == 2:
            color = "green"
          case randInt == 3:
            color = "blue"
          }
    
          // Set color in database
          _, err = db.Exec(fmt.Sprintf("REPLACE INTO metadata (metadata_key, metadata_value) VALUES ('color', '%s')", color))
          if err != nil {
            fmt.Fprintf(w, "Error: %v\n", err)
            return
          }
    
          fmt.Fprintf(w, "Set Color: %s\n", color)
        }
    
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: backendweb
      namespace: backend-team
      labels:
        app: backend
    spec:
      selector:
        matchLabels:
          app: backend
          tier: web
      template:
        metadata:
          labels:
            app: backend
            tier: web
        spec:
          containers:
          - name: backend-container
            image: golang:1.22
            command: ["go"]
            args: ["run", "."]
            workingDir: "/tmp/backend"
            volumeMounts:
              - name: backend-configmap
                mountPath: /tmp/backend/
                readOnly: true
          - name: cloud-sql-proxy
            image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.11.4
            args:
              - "--structured-logs"
              - "--port=3306"
              - "$(CONNECTION_NAME_KEY)"
            securityContext:
              runAsNonRoot: true
            env:
            - name: CONNECTION_NAME_KEY
              valueFrom:
                configMapKeyRef:
                  name: database-configmap
                  key: CONNECTION_NAME
          volumes:
            - name: backend-configmap
              configMap: { name: backend-configmap }
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: backendweb
      namespace: backend-team
      labels:
        app: backend
      annotations:
        networking.gke.io/load-balancer-type: "Internal" # Remove to create an external loadbalancer
    spec:
      selector:
        app: backend
        tier: web
      ports:
      - port: 80
        targetPort: 8080
      type: LoadBalancer

    In dieser Datei werden die folgenden Ressourcen beschrieben:

    • Eine Bereitstellung mit einer Beispielanwendung.
    • Ein Dienst vom Typ LoadBalancer. Der Dienst gibt das Deployment über Port 80 frei. Konfigurieren Sie einen externen Load Balancer, um Ihre Anwendung im Internet freizugeben. Entfernen Sie dazu die Annotation networking.gke.io/load-balancer-type.
  2. Führen Sie in Cloud Shell den folgenden Befehl aus, um das Dienstkonto des Backend-Teams zu imitieren:

    gcloud config set auth/impersonate_service_account backend@PROJECT_ID.iam.gserviceaccount.com
    

    Ersetzen Sie dabei PROJECT_ID durch Ihre Projekt-ID.

  3. Rufen Sie die Clusteranmeldedaten ab:

    gcloud container fleet memberships get-credentials gke-enterprise-cluster --location us-central1
    
  4. Wenden Sie das Manifest des Backend-Teams auf den Cluster an:

    kubectl apply -f backend.yaml
    

Funktion der Backend-Anwendung prüfen

Gehen Sie so vor, um zu prüfen, ob Ihr Cluster ordnungsgemäß ausgeführt wird:

  1. Rufen Sie in der Google Cloud -Console die Seite Arbeitslasten auf:

    Zu Arbeitslasten

  2. Klicken Sie auf die Arbeitslast backend. Die Pod-Detailseite wird angezeigt. Diese Seite enthält Informationen zum Pod, z. B. Annotationen, auf dem Pod ausgeführte Container, Dienste, die den Pod verfügbar machen, und Messwerte, darunter CPU-, Arbeitsspeicher- und Laufwerknutzung.

  3. Klicken Sie auf den LoadBalancer-Dienst backend. Die Detailseite des Dienstes wird angezeigt. Auf dieser Seite werden Informationen zum Dienst angezeigt, z. B. die mit dem Dienst verknüpften Pods und die von den Diensten verwendeten Ports.

  4. Klicken Sie im Abschnitt Endpunkte auf den IPv4-Link, um Ihren Dienst im Browser aufzurufen. Die Ausgabe sieht etwa so aus:

    Backend!
    Hostname: backendweb-765f6c4fc9-cl7jx
    Set Color: green
    

    Immer wenn ein Nutzer auf den Backend-Endpunkt zugreift, wählt der Dienst zufällig eine Farbe aus Rot, Grün oder Blau aus und speichert sie in der freigegebenen Datenbank.

Frontend-Teamanwendung bereitstellen

  1. Sehen Sie sich die folgende Terraform-Datei an:

    cat frontend.yaml
    

    Die Ausgabe sollte in etwa so aussehen:

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: frontend-configmap
      namespace: frontend-team
      labels:
        app: frontend
    data:
      go.mod: |
        module multitenant
    
        go 1.22
    
        require github.com/go-sql-driver/mysql v1.8.1
    
        require filippo.io/edwards25519 v1.1.0 // indirect
    
      go.sum: |
        filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
        filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
        github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
        github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
    
      frontend.go: |
        package main
    
        import (
          "database/sql"
          "fmt"
          "log"
          "net/http"
          "os"
    
          _ "github.com/go-sql-driver/mysql"
        )
    
        func main() {
          mux := http.NewServeMux()
          mux.HandleFunc("/", frontend)
    
          port := "8080"
    
          log.Printf("Server listening on port %s", port)
          log.Fatal(http.ListenAndServe(":"+port, mux))
        }
    
        func frontend(w http.ResponseWriter, r *http.Request) {
          log.Printf("Serving request: %s", r.URL.Path)
    
          host, _ := os.Hostname()
          fmt.Fprintf(w, "Frontend!\n")
          fmt.Fprintf(w, "Hostname: %s\n", host)
    
          // Open database using cloud-sql-proxy sidecar
          db, err := sql.Open("mysql", "multitenant-app@tcp/multitenant-app")
          if err != nil {
            fmt.Fprint(w, "Error: %v\n", err)
            return
          }
    
          // Retrieve color from the database
          var color string
          err = db.QueryRow("SELECT metadata_value FROM metadata WHERE metadata_key='color'").Scan(&color)
          switch {
          case err == sql.ErrNoRows:
            fmt.Fprintf(w, "Error: color not found in database\n")
          case err != nil:
            fmt.Fprintf(w, "Error: %v\n", err)
          default:
            fmt.Fprintf(w, "Got Color: %s\n", color)
          }
        }
    
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: frontendweb
      namespace: frontend-team
      labels:
        app: frontend
    spec:
      selector:
        matchLabels:
          app: frontend
          tier: web
      template:
        metadata:
          labels:
            app: frontend
            tier: web
        spec:
          containers:
          - name: frontend-container
            image: golang:1.22
            command: ["go"]
            args: ["run", "."]
            workingDir: "/tmp/frontend"
            volumeMounts:
              - name: frontend-configmap
                mountPath: /tmp/frontend/
                readOnly: true
          - name: cloud-sql-proxy
            image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.11.4
            args:
              - "--structured-logs"
              - "--port=3306"
              - "$(CONNECTION_NAME_KEY)"
            securityContext:
              runAsNonRoot: true
            env:
            - name: CONNECTION_NAME_KEY
              valueFrom:
                configMapKeyRef:
                  name: database-configmap
                  key: CONNECTION_NAME
          volumes:
            - name: frontend-configmap
              configMap: { name: frontend-configmap }
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: frontendweb
      namespace: frontend-team
      labels:
        app: frontend
      annotations:
        networking.gke.io/load-balancer-type: "Internal" # Remove to create an external loadbalancer
    spec:
      selector:
        app: frontend
        tier: web
      ports:
      - port: 80
        targetPort: 8080
      type: LoadBalancer

    In dieser Datei werden die folgenden Ressourcen beschrieben:

    • Eine Bereitstellung mit einer Beispielanwendung.
    • Ein Dienst vom Typ LoadBalancer. Der Dienst gibt das Deployment über Port 80 frei. Konfigurieren Sie einen externen Load Balancer, um Ihre Anwendung im Internet freizugeben. Entfernen Sie dazu die Annotation networking.gke.io/load-balancer-type.
  2. Führen Sie in Cloud Shell den folgenden Befehl aus, um das Dienstkonto des Frontend-Teams zu imitieren:

    gcloud config set auth/impersonate_service_account frontend@PROJECT_ID.iam.gserviceaccount.com
    

    Ersetzen Sie dabei PROJECT_ID durch Ihre Projekt-ID.

  3. Rufen Sie die Clusteranmeldedaten ab:

    gcloud container fleet memberships get-credentials gke-enterprise-cluster --location us-central1
    
  4. Wenden Sie das Manifest des Frontend-Teams auf den Cluster an:

    kubectl apply -f frontend.yaml
    

Prüfen, ob die Frontend-Anwendung funktioniert

Gehen Sie so vor, um zu prüfen, ob Ihr Cluster ordnungsgemäß ausgeführt wird:

  1. Rufen Sie in der Google Cloud -Console die Seite Arbeitslasten auf:

    Zu Arbeitslasten

  2. Klicken Sie auf die Arbeitslast frontend. Die Pod-Detailseite wird angezeigt. Auf dieser Seite werden Informationen zum Pod angezeigt, z. B. Annotationen, auf dem Pod ausgeführte Container, Dienste, die den Pod verfügbar machen, und Messwerte, darunter CPU-, Arbeitsspeicher- und Laufwerknutzung.

  3. Klicken Sie auf den LoadBalancer-Dienst frontend. Die Detailseite des Dienstes wird angezeigt. Auf dieser Seite werden Informationen zum Dienst angezeigt, z. B. die mit dem Dienst verknüpften Pods und die von den Diensten verwendeten Ports.

  4. Klicken Sie im Abschnitt Endpunkte auf den IPv4-Link, um Ihren Dienst im Browser aufzurufen. Die Ausgabe sieht etwa so aus:

    Frontend!
    Hostname: frontendweb-5cd888d88f-gwwtc
    Got Color: green
    

Bereinigen

Mit den folgenden Schritten vermeiden Sie, dass Ihrem Google Cloud -Konto die auf dieser Seite verwendeten Ressourcen in Rechnung gestellt werden:

  1. Führen Sie in Cloud Shell diesen Befehl aus, um die Identitätsübernahme des Dienstkontos aufzuheben:

    gcloud config unset auth/impersonate_service_account
    
  2. Führen Sie den folgenden Befehl aus, um die Terraform-Ressourcen zu löschen:

    terraform destroy --auto-approve
    

Nächste Schritte