Skip to content

Commit 10db19f

Browse files
committed
feat(network): add resolv.conf path customization
Adds configuration to customize resolv.conf file paths for pod and node DNS resolution in network disruptions. Users can now override default paths via Helm values, ConfigMap, or CLI flags to support different Kubernetes distributions (systemd-resolved, NetworkManager, etc.). Configuration: - injector.networkDisruption.dnsPodResolvConf - injector.networkDisruption.dnsNodeResolvConf Defaults: /etc/resolv.conf (pod), /mnt/host/etc/resolv.conf (node) Logs show which resolv.conf files are loaded with their nameservers for debugging. Jira: CHAOSPLT-1359
1 parent 91df8fa commit 10db19f

File tree

10 files changed

+279
-16
lines changed

10 files changed

+279
-16
lines changed

chart/templates/configmap.yaml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,15 +129,21 @@ data:
129129
{{- end }}
130130
serviceAccount: {{ .Values.injector.serviceAccount | quote }}
131131
chaosNamespace: {{ .Values.chaosNamespace | quote }}
132-
{{- if .Values.injector.networkDisruption.allowedHosts }}
133132
networkDisruption:
134133
hostResolveInterval: {{ .Values.injector.networkDisruption.hostResolveInterval | quote }}
134+
{{- if .Values.injector.networkDisruption.allowedHosts }}
135135
allowedHosts:
136136
{{- range $index, $allowedHost := .Values.injector.networkDisruption.allowedHosts }}
137137
{{ $v := printf "%s;%v;%s;%s" ($allowedHost.host | default "") ($allowedHost.port | default "") ($allowedHost.protocol | default "") ($allowedHost.flow | default "") -}}
138138
- {{ tpl $v $ }}
139139
{{- end }}
140-
{{- end }}
140+
{{- end }}
141+
{{- if .Values.injector.networkDisruption.dnsPodResolvConf }}
142+
dnsPodResolvConf: {{ .Values.injector.networkDisruption.dnsPodResolvConf | quote }}
143+
{{- end }}
144+
{{- if .Values.injector.networkDisruption.dnsNodeResolvConf }}
145+
dnsNodeResolvConf: {{ .Values.injector.networkDisruption.dnsNodeResolvConf | quote }}
146+
{{- end }}
141147
handler:
142148
image: {{ template "chaos-controller.format-image" deepCopy .Values.global.chaos.defaultImage | merge .Values.global.oci | merge .Values.handler.image }}
143149
enabled: {{ .Values.handler.enabled }}

chart/values.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ injector:
132132
# port: 81
133133
# protocol: tcp
134134
# flow: ingress
135+
dnsPodResolvConf: "/etc/resolv.conf" # path for pod DNS resolv.conf
136+
dnsNodeResolvConf: "/mnt/host/run/systemd/resolve/resolv.conf" # path for node DNS resolv.conf
135137
handler:
136138
image:
137139
repo: chaos-handler

cli/injector/network_disruption.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ var networkDisruptionCmd = &cobra.Command{
3232
hostResolveInterval, _ := cmd.Flags().GetDuration("host-resolve-interval")
3333
methods, _ := cmd.Flags().GetStringArray("method")
3434
paths, _ := cmd.Flags().GetStringArray("path")
35+
dnsPodResolvConf, _ := cmd.Flags().GetString("dns-pod-resolv-conf")
36+
dnsNodeResolvConf, _ := cmd.Flags().GetString("dns-node-resolv-conf")
3537

3638
// prepare injectors
3739
for i, config := range configs {
@@ -80,7 +82,12 @@ var networkDisruptionCmd = &cobra.Command{
8082
}
8183

8284
// generate injector
83-
inj, err := injector.NewNetworkDisruptionInjector(spec, injector.NetworkDisruptionInjectorConfig{Config: config, HostResolveInterval: hostResolveInterval})
85+
inj, err := injector.NewNetworkDisruptionInjector(spec, injector.NetworkDisruptionInjectorConfig{
86+
Config: config,
87+
HostResolveInterval: hostResolveInterval,
88+
DNSPodResolvConf: dnsPodResolvConf,
89+
DNSNodeResolvConf: dnsNodeResolvConf,
90+
})
8491
if err != nil {
8592
log.Fatalw("error initializing the network disruption injector: %w", err)
8693
}
@@ -103,4 +110,6 @@ func init() {
103110
networkDisruptionCmd.Flags().Duration("host-resolve-interval", time.Minute, "Interval to resolve hostnames")
104111
networkDisruptionCmd.Flags().StringArray("method", []string{}, "Filter by http method: GET, DELETE, POST, CREATE, PUT, HEAD, PATCH, CONNECT, OPTIONS or TRACE")
105112
networkDisruptionCmd.Flags().StringArray("path", []string{v1beta1.DefaultHTTPPathFilter}, "Filter by path and must not exceed 100 characters")
113+
networkDisruptionCmd.Flags().String("dns-pod-resolv-conf", "", "Path to pod DNS resolv.conf file (defaults to /etc/resolv.conf)")
114+
networkDisruptionCmd.Flags().String("dns-node-resolv-conf", "", "Path to node DNS resolv.conf file (defaults to /mnt/host/etc/resolv.conf)")
106115
}

config/config.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ type Toleration struct {
9999
type injectorNetworkDisruptionConfig struct {
100100
AllowedHosts []string `json:"allowedHosts" yaml:"allowedHosts"`
101101
HostResolveInterval time.Duration `json:"hostResolveInterval" yaml:"hostResolveInterval"`
102+
DNSPodResolvConf string `json:"dnsPodResolvConf" yaml:"dnsPodResolvConf"`
103+
DNSNodeResolvConf string `json:"dnsNodeResolvConf" yaml:"dnsNodeResolvConf"`
102104
}
103105

104106
type handlerConfig struct {
@@ -338,6 +340,18 @@ func New(client corev1client.ConfigMapInterface, logger *zap.SugaredLogger, osAr
338340
return cfg, err
339341
}
340342

343+
mainFS.StringVar(&cfg.Injector.NetworkDisruption.DNSPodResolvConf, "injector-network-disruption-dns-pod-resolv-conf", "/etc/resolv.conf", "Path to pod DNS resolv.conf file")
344+
345+
if err := viper.BindPFlag("injector.networkDisruption.dnsPodResolvConf", mainFS.Lookup("injector-network-disruption-dns-pod-resolv-conf")); err != nil {
346+
return cfg, err
347+
}
348+
349+
mainFS.StringVar(&cfg.Injector.NetworkDisruption.DNSNodeResolvConf, "injector-network-disruption-dns-node-resolv-conf", "/mnt/host/etc/resolv.conf", "Path to node DNS resolv.conf file")
350+
351+
if err := viper.BindPFlag("injector.networkDisruption.dnsNodeResolvConf", mainFS.Lookup("injector-network-disruption-dns-node-resolv-conf")); err != nil {
352+
return cfg, err
353+
}
354+
341355
mainFS.BoolVar(&cfg.Handler.Enabled, "handler-enabled", false, "Enable the chaos handler for on-init disruptions")
342356

343357
if err := viper.BindPFlag("handler.enabled", mainFS.Lookup("handler-enabled")); err != nil {

docs/network_disruption/hosts-and-services.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,10 +245,12 @@ apiVersion: chaos.datadoghq.com/v1beta1
245245
kind: Disruption
246246
metadata:
247247
name: network-disruption-istio
248+
namespace: chaos-demo
248249
spec:
249250
level: pod
250251
selector:
251252
app: my-service
253+
count: 1
252254
network:
253255
drop: 50
254256
hosts:
@@ -290,6 +292,81 @@ network:
290292
- **`pod-fallback-node`** (default): Use when you want resilience - try pod DNS first but fall back to node DNS if it fails
291293
- **`node-fallback-pod`**: Use when node DNS is preferred but you want pod DNS as a backup
292294

295+
#### Customizing resolv.conf Paths
296+
297+
By default, the chaos-controller uses these locations for resolv.conf files:
298+
299+
- **Pod DNS**: `/etc/resolv.conf`
300+
- **Node DNS**: `/mnt/host/etc/resolv.conf`
301+
302+
These defaults are set in the controller configuration. Some Kubernetes distributions or node configurations may use different locations for resolv.conf. You can override the defaults using Helm values:
303+
304+
**Helm Configuration:**
305+
306+
Override resolv.conf paths in your Helm values:
307+
308+
```yaml
309+
injector:
310+
networkDisruption:
311+
# Path for pod DNS resolv.conf
312+
# Default: /etc/resolv.conf
313+
dnsPodResolvConf: "/run/systemd/resolve/resolv.conf"
314+
315+
# Path for node DNS resolv.conf
316+
# Default: /mnt/host/etc/resolv.conf
317+
dnsNodeResolvConf: "/mnt/host/run/systemd/resolve/stub-resolv.conf"
318+
```
319+
320+
**Behavior:**
321+
- Defaults are defined in the controller configuration (can be overridden via ConfigMap, environment variables, or CLI flags)
322+
- The specified resolv.conf file must exist and be readable
323+
- Configuration is passed to injector pods as command-line arguments
324+
- Logging will indicate which resolv.conf file was loaded
325+
326+
**Example: Full Helm configuration for systemd-resolved nodes**
327+
328+
```yaml
329+
# values.yaml
330+
injector:
331+
networkDisruption:
332+
hostResolveInterval: 1m
333+
334+
# For nodes using systemd-resolved (Ubuntu, Debian, etc.)
335+
dnsNodeResolvConf: "/mnt/host/run/systemd/resolve/stub-resolv.conf"
336+
337+
# For pods with custom DNS configuration
338+
dnsPodResolvConf: "/run/systemd/resolve/resolv.conf"
339+
```
340+
341+
**How it works:**
342+
343+
When you deploy with these Helm values:
344+
1. The controller reads the configuration from the ConfigMap
345+
2. For each network disruption, the controller creates an injector pod with CLI arguments:
346+
```bash
347+
/chaos-injector network-disruption \
348+
--dns-pod-resolv-conf /run/systemd/resolve/resolv.conf \
349+
--dns-node-resolv-conf /mnt/host/run/systemd/resolve/stub-resolv.conf \
350+
...
351+
```
352+
3. The injector uses these paths for DNS resolution
353+
4. Logs will show which resolv.conf files were loaded:
354+
```
355+
INFO loaded pod DNS configuration resolv_conf_path=/run/systemd/resolve/resolv.conf nameservers=[8.8.8.8, 8.8.4.4]
356+
INFO loaded node DNS configuration resolv_conf_path=/mnt/host/run/systemd/resolve/stub-resolv.conf nameservers=[10.0.0.1]
357+
```
358+
359+
You can verify the paths used by an injector pod:
360+
```bash
361+
kubectl describe pod chaos-injector-xxxxx -n chaos-engineering | grep dns-
362+
```
363+
364+
**Use cases:**
365+
- **systemd-resolved**: Nodes using systemd-resolved may have resolv.conf at `/run/systemd/resolve/resolv.conf` or `/run/systemd/resolve/stub-resolv.conf`
366+
- **NetworkManager**: Some distributions use `/run/NetworkManager/resolv.conf`
367+
- **Custom Kubernetes distributions**: Distributions like k3s, microk8s, or OpenShift may use non-standard paths
368+
- **Custom DNS configurations**: Environments with custom DNS setups that require specific resolv.conf locations
369+
293370
### Some special cases
294371

295372
Cluster IPs can also be specified to target the relevant pods.

injector/network_disruption.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ type NetworkDisruptionInjectorConfig struct {
6565
DNSClient network.DNSClient
6666
HostResolveInterval time.Duration
6767
BPFConfigInformer ebpf.ConfigInformer
68+
DNSPodResolvConf string
69+
DNSNodeResolvConf string
6870
}
6971

7072
// tcServiceFilter describes a tc filter, representing the service filtered and its priority
@@ -144,7 +146,13 @@ func NewNetworkDisruptionInjector(spec v1beta1.NetworkDisruptionSpec, config Net
144146
}
145147

146148
if config.DNSClient == nil {
147-
config.DNSClient = network.NewDNSClient()
149+
// Create DNS client with custom resolv.conf paths if provided
150+
dnsConfig := network.DNSClientConfig{
151+
PodResolvConfPath: config.DNSPodResolvConf,
152+
NodeResolvConfPath: config.DNSNodeResolvConf,
153+
Logger: config.Log,
154+
}
155+
config.DNSClient = network.NewDNSClient(dnsConfig)
148156
}
149157

150158
if spec.HasHTTPFilters() && config.BPFConfigInformer == nil {

main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@ func main() {
204204
Labels: cfg.Injector.Labels,
205205
Tolerations: cfg.Injector.Tolerations,
206206
NetworkDisruptionAllowedHosts: cfg.Injector.NetworkDisruption.AllowedHosts,
207+
DNSPodResolvConf: cfg.Injector.NetworkDisruption.DNSPodResolvConf,
208+
DNSNodeResolvConf: cfg.Injector.NetworkDisruption.DNSNodeResolvConf,
207209
ImagePullSecrets: cfg.Injector.ImagePullSecrets,
208210
LogLevel: cfg.Injector.LogLevel,
209211
},

network/dns.go

Lines changed: 82 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/avast/retry-go"
1313
"github.com/hashicorp/go-multierror"
1414
"github.com/miekg/dns"
15+
"go.uber.org/zap"
1516
)
1617

1718
const (
@@ -23,6 +24,9 @@ const (
2324
DNSResolverPodFallbackNode = "pod-fallback-node"
2425
// DNSResolverNodeFallbackPod tries node first, then falls back to pod
2526
DNSResolverNodeFallbackPod = "node-fallback-pod"
27+
28+
DefaultPodResolvConfPath = "/etc/resolv.conf"
29+
DefaultNodeResolvConfPath = "/mnt/host/etc/resolv.conf"
2630
)
2731

2832
type DNSConfig struct {
@@ -36,11 +40,37 @@ type DNSClient interface {
3640
ResolveWithStrategy(host string, strategy string) ([]net.IP, error)
3741
}
3842

39-
type dnsClient struct{}
43+
type dnsClient struct {
44+
podResolvConfPath string
45+
nodeResolvConfPath string
46+
log *zap.SugaredLogger
47+
}
4048

41-
// NewDNSClient creates a standard DNS client
42-
func NewDNSClient() DNSClient {
43-
return dnsClient{}
49+
// DNSClientConfig contains configuration for the DNS client
50+
type DNSClientConfig struct {
51+
PodResolvConfPath string
52+
NodeResolvConfPath string
53+
Logger *zap.SugaredLogger
54+
}
55+
56+
// NewDNSClient creates a standard DNS client with optional configuration
57+
func NewDNSClient(config ...DNSClientConfig) DNSClient {
58+
client := dnsClient{
59+
podResolvConfPath: DefaultPodResolvConfPath,
60+
nodeResolvConfPath: DefaultNodeResolvConfPath,
61+
}
62+
63+
// Apply configuration if provided
64+
if len(config) > 0 {
65+
client.podResolvConfPath = config[0].PodResolvConfPath
66+
client.nodeResolvConfPath = config[0].NodeResolvConfPath
67+
68+
if config[0].Logger != nil {
69+
client.log = config[0].Logger
70+
}
71+
}
72+
73+
return client
4474
}
4575

4676
func (c dnsClient) Resolve(host string) ([]net.IP, error) {
@@ -115,32 +145,44 @@ func (c dnsClient) getResolversAndNames(host string, strategy string) (resolvers
115145
strategy = DNSResolverPodFallbackNode
116146
}
117147

148+
// Use configured paths from the client
149+
podPath := c.podResolvConfPath
150+
nodePath := c.nodeResolvConfPath
151+
118152
switch strategy {
119153
case DNSResolverPod:
120-
podDNSConfig, err := dns.ClientConfigFromFile("/etc/resolv.conf")
154+
podDNSConfig, err := dns.ClientConfigFromFile(podPath)
121155
if err != nil {
122-
return nil, nil, fmt.Errorf("can't read '/etc/resolv.conf' file: %w", err)
156+
return nil, nil, fmt.Errorf("can't read pod resolv.conf file: %w", err)
157+
}
158+
159+
if c.log != nil {
160+
c.log.Infow("loaded pod DNS configuration", "resolv_conf_path", podPath, "nameservers", podDNSConfig.Servers)
123161
}
124162

125163
resolvers = podDNSConfig.Servers
126164
names = podDNSConfig.NameList(host)
127165
case DNSResolverNode:
128-
nodeDNSConfig, err := dns.ClientConfigFromFile("/mnt/host/etc/resolv.conf")
166+
nodeDNSConfig, err := dns.ClientConfigFromFile(nodePath)
129167
if err != nil {
130-
return nil, nil, fmt.Errorf("can't read '/mnt/host/etc/resolv.conf' file: %w", err)
168+
return nil, nil, fmt.Errorf("can't read node resolv.conf file: %w", err)
169+
}
170+
171+
if c.log != nil {
172+
c.log.Infow("loaded node DNS configuration", "resolv_conf_path", nodePath, "nameservers", nodeDNSConfig.Servers)
131173
}
132174

133175
resolvers = nodeDNSConfig.Servers
134176
names = nodeDNSConfig.NameList(host)
135177
case DNSResolverPodFallbackNode, DNSResolverNodeFallbackPod:
136-
podDNSConfig, err := dns.ClientConfigFromFile("/etc/resolv.conf")
178+
podDNSConfig, err := dns.ClientConfigFromFile(podPath)
137179
if err != nil {
138-
return nil, nil, fmt.Errorf("can't read '/etc/resolv.conf' file: %w", err)
180+
return nil, nil, fmt.Errorf("can't read pod resolv.conf file: %w", err)
139181
}
140182

141-
nodeDNSConfig, err := dns.ClientConfigFromFile("/mnt/host/etc/resolv.conf")
183+
nodeDNSConfig, err := dns.ClientConfigFromFile(nodePath)
142184
if err != nil {
143-
return nil, nil, fmt.Errorf("can't read '/mnt/host/etc/resolv.conf' file: %w", err)
185+
return nil, nil, fmt.Errorf("can't read node resolv.conf file: %w", err)
144186
}
145187

146188
if strategy == DNSResolverPodFallbackNode {
@@ -149,12 +191,28 @@ func (c dnsClient) getResolversAndNames(host string, strategy string) (resolvers
149191
resolvers = append(resolvers, nodeDNSConfig.Servers...)
150192
names = append([]string{}, podDNSConfig.NameList(host)...)
151193
names = append(names, nodeDNSConfig.NameList(host)...)
194+
195+
if c.log != nil {
196+
c.log.Infow("loaded pod and node DNS configuration (pod-fallback-node strategy)",
197+
"pod_resolv_conf_path", podPath,
198+
"pod_nameservers", podDNSConfig.Servers,
199+
"node_resolv_conf_path", nodePath,
200+
"node_nameservers", nodeDNSConfig.Servers)
201+
}
152202
} else {
153203
// Node first, then pod
154204
resolvers = append([]string{}, nodeDNSConfig.Servers...)
155205
resolvers = append(resolvers, podDNSConfig.Servers...)
156206
names = append([]string{}, nodeDNSConfig.NameList(host)...)
157207
names = append(names, podDNSConfig.NameList(host)...)
208+
209+
if c.log != nil {
210+
c.log.Infow("loaded pod and node DNS configuration (node-fallback-pod strategy)",
211+
"node_resolv_conf_path", nodePath,
212+
"node_nameservers", nodeDNSConfig.Servers,
213+
"pod_resolv_conf_path", podPath,
214+
"pod_nameservers", podDNSConfig.Servers)
215+
}
158216
}
159217
default:
160218
return nil, nil, fmt.Errorf("unknown DNS resolver strategy: %s", strategy)
@@ -194,3 +252,15 @@ func (c dnsClient) resolve(hostName string, protocol string, resolvers []string)
194252

195253
return response, multiErr
196254
}
255+
256+
// Test helpers for unexported functions
257+
258+
// ReadResolvConfFileForTest is a test helper that exposes readResolvConfFile for unit testing
259+
func ReadResolvConfFileForTest(path string) (*dns.ClientConfig, error) {
260+
config, err := dns.ClientConfigFromFile(path)
261+
if err != nil {
262+
return nil, fmt.Errorf("failed to read resolv.conf from %s: %w", path, err)
263+
}
264+
265+
return config, nil
266+
}

0 commit comments

Comments
 (0)