Skip to content

🏗️ 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"
  }
}
# data.tf - Data sources
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
# 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:

  1. Create Component File
# Create new component file
touch managed-services/components/newservice.tf
  1. Define Resources
# managed-services/components/newservice.tf
resource "aws_service_resource" "example" {
  name = "${var.cluster_name}-${var.environment}-service"
  
  # Configuration...
  
  tags = local.common_tags
}
  1. Add Variables
# managed-services/variables.tf
variable "newservice_enabled" {
  description = "Enable new service deployment"
  type        = bool
  default     = false
}
  1. Update Variable Files
# vars/prod.tfvars
newservice_enabled = true
  1. 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

Error: Error acquiring the state lock

Resolution

Check DynamoDB for stale locks. Manual unlock may be required:

terraform force-unlock <LOCK_ID>

Provider Authentication

Error: error configuring Terraform AWS Provider

Resolution

Verify GitHub Actions has proper AWS credentials and IAM role permissions.

Plan Changes on Every Run

~ resource "aws_resource" "example"

Resolution

Check for:

  • Computed values in resources
  • External changes outside Terraform
  • Inconsistent variable values

Debugging Workflows

Enable debug logging in GitHub Actions:

env:
  TF_LOG: DEBUG
  ACTIONS_STEP_DEBUG: true

🔗 Additional Resources