Terraform: Is there a way to reference resources created from a different stack?

Created on 31 Jul 2016  ยท  4Comments  ยท  Source: hashicorp/terraform

Hi,

I'm leveraging AWS ECS with Terraform. I've divided the stacks into a common set of ECS resources (ECS Cluster, ASG, Launch Config, IAM Role + Policy, SG) and application specific resources (ECS Service, ELB, ECS Task, IAM Role + Policy). The reason behind this is so that I can share the ECS Cluster across multiple applications for multi-tenancy.

The different stacks have been created in different Git repos. However the ECS Service needs a reference to the ECS Cluster. What is the recommended way to reference a resource in a different stack?

question

Most helpful comment

The general idea here is to have one of the configurations ("stack" is not usual Terraform terminology) write out information about what it has created into some data store that the other configuration is able to access, using paired resource types and data sources.

For example:

  • You can use platform-specific tagging features or naming conventions to identify resources so that they can be discovered by other configurations. Unfortunately support for this tends to vary by resource due to limitations of the underlying APIs, but for example if you use a predictable tagging scheme on your security groups from the "producer" configuration then a "consumer" configuration can find them using the tags filter argument on the aws_security_group data source by relying on that tagging scheme.
  • If you have HashiCorp Consul in your environment, the "producer" configuration can use consul_key_prefix to publish information to a prefix in Consul's key/value store and the the "consumer" configuration can use the consul_keys data source to retrieve that information.
  • You can similarly use Amazon S3 for this, using the aws_s3_bucket_object resource type to write and the corresponding data source to read. (This'll get easier in the forthcoming 0.12 release because it'll have both jsonencode and jsondecode functions so you can easily bundle several items into a single object, since S3 isn't really optimized for storing and retrieving lots of tiny objects.)
  • You can use DNS to pass information. Terraform supports writing records into many different DNS hosting providers, including AWS Route53, and the dns provider can look up information from DNS in a hosting-provider-agnostic way.

An easy option to get started, if you have your Terraform states published in a common shared location that can be accessed by the credentials running the consumer configuration, is to use terraform_remote_state to read the outputs directly out of the producing configuration's state. This does generally require making states globally readable (or, at least, managing some more complex permissions between them) but avoids the need to introduce an additional data store into the mix.

If you have many more consumers than producers (the common case, I think) then I'd prefer to hide the details of how this data is _retrieved_ in a re-usable module that each consumer can call, and then if you change your mind about how this information is distributed in future you can just update that module and have the various consumers immediately read from the new locations:

module "shared_infra" {
  # This module doesn't _create_ the shared infrastructure, but rather just uses
  # data sources to _find_ that infrastructure, returning the ids it discovered.
  source = "..." # whatever makes sense in your environment

  # Pass any arguments you need to distinguish between distinct deployment
  # environments here. This is just a placeholder, assuming that there's some
  # way to map from this name to a suitable data store for the settings.
  environment = "production"
}

resource "aws_ecs_service" "example" {
  cluster_id = "${module.shared_infra.ecs_cluster_id}"
  # ...
}

I wrote some more words about how I employed this pattern at a previous employer (using Consul as the data store, for example) in an article on my blog, with some other parts of what I discussed here on the "bonus patterns" article in that series.

All 4 comments

Hi @andrewoh531

Thanks for the question here. Hopefully, I can help :)

Right now, I tend to use modules for everything. Think of modules as re-usable terraform scripts. You have a module package that has all the code in it (for example ecs service) and you pass all the variables in

This means for something like a dev environment you would have the following:

module "vpc" {
  source = "../modules/vpc"

  name = "dev"

  cidr            = "10.10.0.0/16"
  private_subnets = ["10.10.160.0/19"]
  public_subnets  = ["10.10.0.0/21"]

  availability_zones = ["${data.aws_availability_zones.zones.names}"]
}

I need to use the outputs of the VPC in ECS Cluster so i add the ECS Cluster module to my environment as follows:

module "ecs_cluster" {
  source = "../modules/ecs_cluster"

  env_prefix = "dev"

  ami_id           = "${var.ecs_ami}"
  cluster_name     = "dev_cluster"
  desired_capacity = "3"
  max_size         = "5"
  instance_type    = "t2.large"

  vpc_id     = "${module.vpc.vpc_id}"
  subnet_ids = ["${module.vpc.private_subnets}"]
}

As you can see, I am passing in vpc_id and subnet_ids from the VPC module. Terraform is clever enough to understand this in the graph and will create the resources in the VPC module before it creates the modules in the ECS cluster module

Hope this sort of helps - I can suggest reading https://medium.com/@petey5000/petes-terraform-tips-694a3c4c5169 as some good tips for real world terraform usage :)

Paul

Yeha, but that way for that module it will create a new resource. He's talking about two different resources created with two different "terraform apply" commands

The general idea here is to have one of the configurations ("stack" is not usual Terraform terminology) write out information about what it has created into some data store that the other configuration is able to access, using paired resource types and data sources.

For example:

  • You can use platform-specific tagging features or naming conventions to identify resources so that they can be discovered by other configurations. Unfortunately support for this tends to vary by resource due to limitations of the underlying APIs, but for example if you use a predictable tagging scheme on your security groups from the "producer" configuration then a "consumer" configuration can find them using the tags filter argument on the aws_security_group data source by relying on that tagging scheme.
  • If you have HashiCorp Consul in your environment, the "producer" configuration can use consul_key_prefix to publish information to a prefix in Consul's key/value store and the the "consumer" configuration can use the consul_keys data source to retrieve that information.
  • You can similarly use Amazon S3 for this, using the aws_s3_bucket_object resource type to write and the corresponding data source to read. (This'll get easier in the forthcoming 0.12 release because it'll have both jsonencode and jsondecode functions so you can easily bundle several items into a single object, since S3 isn't really optimized for storing and retrieving lots of tiny objects.)
  • You can use DNS to pass information. Terraform supports writing records into many different DNS hosting providers, including AWS Route53, and the dns provider can look up information from DNS in a hosting-provider-agnostic way.

An easy option to get started, if you have your Terraform states published in a common shared location that can be accessed by the credentials running the consumer configuration, is to use terraform_remote_state to read the outputs directly out of the producing configuration's state. This does generally require making states globally readable (or, at least, managing some more complex permissions between them) but avoids the need to introduce an additional data store into the mix.

If you have many more consumers than producers (the common case, I think) then I'd prefer to hide the details of how this data is _retrieved_ in a re-usable module that each consumer can call, and then if you change your mind about how this information is distributed in future you can just update that module and have the various consumers immediately read from the new locations:

module "shared_infra" {
  # This module doesn't _create_ the shared infrastructure, but rather just uses
  # data sources to _find_ that infrastructure, returning the ids it discovered.
  source = "..." # whatever makes sense in your environment

  # Pass any arguments you need to distinguish between distinct deployment
  # environments here. This is just a placeholder, assuming that there's some
  # way to map from this name to a suitable data store for the settings.
  environment = "production"
}

resource "aws_ecs_service" "example" {
  cluster_id = "${module.shared_infra.ecs_cluster_id}"
  # ...
}

I wrote some more words about how I employed this pattern at a previous employer (using Consul as the data store, for example) in an article on my blog, with some other parts of what I discussed here on the "bonus patterns" article in that series.

I'm going to lock this issue because it has been closed for _30 days_ โณ. This helps our maintainers find and focus on the active issues.

If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

Was this page helpful?
0 / 5 - 0 ratings