Zum Inhalt

🏗️ Terraform-Integration

Polycrate integriert sich nahtlos mit Terraform für Infrastructure-as-Code und Cloud-Provisioning. Mit Polycrate können Sie Terraform-Workflows in Blocks kapseln, State-Files verwalten und Terraform-Outputs als Artifacts für andere Blocks bereitstellen.

Was ist Terraform?

Terraform ist ein Open-Source Infrastructure-as-Code Tool von HashiCorp, das es ermöglicht, Infrastruktur mit deklarativen Konfigurationsdateien zu definieren und zu verwalten. Terraform unterstützt:

  • Multi-Cloud: AWS, Azure, GCP, DigitalOcean, und viele mehr
  • Deklarative Syntax: Beschreiben Sie den gewünschten Zustand, nicht die Schritte
  • State-Management: Terraform verfolgt den aktuellen Zustand Ihrer Infrastruktur
  • Plan-before-Apply: Vorschau der Änderungen vor Ausführung
  • Resource-Graph: Automatische Abhängigkeitserkennung und parallele Ausführung

Terraform in Polycrate

graph TB
    Block[Terraform-Block] -->|führt aus| TF[Terraform CLI]
    TF -->|liest| Config[*.tf Dateien]
    TF -->|verwaltet| State[State-File]
    State -->|gespeichert in| Artifacts[Artifacts]

    TF -->|provisioniert| Cloud[Cloud-Ressourcen]
    Cloud -->|AWS| EC2[EC2, VPC, S3, ...]
    Cloud -->|Azure| AzureRes[VMs, VNets, ...]
    Cloud -->|GCP| GCPRes[GCE, GKE, ...]

    TF -->|generiert| Output[Terraform-Output]
    Output -->|konvertiert zu| Inventory[Ansible-Inventory]
    Inventory -->|genutzt von| AnsibleBlock[Ansible-Block]

    style Block fill:#e1f5ff
    style State fill:#ffe1e1
    style Cloud fill:#fff4e1

Terraform-Actions in Blocks

Basis-Setup

# blocks/infrastructure/block.poly
name: infrastructure
kind: generic
version: 1.0.0

actions:
  - name: init
    description: Initialize Terraform
    script:
      - cd terraform/
      - terraform init

  - name: plan
    description: Plan Terraform changes
    script:
      - cd terraform/
      - terraform plan

  - name: apply
    description: Apply Terraform changes
    script:
      - |
        cd terraform/
        terraform apply -auto-approve

        # State als Artifact speichern
        ARTIFACTS="{{ .workspace.config.artifacts_root }}/blocks/{{ .block.name }}"
        cp terraform.tfstate $ARTIFACTS/terraform.tfstate
        cp terraform.tfstate.backup $ARTIFACTS/terraform.tfstate.backup 2>/dev/null || true

  - name: destroy
    description: Destroy Terraform-managed infrastructure
    prompt:
      message: "Are you sure you want to destroy the infrastructure?"
    script:
      - cd terraform/
      - terraform destroy -auto-approve

Terraform-Dateien im Block

blocks/infrastructure/
├── block.poly
├── terraform/
│   ├── main.tf
│   ├── variables.tf
│   ├── outputs.tf
│   └── providers.tf
└── scripts/
    └── generate-inventory.py

Terraform-Konfiguration

Beispiel: AWS EC2-Instanzen

terraform/main.tf:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

resource "aws_instance" "web" {
  count         = var.instance_count
  ami           = var.ami_id
  instance_type = var.instance_type

  tags = {
    Name        = "web-${count.index + 1}"
    Environment = var.environment
    ManagedBy   = "Polycrate"
  }

  key_name = var.ssh_key_name

  vpc_security_group_ids = [aws_security_group.web.id]
}

resource "aws_security_group" "web" {
  name        = "web-sg"
  description = "Security group for web servers"

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

terraform/variables.tf:

variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "eu-central-1"
}

variable "instance_count" {
  description = "Number of EC2 instances"
  type        = number
  default     = 2
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.micro"
}

variable "ami_id" {
  description = "AMI ID for EC2 instances"
  type        = string
}

variable "ssh_key_name" {
  description = "SSH key pair name"
  type        = string
}

variable "environment" {
  description = "Environment name"
  type        = string
  default     = "production"
}

terraform/outputs.tf:

output "instance_ids" {
  description = "IDs of EC2 instances"
  value       = aws_instance.web[*].id
}

output "instance_public_ips" {
  description = "Public IPs of EC2 instances"
  value       = aws_instance.web[*].public_ip
}

output "instance_private_ips" {
  description = "Private IPs of EC2 instances"
  value       = aws_instance.web[*].private_ip
}

output "security_group_id" {
  description = "Security group ID"
  value       = aws_security_group.web.id
}

# Output als Ansible-Inventory-Format
output "ansible_inventory" {
  description = "Ansible inventory in JSON format"
  value = {
    all = {
      hosts = {
        for idx, instance in aws_instance.web : "web-${idx + 1}" => {
          ansible_host = instance.public_ip
          ansible_user = "ubuntu"
          ansible_ssh_private_key_file = "{{ .workspace.config.ssh_private_key }}"
          private_ip = instance.private_ip
        }
      }
      children = {
        webservers = {
          hosts = {
            for idx, instance in aws_instance.web : "web-${idx + 1}" => {}
          }
        }
      }
    }
  }
}

Terraform-Output als Inventory

Output generieren und speichern

actions:
  - name: apply
    script:
      - cd terraform/
      - terraform apply -auto-approve

  - name: generate-inventory
    description: Generate Ansible inventory from Terraform output
    script:
      - |
        cd terraform/
        ARTIFACTS="{{ .workspace.config.artifacts_root }}/blocks/{{ .block.name }}"

        # Terraform-Output als JSON
        terraform output -json ansible_inventory | \
          jq '.' > $ARTIFACTS/inventory.json

        # Konvertieren zu YAML (optional)
        python3 ../scripts/json-to-yaml.py $ARTIFACTS/inventory.json > $ARTIFACTS/inventory.yml

        echo "Inventory generated: $ARTIFACTS/inventory.yml"

Python-Script für Konvertierung

scripts/json-to-yaml.py:

#!/usr/bin/env python3
import json
import yaml
import sys

with open(sys.argv[1], 'r') as f:
    data = json.load(f)

# Terraform-Output hat zusätzliches "value"-Wrapper
if 'value' in data:
    data = data['value']

print(yaml.dump(data, default_flow_style=False))

Inventory von anderen Blocks nutzen

# Block: configure-servers
name: configure-servers
kind: linuxapp

inventory:
  from: infrastructure  # Nutzt von Terraform generiertes Inventory

actions:
  - name: install
    playbook: playbooks/setup-webservers.yml

State-Management

State als Artifact speichern

Terraform-State-Files sollten persistent in Artifacts gespeichert werden:

actions:
  - name: apply
    script:
      - |
        cd terraform/
        ARTIFACTS="{{ .workspace.config.artifacts_root }}/blocks/{{ .block.name }}"

        # State aus Artifacts laden (falls vorhanden)
        if [ -f "$ARTIFACTS/terraform.tfstate" ]; then
          cp $ARTIFACTS/terraform.tfstate .
        fi

        # Terraform anwenden
        terraform init
        terraform apply -auto-approve

        # State zurück in Artifacts speichern
        cp terraform.tfstate $ARTIFACTS/
        cp terraform.tfstate.backup $ARTIFACTS/ 2>/dev/null || true

  - name: destroy
    script:
      - |
        cd terraform/
        ARTIFACTS="{{ .workspace.config.artifacts_root }}/blocks/{{ .block.name }}"

        # State laden
        if [ -f "$ARTIFACTS/terraform.tfstate" ]; then
          cp $ARTIFACTS/terraform.tfstate .
        fi

        terraform destroy -auto-approve

        # State aktualisieren
        cp terraform.tfstate $ARTIFACTS/

Remote State (Empfohlen)

Für Team-Umgebungen sollten Sie Terraform Remote State verwenden:

terraform/backend.tf:

terraform {
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "infrastructure/terraform.tfstate"
    region = "eu-central-1"

    # State-Locking mit DynamoDB
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

Terraform + Ansible Workflow

Typischer Workflow: Terraform provisioniert Infrastruktur, Ansible konfiguriert Server.

sequenceDiagram
    participant User
    participant TF as Terraform-Block
    participant Artifacts
    participant Ansible as Ansible-Block
    participant Cloud

    User->>TF: polycrate run infrastructure apply
    TF->>Cloud: Provisioniere VMs
    Cloud-->>TF: VMs erstellt (IPs)
    TF->>Artifacts: Speichere Inventory
    TF->>Artifacts: Speichere State
    TF-->>User: Fertig

    User->>Ansible: polycrate run configure-servers install
    Ansible->>Artifacts: Lade Inventory
    Artifacts-->>Ansible: Inventory mit IPs
    Ansible->>Cloud: Konfiguriere VMs
    Ansible-->>User: Fertig

Workflow-Definition in workspace.poly:

name: my-workspace

blocks:
  - name: infrastructure
    # ...
  - name: configure-servers
    # ...
  - name: my-app
    # ...

workflows:
  - name: provision-and-configure
    steps:
      - name: terraform-plan
        block: infrastructure
        action: plan

      - name: terraform-apply
        block: infrastructure
        action: apply
        prompt:
          message: "Apply Terraform changes?"

      - name: generate-inventory
        block: infrastructure
        action: generate-inventory

      - name: configure-servers
        block: configure-servers
        action: install  # Nutzt generiertes Inventory

      - name: deploy-application
        block: my-app
        action: install

Ausführen:

polycrate workflows run provision-and-configure

Terraform-Variablen aus Block-Config

Nutzen Sie Snapshot-Variablen für Terraform-Variablen:

# block.poly
name: infrastructure
kind: generic

config:
  aws:
    region: eu-central-1
    instance_type: t3.micro
    instance_count: 3
  environment: production

actions:
  - name: apply
    script:
      - |
        cd terraform/

        # Variablen aus Block-Config als TF_VAR_* Environment-Variablen
        export TF_VAR_aws_region="{{ .block.config.aws.region }}"
        export TF_VAR_instance_type="{{ .block.config.aws.instance_type }}"
        export TF_VAR_instance_count="{{ .block.config.aws.instance_count }}"
        export TF_VAR_environment="{{ .block.config.environment }}"

        terraform apply -auto-approve

Oder mit auto.tfvars:

actions:
  - name: apply
    script:
      - |
        cd terraform/

        # terraform.auto.tfvars generieren
        cat > terraform.auto.tfvars <<EOF
        aws_region      = "{{ .block.config.aws.region }}"
        instance_type   = "{{ .block.config.aws.instance_type }}"
        instance_count  = {{ .block.config.aws.instance_count }}
        environment     = "{{ .block.config.environment }}"
        EOF

        terraform apply -auto-approve

Kubernetes-Cluster mit Terraform

EKS-Cluster (AWS)

# terraform/main.tf
module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 19.0"

  cluster_name    = var.cluster_name
  cluster_version = "1.28"

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets

  eks_managed_node_groups = {
    default = {
      min_size     = 2
      max_size     = 4
      desired_size = 2

      instance_types = ["t3.medium"]
    }
  }
}

output "kubeconfig" {
  description = "Kubeconfig for EKS cluster"
  value       = module.eks.kubeconfig
  sensitive   = true
}

Kubeconfig generieren:

actions:
  - name: create-cluster
    script:
      - cd terraform/
      - terraform apply -auto-approve

  - name: generate-kubeconfig
    script:
      - |
        cd terraform/
        ARTIFACTS="{{ .workspace.config.artifacts_root }}/blocks/{{ .block.name }}"

        # Kubeconfig von Terraform-Output
        terraform output -raw kubeconfig > $ARTIFACTS/kubeconfig.yml

        # Oder mit AWS CLI
        aws eks update-kubeconfig \
          --region {{ .block.config.aws.region }} \
          --name {{ .block.config.cluster_name }} \
          --kubeconfig $ARTIFACTS/kubeconfig.yml

        echo "Kubeconfig saved: $ARTIFACTS/kubeconfig.yml"

Apps deployen zu erstelltem Cluster:

# Block: my-k8s-app
name: my-k8s-app
kind: k8sapp

kubeconfig:
  from: eks-cluster  # Nutzt Kubeconfig von Terraform-Block

config:
  namespace: production

actions:
  - name: install
    playbook: playbooks/deploy-helm.yml

Best Practices

1. Separate State per Environment

blocks/
├── infrastructure-dev/
│   ├── block.poly
│   └── terraform/
│       ├── main.tf
│       └── backend-dev.tf
└── infrastructure-prod/
    ├── block.poly
    └── terraform/
        ├── main.tf
        └── backend-prod.tf

2. Plan before Apply

Immer erst plan ausführen:

polycrate run infrastructure plan
# Review changes
polycrate run infrastructure apply

3. Terraform-Module nutzen

Verwenden Sie Terraform-Module für Wiederverwendbarkeit:

module "web_servers" {
  source = "./modules/web-server"

  count         = var.instance_count
  instance_type = var.instance_type
  environment   = var.environment
}

4. State-Locking

Nutzen Sie Remote State mit Locking für Team-Arbeit:

terraform {
  backend "s3" {
    bucket         = "terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "eu-central-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

5. Secrets-Management

Niemals Secrets im Code:

actions:
  - name: apply
    script:
      - |
        # Secrets von externen Quellen laden
        export TF_VAR_db_password=$(aws secretsmanager get-secret-value \
          --secret-id prod/database/password \
          --query SecretString \
          --output text)

        cd terraform/
        terraform apply -auto-approve

6. Terraform-Output für Monitoring

output "monitoring_endpoints" {
  value = {
    prometheus = "http://${aws_instance.prometheus.public_ip}:9090"
    grafana    = "http://${aws_instance.grafana.public_ip}:3000"
  }
}

Vollständiges Beispiel

Multi-Cloud Infrastructure

# blocks/multi-cloud-infra/block.poly
name: multi-cloud-infra
kind: generic
version: 1.0.0

config:
  aws:
    region: eu-central-1
    instance_count: 2
  azure:
    location: westeurope
    vm_count: 1

actions:
  - name: plan
    description: Plan infrastructure changes
    script:
      - cd terraform/
      - terraform init
      - terraform plan

  - name: apply
    description: Apply infrastructure changes
    prompt:
      message: "Apply changes to AWS and Azure?"
    script:
      - |
        cd terraform/
        ARTIFACTS="{{ .workspace.config.artifacts_root }}/blocks/{{ .block.name }}"

        # State laden
        [ -f "$ARTIFACTS/terraform.tfstate" ] && cp $ARTIFACTS/terraform.tfstate .

        # Variablen setzen
        export TF_VAR_aws_region="{{ .block.config.aws.region }}"
        export TF_VAR_aws_instance_count="{{ .block.config.aws.instance_count }}"
        export TF_VAR_azure_location="{{ .block.config.azure.location }}"
        export TF_VAR_azure_vm_count="{{ .block.config.azure.vm_count }}"

        # Apply
        terraform apply -auto-approve

        # State speichern
        cp terraform.tfstate $ARTIFACTS/
        cp terraform.tfstate.backup $ARTIFACTS/ 2>/dev/null || true

  - name: generate-inventory
    description: Generate Ansible inventory
    script:
      - |
        cd terraform/
        ARTIFACTS="{{ .workspace.config.artifacts_root }}/blocks/{{ .block.name }}"

        terraform output -json all_hosts | \
          python3 ../scripts/tf-to-inventory.py > $ARTIFACTS/inventory.yml

        echo "Inventory: $ARTIFACTS/inventory.yml"

  - name: destroy
    description: Destroy infrastructure
    prompt:
      message: "DESTROY all infrastructure in AWS and Azure?"
    script:
      - |
        cd terraform/
        ARTIFACTS="{{ .workspace.config.artifacts_root }}/blocks/{{ .block.name }}"

        [ -f "$ARTIFACTS/terraform.tfstate" ] && cp $ARTIFACTS/terraform.tfstate .

        terraform destroy -auto-approve

        cp terraform.tfstate $ARTIFACTS/

Zusammenhang mit anderen Konzepten

  • Artifacts: Terraform-State und Outputs als Artifacts
  • Ansible: Terraform provisioniert, Ansible konfiguriert
  • Workflows: Orchestrierung von Terraform-Operationen
  • Snapshot: Block-Config als Terraform-Variablen
  • Kubernetes: Kubernetes-Cluster mit Terraform erstellen

Terraform als Provisioner

Nutzen Sie Terraform für Infrastruktur-Provisioning und Ansible für Configuration. So bleiben Verantwortlichkeiten getrennt und beide Tools können ihre Stärken ausspielen!