88 "os"
99 "os/exec"
1010 "path/filepath"
11+ "strconv"
1112 "strings"
1213
1314 "github.com/replicatedhq/embedded-cluster/pkg/helpers"
@@ -19,17 +20,45 @@ import (
1920// purposes.
2021var sysctlConfigPath = "/etc/sysctl.d/99-embedded-cluster.conf"
2122
22- var modulesLoadConfigPath = "/etc/modules-load.d/99-embedded-cluster.conf"
23+ // dynamicSysctlConfigPath is the path to the dynamic sysctl config file that is used to configure
24+ // the embedded cluster.
25+ const dynamicSysctlConfigPath = "/etc/sysctl.d/99-dynamic-embedded-cluster.conf"
26+
27+ // modulesLoadConfigPath is the path to the kernel modules config file that is used to configure
28+ // the embedded cluster.
29+ const modulesLoadConfigPath = "/etc/modules-load.d/99-embedded-cluster.conf"
2330
2431//go:embed static/sysctl.d/99-embedded-cluster.conf
2532var embeddedClusterSysctlConf []byte
2633
2734//go:embed static/modules-load.d/99-embedded-cluster.conf
2835var embeddedClusterModulesConf []byte
2936
30- // ConfigureSysctl writes the sysctl config file for the embedded cluster and reloads the sysctl
31- // configuration. This function has a distinct behavior: if the sysctl binary does not exist it
32- // returns an error but if it fails to lay down the sysctl config on disk it simply returns nil.
37+ // dynamicSysctlConstraints are the constraints that are used to generate the dynamic sysctl
38+ // config file.
39+ var dynamicSysctlConstraints = []sysctlConstraint {
40+ // Increase inotify limits only if they are currently lower,
41+ // ensuring proper operation of applications that monitor filesystem events.
42+ {key : "fs.inotify.max_user_instances" , value : 1024 , operator : sysctlOperatorMin },
43+ {key : "fs.inotify.max_user_watches" , value : 65536 , operator : sysctlOperatorMin },
44+ }
45+
46+ type sysctlOperator string
47+
48+ const (
49+ sysctlOperatorMin sysctlOperator = "min"
50+ sysctlOperatorMax sysctlOperator = "max"
51+ )
52+
53+ type sysctlConstraint struct {
54+ key string
55+ value int64
56+ operator sysctlOperator
57+ }
58+
59+ type sysctlValueGetter func (key string ) (int64 , error )
60+
61+ // ConfigureSysctl writes the sysctl config files for the embedded cluster and reloads the sysctl configuration.
3362// NOTE: do not run this after the cluster has already been installed as it may revert sysctl
3463// settings set by k0s and its extensions.
3564func ConfigureSysctl () error {
@@ -41,6 +70,10 @@ func ConfigureSysctl() error {
4170 return fmt .Errorf ("materialize sysctl config: %w" , err )
4271 }
4372
73+ if err := dynamicSysctlConfig (); err != nil {
74+ return fmt .Errorf ("materialize dynamic sysctl config: %w" , err )
75+ }
76+
4477 if _ , err := helpers .RunCommand ("sysctl" , "--system" ); err != nil {
4578 return fmt .Errorf ("configure sysctl: %w" , err )
4679 }
@@ -58,6 +91,63 @@ func sysctlConfig() error {
5891 return nil
5992}
6093
94+ // dynamicSysctlConfig generates a dynamic sysctl config file based on current system values
95+ // and our constraints.
96+ func dynamicSysctlConfig () error {
97+ return generateDynamicSysctlConfig (getCurrentSysctlValue , dynamicSysctlConfigPath )
98+ }
99+
100+ // generateDynamicSysctlConfig is the testable version of dynamicSysctlConfig that accepts
101+ // a custom sysctl value getter and config path.
102+ func generateDynamicSysctlConfig (getter sysctlValueGetter , configPath string ) error {
103+ if err := os .MkdirAll (filepath .Dir (configPath ), 0755 ); err != nil {
104+ return fmt .Errorf ("create directory: %w" , err )
105+ }
106+
107+ var config strings.Builder
108+ config .WriteString ("# Dynamic sysctl configuration for embedded-cluster\n " )
109+ config .WriteString ("# This file is generated based on system values\n \n " )
110+
111+ for _ , constraint := range dynamicSysctlConstraints {
112+ currentValue , err := getter (constraint .key )
113+ if err != nil {
114+ return fmt .Errorf ("check current value for %s: %w" , constraint .key , err )
115+ }
116+
117+ needsUpdate := false
118+ switch constraint .operator {
119+ case sysctlOperatorMin :
120+ needsUpdate = currentValue < constraint .value
121+ case sysctlOperatorMax :
122+ needsUpdate = currentValue > constraint .value
123+ }
124+
125+ if needsUpdate {
126+ config .WriteString (fmt .Sprintf ("%s = %d\n " , constraint .key , constraint .value ))
127+ }
128+ }
129+
130+ if err := os .WriteFile (configPath , []byte (config .String ()), 0644 ); err != nil {
131+ return fmt .Errorf ("write dynamic config file: %w" , err )
132+ }
133+ return nil
134+ }
135+
136+ // getCurrentSysctlValue reads the current value of a sysctl parameter
137+ func getCurrentSysctlValue (key string ) (int64 , error ) {
138+ out , err := helpers .RunCommand ("sysctl" , "-n" , key )
139+ if err != nil {
140+ return 0 , fmt .Errorf ("get sysctl value: %w" , err )
141+ }
142+
143+ value , err := strconv .ParseInt (strings .TrimSpace (string (out )), 10 , 64 )
144+ if err != nil {
145+ return 0 , fmt .Errorf ("parse sysctl value: %w" , err )
146+ }
147+
148+ return value , nil
149+ }
150+
61151// ConfigureKernelModules writes the kernel modules config file and ensures the kernel modules are
62152// loaded that are listed in the file.
63153func ConfigureKernelModules () error {
0 commit comments