Complete Terraform for ML Tutorial: Infrastructure as Code for Machine Learning

# Tutorial Lengkap Terraform untuk ML: Infrastructure as Code untuk Machine Learning Terraform adalah tool Infrastructure as Code (IaC) yang memungkinkan Anda melakukan provisioning dan mengelola clo...

By Ruby Abdullah · · tutorial
TerraformInfrastructure as CodeAWSMLOpsCloudMachine Learning

Complete Terraform for ML Tutorial: Infrastructure as Code for Machine Learning

Terraform is an Infrastructure as Code (IaC) tool that enables you to provision and manage cloud infrastructure declaratively. For ML teams, Terraform helps automate the deployment of training clusters, inference endpoints, and data pipelines.

Why Terraform for ML?

Terraform Advantages:
  • Reproducibility: Same infrastructure every time
  • Version control: Track infrastructure changes
  • Multi-cloud: AWS, GCP, Azure support
  • Modularity: Reusable infrastructure components
  • State management: Track resource state

Use Cases:
  • ML training infrastructure
  • Inference endpoint deployment
  • Data pipeline provisioning
  • Development environments
  • Multi-region deployments

Installation

# macOS

brew tap hashicorp/tap

brew install hashicorp/tap/terraform

Linux

wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsbrelease -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list

sudo apt update && sudo apt install terraform

Verify installation

terraform --version

Quick Start

1. Project Structure

ml-infrastructure/

├── main.tf # Main configuration

├── variables.tf # Input variables

├── outputs.tf # Output values

├── providers.tf # Provider configuration

├── terraform.tfvars # Variable values

└── modules/

├── training/

├── inference/

└── storage/

2. Basic Configuration

# providers.tf

terraform {

requiredproviders {

aws = {

source = "hashicorp/aws"

version = "~> 5.0"

}

}

requiredversion = ">= 1.0"

}

provider "aws" {

region = var.awsregion

}

# variables.tf

variable "awsregion" {

description = "AWS region"

type = string

default = "us-west-2"

}

variable "environment" {

description = "Environment name"

type = string

default = "dev"

}

variable "projectname" {

description = "Project name"

type = string

default = "ml-platform"

}

3. Basic Commands

# Initialize Terraform

terraform init

Format configuration

terraform fmt

Validate configuration

terraform validate

Plan changes

terraform plan

Apply changes

terraform apply

Destroy infrastructure

terraform destroy

S3 for ML Data

1. Data Lake Setup

# modules/storage/main.tf

resource "awss3bucket" "mldata" {

bucket = "${var.projectname}-${var.environment}-ml-data"

tags = {

Environment = var.environment

Project = var.projectname

Purpose = "ML Data Lake"

}

}

resource "awss3bucketversioning" "mldata" {

bucket = awss3bucket.mldata.id

versioningconfiguration {

status = "Enabled"

}

}

resource "awss3bucketlifecycleconfiguration" "mldata" {

bucket = awss3bucket.mldata.id

rule {

id = "archive-old-data"

status = "Enabled"

transition {

days = 90

storageclass = "STANDARDIA"

}

transition {

days = 180

storageclass = "GLACIER"

}

}

}

Folder structure

resource "awss3object" "rawdata" {

bucket = awss3bucket.mldata.id

key = "raw/"

contenttype = "application/x-directory"

}

resource "awss3object" "processeddata" {

bucket = awss3bucket.mldata.id

key = "processed/"

contenttype = "application/x-directory"

}

resource "awss3object" "models" {

bucket = awss3bucket.mldata.id

key = "models/"

contenttype = "application/x-directory"

}

2. Model Registry Bucket

resource "awss3bucket" "modelregistry" {

bucket = "${var.projectname}-${var.environment}-models"

tags = {

Purpose = "Model Registry"

}

}

resource "awss3bucketpublicaccessblock" "modelregistry" {

bucket = awss3bucket.modelregistry.id

blockpublicacls = true

blockpublicpolicy = true

ignorepublicacls = true

restrictpublicbuckets = true

}

EC2 Training Instances

1. GPU Training Instance

# modules/training/main.tf

data "awsami" "deeplearning" {

mostrecent = true

owners = ["amazon"]

filter {

name = "name"

values = ["Deep Learning AMI (Ubuntu 22.04) "]

}

}

resource "awsinstance" "training" {

ami = data.awsami.deeplearning.id

instancetype = var.instancetype # "p3.2xlarge" for GPU

vpcsecuritygroupids = [awssecuritygroup.training.id]

subnetid = var.subnetid

iaminstanceprofile = awsiaminstanceprofile.training.name

rootblockdevice {

volumesize = 200

volumetype = "gp3"

}

userdata = <<-EOF

#!/bin/bash

# Install additional packages

pip install mlflow wandb

# Download training data

aws s3 sync s3://${var.databucket}/processed/ /data/

# Start training (example)

python /opt/ml/train.py

EOF

tags = {

Name = "${var.projectname}-training"

Environment = var.environment

}

}

resource "awssecuritygroup" "training" {

name = "${var.projectname}-training-sg"

description = "Security group for training instances"

vpcid = var.vpcid

egress {

fromport = 0

toport = 0

protocol = "-1"

cidrblocks = ["0.0.0.0/0"]

}

tags = {

Name = "${var.projectname}-training-sg"

}

}

2. Spot Instance for Cost Savings

resource "awsspotinstancerequest" "trainingspot" {

ami = data.awsami.deeplearning.id

instancetype = "p3.2xlarge"

spotprice = "0.90" # Max price per hour

spottype = "one-time"

waitforfulfillment = true

vpcsecuritygroupids = [awssecuritygroup.training.id]

subnetid = var.subnetid

iaminstanceprofile = awsiaminstanceprofile.training.name

rootblockdevice {

volumesize = 200

volumetype = "gp3"

}

tags = {

Name = "${var.projectname}-training-spot"

}

}

SageMaker Resources

1. SageMaker Domain

# modules/sagemaker/main.tf

resource "awssagemakerdomain" "mldomain" {

domainname = "${var.projectname}-${var.environment}"

authmode = "IAM"

vpcid = var.vpcid

subnetids = var.subnetids

defaultusersettings {

executionrole = awsiamrole.sagemakerexecution.arn

jupyterserverappsettings {

defaultresourcespec {

instancetype = "system"

}

}

kernelgatewayappsettings {

defaultresourcespec {

instancetype = "ml.t3.medium"

}

}

}

tags = {

Environment = var.environment

Project = var.projectname

}

}

resource "awssagemakeruserprofile" "datascientist" {

domainid = awssagemakerdomain.mldomain.id

userprofilename = "data-scientist"

usersettings {

executionrole = awsiamrole.sagemakerexecution.arn

}

}

2. SageMaker Training Job

resource "awssagemakertrainingjob" "modeltraining" {

trainingjobname = "${var.projectname}-${var.modelversion}"

rolearn = awsiamrole.sagemakerexecution.arn

algorithmspecification {

trainingimage = var.trainingimage

traininginputmode = "File"

}

resourceconfig {

instancecount = 1

instancetype = "ml.p3.2xlarge"

volumesizeingb = 50

}

inputdataconfig {

channelname = "training"

datasource {

s3datasource {

s3uri = "s3://${var.databucket}/processed/train/"

s3datatype = "S3Prefix"

}

}

}

outputdataconfig {

s3outputpath = "s3://${var.modelbucket}/output/"

}

stoppingcondition {

maxruntimeinseconds = 86400 # 24 hours

}

hyperparameters = {

epochs = "100"

learningrate = "0.001"

batchsize = "32"

}

}

3. SageMaker Endpoint

resource "awssagemakermodel" "inferencemodel" {

name = "${var.projectname}-model-${var.modelversion}"

executionrolearn = awsiamrole.sagemakerexecution.arn

primarycontainer {

image = var.inferenceimage

modeldataurl = "s3://${var.modelbucket}/models/${var.modelversion}/model.tar.gz"

environment = {

SAGEMAKERPROGRAM = "inference.py"

}

}

tags = {

ModelVersion = var.modelversion

}

}

resource "awssagemakerendpointconfiguration" "inference" {

name = "${var.projectname}-endpoint-config"

productionvariants {

variantname = "primary"

modelname = awssagemakermodel.inferencemodel.name

initialinstancecount = var.endpointinstancecount

instancetype = var.endpointinstancetype

initialvariantweight = 1

}

# Enable data capture

datacaptureconfig {

enablecapture = true

initialsamplingpercentage = 100

destinations3uri = "s3://${var.databucket}/endpoint-data-capture/"

captureoptions {

capturemode = "Input"

}

captureoptions {

capturemode = "Output"

}

}

}

resource "awssagemakerendpoint" "inference" {

name = "${var.projectname}-endpoint"

endpointconfigname = awssagemakerendpointconfiguration.inference.name

tags = {

Environment = var.environment

}

}

EKS for ML Workloads

1. EKS Cluster

# modules/eks/main.tf

module "eks" {

source = "terraform-aws-modules/eks/aws"

version = "~> 19.0"

clustername = "${var.projectname}-${var.environment}"

clusterversion = "1.28"

vpcid = var.vpcid

subnetids = var.privatesubnetids

clusterendpointpublicaccess = true

eksmanagednodegroups = {

# CPU nodes for general workloads

cpunodes = {

name = "cpu-nodes"

instancetypes = ["m5.xlarge"]

minsize = 1

maxsize = 10

desiredsize = 2

labels = {

workload = "general"

}

}

# GPU nodes for training

gpunodes = {

name = "gpu-nodes"

instancetypes = ["p3.2xlarge"]

minsize = 0

maxsize = 5

desiredsize = 0

amitype = "AL2x8664GPU"

labels = {

workload = "gpu"

"nvidia.com/gpu" = "true"

}

taints = [{

key = "nvidia.com/gpu"

value = "true"

effect = "NOSCHEDULE"

}]

}

}

tags = {

Environment = var.environment

Project = var.projectname

}

}

Install NVIDIA device plugin

resource "kubernetesdaemonset" "nvidiadeviceplugin" {

dependson = [module.eks]

metadata {

name = "nvidia-device-plugin-daemonset"

namespace = "kube-system"

}

spec {

selector {

matchlabels = {

name = "nvidia-device-plugin-ds"

}

}

template {

metadata {

labels = {

name = "nvidia-device-plugin-ds"

}

}

spec {

toleration {

key = "nvidia.com/gpu"

operator = "Exists"

effect = "NoSchedule"

}

container {

name = "nvidia-device-plugin-ctr"

image = "nvcr.io/nvidia/k8s-device-plugin:v0.14.0"

securitycontext {

allowprivilegeescalation = false

capabilities {

drop = ["ALL"]

}

}

volumemount {

name = "device-plugin"

mountpath = "/var/lib/kubelet/device-plugins"

}

}

volume {

name = "device-plugin"

hostpath {

path = "/var/lib/kubelet/device-plugins"

}

}

}

}

}

}

2. ML Workload on Kubernetes

# Training Job

resource "kubernetesjob" "trainingjob" {

metadata {

name = "model-training-${var.modelversion}"

namespace = "ml-workloads"

}

spec {

template {

metadata {

labels = {

app = "model-training"

}

}

spec {

nodeselector = {

"nvidia.com/gpu" = "true"

}

toleration {

key = "nvidia.com/gpu"

operator = "Exists"

effect = "NoSchedule"

}

container {

name = "training"

image = var.trainingimage

resources {

limits = {

"nvidia.com/gpu" = "1"

memory = "32Gi"

cpu = "8"

}

}

env {

name = "S3DATAPATH"

value = "s3://${var.databucket}/processed/"

}

env {

name = "S3MODELPATH"

value = "s3://${var.modelbucket}/models/"

}

}

restartpolicy = "Never"

}

}

backofflimit = 2

}

}

IAM Roles for ML

1. SageMaker Execution Role

# modules/iam/main.tf

resource "awsiamrole" "sagemakerexecution" {

name = "${var.projectname}-sagemaker-execution"

assumerolepolicy = jsonencode({

Version = "2012-10-17"

Statement = [{

Action = "sts:AssumeRole"

Effect = "Allow"

Principal = {

Service = "sagemaker.amazonaws.com"

}

}]

})

}

resource "awsiamrolepolicy" "sagemakers3" {

name = "sagemaker-s3-access"

role = awsiamrole.sagemakerexecution.id

policy = jsonencode({

Version = "2012-10-17"

Statement = [

{

Effect = "Allow"

Action = [

"s3:GetObject",

"s3:PutObject",

"s3:DeleteObject",

"s3:ListBucket"

]

Resource = [

"arn:aws:s3:::${var.databucket}",

"arn:aws:s3:::${var.databucket}/",

"arn:aws:s3:::${var.modelbucket}",

"arn:aws:s3:::${var.modelbucket}/"

]

},

{

Effect = "Allow"

Action = [

"logs:CreateLogGroup",

"logs:CreateLogStream",

"logs:PutLogEvents"

]

Resource = "arn:aws:logs:::"

},

{

Effect = "Allow"

Action = [

"ecr:GetDownloadUrlForLayer",

"ecr:BatchGetImage",

"ecr:BatchCheckLayerAvailability"

]

Resource = "*"

}

]

})

}

2. Training Instance Role

resource "awsiamrole" "traininginstance" {

name = "${var.projectname}-training-instance"

assumerolepolicy = jsonencode({

Version = "2012-10-17"

Statement = [{

Action = "sts:AssumeRole"

Effect = "Allow"

Principal = {

Service = "ec2.amazonaws.com"

}

}]

})

}

resource "awsiaminstanceprofile" "training" {

name = "${var.projectname}-training-profile"

role = awsiamrole.traininginstance.name

}

resource "awsiamrolepolicyattachment" "trainings3" {

role = awsiamrole.traininginstance.name

policyarn = "arn:aws:iam::aws:policy/AmazonS3FullAccess"

}

Complete ML Platform Module

# main.tf

module "storage" {

source = "./modules/storage"

projectname = var.projectname

environment = var.environment

}

module "networking" {

source = "./modules/networking"

projectname = var.projectname

environment = var.environment

vpccidr = var.vpccidr

}

module "sagemaker" {

source = "./modules/sagemaker"

projectname = var.projectname

environment = var.environment

vpcid = module.networking.vpcid

subnetids = module.networking.privatesubnetids

databucket = module.storage.databucketid

modelbucket = module.storage.modelbucketid

}

module "eks" {

source = "./modules/eks"

projectname = var.projectname

environment = var.environment

vpcid = module.networking.vpcid

privatesubnetids = module.networking.privatesubnetids

}

outputs.tf

output "sagemakerendpoint" {

value = module.sagemaker.endpointurl

}

output "eksclusterendpoint" {

value = module.eks.clusterendpoint

}

output "databucket" {

value = module.storage.databucketid

}

CI/CD Integration

1. GitHub Actions

# .github/workflows/terraform.yml

name: Terraform

on:

push:

branches: [main]

pullrequest:

branches: [main]

jobs:

terraform:

runs-on: ubuntu-latest

steps:

  • uses: actions/checkout@v3
  • name: Setup Terraform
uses: hashicorp/setup-terraform@v2

  • name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2

with:

aws-access-key-id: ${{ secrets.AWSACCESSKEYID }}

aws-secret-access-key: ${{ secrets.AWSSECRETACCESSKEY }}

aws-region: us-west-2

  • name: Terraform Init
run: terraform init

  • name: Terraform Format
run: terraform fmt -check

  • name: Terraform Plan
run: terraform plan -out=tfplan

  • name: Terraform Apply
if: github.ref == 'refs/heads/main'

run: terraform apply -auto-approve tfplan

2. Remote State

# backend.tf

terraform {

backend "s3" {

bucket = "my-terraform-state-bucket"

key = "ml-platform/terraform.tfstate"

region = "us-west-2"

dynamodbtable = "terraform-locks"

encrypt = true

}

}

Create state resources

resource "awss3bucket" "terraformstate" {

bucket = "my-terraform-state-bucket"

lifecycle {

preventdestroy = true

}

}

resource "awsdynamodbtable" "terraformlocks" {

name = "terraform-locks"

billingmode = "PAYPERREQUEST"

hashkey = "LockID"

attribute {

name = "LockID"

type = "S"

}

}

Best Practices

1. Use Workspaces

# Create workspaces

terraform workspace new dev

terraform workspace new staging

terraform workspace new prod

Switch workspace

terraform workspace select prod

Use in configuration

locals {

environment = terraform.workspace

}

2. Module Versioning

module "mlplatform" {

source = "git::https://github.com/org/terraform-ml-platform.git?ref=v1.2.0"

projectname = var.projectname

environment = var.environment

}

3. Cost Tags

locals {

commontags = {

Project = var.projectname

Environment = var.environment

ManagedBy = "terraform"

CostCenter = var.costcenter

Owner = var.owneremail

}

}

resource "awsinstance" "example" {

# ... configuration ...

tags = merge(local.commontags, {

Name = "example-instance"

})

}

Conclusion

Terraform is essential for ML infrastructure with:

  • Reproducibility: Consistent environments
  • Version control: Track infrastructure changes
  • Modularity: Reusable components
  • Multi-cloud: AWS, GCP, Azure support
  • CI/CD integration: Automated deployments
  • Key takeaways:

    • Use modules for reusability
    • Implement remote state with locking
    • Tag resources for cost tracking
    • Use workspaces for environments
    • Integrate with CI/CD pipelines

    Related Articles

    Complete AWS SageMaker Tutorial: Machine Learning in the Cloud

    Tutorial Lengkap AWS SageMaker: End-to-End ML Pipeline Amazon SageMaker adalah layanan machine learning terkelola penuh ...

    ZenML: Build Portable, Production-Ready ML and LLM Pipelines

    ZenML: Bikin Pipeline ML dan LLM yang Portable dan Siap Produksi Halo temen-temen, ketemu lagi sama aku, Ruby Abdullah. ...

    Complete Replicate Tutorial: Run and Deploy ML Models via API

    Tutorial Lengkap Replicate: Menjalankan dan Deploy Model ML via API Replicate adalah platform cloud yang memungkinkan An...

    Complete Comet ML Tutorial: MLOps Platform for Experiment Tracking and Model Management

    Tutorial Lengkap Comet ML: Platform MLOps untuk Experiment Tracking dan Model Management Dalam dunia machine learning mo...