-
Notifications
You must be signed in to change notification settings - Fork 0
Add Terraform Infrastructure as Code Setup #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| # GPTeasers Terraform Infrastructure | ||
|
|
||
| This directory contains the Infrastructure as Code (IaC) configuration for deploying the GPTeasers quiz application to Azure Container Apps. | ||
|
|
||
| ## 📁 Directory Contents | ||
|
|
||
| - `main.tf` - Main infrastructure configuration (Resource Group, Container App Environment, Container App) | ||
| - `variables.tf` - Input variables for the infrastructure | ||
| - `outputs.tf` - Output values from the deployed infrastructure | ||
| - `providers.tf` - Terraform provider configurations | ||
| - `terraform.tfvars` - Actual values for sensitive variables (not committed to git) | ||
| - `terraform.tfvars.example` - Example file showing required variable structure | ||
| - `terraform.tfstate` - Current state of deployed infrastructure (not committed to git) | ||
| - `graph.dot` - Infrastructure dependency graph in DOT format | ||
| - `infrastructure.png` & `infrastructure.svg` - Visual representations of the infrastructure | ||
|
|
||
| ## 🚀 Deploying the Infrastructure | ||
|
|
||
| ### Prerequisites | ||
|
|
||
| 1. **Azure CLI installed and authenticated**: | ||
| ```bash | ||
| az login | ||
| ``` | ||
|
|
||
| 2. **Terraform installed** (v1.14.3+ recommended) | ||
|
|
||
| 3. **Configure your secrets**: | ||
| ```bash | ||
| cp terraform.tfvars.example terraform.tfvars | ||
| # Edit terraform.tfvars with your actual API keys and tokens | ||
| ``` | ||
|
|
||
| ### Deployment Commands | ||
|
|
||
| 1. **Initialize Terraform** (first time only): | ||
| ```bash | ||
| cd backend/terraform | ||
| terraform init | ||
| ``` | ||
|
|
||
| 2. **Review the planned changes**: | ||
| ```bash | ||
| cd backend/terraform | ||
| terraform plan | ||
| ``` | ||
|
|
||
| 3. **Deploy the infrastructure**: | ||
| ```bash | ||
| cd backend/terraform | ||
| terraform apply | ||
| ``` | ||
|
|
||
| 4. **Get the deployed app URL**: | ||
| ```bash | ||
| cd backend/terraform | ||
| terraform output container_app_url | ||
| ``` | ||
|
|
||
| ### Cleanup | ||
|
|
||
| To destroy all resources: | ||
| ```bash | ||
| cd backend/terraform | ||
| terraform destroy | ||
| ``` | ||
|
|
||
| ## ⚠️ Important Notes | ||
|
|
||
| ### Hardcoded Container Image Concern | ||
|
|
||
| **I'm a bit confused and concerned that the container image is hardcoded in `main.tf`**: | ||
|
|
||
| ```terraform | ||
| image = "ghcr.io/djsaunders1997/gpteasers:7aa9ecacb7f655e25c149d704108690263ec26b4" | ||
| ``` | ||
|
|
||
| This means: | ||
| - The infrastructure is tied to a specific image version/tag | ||
| - Updates require manual Terraform changes | ||
| - CI/CD pipelines can't automatically update the infrastructure | ||
| - Risk of the image being deleted or becoming unavailable | ||
|
|
||
| **Future Enhancement Idea**: This would be a cool project to parameterize the image reference: | ||
| - Add an `image_tag` variable to `variables.tf` | ||
| - Make the image reference dynamic: `"ghcr.io/djsaunders1997/gpteasers:${var.image_tag}"` | ||
| - Allow CI/CD to pass the latest image tag during deployment | ||
| - Enable blue/green deployments with different image versions | ||
|
|
||
| For now, the infrastructure works as-is, but image updates require manual intervention. This could be a fun Terraform parameterization project for the future! 🎯 | ||
|
|
||
| ## 🔧 Configuration Details | ||
|
|
||
| ### Resources Created | ||
|
|
||
| - **Resource Group**: `ContainerApps` (UK West region) | ||
| - **Container App Environment**: `container-app-environment` | ||
| - **Container App**: `gpteasers` with: | ||
| - 0.25 CPU cores, 0.5Gi memory | ||
| - External ingress on port 8000 | ||
| - GitHub Container Registry integration | ||
| - Environment variables for all AI provider API keys | ||
|
|
||
| ### Secrets Management | ||
|
|
||
| The following secrets are managed via Terraform: | ||
| - OpenAI API Key | ||
| - Azure AI API Key & Base URL | ||
| - Gemini API Key | ||
| - DeepSeek API Key | ||
| - GitHub Container Registry Token | ||
|
|
||
| **Security Note**: Secrets are defined in `terraform.tfvars` (gitignored) and managed through Terraform's lifecycle rules to prevent accidental overwrites during CI/CD deployments. | ||
|
|
||
| ### Network Configuration | ||
|
|
||
| - **Ingress**: External enabled, auto transport | ||
| - **Traffic**: 100% to latest revision | ||
| - **Scaling**: 0-1 replicas (manual scaling) | ||
|
|
||
| ## 📊 Infrastructure Visualization | ||
|
|
||
| View the infrastructure dependencies: | ||
| ```bash | ||
| # Generate DOT graph | ||
| cd backend/terraform | ||
| terraform graph > graph.dot | ||
|
|
||
| # Convert to PNG (requires GraphViz) | ||
| dot -Tpng graph.dot -o infrastructure.png | ||
| ``` | ||
|
|
||
| ## 🔍 Troubleshooting | ||
|
|
||
| - **State issues**: `terraform refresh` to sync with Azure | ||
| - **Import existing resources**: `terraform import <resource_type>.<name> <azure_resource_id>` | ||
| - **Debug mode**: `TF_LOG=DEBUG terraform apply` | ||
|
|
||
| ## 📝 Development Workflow | ||
|
|
||
| 1. Make changes to `.tf` files | ||
| 2. Run `terraform plan` to preview | ||
| 3. Run `terraform apply` to deploy | ||
| 4. Use `terraform output` to get URLs/endpoints | ||
| 5. Commit changes (excluding `.tfstate` and `.tfvars`) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| # Resource Group | ||
| resource "azurerm_resource_group" "gpteasers" { | ||
| name = var.resource_group_name | ||
| location = var.location | ||
| } | ||
|
|
||
| # Container App Environment | ||
| resource "azurerm_container_app_environment" "gpteasers" { | ||
| name = var.container_app_environment_name | ||
| location = azurerm_resource_group.gpteasers.location | ||
| resource_group_name = azurerm_resource_group.gpteasers.name | ||
| # Remove log_analytics_workspace_id - the existing environment doesn't have one | ||
| } | ||
|
|
||
| # Container App | ||
| resource "azurerm_container_app" "gpteasers" { | ||
| name = var.container_app_name | ||
| container_app_environment_id = azurerm_container_app_environment.gpteasers.id | ||
| resource_group_name = azurerm_resource_group.gpteasers.name | ||
| revision_mode = "Single" | ||
|
|
||
| template { | ||
| container { | ||
| name = "gpteasers" | ||
| image = "ghcr.io/djsaunders1997/gpteasers:7aa9ecacb7f655e25c149d704108690263ec26b4" | ||
| cpu = 0.25 | ||
| memory = "0.5Gi" | ||
|
|
||
| env { | ||
| name = "OPENAI_API_KEY" | ||
| secret_name = "openai-api-key-secret" | ||
| } | ||
| env { | ||
| name = "AZURE_AI_API_KEY" | ||
| secret_name = "azure-ai-api-key-secret" | ||
| } | ||
| env { | ||
| name = "GEMINI_API_KEY" | ||
| secret_name = "gemini-api-key-secret" | ||
| } | ||
| env { | ||
| name = "DEEPSEEK_API_KEY" | ||
| secret_name = "deepseek-api-key-secret" | ||
| } | ||
| env { | ||
| name = "AZURE_AI_API_BASE" | ||
| secret_name = "azure-ai-api-base-secret" | ||
| } | ||
| } | ||
|
|
||
| min_replicas = 0 | ||
| max_replicas = 1 | ||
| } | ||
|
|
||
| ingress { | ||
| external_enabled = true | ||
| target_port = 8000 | ||
| transport = "auto" # Changed from "Auto" to "auto" to match existing | ||
| traffic_weight { | ||
| latest_revision = true | ||
| percentage = 100 | ||
| } | ||
| } | ||
|
|
||
| registry { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. B - This is a security risk There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. perhaps a .gitignore would be better practice.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Which part do you think is a security risk Dex? This is the secret name, not the value - that's stored in a secrets.tfvars file which is in my gitignore |
||
| server = "ghcr.io" | ||
| username = "DJSaunders1997" | ||
| password_secret_name = "ghcrio-djsaunders1997" | ||
| } | ||
|
|
||
| # Secrets are managed externally - these placeholders prevent Terraform from removing them | ||
| secret { | ||
| name = "openai-api-key-secret" | ||
| value = var.openai_api_key | ||
| } | ||
|
|
||
| secret { | ||
| name = "azure-ai-api-key-secret" | ||
| value = var.azure_ai_api_key | ||
| } | ||
|
|
||
| secret { | ||
| name = "gemini-api-key-secret" | ||
| value = var.gemini_api_key | ||
| } | ||
|
|
||
| secret { | ||
| name = "deepseek-api-key-secret" | ||
| value = var.deepseek_api_key | ||
| } | ||
|
|
||
| secret { | ||
| name = "azure-ai-api-base-secret" | ||
| value = var.azure_ai_api_base | ||
| } | ||
|
|
||
| secret { | ||
| name = "ghcrio-djsaunders1997" | ||
| value = var.ghcr_token | ||
| } | ||
|
|
||
| lifecycle { | ||
| ignore_changes = [ | ||
| secret, # Don't update secrets - they're managed by CI/CD or manually | ||
| template[0].container[0].image, # Allow image updates from CI/CD | ||
| ] | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| output "container_app_url" { | ||
| description = "URL of the deployed container app" | ||
| value = azurerm_container_app.gpteasers.latest_revision_fqdn | ||
| } | ||
|
|
||
| output "resource_group_name" { | ||
| description = "Name of the resource group" | ||
| value = azurerm_resource_group.gpteasers.name | ||
| } | ||
|
|
||
| output "container_app_environment_name" { | ||
| description = "Name of the container app environment" | ||
| value = azurerm_container_app_environment.gpteasers.name | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| terraform { | ||
| required_providers { | ||
| azurerm = { | ||
| source = "hashicorp/azurerm" | ||
| version = "~> 3.0" | ||
| } | ||
| } | ||
| } | ||
|
|
||
| provider "azurerm" { | ||
| features {} | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # Copy this file to terraform.tfvars and fill in your actual values | ||
| # DO NOT commit terraform.tfvars to git - it contains sensitive information | ||
|
|
||
| # API Keys | ||
| openai_api_key = "your-openai-api-key-here" | ||
| gemini_api_key = "your-gemini-api-key-here" | ||
| azure_ai_api_key = "your-azure-ai-api-key-here" | ||
| azure_ai_api_base = "your-azure-ai-api-base-url-here" | ||
| deepseek_api_key = "your-deepseek-api-key-here" | ||
|
|
||
| # GitHub Container Registry | ||
| ghcr_token = "your-github-personal-access-token-here" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| variable "resource_group_name" { | ||
| description = "Name of the resource group" | ||
| type = string | ||
| default = "ContainerApps" | ||
| } | ||
|
|
||
| variable "location" { | ||
| description = "Azure region" | ||
| type = string | ||
| default = "UK West" | ||
| } | ||
|
|
||
| variable "container_app_name" { | ||
| description = "Name of the container app" | ||
| type = string | ||
| default = "gpteasers" | ||
| } | ||
|
|
||
| variable "container_app_environment_name" { | ||
| description = "Name of the container app environment" | ||
| type = string | ||
| default = "container-app-environment" | ||
| } | ||
|
|
||
| # Secret variables - values loaded from terraform.tfvars (not committed) | ||
| variable "openai_api_key" { | ||
| description = "OpenAI API key" | ||
| type = string | ||
| sensitive = true | ||
| } | ||
|
|
||
| variable "gemini_api_key" { | ||
| description = "Gemini API key" | ||
| type = string | ||
| sensitive = true | ||
| } | ||
|
|
||
| variable "azure_ai_api_key" { | ||
| description = "Azure AI API key" | ||
| type = string | ||
| sensitive = true | ||
| } | ||
|
|
||
| variable "azure_ai_api_base" { | ||
| description = "Azure AI API base URL" | ||
| type = string | ||
| sensitive = true | ||
| } | ||
|
|
||
| variable "deepseek_api_key" { | ||
| description = "DeepSeek API key" | ||
| type = string | ||
| sensitive = true | ||
| } | ||
|
|
||
| variable "ghcr_token" { | ||
| description = "GitHub Container Registry token" | ||
| type = string | ||
| sensitive = true | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review:
Correctness:
terraform.tfvars,terraform.tfstate*,.terraform/).Improvements:
Risk Assessment:
.tfstateis managed securely as it can contain sensitive information.Overall:
The changes made appear to address the necessary additions for Terraform file management. Adding comments and sorting the entries would be helpful for code maintenance. Regularly reviewing these ignore lists is important to maintain security and prevent accidental exposure of sensitive information.