10. Infrastructure as Code and Cloud ProvisioningIntroduction and ContextInfrastructure as Code (IaC) is the practice of defining, provisioning, configuring, and changing infrastructure through machine-readable files or programs rather than through manual actions in a cloud console or on individual servers. It applies software-engineering ideas—version control, review, testing, repeatability, modularity, and automation—to infrastructure management. IaC has become central to DevOps because modern systems are distributed across virtual networks, compute instances, containers, managed databases, identity services, storage systems, observability platforms, and security controls. A manually created environment may work, but it is difficult to reproduce, audit, scale, or recover. IaC treats infrastructure as an engineered product whose desired configuration can be described, reviewed, tested, and delivered through a controlled pipeline. This chapter examines two broad approaches. Declarative tools such as Terraform and OpenTofu describe the desired end state, while programmatic approaches such as Pulumi, cloud SDKs, and Python express provisioning logic through general-purpose programming languages. Neither approach is universally superior. The appropriate choice depends on the complexity of the infrastructure, the need for abstraction, organizational skills, governance requirements, and the degree of provider independence required. The chapter also addresses state management, modules, dependencies, environment design, drift detection, policy-as-code, and operational risks. These topics are particularly important at postgraduate level because successful cloud provisioning is not merely a matter of writing configuration. It involves architecture, risk management, lifecycle design, organizational controls, and informed technical judgment. Learning ObjectivesAfter studying this chapter, students should be able to: 1. Explain the purpose, principles, and lifecycle of Infrastructure as Code. 2. Distinguish declarative IaC from programmatic provisioning. 3. Compare Terraform, OpenTofu, Pulumi, cloud SDKs, and Python-based automation. 4. Explain the role of state, dependency graphs, modules, and providers. 5. Design strategies for environments, promotion, secrets, and remote state. 6. Identify and manage infrastructure drift, destructive changes, and partial failure. 7. Apply policy-as-code and governance controls to cloud provisioning. 8. Evaluate a provisioning approach for a realistic organizational scenario. Infrastructure as Code as an Engineering DisciplineThe essential distinction between manual infrastructure administration and IaC is not simply the use of files. It is the introduction of a controlled, repeatable lifecycle. A typical lifecycle contains the following activities: - Define: express infrastructure requirements in configuration or code.
- Validate: check syntax, types, references, security rules, and organizational policies.
- Plan: calculate the changes required to move the actual environment toward the desired state.
- Review: inspect the proposed changes through peer review or automated controls.
- Apply: create, modify, or destroy resources.
- Observe: compare the resulting environment with the intended configuration.
- Evolve: manage later changes through the same controlled process.
The desired state is the target condition specified by the engineering team. The actual state is the condition currently existing in the provider environment. IaC tools attempt to reconcile the two. This reconciliation model supports idempotence: applying the same configuration repeatedly should not create unnecessary duplicate resources or produce unpredictable results. Idempotence does not mean that every operation is harmless. A configuration change may replace a database, detach a network interface, or destroy an object whose name is reused. Therefore, safe IaC requires lifecycle awareness, review, backups, and explicit controls around destructive operations. IaC also creates an auditable record. A version-control commit can identify who proposed a change, what changed, why it changed, and which review approved it. This is valuable for incident investigation, compliance, cost management, and disaster recovery. However, version control alone does not guarantee quality. Poorly designed configuration can reproduce insecure or excessively expensive infrastructure with great efficiency. Declarative and Programmatic ModelsDeclarative configurationA declarative model specifies what infrastructure should exist and which relationships or properties it should have. The tool determines how to achieve that result. Terraform and OpenTofu are prominent examples. A simplified configuration might declare a virtual network, a subnet, and a compute resource without explicitly instructing the tool to call a sequence of API operations. The declarative model offers several advantages. It is relatively concise, makes the target architecture visible, and allows the tool to construct a dependency graph. It is also well suited to standard platform components that can be represented as resources with stable attributes. The principal limitation is that unusual control flow, complex iteration, asynchronous workflows, and conditional business logic can become awkward. Programmatic provisioningA programmatic model uses a general-purpose language or SDK to express provisioning logic. Pulumi supports languages such as Python, TypeScript, Go, and C#. Cloud SDKs allow direct calls to provider APIs, and Python can coordinate provisioning, configuration, testing, and operational tasks. Programmatic provisioning is useful when infrastructure depends on algorithms, external data, sophisticated testing, custom abstractions, or integration with existing software systems. Developers can use functions, classes, loops, exception handling, package management, and unit tests. The trade-off is that the resulting program can conceal infrastructure intent, introduce side effects, and become difficult to reason about if developers treat cloud resources as ordinary application objects. A comparisonTerraform and OpenTofu generally provide a resource-oriented declarative workflow with a plan phase and state management. Pulumi offers a code-first interface while retaining an infrastructure resource model and state-driven deployment process. Cloud SDKs expose provider APIs more directly and therefore provide fine-grained control, but the user must often implement dependency handling, retries, idempotence, state interpretation, and rollback logic. A useful decision principle is to use the highest-level abstraction that still expresses the required behavior safely. Standard networks, clusters, and databases may be best represented declaratively. Complex account discovery, data transformation, or event-driven orchestration may justify Python or an SDK. Many mature organizations use a hybrid model: declarative IaC for the platform foundation and Python for validation, orchestration, testing, and specialized automation. Terraform, OpenTofu, Pulumi, and Cloud SDKsTerraform uses configuration files, providers, resources, data sources, variables, outputs, and a state file. Its plan operation compares configuration, state, and provider observations to propose changes. OpenTofu follows the same general workflow and is an open-source, community-governed alternative with broad compatibility for common Terraform configurations. Exact feature support and provider behavior should be checked against the versions used by an organization. A provider acts as an integration layer between the IaC engine and a service API. It translates resource declarations into API operations and reads remote objects back into the state model. Provider version constraints are important because changes in schemas or behavior can alter plans. Pulumi uses familiar programming languages and permits abstractions such as reusable classes and functions. This can improve developer productivity, especially where teams already have strong software-development capabilities. It also creates a responsibility to maintain code quality, manage dependencies, test logic, and prevent accidental execution of arbitrary side effects during previews. Cloud SDKs, such as provider-specific Python libraries, are lower-level tools. They are appropriate for one-off administrative utilities, dynamic discovery, migration scripts, custom controllers, and workflows not represented conveniently by an IaC provider. They are less suitable as the sole mechanism for a large, long-lived estate unless the organization builds its own state, testing, approval, and recovery mechanisms. The selection should consider: - need for provider portability;
- maturity of required resource integrations;
- team expertise in configuration or programming;
- state and collaboration requirements;
- complexity of control flow;
- testing and policy tooling;
- provider API limitations;
- lifecycle and support expectations.
State, Identity, and ReconciliationState is one of the most important and frequently misunderstood concepts in IaC. It records the relationship between configuration objects and real infrastructure, including resource identities and selected attributes. The tool uses state to determine what it manages and how a configuration change relates to an existing resource. State is not merely a cache. Loss, corruption, concurrent modification, or unauthorized access can cause serious operational problems. State may contain sensitive values, such as connection details or identifiers, even when secrets are not written directly in configuration. It should therefore be stored in a protected remote backend with encryption, access control, versioning, and locking where supported. A team should avoid storing a shared state file in an ordinary local directory or committing it to a public repository. Remote state improves collaboration, but it does not remove the need for access governance. Permissions should follow least privilege, and state access should be logged. State locking prevents two operations from modifying the same state concurrently. Without locking, one apply may overwrite another operation’s changes or produce an inconsistent record. Failed operations also require attention: an API request may succeed while the client times out, leaving infrastructure and state temporarily out of alignment. Refresh, import, reconciliation, or manual investigation may then be needed. Resource identity is equally important. Renaming a logical resource in configuration may cause a tool to interpret it as a destroy-and-create operation even when the underlying object could safely be retained. Refactoring mechanisms, such as explicit resource moves, help preserve identity during configuration restructuring. Import procedures are used when infrastructure already exists but must be brought under IaC management. Modules, Dependencies, and Reusable ArchitectureA module is a reusable unit of infrastructure definition. It may represent a network, a database service, a Kubernetes cluster, or an organizational baseline. Good modules expose a small, meaningful interface through inputs and outputs while hiding implementation details. Modules should have documented assumptions, versioning, examples, validation rules, and secure defaults. Excessively large modules become difficult to upgrade and reuse. Excessively small modules create fragmentation and obscure the architecture. A practical module boundary often corresponds to a coherent capability with a clear lifecycle. Dependencies can be explicit or inferred. If a virtual machine refers to a subnet identifier, the tool can infer that the subnet must exist first. An explicit dependency may be necessary when a relationship is operational rather than represented by a direct attribute—for example, when a service requires a policy or a readiness action before creation. Explicit dependencies should be used carefully because unnecessary dependencies reduce parallelism and make plans harder to understand. The dependency graph determines ordering and enables parallel execution. Cyclic dependencies indicate a design problem. For example, a security policy that requires an output from a service which itself requires that policy cannot be created directly without changing the design, using staged provisioning, or introducing an intermediate identity. Environments and Promotion StrategiesOrganizations commonly maintain development, test, staging, and production environments. The central design question is how to represent their similarities and differences without creating uncontrolled duplication. Separate state per environment is essential. A development apply must never be able to overwrite production state accidentally. Separation may be implemented through distinct directories, workspaces, accounts or subscriptions, backend keys, and credentials. Strong isolation usually combines multiple controls rather than relying on naming conventions alone. Two common models are environment-specific configuration and layered composition. In the first, each environment supplies different variables to a shared module. In the second, a common foundation is composed with environment-specific layers. Both can work, but values that affect security, networking, or availability should be explicit and reviewed rather than hidden in complex conditionals. Promotion should normally move an approved version of infrastructure code through environments. A production deployment should not be produced by editing files manually after testing. Plan artifacts, versioned modules, approval gates, and environment-specific policy checks improve traceability. Credentials should be obtained through a secure identity mechanism, short-lived tokens, or a CI/CD secret store. They should not be embedded in configuration, committed to version control, or printed in logs. Sensitive variables should be marked and handled by backends and pipelines that support appropriate redaction. Drift and Change ManagementDrift occurs when actual infrastructure differs from the configuration and state expected by the IaC workflow. It may result from console changes, emergency commands, provider-side defaults, automatic scaling, external controllers, or unmanaged teams. Some drift is intentional. For example, a managed service may update an operational attribute automatically. Other drift is dangerous, such as an opened firewall rule or an unapproved public endpoint. Drift detection can be performed through scheduled plans, provider inventory comparisons, cloud configuration services, or policy scanners. The response to drift should be deliberate. If the configuration is authoritative, the team may reapply it to restore the desired state. If the manual change is legitimate, it should be incorporated into code and reviewed. If the resource should no longer be managed, ownership should be explicitly changed rather than silently ignoring the difference. Ignoring drift creates two sources of truth. Over time, teams lose confidence in plans, incident recovery becomes uncertain, and security reviews become less reliable. Emergency changes should therefore have an expiry or follow-up process that returns the environment to controlled management. Policy-as-Code and GovernancePolicy-as-code expresses organizational rules in a machine-evaluable form. Policies may prohibit public storage, require encryption, restrict geographic regions, enforce approved instance types, mandate tags, or prevent excessive privileges. They can operate before provisioning, during plan review, or continuously against the deployed environment. A useful governance model combines preventive, detective, and corrective controls. Preventive policies block unacceptable plans. Detective controls identify violations that arise through drift or provider behavior. Corrective processes remediate or escalate the problem. Policies should be understandable, versioned, tested, and accompanied by an exception process. Policy must distinguish between a genuine risk and a harmless difference. A rule that blocks every unusual configuration may encourage teams to bypass governance. Exceptions should have an owner, justification, scope, expiry date, and compensating controls. IaC also supports financial governance. Required cost-center tags, budget alerts, approved regions, resource limits, and review thresholds can be encoded into pipelines. Security and cost policies are most effective when applied early, before resources are created. Practical Workflow for a Production IaC ChangeConsider a team adding a private database subnet to an existing application environment. The engineer first updates the module and adds validation for address ranges and required tags. Formatting, static analysis, security scanning, and unit tests run locally or in CI. A plan is generated against the correct remote state and reviewed by an engineer responsible for networking. The plan reveals that the subnet can be created without replacing existing application resources. A policy check confirms that the subnet has no public route and that encryption-related defaults are present for the intended database service. After approval, the pipeline applies the change using a restricted deployment identity. Post-apply checks verify route tables, security groups, tags, and connectivity from an approved test location. If the plan instead proposed replacing a production database, the correct response would be to stop and investigate. The team might need to preserve resource identity, use a migration strategy, create a new resource and replicate data, or change the module lifecycle rules. The existence of a plan does not make a dangerous action safe; it makes the proposed action visible before execution. Illustrative Case Study: Standardizing a Multi-Account PlatformBackgroundA fictional financial-services organization, Northstar Payments, operates development, testing, and production workloads across several cloud accounts. Teams created infrastructure independently, resulting in inconsistent network rules, missing tags, different logging configurations, and unclear ownership of databases. Situation and problemAn internal review found that some storage resources were publicly accessible and that disaster-recovery documentation did not match the deployed architecture. The platform team had to standardize foundational infrastructure without interrupting existing applications. It also needed to allow product teams to deploy independently while preserving central security controls. AnalysisThe team identified three categories of infrastructure. Network boundaries, identity integration, logging, and baseline policies were classified as shared platform capabilities. Application databases and compute services were classified as product-owned resources. Temporary migration utilities were considered unsuitable for permanent state management. A purely SDK-based solution was rejected because it would require the platform team to build and maintain its own dependency graph, state model, and approval controls. A single enormous Terraform configuration was also rejected because it would create a large blast radius and make ownership unclear. Approach and strategyThe organization created versioned modules for network foundations, audit logging, and identity integration. Separate remote state was established for each account and environment. Product teams consumed approved modules and maintained their application-level stacks in separate repositories. CI pipelines performed formatting, validation, security scanning, policy checks, and plan generation. Policies required encryption, ownership tags, private database placement, and approved regions. Production plans required two reviewers, while development plans used a lighter approval process. Existing resources were imported gradually, with each import followed by configuration alignment and drift review. Python utilities supplemented the declarative system. They discovered resources lacking required tags, generated migration reports, and queried cloud APIs for evidence after deployment. They did not directly modify production resources, thereby preserving the IaC workflow as the authoritative change mechanism. OutcomeThe organization obtained a consistent baseline, clearer ownership, and repeatable environment creation. The migration also exposed limitations: some legacy resources did not map neatly to module assumptions, and provider differences required exceptions. These exceptions were documented and assigned owners rather than hidden in ad hoc scripts. Learning pointsThe case demonstrates that standardization is an organizational design problem as well as a tooling problem. State boundaries should reflect ownership and failure domains. Declarative modules can establish consistency, while Python is valuable for discovery, reporting, and verification. Finally, importing existing resources requires patience; attempting to automate the entire migration in one operation would have increased risk. Risks, Limitations, and Common MistakesA frequent mistake is treating IaC as automatically safe. It can accelerate both good and bad decisions. Common risks include: - storing state without adequate protection;
- using broad cloud credentials in CI;
- applying from an engineer’s laptop without review;
- ignoring provider and module version constraints;
- allowing manual changes with no reconciliation process;
- embedding secrets in variables, logs, or outputs;
- creating overly broad modules with hidden behavior;
- using one state file for unrelated teams or environments;
- failing to test destructive changes;
- assuming a successful API response means every dependent operation succeeded.
There are also technical limitations. Provider schemas may lag behind new cloud features. Some resources have weak import support or complex replacement behavior. Cloud APIs may be eventually consistent, rate-limited, or inconsistent across regions. A plan can become stale between review and apply, especially in highly dynamic environments. Teams must therefore choose appropriate locking, refresh, timing, and post-deployment verification practices. From a professional perspective, ownership and accountability matter. A platform team may provide modules, but application teams remain responsible for understanding the capacity, security, and cost implications of the resources they consume. Automation should clarify responsibility, not obscure it. Governance, Ethics, and Professional ResponsibilityInfrastructure decisions affect confidentiality, availability, privacy, and financial resources. Engineers should apply least privilege, protect customer data, document exceptions, and consider the consequences of automation failures. A script that deletes unused resources may reduce cost but could destroy evidence or violate retention obligations if its classification logic is wrong. Regulated organizations may require separation of duties, approval records, retention of deployment evidence, regional data controls, and demonstrable access reviews. IaC can support these requirements through immutable logs, policy checks, signed artifacts, and controlled deployment identities, but compliance is not achieved by tooling alone. Transparency is also important. Teams should know whether a module creates external monitoring, collects metadata, or enables access for a central platform. Reusable abstractions must not conceal material security or privacy consequences. Emerging DirectionsIaC is increasingly integrated with software supply-chain security, continuous compliance, and platform engineering. Organizations are using standardized internal platforms to expose approved infrastructure capabilities through self-service interfaces. Policy engines are becoming more capable of evaluating plans, configurations, and live resources together. Infrastructure testing is also expanding. Static checks inspect configuration; unit tests evaluate programmatic components; integration tests provision temporary environments; and post-deployment tests verify behavior. Artificial intelligence may assist with configuration explanation, anomaly detection, plan summarization, and remediation suggestions. Such assistance should remain subject to human review because generated changes may misunderstand dependencies, security requirements, or business context. Another emerging concern is managing infrastructure across multiple clouds and edge locations. Abstraction can reduce duplication, but excessive portability layers may hide important provider-specific capabilities. The strategic objective should be informed control rather than portability for its own sake. Best-Practice PrinciplesA mature IaC practice generally follows these principles: 1. Keep infrastructure definitions in version control and require peer review. 2. Separate state by environment, ownership, and failure domain. 3. Protect remote state with encryption, access control, versioning, and locking. 4. Prefer small, documented, versioned modules with secure defaults. 5. Pin important tool, provider, and module versions. 6. Generate and review plans before production changes. 7. Use short-lived deployment identities and least privilege. 8. Treat drift as a governance signal, not merely an inconvenience. 9. Encode security, cost, and compliance requirements as testable policies. 10. Use Python and SDKs strategically for discovery, orchestration, and verification rather than bypassing lifecycle controls. 11. Test replacement, failure, rollback, and recovery behavior. 12. Maintain clear ownership, exception records, and operational documentation. Chapter SummaryInfrastructure as Code applies software-engineering discipline to cloud and platform infrastructure. Declarative tools such as Terraform and OpenTofu describe desired resources and use plans, providers, dependency graphs, and state to reconcile environments. Pulumi provides a code-first approach, while cloud SDKs and Python offer flexible, lower-level control for discovery, orchestration, testing, and specialized workflows. State is central to identity, collaboration, and safe reconciliation; it must be protected, separated, locked, and backed up. Modules support reuse, but they require clear interfaces and versioning. Environment separation, promotion, drift management, and policy-as-code transform isolated scripts into a governed delivery practice. The best architecture is often hybrid: declarative IaC for durable resource management, complemented by programmatic tools for tasks that require algorithms, reporting, or integration. Professional success depends not only on tool selection but also on security, ownership, review, testing, and responsible change management. Key TermsInfrastructure as Code: Management of infrastructure through versioned, machine-readable definitions or programs. Declarative model: An approach that specifies the desired end state while the tool determines execution steps. Programmatic provisioning: Infrastructure creation or management using general-purpose code or SDK calls. Desired state: The target configuration an environment is intended to have. Actual state: The configuration currently observed in the provider environment. Idempotence: The property that repeated application of an operation produces the same intended result without unnecessary duplication. Provider: An integration component that translates IaC resources into cloud or service API operations. State: Data recording managed resource identities, relationships, and relevant attributes. State locking: A mechanism preventing concurrent operations from modifying shared state. Drift: A difference between declared or recorded infrastructure and the live environment. Module: A reusable, versioned unit of infrastructure definition. Dependency graph: A representation of ordering relationships among resources and operations. Import: The process of bringing pre-existing infrastructure under IaC management. Remote backend: A protected service used to store and coordinate IaC state. Policy-as-code: Machine-evaluable rules governing infrastructure security, cost, compliance, or architecture. Plan: A calculated proposal showing changes required to reconcile configuration and infrastructure. Lifecycle rule: A configuration instruction controlling creation, replacement, protection, or destruction behavior. Environment promotion: Moving a reviewed infrastructure version through development, testing, and production stages. Review QuestionsShort Answer Questions1. What is the difference between desired state and actual state in IaC? 2. Why is remote state preferable to an unprotected local state file? 3. Define infrastructure drift and identify two causes of it. 4. What purpose does a module serve? 5. Why should deployment credentials normally be short-lived and least privileged? Long Answer Questions1. Compare declarative provisioning with programmatic provisioning, including their strengths, limitations, and appropriate use cases. 2. Explain how state, locking, resource identity, and import operations contribute to safe infrastructure management. 3. Discuss how modules, dependency graphs, and environment separation support scalable platform engineering. 4. Explain the role of policy-as-code in security, compliance, and financial governance. 5. Design a production IaC workflow covering validation, planning, review, application, and post-deployment verification. Analytical / Application Questions1. A team’s plan proposes replacing a production database after a module refactor. Analyze possible causes and recommend a safe investigation and migration strategy. 2. An organization permits emergency console changes but has no drift process. Evaluate the operational and governance risks and propose a reconciliation model. 3. A company is choosing between OpenTofu, Pulumi, and Python SDK automation for a multi-account platform. Develop a decision framework using team skills, state, testing, provider support, governance, and lifecycle requirements. 22. Multiple Choice Questions with AnswersQuestion 1Which characteristic most directly distinguishes a declarative IaC approach from a programmatic provisioning approach? A. Declarative tools can never use variables or conditions. B. Declarative tools specify the desired result while the tool determines execution steps. C. Programmatic tools cannot interact with cloud APIs. D. Declarative tools do not require version control. Correct Answer: B. Declarative tools specify the desired result while the tool determines execution steps. Question 2Why is state locking important when multiple engineers or pipelines use the same IaC environment? A. It encrypts all cloud resources automatically. B. It prevents provider APIs from returning errors. C. It prevents concurrent operations from corrupting shared state. D. It eliminates the need for code review. Correct Answer: C. It prevents concurrent operations from corrupting shared state. Question 3A cloud resource exists already but is not represented in an IaC state file. Which operation is most relevant? A. Import B. Drift suppression C. Module destruction D. Policy exemption Correct Answer: A. Import Question 4Which situation is the strongest example of infrastructure drift? A. A reviewed configuration change is applied through the pipeline. B. A resource is created in a temporary test account and destroyed afterward. C. An engineer changes a production firewall rule manually without updating IaC. D. A module is versioned and released with documented inputs. Correct Answer: C. An engineer changes a production firewall rule manually without updating IaC. Question 5What is an appropriate use of Python alongside declarative IaC? A. Storing production secrets in source code for easy access. B. Discovering untagged resources and generating compliance reports. C. Bypassing state management for every production change. D. Replacing all dependency and lifecycle controls with loops. Correct Answer: B. Discovering untagged resources and generating compliance reports. Question 6Which design best reduces the risk that a development deployment will affect production? A. Use one shared state file and different naming conventions. B. Allow all environments to use the same deployment identity. C. Separate state, credentials, and environment boundaries. D. Disable plans for development deployments. Correct Answer: C. Separate state, credentials, and environment boundaries. Question 7What is the principal governance function of policy-as-code? A. It guarantees that every cloud API call succeeds. B. It converts organizational rules into machine-evaluable controls. C. It removes the need for infrastructure documentation. D. It automatically makes all modules provider-independent. Correct Answer: B. It converts organizational rules into machine-evaluable controls. Question 8A plan shows that a minor logical rename will destroy and recreate a critical resource. What should the team do first? A. Apply immediately because the plan is generated automatically. B. Ignore the plan and make the change manually in the console. C. Investigate resource identity and use an appropriate move or migration strategy. D. Delete the state file and rerun the plan. Correct Answer: C. Investigate resource identity and use an appropriate move or migration strategy. Question 9Why can an excessively large infrastructure module be problematic? A. It cannot contain any inputs or outputs. B. It may create unclear ownership, difficult upgrades, and a larger blast radius. C. It prevents all resources from being version controlled. D. It guarantees cyclic dependencies. Correct Answer: B. It may create unclear ownership, difficult upgrades, and a larger blast radius. Question 10Which approach is most appropriate for a complex workflow requiring external data discovery, custom logic, and post-deployment verification? A. Use only manually executed console actions. B. Use Python or an SDK in combination with controlled IaC where suitable. C. Remove state management entirely. D. Encode all behavior as undocumented provider defaults. Correct Answer: B. Use Python or an SDK in combination with controlled IaC where suitable.
|