Skip to content

Commit 1a19143

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-1360
1 parent 7b43367 commit 1a19143

File tree

13 files changed

+292
-95
lines changed

13 files changed

+292
-95
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" # (optional) path for pod DNS resolv.conf
136+
# dnsNodeResolvConf: "/mnt/host/etc/resolv.conf" # (optional) 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:
@@ -322,6 +324,81 @@ network:
322324
- **`pod-fallback-node`** (default): Use when you want resilience - try pod DNS first but fall back to node DNS if it fails
323325
- **`node-fallback-pod`**: Use when node DNS is preferred but you want pod DNS as a backup
324326

327+
#### Customizing resolv.conf Paths
328+
329+
By default, the chaos-controller uses these locations for resolv.conf files:
330+
331+
- **Pod DNS**: `/etc/resolv.conf`
332+
- **Node DNS**: `/mnt/host/etc/resolv.conf`
333+
334+
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:
335+
336+
**Helm Configuration:**
337+
338+
Override resolv.conf paths in your Helm values:
339+
340+
```yaml
341+
injector:
342+
networkDisruption:
343+
# Path for pod DNS resolv.conf
344+
# Default: /etc/resolv.conf
345+
dnsPodResolvConf: "/run/systemd/resolve/resolv.conf"
346+
347+
# Path for node DNS resolv.conf
348+
# Default: /mnt/host/etc/resolv.conf
349+
dnsNodeResolvConf: "/mnt/host/run/systemd/resolve/stub-resolv.conf"
350+
```
351+
352+
**Behavior:**
353+
- Defaults are defined in the controller configuration (can be overridden via ConfigMap, environment variables, or CLI flags)
354+
- The specified resolv.conf file must exist and be readable
355+
- Configuration is passed to injector pods as command-line arguments
356+
- Logging will indicate which resolv.conf file was loaded
357+
358+
**Example: Full Helm configuration for systemd-resolved nodes**
359+
360+
```yaml
361+
# values.yaml
362+
injector:
363+
networkDisruption:
364+
hostResolveInterval: 1m
365+
366+
# For nodes using systemd-resolved (Ubuntu, Debian, etc.)
367+
dnsNodeResolvConf: "/mnt/host/run/systemd/resolve/stub-resolv.conf"
368+
369+
# For pods with custom DNS configuration
370+
dnsPodResolvConf: "/run/systemd/resolve/resolv.conf"
371+
```
372+
373+
**How it works:**
374+
375+
When you deploy with these Helm values:
376+
1. The controller reads the configuration from the ConfigMap
377+
2. For each network disruption, the controller creates an injector pod with CLI arguments:
378+
```bash
379+
/chaos-injector network-disruption \
380+
--dns-pod-resolv-conf /run/systemd/resolve/resolv.conf \
381+
--dns-node-resolv-conf /mnt/host/run/systemd/resolve/stub-resolv.conf \
382+
...
383+
```
384+
3. The injector uses these paths for DNS resolution
385+
4. Logs will show which resolv.conf files were loaded:
386+
```
387+
INFO loaded pod DNS configuration resolv_conf_path=/run/systemd/resolve/resolv.conf nameservers=[8.8.8.8, 8.8.4.4]
388+
INFO loaded node DNS configuration resolv_conf_path=/mnt/host/run/systemd/resolve/stub-resolv.conf nameservers=[10.0.0.1]
389+
```
390+
391+
You can verify the paths used by an injector pod:
392+
```bash
393+
kubectl describe pod chaos-injector-xxxxx -n chaos-engineering | grep dns-
394+
```
395+
396+
**Use cases:**
397+
- **systemd-resolved**: Nodes using systemd-resolved may have resolv.conf at `/run/systemd/resolve/resolv.conf` or `/run/systemd/resolve/stub-resolv.conf`
398+
- **NetworkManager**: Some distributions use `/run/NetworkManager/resolv.conf`
399+
- **Custom Kubernetes distributions**: Distributions like k3s, microk8s, or OpenShift may use non-standard paths
400+
- **Custom DNS configurations**: Environments with custom DNS setups that require specific resolv.conf locations
401+
325402
### Some special cases
326403

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

injector/ipresolver.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,8 @@ func resolveHost(client network.DNSClient, host string, dnsStrategy string) ([]*
3535
// if no IP has been parsed, fallback on a hostname
3636
// and try to resolve it by using the container resolv.conf file
3737
var resolvedIPs []net.IP
38-
if dnsStrategy != "" {
39-
resolvedIPs, err = client.ResolveWithStrategy(host, dnsStrategy)
40-
} else {
41-
resolvedIPs, err = client.Resolve(host)
42-
}
38+
39+
resolvedIPs, err = client.ResolveWithStrategy(host, dnsStrategy)
4340

4441
if err != nil {
4542
return nil, fmt.Errorf("can't resolve the given host with the configured dns resolver: %w", err)

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 {

injector/network_disruption_test.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ import (
1414
"strings"
1515
"time"
1616

17+
"github.com/stretchr/testify/mock"
18+
corev1 "k8s.io/api/core/v1"
19+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20+
"k8s.io/apimachinery/pkg/util/intstr"
21+
"k8s.io/apimachinery/pkg/watch"
22+
kubernetes "k8s.io/client-go/kubernetes/fake"
23+
"k8s.io/client-go/testing"
24+
1725
"github.com/DataDog/chaos-controller/api"
1826
"github.com/DataDog/chaos-controller/api/v1beta1"
1927
"github.com/DataDog/chaos-controller/cgroup"
@@ -24,15 +32,9 @@ import (
2432
"github.com/DataDog/chaos-controller/netns"
2533
"github.com/DataDog/chaos-controller/network"
2634
chaostypes "github.com/DataDog/chaos-controller/types"
35+
2736
. "github.com/onsi/ginkgo/v2"
2837
. "github.com/onsi/gomega"
29-
"github.com/stretchr/testify/mock"
30-
corev1 "k8s.io/api/core/v1"
31-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32-
"k8s.io/apimachinery/pkg/util/intstr"
33-
"k8s.io/apimachinery/pkg/watch"
34-
kubernetes "k8s.io/client-go/kubernetes/fake"
35-
"k8s.io/client-go/testing"
3638
)
3739

3840
const (
@@ -143,7 +145,7 @@ var _ = Describe("Failure", func() {
143145

144146
// dns
145147
dns = network.NewDNSClientMock(GinkgoT())
146-
dns.EXPECT().Resolve("kubernetes.default").Return([]net.IP{net.ParseIP("192.168.0.254")}, nil).Maybe()
148+
dns.EXPECT().ResolveWithStrategy("kubernetes", "default").Return([]net.IP{net.ParseIP("192.168.0.254")}, nil).Maybe()
147149

148150
// container
149151
ctn = container.NewContainerMock(GinkgoT())
@@ -398,7 +400,7 @@ var _ = Describe("Failure", func() {
398400
},
399401
}
400402

401-
dns.EXPECT().Resolve("testhost").Return([]net.IP{net.ParseIP(testHostIP), net.ParseIP(testHostIPTwo)}, nil).Once()
403+
dns.EXPECT().ResolveWithStrategy("testhost", "").Return([]net.IP{net.ParseIP(testHostIP), net.ParseIP(testHostIPTwo)}, nil).Once()
402404
})
403405

404406
It("should not raise an error", func() {
@@ -409,7 +411,7 @@ var _ = Describe("Failure", func() {
409411
tc.AssertCalled(GinkgoT(), "AddFilter", []string{"lo", "eth0", "eth1"}, "1:0", "", nilIPNet, buildSingleIPNet(testHostIP), 0, 80, network.TCP, network.ConnStateUndefined, "1:4")
410412
tc.AssertCalled(GinkgoT(), "AddFilter", []string{"lo", "eth0", "eth1"}, "1:0", "", nilIPNet, buildSingleIPNet(testHostIPTwo), 0, 80, network.TCP, network.ConnStateUndefined, "1:4")
411413

412-
dns.EXPECT().Resolve("testhost").Return([]net.IP{net.ParseIP(testHostIPTwo), net.ParseIP(testHostIPThree)}, nil).Maybe()
414+
dns.EXPECT().ResolveWithStrategy("testhost", "").Return([]net.IP{net.ParseIP(testHostIPTwo), net.ParseIP(testHostIPThree)}, nil).Maybe()
413415
time.Sleep(time.Second) // Wait for changed IPs to be caught by the hostWatcher
414416

415417
tc.AssertCalled(GinkgoT(), "DeleteFilter", "lo", uint32(0))
@@ -422,7 +424,7 @@ var _ = Describe("Failure", func() {
422424
tc.AssertCalled(GinkgoT(), "AddFilter", []string{"lo", "eth0", "eth1"}, "1:0", "", nilIPNet, buildSingleIPNet(testHostIP), 0, 80, network.TCP, network.ConnStateUndefined, "1:4")
423425
tc.AssertCalled(GinkgoT(), "AddFilter", []string{"lo", "eth0", "eth1"}, "1:0", "", nilIPNet, buildSingleIPNet(testHostIPTwo), 0, 80, network.TCP, network.ConnStateUndefined, "1:4")
424426

425-
dns.EXPECT().Resolve("testhost").Return([]net.IP{net.ParseIP(testHostIP), net.ParseIP(testHostIPTwo), net.ParseIP(testHostIPThree)}, nil).Maybe()
427+
dns.EXPECT().ResolveWithStrategy("testhost", "").Return([]net.IP{net.ParseIP(testHostIP), net.ParseIP(testHostIPTwo), net.ParseIP(testHostIPThree)}, nil).Maybe()
426428
time.Sleep(time.Second) // Wait for changed IPs to be caught by the hostWatcher
427429

428430
tc.AssertNotCalled(GinkgoT(), "DeleteFilter")
@@ -433,7 +435,7 @@ var _ = Describe("Failure", func() {
433435
tc.AssertCalled(GinkgoT(), "AddFilter", []string{"lo", "eth0", "eth1"}, "1:0", "", nilIPNet, buildSingleIPNet(testHostIP), 0, 80, network.TCP, network.ConnStateUndefined, "1:4")
434436
tc.AssertCalled(GinkgoT(), "AddFilter", []string{"lo", "eth0", "eth1"}, "1:0", "", nilIPNet, buildSingleIPNet(testHostIPTwo), 0, 80, network.TCP, network.ConnStateUndefined, "1:4")
435437

436-
dns.EXPECT().Resolve("testhost").Return([]net.IP{net.ParseIP(testHostIP)}, nil).Maybe()
438+
dns.EXPECT().ResolveWithStrategy("testhost", "").Return([]net.IP{net.ParseIP(testHostIP)}, nil).Maybe()
437439
time.Sleep(time.Second) // Wait for changed IPs to be caught by the hostWatcher
438440

439441
tc.AssertCalled(GinkgoT(), "DeleteFilter", "lo", uint32(0))

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
},

0 commit comments

Comments
 (0)