//

Terraform Remote State richtig nutzen

21.4.2022 | 8 Minuten Lesezeit

Was ist Terraform und was ist State?

Terraform ist ein Tool für die Verwaltung von Infrastruktur in Form von Code, gehört also in den sogenannten Infrastructure-as-Code-Bereich (IaC). Eine kurze Einführung und ein Vergleich zu anderen Tools findet sich hier .

Terraform verwendet intern einen State (persistiert als JSON-Datei), der den aktuellen Zustand der Infrastruktur enthält. Dieser State beschreibt den Zusammenhang zwischen den Ressourcen beim Provider und den im Skript verwendeten Ressourcen. Er wird bei jedem erfolgreichen Deployment aktualisiert. Mittels terraform import kann man auch bereits vorhandene, also nicht mittels Terraform erzeugte, Infrastruktur zum State hinzufügen. Möchte man die Infrastruktur ändern, so macht man Änderungen im Terraform-Code, was den neuen gewünschten Zustand darstellt. Terraform vergleicht diesen neuen Zustand dann mit dem bisherigen und kann dadurch die benötigten Änderungen berechnen, was zu effizienten Deployments führt.

Da State so ein zentraler Bestandteil von Terraform ist, gilt es, diesen entsprechend zu verwalten. Dabei geht es vor allem um Schutz vor unbefugtem Zugriff und Datenverlust.

Was ist Remote State und warum brauche ich das?

Standardmäßig wird der State von Terraform als Datei lokal – also dort, wo Terraform ausgeführt wird – vorgehalten. Dies ist zwar für einzelne Entwickler oder Tests geeignet. Für die Verwaltung eines großen Produktivsystems kann es jedoch sehr problematisch sein, da der State sehr anfällig für Datenverlust ist und man Änderungen genau nur dort durchführen kann, wo sich die State-Datei findet. Ebenso können sich sensible Informationen wie Passwörter im State befinden, weshalb es z. B. keine gute Idee wäre, die State-Datei in ein VCS einzuchecken.

Generell sollte die State-Datei als sensible Information betrachtet werden und es sollten entsprechende Schutzmaßnahmen durchgeführt werden!

Um diesen Problemen zu begegnen, bietet Terraform den sogenannten Remote State an. Hierbei wird der State an einer zentralen Stelle vorgehalten, die im Skript definiert wird, so dass man Deployments von überall ausführen kann, von wo aus auf diese Stelle zugegriffen werden kann. Beliebte Backends hierfür sind AWS S3 oder Azure Blob Storage, die Terraform neben weiteren unterstützt.

Um Remote State zu verwenden, muss das Backend lediglich im Terraform-Skript definiert werden, hier am Beispiel von S3:

1terraform {
2  backend “s3” {
3    bucket = “myremotestatebucket”
4    key = “path/to/my/state.tfstate”
5    region = “eu-central-1”
6    encrypt = true
7  }
8}
9

Bei der Verwendung von Remote State können natürlich Race Conditions auftreten, z. B. wenn mehrere Deployments parallel laufen und dabei den State verändern möchten. Um dies zu verhindern, kann man in AWS eine DynamoDB-Tabelle als Lock verwenden. Die Backend-Konfiguration wird dafür um den Verweis auf die Tabelle erweitert:

1terraform {
2  backend “s3” {
3    bucket = “myremotestatebucket”
4    key = “path/to/my/state.tfstate”
5    region = “eu-central-1”
6    dynamodb_table = “mylocktable”
7  }
8}
9

Wie kann Remote State noch verwendet werden?

Terraform bietet die Möglichkeit, Remote State als Datenquelle zu verwenden. Dies ermöglicht es, in Konfigurationen auf die Ergebnisse anderer Konfigurationen zuzugreifen. Dieser Mechanismus versetzt den Nutzer etwa in die Lage, die Konfiguration eines größeren Gesamtsystems zu dezentralisieren. Eine Variante ist es etwa, gemeinsam verwendete, zentrale Komponenten wie Domains, Zertifikate oder Datenbanken in eine „Infrastruktur“-Konfiguration auszulagern. Die anderen Teile des Systems wie etwa einzelne Microservices können dann in ihren eigenständigen Konfigurationen über den Remote State auf die benötigten Komponenten zugreifen.

Beispiel

Eine SaaS-Applikation, bestehend aus mehreren Komponenten, darunter ein Microservice, der als Lambda implementiert ist, sollen in einem VPC deployt werden. Um dem Microservice-Team die Verantwortung für sicherheitskritische Dinge wie VPC, Subnets oder Security Groups abzunehmen, übernimmt diese das zentrale Deployment:

1terraform {
2  backend “s3” {
3    bucket = “centraldeploymentstate”
4    key = “global/terraform.tfstate”
5    region = “eu-central-1”
6    dynamodb_table = “locktable”
7  }
8}
9 
10resource "aws_vpc" "main" {
11  cidr_block = "10.0.0.0/16"
12 
13  tags = local.common_tags
14}
15 
16resource "aws_subnet" "lambda" {
17  vpc_id 	= aws_vpc.main.id
18  cidr_block = "10.0.1.0/24"
19 
20  tags = local.common_tags
21}
22 
23resource "aws_security_group" "allow_tls" {
24  name    	= "allow_tls"
25  description = "Allow TLS inbound traffic"
26  vpc_id  	= aws_vpc.main.id
27 
28  ingress {
29    description  	= "TLS from VPC"
30    from_port    	= 443
31    to_port      	= 443
32    protocol     	= "tcp"
33    cidr_blocks  	= [aws_vpc.main.cidr_block]
34  }
35 
36  egress {
37    from_port    	= 0
38    to_port      	= 0
39    protocol     	= "-1"
40    cidr_blocks  	= ["0.0.0.0/0"]
41    ipv6_cidr_blocks = ["::/0"]
42  }
43 
44  tags = local.common_tags
45}
46 
47resource "aws_iam_role" "iam_for_lambda" {
48  name = "iam_for_lambda"
49 
50  assume_role_policy = <<EOF
51{
52  "Version": "2012-10-17",
53  "Statement": [
54    {
55      "Action": "sts:AssumeRole",
56      "Principal": {
57        "Service": "lambda.amazonaws.com"
58      },
59      "Effect": "Allow",
60      "Sid": ""
61    }
62  ]
63}
64EOF
65}
66 
67resource "aws_iam_role_policy_attachment" "lambda_role_vpc_execution" {
68  role = aws_iam_role.iam_for_lambda.name
69  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
70}
71

Damit die Microservice-Konfiguration die benötigten Ressourcen verwenden kann, definieren wir diese noch als Outputs:

1output "vpc_id" {
2  value   	= aws_vpc.main.id
3  description = "ID of the main VPC"
4}
5 
6output "lambda_subnet_id" {
7  value   	= aws_subnet.lambda.id
8  description = "ID of the subnet dedicated to Lambdas"
9}
10 
11output "tls_security_group_id" {
12  value   	= aws_security_group.allow_tls.id
13  description = "ID of the allow_tls security group"
14}
15 
16output "lambda_iam_role" {
17  value   	= aws_iam_role.iam_for_lambda.arn
18  description = "ARN of Lambda execution role"
19}
20

Das Microservice-Team definiert sich nun den Remote State der zentralen Konfiguration als Datenquelle, um auf Dinge wie die VPC ID zugreifen zu können:

1data "terraform_remote_state" "global" {
2  backend = "s3"
3  config = {
4    bucket = "centraldeploymentstate"
5    key	   = "global/terraform.tfstate"
6    region = "eu-central-1"
7  }
8}
9

In der Konfiguration des Lambda können wir die zentral verwalteten Ressourcen dann einfach verwenden, ohne manuell IDs oder Ähnliches kopieren zu müssen:

1resource "aws_lambda_function" "test_lambda" {
2  filename  	   = "lambda.zip"
3  function_name    = "TestLambda"
4  role      	   = data.terraform_remote_state.global.outputs.lambda_iam_role
5  handler   	   = "index.js"
6  source_code_hash = filebase64sha256("lambda.zip")
7 
8  runtime = "nodejs12.x"
9  vpc_config {
10    subnet_ids     	= [data.terraform_remote_state.global.outputs.lambda_subnet_id]
11    security_group_ids  = [data.terraform_remote_state.global.outputs.tls_security_group_id]
12  }
13}
14

Vorteile

Bestimmte zentrale Komponenten und Services in einer separaten Konfiguration zusammenzufassen, verringert die Wahrscheinlichkeit von unabsichtlichen Veränderungen und dadurch entstehende Ausfälle.
Zudem müssen sich die Teams der einzelnen Komponenten wie Microservices nicht mit den Besonderheiten für die Konfiguration der zentralen Dienste auskennen, sondern können sich auf die Konfiguration ihres Services konzentrieren.
Zusätzlich können wir auf diese Art und Weise eine gewisse Standardisierung und Wiederverwendung erreichen, z. B. von Policies und Rollen für bestimmte Aufgaben oder gemeinsames zentralisiertes Logging.
Schließlich erhöht die Verwendung getrennter States die Performance von Terraform, da bei jedem Deployment weniger Ist-Zustand mit neuem Soll-Zustand abgeglichen werden muss – im Gegensatz z. B. zu dem Ansatz, jedes Mal das gesamte System neu zu deployen.

Nachteile

Wie immer gibt es natürlich auch hier Nachteile bzw. Dinge, die beachtet werden müssen. Zentral ist natürlich die Frage, wie man die einzelnen Konfigurationen schneidet, also welche Komponenten man gemeinsam deployt und im State hält. Als Richtlinie bieten sich hier vor allem die Abhängigkeiten an. Zyklen müssen durchbrochen werden, sonst klappt das Deployment erst gar nicht.
Wichtig zu beachten ist auch die Kompatibilität zwischen State-Datei und verwendeter Terraform CLI-Version. Wird z. B. das zentrale Deployment mit einer neuen Version aktualisiert, kann es sein, dass ein Komponenten-Deployment mit älterer Version den Remote State nicht mehr lesen und dann fehlschlagen kann. Seit Version 1.0 ist das State-Format jedoch eingefroren, so dass hier immer Rückwärtskompatibilität gewährleistet ist.
Schließlich benötigt für diesen Ansatz jede Konfiguration vollen Zugriff auf den Remote State, der als Abhängigkeit verwendet wird. Dies kann einen Minimum-Required-Access-Ansatz erschweren.

Alternativen

Auch wenn die Verwendung von Remote State zum Teilen von Konfigurationsinformation einfach und schnell ist, gibt es doch Gründe, Alternativen zu verwenden. Die bereits beschriebene Zugriffskontrolle auf den State ist ein gewichtiger Grund, aber z. B. auch die Anforderung, dass andere Systeme (nicht nur Terraform) auf Konfigurationsinformationen zugreifen können sollen. So existieren z. B. in AWS für quasi alle Ressourcen auch entsprechende Datenquellen, die es ermöglichen, nach bestimmten existierenden Ressourcen zu suchen und Ergebnisse zu filtern. So kann etwa nach Subnets gesucht und aus dem Ergebnis können die CIDR-Blöcke exportiert werden:

1data "aws_subnets" "example" { 
2  filter { 
3    name   = "vpc-id" 
4    values = [var.vpc_id] 
5  } 
6} 
7 
8data "aws_subnet" "example" { 
9  for_each = toset(data.aws_subnets.example.ids) 
10  id       = each.value 
11} 
12 
13output "subnet_cidr_blocks" { 
14  value = [for s in data.aws_subnet.example : s.cidr_block] 
15}
16

Wann also sollte ich Remote State zur Dezentralisierung verwenden?

Auf diese Frage gibt es leider keine pauschale Antwort. Man kann jedoch sagen, dass sich das oben beschriebene Beispiel anbietet, wenn ein Entwicklungsteam das Deployment des gesamten Systems und damit auch den gesamten Terraform State kontrolliert. In diesem Fall kann sich ein kleiner Teil des Teams um die grundlegende Infrastruktur kümmern und z. B. die Frontend- oder Microservice-Entwickler jeweils nur um ihre eigenen Konfigurationen. Gerade bei der Entwicklung von POCs oder eines MVP kann dadurch die Entwicklungsgeschwindigkeit erhöht und gleichzeitig eine solide Basis für das Infrastruktur-Management geschaffen werden.

Natürlich gilt es immer zu prüfen, ob die spezifischen Datenquellen für die Ressourcen oder wiederverwendete Terraform-Module nicht auch zum gewünschten Ziel führen können. Denn es kann nicht genug betont werden, dass der State sensible Informationen enthält!

Schließlich noch ein Beispiel aus der Praxis von meinem Kollegen Dennis Grunert. Er verwendet Terraform State, um einen Load Balancer vor „Script Kiddies“ zu schützen. Dazu konfiguriert er die Firewall so, dass sie nur Requests durchlässt, die einen speziellen Header mit einem geheimen Wert enthalten. Diesen geheimen Wert erzeugt man beim Deployment in Terraform mittels der Funktion random_password() und gibt sie sowohl der Firewall als auch dem CDN mit. Neben Terraform und seinem State ist hier nichts weiter erforderlich, um diesen geheimen Wert zu verteilen und vorzuhalten.

Fazit

Remote State ist ein essenzielles Konzept für die produktive Verwendung von Terraform zur Verwaltung von Infrastruktur-Ressourcen. Neben den offensichtlichen Vorteilen der Datensicherheit und -verfügbarkeit bietet es durch die Möglichkeit, auch als Datenquelle zu fungieren, weitreichende Optionen, komplexe Gesamtsysteme modular abzubilden.

Referenzen

Beitrag teilen

Gefällt mir

0

//

Weitere Artikel in diesem Themenbereich

Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.

//

Gemeinsam bessere Projekte umsetzen

Wir helfen Deinem Unternehmen

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.