The integration of Infrastructure as Code into container orchestration environments presents a unique challenge when dealing with the extreme flexibility of the Kubernetes API. While the official Kubernetes provider for Terraform handles standard, well-defined resources effectively, it often falls short when confronted with the vast ecosystem of Custom Resource Definitions (CRDs), operator-managed resources, and complex, multi-document manifests that the community continuously introduces. The kubectl_manifest resource, primarily provided by the kubectl provider, offers a robust solution to this gap. It allows engineers to apply arbitrary YAML manifests through Terraform, effectively mirroring the behavior of the kubectl apply -f command line utility. This approach enables the management of any object expressible in a Kubernetes manifest, ensuring that even the most exotic or rapidly evolving resources can be tracked, updated, and deleted seamlessly within a unified DevOps workflow.
The Need for Generic Manifest Management in Terraform
Kubernetes is an open-source workload scheduler with a primary focus on containerized applications. As the platform matures, the boundary between core API objects and user-defined resources blurs significantly. The official HashiCorp Kubernetes provider provides typed resources for standard entities such as deployments, services, config maps, and namespaces. However, the extensibility of Kubernetes means that a significant portion of production workloads relies on CRDs and custom resources that are not supported natively by the official provider's typed schema. Attempting to force these custom objects into the official provider often leads to brittle configurations, versioning conflicts, or the inability to manage resources at all.
The kubectl provider addresses this by utilizing the Kubernetes API server directly via the kubectl command-line tool's logic. It allows you to use YAML, the format Kubernetes prefers, to define resources. This is particularly critical for teams that standardize on YAML for manual deployments but wish to migrate those same artifacts into a Terraform-managed lifecycle. By using the kubectl_manifest resource, Terraform can track the state of these free-form YAML objects, handling creation, updates, and deletion, including drift detection. This ensures that if a developer modifies a manifest manually via kubectl, Terraform can detect the divergence during a plan operation and reconcile the state, bringing the cluster back in line with the source of truth defined in code.
Benefits of Unified Workflow and Lifecycle Management
Transitioning from CLI-based management to Terraform-based management offers several distinct advantages for organizational scaling. First, it creates a unified workflow. If an organization is already provisioning Kubernetes clusters, load balancers, and networking infrastructure with Terraform, using the same configuration language to deploy applications into those clusters simplifies the tooling stack. There is no need to maintain separate pipelines for infrastructure and application deployment.
Second, it provides full lifecycle management. Terraform does not merely create resources; it manages their entire lifecycle. It updates resources when configuration changes and deletes them when they are removed from the codebase. This eliminates the need for developers to inspect the API to identify orphaned resources or manually clean up state. Third, Terraform understands the graph of relationships between resources. For example, if a Persistent Volume Claim depends on a specific Persistent Volume, Terraform will not attempt to create the claim if the volume creation fails. This dependency graph ensures that complex, multi-resource deployments are applied in the correct order, reducing the risk of transient errors during deployment.
Provider Selection and Versioning Strategy
Selecting the correct provider source is a critical step in configuring kubectl_manifest. The history of this provider involves a transition from one source address to another, which has implications for state management and versioning.
Migration from gavinbunney to alekc
Early adopters of the kubectl provider utilized the source gavinbunney/kubectl. This provider was widely used in large Kubernetes installations to manage the lifecycle of resources. However, the provider has since transitioned to the alekc/kubectl source. Teams currently using the older source may need to migrate their Terraform state to avoid confusion or potential deprecation issues. The migration process is handled through the moved block in Terraform configuration, allowing for a seamless transition without recreating resources.
The following table compares the key attributes of the provider versions and sources:
| Attribute | gavinbunney/kubectl | alekc/kubectl (v2.x) | alekc/kubectl (v3.x) |
|---|---|---|---|
| Source Address | gavinbunney/kubectl |
alekc/kubectl |
alekc/kubectl |
| Terraform Version | 0.12+ | 1.0+ | 1.0+ |
| Protocol Version | 5.0 | 5.0 | 6.0 |
| Framework | SDK | SDK | Plugin Framework |
| Recommended Use | Legacy/Read-Only | Stable for older TF | Current/Future Proof |
For organizations running Terraform version 0.13 to 0.15, the v2.x line of alekc/kubectl is still the appropriate choice, as it serves protocol 5.0. However, for those running Terraform 1.0 or later, especially those looking to leverage the Terraform plugin framework, version 3.0 is the recommended standard. Version 3 is built on the Terraform plugin framework and serves plugin protocol 6.0 only. It is important to note that if a configuration utilizes ephemeral resources, the floor for the Terraform version moves up to 1.10, as ephemeral resources are a language feature introduced in that version regardless of the provider version.
Configuring the kubectl Provider
Configuring the kubectl provider requires pointing it to the correct Kubernetes cluster context. The provider uses the same kubeconfig as the official Kubernetes provider, allowing both to be configured to point at the same cluster if necessary. This is common in environments where standard resources are managed by the kubernetes provider while custom resources are managed by the kubectl provider.
A typical configuration block for the versions.tf file and the provider setup is shown below. Note the specification of the config_path and config_context, which allow for fine-grained control over which cluster is being managed.
```hcl
terraform {
requiredversion = ">= 1.5.0"
requiredproviders {
kubectl = {
source = "gavinbunney/kubectl"
version = "~> 1.14"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.25"
}
}
}
provider "kubectl" {
configpath = "~/.kube/config"
configcontext = "my-cluster"
}
```
For teams migrating to the newer alekc/kubectl provider, the configuration remains similar, though the source address changes. The provider can be installed and managed automatically by Terraform, simplifying the setup process for new projects.
Using kubectl_manifest for Single and Multi-Document YAML
The core resource for this approach is kubectl_manifest. It accepts a yaml_body argument containing the raw YAML string of the resource. This allows for the application of single resources or complex, multi-document YAML files that include multiple objects in a single document.
Single Manifest Application
To apply a single YAML manifest, you define a kubectl_manifest resource and assign the YAML content to the yaml_body attribute. This is particularly useful for resources that are difficult to express using Terraform's native HCL syntax or that are not supported by typed providers.
hcl
resource "kubectl_manifest" "my_app" {
yaml_body = <<-EOT
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
labels:
app: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-container
image: "nginx:latest"
ports:
- containerPort: 80
EOT
}
Multi-Document Processing with Data Sources
In many real-world scenarios, manifests are stored as separate YAML files in a directory structure. The kubectl provider provides helpful data resources to process directories of YAML files. The kubectl_path_documents data source allows you to read a pattern of files and generate a map of documents that can be iterated over using for_each.
This pattern is essential for teams that maintain a repository of manifests and wish to manage them entirely through Terraform. The following example demonstrates how to import a directory of YAML manifests and apply them dynamically:
```hcl
data "kubectlpathdocuments" "this" {
pattern = "./manifests//.yaml"
}
resource "kubectlmanifest" "this" {
foreach = data.kubectlpathdocuments.this.manifests
yaml_body = each.value
}
```
This approach ensures that any YAML file added to the ./manifests/ directory is automatically managed by Terraform. It also supports inline templating, allowing for dynamic values to be injected into the YAML before application.
Importing Existing Resources
A significant advantage of the kubectl_manifest resource is its capability to import existing resources into Terraform state. If a resource already exists in the cluster, Terraform must import it before it can manage it. This is the safest way to avoid recreating or destroying resources that are already running, which is a common risk when moving workloads into Terraform or standardizing cluster management.
The import syntax for kubectl_manifest is distinct from the official kubernetes provider. The import address must match the API path used internally. For example, to import an ArgoCD Application, the address is constructed from the API group, version, namespace, and resource name.
bash
terraform import --var-file=prod.tfvars \
'kubectl_manifest.this["/apis/argoproj.io/v1alpha1/namespaces/argocd/applications/grafana-dev"]'
This flexibility allows for the import of any resource type, including CRDs, as long as the API path is correctly formatted. This is particularly useful for legacy systems where resources were created manually or via other tools, and the organization wishes to bring them under version control and Terraform's purview without disrupting the running service.
Migration and State Management Considerations
When transitioning from one provider source to another, such as from gavinbunney/kubectl to alekc/kubectl, the state of the resources must be preserved. Terraform provides the moved block to handle this transition. This block tells Terraform that a resource with one address has moved to another, allowing it to update the state file without making any changes to the actual Kubernetes resources.
The migration process involves the following steps:
1. Pick a transitional address, such as adding a _v3 suffix to the resource name.
2. Add a moved block to the configuration, mapping the old address to the new one.
3. Run terraform init -upgrade to update the provider.
4. Run terraform plan to verify that the move is recognized and no in-place changes are proposed.
5. Run terraform apply to commit the move.
The following table outlines the attributes that are shared between the older gavinbunney provider and the newer alekc provider. These 20 attributes carry over unchanged, ensuring compatibility during migration.
| Attribute | Type | Description |
|---|---|---|
id |
string | The ID of the resource |
name |
string | The name of the resource |
namespace |
string | The namespace of the resource |
yaml_body |
string | The YAML body of the resource |
yaml_body_file |
string | The path to the YAML file |
dry_run |
bool | Whether to perform a dry run |
wait |
bool | Whether to wait for the resource to be ready |
wait_seconds |
number | The number of seconds to wait |
labels |
map(string) | Labels to apply to the resource |
annotations |
map(string) | Annotations to apply to the resource |
It is important to note that terraform plan -detailed-exitcode may return a code of 2 on the first plan after a moved block is introduced, even if the resource summary indicates "0 to add, 0 to change, 0 to destroy." This is because moved annotations count as "changes present" in the exit code logic. The second plan after the apply should return 0 cleanly. Operators should treat the plan summary line as authoritative for determining if the resources are stable.
Conclusion
The kubectl_manifest resource in Terraform is a powerful tool for managing the full spectrum of Kubernetes resources, from standard core objects to complex custom resources. By leveraging the provider's ability to apply raw YAML, organizations can maintain a unified, version-controlled, and lifecycle-managed approach to their Kubernetes infrastructure. The transition from legacy provider sources to the modern alekc/kubectl provider, while requiring careful state management via moved blocks, ensures that teams are using the most robust and forward-compatible toolset available.
The ability to import existing resources seamlessly and handle multi-document YAML files makes this approach ideal for organizations standardizing their cluster management. As Kubernetes continues to evolve with new CRDs and operator patterns, the generic nature of kubectl_manifest ensures that Terraform configurations remain adaptable without requiring changes to the underlying provider logic. For tech enthusiasts and DevOps teams looking to scale their Kubernetes operations, mastering the kubectl provider is an essential step toward achieving true Infrastructure as Code maturity.