Summary
The problem at hand is injecting a complex YAML configuration with arrays and nested objects into a Go application running on Kubernetes, deployed using Helm. The application uses koanf to load configuration from YAML files and environment variables. The goal is to find a clean and idiomatic Kubernetes solution to inject this configuration without flattening it manually into environment variables.
Root Cause
The root cause of the problem is that environment variables are flat strings, which makes it difficult to inject a complex YAML configuration with arrays and nested objects. Additionally, Helm templating for arrays into env vars becomes very verbose and error-prone.
Why This Happens in Real Systems
This problem occurs in real systems because:
- Environment variables are not designed to handle complex configurations with arrays and nested objects.
- Helm templating can become verbose and error-prone when dealing with complex configurations.
- Kubernetes and Helm provide alternative solutions, such as ConfigMaps, to handle complex configurations.
Real-World Impact
The real-world impact of this problem is:
- Increased complexity in managing and injecting complex configurations into applications.
- Error-prone configurations that can lead to application failures or unexpected behavior.
- Difficulty in scaling applications that require complex configurations.
Example or Code
package main
import (
"fmt"
"io/ioutil"
"path/filepath"
"github.com/kubernetes/client-go/util/homedir"
"k8s.io/client-go/rest"
)
func main() {
// Create a ConfigMap
configMap := map[string]string{
"cron": `
jobs:
- job_kind: daily
topic: Topic1
interval: 1
day_of_month_to_start: 1
day_of_month_to_stop: 31
at_times:
- hour: 05
minute: 00
second: 00
- hour: 11
minute: 00
second: 00
- job_kind: monthly
topic: Topic2
interval: 1
days_of_the_month:
- -1
at_times:
- hour: 21
minute: 30
second: 0
`,
}
// Create a Kubernetes client
kubeConfig := filepath.Join(homedir.HomeDir(), ".kube", "config")
config, err := rest.InClusterConfig()
if err != nil {
config, err = rest.LoadKubeConfig(kubeConfig)
if err != nil {
fmt.Println(err)
return
}
}
// Create a ConfigMap object
configMapObj := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "my-config",
},
Data: configMap,
}
// Apply the ConfigMap to the cluster
configMapClient := kubeclientset.CoreV1().ConfigMaps("default")
_, err = configMapClient.Create(context.TODO(), configMapObj, metav1.CreateOptions{})
if err != nil {
fmt.Println(err)
return
}
// Load the ConfigMap into the application
configMap, err = configMapClient.Get(context.TODO(), "my-config", metav1.GetOptions{})
if err != nil {
fmt.Println(err)
return
}
// Unmarshal the ConfigMap into a Go struct
var cronConfig CronConfig
err = yaml.Unmarshal([]byte(configMap.Data["cron"]), &cronConfig)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(cronConfig)
}
type CronConfig struct {
Jobs []Job `yaml:"jobs"`
}
type Job struct {
JobKind string `yaml:"job_kind"`
Topic string `yaml:"topic"`
Interval int `yaml:"interval"`
DayOfMonthToStart int `yaml:"day_of_month_to_start"`
DayOfMonthToStop int `yaml:"day_of_month_to_stop"`
AtTimes []AtTime `yaml:"at_times"`
DaysOfTheMonth []int `yaml:"days_of_the_month"`
}
type AtTime struct {
Hour int `yaml:"hour"`
Minute int `yaml:"minute"`
Second int `yaml:"second"`
}
How Senior Engineers Fix It
Senior engineers fix this problem by:
- Using ConfigMaps to store complex configurations with arrays and nested objects.
- Creating a Kubernetes client to apply the ConfigMap to the cluster.
- Loading the ConfigMap into the application using the Kubernetes client.
- Unmarshaling the ConfigMap into a Go struct using a YAML unmarshaller.
Why Juniors Miss It
Juniors may miss this solution because:
- They may not be familiar with ConfigMaps and their use cases.
- They may not know how to create a Kubernetes client and apply a ConfigMap to the cluster.
- They may not understand how to load and unmarshal a ConfigMap into a Go struct.
- They may not be aware of the best practices for handling complex configurations in Kubernetes and Helm.