Blog von Obi Madu
Zurück zu allen Artikeln
InfrastructureDevOpsProjects

Implementierung des Terraform-Kern-Workflows in Zusammenarbeit über Github Pull Requests, Actions, Bot, Environments & ein Remote-Backend.

Implementieren Sie einen kollaborativen Terraform-Workflow unter Verwendung von GitHub Actions, Pull Requests und einem Remote-Backend für Produktionsteams.

Implementierung des Terraform-Kern-Workflows in Zusammenarbeit über Github Pull Requests, Actions, Bot, Environments & ein Remote-Backend.

Hallo! und willkommen!! In diesem Projekt werden wir eine einfache Infrastruktur mit Terraform erstellen und dabei den Terraform-Kern-Workflow (Schreiben, Planen & Anwenden) kollaborativ für den Produktionseinsatz implementieren. Einfach ausgedrückt werden wir Infrastruktur mit Terraform so bereitstellen, dass ein Team von Ingenieuren Änderungen am System einführen, diese detailliert überprüfen, genehmigen oder ablehnen und (falls genehmigt) in der Produktion bereitstellen kann.

Das System, das wir in diesem Projekt aufbauen, eignet sich für den Betrieb kleiner bis mittelständischer Organisationen (mit oder ohne die mögliche Ergänzung einiger weiterer Dinge, über die wir am Ende des Projekts sprechen werden).

Für dieses Projekt sind Sie ein Infrastructure Engineer, der mit der Erstellung der initialen Infrastruktur für ein mittelständisches Unternehmen beauftragt ist. Wir beginnen damit, unsere Infrastruktur von Grund auf neu zu erstellen, arbeiten durchgehend mit Versionskontrolle (Git), bootstrappen unser System über ein lokales Backend auf unserer Workstation, migrieren es auf ein Remote-Backend, um Zusammenarbeit zu ermöglichen, und erstellen Skripte, um den gesamten Prozess der Continuous Integration und Continuous Deployment zu automatisieren.

Wir werden:

  • Eine grundlegende Infrastruktur mit Terraform auf DigitalOcean & Cloudflare erstellen
  • Ein Google Cloud Storage Remote-Backend konfigurieren, um Zusammenarbeit zu ermöglichen
  • Den Zugriff auf unser GCS-Backend über Github Environments & Actions-Workflows konfigurieren
  • Actions-Workflow-Skripte erstellen, um unsere Infrastrukturänderungen zu initialisieren, zu validieren, in der Vorschau anzuzeigen und anzuwenden.
  • Github Pull Requests und den Github Bot für die Überprüfung (sowie Genehmigung oder Ablehnung) der vorgeschlagenen Infrastrukturänderungen nutzen.

Hier ist das Projekt-Repository auf Github: https://github.com/obiMadu/terraform-github-workflow

Lassen Sie uns direkt eintauchen 🚀

1. Erstellen unserer Infrastruktur über Terraform

Infrastructure

Wir werden die sehr einfache Infrastruktur erstellen, die im obigen Diagramm dargestellt ist. Ein einfacher Webserver, auf dem Nginx auf Digital Ocean läuft, mit einer IPv4-Adresse und einem DNS A record in einer Cloudflare-Zone, der eine Subdomain darauf verweist. Wir werden dies erreichen, indem wir nur zwei Terraform-Ressourcen erstellen.

Sie könnten Cloudflare leicht gegen jeden anderen Domain Name Service Ihrer Wahl austauschen. Wenn Sie Ihre Domains beispielsweise über Amazon Route 53 verwalten, müssen Sie für dieses Tutorial nicht zu Cloudflare wechseln. Erstellen Sie einfach die entsprechenden Terraform-Konfigurationen, die erforderlich sind, um die IPv4-Adresse von unserer Serverinstanz auf einen Domainnamen abzubilden (es muss auch keine Subdomain sein, Sie können eine Apex-Domain abbilden, wie auch immer Sie es bevorzugen).

Wie (wahrscheinlich) erwartet, folgt daraus auch, dass Sie Ihren Server nicht auf Digital Ocean erstellen müssen. Sie könnten ihn auf AWS oder bei jedem anderen Anbieter Ihrer Wahl bereitstellen, vorausgesetzt, wir erhalten einen Server mit installiertem Nginx und einer IPv4-Adresse.

Um die Sache noch interessanter zu machen, könnten Sie sogar einen Basis-Server bereitstellen und ein Tool wie Ansible verwenden, um Nginx oder eine andere Web-App darauf bereitzustellen. Sie könnten Ihr eigenes benutzerdefiniertes Image mit Tools wie Packer erstellen und dieses ebenfalls verwenden. Ihre Entscheidung. Dieses Projekt zielt darauf ab, schnell und einfach nachvollziehbar zu sein.

1.1 Erstellen von providers.tf

Als Nächstes werden wir eine providers.tf-Datei erstellen, um unsere verschiedenen Provider zu konfigurieren. Die Versionsbeschränkungen der Provider sind auf die neuesten zum Zeitpunkt der Erstellung dieses Artikels festgelegt.

terraform {
  required_providers {
    digitalocean = {
      source = "digitalocean/digitalocean"
      version = "2.34.1"
    }
    cloudflare = {
      source = "cloudflare/cloudflare"
      version = "4.24.0"
    }
  }
}

# Digital Ocean API credentials
provider "digitalocean" {
  token = var.do_token
}

provider "cloudflare" {
  api_token = var.cloudflare_api_token
}

Das Obige sind die absoluten Mindestargumente, die von den verschiedenen Providern benötigt werden, um ordnungsgemäß zu funktionieren. Mit dieser erstellten Datei werden wir Git in unserem Projektverzeichnis initialisieren und diese Datei zu unserem allerersten Commit über die folgenden Befehle machen;

git init .
git add .
git commit -m "Initial commit"

Wir haben unsere Argumentwerte in Variablen umgewandelt, um die harte Codierung dieser sensiblen Werte in unseren Konfigurationsdateien zu verhindern. Denken Sie auch daran, dass Sie die Git-Commit-Nachrichten nach Belieben anpassen können.

1.2 Erstellen von variables.tf

Da wir unseren Provider-Konfigurationen Variablen als Werte für die Argumente zugewiesen haben, ist es an der Zeit, diese Variablen zu deklarieren. Wir tun dies in einer Datei, die wir variables.tf nennen, mit folgendem Inhalt.

variable "do_token" {
  type = string
}

variable "do_region" {
  type = string
}

variable "cloudflare_api_token" {
  type = string
}

variable cloudflare_zone_id" {
  type = string
}

Wir können nun fortfahren und diese neue Datei mit einer ordnungsgemäßen Commit-Nachricht an Git übergeben.

1.3 Initialisieren von Terraform

Nun, da wir unsere Basiskonfiguration haben, ist es an der Zeit, terraform init in unserem Projektverzeichnis auszuführen, um Terraform zu initialisieren.

Terraform Init

Ihre Erfolgsmeldung sollte ähnlich wie oben aussehen.

Nun müssen wir noch zwei weitere Dinge tun;

  • Dem Projekt eine für Terraform geeignete .gitignore-Datei hinzufügen
  • Die Werte für die verschiedenen Variablen bereitstellen, für deren Verwendung wir Terraform konfiguriert haben.

Wir können eine für Terraform geeignete .gitignore-Datei von der folgenden Adresse abrufen https://github.com/github/gitignore/blob/main/Terraform.gitignoretext

Nun ist es an der Zeit, die verschiedenen API-Schlüssel, die für unsere Provider erforderlich sind, von ihren jeweiligen Plattformen abzurufen. Wir benötigen;

  • Ein API-Token mit write-Zugriff von Digital Ocean, zusammen mit einem Regions-Shortcode (der angibt, in welcher Region wir unsere Ressourcen bereitstellen möchten)
  • Ein API-Token von Cloudflare, zusammen mit der Zonen-ID für die Domain, innerhalb derer wir unseren DNS-Eintrag erstellen möchten.

Wie genau Sie diese Anmeldeinformationen erhalten, wird hier nicht behandelt (Sie sollten in der Lage sein, sie zu erwerben).

Der Einfachheit halber können Sie die DigitalOcean-Region fra1 verwenden. Diese entspricht dem Rechenzentrum in Frankfurt.

Nun ist es an der Zeit, Terraform die von uns erworbenen Werte zur Verfügung zu stellen. Um unkompliziert zu sein, werden wir eine terraform.tfvars-Datei erstellen und unsere Variablenwerte einspeisen. Unsere Datei sollte in etwa wie folgt aussehen;

do_token = "value"
do_region = "fra1"

cloudflare_api_token = "value"
cloudflare_zone_id = "value"

DENKEN SIE DARAN: Sie dürfen die Datei terraform.tfvars niemals an Git übergeben. Diese Datei enthält wichtige Anwendungsgeheimnisse, die mit niemandem geteilt werden dürfen. Wenn ein Bedrohungsakteur Zugriff auf diese Geheimnisse erhält, ist unsere gesamte Infrastruktur in Gefahr, kompromittiert zu werden. Der einzige Zweck dieser Datei ist die lokale Verwendung. Wir werden eine sicherere Methode anwenden, um diese Werte für Terraform bereitzustellen, wenn wir in die CI/CD-Umgebung gelangen.

1.4 Erstellen der Ressourcen

Nun ist es an der Zeit, die Ressourcen für unsere Infrastruktur tatsächlich zu erstellen. Wir werden zwei Dateien erstellen: eine servers.tf-Datei und eine dns.tf-Datei.

  • In die erste Datei, servers.tf, fügen wir Ressourcenkonfigurationen ein, um unseren DigitalOcean-Server mit dem offiziellen DigitalOcean Nginx-Image zu erstellen. Und zwar so (wir erstellen eine Ressource digitalocean_droplet);
#create server with nginx image
resource "digitalocean_droplet" "server" {
  name     = "gate"
  size     = "s-1vcpu-1gb"
  image    = "nginx"
  region   = var.do_region
}
  • Die zweite Datei, dns.tf, konfigurieren wir wie folgt (wir erstellen eine Ressource cloudflare_record);
# Add a record to the domain
resource "cloudflare_record" "server" {
  zone_id = var.cloudflare_zone_id
  name    = "server"
  value   = digitalocean_droplet.server.ipv4_address
  type    = "A"
  ttl     = 1
  proxied = true
}

Speichern Sie nun beide Dateien und übergeben Sie sie mit ordnungsgemäßen Commit-Nachrichten an Git.

Zu diesem Zeitpunkt sollte Ihr Verzeichnisbaum genau so aussehen;

Directory tree 1

2. Erstellen und Initialisieren des Remote-Backends

Es ist an der Zeit, unserem Projekt ein Remote-Backend hinzuzufügen. Da ich ein Google Cloud Engineer bin, werde ich in diesem Projekt mit einem gcs Remote-Backend arbeiten. Sie können dies in jedes andere Remote-Backend Ihrer Wahl ändern, beispielsweise in ein s3-Backend.

Also habe ich einen Google Cloud Storage Bucket mit dem Namen terraform-github-workflow erstellt. Ich habe ein Dienstkonto unter demselben Namen erstellt und dem Prinzipal die Berechtigung Storage Object Admin für den GCS-Bucket zugewiesen. Dadurch kann das Dienstkonto Objekte im Bucket terraform-github-workflow erstellen und verwalten. Anschließend habe ich einen JSON service account key für den Dienstkonto-Prinzipal erstellt, den ich heruntergeladen und an einem guten Ort auf meiner lokalen Workstation gespeichert habe.

Der letzte Schritt bestand darin, die Umgebungsvariable GOOGLE_APPLICATION_CREDENTIALS auf den absoluten Pfad des JSON-Schlüssels zu setzen, der auf meinen PC heruntergeladen wurde, und zwar so;

export GOOGLE_APPLICATION_CREDENTIALS=/path/to/json/key/file

Um sicherzugehen, können Sie jederzeit einen anderen service account key erstellen, den Sie verwenden, wenn wir in die CI/CD-Umgebung gelangen.

Schließlich ist es an der Zeit, terraform init auszuführen, um dieses neue Backend zu initialisieren. Abhängig vom gewählten Backend sollten Sie eine Erfolgsmeldung sehen, die in etwa so aussieht;

Initialize Remote Backend

3. Erstellen von Github Actions-Workflows

Nun, da unser Terraform Remote-Backend initialisiert ist, ist es an der Zeit, die Github Actions-Skripte für unsere CI/CD-Pipeline zu erstellen. Wir werden insgesamt 3 verschiedene Workflows wie folgt erstellen;

  • Ein validate-Workflow, der bei jedem Push in den plan-Branch unseres Projekts ausgelöst wird. Dieser wird alle neuen Änderungen an unserer Codebasis mithilfe des Befehls terraform validate validieren.
  • Ein plan-Workflow, der bei jedem Pull Request in den main-Branch des Projekts ausgelöst wird. Dieser Workflow führt einen Trockenlauf der anstehenden Infrastrukturänderungen durch und verwendet den Github Bot, um diese Änderungen als Kommentar zu diesem Pull Request hinzuzufügen.
  • Ein deploy-Workflow, der bei jedem Push/Merge in den main-Branch ausgelöst wird. Dieser stellt unsere genehmigten Infrastrukturänderungen bereit.

3.1 Erstellen von workflows/validate.yml

Wir beginnen mit der Erstellung eines .github/workflows-Ordners im Stammverzeichnis unseres Projekts. Innerhalb des workflows-Verzeichnisses erstellen wir eine validate.yml-Datei. Unser Verzeichnisbaum sollte zu diesem Zeitpunkt so aussehen;

Tree 2

Hier ist der Inhalt der validate-Workflow-Datei;

name: Validate

on:
    push: 
        branches: [plan]

env:
    GOOGLE_APPLICATION_CREDENTIALS: ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}      

jobs:
  deploy:
    runs-on: ubuntu-latest
      
    steps:
      - uses: actions/checkout@v2
        with:
          submodules: true  # Fetch submodules (true OR recursive)
          fetch-depth: 0    # Fetch all history for .GitInfo and .Lastmod

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_wrapper: true

      - name: Setup Backend Credentials
        id: backend
        run: echo '${{ secrets.GCS_KEY }}' > ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}

      - name: Terraform Init
        id: init
        run: terraform init

      - name: Terraform Validate
        id: terraform
        run: terraform validate -no-color

Dieser Workflow:

  • Wird bei jedem Push in den plan-Branch ausgelöst
  • Läuft auf dem latest Ubuntu-Image
  • Checkt unseren Code als ersten Schritt aus
    • Richtet dann Terraform ein
    • Richtet die gcs-Backend-Anmeldeinformationen ein
    • Führt terraform init aus &
    • Führt terraform validate auf unserer Codebasis aus

Lassen Sie uns nun diese Datei an Git übergeben und fortfahren.

3.2 Erstellen von workflows/deploy.yml

Als Nächstes erstellen wir unseren deploy-Workflow mit folgendem Inhalt;

name: Deploy

on:
    push:
        branches: [main]

env:
    GOOGLE_APPLICATION_CREDENTIALS: ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}

jobs:
  deploy:
    runs-on: ubuntu-latest
      
    steps:
      - uses: actions/checkout@v2
        with:
          submodules: true  # Fetch submodules (true OR recursive)
          fetch-depth: 0    # Fetch all history for .GitInfo and .Lastmod

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3

      - name: Setup Backend Credentials
        id: backend
        run: echo '${{ secrets.GCS_KEY }}' > ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}

      - name: Terraform Init
        id: init
        run: terraform init
        
      - name: Terraform Apply
        id: apply
        env:
          TF_VAR_do_token: ${{ secrets.do_token }}
          TF_VAR_do_region: ${{ secrets.do_region }}
          TF_VAR_cloudflare_api_token: ${{ secrets.cloudflare_api_token }}
          TF_VAR_cloudflare_zone_id: ${{ secrets.cloudflare_zone_id }}
        run: terraform apply -auto-approve

Dieser Workflow:

  • Wird bei jedem Push/Merge in den main-Branch ausgelöst
  • Läuft auf dem latest Ubuntu-Image
  • Checkt unseren Code als ersten Schritt aus
    • Richtet dann Terraform ein
    • Richtet die gcs-Backend-Anmeldeinformationen ein
    • Führt terraform init aus &
    • Führt terraform apply mit -auto-approve aus, um unsere Infrastruktur bereitzustellen

Übergeben Sie diesen Workflow noch einmal mit einer ordnungsgemäßen Commit-Nachricht an Git, und wir erstellen unseren letzten Workflow.

3.3 Erstellen von workflows/plan.yml

Der letzte (aber definitiv nicht der unwichtigste) Workflow, den wir erstellen, ist der plan-Workflow. Wir erstellen diesen Workflow zuletzt, da wir darin unseren Github Bot integrieren werden, um die Ausgaben unserer Trockenläufe bereitzustellen, sodass unsere Pull-Request-Überprüfungen einfacher und wertvoller werden.

Wir werden also zuerst den Workflow mit den erforderlichen Funktionen erstellen und dann im nächsten Schritt den Bot integrieren. Unten ist der Code für unseren plan-Workflow;

name: Plan

on:
    pull_request: 
        branches: [main]

env:
  GOOGLE_APPLICATION_CREDENTIALS: ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions: 
      pull-requests: write
      
    steps:
      - uses: actions/checkout@v2
        with:
          submodules: true  # Fetch submodules (true OR recursive)
          fetch-depth: 0    # Fetch all history for .GitInfo and .Lastmod

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_wrapper: true

      - name: Setup Backend Credentials
        id: backend
        run: echo '${{ secrets.GCS_KEY }}' > ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}

      - name: Terraform Init
        id: init
        run: terraform init

      - name: Terraform fmt
        id: fmt
        run: terraform fmt -check
        continue-on-error: true

      - name: Terraform Validate
        id: validate
        run: terraform validate -no-color
        
      - name: Terraform Plan
        id: plan
        env:
            TF_VAR_do_token: ${{ secrets.do_token }}
            TF_VAR_do_region: ${{ secrets.do_region }}
            TF_VAR_cloudflare_api_token: ${{ secrets.cloudflare_api_token }}
            TF_VAR_cloudflare_zone_id: ${{ secrets.cloudflare_zone_id }}
        run: terraform plan -no-color

Dieser Workflow:

  • Wird bei jedem Pull Request in den main-Branch ausgelöst
  • Läuft auf dem latest Ubuntu-Image
  • Erwirbt die Berechtigung, in Pull Requests zu schreiben
  • Checkt unseren Code als ersten Schritt aus
    • Richtet dann Terraform ein
    • Richtet die gcs-Backend-Anmeldeinformationen ein
    • Führt terraform init aus
    • Führt terraform fmt aus
    • Führt terraform validate (erneut) aus
    • Führt terraform plan für einen Trockenlauf unserer Infrastruktur aus

Perfekt! Nun können wir fortfahren und den Bot im nächsten Abschnitt hinzufügen. Denken Sie daran, Ihren neuen plan-Workflow an Git zu übergeben.

4. Einrichten des Github Bots

Das Hinzufügen des Github Bots zu unserem Workflow ist ziemlich unkompliziert. Es ist nur ein zusätzlicher Schritt in unserem Plan-Workflow.

Fügen Sie Ihrer plan.yml-Workflow-Datei den folgenden Schritt hinzu:

      - name: Comment plan on PR
        uses: actions/github-script@v6
        if: github.event_name == 'pull_request'
        env:
          PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
                // 1. Retrieve existing bot comments for the PR
                const { data: comments } = await github.rest.issues.listComments({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.issue.number,
                })
                const botComment = comments.find(comment => {
                return comment.user.type === 'Bot' && comment.body.includes('Terraform Format and Style')
                })

                // 2. Prepare format of the comment
                const output = ` ## Terraform Results
                #### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
                #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
                #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
                <details><summary>Validation Output</summary>

                \`\`\`\n
                ${{ steps.validate.outputs.stdout }}
                \`\`\`

                </details>

                #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`

                <details><summary>Show Plan</summary>

                \`\`\`\n
                ${process.env.PLAN}
                \`\`\`

                </details>

                *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ env.tf_actions_working_dir }}\`, Workflow: \`${{ github.workflow }}\`*`;

                // 3. If we have a comment, update it, otherwise create a new one
                if (botComment) {
                github.rest.issues.updateComment({
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    comment_id: botComment.id,
                    body: output
                })
                } else {
                github.rest.issues.createComment({
                    issue_number: context.issue.number,
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    body: output
                })
                }

Ihre gesamte plan.yml-Datei sollte nun genau so aussehen:

name: Plan

on:
    pull_request: 
        branches: [main]

env:
  GOOGLE_APPLICATION_CREDENTIALS: ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions: 
      pull-requests: write
      
    steps:
      - uses: actions/checkout@v2
        with:
          submodules: true  # Fetch submodules (true OR recursive)
          fetch-depth: 0    # Fetch all history for .GitInfo and .Lastmod

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_wrapper: true

      - name: Setup Backend Credentials
        id: backend
        run: echo '${{ secrets.GCS_KEY }}' > ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}

      - name: Terraform Init
        id: init
        run: terraform init

      - name: Terraform fmt
        id: fmt
        run: terraform fmt -check
        continue-on-error: true

      - name: Terraform Validate
        id: validate
        run: terraform validate -no-color
        
      - name: Terraform Plan
        id: plan
        env:
            TF_VAR_do_token: ${{ secrets.do_token }}
            TF_VAR_do_region: ${{ secrets.do_region }}
            TF_VAR_cloudflare_api_token: ${{ secrets.cloudflare_api_token }}
            TF_VAR_cloudflare_zone_id: ${{ secrets.cloudflare_zone_id }}
        run: terraform plan -no-color

      - name: Comment plan on PR
        uses: actions/github-script@v6
        if: github.event_name == 'pull_request'
        env:
          PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
                // 1. Retrieve existing bot comments for the PR
                const { data: comments } = await github.rest.issues.listComments({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.issue.number,
                })
                const botComment = comments.find(comment => {
                return comment.user.type === 'Bot' && comment.body.includes('Terraform Format and Style')
                })

                // 2. Prepare format of the comment
                const output = ` ## Terraform Results
                #### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
                #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
                #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
                <details><summary>Validation Output</summary>

                \`\`\`\n
                ${{ steps.validate.outputs.stdout }}
                \`\`\`

                </details>

                #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`

                <details><summary>Show Plan</summary>

                \`\`\`\n
                ${process.env.PLAN}
                \`\`\`

                </details>

                *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ env.tf_actions_working_dir }}\`, Workflow: \`${{ github.workflow }}\`*`;

                // 3. If we have a comment, update it, otherwise create a new one
                if (botComment) {
                github.rest.issues.updateComment({
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    comment_id: botComment.id,
                    body: output
                })
                } else {
                github.rest.issues.createComment({
                    issue_number: context.issue.number,
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    body: output
                })
                }

5. Codebasis zu Github pushen

Nachdem Sie mit der Übergabe der oben genannten neuen Ergänzungen an Git fertig sind, ist es an der Zeit, dass wir unsere Infrastruktur-Codebasis auf Github publish.

Gehen Sie nun einfach zu Github und erstellen Sie ein öffentliches (oder privates) Repository, fügen Sie das Repository als origin-Remote für das Projekt auf unserer lokalen Maschine hinzu und pushen Sie es.

Sofort danach sollte Ihr deploy-Workflow beginnen auszuführen, aber keine Sorge, er wird fehlschlagen. Er wird fehlschlagen, da die Github Actions-Umgebung nicht mit den richtigen Anmeldeinformationen konfiguriert wurde, um sowohl unser Terraform-Backend zu initialisieren als auch die entsprechenden Werte für die in unserer Infrastruktur definierten Variablen bereitzustellen.

The very first Deploy workflow fails
Failed Deploy workflow details

Nun ist es an der Zeit, die Github Actions CI/CD-Umgebung ordnungsgemäß mit den entsprechenden Zugangsdaten zu konfigurieren, um unseren Workload bereitzustellen.

6. Konfigurieren von Github Environments für das Terraform-Backend und die Variablen

Nun müssen wir zu Github Environments gehen und die secrets und env vars konfigurieren, die für den Erfolg unseres Workflows erforderlich sind.

Wenn Sie in unseren Github Actions-Workflows aufgepasst haben, haben wir für jeden Vorgang, der sich auf den Terraform-Status auswirkt, die verschiedenen Umgebungsvariablen eingefügt, die von Terraform zur Ausführung benötigt werden. Ein Beispiel hierfür findet sich beim Befehl terraform apply im deploy-Workflow. Der Code-Ausschnitt lautet wie folgt;

      - name: Terraform Apply
        id: apply
        env:
          TF_VAR_do_token: ${{ secrets.do_token }}
          TF_VAR_do_region: ${{ secrets.do_region }}
          TF_VAR_cloudflare_api_token: ${{ secrets.cloudflare_api_token }}
          TF_VAR_cloudflare_zone_id: ${{ secrets.cloudflare_zone_id }}
        run: terraform apply -auto-approve

Unten ist also eine Liste aller verschiedenen secrets und env vars, die wir festlegen müssen (Sie werden sehen, dass sie alle an den entsprechenden Stellen in die verschiedenen Workflows integriert wurden);

  • Terraform-Variablen
    • TF_VAR_do_token
    • TF_VAR_do_region
    • TF_VAR_cloudflare_api_token
    • TF_VAR_cloudflare_zone_id
  • Remote-Backend-Secrets & Variablen
    • GCS_KEY - Der Google Cloud Storage Schlüssel.
    • GOOGLE_APPLICATION_CREDENTIALS - Pfad zum Google Cloud Storage Schlüssel im Dateisystem

Denken Sie daran, einen anderen GCS Key für die Github Actions-Umgebung zu erstellen und den zu deaktivieren, den wir zur lokalen Initialisierung von Terraform verwendet haben. Von nun an werden alle neuen Änderungen an der Infrastruktur über unseren Workflow verarbeitet. Sie sollten Ihren Schlüssel nicht einmal für die anfängliche Bereitstellung von Ressourcen verwenden.

Gehen Sie nun zu Ihrer Seite Github Dashboard -> Settings -> Secrets and Variables -> Actions. Das ist die unten dargestellte Seite.

Create secrets and variables.

Als Nächstes werden wir die Werte für die verschiedenen Secrets und Umgebungsvariablen ausfüllen.

Da wir in diesem Projekt nur mit einer einzigen Umgebung arbeiten, werden wir diese als Repository-Secrets und -Variablen ausfüllen. In einem fortgeschritteneren Setup, bei dem wir Entwicklungs-, Staging-, QA- und Produktionsumgebungen hätten, würden wir die Kernlösung von Github Environments nutzen. Dieses Projekt geht nicht so weit in die Tiefe, daher bleiben wir bei den Repo-Secrets und -Variablen.

  1. Abgesehen von der Variablen GOOGLE_APPLICATION_CREDENTIALS sollte alles andere ein Geheimnis sein. Verwenden Sie für diese Variable den Wert ./key.json, um es einfach zu halten.

  2. Um zwischen secrets und variables zu wechseln, nutzen Sie die Registerkarten unter den gleichen Namen auf der Seite.

Wenn alles eingestellt ist, sollten wir Seiten haben, die wie folgt aussehen:

All our repo secrets set
All our repo variables set

Nun sind alle unsere Secrets und Variablen konfiguriert. 🎉 🎉

7. Testen des gesamten Workflows

Schließlich ist es an der Zeit, dieses riesige Rad in Bewegung zu setzen.

7.1 Erstellen des plan-Branches

Der erste Schritt wird sein, den plan-Branch des Projekts zu erstellen. Wir haben ihn in den vorherigen Schritten oft erwähnt, es ist an der Zeit, ihn tatsächlich zu erstellen.

Wir werden das mit dem folgenden Befehl erreichen:

git checkout -b plan

Dadurch wird der neue plan-Branch erstellt und wir in unserem aktuellen Arbeitsverzeichnis dorthin gewechselt (ausgecheckt). Als Nächstes pushen wir diesen Branch zu Github und setzen ihn so, dass er einen neuen Origin-Branch unter dem gleichen plan-Namen verfolgt.

git push -u origin plan

Dieser Push sollte einen validate-Workflow auf Github auslösen. Wenn dieser erfolgreich ist, sind wir auf dem richtigen Weg. Wenn nicht, müssen Sie sich umsehen, um herauszufinden, was Sie möglicherweise übersehen haben, um es zu beheben. Sehen Sie sich die Fehlerprotokolle des Actions-Workflows an, um Fehler zu verstehen.

Wenn Sie dieser Anleitung jedoch religiös gefolgt sind, sollten Sie Erfolgsbildschirme ähnlich den unten stehenden haben. Ihr allererster validate-Workflow sollte ein Erfolg sein.

Successfull Validate workflow run
All steps executed successfully

7.2 Vorbereiten auf und Erstellen des allerersten PR zu main

Um unseren Workflow in Gang zu bringen, müssen wir unseren ersten Pull Request an den main-Branch erstellen. Zu diesem Zeitpunkt wird das unmöglich sein, da unsere main- und plan-Branches synchronisiert sind. Um es zum Laufen zu bringen, müssen wir einen Dummy-Commit in den plan-Branch machen.

Um es einfach zu halten, fügen wir einfach einen Kommentar zu einer unserer Infrastrukturdateien hinzu, übergeben die Änderung und erstellen einen PR zu main.

Sie können einer beliebigen Datei Ihrer Wahl einen Kommentar hinzufügen. Ich werde das bei der Datei backend.tf tun. Ich werde die folgenden Zeilen am Anfang der Datei hinzufügen

# Configure the remote gcs backend

Speichern Sie nun Ihre geänderte Datei, übergeben (commit) Sie sie und pushen Sie sie zu Github. Das sollte einen neuen validate-Workflow-Durchlauf auf Github starten;

3rd workflow run

Und es sollte erfolgreich sein.

Success

Nun können wir fortfahren und unseren ersten Pull Request zu main erstellen. Wenn alles gut funktioniert, sollten Sie Ausgaben ähnlich den unten abgebildeten erhalten.

Plan workflow starts to run
Plan workflow succeeds

In Ihrem Pull Request sollte jetzt alles grün sein 🎉 🎉

7.3 Untersuchen der Plan-Ausgaben über den Bot-Kommentar

Nachdem Ihre Workflows erfolgreich ausgeführt wurden, sollten Sie den neuen Bot-Kommentar sehen können, der sehr wichtige Details zu Ihren vorgeschlagenen Infrastrukturänderungen enthält.

Github Bot comments Terraform Plan Output

Gehen Sie voran und erweitern Sie die verschiedenen Abschnitte des Kommentars, um mehr Details zu erhalten. Ein Klick auf die Schaltfläche Show Plan zeigt Ihnen beispielsweise die Ausgabe des Befehls terraform plan.

Wie Sie sehen können, da wir nur zwei Ressourcen in unserer Konfiguration definiert haben, sehen wir einen Plan zur Erstellung von zwei Ressourcen.

Plan: 2 to Add

7.4 Überprüfen, Iterieren und Genehmigen (oder Ablehnen) des Plans

An diesem Punkt kann mithilfe der nativen Github Branch Rules verlangt werden, dass eine Mindestanzahl von Prüfern die neuen Infrastrukturänderungen überprüft und genehmigt. Das Team kann ändern, was immer es braucht; neue Commits in den plan-Branch sammeln sich unter diesem Pull Request, lösen jedes Mal eine erneute Ausführung des plan workflows aus und stellen so sicher, dass das Team die aktuellsten Änderungen sieht, die beim nächsten Apply vorgenommen werden.

Da wir diesen Workflow bis zum Ende durchziehen und sicherstellen möchten, dass unsere Infrastruktur tatsächlich erstellt wird, gehen wir davon aus, dass das Team unsere neue Infrastruktur absolut großartig findet und die Verantwortlichen sie abgezeichnet haben. Wir werden daher fortfahren und diesen Pull Request #1 mergen und unsere Infrastruktur bereitstellen lassen.

7.5 Und... es ist Weihnachten

Confirm Merge Pull Request

Ich mache ein rebasing, einfach weil ich eine aufgeräumte Commit-Historie mag.

Pull Request #1 Merged

Dies sollte unseren deploy-Workflow im main-Branch auslösen, und wie erwartet sollte es einwandfrei funktionieren, etwa so;

Workflow Deployed Successfully
All Deploy Steps Successful

WIR HABEN ES GESCHAFFT!!! 🎉 🎉

Um zu bestätigen, dass unsere Infrastruktur in der realen Welt live ist, können wir ihre Webadresse besuchen. Meine war server.obi.ninja und sie funktionierte einwandfrei.

All Deploy Steps Successful

Ich habe https://, weil ich den kostenlosen SSL-Dienst von Cloudflare eingerichtet habe. Dies habe ich außerhalb von Terraform erledigt, als ich die Cloudflare-Zone selbst einrichtete, um dieses Projekt und diese Anleitung einfach zu halten.

8. Zusätzliche Hinweise

8.1 Github Branch Protection Rules

Um dieses Projekt kugelsicher zu machen, damit es für Ihre Organisation zuverlässig funktioniert, müssen Sie Github Branch Protection Rules verwenden. Mit diesen Regeln können Sie wichtige Dinge erzwingen, wie z.B.;

  • Sicherstellen, dass die Branches main und plan nicht gelöscht werden können
  • Einen Merge nach main nur zulassen, wenn sowohl der validate- als auch der plan-Workflow erfolgreich waren
  • Angabe der Mindestanzahl von Personen, die befugt sind, jede neue Änderung abzuzeichnen
  • usw.

8.2 Zusätzliche Tools

Möglicherweise möchten Sie die verschiedenen Teile dieses Projekts um zusätzliche Tools erweitern, um die Sicherheit zu erhöhen. Tools wie:

  • Hashicorp Vault für die Verwaltung von Geheimnissen
  • Sonarqube für zusätzliche statische und Code-Qualitätsanalyse
  • usw.

8.3 Zerstören der Infrastruktur

Dies ist ein Team-Workflow, und er ist das Rückgrat der Unternehmensinfrastruktur. Infolgedessen muss jede Zerstörung von Ressourcen durch Löschen oder Auskommentieren des Terraform-Codes für besagte Ressourcen erfolgen, und diese Änderung muss vom verantwortlichen Team überprüft werden. Niemand wird einfach in der Lage sein, terraform destroy auszuführen und Ressourcen zu zerstören, solange die Infrastrukturgeheimnisse ordnungsgemäß verwaltet werden.

8.4 Fazit

Ich hatte viel Spaß bei der Konzeption, Umsetzung und Dokumentation dieses Projekts. Ich hoffe, Sie finden es nützlich. Zögern Sie nicht, mir im Kommentarbereich unten mitzuteilen, was Sie denken, ob es Fehler gibt oder Raum für Verbesserungen besteht.