Terraform: инфраструктура на уровне кода — страница 13 из 65

git add .gitignore

git commit -m "Add a .gitignore file"

Чтобы поделиться этим кодом со своими коллегами, вам необходимо создать общий Git-репозиторий, к которому вы все можете обращаться. Для этого можно использовать GitHub. Перейдите на сайт https://github.com, зарегистрируйтесь (если вы этого еще не сделали), создайте новый репозиторий и укажите его в качестве удаленной точки входа под названием origin для своего локального Git-репозитория:

git remote add origin git@github.com:/.git

Теперь, когда вы хотите поделиться своими изменениями с коллегами, можете загрузить изменения в репозиторий origin:

git push origin master

Если вы хотите посмотреть, какие изменения внесли ваши коллеги, можете загрузить изменения из репозитория origin:

git pull origin master

При дальнейшем чтении этой книги и в целом при применении Terraform не забывайте регулярно фиксировать (gitcommit) и загружать (gitpush) свои изменения. Это позволит работать над кодом совместно с членами вашей команды. К тому же все изменения вашей инфраструктуры будут записываться в журнал фиксаций, что придется очень кстати в случае отладки. Больше о командной работе с Terraform вы узнаете в главе 8.


Развертывание одного веб-сервера

Следующим шагом будет развертывание на этом инстансе веб-сервера. Мы попытаемся развернуть простейшую веб-архитектуру: один веб-сервер, способный отвечать на HTTP-запросы (рис. 2.6).

В реальных условиях для создания веб-сервера использовался бы веб-фреймворк наподобие Ruby on Rails или Django. Чтобы не усложнять этот пример, мы запустим простейший веб-сервер, который всегда возвращает текст Hello,World29:

#!/bin/bash

echo "Hello, World" > index.html

nohup busybox httpd -f -p 8080 &

Рис. 2.6. Начнем с простой архитектуры: одного веб-сервера, запущенного в AWS, который отвечает на HTTP-запросы

Это bash-скрипт, записывающий текст Hello, World в файл index.html, который затем раздается на порте 8080 с помощью веб-сервера, запущенного с использованием инструмента под названием busybox (https://busybox.net/) (в Ubuntu установлен по умолчанию). Я указал команду busybox между nohup и &, чтобы веб-сервер постоянно работал в фоне, даже после завершения bash-скрипта.


Номера портов

В этом примере вместо стандартного HTTP-порта 80 используется 8080. Дело в том, что все порты меньше 1024 требуют администраторских привилегий. Это потенциальный риск безопасности, поскольку любой злоумышленник, которому удастся взломать ваш сервер, тоже получит привилегии администратора.

В связи с этим веб-сервер рекомендуется запускать от имени обычного пользователя с ограниченными правами доступа. Это означает, что вам следует прослушивать порты с более высокими номерами, но, как вы увидите позже в этой главе, вы можете настроить балансировщик нагрузки так, чтобы он работал на порте 80 и перенаправлял трафик на более высокие порты ваших серверов.

Как запустить этот скрипт на сервере EC2? Как уже упоминалось в подразделе «Средства шаблонизации серверов» на с. 31, для этого обычно применяется инструмент вроде Packer, который создает пользовательский образ AMI с предустановленным веб-сервером. Поскольку в этом примере в качестве веб-сервера используется однострочный скрипт на основе busybox, вы можете обойтись стандартным образом Ubuntu 18.04 и запустить скрипт Hello, World в рамках конфигурации пользовательских данных вашего сервера EC2. Вы можете передать параметру user_data в коде Terraform то, что будет выполнено при загрузке сервера: либо скрипт командной оболочки, либо директиву cloud-init. Вариант со скриптом показан ниже:

resource "aws_instance" "example" {

  ami                   = "ami-0c55b159cbfafe1f0"

  instance_type         = "t2.micro"

  user_data = <<-EOF

              #!/bin/bash

              echo "Hello, World" > index.html

              nohup busybox httpd -f -p 8080 &

              EOF

  tags = {

    Name = "terraform-example"

  }

}

<<-EOF и EOF — элементы синтаксиса heredoc, который позволяет создавать многострочные строковые литералы без использования множества символов перехода на новую строку.

Перед запуском этого веб-сервера нужно сделать еще кое-что. AWS по умолчанию закрывает для сервера EC2 весь входящий и исходящий трафик. Чтобы ваш сервер мог принимать запросы на порте 8080, необходимо создать группу безопасности:

resource "aws_security_group" "instance" {

  name = "terraform-example-instance"

  ingress {

    from_port   = 8080

    to_port     = 8080

    protocol    = "tcp"

    cidr_blocks = ["0.0.0.0/0"]

  }

}

Этот код создает новый ресурс под названием aws_security_group (обратите внимание, что все ресурсы в AWS начинаются с aws_) и делает так, чтобы эта группа разрешала принимать на порте 8080 TCP-запросы из блока CIDR 0.0.0.0/0. Блок CIDR — это краткая запись диапазона IP-адресов. Например, блок CIDR 10.0.0.0/24 представляет все IP-адреса между 10.0.0.0 и 10.0.0.255. Блок CIDR 0.0.0.0/0 охватывает диапазон всех возможных IP-адресов, поэтому данная группа безопасности разрешает принимать на порте 8080 запросы с любого IP30.

Создания группы безопасности как таковой будет недостаточно. Нужно сделать так, чтобы сервер EC2 ее использовал. Для этого вы должны передать ее идентификатор аргументу vpc_security_group_ids ресурса aws_instance. Но сначала необходимо познакомиться с выражениями Terraform.

В Terraform выражением является все, что возвращает значение. Вы уже видели простейший тип выражений — литералы, такие как строки (например, "ami-0c55b159cbfafe1f0") и числа (скажем, 5). Terraform поддерживает много других разновидностей выражений, которые будут встречаться на страницах этой книги.

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

_..

PROVIDER — это имя провайдера (например, aws), TYPE — это тип ресурса (вроде security_group), NAME — имя этого ресурса (в нашем случае группа безопасности называется "instance"), а ATTRIBUTE — это либо один из аргументов ресурса (скажем, name), либо один из атрибутов, которые он экспортировал (список доступных атрибутов можно найти в документации каждого ресурса). Группа безопасности экспортирует атрибут под названием id, поэтому к нему можно обратиться с помощью такого выражения:

aws_security_group.instance.id

Вы можете использовать идентификатор этой группы безопасности в аргументе vpc_security_group_ids ресурса aws_instance:

resource "aws_instance" "example" {

  ami                    = "ami-0c55b159cbfafe1f0"

  instance_type          = "t2.micro"

  vpc_security_group_ids = [aws_security_group.instance.id]

  user_data = <<-EOF

              #!/bin/bash

              echo "Hello, World" > index.html

              nohup busybox httpd -f -p 8080 &

              EOF

  tags = {

    Name = "terraform-example"

  }

}

Ссылаясь в одном ресурсе на другой, вы создаете неявную зависимость. Terraform анализирует такие зависимости, строит из них граф и применяет его для автоматического определения порядка, в котором должны создаваться ресурсы. ­Например, если бы этот код развертывался с нуля, система Terraform знала бы о том, что группу безопасности нужно создать раньше, чем сервер EC2, поскольку последний использует ID этой группы. Вы можете даже вывести граф зависимостей с помощью команды graph:

$ terraform graph

digraph {

        compound = "true"

        newrank = "true"

        subgraph "root" {

                "[root] aws_instance.example"

                  [label = "aws_instance.example", shape = "box"]

                "[root] aws_security_group.instance"

                  [label = "aws_security_group.instance", shape = "box"]

                "[root] provider.aws"

                  [label = "provider.aws", shape = "diamond"]

                "[root] aws_instance.example" ->

                  "[root] aws_security_group.instance"

                "[root] aws_security_group.instance" ->

                  "[root] provider.aws"

                "[root] meta.count-boundary (EachMode fixup)" ->

                  "[root] aws_instance.example"

                "[root] provider.aws (close)" ->

                  "[root] aws_instance.example"

                "[root] root" ->

                  "[root] meta.count-boundary (EachMode fixup)"

                "[root] root" ->

                  "[root] provider.aws (close)"

        }

}

Вывод выполнен на языке описания графов под названием DOT. Сам граф можно отобразить, как это сделано на рис. 2.7, с использованием настольного приложенияGraphviz или его веб-версии GraphvizOnline (bit.ly/2mPbxmg).

При прохождении по дереву зависимостей Terraform пытается как можно сильнее распараллелить создание ресурсов, что приводит к довольно эффективному применению изменений. В этом прелесть декларативного языка: вы просто описываете то, что вам нужно, а Terraform определяет наиболее эффективный способ реализации.

Рис. 2.7. Граф зависимостей для сервера EC2 и его группы безопасности

Выполнив команду apply, вы увидите, что Terraform хочет создать группу безопасности и заменить имеющийся сервер EC2 другим — с новыми пользовательскими данными:

$ terraform apply

(...)

Terraform will perform the following actions:

  # aws_instance.example must be replaced

-/+ resource "aws_instance" "example" {