Platform Engineering: Least Surprise (Consistency Across Environments)

中文翻译即将推出

Background

The whole point of infrastructure as code is a single template, parameterised with environment specific variables, applied to dev, staging and production. You write it once, you change a variable, and the same resources come up in each account. In this post I’ll discuss why that promise only holds when the template itself stays consistent, and what to do with the cases that do not fit.

The value is not the code; it is the guarantee. When the template is the same everywhere, “we tested this in staging” actually means “this is safe in prod”. The moment the environments diverge silently, that sentence stops being true, and you find out at the worst possible time.

The rule

Any exception to consistency must be deliberately evaluated, documented, scoped to one environment, and guarded from bleeding into the others. An exception you did not decide on is drift; drift is the thing IaC was supposed to remove.

So the test for any deviation is not “does this work”; it is “can I name why this environment is different, and can I stop that difference from spreading”.

A legitimate exception

Here is one that earns its place. In dev, we let developers assume the pipeline role directly to test infrastructure changes. In staging and production, only the CI/CD pipeline may assume that role.

The reason is the feedback loop. Pushing every small infrastructure change through the pipeline just to see whether an IAM policy is shaped correctly is slow; assuming the role locally in a throwaway account shortens that loop to seconds. The upside is a much faster inner loop for the person writing the infrastructure. The downside is a real permission difference between dev and the rest, so it has to be scoped explicitly to the dev account and never replicated upward.

That last part is the actual work. The exception is expressed as a variable, not a copy:

variable "allow_direct_role_assumption" {
  type    = bool
  default = false
}

dev.tfvars sets it to true; staging and prod never do. The difference lives in one boolean in one place, and you can grep for it.

The anti-pattern

The tempting way to handle an exception is to copy the template. You have a dev variant, a staging variant, a prod variant, and each one drifts on its own schedule. There is no longer a single source of truth; the environments diverge silently, and the maintenance burden compounds every time you touch one of them. This defeats the purpose of IaC entirely.

Exceptions should always be expressed as parameterised variations inside a shared template, never as forks into independent codebases. A variable you can read top to bottom is auditable; three separate directories are not.

Conclusion

Consistency is the feature. When you are tempted to fork a template for a quick difference, stop and turn the difference into a parameter instead. If it cannot be a parameter, it probably should not be an exception. When in doubt, consistency wins over the convenience of a separate copy.

Share this post: 分享这篇文章:

Comments 评论