ЁЯПл The SchoolтА║ЁЯУЬ TerraformтА║ЁЯФТ рдзрдбрд╛ 08 тАФ Remote state рдЖрдгрд┐ locking: рдПрдХ рдиреЛрдВрджрд╡рд╣реА, рдПрдХрдЪ рд▓рд┐рд╣рд┐рдгрд╛рд░реА
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯФТ рдзрдбрд╛ 08 тАФ Remote state рдЖрдгрд┐ locking: рдПрдХ рдиреЛрдВрджрд╡рд╣реА, рдПрдХрдЪ рд▓рд┐рд╣рд┐рдгрд╛рд░реА

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 08 ┬╖ рдорд╛рдЧреАрд▓: lesson-07-modules ┬╖ рдкреБрдвреАрд▓: lesson-09-drift-import


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ07, рдЖрдгрд┐ рддреНрдпрд╛рд╕реЛрдмрдд teamwork. рдиреЛрдВрджрд╡рд╣реА laptop рд╡рд░реВрди рдирд┐рдШреВрди рдПрдХрд╛ remote backend рдордзреНрдпреЗ рдЬрд╛рддреЗ тАФ рдПрдХ рд╕рд╛рдорд╛рдпрд┐рдХ рдЬрд╛рдЧрд╛ рдЬрд┐рдереЗ рд╕рдЧрд│реЗ рд╡рд╛рдЪрддрд╛рдд рдЖрдгрд┐ рд▓рд┐рд╣рд┐рддрд╛рдд. рдЖрдгрд┐ рддреНрдпрд╛ backend рд▓рд╛ рдХреБрд▓реВрдк рдЕрд╕рддреЗ: рдПрдХ run рддреЗ рдзрд░реВрди рдареЗрд╡рдд рдЕрд╕рддрд╛рдирд╛, рджреБрд╕рд▒реНрдпрд╛ run рд▓рд╛ рдкрд╣рд┐рд▓реНрдпрд╛ run рдЪреЗ рдХрд╛рдо рдЧреБрдкрдЪреВрдк рдкреБрд╕реВрди рдЯрд╛рдХрдгреНрдпрд╛рдРрд╡рдЬреА Error acquiring the state lock рдорд┐рд│рддреЛ. locking() рд╣реЗ iac/demo.py рдордзреНрдпреЗ рдЖрд╣реЗ; Backend рд╣реЗ iac/engine.py рдордзреНрдпреЗ.

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рдиреЛрдВрджрд╡рд╣реА рдЖрддрд╛ рдХреЛрдгрд╛рдЪреНрдпрд╛рд╣реА рджрдкреНрддрд░рд╛рдд рдирд╛рд╣реА, рддрд░ рд╢рд╛рд│реЗрдЪреНрдпрд╛ рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдд рд░рд╛рд╣рддреЗ. ЁЯПлЁЯУТ

рд╕реЛрдорд╡рд╛рд░реА рдХрддрд░рд┐рдирд╛ рдЖрдгрд┐ рджреАрдкрд┐рдХрд╛ рджреЛрдШреАрдВрдирд╛рд╣реА рдХрд╛рд╣реАрддрд░реА рдмрд╛рдВрдзрд╛рдпрдЪреЗ рдЖрд╣реЗ. рджреЛрдШреА рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдд рдЬрд╛рддрд╛рдд рдЖрдгрд┐ рдиреЛрдВрджрд╡рд╣реАрдЪреА рдкреНрд░рдд рдШреЗрддрд╛рдд (рддреНрдпрд╛рдд рд▓рд┐рд╣рд┐рд▓реЗ рдЖрд╣реЗ: рдПрдХ lab). рдХрддрд░рд┐рдирд╛ рдПрдХ gym рдмрд╛рдВрдзрддреЗ. рджреАрдкрд┐рдХрд╛ рдПрдХ library рдмрд╛рдВрдзрддреЗ. рдХрддрд░рд┐рдирд╛ рдЖрдкрд▓реА рдкреНрд░рдд рдкрд░рдд рд▓рд┐рд╣рд┐рддреЗ: lab, gym. рдордЧ рджреАрдкрд┐рдХрд╛ рддрд┐рдЪреА рдкреНрд░рдд рдкрд░рдд рд▓рд┐рд╣рд┐рддреЗ: lab, library. рджреАрдкрд┐рдХрд╛рдЪреА рдкреНрд░рдд рдЬрд┐рдВрдХрддреЗ.

рдЖрддрд╛ рдиреЛрдВрджрд╡рд╣реАрдд рд▓рд┐рд╣рд┐рд▓реЗ рдЖрд╣реЗ lab, library. рдкрдг campus рд╡рд░ рдПрдХ gym рд╕реБрджреНрдзрд╛ рдЖрд╣реЗ! рдХреЛрдгрд╛рдЪреНрдпрд╛рдЪ рдиреЛрдВрджрд╡рд╣реАрд▓рд╛ рддреНрдпрд╛рдмрджреНрджрд▓ рдорд╛рд╣реАрдд рдирд╛рд╣реА. рддреЗ рдмрд╛рдВрдзрд▓реЗрд▓реЗ рдЖрд╣реЗ, рддреНрдпрд╛рдЪрд╛ рдЦрд░реНрдЪ рд╣реЛрддреЛ рдЖрд╣реЗ, рдЖрдгрд┐ рдХреЛрдгрддрд╛рд╣реА рдЖрд░рд╛рдЦрдбрд╛ рдХрдзреАрдЪ рддреНрдпрд╛рдЪреА рдХрд╛рд│рдЬреА рдШреЗрдгрд╛рд░ рдирд╛рд╣реА. ЁЯС╗

рдореНрд╣рдгреВрди рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рд▓рд╛ рдиреЛрдВрджрд╡рд╣реАрд╕рд╛рдареА рдПрдХ рдХрд┐рд▓реНрд▓реА ЁЯФС рдорд┐рд│рддреЗ. рдЬрд┐рдЪреНрдпрд╛рдХрдбреЗ рдХрд┐рд▓реНрд▓реА, рддреАрдЪ рд▓рд┐рд╣реВ рд╢рдХрддреЗ. рдХрддрд░рд┐рдирд╛рдХрдбреЗ рдХрд┐рд▓реНрд▓реА рдЕрд╕рддрд╛рдирд╛ рджреАрдкрд┐рдХрд╛ рдЖрд▓реА, рддрд░ рдХрд╛рд░реНрдпрд╛рд▓рдп рд╕рд╛рдВрдЧрддреЗ: "рдорд╛рдл рдХрд░рд╛ тАФ рдХрд┐рд▓реНрд▓реА рдХрддрд░рд┐рдирд╛рдХрдбреЗ рдЖрд╣реЗ, рдПрдХрд╛ apply рд╕рд╛рдареА." рджреАрдкрд┐рдХрд╛ рдерд╛рдВрдмрддреЗ, рдордЧ рдирд╡реА рдиреЛрдВрджрд╡рд╣реА рд╡рд╛рдЪрддреЗ тАФ рдЖрдгрд┐ рдЖрддрд╛ рддрд┐рдЪрд╛ plan рджрд╛рдЦрд╡рддреЛ рдХреА рддрд┐рдЪреА branch gym рдкрд╛рдбреВрди рдЯрд╛рдХреЗрд▓, рдХрд╛рд░рдг рддрд┐рдЪреНрдпрд╛ рдЖрд░рд╛рдЦрдбреНрдпрд╛рдд рддреЗ рдЕрдЬреВрди рдирд╛рд╣реА. рддреА рдЖрдзреА рдХрддрд░рд┐рдирд╛рдЪрд╛ рдмрджрд▓ рдШреЗрддреЗ. рдордЧ рд╕рдЧрд│реЗ рд╕реБрд░рдХреНрд╖рд┐рдд рд╣реЛрддреЗ.

ЁЯЧ║я╕П рдЖрдХреГрддреА

sequenceDiagram
    participant K as Katrina
    participant B as backend (register + lock)
    participant D as Dipika
    K->>B: lock тЖТ lock-0001 тЬЕ
    D->>B: lock тЖТ Error acquiring the state lock (Who: katrina)
    K->>B: write state serial 2 (lab, gym) ┬╖ unlock
    D->>B: lock тЖТ lock-0002 тЬЕ ┬╖ read serial 2
    Note over D: plan: 1 to add, 1 to destroy тЖР her branch lacks the gym
    D->>B: pull Katrina's change тЖТ 1 to add ┬╖ write serial 3 ┬╖ unlock

ЁЯЧ║я╕П рд░реЗрдЦрд╛рдЯрд▓реЗрд▓реА рдЖрд╡реГрддреНрддреА + рдПрдХ lab: https://school-edh.pages.dev/terraform/lesson-diagrams.html#l08

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг рдЬреНрдпрд╛ рдХреНрд╖рдгреА рджреЛрди рд▓реЛрдХ (рдХрд┐рдВрд╡рд╛ рдПрдХ рд╡реНрдпрдХреНрддреА рдЖрдгрд┐ рдПрдХ CI job) рдПрдХрд╛рдЪ campus рд╡рд░ Terraform рдЪрд╛рд▓рд╡рддрд╛рдд, рддреНрдпрд╛ рдХреНрд╖рдгреА laptop рд╡рд░рдЪреА рдиреЛрдВрджрд╡рд╣реА рд░рдЪрдиреЗрдиреЗрдЪ рдЪреБрдХреАрдЪреА рдард░рддреЗ: рдкреНрд░рддреНрдпреЗрдХрд╛рдХрдбреЗ рд╡реЗрдЧрд│реА рдкреНрд░рдд рдЕрд╕рддреЗ. Remote state рд╕рдЧрд│реНрдпрд╛рдВрдирд╛ рддреАрдЪ рдиреЛрдВрджрд╡рд╣реА рджреЗрддреЗ. Locking рдореБрд│реЗ рд▓рд┐рдЦрд╛рдг рдПрдХрд╛ рд╡реЗрд│реА рдПрдХ рд╣реЛрддреЗ. рдпрд╛ рджреЛрдШрд╛рдВрдкреИрдХреА рдХреЛрдгреАрд╣реА рджреЛрди рд▓реЛрдХрд╛рдВрдЪреЗ рдЖрд░рд╛рдЦрдбреЗ merge рдХрд░рдд рдирд╛рд╣реА тАФ рддреЗ рдХрд╛рдо рдЕрдЬреВрдирд╣реА git рдЪреЗ рдЖрд╣реЗ, рдЬрд╕реЗ рджреАрдкрд┐рдХрд╛рдЪреЗ 1 to destroy рджрд╛рдЦрд╡рддреЗ.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

iac/engine.py рдордзреАрд▓ Backend states (path тЖТ state) рдЖрдгрд┐ locks (path тЖТ lock info) рдареЗрд╡рддреЗ. lock(path, who) lock-0001 рд╕рд╛рд░рдЦрд╛ id рдкрд░рдд рджреЗрддреЗ, рдХрд┐рдВрд╡рд╛ path locked рдЕрд╕реЗрд▓ рддрд░ рдзрд╛рд░рдХрд╛рдЪреНрдпрд╛ рдорд╛рд╣рд┐рддреАрд╕рд╣ LockError рдЙрдард╡рддреЗ. unlock(path, id) рдлрдХреНрдд рдпреЛрдЧреНрдп id рдиреЗрдЪ рдЪрд╛рд▓рддреЗ. apply(..., backend=be, key=path) рдирд╡реА state backend рдордзреНрдпреЗ рд▓рд┐рд╣рд┐рддреЗ. iac/demo.py рдордзреАрд▓ locking() рд╕реЛрдорд╡рд╛рд░рдЪреА рдЧреЛрд╖реНрдЯ рджреЛрдирджрд╛ рдЪрд╛рд▓рд╡рддреЗ: рдПрдХрджрд╛ рдХреБрд▓реВрдкрд╛рд╢рд┐рд╡рд╛рдп рдЖрдгрд┐ рдПрдХрджрд╛ рдХреБрд▓реВрдкрд╛рд╕рд╣.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

python3 iac/demo.py locking
python3 - <<'EOF'
import sys; sys.path.insert(0, "iac")
from engine import Backend, LockError
be = Backend(); path = be.path()
a = be.lock(path, "katrina@works-office")
for who in ("dipika@works-office", "aishwarya@ci"):
    try: be.lock(path, who)
    except LockError as e: print(who, "тЖТ", e)
be.unlock(path, "lock-9999"); print("unlock with the wrong id тЖТ still locked:", path in be.locks)
be.unlock(path, a); print("unlock with", a, "тЖТ still locked:", path in be.locks)
print("next lock:", be.lock(path, "dipika@works-office"))
EOF

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

locking рд╣реЗ рдЫрд╛рдкрддреЗ:

   both read serial 1 ┬╖ Katrina: Plan: 1 to add, 0 to change, 0 to destroy. ┬╖ Dipika: Plan: 1 to add, 0 to change, 0 to destroy.
   both wrote serial 2; Dipika wrote last тЖТ the register knows ['school_room.lab', 'school_room.library']
   the campus has 3 rooms, the register knows 2 тЖТ orphan: ['gym'] тАФ built, paid for, managed by nobody
   Dipika тЖТ Error acquiring the state lock ┬╖ Lock Info: ID lock-0001 ┬╖ Path campus.tfstate ┬╖ Operation OperationTypeApply ┬╖ Who katrina@works-office
   Katrina is done (serial 2); Dipika gets lock-0002, reads the NEW register тЖТ Plan: 1 to add, 0 to change, 1 to destroy.  тЖР her branch lacks the gym
   she pulls Katrina's change first тЖТ Plan: 1 to add, 0 to change, 0 to destroy.
   Apply complete! Resources: 1 added, 0 changed, 0 destroyed. serial 3 ┬╖ campus rooms 3 = register 3

рддреБрдордЪрд╛ snippet рд╣реЗ рдЫрд╛рдкрддреЛ:

dipika@works-office тЖТ Error acquiring the state lock ┬╖ Lock Info: ID lock-0001 ┬╖ Path campus.tfstate ┬╖ Operation OperationTypeApply ┬╖ Who katrina@works-office
aishwarya@ci тЖТ Error acquiring the state lock ┬╖ Lock Info: ID lock-0001 ┬╖ Path campus.tfstate ┬╖ Operation OperationTypeApply ┬╖ Who katrina@works-office
unlock with the wrong id тЖТ still locked: True
unlock with lock-0001 тЖТ still locked: False
next lock: lock-0002

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

рдХреБрд▓реВрдкрд╛рд╢рд┐рд╡рд╛рдп, рджреЛрди рдмрд░реЛрдмрд░ plans рдореБрд│реЗ рдЪреБрдХреАрдЪреА рдиреЛрдВрджрд╡рд╣реА рддрдпрд╛рд░ рдЭрд╛рд▓реА: рджреЛрдШреАрдВрдиреА serial 2 рд▓рд┐рд╣рд┐рд▓реЗ, рджреБрд╕рд░реЗ рд▓рд┐рдЦрд╛рдг рдЬрд┐рдВрдХрд▓реЗ, рдЖрдгрд┐ gym orphan рдЭрд╛рд▓реЗ (campus рд╡рд░ 3 рдЦреЛрд▓реНрдпрд╛, рдиреЛрдВрджрд╡рд╣реАрдд 2). рдХреБрд▓реВрдкрд╛рд╕рд╣, рджреБрд╕рд░рд╛ run рдирд╛рдХрд╛рд░рд▓рд╛ рдЧреЗрд▓рд╛, рдЖрдгрд┐ рддреЛ рдирдВрддрд░ рдЪрд╛рд▓рд▓рд╛ рддреЗрд╡реНрд╣рд╛ рддреНрдпрд╛рдиреЗ рдирд╡реНрдпрд╛ рдиреЛрдВрджрд╡рд╣реАрд╡рд░реВрди plan рдХреЗрд▓рд╛ тАФ рддреНрдпрд╛рдореБрд│реЗ рдЦрд░рд╛ рд╕рдВрдШрд░реНрд╖ (1 to destroy) рд▓рдкреВрди рди рд░рд╛рд╣рддрд╛ рдЙрдШрдб рдЭрд╛рд▓рд╛. рдХреБрд▓реВрдк рдлрдХреНрдд рддреНрдпрд╛рдЪреНрдпрд╛ рд╕реНрд╡рддрдГрдЪреНрдпрд╛ id рдиреЗрдЪ рдЙрдШрдбрддреЗ.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд

рдЦрд▒реНрдпрд╛ account рд╡рд░ тАФ S3-native locking рд╕рд╣ S3 remote state (Terraform 1.10+; рддреНрдпрд╛рдЖрдзреА S3 рд╡рд░ locking рд╕рд╛рдареА DynamoDB table рд▓рд╛рдЧрдд рдЕрд╕реЗ, рдЬреЗ рдирд╡реНрдпрд╛ versions рдордзреНрдпреЗ deprecated рдЖрд╣реЗ):

terraform {
  backend "s3" {
    bucket       = "school-tfstate-example"
    key          = "campus/terraform.tfstate"
    region       = "ap-south-1"
    encrypt      = true
    use_lockfile = true      # writes campus/terraform.tfstate.tflock next to the state
  }
}
aws s3api put-bucket-versioning --bucket school-tfstate-example \
    --versioning-configuration Status=Enabled
terraform init -migrate-state          # move an existing local state into the bucket
terraform plan -lock-timeout=5m        # wait up to 5 minutes for a held lock
terraform force-unlock 6d1f7c0e-...    # only when the lock's owner is certainly dead

рдЗрддрд░ backends рдЖрдкрд╛рдкрд▓реНрдпрд╛ рдкрджреНрдзрддреАрдиреЗ lock рдХрд░рддрд╛рдд: GCS state рдЪреНрдпрд╛ рд╢реЗрдЬрд╛рд░реА рдПрдХ lock object рд▓рд┐рд╣рд┐рддреЗ, azurerm state blob рд╡рд░ lease рдШреЗрддреЗ, HCP Terraform service рдордзреНрдпреЗрдЪ рдкреНрд░рддреНрдпреЗрдХ workspace рд▓рд╛ lock рдХрд░рддреЗ. рдореЛрдареНрдпрд╛ estates рдЪреА рдЕрдиреЗрдХ states рдордзреНрдпреЗ рд╡рд┐рднрд╛рдЧрдгреА рдХрд░рд╛ (network, data, apps) рдЖрдгрд┐ рддреНрдпрд╛рдВрдЪреНрдпрд╛рддреАрд▓ рдореВрд▓реНрдпреЗ outputs рдЖрдгрд┐ terraform_remote_state data source рдиреЗ рд╡рд╛рдЪрд╛ тАФ рдХрд┐рдВрд╡рд╛ рддреНрдпрд╛рд╣реВрди рдЪрд╛рдВрдЧрд▓реЗ, рдЦрд▒реНрдпрд╛ objects рд╡рд╛рдЪрдгрд╛рд▒реНрдпрд╛ data sources рдиреЗ.

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ рдЖрд╣реЗ: рдЖрдЬрдЪ рдкреНрд░рддреНрдпреЗрдХ state bucket рд╡рд░ рддреАрди рдЧреЛрд╖реНрдЯреА рддрдкрд╛рд╕рд╛: versioning рд╕реБрд░реВ, encryption рд╕реБрд░реВ, public access blocked тАФ рдЖрдгрд┐ рддреНрдпрд╛ bucket рдХрдбреЗ рдирд┐рд░реНрджреЗрд╢ рдХрд░рдгрд╛рд▒реНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ backend block рдордзреНрдпреЗ locking configure рдХреЗрд▓реЗрд▓реЗ рдЖрд╣реЗ.

тПня╕П рдкреБрдвреЗ

рдиреЛрдВрджрд╡рд╣реА рддреЗрд╡реНрд╣рд╛рдЪ рдмрд░реЛрдмрд░ рд░рд╛рд╣реВ рд╢рдХрддреЗ рдЬреЗрд╡реНрд╣рд╛ рдХреЛрдгреАрд╣реА рддрд┐рдЪреНрдпрд╛ рдирдХрд│рдд campus рдмрджрд▓рдд рдирд╛рд╣реА. рдкрдг рдХреЛрдгреАрддрд░реА рдиреЗрд╣рдореАрдЪ рдмрджрд▓рддреЛ. рдкреБрдвреЗ: drift рдЖрдгрд┐ import.

git checkout lesson-09-drift-import

ЁЯФТ Lesson 08 тАФ Remote state & locking: one register, one writer

ЁЯУН You are here: Lesson 08 of 12 ┬╖ Previous: lesson-07-modules ┬╖ Next: lesson-09-drift-import


ЁЯУж What's in this branch

Lessons 01тАУ07, plus teamwork. The register moves off the laptop into a remote backend тАФ one shared place everyone reads and writes. And the backend has a lock: while one run holds it, a second run gets Error acquiring the state lock instead of silently overwriting the first run's work. locking() in iac/demo.py; Backend in iac/engine.py.

ЁЯзТ Explain like I'm 5

The register now lives in the school office, not in anyone's bag. ЁЯПлЁЯУТ

On Monday, Katrina and Dipika both want to build something. They both walk to the office and copy the register (it says: one lab). Katrina builds a gym. Dipika builds a library. Katrina writes her copy back: lab, gym. Then Dipika writes her copy back: lab, library. Dipika's copy wins.

Now the register says lab, library. But the campus has a gym too! Nobody's register knows about it. It is built, it costs money, and no plan will ever look after it. ЁЯС╗

So the office gets a key ЁЯФС for the register. Whoever takes the key can write. When Dipika comes while Katrina has the key, the office says: "Sorry тАФ Katrina has it, for an apply." Dipika waits, then reads the new register тАФ and her plan now shows that her branch would destroy the gym, because her plans don't have it yet. She pulls Katrina's change first. Then it is safe.

ЁЯЧ║я╕П Diagram

sequenceDiagram
    participant K as Katrina
    participant B as backend (register + lock)
    participant D as Dipika
    K->>B: lock тЖТ lock-0001 тЬЕ
    D->>B: lock тЖТ Error acquiring the state lock (Who: katrina)
    K->>B: write state serial 2 (lab, gym) ┬╖ unlock
    D->>B: lock тЖТ lock-0002 тЬЕ ┬╖ read serial 2
    Note over D: plan: 1 to add, 1 to destroy тЖР her branch lacks the gym
    D->>B: pull Katrina's change тЖТ 1 to add ┬╖ write serial 3 ┬╖ unlock

ЁЯЧ║я╕П Drawn version + a lab: https://school-edh.pages.dev/terraform/lesson-diagrams.html#l08

тЭУ What

ЁЯдФ Why

Because the moment two people (or a person and a CI job) run Terraform against the same campus, a laptop register is wrong by design: each has a different copy. Remote state gives everyone the same register. Locking makes writes one at a time. Neither of them merges two people's plans тАФ that is still git's job, as Dipika's 1 to destroy shows.

ЁЯФз How (in this repo)

Backend in iac/engine.py keeps states (path тЖТ state) and locks (path тЖТ lock info). lock(path, who) returns an id like lock-0001, or raises LockError with the holder's info if the path is locked. unlock(path, id) only works with the right id. apply(..., backend=be, key=path) writes the new state to the backend. locking() in iac/demo.py runs the Monday story twice: without the lock and with it.

ЁЯзк Try it

python3 iac/demo.py locking
python3 - <<'EOF'
import sys; sys.path.insert(0, "iac")
from engine import Backend, LockError
be = Backend(); path = be.path()
a = be.lock(path, "katrina@works-office")
for who in ("dipika@works-office", "aishwarya@ci"):
    try: be.lock(path, who)
    except LockError as e: print(who, "тЖТ", e)
be.unlock(path, "lock-9999"); print("unlock with the wrong id тЖТ still locked:", path in be.locks)
be.unlock(path, a); print("unlock with", a, "тЖТ still locked:", path in be.locks)
print("next lock:", be.lock(path, "dipika@works-office"))
EOF

тЬЕ Verify тАФ what you should see

locking prints:

   both read serial 1 ┬╖ Katrina: Plan: 1 to add, 0 to change, 0 to destroy. ┬╖ Dipika: Plan: 1 to add, 0 to change, 0 to destroy.
   both wrote serial 2; Dipika wrote last тЖТ the register knows ['school_room.lab', 'school_room.library']
   the campus has 3 rooms, the register knows 2 тЖТ orphan: ['gym'] тАФ built, paid for, managed by nobody
   Dipika тЖТ Error acquiring the state lock ┬╖ Lock Info: ID lock-0001 ┬╖ Path campus.tfstate ┬╖ Operation OperationTypeApply ┬╖ Who katrina@works-office
   Katrina is done (serial 2); Dipika gets lock-0002, reads the NEW register тЖТ Plan: 1 to add, 0 to change, 1 to destroy.  тЖР her branch lacks the gym
   she pulls Katrina's change first тЖТ Plan: 1 to add, 0 to change, 0 to destroy.
   Apply complete! Resources: 1 added, 0 changed, 0 destroyed. serial 3 ┬╖ campus rooms 3 = register 3

Your snippet prints:

dipika@works-office тЖТ Error acquiring the state lock ┬╖ Lock Info: ID lock-0001 ┬╖ Path campus.tfstate ┬╖ Operation OperationTypeApply ┬╖ Who katrina@works-office
aishwarya@ci тЖТ Error acquiring the state lock ┬╖ Lock Info: ID lock-0001 ┬╖ Path campus.tfstate ┬╖ Operation OperationTypeApply ┬╖ Who katrina@works-office
unlock with the wrong id тЖТ still locked: True
unlock with lock-0001 тЖТ still locked: False
next lock: lock-0002

ЁЯПБ What you just proved

Without a lock, two correct plans produced a wrong register: both wrote serial 2, the second write won, and the gym became an orphan (3 rooms on the campus, 2 in the register). With the lock, the second run was refused, and when it ran later it planned against the new register тАФ which exposed the real conflict (1 to destroy) instead of hiding it. The lock only opens with its own id.

тЪая╕П Common mistakes

ЁЯПн In production

On a real account тАФ S3 remote state with S3-native locking (Terraform 1.10+; before that, locking on S3 needed a DynamoDB table, which newer versions deprecate):

terraform {
  backend "s3" {
    bucket       = "school-tfstate-example"
    key          = "campus/terraform.tfstate"
    region       = "ap-south-1"
    encrypt      = true
    use_lockfile = true      # writes campus/terraform.tfstate.tflock next to the state
  }
}
aws s3api put-bucket-versioning --bucket school-tfstate-example \
    --versioning-configuration Status=Enabled
terraform init -migrate-state          # move an existing local state into the bucket
terraform plan -lock-timeout=5m        # wait up to 5 minutes for a held lock
terraform force-unlock 6d1f7c0e-...    # only when the lock's owner is certainly dead

Other backends lock in their own way: GCS writes a lock object next to the state, azurerm takes a lease on the state blob, HCP Terraform locks per workspace in the service. Split large estates into several states (network, data, apps) and read values across them with outputs and the terraform_remote_state data source тАФ or better, with data sources that read the real objects.

ЁЯПн Why this matters in production: check three things on every state bucket today: versioning on, encryption on, public access blocked тАФ and that locking is configured in every backend block that points at it.

тПня╕П Next

The register can only be right if nobody changes the campus behind its back. Someone always does. Next: drift and import.

git checkout lesson-09-drift-import
тЖР PreviousmodulesNext тЖТdrift import

This page is the lesson's README from the lesson-08-remote-state-locking branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.