Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion cmd/kubesolo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type kubesolo struct {
localStorage bool
localStorageSharedPath string
fullMode bool
disableIPv6 bool
embedded types.Embedded
}

Expand Down Expand Up @@ -76,6 +77,7 @@ func service() (*kubesolo, error) {
localStorage: *flags.LocalStorage,
localStorageSharedPath: *flags.LocalStorageSharedPath,
fullMode: *flags.Full,
disableIPv6: *flags.DisableIPv6,
}, nil
}

Expand Down Expand Up @@ -238,7 +240,7 @@ func (s *kubesolo) run() {
}

log.Info().Str("component", "kubesolo").Msg("deploying coredns...")
if err := coredns.Deploy(s.embedded.AdminKubeconfigFile); err != nil {
if err := coredns.Deploy(s.embedded.AdminKubeconfigFile, s.embedded.DisableIPv6); err != nil {
log.Fatal().Err(err).Msg("failed to deploy coredns")
}

Expand Down Expand Up @@ -351,6 +353,12 @@ func (s *kubesolo) bootstrap() {
// Load required kernel modules before any networking setup
system.LoadRequiredModules()

if s.disableIPv6 {
if err := network.DisableIPv6Sysctls(); err != nil {
log.Warn().Err(err).Msg("failed to disable ipv6 sysctls")
}
}

// System Node IP
nodeIP, err := network.GetNodeIP()
if err != nil {
Expand Down Expand Up @@ -491,5 +499,8 @@ func (s *kubesolo) bootstrap() {

// Full mode
FullMode: s.fullMode,

// IPv6
DisableIPv6: s.disableIPv6,
}
}
1 change: 1 addition & 0 deletions internal/config/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ var (
Debug = Application.Flag("debug", "Enable debug logging. Defaults to false.").Envar("KUBESOLO_DEBUG").Default("false").Bool()
PprofServer = Application.Flag("pprof-server", "Enable pprof server. Defaults to false.").Envar("KUBESOLO_PPROF_SERVER").Default("false").Bool()
Full = Application.Flag("full", "Disable memory-saving overrides and use upstream Kubernetes defaults. Kubesolo still uses NodeSetter in favour of the scheduler. Recommended for CI and developer environments where memory is not constrained. Leave unset for edge deployments.").Envar("KUBESOLO_FULL").Default("false").Bool()
DisableIPv6 = Application.Flag("disable-ipv6", "Disable IPv6 support. When set, CoreDNS will not serve ip6.arpa reverse zones and kubelet will register with an explicit IPv4 node address. Defaults to false.").Envar("KUBESOLO_DISABLE_IPV6").Default("false").Bool()
)
35 changes: 35 additions & 0 deletions internal/runtime/network/ipv6.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package network

import (
"errors"
"fmt"
"os"

"github.com/rs/zerolog/log"
)

var ipv6SysctlPaths = []string{
"/proc/sys/net/ipv6/conf/all/disable_ipv6",
"/proc/sys/net/ipv6/conf/default/disable_ipv6",
"/proc/sys/net/ipv6/conf/lo/disable_ipv6",
}

Comment thread
stevensbkang marked this conversation as resolved.
// DisableIPv6Sysctls writes 1 to the kernel sysctl paths that disable IPv6 on
// all interfaces. It is idempotent — paths already set to 1 are skipped. All
// paths are attempted regardless of failure; errors are aggregated and returned.
func DisableIPv6Sysctls() error {
var errs []error
for _, path := range ipv6SysctlPaths {
current, err := os.ReadFile(path)
if err == nil && len(current) > 0 && current[0] == '1' {
log.Debug().Str("component", "network").Msgf("ipv6 already disabled: %s", path)
continue
}
if err := os.WriteFile(path, []byte("1"), 0644); err != nil {
errs = append(errs, fmt.Errorf("%s: %w", path, err))
continue
}
log.Info().Str("component", "network").Msgf("disabled ipv6: %s", path)
}
Comment thread
stevensbkang marked this conversation as resolved.
return errors.Join(errs...)
}
61 changes: 46 additions & 15 deletions pkg/components/coredns/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,34 @@ package coredns

import (
"context"
"encoding/json"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8stypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
)

// CoreDNSConfig contains minimal CoreDNS Corefile configuration
const CoreDNSConfig = `.:53 {
const coreDNSConfigIPv4Only = `.:53 {
errors
loop
cache 30 {
disable denial cluster.local
}
kubernetes cluster.local in-addr.arpa {
pods insecure
fallthrough in-addr.arpa
ttl 30
}
forward . /etc/resolv.conf
minimal
reload
health :8080
ready :8181
}`

const coreDNSConfigDualStack = `.:53 {
errors
loop
cache 30 {
Expand All @@ -28,31 +47,43 @@ const CoreDNSConfig = `.:53 {
ready :8181
}`

// createConfigMap creates a configMap with the bare minimum CoreDNS configuration
// it creates a new configmap if it does not exist
// it updates the configmap if it already exists
// it returns an error if it fails
func createConfigMap(ctx context.Context, clientset *kubernetes.Clientset) error {
func coreDNSConfig(disableIPv6 bool) string {
if disableIPv6 {
return coreDNSConfigIPv4Only
}
return coreDNSConfigDualStack
}

// createConfigMap creates or patches the CoreDNS ConfigMap with the Corefile
// for the selected IP family mode. On update it uses a merge patch so existing
// metadata (labels, annotations) and unrelated data keys are preserved.
func createConfigMap(ctx context.Context, clientset *kubernetes.Clientset, disableIPv6 bool) error {
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: coreDNSConfigMapName,
Namespace: coreDNSNamespace,
},
Data: map[string]string{
"Corefile": CoreDNSConfig,
"Corefile": coreDNSConfig(disableIPv6),
},
}

Comment thread
stevensbkang marked this conversation as resolved.
_, err := clientset.CoreV1().ConfigMaps(coreDNSNamespace).Create(ctx, configMap, metav1.CreateOptions{})
if err != nil && !errors.IsAlreadyExists(err) {
if err == nil {
return nil
}
if !errors.IsAlreadyExists(err) {
return err
}

if errors.IsAlreadyExists(err) {
_, err = clientset.CoreV1().ConfigMaps(coreDNSNamespace).Update(ctx, configMap, metav1.UpdateOptions{})
if err != nil {
return err
}
patch, err := json.Marshal(map[string]any{
"data": map[string]string{"Corefile": coreDNSConfig(disableIPv6)},
})
if err != nil {
return err
}
return nil
_, err = clientset.CoreV1().ConfigMaps(coreDNSNamespace).Patch(
ctx, coreDNSConfigMapName, k8stypes.MergePatchType, patch, metav1.PatchOptions{},
)
return err
}
4 changes: 2 additions & 2 deletions pkg/components/coredns/coredns.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const (
)

// Deploy deploys all the necessary Kubernetes resources for CoreDNS
func Deploy(adminKubeconfig string) error {
func Deploy(adminKubeconfig string, disableIPv6 bool) error {
time.Sleep(types.DefaultComponentSleep)

ctx, cancel := context.WithTimeout(context.Background(), types.DefaultContextTimeout)
Expand All @@ -33,7 +33,7 @@ func Deploy(adminKubeconfig string) error {
return fmt.Errorf("failed to create kubernetes client: %v", err)
}

if err := createConfigMap(ctx, clientset); err != nil {
if err := createConfigMap(ctx, clientset, disableIPv6); err != nil {
return fmt.Errorf("failed to create CoreDNS ConfigMap: %v", err)
}

Expand Down
8 changes: 6 additions & 2 deletions pkg/kubernetes/kubelet/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ import (
)

func (s *service) configureKubeletArgs(command *cobra.Command) {
command.SetArgs([]string{
args := []string{
"--config", s.kubeletConfigFile,
"--hostname-override", s.nodeName,
"--root-dir", s.kubeletDir,
"--kubeconfig", s.kubeletKubeConfigFile,
})
}
if s.disableIPv6 {
args = append(args, "--node-ip", s.nodeIP)
}
command.SetArgs(args)
}
2 changes: 2 additions & 0 deletions pkg/kubernetes/kubelet/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type service struct {
kubeletCertPath string
adminKubeconfig string
fullMode bool
disableIPv6 bool
}

// NewService creates a new kubelet service
Expand All @@ -51,5 +52,6 @@ func NewService(ctx context.Context, cancel context.CancelFunc, kubeletReady cha
nodeName: system.GetHostname(),
adminKubeconfig: embedded.AdminKubeconfigFile,
fullMode: embedded.FullMode,
disableIPv6: embedded.DisableIPv6,
}
}
3 changes: 3 additions & 0 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ type Embedded struct {

// Full mode — disables memory-saving overrides, uses upstream Kubernetes defaults
FullMode bool

// IPv6
DisableIPv6 bool
}

// EdgeAgentConfig contains configuration for Portainer Edge Agent
Expand Down