🏗️ Terraform Infrastructure as Code with GitHub Actions¶
Overview¶
This documentation covers the Terraform repository used for managing AWS infrastructure, specifically focused on EKS clusters and managed services. All Terraform operations are executed through GitHub Actions workflows, ensuring consistent and auditable infrastructure changes.
📂 Repository Structure¶
The repository follows a modular approach with clear separation of concerns:
unibeam-workload-sia-prod-terraform/
├── managed-services/
│ ├── components/ # Individual infrastructure components
│ │ ├── eks.tf
│ │ ├── iam_*.tf
│ │ ├── mongodb.tf
│ │ ├── redis.tf
│ │ ├── s3.tf
│ │ └── ...
│ ├── providers.tf # AWS provider configuration
│ ├── variables.tf # Input variables
│ ├── locals.tf # Local computations
│ ├── outputs.tf # Output values
│ └── data.tf # Data sources
├── Cloudformation/ # CloudFormation templates (Karpenter)
└── .github/
└── workflows/ # GitHub Actions workflows
Key Directories¶
managed-services/¶
Contains the main Terraform configurations for provisioning AWS infrastructure:
- EKS Configuration: EKS cluster setup, node groups, and add-ons
- IAM Roles & Policies: Service account roles, pod execution roles
- Managed Databases: MongoDB Atlas with PrivateLink
- Caching: RedisLabs Cloud with PrivateLink
- Storage: S3 buckets with lifecycle policies and replication
- Networking: Route53 private zones, security groups
- Monitoring: CloudWatch, IMDSv2 configurations
Cloudformation/¶
CloudFormation templates for specific components like Karpenter auto-scaling.
🔧 Terraform File Patterns¶
The repository follows a consistent file organization pattern:
Standard Files¶
# providers.tf - Provider configuration
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
# Backend configuration specified in workflows
}
}
# variables.tf - Input variable definitions
variable "region" {
description = "AWS region for deployment"
type = string
}
variable "environment" {
description = "Environment name (dev, staging, prod)"
type = string
}
# locals.tf - Local variables and computations
locals {
common_tags = {
ManagedBy = "Terraform"
Environment = var.environment
Repository = "unibeam-workload-sia-prod-terraform"
}
}
# outputs.tf - Output values
output "eks_cluster_endpoint" {
description = "EKS cluster endpoint"
value = aws_eks_cluster.main.endpoint
}
🎬 GitHub Actions Workflow¶
Workflow Architecture¶
All Terraform operations are executed through GitHub Actions, providing:
- ✅ Consistent execution environment
- ✅ Audit trail for all changes
- ✅ Pull request validation
- ✅ Automated state management
- ✅ Multi-region support
Typical Workflow Structure¶
name: Terraform Deploy
on:
push:
branches: [main]
paths:
- 'managed-services/**'
pull_request:
branches: [main]
paths:
- 'managed-services/**'
workflow_dispatch:
inputs:
component:
description: 'Component to deploy'
required: true
action:
description: 'Terraform action (plan/apply)'
required: true
default: 'plan'
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: ${{ secrets.AWS_TERRAFORM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.5.0
- name: Terraform Init
run: |
terraform init \
-backend-config="bucket=${{ secrets.TF_STATE_BUCKET }}" \
-backend-config="key=${{ inputs.component }}/terraform.tfstate" \
-backend-config="region=${{ vars.AWS_REGION }}"
- name: Terraform Plan
id: plan
run: terraform plan -var-file="vars/${{ vars.ENVIRONMENT }}.tfvars" -out=tfplan
- name: Terraform Apply
if: github.event.inputs.action == 'apply'
run: terraform apply -auto-approve tfplan
Workflow Triggers¶
Deployment Triggers
- Push to Main: Automatic plan generation for review
- Pull Request: Plan preview in PR comments
- Manual Dispatch: On-demand deployments with parameters
- Scheduled: Optional drift detection runs
State Management¶
State Backend
Terraform state is stored in S3 with:
- Encryption: Server-side encryption enabled
- Versioning: State file versioning for rollback
- Locking: DynamoDB table for state locking
- Component Isolation: Separate state files per component
# Backend configuration example
terraform {
backend "s3" {
bucket = "terraform-state-bucket"
key = "managed-services/eks/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-lock"
}
}
🔐 Security Patterns¶
IAM Role Structure¶
The repository implements least privilege principles:
# iam_eks_role.tf - EKS cluster IAM role
resource "aws_iam_role" "eks_cluster" {
name = "${var.cluster_name}-eks-cluster-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "eks.amazonaws.com"
}
}]
})
tags = local.common_tags
}
Security Group Patterns¶
Security groups are organized by environment type:
- DMZ: Public-facing services
- Domain: Internal service communication
- Whitelist: Restricted access patterns
# Example security group pattern
resource "aws_security_group" "eks_nodes" {
name = "${var.cluster_name}-eks-nodes"
description = "Security group for EKS worker nodes"
vpc_id = data.aws_vpc.main.id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(local.common_tags, {
Name = "${var.cluster_name}-eks-nodes"
})
}
IMDSv2 Configuration¶
Metadata Service Security
EC2 instances are configured to require IMDSv2:
# imdsv2.tf
resource "aws_launch_template" "eks_nodes" {
metadata_options {
http_endpoint = "enabled"
http_tokens = "required" # IMDSv2
http_put_response_hop_limit = 1
}
}
🚀 Common Operations¶
Adding a New Service¶
Follow these steps to add a new managed service:
- Create Component File
- Define Resources
# managed-services/components/newservice.tf
resource "aws_service_resource" "example" {
name = "${var.cluster_name}-${var.environment}-service"
# Configuration...
tags = local.common_tags
}
- Add Variables
# managed-services/variables.tf
variable "newservice_enabled" {
description = "Enable new service deployment"
type = bool
default = false
}
- Update Variable Files
- Add Outputs
# managed-services/outputs.tf
output "newservice_endpoint" {
description = "New service endpoint"
value = aws_service_resource.example.endpoint
}
Running Terraform Operations¶
GitHub Actions Execution
All operations should be performed through GitHub Actions:
Via Pull Request:
# Create feature branch
git checkout -b feature/add-newservice
# Make changes
git add managed-services/
# Commit and push
git commit -m "Add newservice configuration"
git push origin feature/add-newservice
# Open PR - automatic plan will be generated
Via Manual Dispatch:
1. Navigate to Actions tab in GitHub
2. Select "Terraform Deploy" workflow
3. Click "Run workflow"
4. Select:
- Component: newservice
- Action: plan or apply
- Environment: prod
Reviewing Plans¶
Plans are automatically posted as PR comments:
Terraform Plan Summary:
+ 5 to add
~ 2 to change
- 0 to destroy
Resources to be added:
+ aws_service_resource.example
+ aws_iam_role.example_role
...
🌍 Multi-Region Deployments¶
Region-Specific Variables¶
Each region has its own variable file:
vars/
├── east-us.tfvars # US East region
├── west-us.tfvars # US West region
└── common.tfvars # Shared variables
# vars/east-us.tfvars
region = "us-east-1"
availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]
cluster_name = "unibeam-prod-east"
Deploying to Multiple Regions¶
State Isolation
Each region maintains separate state files to prevent conflicts.
# GitHub Actions matrix strategy
strategy:
matrix:
region: [east-us, west-us]
steps:
- name: Terraform Apply
run: |
terraform init -backend-config="key=${{ matrix.region }}/terraform.tfstate"
terraform apply -var-file="vars/${{ matrix.region }}.tfvars"
🔍 Monitoring and Validation¶
Pre-Deployment Checks¶
The workflow includes validation steps:
- name: Terraform Format Check
run: terraform fmt -check -recursive
- name: Terraform Validate
run: terraform validate
- name: TFLint
uses: terraform-linters/setup-tflint@v1
run: tflint
Post-Deployment Verification¶
- name: Verify EKS Cluster
if: steps.apply.outcome == 'success'
run: |
aws eks describe-cluster --name ${{ vars.CLUSTER_NAME }}
kubectl get nodes
📋 Best Practices Checklist¶
When working with this repository:
- [x] Always use GitHub Actions for deployments
- [x] Create feature branches for changes
- [x] Review plan output before applying
- [x] Follow existing naming conventions
- [x] Update relevant variable files for all regions
- [x] Add appropriate tags to resources
- [x] Document new components in comments
- [x] Test changes in non-prod environments first
- [x] Use IMDSv2 for EC2 instances
- [x] Implement least privilege IAM policies
🆘 Troubleshooting¶
Common Issues¶
State Lock Errors¶
Resolution
Check DynamoDB for stale locks. Manual unlock may be required:
Provider Authentication¶
Resolution
Verify GitHub Actions has proper AWS credentials and IAM role permissions.
Plan Changes on Every Run¶
Resolution
Check for:
- Computed values in resources
- External changes outside Terraform
- Inconsistent variable values
Debugging Workflows¶
Enable debug logging in GitHub Actions: