I denne øvelsen skal du hoste en React-applikasjon som viser kryptovaluta-informasjon på en morsom og interaktiv måte. Applikasjonen er allerede bygget og klar til å deployes - din jobb er å lære hvordan man bruker Terraform for å sette opp infrastrukturen for å hoste den på AWS.
Du vil bruke Infrastructure as Code (IaC) for å automatisere hele prosessen med å sette opp:
- En S3 bucket for å hoste nettsiden
- CloudFront CDN for global distribusjon og HTTPS
- DNS-konfigurasjon for custom domenenavn
- CI/CD pipeline for automatisk deployment
Gjennom denne øvelsen vil du mestre:
- Terraform grunnleggende: Ressurser, variabler, outputs og state management
- AWS S3 Website Hosting: Konfigurasjon av S3 buckets for statiske nettsider
- CloudFront CDN: Global distribusjon med HTTPS og caching
- Terraform-moduler: Bygge gjenbrukbar infrastruktur-kode
- Remote State: Håndtere Terraform state i team-miljøer
- CI/CD med GitHub Actions: Automatisere infrastruktur-deployment
- Infrastructure as Code: Best practices for å versjonere og administrere infrastruktur
- Fork dette repositoriet til din egen GitHub-konto
- Åpne Codespace: Klikk på "Code" → "Codespaces" → "Create codespace on main"
- Vent på at Codespace starter: Dette kan ta et par minutter første gang
- Terminalvindu: Du vil utføre de fleste kommandoer i terminalen som åpner seg nederst i Codespace
- AWS Credentials. Kjør
aws configureog legg inn AWS aksessnøkler.
Ekspert tips: Trykk . (punktum) når du er i et GitHub repository for å åpne det direkte i en nettleser-basert VS Code editor. Dette er raskere enn å starte en full Codespace og perfekt for raske editeringer!
Repositoriet er allerede klonet i ditt Codespace. Verifiser at du er i riktig mappe:
pwd
lsDu skal se filene fra dette repositoriet, inkludert mappen s3_demo_website.
Nå skal du bygge opp Terraform-konfigurasjonen fra bunnen av. Du vil lære om de ulike AWS S3-ressursene som trengs for å hoste en statisk nettside.
- Opprett
providers.tfi rotmappen av prosjektet:
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "eu-west-1"
}
# Alias provider for us-east-1
# Nødvendig for CloudFront ACM-sertifikater senere i oppgaven
provider "aws" {
alias = "us-east-1"
region = "us-east-1"
}Forklaring:
required_versionsikrer at Terraform-versjonen er minst 1.0required_providersspesifiserer AWS provider versjon (~> 5.0 betyr versjon 5.x)- Default provider bruker
eu-west-1 - Aliased provider for
us-east-1vil brukes senere for ACM-sertifikater (se Appendix A)
-
Opprett
main.tfi rotmappen av prosjektet -
Opprett S3 bucket-ressursen med et hardkodet bucket-navn (erstatt
<unikt-bucket-navn>med ditt eget unike navn, f.eks. dine initialer eller studentnummer):
Viktig om bucket-navn:
- S3 bucket-navn må være globalt unike på tvers av alle AWS-kontoer i hele verden
- Hvis noen andre allerede bruker navnet "my-website", kan du ikke bruke det samme navnet
- Bruk derfor noe unikt som dine initialer, studentnummer, eller en kombinasjon:
glennbech-pgr301-website - Det er også strenge regler for formatet: https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html
resource "aws_s3_bucket" "website" {
bucket = "unikt-bucket-navn" # Bytt til noe globalt unikt
}- Konfigurer S3 bucket for website hosting:
resource "aws_s3_bucket_website_configuration" "website" {
bucket = aws_s3_bucket.website.id
index_document {
suffix = "index.html"
}
error_document {
key = "error.html"
}
}- Åpne bucketen for offentlig tilgang (nødvendig for static websites):
resource "aws_s3_bucket_public_access_block" "website" {
bucket = aws_s3_bucket.website.id
block_public_acls = false
block_public_policy = false
ignore_public_acls = false
restrict_public_buckets = false
}- Legg til en bucket policy som tillater offentlig lesing:
resource "aws_s3_bucket_policy" "website" {
bucket = aws_s3_bucket.website.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "PublicReadGetObject"
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.website.arn}/*"
}
]
})
depends_on = [aws_s3_bucket_public_access_block.website]
}- Legg til en output for å få URL-en til nettsiden:
output "s3_website_url" {
value = "http://${aws_s3_bucket.website.bucket}.s3-website.${aws_s3_bucket.website.region}.amazonaws.com"
description = "URL for the S3 hosted website"
}Nå er du klar til å deploye infrastrukturen. Sørg for at du har erstattet unikt-bucket-navn i main.tf med ditt eget unike navn.
terraform init
terraform applyFør vi kan laste opp nettsiden til S3, må vi bygge React-applikasjonen. Dette kompilerer TypeScript-koden og optimaliserer alle assets for produksjon.
- Naviger til applikasjonsmappen:
cd s3_demo_website- Installer dependencies (hvis ikke allerede gjort):
npm install- Bygg applikasjonen:
npm run buildDette vil opprette en dist-mappe med den ferdige produksjonsklare nettsiden.
- Gå tilbake til rotmappen:
cd ..Nå kan vi laste opp den bygde nettsiden til S3 bucketen ved hjelp av AWS CLI:
aws s3 sync s3_demo_website/dist s3://unikt-bucket-navnLegg merke til at vi synkroniserer dist-mappen, ikke hele s3_demo_website-mappen. dist-mappen inneholder kun de optimaliserte filene som trengs for produksjon.
Gå til AWS Console, og tjenesten S3, og se på objekter og bucket-egenskaper for å forstå hvordan alt er satt opp.
Hent URL-en til nettsiden:
terraform output s3_website_urlÅpne URL-en i nettleseren for å se din statiske nettside.
Nå som du har fått infrastrukturen til å fungere med hardkodet bucket-navn, skal vi gjøre konfigurasjonen mer fleksibel ved å introdusere variabler.
- Legg til en variabel for bucket-navnet øverst i
main.tf:
variable "bucket_name" {
description = "The name of the S3 bucket"
type = string
}- Erstatt det hardkodede bucket-navnet i S3 bucket-ressursen:
resource "aws_s3_bucket" "website" {
bucket = var.bucket_name # Endret fra hardkodet verdi
}- Apply endringene med variabelen:
terraform apply -var 'bucket_name=ditt_bucket_navn'Terraform vil nå vise at det ikke er nødvendig med endringer, siden bucket-navnet er det samme.
Fordelen med variabler: Du kan nå enkelt endre bucket-navnet uten å redigere koden, og gjenbruke samme konfigurasjon for flere miljøer.
I stedet for å måtte oppgi verdier på kommandolinjen hver gang, kan du sette default-verdier for variabler. Dette gjør det enklere å jobbe med Terraform i daglig bruk.
- Oppdater variabelen med en default-verdi:
variable "bucket_name" {
description = "The name of the S3 bucket"
type = string
default = "ditt-bucket-navn" # Erstatt med ditt eget unike navn
}- Apply uten å spesifisere variabel:
terraform applyTerraform vil nå bruke default-verdien uten at du må oppgi den på kommandolinjen.
Best practice: Bruk default-verdier for variabler som sjelden endres, men la kritiske verdier (som bucket-navn i produksjon) være uten default for å sikre at de blir eksplisitt satt.
Prøv å endre HTML- og CSS-filene i s3_demo_website-mappen, og kjør sync-kommandoen på nytt for å se endringene:
aws s3 sync s3_demo_website/dist s3://unikt-bucket-navnDu har nå deployet og håndtert en statisk nettside på AWS ved hjelp av Terraform og AWS CLI.
I denne delen skal vi utvide infrastrukturen med mer avanserte Terraform-konsepter. Du vil lære om:
- Remote state management for team-samarbeid
- Terraform-moduler for gjenbrukbar infrastruktur
- CloudFront CDN for global distribusjon
- Automatisering med GitHub Actions
Når flere personer jobber med samme infrastruktur, eller når vi skal automatisere med CI/CD, trenger vi en felles plass å lagre Terraform state. Lokal state fungerer ikke i team-miljøer.
Først må vi lage en S3 bucket og DynamoDB-tabell for state management. Disse må opprettes før vi konfigurerer backend.
- Opprett en ny fil
backend-setup.tfi rotmappen:
# This file creates the resources needed for Terraform remote state
# Run this FIRST before configuring the backend
data "aws_region" "current" {}
resource "aws_s3_bucket" "terraform_state" {
bucket = "ditt-navn-terraform-state" # Bytt til unikt navn
tags = {
Name = "Terraform State"
Environment = "Infrastructure"
}
}
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_dynamodb_table" "terraform_locks" {
name = "ditt-navn-terraform-state-locks" # Bytt til unikt navn
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
tags = {
Name = "Terraform State Locks"
}
}
output "backend_config" {
value = <<-EOT
backend "s3" {
bucket = "${aws_s3_bucket.terraform_state.id}"
key = "website/terraform.tfstate"
region = "${data.aws_region.current.name}"
dynamodb_table = "${aws_dynamodb_table.terraform_locks.id}"
encrypt = true
}
EOT
description = "Backend configuration to add to your terraform block"
}- Deploy backend-ressursene:
terraform applyMerk output som viser backend-konfigurasjonen du skal bruke.
- Opprett fil
backend.tfi rotmappen.
Du kan kopiere backend-konfigurasjonen fra output av forrige terraform apply, eller skrive den manuelt:
terraform {
backend "s3" {
bucket = "ditt-navn-terraform-state" # Samme som i backend-setup.tf
key = "website/terraform.tfstate"
region = "eu-west-1" # Din region
dynamodb_table = "ditt-navn-terraform-state-locks" # Samme som i backend-setup.tf
encrypt = true
}
}- Migrer state til remote backend:
terraform init -migrate-stateTerraform vil spørre om du vil kopiere eksisterende state til det nye backend. Svar yes.
- Verifiser:
- Gå til S3 Console og se at state-filen er lastet opp
- Din lokale
terraform.tfstateskal nå være tom eller borte
Gratulerer! State er nå lagret sentralt. Hvis flere personer jobber på samme prosjekt, vil de alle dele samme state. I tillegg er dere nå beskyttet mot at flere gjør terraform apply samtidig - DynamoDB-tabellen sørger for state locking slik at kun én person kan gjøre endringer om gangen.
Du kan teste state locking ved å åpne to terminaler og prøve å kjøre terraform apply i begge samtidig:
- Terminal 1: Kjør
terraform applyog bekreft medyes - Terminal 2: Kjør raskt
terraform applymens Terminal 1 fortsatt jobber
Tips: Du må være litt rask, siden terraform apply går ganske fort når det ikke er mange endringer!
Du vil se at Terminal 2 får en feilmelding om at state er låst, med informasjon om hvem som holder låsen. Dette forhindrer at to personer gjør motstridende endringer samtidig.
Moduler er Terraforms måte å pakke og gjenbruke infrastruktur-kode på. I stedet for å copy-paste kode, lager vi en modul som kan brukes flere steder med ulike konfigurasjoner.
Analogi: En modul er som en funksjon i programmering - den tar inputs (variabler), utfører operasjoner (ressurser), og returnerer outputs.
En typisk Terraform-modul består av følgende filer:
modules/s3-website/
├── main.tf # Hovedressurser (S3, CloudFront, etc.)
├── variables.tf # Input-variabler som modulen tar imot
├── outputs.tf # Output-verdier som modulen returnerer
└── versions.tf # Provider requirements (valgfri)
Fordeler med moduler:
- Gjenbrukbarhet: Samme modul kan brukes i flere prosjekter eller miljøer
- Abstrahering: Skjuler kompleksitet bak et enkelt grensesnitt
- Standardisering: Sikrer konsistent infrastruktur på tvers av prosjekter
- Vedlikehold: Endringer på ett sted propagerer til alle bruksområder
- Hovedmodul: Hovedkonfigurasjonen i prosjektets rotmappe
- Undermodul: Gjenbrukbare moduler i
modules/-mappen
Hovedmodulen kaller undermoduler og sender inn verdier via variabler:
# Hovedmodul (main.tf)
module "s3_website" {
source = "./modules/s3-website" # Peker til modul-mappen
# Variabler som sendes til modulen
bucket_name = "min-bucket"
subdomain = "mitt-navn"
}
# Outputs fra modulen brukes slik:
output "website_url" {
value = module.s3_website.cloudfront_url
}I denne oppgaven bruker vi to AWS providers (multi-region setup):
- Default provider i
eu-west-1for S3, CloudFront, etc. - Aliased provider i
us-east-1for ACM-sertifikater (CloudFront-krav)
Viktig: Du må sende begge providers til modulen eksplisitt. Se Appendix A: Provider Configuration for detaljert forklaring av hvordan dette fungerer.
I rot-nivå main.tf, send providers til modulen:
module "s3_website" {
source = "./modules/s3-website"
providers = {
aws = aws # Default provider
aws.us-east-1 = aws.us-east-1 # Aliased provider
}
bucket_name = var.bucket_name
subdomain = var.subdomain
}Lag mappestrukturen for modulen slik:
mkdir -p modules/s3-website
touch modules/s3-website/main.tf
touch modules/s3-website/variables.tf
touch modules/s3-website/outputs.tfDette gir følgende struktur:
modules/
└── s3-website/
├── main.tf
├── variables.tf
└── outputs.tf
Variabler er det som gjør moduler gjenbrukbare - de lar deg bruke samme modul med ulike verdier for forskjellige miljøer eller brukstilfeller. Uten variabler ville modulen alltid opprette de samme ressursene med de samme verdiene, noe som ville gjøre den ubrukelig for gjenbruk.
Fyll inn modules/s3-website/variables.tf:
variable "bucket_name" {
description = "Name of the S3 bucket"
type = string
}
variable "tags" {
description = "Tags to apply to resources"
type = map(string)
default = {}
}
Flytt S3-ressursene fra root main.tf til modules/s3-website/main.tf:
- Kopier alle S3-relaterte ressurser (
aws_s3_bucket,aws_s3_bucket_website_configuration, etc.) - Erstatt hardkodede verdier med
var.bucket_name,var.tags, etc.
Hint: I modulen skal du bruke var.bucket_name direkte.
Fyll inn modules/s3-website/outputs.tf:
data "aws_region" "current" {}
output "bucket_name" {
description = "Name of the S3 bucket"
value = aws_s3_bucket.website.id
}
output "website_url" {
description = "URL of the S3 website"
value = "http://${aws_s3_bucket.website.bucket}.s3-website.${data.aws_region.current.name}.amazonaws.com"
}
output "bucket_arn" {
description = "ARN of the S3 bucket"
value = aws_s3_bucket.website.arn
}
Før vi refaktorerer koden til å bruke modulen, må vi håndtere et kritisk problem:
Når du flytter ressurser fra root til en modul, endrer adressene seg:
- Gammel adresse:
aws_s3_bucket.website - Ny adresse:
module.s3_website.aws_s3_bucket.website
Terraform vil tro at du vil:
- Slette de gamle ressursene
- Opprette nye ressurser med samme konfigurasjon
Resultat: Din S3 bucket blir slettet og gjenskapt!
"Ignorance is bliss" - Start på nytt med modulen.
- Tøm bucketen:
aws s3 rm s3://ditt-bucket-navn --recursive- Destroy eksisterende infrastruktur:
terraform destroy- Fortsett til Del B og bygg opp igjen med modul
Fordel: Enkelt og greit Ulempe: Du mister eksisterende data (ok for demo)
"I want to see how deep the rabbit hole goes" - Lær Terraform state management!
Terraform har en innebygd måte å håndtere refactoring: moved blocks.
Steg 1: Legg til moved blocks i rot-nivå main.tf (ikke
modulens main.tf) som forteller Terraform hvor ressursene skal flyttes.
Disse moved blokkene legges til øverst i rot-nivå main.tf, før de eksisterende ressursene:
# Legg til i rot-nivå main.tf FØR du sletter de gamle ressursene
moved {
from = aws_s3_bucket.website
to = module.s3_website.aws_s3_bucket.website
}
moved {
from = aws_s3_bucket_website_configuration.website
to = module.s3_website.aws_s3_bucket_website_configuration.website
}
moved {
from = aws_s3_bucket_public_access_block.website
to = module.s3_website.aws_s3_bucket_public_access_block.website
}
moved {
from = aws_s3_bucket_policy.website
to = module.s3_website.aws_s3_bucket_policy.website
}Steg 2: I rot-nivå main.tf, erstatt alle S3-ressursene med et modul-kall.
slett alle S3-ressursene og erstatt med et modul-kall.
module "s3_website" {
source = "./modules/s3-website"
bucket_name = "ditt-bucket-navn"
tags = {
Name = "Crypto Juice Exchange"
Environment = "Demo"
ManagedBy = "Terraform"
}
}Etter denne endringen skal rot-nivå main.tf kun inneholde:
movedblokkene fra Steg 1- Modul-kallet nedenfor
- Eventuelle outputs (som oppdateres i Steg 3)
Steg 3: Oppdater outputs i root main.tf til å bruke module outputs:
output "s3_website_url" {
value = module.s3_website.website_url
description = "URL for the S3 hosted website"
}
output "bucket_name" {
value = module.s3_website.bucket_name
description = "Name of the S3 bucket"
}Steg 4: Re-initialiser Terraform for modulen:
terraform initSteg 5: Kjør plan og observer at ressursene flyttes uten å bli gjenskapt:
terraform planDu skal nå se linjer som bekrefter at ressursene flyttes i state, for eksempel:
# aws_s3_bucket.website has moved to module.s3_website.aws_s3_bucket.website
# aws_s3_bucket_website_configuration.website has moved to module.s3_website.aws_s3_bucket_website_configuration.website
# aws_s3_bucket_public_access_block.website has moved to module.s3_website.aws_s3_bucket_public_access_block.website
# aws_s3_bucket_policy.website has moved to module.s3_website.aws_s3_bucket_policy.website
Terraform vil konkludere med: "No changes. Your infrastructure matches the configuration."
Dette betyr at moved blokkene fungerte - ressursene blir flyttet i state uten å bli slettet og gjenskapt!
Steg 6: Apply for å oppdatere state:
terraform applySteg 7: Når alt fungerer, kan du fjerne moved blocks (de trengs ikke lenger)
Du er nå ferdig! Red Pill-brukere kan hoppe over Del B nedenfor og gå videre til Del 3: CloudFront CDN.
Fordel: Lær avansert Terraform, ingen downtime Ulempe: Krever mer forståelse
Velg din vei: Blue Pill-brukere fortsetter til Del B nedenfor. Red Pill-brukere hopper til Del 3.
Denne seksjonen er kun for Blue Pill-brukere som valgte å starte på nytt.
Nå skal du refaktorere root main.tf til å bruke modulen du nettopp laget.
- I root
main.tf: Erstatt alle S3-ressursene med et modul-kall:
module "s3_website" {
source = "./modules/s3-website"
bucket_name = "ditt-bucket-navn"
tags = {
Name = "Crypto Juice Exchange"
Environment = "Demo"
ManagedBy = "Terraform"
}
}- Oppdater outputs i root
main.tftil å bruke module outputs:
output "s3_website_url" {
value = module.s3_website.website_url
description = "URL for the S3 hosted website"
}
output "bucket_name" {
value = module.s3_website.bucket_name
description = "Name of the S3 bucket"
}- Test konfigurasjonen:
terraform init # Re-initialiser for modulen
terraform plan
terraform applyForventet resultat: Terraform skal si at det ikke er noen endringer nødvendig (hvis du har flyttet alt riktig).
- Kan du legge til en
enable_versioningvariable i modulen som gjør versioning optional? - Hint: Bruk
countellerfor_eachbasert på variabelen
resource "aws_s3_bucket_versioning" "website" {
count = var.enable_versioning ? 1 : 0
bucket = aws_s3_bucket.website.id
# ...
}S3 website hosting er bra, men har begrensninger:
- Ingen HTTPS support
- Ikke globalt distribuert (slow for brukere langt fra bucket region)
- Ingen custom domain uten ekstra setup
CloudFront løser alt dette, og krever overraskende lite kode!
Utvid modules/s3-website/main.tf med CloudFront:
# ============================================
# CloudFront Distribution for Global CDN
# ============================================
resource "aws_cloudfront_distribution" "website" {
enabled = true
default_root_object = "index.html"
origin {
domain_name = aws_s3_bucket_website_configuration.website.website_endpoint
origin_id = "S3-${var.bucket_name}"
custom_origin_config {
origin_protocol_policy = "http-only"
http_port = 80
https_port = 443
origin_ssl_protocols = ["TLSv1.2"]
}
}
default_cache_behavior {
target_origin_id = "S3-${var.bucket_name}"
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
min_ttl = 0
default_ttl = 0 # Instant refresh - ingen caching
max_ttl = 0
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}Utvid modules/s3-website/outputs.tf:
output "cloudfront_url" {
description = "CloudFront distribution URL (HTTPS enabled)"
value = "https://${aws_cloudfront_distribution.website.domain_name}"
}
output "cloudfront_domain" {
description = "CloudFront domain name"
value = aws_cloudfront_distribution.website.domain_name
}I root main.tf, legg til CloudFront output:
output "cloudfront_url" {
value = module.s3_website.cloudfront_url
description = "CloudFront URL with HTTPS"
}terraform applyMerk: CloudFront deployment tar 5-15 minutter. Dette er normalt!
terraform output cloudfront_urlÅpne URL-en i nettleseren. Legg merke til:
- HTTPS fungerer automatisk
- URL-en er global (CloudFront, ikke region-spesifikk)
Imponerende enkelt, ikke sant? Med ~40 linjer kode har du global CDN med HTTPS!
Du har nå lært:
- Remote State Management: State deling i team og CI/CD
- Terraform-moduler: Gjenbrukbar, DRY infrastruktur-kode
- CloudFront CDN: Global distribusjon med HTTPS, minimal kode
- State Management med moved blocks: Refaktorering uten downtime
Neste steg: Utforsk bonusoppgavene nedenfor for å lære enda mer om GitHub Actions CI/CD, custom domains, og avanserte Terraform-konsepter!
Så langt har vi kun brukt resource blokker som oppretter nye ressurser i AWS. Men hva hvis vi vil bruke noe som allerede eksisterer? Det er her data sources kommer inn.
Data sources lar deg lese informasjon om eksisterende ressurser uten å endre dem. Tenk på det som:
resource= "Opprett dette" (write)data= "Hent info om dette" (read-only)
Vi har en delt Route53 hosted zone for domenet thecloudcollege.com som du kan bruke. I stedet for å opprette en ny hosted zone, skal vi hente den eksisterende med en data source.
Legg til øverst i modules/s3-website/main.tf:
# Data source - henter informasjon om eksisterende hosted zone
data "aws_route53_zone" "main" {
zone_id = "Z09151061LZNRB9E4BYEL" # thecloudcollege.com
}Vi har et wildcard-sertifikat (*.thecloudcollege.com) i us-east-1 som dekker alle subdomener.
Legg til i modules/s3-website/main.tf:
# Data source - henter eksisterende wildcard ACM-sertifikat
# Bruker us-east-1 provider fordi CloudFront krever sertifikat i denne regionen
data "aws_acm_certificate" "wildcard" {
provider = aws.us-east-1
domain = "*.thecloudcollege.com"
statuses = ["ISSUED"]
}Nå må CloudFront konfigureres til å akseptere requests fra ditt custom domain og bruke ACM-sertifikatet for HTTPS.
Finn aws_cloudfront_distribution ressursen i modules/s3-website/main.tf og gjør følgende endringer:
- Legg til
aliasesfor custom domain (rett underenabledogdefault_root_object):
resource "aws_cloudfront_distribution" "website" {
enabled = true
default_root_object = "index.html"
aliases = ["${var.subdomain}.thecloudcollege.com"] # NYTT: Custom domain
# ... resten av konfigurasjonen ...
}- Erstatt
viewer_certificateblokken (den eksisterende brukercloudfront_default_certificate = true):
viewer_certificate {
acm_certificate_arn = data.aws_acm_certificate.wildcard.arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}Legg til Route53 record i modules/s3-website/main.tf:
# Resource - oppretter DNS-record som peker til CloudFront
resource "aws_route53_record" "website" {
zone_id = data.aws_route53_zone.main.zone_id
name = "${var.subdomain}.thecloudcollege.com"
type = "A"
alias {
name = aws_cloudfront_distribution.website.domain_name
zone_id = aws_cloudfront_distribution.website.hosted_zone_id
evaluate_target_health = false
}
}Legg til i modules/s3-website/variables.tf:
variable "subdomain" {
description = "Subdomain for the website (e.g., 'glenn' becomes glenn.thecloudcollege.com)"
type = string
}Oppdater modul-kallet i root main.tf:
module "s3_website" {
source = "./modules/s3-website"
bucket_name = "ditt-bucket-navn"
subdomain = "ditt-navn" # Endre til ditt navn
tags = {
Name = "My Website"
Environment = "Demo"
}
}Oppdater modul-kallet i rot main.tf for å inkludere subdomain og sende providers til modulen (se Appendix A for detaljer):
module "s3_website" {
source = "./modules/s3-website"
# Send begge providers til modulen
providers = {
aws = aws
aws.us-east-1 = aws.us-east-1
}
bucket_name = "ditt-bucket-navn"
subdomain = "ditt-navn" # Endre til ditt unike navn (f.eks. "glenn")
tags = {
Name = "My Website"
Environment = "Demo"
}
}Legg til output i rot main.tf:
output "custom_domain_url" {
value = module.s3_website.custom_domain_url
description = "Custom domain URL with HTTPS"
}Legg til output i modules/s3-website/outputs.tf:
output "custom_domain_url" {
description = "Your custom domain URL"
value = "https://${var.subdomain}.thecloudcollege.com"
}Opprett modules/s3-website/versions.tf for å deklarere forventede providers:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
configuration_aliases = [aws.us-east-1]
}
}
}- Re-initialiser Terraform (nødvendig pga. ny provider):
terraform init- Kjør plan for å se hva som vil bli opprettet/endret:
terraform planDu skal se at CloudFront blir oppdatert og at en ny Route53 record blir opprettet.
- Apply endringene:
terraform applyMerk: CloudFront deployment tar 5-15 minutter når konfigurasjonen endres.
- Hent din custom domain URL:
terraform output custom_domain_url- Test din custom domain:
Vent noen minutter på at CloudFront-distribusjonen er ferdig deployet, og åpne URL-en i nettleseren. Din side vil nå være tilgjengelig på https://ditt-navn.thecloudcollege.com med full HTTPS!
Nøkkelpunkter:
- Data sources lar deg hente informasjon om eksisterende ressurser uten å endre dem
- Provider alias (
provider = aws.us-east-1) lar deg bruke flere regioner i samme konfigurasjon - CloudFront krever ACM-sertifikater i us-east-1 region
- Route53
aliasrecords peker til AWS-ressurser (som CloudFront) uten å bruke IP-adresser - Data sources refereres med
data.<type>.<name>, f.eks.data.aws_route53_zone.main.zone_id
Automatiser Terraform deployment:
- Pull Request: Kjør
terraform planog vis endringer - Merge til main: Kjør
terraform applyautomatisk
Lag .github/workflows/terraform.yml:
name: Terraform Infrastructure
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
AWS_REGION: eu-west-1
TF_VERSION: 1.6.0
jobs:
terraform:
name: Terraform Plan & Apply
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Terraform Init
run: terraform init
- name: Terraform Format Check
run: terraform fmt -check
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
id: plan
run: terraform plan -no-color
continue-on-error: true
- name: Comment Plan on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const output = `### Terraform Plan
\`\`\`
${{ steps.plan.outputs.stdout }}
\`\`\`
*Pushed by: @${{ github.actor }}*`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
- name: Terraform Apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approveDu må gi GitHub Actions tilgang til AWS:
- Gå til ditt GitHub repository
- Settings → Secrets and variables → Actions
- Klikk "New repository secret"
- Legg til to secrets:
- Name:
AWS_ACCESS_KEY_ID, Value:<din AWS access key> - Name:
AWS_SECRET_ACCESS_KEY, Value:<din AWS secret key>
- Name:
Sikkerhetstips: Disse secrets bør være fra en dedicated IAM-bruker med minimal permissions (kun det Terraform trenger).
- Lag en ny branch:
git checkout -b test-pipeline- Gjør en liten endring (f.eks. i README eller legg til en tag):
# I main.tf
module "s3_website" {
# ...
tags = {
# ...
PipelineTest = "true" # Ny tag
}
}- Commit og push:
git add .
git commit -m "Test GitHub Actions pipeline"
git push origin test-pipeline-
Opprett Pull Request på GitHub
-
Observer:
- GitHub Actions kjører
terraform plan - En kommentar vises på PR med plan output
- Du kan se hva som vil endres før merge
- GitHub Actions kjører
-
Merge PR til main:
- GitHub Actions kjører
terraform applyautomatisk - Infrastrukturen oppdateres uten manuell intervensjon
- GitHub Actions kjører
Gratulerer! Du har nå full CI/CD for infrastrukturen din.
Legg til validation i modules/s3-website/variables.tf:
variable "bucket_name" {
description = "Name of the S3 bucket"
type = string
validation {
condition = can(regex("^[a-z0-9][a-z0-9-]*[a-z0-9]$", var.bucket_name))
error_message = "Bucket name must start and end with lowercase letter or number, and contain only lowercase letters, numbers, and hyphens."
}
}- Terraform Modules Documentation
- AWS CloudFront Developer Guide
- GitHub Actions Terraform Tutorial
- Terraform Best Practices
Denne seksjonen gir en grundig forklaring av hvordan Terraform håndterer providers i moduler, spesielt når du trenger å bruke flere AWS-regioner.
Best practice: Provider-konfigurasjon skal være i hovedmodul, ikke i undermoduler.
# Hovedmodul (providers.tf) - RIKTIG
provider "aws" {
region = "eu-west-1"
}
# Undermodul - IKKE konfigurer providers herHvorfor?
- Moduler skal være gjenbrukbare på tvers av ulike AWS-kontoer og regioner
- Hovedmodulen kontrollerer hvilke credentials og regioner som brukes
- Unngår konflikter når modulen brukes flere ganger
Som standard arver moduler automatisk provider-konfigurasjonen fra hovedmodulen:
# Hovedmodul
provider "aws" {
region = "eu-west-1"
}
module "s3_website" {
source = "./modules/s3-website"
# Provider arves automatisk - ingen ekstra konfigurasjon nødvendig
}Dette fungerer utmerket for enkle use cases der du kun trenger én provider-konfigurasjon.
I denne oppgaven trenger vi to AWS providers fordi:
- Hovedressurser (S3, CloudFront) skal være i
eu-west-1 - ACM-sertifikater for CloudFront må være i
us-east-1(AWS-krav)
Opprett eller oppdater providers.tf i rotmappen:
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "eu-west-1"
}
# Alias provider for us-east-1
# Nødvendig fordi CloudFront krever ACM-sertifikater i us-east-1
provider "aws" {
alias = "us-east-1"
region = "us-east-1"
}Forklaring:
- Første
provider "aws"uten alias er default provider - Andre
provider "aws"medalias = "us-east-1"er en navngitt provider - Du kan ha flere aliased providers hvis du trenger flere regioner
Når du bruker aliased providers, må du eksplisitt sende dem til modulen:
# Hovedmodul (main.tf)
module "s3_website" {
source = "./modules/s3-website"
# Send begge providers til modulen
providers = {
aws = aws # Default provider
aws.us-east-1 = aws.us-east-1 # Aliased provider
}
bucket_name = var.bucket_name
subdomain = var.subdomain
}Merk:
aws = awssender default provideraws.us-east-1 = aws.us-east-1sender aliased provider- Uten
providers-blokken vil modulen kun få default provider
Modulen må eksplisitt deklarere at den forventer en aliased provider:
# modules/s3-website/versions.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
configuration_aliases = [aws.us-east-1]
}
}
}configuration_aliases forteller Terraform:
- Denne modulen forventer å motta en aliased provider kalt
aws.us-east-1 - Hovedmodulen må sende denne provideren når modulen kalles
# modules/s3-website/main.tf
# Bruker default provider (eu-west-1) - ingen provider-attributt nødvendig
resource "aws_s3_bucket" "website" {
bucket = var.bucket_name
}
resource "aws_cloudfront_distribution" "website" {
# Også default provider (eu-west-1)
enabled = true
# ...
}
# Bruker eksplisitt aliased provider (us-east-1)
data "aws_acm_certificate" "wildcard" {
provider = aws.us-east-1 # Eksplisitt spesifisert
domain = "*.thecloudcollege.com"
statuses = ["ISSUED"]
}Regel:
- Ressurser uten
provider-attributt bruker default provider - Ressurser med
provider = aws.us-east-1bruker aliased provider
Error: Provider configuration not present
Module module.s3_website does not declare a provider named aws.us-east-1
Løsning: Legg til configuration_aliases i modules/s3-website/versions.tf.
Error: Module does not support aws.us-east-1 provider configuration
Løsning: Modulen må eksplisitt deklarere at den forventer aliased provider via configuration_aliases.
Problem: ACM-sertifikat blir opprettet i eu-west-1 i stedet for us-east-1.
Løsning: Legg til provider = aws.us-east-1 på ressursen.
For å bruke multi-region providers i moduler:
- Hovedmodul: Definer alle providers (default + aliased) i
providers.tf - Module call: Send providers eksplisitt via
providers = { ... } - Module declaration: Deklarer forventede providers med
configuration_aliases - Resources: Bruk
provider = aws.aliaspå ressurser som trenger aliased provider
Dette gir deg full kontroll over hvilke regioner som brukes for hvilke ressurser.