Mitverfasst von dem großartigen Daniel Lamando

Einführung

Die Verwendung von Modulen, um Ihren Terraform-Code DRY („Don’t Repeat Yourself“) zu halten, ist sehr verbreitet, aber die beste Methode zur Veröffentlichung und Verteilung dieser Module zu finden, ist nicht immer einfach. Auch wir bei Forto sind schnell auf diese Herausforderung gestoßen, als immer mehr Teams in unserer Organisation begannen, Terraform zu nutzen, um ihre Infrastruktur mit Code zu beschreiben. Aufmerksame Leser unseres vorherigen Terraform-Blogposts könnten sogar ein kleines Easter Egg bemerkt haben:

This image shows a code snippet within a Terraform configuration file, specifically focusing on an AWS SQS (Simple Queue Service) module. The file being edited is named sqs.tf, and the code snippet uses a module named sqs_example_queue. Key parameters are defined within the module, including source, queue_name, and dead_letter_queue_create.

A notable part of the image is a highlighted warning: "../../../../modules/aws-sqs-queue@v2" # don't version your modules like this. This comment suggests that specifying module versions directly within the file path (such as @v2 in the directory structure) is considered a bad practice in Terraform. Proper module versioning should ideally be managed through Terraform’s versioning mechanisms rather than hardcoding the version within file paths.

Below the code snippet, additional context shows that this code change is part of a pull request titled "DLQ FTW!!![production]" with a reviewer assigned and relevant labels for promotion and automation.

This image highlights the importance of following best practices in Terraform module management to ensure code maintainability and avoid issues associated with improper versioning methods.

Dieses Snippet zeigt, wie wir Terraform-Module damals referenzierten, und offenbarte eine bedeutende technische Schuld: die Referenzierung von unversionierten Terraform-Modulen per Pfad. Diese Praxis machte jede Änderung an einem Modul zu einem riskanten Unterfangen. In einigen Fällen erstellten wir zusätzliche Kopien unserer Module, um das Risiko zu minimieren (was zu „@v2“ führte, wie im Screenshot zu sehen).

Als wir erkannten, dass wir unsere Terraform-Module versionieren mussten, plante ich einen Hack Day, um eine Lösung zu finden. Zum Kontext: Forto veranstaltet monatlich eintägige Hackathons, bei denen Ingenieure ermutigt werden, an individuellen Projekten zu arbeiten, die nichts mit ihrer täglichen Arbeit zu tun haben. Diese Projekte können von Machine Learning bis hin zur Musik-Synthese reichen. Dennoch nutzen wir diese Hackathons manchmal, um Prototypen oder Lösungen zu entwickeln, die zwar mit unserer Arbeit zusammenhängen, aber nicht sofort priorisiert werden.

Das Versionierungstool unserer Wahl für diesen ersten Hack Day war Versio. Während unsere Versio-Implementierung funktionierte, um Versionsnummern zu erhöhen und Releases unserer Module zu erstellen, stießen wir auf einige kleinere Probleme. Als dann immer mehr Ingenieure in der Firma Moduländerungen vornahmen, wurden die Nachteile der zusätzlichen Komplexität deutlich. Nach ein paar Monaten entschieden wir uns, einen zweiten Hack Day zu organisieren, um zu prüfen, ob eine einfachere Lösung von Grund auf effektiver sein könnte.

Unser neuer Workflow erlaubt es weiterhin, Terraform-Module innerhalb eines Monorepos individuell zu versionieren. Der Release-Prozess ist jetzt jedoch eng mit dem Pull-Request-Lebenszyklus verknüpft. Nach der Zusammenführung eines Pull Requests wird eine neue Versionsnummer berechnet, und die erstellten Artefakte werden in einem S3-Bucket abgelegt. Abschließend wird das Wiki des Repositories mit automatisch generierter Moduldokumentation und einem neuen Changelog-Eintrag aktualisiert. Das Ergebnis ist ein vereinfachter Versionierungs-Workflow, der verschiedene GitHub-Funktionen wie PR-Labels, GitHub Actions und die GraphQL-API nutzt.

In den folgenden Abschnitten erklären wir, warum wir unsere Module versionieren, teilen die Entscheidungen, die wir getroffen haben, und zeigen einen beispielhaften Ablauf eines Modul-Upgrades.

Wir haben außerdem ein öffentliches Referenz-Repository erstellt: github.com/freight-hub/terraform-modules-demo, damit jeder diesen Workflow einfach in seinen eigenen Repositories umsetzen kann.

(„Freighthub“ war Fortos vorheriger Markenname. Ehrlich gesagt, steht die Umbenennung unserer GitHub-Organisation ganz oben auf dem Jira-Board!)

Der Ablauf

Änderung an einem Modul vornehmen

Das gehört schließlich zu Ihrem Job.

This image shows a code snippet from a Terraform file (variables.tf) used to define variables in an AWS VPC (Virtual Private Cloud) module. The file specifies Terraform variables that configure aspects of the VPC setup.

Key Highlights:
New Variable Addition:

The green-highlighted code represents a newly added variable named example_variable.
Attributes of example_variable:
Description: "An example variable to showcase the module release process".
Type: Set to bool (boolean).
Default: Set to true.
This variable is added to demonstrate how new variables can be incorporated in a module to support release or update processes.
Existing Variable:

Below the new addition, there is an existing variable named create_vpc.
Description: "Controls if VPC should be created (it affects almost all resources)".
Type: Boolean, indicating it toggles the creation of the VPC itself, impacting several dependent resources.
This image serves as an example of how variables are structured in Terraform to control module behavior. By defining variables with clear descriptions, types, and default values, the module becomes more customizable and user-friendly for infrastructure management in AWS. This approach helps standardize variable handling in infrastructure-as-code practices.

Das neue Modul vor der Veröffentlichung testen

Wenn Sie Ihren Code lokal testen möchten, können Sie eine quellpfadbasierte Modulreferenz im Dateisystem verwenden, wie zum Beispiel:

source = “../../../modules-repos/name_of_module”

Falls Sie die unveröffentlichte Änderung von der CI/CD-Pipeline testen lassen möchten (siehe vorherigen Artikel), können Sie eine git-basierte Modulreferenz verwenden, wie zum Beispiel:

source = “[email protected]:freight-hub/terraform-modules-demo.git?ref=BRANCH-NAME”

Öffnen Sie einen Pull Request (PR) für die Änderung.

Stellen Sie sicher, dass Sie den für das Changelog vorgesehenen Text in die PR-Beschreibung einfügen. Ein aussagekräftiger Changelog-Text sorgt dafür, dass die Versionshistorie genau und nützlich bleibt.

This image depicts the GitHub interface for opening a pull request, used to propose changes to a code repository. In this example, a new branch named example_pr_minor_release has been created to introduce a minor change to a Terraform module.

Key Details:
Branch Information:

The branch name, example_pr_minor_release, indicates this pull request is intended for a minor update to the module, likely adding or modifying a small feature or variable.
Pull Request Title:

The user has titled the pull request as "Add variable to showcase a minor release", signaling the specific purpose of the change.
Placeholder Text for Change Log:

In the pull request description box, there’s placeholder text reading: ""
This reminder emphasizes the importance of providing a clear, meaningful description that will appear in the Terraform module's changelog. The final description should summarize the changes to inform users of the update’s impact.
Merge Status:

A green label indicating "Able to merge" signifies that the branch has no conflicts with the main branch, allowing for an automatic merge if approved.
This image highlights a best practice in collaborative development: using clear descriptions and meaningful change logs in pull requests. This process helps team members and module users understand updates, improving module maintainability and transparency in version control.
This image captures a GitHub pull request screen for a minor update to a Terraform module, specifically adding a new variable to showcase a minor release. The pull request is titled "Add variable to showcase a minor release #6" and aims to merge a change from the example_pr_minor_release branch into the main branch. The creator of the pull request, user “Oded-B,” added the "example_variable" to the variables.tf file, which is evident from the commit message and change summary in the pull request.

In this interface, we see an interaction with labels that can categorize the pull request based on its scope or type of release. The arrow highlights the "Apply labels to this pull request" option, which includes labels such as "major," "minor," "no-release," and "patch." This feature enables the pull request creator or reviewers to tag the update appropriately, indicating that this particular change qualifies as a "minor" update due to its limited impact on the module.

However, not all automated checks have passed successfully. There’s a red warning message reading "Some checks were not successful," which indicates one failed check and three skipped checks. Specifically, the failed check is related to "Monorepo Versioning / Detect pull request context," which implies an issue with context detection in the versioning workflow. Despite these check issues, a green message at the bottom confirms that "This branch has no conflicts with the base branch," meaning that, structurally, the changes can be merged without conflict.

This pull request screen reflects typical collaborative workflows for maintaining Terraform modules. Automated checks verify coding standards, while labels help categorize the change’s impact, contributing to a streamlined and organized process in a shared repository. Proper use of labels, check results, and clear descriptions enhance clarity and transparency in version-controlled environments.

Die Pipeline wird einen PR-Kommentar mit den aktuellen und neuen Versionsnummern posten.

This image illustrates a release plan update generated by GitHub Actions in a pull request for a Terraform module, specifically showing version control adjustments as part of a minor update. In this pull request, the user "Oded-B" has applied the "minor" label to indicate that the proposed changes are incremental and non-breaking, aligned with semantic versioning practices for software releases.

The release plan table displays essential information:

Directory: Specifies the affected module or directory, in this case, terraform-aws-vpc.
Previous Version: The last published version of this module, listed as 1.0.0.
New Version: The planned version after this update, specified as 1.1.0.
This version bump from 1.0.0 to 1.1.0 signals a minor release, indicating enhancements or new features that are backward-compatible without breaking changes. This helps users of the module understand that they can update to this version without needing significant adjustments.

This structured release plan and label ensure that module users are well-informed about the nature and impact of the change, providing a clear preview of the updated version and helping maintain an organized changelog. This process of automated version management in a collaborative repository streamlines deployment and maintains consistency across releases, offering clarity for developers and users alike.

Beim Merge stellen wir das Wiki/Changelog bereit und laden die Artefakte in den Bucket hoch.

This image shows a terminal output from an automated GitHub Action that pushes recent changes to a GitHub repository’s wiki. The update here documents a new release version for a Terraform module, specifically updating the terraform-aws-vpc module to version 1.1.0.

Key details in the output include:

Git Configuration: The command Run git config --local user.email "github-actions[bot]@users.noreply.github.com" configures the GitHub bot's email for this commit, indicating that this change was automated through GitHub Actions rather than manually pushed by a user.
Version Update: The line [master cecacc5] terraform-aws-vpc @ 1.1.0 specifies the module and the new version number, 1.1.0, reflecting the minor update made to the module.
File Change Summary: The output shows that one file was changed, with 11 insertions and 4 deletions, suggesting modifications to the documentation or versioning file in the wiki.
Push to Wiki Repository: The update is pushed to the URL https://github.com/freight-hub/terraform-modules-demo.wiki, which points to the wiki section of the terraform-modules-demo repository, indicating that documentation or release notes are being updated to reflect the latest changes.
This automated update ensures that documentation in the GitHub Wiki is synchronized with the latest release, allowing users to access up-to-date information on module versions and changes. This process maintains consistency in version tracking and enhances transparency in the module’s development lifecycle by making sure that each release is well-documented and accessible directly from the wiki.
This image displays a terminal log from an automated process that prepares and uploads files related to a Terraform module release. The module in question is terraform-aws-vpc, and this log captures the steps involved in packaging and uploading the module files to an Amazon S3 storage location for distribution.

Key Details in the Process:
Module Directory:

The process begins by locating the specific module directory, terraform-aws-vpc, within the workspace. It navigates through paths related to the project structure in a continuous integration/continuous deployment (CI/CD) environment.
File List:

Within the terraform-aws-vpc directory, the log lists several files related to the module:
changelog.md: Likely contains a record of changes made in the new release.
documentation.md: Provides detailed documentation for users of the module.
new-version.txt and previous-version.txt: Text files that likely indicate the current and prior versions, which help track versioning within the CI/CD pipeline.
terraform-module.zip: A compressed file containing the Terraform module files, prepared for upload.
File Upload:

The terraform-module.zip file, approximately 76 KB in size, is successfully uploaded to an Amazon S3 bucket at the path s3://forto-terraform-modules-demo/terraform-aws-vpc-1.1.0.zip. The version number (1.1.0) in the file path indicates that this is a minor release update for the module.
Completion:

The upload progress shows completion at a transfer rate of 72.7 KiB/s, with the file fully uploaded, indicating a successful deployment of the module.
This automated process highlights how CI/CD pipelines can streamline the release and distribution of Terraform modules. By organizing files like changelogs and version tracking documents, compressing the module, and uploading it to a storage location, this system ensures that updated modules are readily available for users. This process helps maintain version consistency and facilitates smooth access to the latest module releases in shared storage, supporting efficient infrastructure management in cloud environments.
This image shows a portion of a documentation table for a Terraform module, listing available variables and their descriptions. The table helps users understand the purpose and expected values of each variable within the module, which, in this case, appears to configure aspects of an AWS environment.

Key Variables Displayed:
enable_vpn_gateway:

This variable determines whether a new VPN Gateway resource should be created.
The description suggests that the value should be set to true if the user intends to include a VPN Gateway in the configuration.
example_variable:

This variable is highlighted in orange, drawing attention to its presence as a recent addition.
The description reads, "An example variable to showcase the module release process," implying that this variable was added as part of a minor update or for demonstration purposes. It likely serves as a placeholder or example for users to understand the module’s customization potential.
external_nat_ip_ids:

This variable holds a list of Elastic IP (EIP) IDs to be assigned to NAT Gateways.
The description indicates that this variable is used in combination with NAT configurations to assign specific EIP addresses to NAT Gateways, a common requirement in AWS infrastructure setups.
This documentation table is part of best practices for Terraform module development, making it easy for users to find the purpose and usage of each variable. By clearly describing each variable, including examples or placeholders like example_variable, this table enhances the module's usability, helping users configure it according to their infrastructure needs.
The image shows a "Changelog" section for a Terraform module, listing two versions with release notes:

Version 1.1.0 (2021-10-05):

Linked to PR #6, which introduces a minor release by adding a new variable, example_variable.
Version 1.0.0 (2021-10-05):

Linked to PR #4, which includes a whitespace change to trigger a release after fixing CI/CD configuration issues.
This entry also notes it as the initial release of the module, with a reference to the source GitHub repository link for terraform-aws-vpc.
Each version includes a date, pull request reference, and a brief description of changes.

Verwenden Sie das Modul über die S3-URL

Private Repositories sollten S3-URLs nutzen, damit Terraform die AWS-Authentifizierung und -Autorisierung verwenden kann. Öffentliche Repositories können einfach normale HTTP-URLs nutzen.

source = “s3:https://forto-terrform-modules-demo.s3.eu-west-1.amazonaws.com/terraform-aws-vpc-1.1.0.zip”

Die Überlegung

Warum sollten Module überhaupt versioniert werden

Deterministisches Verhalten – Überraschungen sind vielleicht nett bei Geburtstagen, aber nicht in Code-Repositories für Infrastruktur! Operatoren sollten Änderungen an ihren Infrastruktur-Objekten nur erwarten, wenn sie den Code in ihrem Terraform-Arbeitsbereich ändern, unabhängig davon, was an den Modulen gemacht wurde. Wenn eine neue Version eines Moduls benötigt wird (z. B. zur Behebung eines Fehlers oder zum Hinzufügen von Funktionen), muss eine explizite Änderung am Terraform-Arbeitsbereich vorgenommen werden. Dadurch wird sichergestellt, dass Richtlinien wie GitHub CODEOWNER Required Reviews eingehalten werden.

Warum SemVer?

Die Nutzer der Module können leicht erkennen, ob sie vor einem Upgrade einen Blick in das Changelog werfen müssen, da Hauptversionen auf große oder breaking Changes hinweisen. Dabei ist zu beachten, dass Patch-Versionen nicht unbedingt weniger Risiko mit sich bringen als Minor- oder sogar Major-Versionen. Das Risiko wird durch Tests sowie Code- und Plan-Reviews gemanagt.

Einige Entscheidungen, die wir getroffen haben

#1 Separates Repository

Die CI/CD- und Berechtigungs-/Besitzmodelle unterscheiden sich stark von anderen Terraform-Repositories. Wenn Module und Workspaces im selben Repository untergebracht wären, würde dies zu einer hochkomplexen Repository-Konfiguration führen.

#2 Pull-Request(PR)-basiert

Ursprünglich verfolgten wir einen commit-basierten Ansatz. Versio prüfte alle [konventionellen] Commit-Nachrichten in einem gemergten PR, entschied über ein Versions-Upgrade und veröffentlichte die Änderungen.

Obwohl uns der reine Git-Ansatz gefiel, führte er zu verwirrenden Nutzererfahrungen. Beispielsweise konnten Commit-Nachrichten aus einem „Squash“-Merge wieder auftauchen und den Versio-Release-Befehl fehlschlagen lassen. Dieser Fehler trat nach Abschluss des Merges auf, was die Behebung besonders ärgerlich machte.

Da GitHub keine zentralisierten Pre-Commit-Hooks bereitstellt, konnten wir keine Richtlinie für diese Commit-Nachrichten leicht durchsetzen. Nutzer mussten die Repository-Anleitung genau befolgen oder ihre PR-Git-Historie neu schreiben.

Für unsere von Grund auf neu entwickelte Lösung begannen wir, den GitHub-PR selbst zu nutzen. Ein Satz aus vier PR-Labels (major, minor, patch und no-release) ersetzte das komplexe Scannen von Commit-Nachrichten nach Versionsmarkierungen wie „feat!“ und „fix:“. Durch das Ignorieren der Git-Historie wurden die Versionssprünge wesentlich zuverlässiger und vorhersehbarer.

Außerdem konnten wir die „geplante“ Version im PR-Kommentar anzeigen und PR-Merges ohne Label blockieren, wodurch die Aufmerksamkeit auf die Größe des Versionssprungs gelenkt wurde. Dies wurde mit unserem eigenen Code in bekannten Sprachen (Typescript/YAML/Bash) und etwa 95 % weniger Codezeilen im Vergleich zu Versios Rust-Codebasis implementiert.

#3 Versions-„State“ in Git-Tags speichern

  • Dies ist einer der Betriebsmodi von Versio, und wir fanden ihn großartig! Das Speichern der Version in einer Datei im Repository würde nach jeder Veröffentlichung einen zusätzlichen Commit erfordern.
  • Die Verbindung zu einer externen Datenbank würde viel zusätzliche Komplexität hinzufügen!

#4 Speichern des Modul-Registers auf Amazon S3

  • Praktisch kostenlos für diese Datenmenge.
  • Für private Registries wird die AWS-Authentifizierung/-Autorisierung bereits vom Terraform-CLI gehandhabt (vorausgesetzt, Sie verwenden das S3-Backend von Terraform).
  • Wir bevorzugen strikte Versionierung (siehe oben), sodass keine Versionsbeschränkungen erforderlich sind. Wenn die Versionsauflösung wichtig wäre, hätten wir uns stattdessen wahrscheinlich für das offizielle Terraform-Register entschieden.

#5 Dokumentation und Changelog in der GitHub-Wiki

  • Kostenlos oder, falls private Repos verwendet werden, bereits bezahlt.
  • Einfach zugänglich für Entwickler.
  • Mühelos authentifiziert, falls erforderlich.

#6 Verwendung von GitHub Actions

  • Gute Unterstützung für dynamische Workflows.
  • Einfache Integration mit GitHub-Pull-Requests.
 

Unsere nächsten Schritte

Der große Nachteil einer strikten Versionierung ist der sogenannte Versionsdrift. Upgrades müssen explizit durchgeführt werden, und wenn diese nicht konsequent umgesetzt werden, bleiben frühere Versionen der Module in Gebrauch, lange nachdem neue Versionen verfügbar sind.

Wir planen, einen weiteren Job in die Terraform-Module-Pipeline zu integrieren, der automatisch PRs (Pull Requests) öffnet, um alle Modulreferenzen im Workspaces-Root-Repository auf die neueste Version zu aktualisieren.

Für Minor- und Patch-Versionserhöhungen sollten diese PRs direkt bereit sein, um wie vorgegeben gemergt zu werden. Major-Versionserhöhungen hingegen würden als Entwurfs-PRs geöffnet, da wir davon ausgehen, dass größere oder breaking Changes mehr Arbeit erfordern, bevor die PRs für den Merge bereit sind.

Das war’s!

Ich hoffe, Sie fanden diesen Artikel relevant! Schauen Sie sich gerne unser Referenz-Repository an: https://github.com/freight-hub/terraform-modules-demo. Es sollte möglich sein, es zu klonen und die CI in wenigen Stunden an Ihre Bedürfnisse anzupassen.

Und vergessen Sie nicht, unsere Karriereseite zu besuchen! Wir arbeiten an vielen spannenden Projekten, von denen die meisten gar nicht in unserem Blog landen.