Статьи

Управление несколькими средами с рабочими пространствами Terraform

Начало

Если вы хотите управлять (создавать, изменять и удалять) своей инфраструктурой, начать работу с Terraform  очень просто. Просто создайте файлы, заканчивающиеся на .tf, содержащие описание ресурсов, которые вы хотите иметь.

Например, если мы хотим создать небольшую инфраструктуру в облачном провайдере AWS:

  • Ведро S3 (для Terraform)
  • Ведро S3 (для нашего сайта)
  • Ведро S3 (для логов сайта)
  • Распределение CloudFront (без SSL для этого примера)

Вам также может понравиться Руководство для начинающих Linode по Terraform .

Нам просто нужно создать несколько файлов .tf, например:

aws_s3.tf
aws_cloudfront.tf
root.tf
variables.tf
output.tf

aws_s3.tf:

# s3 bucket for terraform state files
resource "aws_s3_bucket" "com_scraly_terraform" {
    bucket = "${var.aws_s3_bucket_terraform}"
    acl    = "private"

    versioning {
        enabled = true
    }

    tags {
        Tool    = "${var.tags-tool}"
        Contact = "${var.tags-contact}"
    }
}

# s3 bucket for front logs
resource "aws_s3_bucket" "front_logs" {
  bucket = "${terraform.workspace == "preprod" ? var.bucket_demo_logs_preprod : var.bucket_demo_logs}"
  acl    = "log-delivery-write"

  tags {
    Tool    = "${var.tags-tool}"
    Contact = "${var.tags-contact}"
  }
}

# s3 bucket reached by cloudfront
resource "aws_s3_bucket" "front_bucket" {
  bucket = "${terraform.workspace == "preprod" ? var.bucket_demo_preprod : var.bucket_demo}"
  acl    = "private"

  force_destroy = false

  depends_on = ["aws_s3_bucket.front_bucket-logs"]

  versioning {
    enabled = true
  }

  logging {
    target_bucket = "${aws_s3_bucket.front_bucket-logs.bucket}"
    target_prefix = "root/"
  }

  website {
    index_document = "index.html"
  }

  tags {
    Tool    = "${var.tags-tool}"
    Contact = "${var.tags-contact}"
  }

  cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["GET", "PUT", "POST", "DELETE"]
    allowed_origins = ["*"]
  }

  policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
  "Sid": "PublicReadGetObject",
  "Effect": "Allow",
  "Principal": "*",
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:::${terraform.workspace == "preprod" ? var.bucket_demo_preprod : var.bucket_demo}/*"
}
]
}
POLICY
}

aws_cloudfront.tf:

resource "aws_cloudfront_distribution" "front_cdn" {

  # Use All Edge Locations (Best Performance)
  price_class  = "PriceClass_All"
  http_version = "http2"

  "origin" {
    origin_id   = "origin-bucket-${aws_s3_bucket.front_bucket.id}"
    domain_name = "${aws_s3_bucket.front_bucket.website_endpoint}"
    origin_path = "/root"

    custom_origin_config {
      origin_protocol_policy = "http-only"
      http_port              = "80"
      https_port             = "443"
      origin_ssl_protocols   = ["TLSv1"]
    }
  }

  enabled             = true
  is_ipv6_enabled     = true
  default_root_object = "index.html"

  logging_config {
    include_cookies = true
    bucket          = "${aws_s3_bucket.front_bucket-logs.bucket}.s3.amazonaws.com"
    prefix          = "cloudfront/"
  }


  "default_cache_behavior" {
    allowed_methods = ["GET", "HEAD", "DELETE", "OPTIONS", "PATCH", "POST", "PUT"]
    cached_methods  = ["GET", "HEAD"]

    "forwarded_values" {
      query_string = false

      cookies {
        forward = "none"
      }
    }

    min_ttl          = "21600"
    default_ttl      = "86400"
    max_ttl          = "31536000"
    target_origin_id = "origin-bucket-${aws_s3_bucket.front_bucket.id}"
    // This redirects any HTTP request to HTTPS. Security first!
    viewer_protocol_policy = "redirect-to-https"
    compress               = true
  }
  "restrictions" {
    "geo_restriction" {
      restriction_type = "none"
    }
  }
  # Pre-requisits: Put a SSL cert on AWS store in us-east-1 region
  # Generate a csr in loacalhost, make requst to IT, get the returned cert
  # put the cert + intermediate + private key in AWS (click in import button)
  "viewer_certificate" {
    # default certificate if you don't already added one in AWS certificate manager
    cloudfront_default_certificate = true
    minimum_protocol_version = "TLSv1.1_2016"
  }
  aliases = ["${var.demo_domain_name}"]

  depends_on = ["aws_s3_bucket.front_bucket"]

  tags {
    Tool    = "${var.tags-tool}"
    Contact = "${var.tags-contact}"
  }
}

root.tf:

provider "aws" {
  region = "eu-central-1"
}

variables.tf:

variable "default-aws-region" {
    default = "eu-central-1"
}

variable "tags-tool" {
    default = "Terraform"
}

variable "tags-contact" {
    default = "Aurelie Vache"
}

variable "aws_s3_bucket_terraform" {
    default = "com.dzone.scraly.terraform"
}

variable "bucket_demo" {
  default = "com.dzone.scraly.demo"
}

variable "bucket_demo_logs" {
  default = "com.dzone.scraly.demo.logs"
}

variable "bucket_demo_preprod" {
  default = "com.dzone.scraly.demo.preprod.demo"
}

variable "bucket_demo_logs_preprod" {
  default = "com.dzone.scraly.demo.preprod.demo.logs"
}

variable "demo_domain_name" {
  default = "demo.dzone.scraly.com"
}

output.tf:

output "cloudfront_id" {
  value = "${aws_cloudfront_distribution.front_cdn.id}"
}

Теперь нам нужно инициализировать Terraform (только в первый раз), сгенерировать план и применить его.

$ terraform init
Initializing the backend...

Initializing provider plugins...

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Команда  initинициализирует ваш рабочий каталог, который содержит файлы конфигурации .tf.

Это первая команда, которая будет выполнена для новой конфигурации или, например, после проверки существующей конфигурации в данном git-репозитории.

Команда  initбудет:

  • Загрузите и установите провайдеры / плагины Terraform
  • Инициализировать бэкэнд (если определен)
  • Скачать и установить модули (если определены)

Начиная с Terraform v0.11 +, вместо того, чтобы делать план, а затем применять его; если вы находитесь в интерактивном использовании, теперь вам просто нужно выполнить  terraform apply. Команда создаст план, предложит пользователю, и, если ответ «  Да» , Terraform применит план и внесет все изменения.

$ terraform apply
...
Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value:
  yes
...

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

Outputs:

cloudfront_id = 123456789

Наша инфраструктура создана, отлично. Но мы работаем в одиночку, только в одной среде.

Tfstate должен храниться в удаленном

В компании или в проекте OSS вы не работаете в одиночку, поэтому вам нужно прекратить локальное хранение tfstate и начать его удаленное хранение в «облаке», чтобы поделиться им.

Для информации или напоминания состояние tf — это снимок вашей инфраструктуры с момента последнего запуска   terraform apply. По умолчанию tfstate хранится локально в файле terraform.tfstate . Но когда мы работаем в команде, мы должны хранить tfstate удаленно:

Терраформ государство

Теперь мы создадим бэкэнд-ресурс, чтобы сохранить состояние tfstate в сегменте s3 и зашифровать его.

backend.tf:

# Backend configuration is loaded early so we can't use variables
terraform {
  backend "s3" {
    region = "eu-central-1"
    bucket = "com.scraly.terraform"
    key = "state.tfstate"
    encrypt = true    #AES-256 encryption
  }
}

Когда мы создали ресурсы корзины s3, в которые мы поместили наше tfstate, мы активировали управление версиями. Это не ошибка или копирование. Рекомендуется включить управление версиями для файлов состояния. У AWS S3 buckets есть такая возможность, которую вы можете использовать, поскольку у Terraform есть бэкэнд. Представьте себе, внезапно, ваш файл состояния был поврежден. Благодаря версии файлов состояния вы можете использовать старое состояние и дышать.

Мы создали до нашего файла tfstate, поэтому нам нужно преобразовать локальное состояние в удаленное (и сохранить наше состояние в созданной нами корзине s3):

$ terraform state push

Workspaces

Прежде чем использовать рабочие пространства Terraform, для работы с несколькими средами необходимо было создать одну папку для каждой учетной записи поставщика среды / облака и поместить в нее файлы .tf. Решение было не удобным, его легко можно было использовать с дублирующимися файлами .tf.

Начиная с Terraform v0.10 +, для управления несколькими различными наборами ресурсов / сред инфраструктуры, мы можем использовать рабочее пространство Terraform.

Интерфейс командной строки Terraform для рабочих пространств предлагает несколько команд:

$ terraform workspace list // The command will list all existing workspaces
$ terraform workspace new <workspace_name> // The command will create a workspace
$ terraform workspace select <workspace_name> // The command select a workspace
$ terraform workspace delete <workspace_name> // The command delete a workspace

С помощью CLI мы можем легко создавать, выбирать и перечислять рабочее пространство следующим образом:

Создать рабочие пространства:

$ terraform workspace new dev
Created and switched to workspace 'dev'
$ terraform workspace new preprod
Created and switched to workspace 'preprod'
$ terraform workspace new prod
Created and switched to workspace 'prod'

Выберите рабочую область разработчика:

$ terraform workspace select dev

Список рабочих областей:

$ terraform workspace list
default
* dev
preprod
prod

С этой конфигурацией рабочей области, когда   terraform apply успешно выполнено, ваше tfstate теперь будет сохранено в папке хорошей среды в корзине s3:

com.scraly.terraform
env:/
    dev/
       state.tfstate    
    preprod/
        state.tfstate    
    prod/
        state.tfstate

Идеально, потому что лучше всего разделять состояния в каждой среде.

Как вы можете видеть из предыдущих файлов .tf, мы можем вызвать переменную в зависимости от текущего рабочего пространства:

bucket = "${terraform.workspace == "preprod" ? var.bucket_demo_preprod : var.bucket_demo}"

Как вы видели, с рабочими пространствами Terraform мы легко управляем несколькими / несколькими средами без головной боли.