Data-источники: чтение существующего

Data-источник (data) — это окно в мир, которым Terraform не управляет: он читает уже существующие ресурсы и внешние данные, чтобы использовать их в своём конфиге.

Resource создаёт и владеет. Data только смотрит и не владеет. Если вы хотите подключиться к чужой VPC, найти свежий образ или прочитать аккаунт — это работа для data-источника.

Иногда нужно сослаться на то, что создано не вами: общая сеть от другой команды, актуальный AMI-образ от вендора, текущий регион. Создавать такие ресурсы вы не должны — но прочитать их атрибуты нужно. Для этого и существует data: это read-only запрос к API провайдера.

Resource против data

# data: ПРОЧИТАТЬ свежий образ Ubuntu
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]
  filter {
    name   = "name"
    values = ["ubuntu/images/*-22.04-*"]
  }
}

# resource: ИСПОЛЬЗОВАТЬ прочитанное
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id   # data.<type>.<name>.<attr>
  instance_type = "t2.micro"
}

Адрес data-источника начинается с data.: data.aws_ami.ubuntu.id. Терраформ выполняет запрос на этапе plan, подставляя результат в зависимые ресурсы. Если данные зависят от ещё не созданного ресурса, чтение откладывается до apply.

Как работает под капотом

Data-источник с фильтрами работает как запрос к базе: провайдер возвращает список, фильтры его сужают, а most_recent выбирает свежайший. Смоделируем выбор AMI:

all_amis = [
    {"id": "ami-001", "name": "ubuntu-22.04-2024-01", "owner": "099720109477"},
    {"id": "ami-002", "name": "ubuntu-22.04-2024-06", "owner": "099720109477"},
    {"id": "ami-003", "name": "debian-12-2024-06",    "owner": "136693071363"},
]

def query(images, owners, name_prefix, most_recent=True):
    found = [i for i in images
             if i["owner"] in owners and i["name"].startswith(name_prefix)]
    if not found:
        raise ValueError("data.aws_ami: ничего не найдено!")
    if most_recent:
        found.sort(key=lambda i: i["name"], reverse=True)
    return found[0]

ami = query(all_amis, ["099720109477"], "ubuntu-22.04")
print("Выбран AMI:", ami["id"], "-", ami["name"])

«Попробуй сам ▶» — фильтр по владельцу и префиксу + сортировка по дате дают свежий образ. Если ничего не нашлось — ошибка (частая боль на практике).

Частые ошибки

  • Путать data и resource. data ничего не создаёт; если написать data "aws_instance", ожидая создания сервера, — сервера не будет.
  • Фильтр без результата. Если data ничего не нашёл, apply падает. Особенно коварно для образов, которые удаляют со временем.
  • Несколько совпадений без most_recent. Если фильтр вернул много объектов, Terraform не знает, какой брать, и выдаёт ошибку.

Best practices

  • Используйте data для всего, чем вы не владеете: чужие сети, общие образы, текущий аккаунт/регион.
  • Делайте фильтры точными (owners + конкретный префикс), чтобы не поймать чужой объект.
  • Для образов, меняющихся со временем, фиксируйте конкретную версию там, где важна воспроизводимость.

Разбор глубже

Момент, когда data-источник читает данные, важнее, чем кажется. Если все его аргументы известны заранее, Terraform выполняет запрос на этапе plan — и результат сразу виден в плане. Но если data-источник зависит от атрибута ресурса, который ещё не создан, чтение откладывается до apply, и в плане на его месте будет (known after apply). Это объясняет загадочные ситуации, когда план не может показать финальные значения: он просто ещё не знает результата отложенного чтения. Понимание этой механики снимает массу вопросов на практике.

Особый и очень частый data-источник — это «информация о себе»: aws_caller_identity отдаёт ID текущего аккаунта, aws_region — текущий регион, aws_availability_zones — список доступных зон. Их используют, чтобы не хардкодить такие значения, а вычислять динамически: код, написанный через эти data-источники, автоматически адаптируется к тому аккаунту и региону, в котором его запускают. Это делает один и тот же модуль по-настоящему переносимым между средами без правок.

Стоит выработать привычку делать фильтры data-источников максимально строгими. Слишком широкий фильтр опасен вдвойне: он может вернуть несколько объектов (и Terraform упадёт с требованием уточнить), а может молча подцепить чужой ресурс — например, образ другой команды с похожим именем. Указание точного владельца, конкретного префикса и, где уместно, тегов превращает запрос из «найди что-нибудь похожее» в «найди ровно то, что я имею в виду». А для критичных к воспроизводимости мест свежий образ через most_recent иногда сознательно заменяют на жёстко зафиксированный ID, чтобы план был стабилен от запуска к запуску.

Итог: data — read-only мост к существующей инфраструктуре и внешним данным; он читает, но не владеет. Дальше — центральная тема Terraform: state.

Проверьте себя
1. Чем data-источник отличается от resource?
Adata быстрее создаёт ресурсы
Bdata только читает существующее и ничем не владеет, resource создаёт и управляет
CРазницы нет
Ddata работает только в модулях
2. Что произойдёт, если фильтр data-источника ничего не найдёт?
ATerraform создаст ресурс сам
Bapply упадёт с ошибкой
CВернётся null без ошибки
DTerraform возьмёт первый попавшийся