Managing Kubernetes Configuration at Scale with Terraform ConfigMaps

Kubernetes configuration management has evolved significantly beyond the traditional method of embedding settings directly into container images or relying on manual YAML edits. The kubernetes_config_map resource in Terraform provides a declarative, version-controllable, and automation-ready mechanism to manage configuration data. For engineering teams and DevOps practitioners, the ability to treat configuration as code is essential for maintaining consistency across environments, facilitating disaster recovery, and enabling seamless CI/CD pipelines. By leveraging Terraform to manage ConfigMaps, teams can decouple application configuration from the underlying infrastructure code, ensuring that changes to settings such as database connection strings, feature flags, and file-based configurations are applied atomically and safely.

This article provides a comprehensive technical deep dive into the kubernetes_config_map resource. It covers provider configuration, basic key-value implementation, file-based data ingestion, dynamic environment-based logic, and advanced consumption patterns within Kubernetes pods. We will analyze the syntax, provider versions, and best practices derived from authoritative documentation and real-world implementation examples.

Provider Configuration and Versioning

Before defining any resources, it is critical to establish the provider configuration. The Terraform Kubernetes provider acts as the bridge between Terraform and the Kubernetes API server. The version of the provider dictates which API versions and features are supported. Based on current documentation and examples, the recommended provider is hashicorp/kubernetes.

In recent implementations, the provider version is often pinned to a specific range to ensure stability. For example, some legacy or specific examples utilize ~> 2.0, while newer documentation and best practices often recommend ~> 2.25 or even ~> 3.0 depending on the cluster's API version and the specific features required. The required_version for Terraform itself is typically set to >= 1.0.0 or >= 1.0 to ensure compatibility with modern Terraform language features.

The provider block must define how Terraform authenticates with the Kubernetes cluster. This can be done via several methods, including a local configuration file, environment variables, or explicit certificate variables.

Local Configuration File Method

The simplest approach for local development or CI/CD systems with access to a standard kubeconfig is to point to the configuration file.

```hcl
terraform {
requiredversion = ">= 1.0"
required
providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.25"
}
}
}

provider "kubernetes" {
config_path = "~/.kube/config"
}
```

In this configuration, Terraform reads the default context from the user's kubeconfig file. This is ideal for local testing where kubectl is already configured.

Certificate-Based Authentication

For production environments, particularly when integrating with cloud providers or secure CI/CD runners, explicit certificate-based authentication is often preferred. This method requires passing the cluster host, client certificate, client key, and cluster CA certificate. These values are frequently passed as base64-encoded strings.

```hcl
variable "host" {
type = string
}

variable "client_certificate" {
type = string
}

variable "client_key" {
type = string
}

variable "clustercacertificate" {
type = string
}

provider "kubernetes" {
host = var.host
clientcertificate = base64decode(var.clientcertificate)
clientkey = base64decode(var.clientkey)
clustercacertificate = base64decode(var.clustercacertificate)
}
```

This pattern is common in tutorials involving local clusters managed by tools like kind (Kubernetes IN Docker). The variables are populated by extracting data from kubectl config view --minify --flatten. This ensures that the Terraform provider uses the exact same credentials as the kubectl context, reducing configuration drift.

Basic ConfigMap Implementation with Key-Value Pairs

The most fundamental use case for kubernetes_config_map is storing simple string key-value pairs. This is particularly useful for storing environment variables, connection strings, and application settings that do not require complex file structures.

Simple String Data

The data block within the resource definition accepts a map of strings. Keys must be unique within the ConfigMap.

```hcl
resource "kubernetesconfigmap" "app_settings" {
metadata {
name = "app-settings"
namespace = "default"
labels = {
app = "my-app"
managed-by = "terraform"
}
}

data = {
# Database connection settings
DATABASEHOST = "postgres.database.svc.cluster.local"
DATABASE
PORT = "5432"
DATABASE_NAME = "myapp"

# Application settings
LOG_LEVEL       = "info"
CACHE_TTL_SECONDS = "300"
MAX_CONNECTIONS = "100"

# Feature flags
ENABLE_FEATURE_X = "true"
ENABLE_BETA_UI   = "false"

}
}
```

In this example, the ConfigMap named app-settings is created in the default namespace. The labels app and managed-by help with resource management and identification. The data block contains various application settings. Note that all values are strings. If a boolean or integer value is required, it must be stringified. For instance, LOG_LEVEL is set to "info", and ENABLE_FEATURE_X is set to "true".

Minimal Example

For a quick sanity check or a very simple application, a minimal configuration might look like this:

```hcl
resource "kubernetesconfigmap" "changemesimpleconfig_map" {
metadata {
name = "changeme-simple-config-map"
}

data = {
apihost = "myhost:443"
db
host = "dbhost:5432"
}
}
```

This example demonstrates the bare minimum required fields: the metadata block with a name, and the data block with at least one key-value pair.

File-Based Configuration and Binary Data

Applications often require complex configuration files, such as nginx.conf, application.yaml, or certificate bundles. Terraform allows these files to be read from the local filesystem and stored within the ConfigMap.

Using file() for Text Content

The file() function reads the content of a file into a string. This is ideal for text-based configuration files.

```hcl
resource "kubernetesconfigmap" "changemefromfilesconfigmap" {
metadata {
name = "changeme-from-files-config-map"
}

data = {
# Read the content of the YAML file
"myconfigfile.yml" = "${file("${path.module}/myconfigfile.yml")}"
}

binarydata = {
# Read and base64-encode the binary file
"my
payload.bin" = "${filebase64("${path.module}/my_payload.bin")}"
}
}
```

In this configuration, my_config_file.yml is read using the file() function. The path is constructed dynamically using path.module, ensuring the file is located relative to the directory containing the Terraform configuration. The key in the ConfigMap is "my_config_file.yml", which allows pods to mount this key as a file with that specific name.

Handling Binary Data

For binary files, such as images, certificates, or serialized objects, the binary_data block is used. This block accepts a map of strings that are base64-encoded. The filebase64() function reads the file and encodes it, satisfying the requirement.

Field Type Description
data map(string) A map of string keys to string values. Suitable for text data.
binary_data map(string) A map of string keys to base64-encoded string values. Suitable for binary data.

Advanced Patterns: Dynamic Configuration

Hardcoding values into Terraform files is not scalable for multi-environment deployments. Terraform’s dynamic features, such as variables and locals, allow for the creation of environment-aware ConfigMaps.

Merging Environment-Specific Data

By using variables and local values, you can define a base configuration and merge it with environment-specific overrides. This pattern ensures that common settings are consistent while allowing for environment-specific tuning.

```hcl
variable "environment" {
type = string
default = "production"
}

variable "app_config" {
type = map(string)
default = {}
}

locals {
# Base configuration that applies everywhere
baseconfig = {
APP
NAME = "my-application"
APP_VERSION = "3.0.0"
}

# Environment-specific configuration
envconfig = {
production = {
LOG
LEVEL = "warn"
DEBUGMODE = "false"
CACHE
ENABLED = "true"
MAXCONNECTIONS = "500"
}
staging = {
LOG
LEVEL = "info"
DEBUGMODE = "false"
CACHE
ENABLED = "true"
MAXCONNECTIONS = "100"
}
development = {
LOG
LEVEL = "debug"
DEBUGMODE = "true"
CACHE
ENABLED = "false"
MAX_CONNECTIONS = "20"
}
}
}

resource "kubernetesconfigmap" "dynamic_config" {
metadata {
name = "app-config-${var.environment}"
namespace = var.environment
}

# Merge base config with environment-specific values and any overrides
data = merge(
local.baseconfig,
local.env
config[var.environment],
var.app_config
)
}
```

In this example:
1. The environment variable determines the target environment.
2. local.base_config defines shared settings.
3. local.env_config defines settings specific to each environment.
4. The merge() function combines these maps. If a key exists in multiple maps, the rightmost map wins. Thus, var.app_config can override any other settings, providing a flexible mechanism for temporary overrides without modifying the core code.
5. The name of the ConfigMap and the namespace are also derived from the environment variable, ensuring isolation between environments.

Consuming ConfigMaps in Pods

Creating a ConfigMap is only the first step; applications must consume it. This is typically done by mounting the ConfigMap as a volume or setting environment variables within the pod spec.

Mounting as a Volume

Mounting allows the application to read configuration as files. This is useful for applications that expect configuration files at specific paths, such as /etc/nginx/nginx.conf.

```hcl
resource "kubernetes_pod" "example" {
metadata {
name = "example-pod"
}

spec {
container {
name = "nginx"
image = "nginx:stable-alpine-slim"

  volume_mount {
    name       = "nginx-config"
    mount_path = "/etc/nginx"
    sub_path   = "nginx.conf" # Mount a single file, not the whole dir
    read_only  = true
  }

  resources {
    requests = {
      cpu    = "50m"
      memory = "64Mi"
    }
  }
}

volume {
  name = "nginx-config"
  config_map {
    name        = kubernetes_config_map.nginx_config.metadata[0].name
    default_mode = "0644" # Set file permissions
  }
}

}
}
```

In this configuration, the volume_mount block specifies sub_path = "nginx.conf", which mounts only the specific key nginx.conf from the ConfigMap as a single file. The default_mode in the config_map volume source sets the file permissions to 0644, which is a standard readable/writable-by-owner mode.

Comparison of Consumption Methods

Method Use Case Pros Cons
Volume Mount Config files (YAML, XML, INI) No restart required for some apps; supports complex structures. Not suitable for simple key-value env vars.
Environment Variable Simple settings, connection strings Easy to access in most runtimes. Secrets are visible in process environment; limited size.
Sub-Path Mount Specific file within a large ConfigMap Allows mounting one file without exposing the whole directory. Slightly more complex configuration.

Metadata and Resource Attributes

Understanding the metadata attributes is crucial for managing and importing resources. The metadata block contains standard Kubernetes object metadata.

  • name: (Optional) Name of the config map, must be unique. Cannot be updated after creation.
  • namespace: (Optional) Namespace defines the space within which name of the config map must be unique. Defaults to default if not specified.
  • labels: (Optional) Map of labels to add to the resource.
  • generation: A sequence number representing a specific generation of the desired state.
  • resource_version: An opaque value that represents the internal version of this config map that can be used by clients to determine when config map has changed.
  • self_link: A URL representing this config map.
  • uid: The unique in time and space value for this config map.

These attributes are often exposed as read-only attributes in Terraform state. For example, you can reference kubernetes_config_map.app_settings.metadata[0].uid if you need the unique identifier for another resource.

Importing Existing ConfigMaps

When adopting Terraform for existing infrastructure, it is often necessary to import resources that were created manually or via kubectl. The kubernetes_config_map resource supports importation using the namespace and name.

The syntax for importing a ConfigMap is:

bash $ terraform import kubernetes_config_map.example default/my-config

In this command:
* kubernetes_config_map.example is the resource address in your Terraform configuration.
* default/my-config is the namespace and name of the existing ConfigMap.

After import, you must ensure that the Terraform configuration matches the imported resource to prevent state drift. This is a critical step in adopting infrastructure as code for existing systems.

Conclusion

The kubernetes_config_map resource in Terraform is a powerful tool for managing Kubernetes configuration. By treating configuration as code, teams can ensure consistency, version control, and automation. The provider supports various authentication methods, from simple local kubeconfig files to complex certificate-based setups. The resource itself supports both simple key-value pairs and complex file-based data, including binary content.

Advanced patterns, such as dynamic configuration using merge() and environment variables, allow for scalable multi-environment deployments. Consuming these ConfigMaps via volume mounts or environment variables provides flexibility for different application architectures. Understanding the metadata attributes and the import mechanism further enhances the operational capability of managing Kubernetes resources via Terraform.

For production environments, it is recommended to:
1. Pin the provider version to a tested range.
2. Use secrets for sensitive data, as ConfigMaps are not encrypted.
3. Utilize labels for effective resource management and querying.
4. Regularly review and update configurations as part of the CI/CD pipeline.

By following these practices, organizations can achieve robust, reliable, and manageable Kubernetes configurations.

Sources

  1. Terraform Examples - Kubernetes Config Map
  2. OneUptime - How to Create Kubernetes ConfigMaps with Terraform
  3. W3Cub - Terraform Kubernetes Config Map
  4. HashiCorp - Terraform Kubernetes Provider Tutorial

Related Posts