How to deploy aws infrastructure in multiple regions using terraform? - amazon-web-services

I want to deploy my infrastructure to multiple regions, right now I am doing this using provider-alias and thus a single state file is created.
main.tf
module "mumbai" {
source = "./site-to-site-vpn-setup"
providers = { aws = aws.mumbai }
vpc_cidr = local.vpc_cidr
cg_ip_address = lookup(local.region_map, "mumbai", "") == "" ? "" : lookup(local.region_map, "mumbai", "").cg_ip_address
}
module "seoul" {
source = "./site-to-site-vpn-setup"
providers = { aws = aws.seoul }
vpc_cidr = local.vpc_cidr
cg_ip_address = lookup(local.region_map, "seoul", "") == "" ? "" : lookup(local.region_map, "seoul", "").cg_ip_address
}
providers.tf
provider "aws" {
alias = "mumbai"
region = "ap-south-1"
}
provider "aws" {
alias = "seoul"
region = "ap-northeast-2"
}
Now, for example I want to increase the computes for all the regions but I want to first apply it to say 2 regions for testing. With the current structure if I do terraform apply then it is applied in all the regions. Now one solution can be to create different directories for different regions and thus different state files for each, Now how can this be optimized further or what other better approaches are there?

Related

Terraform loop through multiple providers(accounts) - invokation through module

i have a use case where need help to use for_each to loop through multiple providers( AWS accounts & regions) and this is a module, the TF will be using hub and spoke model.
below is the TF Pseudo code i would like to achieve.
module.tf
---------
app_accounts = [
{ "account" : "53xxxx08", "app_vpc_id" : "vpc-0fxxxxxfec8", "role" : "xxxxxxx", "profile" : "child1"},
{ "account" : "53xxxx08", "app_vpc_id" : "vpc-0fxxxxxfec8", "role" : "xxxxxxx", "profile" : "child2"}
]
below are the provider and resource files, pleas ignore the variables and output files, as its not relevant here
provider.tf
------------
provider "aws" {
for_each = var.app_accounts
alias = "child"
profile = each.value.role
}
here is the main resouce block where i want to multiple child accounts against single master account, so i want to iterate through the loop
resource "aws_route53_vpc_association_authorization" "master" {
provider = aws.master
vpc_id = vpc_id
zone_id = zone_id
}
resource "aws_route53_zone_association" "child" {
provider = aws.child
vpc_id = vpc_id
zone_id = zone_id
}
any idea on how to achieve this, please? thanks in advance.
The typical way to achieve your goal in Terraform is to define a shared module representing the objects that should be present in a single account and then to call that module once for each account, passing a different provider configuration into each.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
provider "aws" {
alias = "master"
# ...
}
provider "aws" {
alias = "example1"
profile = "example1"
}
module "example1" {
source = "./modules/account"
account = "53xxxx08"
app_vpc_id = "vpc-0fxxxxxfec8"
providers = {
aws = aws.example1
aws.master = aws.master
}
}
provider "aws" {
alias = "example2"
profile = "example2"
}
module "example2" {
source = "./modules/account"
account = "53xxxx08"
app_vpc_id = "vpc-0fxxxxxfec8"
providers = {
aws = aws.example2
aws.master = aws.master
}
}
The ./modules/account directory would then contain the resource blocks describing what should exist in each individual account. For example:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
configuration_aliases = [ aws, aws.master ]
}
}
}
variable "account" {
type = string
}
variable "app_vpc_id" {
type = string
}
resource "aws_route53_zone" "example" {
# (omitting the provider argument will associate
# with the default provider configuration, which
# is different for each instance of this module)
# ...
}
resource "aws_route53_vpc_association_authorization" "master" {
provider = aws.master
vpc_id = var.app_vpc_id
zone_id = aws_route53_zone.example.id
}
resource "aws_route53_zone_association" "child" {
provider = aws.master
vpc_id = var.app_vpc_id
zone_id = aws_route53_zone.example.id
}
(I'm not sure if you actually intended var.app_vpc_id to be the VPC specified for those zone associations, but my goal here is only to show the general pattern, not to show a fully-working example.)
Using a shared module in this way allows to avoid repeating the definitions for each account separately, and keeps each account-specific setting specified in only one place (either in a provider "aws" block or in a module block).
There is no way to make this more dynamic within the Terraform language itself, but if you expect to be adding and removing accounts regularly and want to make it more systematic then you could use code generation for the root module to mechanically produce the provider and module block for each account, to ensure that they all remain consistent and that you can update them all together in case you need to change the interface of the shared module in a way that will affect all of the calls.

Using Terraform Provider in aws module

I am going through the terraform documentation, and it seems unclear to me. I'm quite new to Terraform so no doubt i'm misunderstanding something here:
https://developer.hashicorp.com/terraform/language/modules/develop/providers
Problem:
My terraform pipeline is returning the following warning:
│
│ on waf-cdn.tf line 9, in module "waf_cdn":
│ 9: aws = aws.useastone
│
│ Module module.waf_cdn does not declare a provider named aws.
│ If you wish to specify a provider configuration for the module, add an entry for aws in the required_providers block within the module.
My root module is calling a child waf module. I understand that i need to configure my provider within my root module. There are 2 files within my root module:
...terraform.tf...
terraform {
backend "s3" {}
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 4.33.0"
}
random = {
source = "hashicorp/random"
version = "3.1.0"
}
local = {
source = "hashicorp/local"
version = "2.1.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = ">= 2.0.1"
}
}
}
...and providers.tf...
provider "aws" {
region = var.region
assume_role {
role_arn = "arn:aws:iam::${var.account_id}:role/${local.role_name}"
}
}
provider "aws" {
region = "us-east-1"
alias = "useastone"
assume_role {
role_arn = "arn:aws:iam::${var.account_id}:role/${local.role_name}"
}
}
provider "aws" {
region = var.region
alias = "master"
assume_role {
role_arn = replace(
"arn:aws:iam::${var.master_account_id}:role/${local.role_name}",
local.app_region,
"master"
)
}
}
When calling the child module, the SCOPE attribute of the waf needs to specify the region as us-east-1 for CLOUDFRONT as it is a global service in AWS. Therefore, i need to pass the useastone provider when calling the child waf module as seen below:
module "waf_cdn" {
source = "../modules/qa-aws-waf-common"
name = "${local.waf_prefix}-cdn"
logging_arn = aws_kinesis_firehose_delivery_stream.log_stream_cdn.arn
scope = "CLOUDFRONT"
tags = merge(module.tags.tags, { name = "${local.name_prefix}-qa-waf-cdn" })
providers = {
aws = aws.useastone
}
}
With this code i'm getting the error show above.
I'm banging my head against the documentation here so any help guys would be really appreciated.
Here's hoping, thanks!
As per the documentation you linked, here is the passage you are interested in [1]:
Additional provider configurations (those with the alias argument set) are never inherited automatically by child modules, and so must always be passed explicitly using the providers map.
Since that is the case, you need to define the provider(s) on the module level as well:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 4.33.0"
configuration_aliases = [ aws.useastone ]
}
}
}
That would probably be an additional providers.tf file in ../modules/qa-aws-waf-common.
[1] https://developer.hashicorp.com/terraform/language/modules/develop/providers#passing-providers-explicitly

I am trying to create a service account using terraform i have 3 accounts dev,qa, production. I want to give access to s3 bucket in each environment

I have this in my main.tf
module "service_accout_iam_role" {
for_each = { for sa in local.service_accounts : sa.name => sa }
source = "./service_account_iam_role"
environment = var.environment
eks_cluster_name = var.eks_cluster_name
account_id = var.account_id
region = var.region
service_account_name = each.value.name
namespace = each.value.namespace
policies = each.value.policies
}
And
locals {
service_accounts = [
{
name = "my-account"
namespace = "test123"
policies = [
{
name = "deleteS3"
resources = [
"arn:aws:s3:::my-dev-bucket",
"arn:aws:s3:::my-qa-bucket",
"arn:aws:s3:::my-Prod-bucket"
]
},
]
},
]
}
whenever i ran terraform apply in dev it shoould give permsiions in dev, when i ran qa it needs access to qa and same for production. How can i write a condition?
You should have three separate setups for your environments. This is most commonly done using workspaces. Otherwise, whenever you change your env, you will be just overwriting the settings of the old environment.

Deploying resource to multiple regions w/ TF 0.12/13

We have a rather complex environment where we have lots of AWS accounts, in multiple regions and these are all connected to a transit network via VPN tunnels.
At the moment we deploy Customer Gateways via a "VPC" module for each VPC in a region but the problem that we get is that deploying the first VPC is fine but subsequent VPC deploys cause issues with the fact that the CGW is already there and so we have to import it before we can continue which isn't an ideal place to be in and also I think there's a risk that if we tear down a VPC it might try to kill the CGW that is being used by other VPN's.
What I'm wanting to do is deploy the CGW's separately from the VPC and then the VPC does a data lookup for the CGW.
I've been thinking that perhaps we can use our "base" job to provision the CGW's that are defined in the variables file but nothing I've tried has worked so far.
The variable definition would be:
variable "region_data" {
type = list(object({
region = string
deploy_cgw = bool
gateways = any
}))
default = [
{
region = "eu-west-1"
deploy_cgw = true
gateways = [
{
name = "gateway1"
ip = "1.2.3.4"
},
{
name = "gateway2"
ip = "2.3.4.5"
}
]
},
{
region = "us-east-1"
deploy_cgw = true
gateways = [
{
name = "gateway1"
ip = "2.3.4.5"
},
{
name = "gateway2"
ip = "3.4.5.6"
}
]
}
]
}
I've tried a few things, like:
locals {
regions = [for region in var.region_data : region if region.deploy_cgw]
cgws = flatten([
for region in local.regions : [
for gateway in region.gateways : {
region = region.region
name = gateway.name
ip = gateway.ip
}
]
])
}
provider "aws" {
region = "eu-west-1"
alias = "eu-west-1"
}
provider "aws" {
region = "us-east-1"
alias = "us-east-1"
}
module "cgw" {
source = "../../../modules/customer-gateway"
for_each = { for cgw in local.cgws: "${cgw.region}.${cgw.name}" => cgw }
name_tag = each.value.name
ip_address = each.value.ip
providers = {
aws = "aws.${each.value.region}"
}
}
But with this I get:
Error: Invalid provider configuration reference
on main.tf line 439, in module "cgw":
439: aws = "aws.${each.value.region}"
A provider configuration reference must not be given in quotes.
If I move the AWS provider into the module and pass the region as a parameter, I get the following:
Error: Module does not support for_each
on main.tf line 423, in module "cgw":
423: for_each = { for cgw in local.testing : "${cgw.region}.${cgw.name}" => cgw }
Module "cgw" cannot be used with for_each because it contains a nested
provider configuration for "aws", at
I've done quite a bit of research and the last one I understand is something that Terraform take a tough stance on.
Is what I'm asking possible?
for_each can't be used on modules that have providers defined within them. I was disappointed to find this out too. They do this because having nested providers does cause nightmares if that provider goes away, then you have orphaned resources in the state that you can't manage and your plans will fail. It is, however, entirely possible in https://www.pulumi.com/. I'm sick of the limitations in terraform and will be moving to pulumi. But that's not what you asked so I'll move on.
Definitely don't keep importing it. You'll end up with multiple parts of your terraform managing the same resource.
Just create the cgw once per region. Then pass the id into your vpc module. You can't iterate over providers, so have one module per provider. In other words, for each over all vpcs in the same account and same region per module call.
resource "aws_customer_gateway" "east" {
bgp_asn = 65000
ip_address = "172.83.124.10"
type = "ipsec.1"
}
resource "aws_customer_gateway" "west" {
bgp_asn = 65000
ip_address = "172.83.128.10"
type = "ipsec.1"
}
module "east" {
source = "../../../modules/customer-gateway"
for_each = map(
{
name = "east1"
ip = "1.2.3.4"
},
{
name = "east2"
ip = "1.2.3.5"
},
)
name_tag = each.value.name
ip_address = each.value.ip
cgw_id = aws_customer_gateway.east.id
providers = {
aws = "aws.east"
}
}
module "west" {
source = "../../../modules/customer-gateway"
for_each = map(
{
name = "west1"
ip = "1.2.3.4"
},
{
name = "west2"
ip = "1.2.3.5"
},
)
name_tag = each.value.name
ip_address = each.value.ip
cgw_id = aws_customer_gateway.west.id
providers = {
aws = "aws.west"
}
}

can terraform be used simply to create resources in different AWS regions?

I have the following deploy.tf file:
provider "aws" {
region = "us-east-1"
}
provider "aws" {
alias = "us_west_1"
region = "us-west-2"
}
resource "aws_us_east_1" "my_test" {
# provider = "aws.us_east_1"
count = 1
ami = "ami-0820..."
instance_type = "t2.micro"
}
resource "aws_us_west_1" "my_test" {
provider = "aws.us_west_1"
count = 1
ami = "ami-0d74..."
instance_type = "t2.micro"
}
I am trying to use it to deploy 2 servers, one in each region. I keep getting errors like:
aws_us_east_1.narc_test: Provider doesn't support resource: aws_us_east_1
I have tried setting alias's for both provider blocks, and referring to the correct region in a number of different ways. I've read up on multi region support, and some answers suggest this can be accomplished with modules, however, this is a simple test, and I'd like to keep it simple. Is this currently possible?
Yes it can be used to create resources in different regions even inside just one file. There is no need to use modules for your test scenario.
Your error is caused by a typo probably. If you want to launch an ec2 instance the resource you wanna create is aws_instance and not aws_us_west_1 or aws_us_east_1.
Sure enough Terraform does not know this kind of resource since it does simply not exist. Change it to aws_instance and you should be good to go! Additionally you should probably name them differently to avoid double naming using my_test for both resources.
Step 1
Add region alias in the main.tf file where you gonna execute the terraform plan.
provider "aws" {
region = "eu-west-1"
alias = "main"
}
provider "aws" {
region = "us-east-1"
alias = "useast1"
}
Step 2
Add providers block inside your module definition block
module "lambda_edge_rule" {
providers = {
aws = aws.useast1
}
source = "../../../terraform_modules/lambda"
tags = var.tags
}
Step 3
Define "aws" as providers inside your module. ( source = ../../../terraform_modules/lambda")
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 2.7.0"
}
}
}
resource "aws_lambda_function" "lambda" {
function_name = "blablabla"
.
.
.
.
.
.
.
}
Note: Terraform version v1.0.5 as of now.