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 cloud infrastructure secara deklaratif. Untuk tim ML, Terraform membantu mengotomatisasi deployment training clusters, inference endpoints, dan data pipelines.
Mengapa Terraform untuk ML?
Keunggulan Terraform:- Reproducibility: Infrastructure sama setiap waktu
- Version control: Track perubahan infrastructure
- Multi-cloud: Support AWS, GCP, Azure
- Modularity: Reusable infrastructure components
- State management: Track resource state
- ML training infrastructure
- Inference endpoint deployment
- Data pipeline provisioning
- Development environments
- Multi-region deployments
Instalasi
# 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 untuk 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.project
name}-${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" untuk 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 (contoh)
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 untuk 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 untuk Cost Savings
resource "awsspotinstancerequest" "trainingspot" {
ami = data.aws
ami.deeplearning.id
instance
type = "p3.2xlarge"
spotprice = "0.90" # Max price per jam
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" {
training
jobname = "${var.projectname}-${var.modelversion}"
role
arn = awsiamrole.sagemakerexecution.arn
algorithm
specification {
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 jam
}
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 untuk 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 untuk general workloads
cpunodes = {
name = "cpu-nodes"
instancetypes = ["m5.xlarge"]
minsize = 1
maxsize = 10
desiredsize = 2
labels = {
workload = "general"
}
}
# GPU nodes untuk 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 di 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 untuk 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
}
Integrasi CI/CD
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
}
}
Buat 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. Gunakan Workspaces
# Buat workspaces
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
Switch workspace
terraform workspace select prod
Gunakan di 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"
})
}
Kesimpulan
Terraform adalah essential untuk ML infrastructure dengan:
Key takeaways:
- Gunakan modules untuk reusability
- Implement remote state dengan locking
- Tag resources untuk cost tracking
- Gunakan workspaces untuk environments
- Integrate dengan CI/CD pipelines