mirror of
https://github.com/karpathy/nanochat.git
synced 2026-05-08 00:39:50 +00:00
Merge pull request #13 from manmohan659/feat/terraform-infra
feat(terraform): provision full AWS stack for samosaChaat
This commit is contained in:
commit
2be82fe731
12
.gitignore
vendored
12
.gitignore
vendored
|
|
@ -20,3 +20,15 @@ models/
|
|||
CLAUDE.md
|
||||
wandb/
|
||||
onnx_export/
|
||||
|
||||
# Terraform
|
||||
**/.terraform/*
|
||||
*.tfstate
|
||||
*.tfstate.*
|
||||
*.tfvars
|
||||
crash.log
|
||||
crash.*.log
|
||||
override.tf
|
||||
override.tf.json
|
||||
*_override.tf
|
||||
*_override.tf.json
|
||||
|
|
|
|||
35
terraform/backend.tf
Normal file
35
terraform/backend.tf
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# Shared remote-state configuration.
|
||||
#
|
||||
# Each environment overrides the `key` via `terraform init -backend-config="key=..."`
|
||||
# (the harness in environments/<env>/main.tf passes `terraform { backend "s3" {} }`
|
||||
# with no inline values so the same bucket can host multiple state files).
|
||||
#
|
||||
# Bootstrap (run once, manually):
|
||||
#
|
||||
# aws s3api create-bucket \
|
||||
# --bucket samosachaat-terraform-state \
|
||||
# --region us-west-2 \
|
||||
# --create-bucket-configuration LocationConstraint=us-west-2
|
||||
# aws s3api put-bucket-versioning \
|
||||
# --bucket samosachaat-terraform-state \
|
||||
# --versioning-configuration Status=Enabled
|
||||
# aws s3api put-bucket-encryption \
|
||||
# --bucket samosachaat-terraform-state \
|
||||
# --server-side-encryption-configuration \
|
||||
# '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'
|
||||
# aws dynamodb create-table \
|
||||
# --table-name samosachaat-terraform-locks \
|
||||
# --attribute-definitions AttributeName=LockID,AttributeType=S \
|
||||
# --key-schema AttributeName=LockID,KeyType=HASH \
|
||||
# --billing-mode PAY_PER_REQUEST \
|
||||
# --region us-west-2
|
||||
|
||||
terraform {
|
||||
backend "s3" {
|
||||
bucket = "samosachaat-terraform-state"
|
||||
key = "global/placeholder.tfstate"
|
||||
region = "us-west-2"
|
||||
encrypt = true
|
||||
dynamodb_table = "samosachaat-terraform-locks"
|
||||
}
|
||||
}
|
||||
125
terraform/environments/dev/.terraform.lock.hcl
Normal file
125
terraform/environments/dev/.terraform.lock.hcl
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/hashicorp/aws" {
|
||||
version = "5.100.0"
|
||||
constraints = ">= 4.33.0, >= 5.0.0, >= 5.79.0, >= 5.92.0, >= 5.95.0, < 6.0.0"
|
||||
hashes = [
|
||||
"h1:Ijt7pOlB7Tr7maGQIqtsLFbl7pSMIj06TVdkoSBcYOw=",
|
||||
"zh:054b8dd49f0549c9a7cc27d159e45327b7b65cf404da5e5a20da154b90b8a644",
|
||||
"zh:0b97bf8d5e03d15d83cc40b0530a1f84b459354939ba6f135a0086c20ebbe6b2",
|
||||
"zh:1589a2266af699cbd5d80737a0fe02e54ec9cf2ca54e7e00ac51c7359056f274",
|
||||
"zh:6330766f1d85f01ae6ea90d1b214b8b74cc8c1badc4696b165b36ddd4cc15f7b",
|
||||
"zh:7c8c2e30d8e55291b86fcb64bdf6c25489d538688545eb48fd74ad622e5d3862",
|
||||
"zh:99b1003bd9bd32ee323544da897148f46a527f622dc3971af63ea3e251596342",
|
||||
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
|
||||
"zh:9f8b909d3ec50ade83c8062290378b1ec553edef6a447c56dadc01a99f4eaa93",
|
||||
"zh:aaef921ff9aabaf8b1869a86d692ebd24fbd4e12c21205034bb679b9caf883a2",
|
||||
"zh:ac882313207aba00dd5a76dbd572a0ddc818bb9cbf5c9d61b28fe30efaec951e",
|
||||
"zh:bb64e8aff37becab373a1a0cc1080990785304141af42ed6aa3dd4913b000421",
|
||||
"zh:dfe495f6621df5540d9c92ad40b8067376350b005c637ea6efac5dc15028add4",
|
||||
"zh:f0ddf0eaf052766cfe09dea8200a946519f653c384ab4336e2a4a64fdd6310e9",
|
||||
"zh:f1b7e684f4c7ae1eed272b6de7d2049bb87a0275cb04dbb7cda6636f600699c9",
|
||||
"zh:ff461571e3f233699bf690db319dfe46aec75e58726636a0d97dd9ac6e32fb70",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/cloudinit" {
|
||||
version = "2.3.7"
|
||||
constraints = ">= 2.0.0"
|
||||
hashes = [
|
||||
"h1:M9TpQxKAE/hyOwytdX9MUNZw30HoD/OXqYIug5fkqH8=",
|
||||
"zh:06f1c54e919425c3139f8aeb8fcf9bceca7e560d48c9f0c1e3bb0a8ad9d9da1e",
|
||||
"zh:0e1e4cf6fd98b019e764c28586a386dc136129fef50af8c7165a067e7e4a31d5",
|
||||
"zh:1871f4337c7c57287d4d67396f633d224b8938708b772abfc664d1f80bd67edd",
|
||||
"zh:2b9269d91b742a71b2248439d5e9824f0447e6d261bfb86a8a88528609b136d1",
|
||||
"zh:3d8ae039af21426072c66d6a59a467d51f2d9189b8198616888c1b7fc42addc7",
|
||||
"zh:3ef4e2db5bcf3e2d915921adced43929214e0946a6fb11793085d9a48995ae01",
|
||||
"zh:42ae54381147437c83cbb8790cc68935d71b6357728a154109d3220b1beb4dc9",
|
||||
"zh:4496b362605ae4cbc9ef7995d102351e2fe311897586ffc7a4a262ccca0c782a",
|
||||
"zh:652a2401257a12706d32842f66dac05a735693abcb3e6517d6b5e2573729ba13",
|
||||
"zh:7406c30806f5979eaed5f50c548eced2ea18ea121e01801d2f0d4d87a04f6a14",
|
||||
"zh:7848429fd5a5bcf35f6fee8487df0fb64b09ec071330f3ff240c0343fe2a5224",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/null" {
|
||||
version = "3.2.4"
|
||||
constraints = ">= 3.0.0"
|
||||
hashes = [
|
||||
"h1:L5V05xwp/Gto1leRryuesxjMfgZwjb7oool4WS1UEFQ=",
|
||||
"zh:59f6b52ab4ff35739647f9509ee6d93d7c032985d9f8c6237d1f8a59471bbbe2",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
"zh:795c897119ff082133150121d39ff26cb5f89a730a2c8c26f3a9c1abf81a9c43",
|
||||
"zh:7b9c7b16f118fbc2b05a983817b8ce2f86df125857966ad356353baf4bff5c0a",
|
||||
"zh:85e33ab43e0e1726e5f97a874b8e24820b6565ff8076523cc2922ba671492991",
|
||||
"zh:9d32ac3619cfc93eb3c4f423492a8e0f79db05fec58e449dee9b2d5873d5f69f",
|
||||
"zh:9e15c3c9dd8e0d1e3731841d44c34571b6c97f5b95e8296a45318b94e5287a6e",
|
||||
"zh:b4c2ab35d1b7696c30b64bf2c0f3a62329107bd1a9121ce70683dec58af19615",
|
||||
"zh:c43723e8cc65bcdf5e0c92581dcbbdcbdcf18b8d2037406a5f2033b1e22de442",
|
||||
"zh:ceb5495d9c31bfb299d246ab333f08c7fb0d67a4f82681fbf47f2a21c3e11ab5",
|
||||
"zh:e171026b3659305c558d9804062762d168f50ba02b88b231d20ec99578a6233f",
|
||||
"zh:ed0fe2acdb61330b01841fa790be00ec6beaac91d41f311fb8254f74eb6a711f",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/random" {
|
||||
version = "3.8.1"
|
||||
constraints = ">= 3.1.0, >= 3.5.0"
|
||||
hashes = [
|
||||
"h1:u8AKlWVDTH5r9YLSeswoVEjiY72Rt4/ch7U+61ZDkiQ=",
|
||||
"zh:08dd03b918c7b55713026037c5400c48af5b9f468f483463321bd18e17b907b4",
|
||||
"zh:0eee654a5542dc1d41920bbf2419032d6f0d5625b03bd81339e5b33394a3e0ae",
|
||||
"zh:229665ddf060aa0ed315597908483eee5b818a17d09b6417a0f52fd9405c4f57",
|
||||
"zh:2469d2e48f28076254a2a3fc327f184914566d9e40c5780b8d96ebf7205f8bc0",
|
||||
"zh:37d7eb334d9561f335e748280f5535a384a88675af9a9eac439d4cfd663bcb66",
|
||||
"zh:741101426a2f2c52dee37122f0f4a2f2d6af6d852cb1db634480a86398fa3511",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
"zh:a902473f08ef8df62cfe6116bd6c157070a93f66622384300de235a533e9d4a9",
|
||||
"zh:b85c511a23e57a2147355932b3b6dce2a11e856b941165793a0c3d7578d94d05",
|
||||
"zh:c5172226d18eaac95b1daac80172287b69d4ce32750c82ad77fa0768be4ea4b8",
|
||||
"zh:dab4434dba34aad569b0bc243c2d3f3ff86dd7740def373f2a49816bd2ff819b",
|
||||
"zh:f49fd62aa8c5525a5c17abd51e27ca5e213881d58882fd42fec4a545b53c9699",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/time" {
|
||||
version = "0.13.1"
|
||||
constraints = ">= 0.9.0"
|
||||
hashes = [
|
||||
"h1:ZT5ppCNIModqk3iOkVt5my8b8yBHmDpl663JtXAIRqM=",
|
||||
"zh:02cb9aab1002f0f2a94a4f85acec8893297dc75915f7404c165983f720a54b74",
|
||||
"zh:04429b2b31a492d19e5ecf999b116d396dac0b24bba0d0fb19ecaefe193fdb8f",
|
||||
"zh:26f8e51bb7c275c404ba6028c1b530312066009194db721a8427a7bc5cdbc83a",
|
||||
"zh:772ff8dbdbef968651ab3ae76d04afd355c32f8a868d03244db3f8496e462690",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
"zh:898db5d2b6bd6ca5457dccb52eedbc7c5b1a71e4a4658381bcbb38cedbbda328",
|
||||
"zh:8de913bf09a3fa7bedc29fec18c47c571d0c7a3d0644322c46f3aa648cf30cd8",
|
||||
"zh:9402102c86a87bdfe7e501ffbb9c685c32bbcefcfcf897fd7d53df414c36877b",
|
||||
"zh:b18b9bb1726bb8cfbefc0a29cf3657c82578001f514bcf4c079839b6776c47f0",
|
||||
"zh:b9d31fdc4faecb909d7c5ce41d2479dd0536862a963df434be4b16e8e4edc94d",
|
||||
"zh:c951e9f39cca3446c060bd63933ebb89cedde9523904813973fbc3d11863ba75",
|
||||
"zh:e5b773c0d07e962291be0e9b413c7a22c044b8c7b58c76e8aa91d1659990dfb5",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/tls" {
|
||||
version = "4.2.1"
|
||||
constraints = ">= 3.0.0, >= 4.0.0"
|
||||
hashes = [
|
||||
"h1:akFNuHwvrtnYMBofieoeXhPJDhYZzJVu/Q/BgZK2fgg=",
|
||||
"zh:0d1e7d07ac973b97fa228f46596c800de830820506ee145626f079dd6bbf8d8a",
|
||||
"zh:5c7e3d4348cb4861ab812973ef493814a4b224bdd3e9d534a7c8a7c992382b86",
|
||||
"zh:7c6d4a86cd7a4e9c1025c6b3a3a6a45dea202af85d870cddbab455fb1bd568ad",
|
||||
"zh:7d0864755ba093664c4b2c07c045d3f5e3d7c799dda1a3ef33d17ed1ac563191",
|
||||
"zh:83734f57950ab67c0d6a87babdb3f13c908cbe0a48949333f489698532e1391b",
|
||||
"zh:951e3c285218ebca0cf20eaa4265020b4ef042fea9c6ade115ad1558cfe459e5",
|
||||
"zh:b9543955b4297e1d93b85900854891c0e645d936d8285a190030475379c5c635",
|
||||
"zh:bb1bd9e86c003d08c30c1b00d44118ed5bbbf6b1d2d6f7eaac4fa5c6ebea5933",
|
||||
"zh:c9477bfe00653629cd77ddac3968475f7ad93ac3ca8bc45b56d1d9efb25e4a6e",
|
||||
"zh:d4cfda8687f736d0cba664c22ec49dae1188289e214ef57f5afe6a7217854fed",
|
||||
"zh:dc77ee066cf96532a48f0578c35b1eaf6dc4d8ddd0e3ae8e029a3b10676dd5d3",
|
||||
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
|
||||
]
|
||||
}
|
||||
107
terraform/environments/dev/main.tf
Normal file
107
terraform/environments/dev/main.tf
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
locals {
|
||||
name_prefix = "samosachaat-${var.environment}"
|
||||
cluster_name = "${local.name_prefix}-eks"
|
||||
|
||||
tags = {
|
||||
Project = "samosachaat"
|
||||
Environment = var.environment
|
||||
}
|
||||
}
|
||||
|
||||
module "vpc" {
|
||||
source = "../../modules/vpc"
|
||||
|
||||
name = local.name_prefix
|
||||
cluster_name = local.cluster_name
|
||||
cidr = "10.0.0.0/16"
|
||||
azs = ["us-west-2a", "us-west-2b", "us-west-2c"]
|
||||
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
|
||||
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
|
||||
single_nat_gateway = true
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "eks" {
|
||||
source = "../../modules/eks"
|
||||
|
||||
cluster_name = local.cluster_name
|
||||
cluster_version = "1.29"
|
||||
vpc_id = module.vpc.vpc_id
|
||||
private_subnet_ids = module.vpc.private_subnet_ids
|
||||
|
||||
node_instance_type = "t3.large"
|
||||
node_min_size = 2
|
||||
node_max_size = 4
|
||||
node_desired_size = 2
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "ecr" {
|
||||
source = "../../modules/ecr"
|
||||
|
||||
force_delete = true
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "iam" {
|
||||
source = "../../modules/iam"
|
||||
|
||||
name_prefix = local.name_prefix
|
||||
oidc_provider_arn = module.eks.oidc_provider_arn
|
||||
oidc_provider_url = module.eks.oidc_provider_url
|
||||
create_github_oidc = true
|
||||
github_repositories = var.github_repositories
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "rds" {
|
||||
source = "../../modules/rds"
|
||||
|
||||
identifier = "${local.name_prefix}-pg"
|
||||
vpc_id = module.vpc.vpc_id
|
||||
private_subnet_ids = module.vpc.private_subnet_ids
|
||||
eks_node_security_group_id = module.eks.node_security_group_id
|
||||
|
||||
instance_class = "db.t3.micro"
|
||||
multi_az = false
|
||||
skip_final_snapshot = true
|
||||
deletion_protection = false
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "efs" {
|
||||
source = "../../modules/efs"
|
||||
|
||||
name = "${local.name_prefix}-models"
|
||||
vpc_id = module.vpc.vpc_id
|
||||
private_subnet_ids = module.vpc.private_subnet_ids
|
||||
eks_node_security_group_id = module.eks.node_security_group_id
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "acm" {
|
||||
source = "../../modules/acm"
|
||||
|
||||
domain_name = var.domain_name
|
||||
subject_alternative_names = ["*.${var.domain_name}"]
|
||||
# First apply: leave wait_for_validation = false so Route53 records can be
|
||||
# created in the same plan. Flip to true on a follow-up apply if desired.
|
||||
wait_for_validation = false
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "route53" {
|
||||
source = "../../modules/route53"
|
||||
|
||||
domain_name = var.domain_name
|
||||
subdomains = ["grafana"]
|
||||
acm_validation_records = module.acm.validation_records
|
||||
# alb_dns_name / alb_zone_id are populated after the AWS Load Balancer
|
||||
# Controller provisions the Ingress. Re-apply with -var to wire them up.
|
||||
alb_dns_name = ""
|
||||
alb_zone_id = ""
|
||||
}
|
||||
70
terraform/environments/dev/outputs.tf
Normal file
70
terraform/environments/dev/outputs.tf
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
output "vpc_id" {
|
||||
description = "VPC identifier."
|
||||
value = module.vpc.vpc_id
|
||||
}
|
||||
|
||||
output "private_subnet_ids" {
|
||||
description = "Private subnet identifiers."
|
||||
value = module.vpc.private_subnet_ids
|
||||
}
|
||||
|
||||
output "public_subnet_ids" {
|
||||
description = "Public subnet identifiers."
|
||||
value = module.vpc.public_subnet_ids
|
||||
}
|
||||
|
||||
output "cluster_name" {
|
||||
description = "EKS cluster name."
|
||||
value = module.eks.cluster_name
|
||||
}
|
||||
|
||||
output "cluster_endpoint" {
|
||||
description = "EKS API endpoint."
|
||||
value = module.eks.cluster_endpoint
|
||||
}
|
||||
|
||||
output "cluster_oidc_provider_arn" {
|
||||
description = "OIDC provider ARN for IRSA bindings."
|
||||
value = module.eks.oidc_provider_arn
|
||||
}
|
||||
|
||||
output "rds_endpoint" {
|
||||
description = "RDS endpoint (host:port)."
|
||||
value = module.rds.db_instance_endpoint
|
||||
}
|
||||
|
||||
output "rds_password" {
|
||||
description = "Generated RDS master password."
|
||||
value = module.rds.db_password
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "ecr_repository_urls" {
|
||||
description = "ECR repository URLs by name."
|
||||
value = module.ecr.repository_urls
|
||||
}
|
||||
|
||||
output "efs_file_system_id" {
|
||||
description = "EFS filesystem ID for model weights."
|
||||
value = module.efs.file_system_id
|
||||
}
|
||||
|
||||
output "acm_certificate_arn" {
|
||||
description = "ACM cert ARN for the ALB Ingress."
|
||||
value = module.acm.certificate_arn
|
||||
}
|
||||
|
||||
output "route53_zone_id" {
|
||||
description = "Route53 hosted zone ID."
|
||||
value = module.route53.zone_id
|
||||
}
|
||||
|
||||
output "alb_controller_role_arn" {
|
||||
description = "IRSA role ARN for the AWS Load Balancer Controller."
|
||||
value = module.iam.alb_controller_role_arn
|
||||
}
|
||||
|
||||
output "github_actions_role_arn" {
|
||||
description = "IAM role for GitHub Actions OIDC assumption."
|
||||
value = module.iam.github_actions_role_arn
|
||||
}
|
||||
23
terraform/environments/dev/variables.tf
Normal file
23
terraform/environments/dev/variables.tf
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
variable "region" {
|
||||
description = "AWS region."
|
||||
type = string
|
||||
default = "us-west-2"
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
description = "Environment name (dev/uat/prod)."
|
||||
type = string
|
||||
default = "dev"
|
||||
}
|
||||
|
||||
variable "domain_name" {
|
||||
description = "Apex domain — must already have a Route53 hosted zone."
|
||||
type = string
|
||||
default = "samosachaat.art"
|
||||
}
|
||||
|
||||
variable "github_repositories" {
|
||||
description = "GitHub repos that may assume the CI role."
|
||||
type = list(string)
|
||||
default = ["manmohan659/nanochat"]
|
||||
}
|
||||
38
terraform/environments/dev/versions.tf
Normal file
38
terraform/environments/dev/versions.tf
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = ">= 3.5"
|
||||
}
|
||||
tls = {
|
||||
source = "hashicorp/tls"
|
||||
version = ">= 4.0"
|
||||
}
|
||||
}
|
||||
|
||||
backend "s3" {
|
||||
bucket = "samosachaat-terraform-state"
|
||||
key = "envs/dev/terraform.tfstate"
|
||||
region = "us-west-2"
|
||||
encrypt = true
|
||||
dynamodb_table = "samosachaat-terraform-locks"
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
region = var.region
|
||||
|
||||
default_tags {
|
||||
tags = {
|
||||
Project = "samosachaat"
|
||||
Environment = var.environment
|
||||
ManagedBy = "terraform"
|
||||
}
|
||||
}
|
||||
}
|
||||
125
terraform/environments/prod/.terraform.lock.hcl
Normal file
125
terraform/environments/prod/.terraform.lock.hcl
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/hashicorp/aws" {
|
||||
version = "5.100.0"
|
||||
constraints = ">= 4.33.0, >= 5.0.0, >= 5.79.0, >= 5.92.0, >= 5.95.0, < 6.0.0"
|
||||
hashes = [
|
||||
"h1:Ijt7pOlB7Tr7maGQIqtsLFbl7pSMIj06TVdkoSBcYOw=",
|
||||
"zh:054b8dd49f0549c9a7cc27d159e45327b7b65cf404da5e5a20da154b90b8a644",
|
||||
"zh:0b97bf8d5e03d15d83cc40b0530a1f84b459354939ba6f135a0086c20ebbe6b2",
|
||||
"zh:1589a2266af699cbd5d80737a0fe02e54ec9cf2ca54e7e00ac51c7359056f274",
|
||||
"zh:6330766f1d85f01ae6ea90d1b214b8b74cc8c1badc4696b165b36ddd4cc15f7b",
|
||||
"zh:7c8c2e30d8e55291b86fcb64bdf6c25489d538688545eb48fd74ad622e5d3862",
|
||||
"zh:99b1003bd9bd32ee323544da897148f46a527f622dc3971af63ea3e251596342",
|
||||
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
|
||||
"zh:9f8b909d3ec50ade83c8062290378b1ec553edef6a447c56dadc01a99f4eaa93",
|
||||
"zh:aaef921ff9aabaf8b1869a86d692ebd24fbd4e12c21205034bb679b9caf883a2",
|
||||
"zh:ac882313207aba00dd5a76dbd572a0ddc818bb9cbf5c9d61b28fe30efaec951e",
|
||||
"zh:bb64e8aff37becab373a1a0cc1080990785304141af42ed6aa3dd4913b000421",
|
||||
"zh:dfe495f6621df5540d9c92ad40b8067376350b005c637ea6efac5dc15028add4",
|
||||
"zh:f0ddf0eaf052766cfe09dea8200a946519f653c384ab4336e2a4a64fdd6310e9",
|
||||
"zh:f1b7e684f4c7ae1eed272b6de7d2049bb87a0275cb04dbb7cda6636f600699c9",
|
||||
"zh:ff461571e3f233699bf690db319dfe46aec75e58726636a0d97dd9ac6e32fb70",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/cloudinit" {
|
||||
version = "2.3.7"
|
||||
constraints = ">= 2.0.0"
|
||||
hashes = [
|
||||
"h1:M9TpQxKAE/hyOwytdX9MUNZw30HoD/OXqYIug5fkqH8=",
|
||||
"zh:06f1c54e919425c3139f8aeb8fcf9bceca7e560d48c9f0c1e3bb0a8ad9d9da1e",
|
||||
"zh:0e1e4cf6fd98b019e764c28586a386dc136129fef50af8c7165a067e7e4a31d5",
|
||||
"zh:1871f4337c7c57287d4d67396f633d224b8938708b772abfc664d1f80bd67edd",
|
||||
"zh:2b9269d91b742a71b2248439d5e9824f0447e6d261bfb86a8a88528609b136d1",
|
||||
"zh:3d8ae039af21426072c66d6a59a467d51f2d9189b8198616888c1b7fc42addc7",
|
||||
"zh:3ef4e2db5bcf3e2d915921adced43929214e0946a6fb11793085d9a48995ae01",
|
||||
"zh:42ae54381147437c83cbb8790cc68935d71b6357728a154109d3220b1beb4dc9",
|
||||
"zh:4496b362605ae4cbc9ef7995d102351e2fe311897586ffc7a4a262ccca0c782a",
|
||||
"zh:652a2401257a12706d32842f66dac05a735693abcb3e6517d6b5e2573729ba13",
|
||||
"zh:7406c30806f5979eaed5f50c548eced2ea18ea121e01801d2f0d4d87a04f6a14",
|
||||
"zh:7848429fd5a5bcf35f6fee8487df0fb64b09ec071330f3ff240c0343fe2a5224",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/null" {
|
||||
version = "3.2.4"
|
||||
constraints = ">= 3.0.0"
|
||||
hashes = [
|
||||
"h1:L5V05xwp/Gto1leRryuesxjMfgZwjb7oool4WS1UEFQ=",
|
||||
"zh:59f6b52ab4ff35739647f9509ee6d93d7c032985d9f8c6237d1f8a59471bbbe2",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
"zh:795c897119ff082133150121d39ff26cb5f89a730a2c8c26f3a9c1abf81a9c43",
|
||||
"zh:7b9c7b16f118fbc2b05a983817b8ce2f86df125857966ad356353baf4bff5c0a",
|
||||
"zh:85e33ab43e0e1726e5f97a874b8e24820b6565ff8076523cc2922ba671492991",
|
||||
"zh:9d32ac3619cfc93eb3c4f423492a8e0f79db05fec58e449dee9b2d5873d5f69f",
|
||||
"zh:9e15c3c9dd8e0d1e3731841d44c34571b6c97f5b95e8296a45318b94e5287a6e",
|
||||
"zh:b4c2ab35d1b7696c30b64bf2c0f3a62329107bd1a9121ce70683dec58af19615",
|
||||
"zh:c43723e8cc65bcdf5e0c92581dcbbdcbdcf18b8d2037406a5f2033b1e22de442",
|
||||
"zh:ceb5495d9c31bfb299d246ab333f08c7fb0d67a4f82681fbf47f2a21c3e11ab5",
|
||||
"zh:e171026b3659305c558d9804062762d168f50ba02b88b231d20ec99578a6233f",
|
||||
"zh:ed0fe2acdb61330b01841fa790be00ec6beaac91d41f311fb8254f74eb6a711f",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/random" {
|
||||
version = "3.8.1"
|
||||
constraints = ">= 3.1.0, >= 3.5.0"
|
||||
hashes = [
|
||||
"h1:u8AKlWVDTH5r9YLSeswoVEjiY72Rt4/ch7U+61ZDkiQ=",
|
||||
"zh:08dd03b918c7b55713026037c5400c48af5b9f468f483463321bd18e17b907b4",
|
||||
"zh:0eee654a5542dc1d41920bbf2419032d6f0d5625b03bd81339e5b33394a3e0ae",
|
||||
"zh:229665ddf060aa0ed315597908483eee5b818a17d09b6417a0f52fd9405c4f57",
|
||||
"zh:2469d2e48f28076254a2a3fc327f184914566d9e40c5780b8d96ebf7205f8bc0",
|
||||
"zh:37d7eb334d9561f335e748280f5535a384a88675af9a9eac439d4cfd663bcb66",
|
||||
"zh:741101426a2f2c52dee37122f0f4a2f2d6af6d852cb1db634480a86398fa3511",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
"zh:a902473f08ef8df62cfe6116bd6c157070a93f66622384300de235a533e9d4a9",
|
||||
"zh:b85c511a23e57a2147355932b3b6dce2a11e856b941165793a0c3d7578d94d05",
|
||||
"zh:c5172226d18eaac95b1daac80172287b69d4ce32750c82ad77fa0768be4ea4b8",
|
||||
"zh:dab4434dba34aad569b0bc243c2d3f3ff86dd7740def373f2a49816bd2ff819b",
|
||||
"zh:f49fd62aa8c5525a5c17abd51e27ca5e213881d58882fd42fec4a545b53c9699",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/time" {
|
||||
version = "0.13.1"
|
||||
constraints = ">= 0.9.0"
|
||||
hashes = [
|
||||
"h1:ZT5ppCNIModqk3iOkVt5my8b8yBHmDpl663JtXAIRqM=",
|
||||
"zh:02cb9aab1002f0f2a94a4f85acec8893297dc75915f7404c165983f720a54b74",
|
||||
"zh:04429b2b31a492d19e5ecf999b116d396dac0b24bba0d0fb19ecaefe193fdb8f",
|
||||
"zh:26f8e51bb7c275c404ba6028c1b530312066009194db721a8427a7bc5cdbc83a",
|
||||
"zh:772ff8dbdbef968651ab3ae76d04afd355c32f8a868d03244db3f8496e462690",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
"zh:898db5d2b6bd6ca5457dccb52eedbc7c5b1a71e4a4658381bcbb38cedbbda328",
|
||||
"zh:8de913bf09a3fa7bedc29fec18c47c571d0c7a3d0644322c46f3aa648cf30cd8",
|
||||
"zh:9402102c86a87bdfe7e501ffbb9c685c32bbcefcfcf897fd7d53df414c36877b",
|
||||
"zh:b18b9bb1726bb8cfbefc0a29cf3657c82578001f514bcf4c079839b6776c47f0",
|
||||
"zh:b9d31fdc4faecb909d7c5ce41d2479dd0536862a963df434be4b16e8e4edc94d",
|
||||
"zh:c951e9f39cca3446c060bd63933ebb89cedde9523904813973fbc3d11863ba75",
|
||||
"zh:e5b773c0d07e962291be0e9b413c7a22c044b8c7b58c76e8aa91d1659990dfb5",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/tls" {
|
||||
version = "4.2.1"
|
||||
constraints = ">= 3.0.0, >= 4.0.0"
|
||||
hashes = [
|
||||
"h1:akFNuHwvrtnYMBofieoeXhPJDhYZzJVu/Q/BgZK2fgg=",
|
||||
"zh:0d1e7d07ac973b97fa228f46596c800de830820506ee145626f079dd6bbf8d8a",
|
||||
"zh:5c7e3d4348cb4861ab812973ef493814a4b224bdd3e9d534a7c8a7c992382b86",
|
||||
"zh:7c6d4a86cd7a4e9c1025c6b3a3a6a45dea202af85d870cddbab455fb1bd568ad",
|
||||
"zh:7d0864755ba093664c4b2c07c045d3f5e3d7c799dda1a3ef33d17ed1ac563191",
|
||||
"zh:83734f57950ab67c0d6a87babdb3f13c908cbe0a48949333f489698532e1391b",
|
||||
"zh:951e3c285218ebca0cf20eaa4265020b4ef042fea9c6ade115ad1558cfe459e5",
|
||||
"zh:b9543955b4297e1d93b85900854891c0e645d936d8285a190030475379c5c635",
|
||||
"zh:bb1bd9e86c003d08c30c1b00d44118ed5bbbf6b1d2d6f7eaac4fa5c6ebea5933",
|
||||
"zh:c9477bfe00653629cd77ddac3968475f7ad93ac3ca8bc45b56d1d9efb25e4a6e",
|
||||
"zh:d4cfda8687f736d0cba664c22ec49dae1188289e214ef57f5afe6a7217854fed",
|
||||
"zh:dc77ee066cf96532a48f0578c35b1eaf6dc4d8ddd0e3ae8e029a3b10676dd5d3",
|
||||
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
|
||||
]
|
||||
}
|
||||
104
terraform/environments/prod/main.tf
Normal file
104
terraform/environments/prod/main.tf
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
locals {
|
||||
name_prefix = "samosachaat-${var.environment}"
|
||||
cluster_name = "${local.name_prefix}-eks"
|
||||
|
||||
tags = {
|
||||
Project = "samosachaat"
|
||||
Environment = var.environment
|
||||
}
|
||||
}
|
||||
|
||||
module "vpc" {
|
||||
source = "../../modules/vpc"
|
||||
|
||||
name = local.name_prefix
|
||||
cluster_name = local.cluster_name
|
||||
cidr = "10.0.0.0/16"
|
||||
azs = ["us-west-2a", "us-west-2b", "us-west-2c"]
|
||||
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
|
||||
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
|
||||
single_nat_gateway = false
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "eks" {
|
||||
source = "../../modules/eks"
|
||||
|
||||
cluster_name = local.cluster_name
|
||||
cluster_version = "1.29"
|
||||
vpc_id = module.vpc.vpc_id
|
||||
private_subnet_ids = module.vpc.private_subnet_ids
|
||||
|
||||
node_instance_type = "t3.xlarge"
|
||||
node_min_size = 3
|
||||
node_max_size = 10
|
||||
node_desired_size = 3
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "ecr" {
|
||||
source = "../../modules/ecr"
|
||||
|
||||
force_delete = false
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "iam" {
|
||||
source = "../../modules/iam"
|
||||
|
||||
name_prefix = local.name_prefix
|
||||
oidc_provider_arn = module.eks.oidc_provider_arn
|
||||
oidc_provider_url = module.eks.oidc_provider_url
|
||||
# GitHub OIDC provider is created by the dev env (account-level resource).
|
||||
create_github_oidc = false
|
||||
github_repositories = var.github_repositories
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "rds" {
|
||||
source = "../../modules/rds"
|
||||
|
||||
identifier = "${local.name_prefix}-pg"
|
||||
vpc_id = module.vpc.vpc_id
|
||||
private_subnet_ids = module.vpc.private_subnet_ids
|
||||
eks_node_security_group_id = module.eks.node_security_group_id
|
||||
|
||||
instance_class = "db.t3.medium"
|
||||
multi_az = true
|
||||
skip_final_snapshot = false
|
||||
deletion_protection = true
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "efs" {
|
||||
source = "../../modules/efs"
|
||||
|
||||
name = "${local.name_prefix}-models"
|
||||
vpc_id = module.vpc.vpc_id
|
||||
private_subnet_ids = module.vpc.private_subnet_ids
|
||||
eks_node_security_group_id = module.eks.node_security_group_id
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "acm" {
|
||||
source = "../../modules/acm"
|
||||
|
||||
domain_name = var.domain_name
|
||||
subject_alternative_names = ["*.${var.domain_name}"]
|
||||
wait_for_validation = false
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "route53" {
|
||||
source = "../../modules/route53"
|
||||
|
||||
domain_name = var.domain_name
|
||||
subdomains = ["grafana"]
|
||||
acm_validation_records = module.acm.validation_records
|
||||
alb_dns_name = ""
|
||||
alb_zone_id = ""
|
||||
}
|
||||
70
terraform/environments/prod/outputs.tf
Normal file
70
terraform/environments/prod/outputs.tf
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
output "vpc_id" {
|
||||
description = "VPC identifier."
|
||||
value = module.vpc.vpc_id
|
||||
}
|
||||
|
||||
output "private_subnet_ids" {
|
||||
description = "Private subnet identifiers."
|
||||
value = module.vpc.private_subnet_ids
|
||||
}
|
||||
|
||||
output "public_subnet_ids" {
|
||||
description = "Public subnet identifiers."
|
||||
value = module.vpc.public_subnet_ids
|
||||
}
|
||||
|
||||
output "cluster_name" {
|
||||
description = "EKS cluster name."
|
||||
value = module.eks.cluster_name
|
||||
}
|
||||
|
||||
output "cluster_endpoint" {
|
||||
description = "EKS API endpoint."
|
||||
value = module.eks.cluster_endpoint
|
||||
}
|
||||
|
||||
output "cluster_oidc_provider_arn" {
|
||||
description = "OIDC provider ARN for IRSA bindings."
|
||||
value = module.eks.oidc_provider_arn
|
||||
}
|
||||
|
||||
output "rds_endpoint" {
|
||||
description = "RDS endpoint (host:port)."
|
||||
value = module.rds.db_instance_endpoint
|
||||
}
|
||||
|
||||
output "rds_password" {
|
||||
description = "Generated RDS master password."
|
||||
value = module.rds.db_password
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "ecr_repository_urls" {
|
||||
description = "ECR repository URLs by name."
|
||||
value = module.ecr.repository_urls
|
||||
}
|
||||
|
||||
output "efs_file_system_id" {
|
||||
description = "EFS filesystem ID for model weights."
|
||||
value = module.efs.file_system_id
|
||||
}
|
||||
|
||||
output "acm_certificate_arn" {
|
||||
description = "ACM cert ARN for the ALB Ingress."
|
||||
value = module.acm.certificate_arn
|
||||
}
|
||||
|
||||
output "route53_zone_id" {
|
||||
description = "Route53 hosted zone ID."
|
||||
value = module.route53.zone_id
|
||||
}
|
||||
|
||||
output "alb_controller_role_arn" {
|
||||
description = "IRSA role ARN for the AWS Load Balancer Controller."
|
||||
value = module.iam.alb_controller_role_arn
|
||||
}
|
||||
|
||||
output "github_actions_role_arn" {
|
||||
description = "IAM role for GitHub Actions OIDC assumption."
|
||||
value = module.iam.github_actions_role_arn
|
||||
}
|
||||
23
terraform/environments/prod/variables.tf
Normal file
23
terraform/environments/prod/variables.tf
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
variable "region" {
|
||||
description = "AWS region."
|
||||
type = string
|
||||
default = "us-west-2"
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
description = "Environment name (dev/uat/prod)."
|
||||
type = string
|
||||
default = "prod"
|
||||
}
|
||||
|
||||
variable "domain_name" {
|
||||
description = "Apex domain — must already have a Route53 hosted zone."
|
||||
type = string
|
||||
default = "samosachaat.art"
|
||||
}
|
||||
|
||||
variable "github_repositories" {
|
||||
description = "GitHub repos that may assume the CI role."
|
||||
type = list(string)
|
||||
default = ["manmohan659/nanochat"]
|
||||
}
|
||||
38
terraform/environments/prod/versions.tf
Normal file
38
terraform/environments/prod/versions.tf
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = ">= 3.5"
|
||||
}
|
||||
tls = {
|
||||
source = "hashicorp/tls"
|
||||
version = ">= 4.0"
|
||||
}
|
||||
}
|
||||
|
||||
backend "s3" {
|
||||
bucket = "samosachaat-terraform-state"
|
||||
key = "envs/prod/terraform.tfstate"
|
||||
region = "us-west-2"
|
||||
encrypt = true
|
||||
dynamodb_table = "samosachaat-terraform-locks"
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
region = var.region
|
||||
|
||||
default_tags {
|
||||
tags = {
|
||||
Project = "samosachaat"
|
||||
Environment = var.environment
|
||||
ManagedBy = "terraform"
|
||||
}
|
||||
}
|
||||
}
|
||||
125
terraform/environments/uat/.terraform.lock.hcl
Normal file
125
terraform/environments/uat/.terraform.lock.hcl
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/hashicorp/aws" {
|
||||
version = "5.100.0"
|
||||
constraints = ">= 4.33.0, >= 5.0.0, >= 5.79.0, >= 5.92.0, >= 5.95.0, < 6.0.0"
|
||||
hashes = [
|
||||
"h1:Ijt7pOlB7Tr7maGQIqtsLFbl7pSMIj06TVdkoSBcYOw=",
|
||||
"zh:054b8dd49f0549c9a7cc27d159e45327b7b65cf404da5e5a20da154b90b8a644",
|
||||
"zh:0b97bf8d5e03d15d83cc40b0530a1f84b459354939ba6f135a0086c20ebbe6b2",
|
||||
"zh:1589a2266af699cbd5d80737a0fe02e54ec9cf2ca54e7e00ac51c7359056f274",
|
||||
"zh:6330766f1d85f01ae6ea90d1b214b8b74cc8c1badc4696b165b36ddd4cc15f7b",
|
||||
"zh:7c8c2e30d8e55291b86fcb64bdf6c25489d538688545eb48fd74ad622e5d3862",
|
||||
"zh:99b1003bd9bd32ee323544da897148f46a527f622dc3971af63ea3e251596342",
|
||||
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
|
||||
"zh:9f8b909d3ec50ade83c8062290378b1ec553edef6a447c56dadc01a99f4eaa93",
|
||||
"zh:aaef921ff9aabaf8b1869a86d692ebd24fbd4e12c21205034bb679b9caf883a2",
|
||||
"zh:ac882313207aba00dd5a76dbd572a0ddc818bb9cbf5c9d61b28fe30efaec951e",
|
||||
"zh:bb64e8aff37becab373a1a0cc1080990785304141af42ed6aa3dd4913b000421",
|
||||
"zh:dfe495f6621df5540d9c92ad40b8067376350b005c637ea6efac5dc15028add4",
|
||||
"zh:f0ddf0eaf052766cfe09dea8200a946519f653c384ab4336e2a4a64fdd6310e9",
|
||||
"zh:f1b7e684f4c7ae1eed272b6de7d2049bb87a0275cb04dbb7cda6636f600699c9",
|
||||
"zh:ff461571e3f233699bf690db319dfe46aec75e58726636a0d97dd9ac6e32fb70",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/cloudinit" {
|
||||
version = "2.3.7"
|
||||
constraints = ">= 2.0.0"
|
||||
hashes = [
|
||||
"h1:M9TpQxKAE/hyOwytdX9MUNZw30HoD/OXqYIug5fkqH8=",
|
||||
"zh:06f1c54e919425c3139f8aeb8fcf9bceca7e560d48c9f0c1e3bb0a8ad9d9da1e",
|
||||
"zh:0e1e4cf6fd98b019e764c28586a386dc136129fef50af8c7165a067e7e4a31d5",
|
||||
"zh:1871f4337c7c57287d4d67396f633d224b8938708b772abfc664d1f80bd67edd",
|
||||
"zh:2b9269d91b742a71b2248439d5e9824f0447e6d261bfb86a8a88528609b136d1",
|
||||
"zh:3d8ae039af21426072c66d6a59a467d51f2d9189b8198616888c1b7fc42addc7",
|
||||
"zh:3ef4e2db5bcf3e2d915921adced43929214e0946a6fb11793085d9a48995ae01",
|
||||
"zh:42ae54381147437c83cbb8790cc68935d71b6357728a154109d3220b1beb4dc9",
|
||||
"zh:4496b362605ae4cbc9ef7995d102351e2fe311897586ffc7a4a262ccca0c782a",
|
||||
"zh:652a2401257a12706d32842f66dac05a735693abcb3e6517d6b5e2573729ba13",
|
||||
"zh:7406c30806f5979eaed5f50c548eced2ea18ea121e01801d2f0d4d87a04f6a14",
|
||||
"zh:7848429fd5a5bcf35f6fee8487df0fb64b09ec071330f3ff240c0343fe2a5224",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/null" {
|
||||
version = "3.2.4"
|
||||
constraints = ">= 3.0.0"
|
||||
hashes = [
|
||||
"h1:L5V05xwp/Gto1leRryuesxjMfgZwjb7oool4WS1UEFQ=",
|
||||
"zh:59f6b52ab4ff35739647f9509ee6d93d7c032985d9f8c6237d1f8a59471bbbe2",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
"zh:795c897119ff082133150121d39ff26cb5f89a730a2c8c26f3a9c1abf81a9c43",
|
||||
"zh:7b9c7b16f118fbc2b05a983817b8ce2f86df125857966ad356353baf4bff5c0a",
|
||||
"zh:85e33ab43e0e1726e5f97a874b8e24820b6565ff8076523cc2922ba671492991",
|
||||
"zh:9d32ac3619cfc93eb3c4f423492a8e0f79db05fec58e449dee9b2d5873d5f69f",
|
||||
"zh:9e15c3c9dd8e0d1e3731841d44c34571b6c97f5b95e8296a45318b94e5287a6e",
|
||||
"zh:b4c2ab35d1b7696c30b64bf2c0f3a62329107bd1a9121ce70683dec58af19615",
|
||||
"zh:c43723e8cc65bcdf5e0c92581dcbbdcbdcf18b8d2037406a5f2033b1e22de442",
|
||||
"zh:ceb5495d9c31bfb299d246ab333f08c7fb0d67a4f82681fbf47f2a21c3e11ab5",
|
||||
"zh:e171026b3659305c558d9804062762d168f50ba02b88b231d20ec99578a6233f",
|
||||
"zh:ed0fe2acdb61330b01841fa790be00ec6beaac91d41f311fb8254f74eb6a711f",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/random" {
|
||||
version = "3.8.1"
|
||||
constraints = ">= 3.1.0, >= 3.5.0"
|
||||
hashes = [
|
||||
"h1:u8AKlWVDTH5r9YLSeswoVEjiY72Rt4/ch7U+61ZDkiQ=",
|
||||
"zh:08dd03b918c7b55713026037c5400c48af5b9f468f483463321bd18e17b907b4",
|
||||
"zh:0eee654a5542dc1d41920bbf2419032d6f0d5625b03bd81339e5b33394a3e0ae",
|
||||
"zh:229665ddf060aa0ed315597908483eee5b818a17d09b6417a0f52fd9405c4f57",
|
||||
"zh:2469d2e48f28076254a2a3fc327f184914566d9e40c5780b8d96ebf7205f8bc0",
|
||||
"zh:37d7eb334d9561f335e748280f5535a384a88675af9a9eac439d4cfd663bcb66",
|
||||
"zh:741101426a2f2c52dee37122f0f4a2f2d6af6d852cb1db634480a86398fa3511",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
"zh:a902473f08ef8df62cfe6116bd6c157070a93f66622384300de235a533e9d4a9",
|
||||
"zh:b85c511a23e57a2147355932b3b6dce2a11e856b941165793a0c3d7578d94d05",
|
||||
"zh:c5172226d18eaac95b1daac80172287b69d4ce32750c82ad77fa0768be4ea4b8",
|
||||
"zh:dab4434dba34aad569b0bc243c2d3f3ff86dd7740def373f2a49816bd2ff819b",
|
||||
"zh:f49fd62aa8c5525a5c17abd51e27ca5e213881d58882fd42fec4a545b53c9699",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/time" {
|
||||
version = "0.13.1"
|
||||
constraints = ">= 0.9.0"
|
||||
hashes = [
|
||||
"h1:ZT5ppCNIModqk3iOkVt5my8b8yBHmDpl663JtXAIRqM=",
|
||||
"zh:02cb9aab1002f0f2a94a4f85acec8893297dc75915f7404c165983f720a54b74",
|
||||
"zh:04429b2b31a492d19e5ecf999b116d396dac0b24bba0d0fb19ecaefe193fdb8f",
|
||||
"zh:26f8e51bb7c275c404ba6028c1b530312066009194db721a8427a7bc5cdbc83a",
|
||||
"zh:772ff8dbdbef968651ab3ae76d04afd355c32f8a868d03244db3f8496e462690",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
"zh:898db5d2b6bd6ca5457dccb52eedbc7c5b1a71e4a4658381bcbb38cedbbda328",
|
||||
"zh:8de913bf09a3fa7bedc29fec18c47c571d0c7a3d0644322c46f3aa648cf30cd8",
|
||||
"zh:9402102c86a87bdfe7e501ffbb9c685c32bbcefcfcf897fd7d53df414c36877b",
|
||||
"zh:b18b9bb1726bb8cfbefc0a29cf3657c82578001f514bcf4c079839b6776c47f0",
|
||||
"zh:b9d31fdc4faecb909d7c5ce41d2479dd0536862a963df434be4b16e8e4edc94d",
|
||||
"zh:c951e9f39cca3446c060bd63933ebb89cedde9523904813973fbc3d11863ba75",
|
||||
"zh:e5b773c0d07e962291be0e9b413c7a22c044b8c7b58c76e8aa91d1659990dfb5",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/tls" {
|
||||
version = "4.2.1"
|
||||
constraints = ">= 3.0.0, >= 4.0.0"
|
||||
hashes = [
|
||||
"h1:akFNuHwvrtnYMBofieoeXhPJDhYZzJVu/Q/BgZK2fgg=",
|
||||
"zh:0d1e7d07ac973b97fa228f46596c800de830820506ee145626f079dd6bbf8d8a",
|
||||
"zh:5c7e3d4348cb4861ab812973ef493814a4b224bdd3e9d534a7c8a7c992382b86",
|
||||
"zh:7c6d4a86cd7a4e9c1025c6b3a3a6a45dea202af85d870cddbab455fb1bd568ad",
|
||||
"zh:7d0864755ba093664c4b2c07c045d3f5e3d7c799dda1a3ef33d17ed1ac563191",
|
||||
"zh:83734f57950ab67c0d6a87babdb3f13c908cbe0a48949333f489698532e1391b",
|
||||
"zh:951e3c285218ebca0cf20eaa4265020b4ef042fea9c6ade115ad1558cfe459e5",
|
||||
"zh:b9543955b4297e1d93b85900854891c0e645d936d8285a190030475379c5c635",
|
||||
"zh:bb1bd9e86c003d08c30c1b00d44118ed5bbbf6b1d2d6f7eaac4fa5c6ebea5933",
|
||||
"zh:c9477bfe00653629cd77ddac3968475f7ad93ac3ca8bc45b56d1d9efb25e4a6e",
|
||||
"zh:d4cfda8687f736d0cba664c22ec49dae1188289e214ef57f5afe6a7217854fed",
|
||||
"zh:dc77ee066cf96532a48f0578c35b1eaf6dc4d8ddd0e3ae8e029a3b10676dd5d3",
|
||||
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
|
||||
]
|
||||
}
|
||||
104
terraform/environments/uat/main.tf
Normal file
104
terraform/environments/uat/main.tf
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
locals {
|
||||
name_prefix = "samosachaat-${var.environment}"
|
||||
cluster_name = "${local.name_prefix}-eks"
|
||||
|
||||
tags = {
|
||||
Project = "samosachaat"
|
||||
Environment = var.environment
|
||||
}
|
||||
}
|
||||
|
||||
module "vpc" {
|
||||
source = "../../modules/vpc"
|
||||
|
||||
name = local.name_prefix
|
||||
cluster_name = local.cluster_name
|
||||
cidr = "10.0.0.0/16"
|
||||
azs = ["us-west-2a", "us-west-2b", "us-west-2c"]
|
||||
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
|
||||
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
|
||||
single_nat_gateway = true
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "eks" {
|
||||
source = "../../modules/eks"
|
||||
|
||||
cluster_name = local.cluster_name
|
||||
cluster_version = "1.29"
|
||||
vpc_id = module.vpc.vpc_id
|
||||
private_subnet_ids = module.vpc.private_subnet_ids
|
||||
|
||||
node_instance_type = "t3.large"
|
||||
node_min_size = 2
|
||||
node_max_size = 4
|
||||
node_desired_size = 2
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "ecr" {
|
||||
source = "../../modules/ecr"
|
||||
|
||||
force_delete = false
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "iam" {
|
||||
source = "../../modules/iam"
|
||||
|
||||
name_prefix = local.name_prefix
|
||||
oidc_provider_arn = module.eks.oidc_provider_arn
|
||||
oidc_provider_url = module.eks.oidc_provider_url
|
||||
# GitHub OIDC provider is created by the dev env (account-level resource).
|
||||
create_github_oidc = false
|
||||
github_repositories = var.github_repositories
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "rds" {
|
||||
source = "../../modules/rds"
|
||||
|
||||
identifier = "${local.name_prefix}-pg"
|
||||
vpc_id = module.vpc.vpc_id
|
||||
private_subnet_ids = module.vpc.private_subnet_ids
|
||||
eks_node_security_group_id = module.eks.node_security_group_id
|
||||
|
||||
instance_class = "db.t3.micro"
|
||||
multi_az = false
|
||||
skip_final_snapshot = true
|
||||
deletion_protection = false
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "efs" {
|
||||
source = "../../modules/efs"
|
||||
|
||||
name = "${local.name_prefix}-models"
|
||||
vpc_id = module.vpc.vpc_id
|
||||
private_subnet_ids = module.vpc.private_subnet_ids
|
||||
eks_node_security_group_id = module.eks.node_security_group_id
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "acm" {
|
||||
source = "../../modules/acm"
|
||||
|
||||
domain_name = var.domain_name
|
||||
subject_alternative_names = ["*.${var.domain_name}"]
|
||||
wait_for_validation = false
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
module "route53" {
|
||||
source = "../../modules/route53"
|
||||
|
||||
domain_name = var.domain_name
|
||||
subdomains = ["grafana"]
|
||||
acm_validation_records = module.acm.validation_records
|
||||
alb_dns_name = ""
|
||||
alb_zone_id = ""
|
||||
}
|
||||
70
terraform/environments/uat/outputs.tf
Normal file
70
terraform/environments/uat/outputs.tf
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
output "vpc_id" {
|
||||
description = "VPC identifier."
|
||||
value = module.vpc.vpc_id
|
||||
}
|
||||
|
||||
output "private_subnet_ids" {
|
||||
description = "Private subnet identifiers."
|
||||
value = module.vpc.private_subnet_ids
|
||||
}
|
||||
|
||||
output "public_subnet_ids" {
|
||||
description = "Public subnet identifiers."
|
||||
value = module.vpc.public_subnet_ids
|
||||
}
|
||||
|
||||
output "cluster_name" {
|
||||
description = "EKS cluster name."
|
||||
value = module.eks.cluster_name
|
||||
}
|
||||
|
||||
output "cluster_endpoint" {
|
||||
description = "EKS API endpoint."
|
||||
value = module.eks.cluster_endpoint
|
||||
}
|
||||
|
||||
output "cluster_oidc_provider_arn" {
|
||||
description = "OIDC provider ARN for IRSA bindings."
|
||||
value = module.eks.oidc_provider_arn
|
||||
}
|
||||
|
||||
output "rds_endpoint" {
|
||||
description = "RDS endpoint (host:port)."
|
||||
value = module.rds.db_instance_endpoint
|
||||
}
|
||||
|
||||
output "rds_password" {
|
||||
description = "Generated RDS master password."
|
||||
value = module.rds.db_password
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "ecr_repository_urls" {
|
||||
description = "ECR repository URLs by name."
|
||||
value = module.ecr.repository_urls
|
||||
}
|
||||
|
||||
output "efs_file_system_id" {
|
||||
description = "EFS filesystem ID for model weights."
|
||||
value = module.efs.file_system_id
|
||||
}
|
||||
|
||||
output "acm_certificate_arn" {
|
||||
description = "ACM cert ARN for the ALB Ingress."
|
||||
value = module.acm.certificate_arn
|
||||
}
|
||||
|
||||
output "route53_zone_id" {
|
||||
description = "Route53 hosted zone ID."
|
||||
value = module.route53.zone_id
|
||||
}
|
||||
|
||||
output "alb_controller_role_arn" {
|
||||
description = "IRSA role ARN for the AWS Load Balancer Controller."
|
||||
value = module.iam.alb_controller_role_arn
|
||||
}
|
||||
|
||||
output "github_actions_role_arn" {
|
||||
description = "IAM role for GitHub Actions OIDC assumption."
|
||||
value = module.iam.github_actions_role_arn
|
||||
}
|
||||
23
terraform/environments/uat/variables.tf
Normal file
23
terraform/environments/uat/variables.tf
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
variable "region" {
|
||||
description = "AWS region."
|
||||
type = string
|
||||
default = "us-west-2"
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
description = "Environment name (dev/uat/prod)."
|
||||
type = string
|
||||
default = "uat"
|
||||
}
|
||||
|
||||
variable "domain_name" {
|
||||
description = "Apex domain — must already have a Route53 hosted zone."
|
||||
type = string
|
||||
default = "samosachaat.art"
|
||||
}
|
||||
|
||||
variable "github_repositories" {
|
||||
description = "GitHub repos that may assume the CI role."
|
||||
type = list(string)
|
||||
default = ["manmohan659/nanochat"]
|
||||
}
|
||||
38
terraform/environments/uat/versions.tf
Normal file
38
terraform/environments/uat/versions.tf
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = ">= 3.5"
|
||||
}
|
||||
tls = {
|
||||
source = "hashicorp/tls"
|
||||
version = ">= 4.0"
|
||||
}
|
||||
}
|
||||
|
||||
backend "s3" {
|
||||
bucket = "samosachaat-terraform-state"
|
||||
key = "envs/uat/terraform.tfstate"
|
||||
region = "us-west-2"
|
||||
encrypt = true
|
||||
dynamodb_table = "samosachaat-terraform-locks"
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
region = var.region
|
||||
|
||||
default_tags {
|
||||
tags = {
|
||||
Project = "samosachaat"
|
||||
Environment = var.environment
|
||||
ManagedBy = "terraform"
|
||||
}
|
||||
}
|
||||
}
|
||||
29
terraform/modules/acm/main.tf
Normal file
29
terraform/modules/acm/main.tf
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_acm_certificate" "this" {
|
||||
domain_name = var.domain_name
|
||||
subject_alternative_names = var.subject_alternative_names
|
||||
validation_method = "DNS"
|
||||
|
||||
lifecycle {
|
||||
create_before_destroy = true
|
||||
}
|
||||
|
||||
tags = var.tags
|
||||
}
|
||||
|
||||
# Route53 records are created in the route53 module from validation_records output.
|
||||
resource "aws_acm_certificate_validation" "this" {
|
||||
count = var.wait_for_validation ? 1 : 0
|
||||
|
||||
certificate_arn = aws_acm_certificate.this.arn
|
||||
validation_record_fqdns = var.validation_record_fqdns
|
||||
}
|
||||
29
terraform/modules/acm/outputs.tf
Normal file
29
terraform/modules/acm/outputs.tf
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
output "certificate_arn" {
|
||||
description = "ARN of the issued certificate (use on the ALB Ingress annotation alb.ingress.kubernetes.io/certificate-arn)."
|
||||
value = aws_acm_certificate.this.arn
|
||||
}
|
||||
|
||||
output "domain_validation_options" {
|
||||
description = "Raw validation options from AWS — used by the route53 module."
|
||||
value = aws_acm_certificate.this.domain_validation_options
|
||||
}
|
||||
|
||||
output "validation_records" {
|
||||
description = "Map keyed by domain → { name, type, record } ready to plug into route53.acm_validation_records."
|
||||
value = {
|
||||
for dvo in aws_acm_certificate.this.domain_validation_options :
|
||||
dvo.domain_name => {
|
||||
name = dvo.resource_record_name
|
||||
type = dvo.resource_record_type
|
||||
record = dvo.resource_record_value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output "validation_record_fqdns" {
|
||||
description = "List of FQDNs to feed into aws_acm_certificate_validation."
|
||||
value = [
|
||||
for dvo in aws_acm_certificate.this.domain_validation_options :
|
||||
dvo.resource_record_name
|
||||
]
|
||||
}
|
||||
28
terraform/modules/acm/variables.tf
Normal file
28
terraform/modules/acm/variables.tf
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
variable "domain_name" {
|
||||
description = "Primary domain on the certificate (e.g. samosachaat.art)."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "subject_alternative_names" {
|
||||
description = "SAN list (e.g. [\"*.samosachaat.art\"])."
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "wait_for_validation" {
|
||||
description = "Block apply until DNS validation succeeds. Disable on first apply if Route53 records are created in the same plan."
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "validation_record_fqdns" {
|
||||
description = "FQDNs of the DNS validation records (passed in from the route53 module)."
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Tags applied to every resource."
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
50
terraform/modules/ecr/main.tf
Normal file
50
terraform/modules/ecr/main.tf
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_ecr_repository" "this" {
|
||||
for_each = toset(var.repository_names)
|
||||
|
||||
name = each.key
|
||||
image_tag_mutability = "MUTABLE"
|
||||
force_delete = var.force_delete
|
||||
|
||||
image_scanning_configuration {
|
||||
scan_on_push = true
|
||||
}
|
||||
|
||||
encryption_configuration {
|
||||
encryption_type = "AES256"
|
||||
}
|
||||
|
||||
tags = var.tags
|
||||
}
|
||||
|
||||
resource "aws_ecr_lifecycle_policy" "keep_last_20" {
|
||||
for_each = aws_ecr_repository.this
|
||||
|
||||
repository = each.value.name
|
||||
|
||||
policy = jsonencode({
|
||||
rules = [
|
||||
{
|
||||
rulePriority = 1
|
||||
description = "Keep only the last 20 images"
|
||||
selection = {
|
||||
tagStatus = "any"
|
||||
countType = "imageCountMoreThan"
|
||||
countNumber = 20
|
||||
}
|
||||
action = {
|
||||
type = "expire"
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
14
terraform/modules/ecr/outputs.tf
Normal file
14
terraform/modules/ecr/outputs.tf
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
output "repository_urls" {
|
||||
description = "Map of repository name → registry URL (used by CI/CD `docker push`)."
|
||||
value = { for name, repo in aws_ecr_repository.this : name => repo.repository_url }
|
||||
}
|
||||
|
||||
output "repository_arns" {
|
||||
description = "Map of repository name → ARN."
|
||||
value = { for name, repo in aws_ecr_repository.this : name => repo.arn }
|
||||
}
|
||||
|
||||
output "registry_id" {
|
||||
description = "Account ID hosting the registry (same for all repos)."
|
||||
value = values(aws_ecr_repository.this)[0].registry_id
|
||||
}
|
||||
22
terraform/modules/ecr/variables.tf
Normal file
22
terraform/modules/ecr/variables.tf
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
variable "repository_names" {
|
||||
description = "ECR repositories to create."
|
||||
type = list(string)
|
||||
default = [
|
||||
"samosachaat-frontend",
|
||||
"samosachaat-auth",
|
||||
"samosachaat-chat-api",
|
||||
"samosachaat-inference",
|
||||
]
|
||||
}
|
||||
|
||||
variable "force_delete" {
|
||||
description = "Allow Terraform to destroy repositories even if they contain images (true for dev only)."
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Tags applied to every resource."
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
73
terraform/modules/efs/main.tf
Normal file
73
terraform/modules/efs/main.tf
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_security_group" "efs" {
|
||||
name = "${var.name}-efs-sg"
|
||||
description = "NFS from EKS nodes to model-weights EFS"
|
||||
vpc_id = var.vpc_id
|
||||
|
||||
ingress {
|
||||
description = "NFS from EKS nodes"
|
||||
from_port = 2049
|
||||
to_port = 2049
|
||||
protocol = "tcp"
|
||||
security_groups = [var.eks_node_security_group_id]
|
||||
}
|
||||
|
||||
egress {
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
protocol = "-1"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
tags = var.tags
|
||||
}
|
||||
|
||||
resource "aws_efs_file_system" "this" {
|
||||
creation_token = var.name
|
||||
encrypted = true
|
||||
performance_mode = var.performance_mode
|
||||
throughput_mode = var.throughput_mode
|
||||
|
||||
lifecycle_policy {
|
||||
transition_to_ia = "AFTER_30_DAYS"
|
||||
}
|
||||
|
||||
tags = merge(var.tags, { Name = var.name })
|
||||
}
|
||||
|
||||
resource "aws_efs_mount_target" "this" {
|
||||
for_each = toset(var.private_subnet_ids)
|
||||
file_system_id = aws_efs_file_system.this.id
|
||||
subnet_id = each.key
|
||||
security_groups = [aws_security_group.efs.id]
|
||||
}
|
||||
|
||||
# Access point used by inference pods (UID/GID match the container user).
|
||||
resource "aws_efs_access_point" "model_weights" {
|
||||
file_system_id = aws_efs_file_system.this.id
|
||||
|
||||
posix_user {
|
||||
uid = 1000
|
||||
gid = 1000
|
||||
}
|
||||
|
||||
root_directory {
|
||||
path = "/model-weights"
|
||||
creation_info {
|
||||
owner_uid = 1000
|
||||
owner_gid = 1000
|
||||
permissions = "0755"
|
||||
}
|
||||
}
|
||||
|
||||
tags = var.tags
|
||||
}
|
||||
26
terraform/modules/efs/outputs.tf
Normal file
26
terraform/modules/efs/outputs.tf
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
output "file_system_id" {
|
||||
description = "EFS filesystem ID — pass to the EFS CSI driver StorageClass."
|
||||
value = aws_efs_file_system.this.id
|
||||
}
|
||||
|
||||
output "file_system_arn" {
|
||||
description = "Filesystem ARN."
|
||||
value = aws_efs_file_system.this.arn
|
||||
}
|
||||
|
||||
output "dns_name" {
|
||||
description = "Mount DNS name."
|
||||
value = "${aws_efs_file_system.this.id}.efs.${data.aws_region.current.name}.amazonaws.com"
|
||||
}
|
||||
|
||||
output "access_point_id" {
|
||||
description = "Access point for the model-weights directory."
|
||||
value = aws_efs_access_point.model_weights.id
|
||||
}
|
||||
|
||||
output "security_group_id" {
|
||||
description = "EFS security group."
|
||||
value = aws_security_group.efs.id
|
||||
}
|
||||
|
||||
data "aws_region" "current" {}
|
||||
37
terraform/modules/efs/variables.tf
Normal file
37
terraform/modules/efs/variables.tf
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
variable "name" {
|
||||
description = "Filesystem name (used in tags and the creation token)."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "vpc_id" {
|
||||
description = "VPC the mount targets live in."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "private_subnet_ids" {
|
||||
description = "Private subnets that get mount targets (one per AZ)."
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "eks_node_security_group_id" {
|
||||
description = "Node SG allowed to mount the filesystem."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "performance_mode" {
|
||||
description = "EFS performance mode."
|
||||
type = string
|
||||
default = "generalPurpose"
|
||||
}
|
||||
|
||||
variable "throughput_mode" {
|
||||
description = "EFS throughput mode."
|
||||
type = string
|
||||
default = "bursting"
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Tags applied to every resource."
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
60
terraform/modules/eks/main.tf
Normal file
60
terraform/modules/eks/main.tf
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data "aws_ssm_parameter" "eks_ami_id" {
|
||||
name = "/aws/service/eks/optimized-ami/${var.cluster_version}/amazon-linux-2/recommended/image_id"
|
||||
}
|
||||
|
||||
module "eks" {
|
||||
source = "terraform-aws-modules/eks/aws"
|
||||
version = "~> 20.0"
|
||||
|
||||
cluster_name = var.cluster_name
|
||||
cluster_version = var.cluster_version
|
||||
|
||||
cluster_endpoint_public_access = true
|
||||
cluster_endpoint_private_access = true
|
||||
|
||||
enable_irsa = true
|
||||
|
||||
vpc_id = var.vpc_id
|
||||
subnet_ids = var.private_subnet_ids
|
||||
control_plane_subnet_ids = var.private_subnet_ids
|
||||
|
||||
cluster_addons = {
|
||||
coredns = { most_recent = true }
|
||||
kube-proxy = { most_recent = true }
|
||||
vpc-cni = { most_recent = true }
|
||||
aws-ebs-csi-driver = { most_recent = true }
|
||||
aws-efs-csi-driver = { most_recent = true }
|
||||
}
|
||||
|
||||
eks_managed_node_group_defaults = {
|
||||
ami_id = data.aws_ssm_parameter.eks_ami_id.value
|
||||
enable_bootstrap_user_data = true
|
||||
}
|
||||
|
||||
eks_managed_node_groups = {
|
||||
default = {
|
||||
min_size = var.node_min_size
|
||||
max_size = var.node_max_size
|
||||
desired_size = var.node_desired_size
|
||||
|
||||
instance_types = [var.node_instance_type]
|
||||
capacity_type = "ON_DEMAND"
|
||||
|
||||
labels = {
|
||||
role = "general"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tags = var.tags
|
||||
}
|
||||
34
terraform/modules/eks/outputs.tf
Normal file
34
terraform/modules/eks/outputs.tf
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
output "cluster_name" {
|
||||
description = "EKS cluster name."
|
||||
value = module.eks.cluster_name
|
||||
}
|
||||
|
||||
output "cluster_endpoint" {
|
||||
description = "EKS API server endpoint."
|
||||
value = module.eks.cluster_endpoint
|
||||
}
|
||||
|
||||
output "cluster_certificate_authority_data" {
|
||||
description = "Base64-encoded cluster CA certificate."
|
||||
value = module.eks.cluster_certificate_authority_data
|
||||
}
|
||||
|
||||
output "cluster_security_group_id" {
|
||||
description = "Cluster control-plane security group."
|
||||
value = module.eks.cluster_security_group_id
|
||||
}
|
||||
|
||||
output "node_security_group_id" {
|
||||
description = "Security group attached to managed node group ENIs (used by RDS / EFS to allow inbound traffic from nodes)."
|
||||
value = module.eks.node_security_group_id
|
||||
}
|
||||
|
||||
output "oidc_provider_arn" {
|
||||
description = "IRSA OIDC provider ARN."
|
||||
value = module.eks.oidc_provider_arn
|
||||
}
|
||||
|
||||
output "oidc_provider_url" {
|
||||
description = "IRSA OIDC issuer URL (without https://)."
|
||||
value = module.eks.oidc_provider
|
||||
}
|
||||
50
terraform/modules/eks/variables.tf
Normal file
50
terraform/modules/eks/variables.tf
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
variable "cluster_name" {
|
||||
description = "EKS cluster name."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "cluster_version" {
|
||||
description = "Kubernetes version for the EKS control plane."
|
||||
type = string
|
||||
default = "1.29"
|
||||
}
|
||||
|
||||
variable "vpc_id" {
|
||||
description = "VPC the cluster lives in."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "private_subnet_ids" {
|
||||
description = "Private subnets for nodes and control-plane ENIs."
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "node_instance_type" {
|
||||
description = "EC2 instance type for the managed node group."
|
||||
type = string
|
||||
default = "t3.large"
|
||||
}
|
||||
|
||||
variable "node_min_size" {
|
||||
description = "Minimum nodes in the managed node group."
|
||||
type = number
|
||||
default = 2
|
||||
}
|
||||
|
||||
variable "node_max_size" {
|
||||
description = "Maximum nodes in the managed node group."
|
||||
type = number
|
||||
default = 4
|
||||
}
|
||||
|
||||
variable "node_desired_size" {
|
||||
description = "Desired nodes in the managed node group."
|
||||
type = number
|
||||
default = 2
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Tags applied to every resource."
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
197
terraform/modules/iam/main.tf
Normal file
197
terraform/modules/iam/main.tf
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
tls = {
|
||||
source = "hashicorp/tls"
|
||||
version = ">= 4.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data "aws_caller_identity" "current" {}
|
||||
data "aws_partition" "current" {}
|
||||
|
||||
##############################################
|
||||
# EKS managed-node-group instance role
|
||||
##############################################
|
||||
|
||||
data "aws_iam_policy_document" "ec2_assume" {
|
||||
statement {
|
||||
actions = ["sts:AssumeRole"]
|
||||
principals {
|
||||
type = "Service"
|
||||
identifiers = ["ec2.amazonaws.com"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "eks_node" {
|
||||
name = "${var.name_prefix}-eks-node"
|
||||
assume_role_policy = data.aws_iam_policy_document.ec2_assume.json
|
||||
tags = var.tags
|
||||
}
|
||||
|
||||
locals {
|
||||
eks_node_managed_policies = {
|
||||
worker = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonEKSWorkerNodePolicy"
|
||||
cni = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonEKS_CNI_Policy"
|
||||
ecr_pull = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
|
||||
ebs_csi = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy"
|
||||
efs_csi = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AmazonEFSCSIDriverPolicy"
|
||||
ssm = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonSSMManagedInstanceCore"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "eks_node" {
|
||||
for_each = local.eks_node_managed_policies
|
||||
role = aws_iam_role.eks_node.name
|
||||
policy_arn = each.value
|
||||
}
|
||||
|
||||
resource "aws_iam_instance_profile" "eks_node" {
|
||||
name = aws_iam_role.eks_node.name
|
||||
role = aws_iam_role.eks_node.name
|
||||
}
|
||||
|
||||
##############################################
|
||||
# AWS Load Balancer Controller IRSA role
|
||||
##############################################
|
||||
|
||||
data "aws_iam_policy_document" "alb_irsa_assume" {
|
||||
count = var.oidc_provider_arn == "" ? 0 : 1
|
||||
|
||||
statement {
|
||||
actions = ["sts:AssumeRoleWithWebIdentity"]
|
||||
principals {
|
||||
type = "Federated"
|
||||
identifiers = [var.oidc_provider_arn]
|
||||
}
|
||||
condition {
|
||||
test = "StringEquals"
|
||||
variable = "${var.oidc_provider_url}:sub"
|
||||
values = ["system:serviceaccount:kube-system:aws-load-balancer-controller"]
|
||||
}
|
||||
condition {
|
||||
test = "StringEquals"
|
||||
variable = "${var.oidc_provider_url}:aud"
|
||||
values = ["sts.amazonaws.com"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "alb_controller" {
|
||||
count = var.oidc_provider_arn == "" ? 0 : 1
|
||||
name = "${var.name_prefix}-alb-controller"
|
||||
assume_role_policy = data.aws_iam_policy_document.alb_irsa_assume[0].json
|
||||
tags = var.tags
|
||||
}
|
||||
|
||||
resource "aws_iam_policy" "alb_controller" {
|
||||
count = var.oidc_provider_arn == "" ? 0 : 1
|
||||
name = "${var.name_prefix}-alb-controller"
|
||||
description = "Permissions required by the AWS Load Balancer Controller."
|
||||
policy = file("${path.module}/policies/alb_controller.json")
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "alb_controller" {
|
||||
count = var.oidc_provider_arn == "" ? 0 : 1
|
||||
role = aws_iam_role.alb_controller[0].name
|
||||
policy_arn = aws_iam_policy.alb_controller[0].arn
|
||||
}
|
||||
|
||||
##############################################
|
||||
# GitHub Actions OIDC provider + CI/CD role
|
||||
##############################################
|
||||
|
||||
data "tls_certificate" "github" {
|
||||
count = var.create_github_oidc ? 1 : 0
|
||||
url = "https://token.actions.githubusercontent.com"
|
||||
}
|
||||
|
||||
resource "aws_iam_openid_connect_provider" "github" {
|
||||
count = var.create_github_oidc ? 1 : 0
|
||||
url = "https://token.actions.githubusercontent.com"
|
||||
client_id_list = ["sts.amazonaws.com"]
|
||||
thumbprint_list = data.tls_certificate.github[0].certificates[*].sha1_fingerprint
|
||||
tags = var.tags
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "github_assume" {
|
||||
count = var.create_github_oidc ? 1 : 0
|
||||
|
||||
statement {
|
||||
actions = ["sts:AssumeRoleWithWebIdentity"]
|
||||
principals {
|
||||
type = "Federated"
|
||||
identifiers = [aws_iam_openid_connect_provider.github[0].arn]
|
||||
}
|
||||
condition {
|
||||
test = "StringEquals"
|
||||
variable = "token.actions.githubusercontent.com:aud"
|
||||
values = ["sts.amazonaws.com"]
|
||||
}
|
||||
condition {
|
||||
test = "StringLike"
|
||||
variable = "token.actions.githubusercontent.com:sub"
|
||||
values = [for r in var.github_repositories : "repo:${r}:*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "github_actions" {
|
||||
count = var.create_github_oidc ? 1 : 0
|
||||
name = "${var.name_prefix}-github-actions"
|
||||
assume_role_policy = data.aws_iam_policy_document.github_assume[0].json
|
||||
tags = var.tags
|
||||
}
|
||||
|
||||
# Permissions the CI role needs to push images, update kubeconfig, and apply manifests.
|
||||
data "aws_iam_policy_document" "github_actions" {
|
||||
count = var.create_github_oidc ? 1 : 0
|
||||
|
||||
statement {
|
||||
sid = "ECRAuth"
|
||||
actions = [
|
||||
"ecr:GetAuthorizationToken",
|
||||
]
|
||||
resources = ["*"]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "ECRPushPull"
|
||||
actions = [
|
||||
"ecr:BatchCheckLayerAvailability",
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:CompleteLayerUpload",
|
||||
"ecr:GetDownloadUrlForLayer",
|
||||
"ecr:InitiateLayerUpload",
|
||||
"ecr:PutImage",
|
||||
"ecr:UploadLayerPart",
|
||||
"ecr:DescribeRepositories",
|
||||
"ecr:ListImages",
|
||||
]
|
||||
resources = ["arn:${data.aws_partition.current.partition}:ecr:*:${data.aws_caller_identity.current.account_id}:repository/samosachaat-*"]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "EKSDescribe"
|
||||
actions = ["eks:DescribeCluster", "eks:ListClusters"]
|
||||
resources = ["*"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_policy" "github_actions" {
|
||||
count = var.create_github_oidc ? 1 : 0
|
||||
name = "${var.name_prefix}-github-actions"
|
||||
policy = data.aws_iam_policy_document.github_actions[0].json
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "github_actions" {
|
||||
count = var.create_github_oidc ? 1 : 0
|
||||
role = aws_iam_role.github_actions[0].name
|
||||
policy_arn = aws_iam_policy.github_actions[0].arn
|
||||
}
|
||||
29
terraform/modules/iam/outputs.tf
Normal file
29
terraform/modules/iam/outputs.tf
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
output "eks_node_role_arn" {
|
||||
description = "ARN of the EKS managed-node-group instance role."
|
||||
value = aws_iam_role.eks_node.arn
|
||||
}
|
||||
|
||||
output "eks_node_role_name" {
|
||||
description = "Name of the EKS node role."
|
||||
value = aws_iam_role.eks_node.name
|
||||
}
|
||||
|
||||
output "eks_node_instance_profile_name" {
|
||||
description = "Instance profile attached to EKS nodes."
|
||||
value = aws_iam_instance_profile.eks_node.name
|
||||
}
|
||||
|
||||
output "alb_controller_role_arn" {
|
||||
description = "IAM role to bind to the aws-load-balancer-controller ServiceAccount via IRSA."
|
||||
value = try(aws_iam_role.alb_controller[0].arn, "")
|
||||
}
|
||||
|
||||
output "github_actions_role_arn" {
|
||||
description = "Role to assume from GitHub Actions for CI/CD (empty if not enabled)."
|
||||
value = try(aws_iam_role.github_actions[0].arn, "")
|
||||
}
|
||||
|
||||
output "github_oidc_provider_arn" {
|
||||
description = "GitHub OIDC provider ARN (empty if not enabled)."
|
||||
value = try(aws_iam_openid_connect_provider.github[0].arn, "")
|
||||
}
|
||||
219
terraform/modules/iam/policies/alb_controller.json
Normal file
219
terraform/modules/iam/policies/alb_controller.json
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"iam:CreateServiceLinkedRole"
|
||||
],
|
||||
"Resource": "*",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"iam:AWSServiceName": "elasticloadbalancing.amazonaws.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:DescribeAccountAttributes",
|
||||
"ec2:DescribeAddresses",
|
||||
"ec2:DescribeAvailabilityZones",
|
||||
"ec2:DescribeInternetGateways",
|
||||
"ec2:DescribeVpcs",
|
||||
"ec2:DescribeVpcPeeringConnections",
|
||||
"ec2:DescribeSubnets",
|
||||
"ec2:DescribeSecurityGroups",
|
||||
"ec2:DescribeInstances",
|
||||
"ec2:DescribeNetworkInterfaces",
|
||||
"ec2:DescribeTags",
|
||||
"ec2:GetCoipPoolUsage",
|
||||
"ec2:DescribeCoipPools",
|
||||
"elasticloadbalancing:DescribeLoadBalancers",
|
||||
"elasticloadbalancing:DescribeLoadBalancerAttributes",
|
||||
"elasticloadbalancing:DescribeListeners",
|
||||
"elasticloadbalancing:DescribeListenerCertificates",
|
||||
"elasticloadbalancing:DescribeSSLPolicies",
|
||||
"elasticloadbalancing:DescribeRules",
|
||||
"elasticloadbalancing:DescribeTargetGroups",
|
||||
"elasticloadbalancing:DescribeTargetGroupAttributes",
|
||||
"elasticloadbalancing:DescribeTargetHealth",
|
||||
"elasticloadbalancing:DescribeTags"
|
||||
],
|
||||
"Resource": "*"
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"cognito-idp:DescribeUserPoolClient",
|
||||
"acm:ListCertificates",
|
||||
"acm:DescribeCertificate",
|
||||
"iam:ListServerCertificates",
|
||||
"iam:GetServerCertificate",
|
||||
"waf-regional:GetWebACL",
|
||||
"waf-regional:GetWebACLForResource",
|
||||
"waf-regional:AssociateWebACL",
|
||||
"waf-regional:DisassociateWebACL",
|
||||
"wafv2:GetWebACL",
|
||||
"wafv2:GetWebACLForResource",
|
||||
"wafv2:AssociateWebACL",
|
||||
"wafv2:DisassociateWebACL",
|
||||
"shield:GetSubscriptionState",
|
||||
"shield:DescribeProtection",
|
||||
"shield:CreateProtection",
|
||||
"shield:DeleteProtection"
|
||||
],
|
||||
"Resource": "*"
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:AuthorizeSecurityGroupIngress",
|
||||
"ec2:RevokeSecurityGroupIngress"
|
||||
],
|
||||
"Resource": "*"
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:CreateSecurityGroup"
|
||||
],
|
||||
"Resource": "*"
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:CreateTags"
|
||||
],
|
||||
"Resource": "arn:aws:ec2:*:*:security-group/*",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"ec2:CreateAction": "CreateSecurityGroup"
|
||||
},
|
||||
"Null": {
|
||||
"aws:RequestTag/elbv2.k8s.aws/cluster": "false"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:CreateTags",
|
||||
"ec2:DeleteTags"
|
||||
],
|
||||
"Resource": "arn:aws:ec2:*:*:security-group/*",
|
||||
"Condition": {
|
||||
"Null": {
|
||||
"aws:RequestTag/elbv2.k8s.aws/cluster": "true",
|
||||
"aws:ResourceTag/elbv2.k8s.aws/cluster": "false"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:AuthorizeSecurityGroupIngress",
|
||||
"ec2:RevokeSecurityGroupIngress",
|
||||
"ec2:DeleteSecurityGroup"
|
||||
],
|
||||
"Resource": "*",
|
||||
"Condition": {
|
||||
"Null": {
|
||||
"aws:ResourceTag/elbv2.k8s.aws/cluster": "false"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"elasticloadbalancing:CreateLoadBalancer",
|
||||
"elasticloadbalancing:CreateTargetGroup"
|
||||
],
|
||||
"Resource": "*",
|
||||
"Condition": {
|
||||
"Null": {
|
||||
"aws:RequestTag/elbv2.k8s.aws/cluster": "false"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"elasticloadbalancing:CreateListener",
|
||||
"elasticloadbalancing:DeleteListener",
|
||||
"elasticloadbalancing:CreateRule",
|
||||
"elasticloadbalancing:DeleteRule"
|
||||
],
|
||||
"Resource": "*"
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"elasticloadbalancing:AddTags",
|
||||
"elasticloadbalancing:RemoveTags"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:elasticloadbalancing:*:*:targetgroup/*/*",
|
||||
"arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*",
|
||||
"arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*"
|
||||
],
|
||||
"Condition": {
|
||||
"Null": {
|
||||
"aws:RequestTag/elbv2.k8s.aws/cluster": "true",
|
||||
"aws:ResourceTag/elbv2.k8s.aws/cluster": "false"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"elasticloadbalancing:AddTags",
|
||||
"elasticloadbalancing:RemoveTags"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:elasticloadbalancing:*:*:listener/net/*/*/*",
|
||||
"arn:aws:elasticloadbalancing:*:*:listener/app/*/*/*",
|
||||
"arn:aws:elasticloadbalancing:*:*:listener-rule/net/*/*/*",
|
||||
"arn:aws:elasticloadbalancing:*:*:listener-rule/app/*/*/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"elasticloadbalancing:ModifyLoadBalancerAttributes",
|
||||
"elasticloadbalancing:SetIpAddressType",
|
||||
"elasticloadbalancing:SetSecurityGroups",
|
||||
"elasticloadbalancing:SetSubnets",
|
||||
"elasticloadbalancing:DeleteLoadBalancer",
|
||||
"elasticloadbalancing:ModifyTargetGroup",
|
||||
"elasticloadbalancing:ModifyTargetGroupAttributes",
|
||||
"elasticloadbalancing:DeleteTargetGroup"
|
||||
],
|
||||
"Resource": "*",
|
||||
"Condition": {
|
||||
"Null": {
|
||||
"aws:ResourceTag/elbv2.k8s.aws/cluster": "false"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"elasticloadbalancing:RegisterTargets",
|
||||
"elasticloadbalancing:DeregisterTargets"
|
||||
],
|
||||
"Resource": "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*"
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"elasticloadbalancing:SetWebAcl",
|
||||
"elasticloadbalancing:ModifyListener",
|
||||
"elasticloadbalancing:AddListenerCertificates",
|
||||
"elasticloadbalancing:RemoveListenerCertificates",
|
||||
"elasticloadbalancing:ModifyRule"
|
||||
],
|
||||
"Resource": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
34
terraform/modules/iam/variables.tf
Normal file
34
terraform/modules/iam/variables.tf
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
variable "name_prefix" {
|
||||
description = "Prefix for IAM resource names (e.g. samosachaat-dev)."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "oidc_provider_arn" {
|
||||
description = "EKS OIDC provider ARN. Pass empty string to skip ALB controller role creation."
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "oidc_provider_url" {
|
||||
description = "EKS OIDC issuer hostname (no scheme, e.g. oidc.eks.us-west-2.amazonaws.com/id/XXX)."
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "create_github_oidc" {
|
||||
description = "Create the GitHub Actions OIDC provider + CI role. Set to true exactly once per AWS account."
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "github_repositories" {
|
||||
description = "GitHub repositories allowed to assume the CI role (e.g. [\"manmohan659/nanochat\"])."
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Tags applied to every resource."
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
87
terraform/modules/rds/main.tf
Normal file
87
terraform/modules/rds/main.tf
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = ">= 3.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "random_password" "db" {
|
||||
length = 32
|
||||
special = true
|
||||
override_special = "!#$%&*()-_=+[]{}<>:?"
|
||||
}
|
||||
|
||||
resource "aws_security_group" "db" {
|
||||
name = "${var.identifier}-rds-sg"
|
||||
description = "PostgreSQL access for samosaChaat from EKS nodes only"
|
||||
vpc_id = var.vpc_id
|
||||
|
||||
ingress {
|
||||
description = "PostgreSQL from EKS nodes"
|
||||
from_port = 5432
|
||||
to_port = 5432
|
||||
protocol = "tcp"
|
||||
security_groups = [var.eks_node_security_group_id]
|
||||
}
|
||||
|
||||
egress {
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
protocol = "-1"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
tags = var.tags
|
||||
}
|
||||
|
||||
module "db" {
|
||||
source = "terraform-aws-modules/rds/aws"
|
||||
version = "~> 6.0"
|
||||
|
||||
identifier = var.identifier
|
||||
|
||||
engine = "postgres"
|
||||
engine_version = "15"
|
||||
family = "postgres15"
|
||||
major_engine_version = "15"
|
||||
instance_class = var.instance_class
|
||||
|
||||
allocated_storage = var.allocated_storage
|
||||
max_allocated_storage = var.max_allocated_storage
|
||||
storage_encrypted = true
|
||||
|
||||
db_name = var.db_name
|
||||
username = var.db_username
|
||||
password = random_password.db.result
|
||||
port = 5432
|
||||
|
||||
manage_master_user_password = false
|
||||
|
||||
multi_az = var.multi_az
|
||||
db_subnet_group_name = null
|
||||
subnet_ids = var.private_subnet_ids
|
||||
create_db_subnet_group = true
|
||||
vpc_security_group_ids = [aws_security_group.db.id]
|
||||
|
||||
publicly_accessible = false
|
||||
|
||||
backup_retention_period = 7
|
||||
backup_window = "03:00-04:00"
|
||||
maintenance_window = "Mon:04:00-Mon:05:00"
|
||||
|
||||
skip_final_snapshot = var.skip_final_snapshot
|
||||
deletion_protection = var.deletion_protection
|
||||
|
||||
performance_insights_enabled = true
|
||||
create_monitoring_role = true
|
||||
monitoring_interval = 60
|
||||
|
||||
tags = var.tags
|
||||
}
|
||||
36
terraform/modules/rds/outputs.tf
Normal file
36
terraform/modules/rds/outputs.tf
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
output "db_instance_endpoint" {
|
||||
description = "Endpoint of the form host:port."
|
||||
value = module.db.db_instance_endpoint
|
||||
}
|
||||
|
||||
output "db_instance_address" {
|
||||
description = "Hostname of the DB instance."
|
||||
value = module.db.db_instance_address
|
||||
}
|
||||
|
||||
output "db_instance_port" {
|
||||
description = "Listening port."
|
||||
value = module.db.db_instance_port
|
||||
}
|
||||
|
||||
output "db_instance_name" {
|
||||
description = "Initial database name."
|
||||
value = module.db.db_instance_name
|
||||
}
|
||||
|
||||
output "db_instance_username" {
|
||||
description = "Master username."
|
||||
value = module.db.db_instance_username
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "db_password" {
|
||||
description = "Generated master password (write to Secrets Manager / Parameter Store from your env config)."
|
||||
value = random_password.db.result
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "db_security_group_id" {
|
||||
description = "Security group attached to the DB."
|
||||
value = aws_security_group.db.id
|
||||
}
|
||||
73
terraform/modules/rds/variables.tf
Normal file
73
terraform/modules/rds/variables.tf
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
variable "identifier" {
|
||||
description = "DB instance identifier (also used as name prefix)."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "vpc_id" {
|
||||
description = "VPC the database lives in."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "private_subnet_ids" {
|
||||
description = "Private subnets for the DB subnet group (>= 2 AZs)."
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "eks_node_security_group_id" {
|
||||
description = "Node SG that should be allowed inbound to PostgreSQL."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "instance_class" {
|
||||
description = "RDS instance class (e.g. db.t3.micro for dev, db.t3.medium for prod)."
|
||||
type = string
|
||||
default = "db.t3.micro"
|
||||
}
|
||||
|
||||
variable "db_name" {
|
||||
description = "Initial database name."
|
||||
type = string
|
||||
default = "samosachaat"
|
||||
}
|
||||
|
||||
variable "db_username" {
|
||||
description = "Master username."
|
||||
type = string
|
||||
default = "samosachaat_admin"
|
||||
}
|
||||
|
||||
variable "allocated_storage" {
|
||||
description = "Initial storage (GB)."
|
||||
type = number
|
||||
default = 20
|
||||
}
|
||||
|
||||
variable "max_allocated_storage" {
|
||||
description = "Storage autoscaling cap (GB)."
|
||||
type = number
|
||||
default = 100
|
||||
}
|
||||
|
||||
variable "multi_az" {
|
||||
description = "Enable Multi-AZ (recommended for prod)."
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "skip_final_snapshot" {
|
||||
description = "Skip the final snapshot when destroying (true for dev)."
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "deletion_protection" {
|
||||
description = "Block accidental deletion (recommended for prod)."
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Tags applied to every resource."
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
58
terraform/modules/route53/main.tf
Normal file
58
terraform/modules/route53/main.tf
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Use an existing hosted zone (created out-of-band when registering the domain).
|
||||
data "aws_route53_zone" "this" {
|
||||
name = var.domain_name
|
||||
private_zone = false
|
||||
}
|
||||
|
||||
# alb_dns_name / alb_zone_id come from the AWS Load Balancer Controller after the
|
||||
# Ingress is created (look up via `kubectl get ingress` or a data source). Pass
|
||||
# empty strings to skip A-record creation on the first apply, then re-apply.
|
||||
resource "aws_route53_record" "apex" {
|
||||
count = var.alb_dns_name == "" ? 0 : 1
|
||||
|
||||
zone_id = data.aws_route53_zone.this.zone_id
|
||||
name = var.domain_name
|
||||
type = "A"
|
||||
|
||||
alias {
|
||||
name = var.alb_dns_name
|
||||
zone_id = var.alb_zone_id
|
||||
evaluate_target_health = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_route53_record" "subdomains" {
|
||||
for_each = var.alb_dns_name == "" ? toset([]) : toset(var.subdomains)
|
||||
|
||||
zone_id = data.aws_route53_zone.this.zone_id
|
||||
name = "${each.key}.${var.domain_name}"
|
||||
type = "A"
|
||||
|
||||
alias {
|
||||
name = var.alb_dns_name
|
||||
zone_id = var.alb_zone_id
|
||||
evaluate_target_health = true
|
||||
}
|
||||
}
|
||||
|
||||
# ACM DNS-validation CNAMEs. Pass the map exported by the ACM module.
|
||||
resource "aws_route53_record" "acm_validation" {
|
||||
for_each = var.acm_validation_records
|
||||
|
||||
zone_id = data.aws_route53_zone.this.zone_id
|
||||
name = each.value.name
|
||||
type = each.value.type
|
||||
records = [each.value.record]
|
||||
ttl = 60
|
||||
allow_overwrite = true
|
||||
}
|
||||
14
terraform/modules/route53/outputs.tf
Normal file
14
terraform/modules/route53/outputs.tf
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
output "zone_id" {
|
||||
description = "Hosted zone ID for the apex domain."
|
||||
value = data.aws_route53_zone.this.zone_id
|
||||
}
|
||||
|
||||
output "name_servers" {
|
||||
description = "Authoritative name servers (configure these at the registrar)."
|
||||
value = data.aws_route53_zone.this.name_servers
|
||||
}
|
||||
|
||||
output "apex_record_fqdn" {
|
||||
description = "FQDN of the apex A record (empty until alb_dns_name is supplied)."
|
||||
value = try(aws_route53_record.apex[0].fqdn, "")
|
||||
}
|
||||
32
terraform/modules/route53/variables.tf
Normal file
32
terraform/modules/route53/variables.tf
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
variable "domain_name" {
|
||||
description = "Apex domain (e.g. samosachaat.art). A hosted zone for this domain must already exist."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "subdomains" {
|
||||
description = "Subdomains to alias to the ALB (e.g. [\"grafana\", \"api\"])."
|
||||
type = list(string)
|
||||
default = ["grafana"]
|
||||
}
|
||||
|
||||
variable "alb_dns_name" {
|
||||
description = "ALB DNS name from the AWS Load Balancer Controller. Empty string skips A-record creation (first-apply bootstrap)."
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "alb_zone_id" {
|
||||
description = "ALB hosted-zone ID (region-specific)."
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "acm_validation_records" {
|
||||
description = "Map keyed by domain → { name, type, record } — pass module.acm.validation_records here."
|
||||
type = map(object({
|
||||
name = string
|
||||
type = string
|
||||
record = string
|
||||
}))
|
||||
default = {}
|
||||
}
|
||||
44
terraform/modules/vpc/main.tf
Normal file
44
terraform/modules/vpc/main.tf
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
cluster_tag_key = "kubernetes.io/cluster/${var.cluster_name}"
|
||||
}
|
||||
|
||||
module "vpc" {
|
||||
source = "terraform-aws-modules/vpc/aws"
|
||||
version = "~> 5.0"
|
||||
|
||||
name = "${var.name}-vpc"
|
||||
cidr = var.cidr
|
||||
|
||||
azs = var.azs
|
||||
private_subnets = var.private_subnets
|
||||
public_subnets = var.public_subnets
|
||||
|
||||
enable_nat_gateway = true
|
||||
single_nat_gateway = var.single_nat_gateway
|
||||
one_nat_gateway_per_az = !var.single_nat_gateway
|
||||
|
||||
enable_dns_hostnames = true
|
||||
enable_dns_support = true
|
||||
|
||||
public_subnet_tags = {
|
||||
"kubernetes.io/role/elb" = "1"
|
||||
(local.cluster_tag_key) = "shared"
|
||||
}
|
||||
|
||||
private_subnet_tags = {
|
||||
"kubernetes.io/role/internal-elb" = "1"
|
||||
(local.cluster_tag_key) = "shared"
|
||||
}
|
||||
|
||||
tags = var.tags
|
||||
}
|
||||
34
terraform/modules/vpc/outputs.tf
Normal file
34
terraform/modules/vpc/outputs.tf
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
output "vpc_id" {
|
||||
description = "VPC identifier."
|
||||
value = module.vpc.vpc_id
|
||||
}
|
||||
|
||||
output "vpc_cidr_block" {
|
||||
description = "Primary CIDR block of the VPC."
|
||||
value = module.vpc.vpc_cidr_block
|
||||
}
|
||||
|
||||
output "private_subnet_ids" {
|
||||
description = "Private subnet identifiers (one per AZ)."
|
||||
value = module.vpc.private_subnets
|
||||
}
|
||||
|
||||
output "public_subnet_ids" {
|
||||
description = "Public subnet identifiers (one per AZ)."
|
||||
value = module.vpc.public_subnets
|
||||
}
|
||||
|
||||
output "private_subnet_cidrs" {
|
||||
description = "Private subnet CIDR blocks."
|
||||
value = module.vpc.private_subnets_cidr_blocks
|
||||
}
|
||||
|
||||
output "azs" {
|
||||
description = "AZs in use."
|
||||
value = module.vpc.azs
|
||||
}
|
||||
|
||||
output "natgw_ids" {
|
||||
description = "NAT gateway identifiers."
|
||||
value = module.vpc.natgw_ids
|
||||
}
|
||||
45
terraform/modules/vpc/variables.tf
Normal file
45
terraform/modules/vpc/variables.tf
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
variable "name" {
|
||||
description = "Name prefix used for the VPC and related resources."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "cluster_name" {
|
||||
description = "EKS cluster name used to tag subnets so the AWS Load Balancer Controller can discover them."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "cidr" {
|
||||
description = "CIDR block for the VPC."
|
||||
type = string
|
||||
default = "10.0.0.0/16"
|
||||
}
|
||||
|
||||
variable "azs" {
|
||||
description = "Availability zones to spread subnets across."
|
||||
type = list(string)
|
||||
default = ["us-west-2a", "us-west-2b", "us-west-2c"]
|
||||
}
|
||||
|
||||
variable "private_subnets" {
|
||||
description = "Private subnet CIDRs (one per AZ, in matching order)."
|
||||
type = list(string)
|
||||
default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
|
||||
}
|
||||
|
||||
variable "public_subnets" {
|
||||
description = "Public subnet CIDRs (one per AZ, in matching order)."
|
||||
type = list(string)
|
||||
default = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
|
||||
}
|
||||
|
||||
variable "single_nat_gateway" {
|
||||
description = "When true, all private subnets route through a single NAT gateway (dev). When false, one NAT per AZ (prod)."
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Tags applied to every resource."
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
18
terraform/versions.tf
Normal file
18
terraform/versions.tf
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = ">= 3.5"
|
||||
}
|
||||
tls = {
|
||||
source = "hashicorp/tls"
|
||||
version = ">= 4.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user