kubeletの--protect-kernel-defaultsをtrueにする際に必要なkernel tuning

# はじめに

kubeletの`--protect-kernel-defaults`をいじっていたときにいろいろみたことのまとめです。

# 事の起こり

- kubeadmで構築したKubernetesクラスタを対象にKubernetes CIS Benchmarkの確認中、下記項目を見つける。
  - 4.2.6 Ensure that the --protect-kernel-defaults argument is set to true
- とりあえず`--protect-kernel-defaults`を`true`にして起動してみたが、kubelet自体がエラーで立ち上がらない。
- kernel tuningすればよさそうだが、ドキュメントにどれ直せばよいかがなかったのでソース読んで調べてみることに

# --protect-kernel-defaultsとは

https://github.com/kubernetes/kubernetes/pull/27874

動きは下記

- `false`
  - kubelet起動時にkernelパラメータの変更処理を行う。
- `true`
  - kubelet起動時にkernelパラメータのチェックを行い、想定と異なっていた際はエラーを出力して終了

「じゃあkubeletが想定するパラメータってなんじゃい」というのが調査対象です。

# 調査

## setupNode

とりあえず`ProtectKernelDefaults`で検索。  
それっぽい部分が見つかりました。

https://github.com/kubernetes/kubernetes/blob/release-1.19/pkg/kubelet/cm/container_manager_linux.go#L447

```go
func (cm *containerManagerImpl) setupNode(activePods ActivePodsFunc) error {
	f, err := validateSystemRequirements(cm.mountUtil)
	if err != nil {
		return err
	}
	if !f.cpuHardcapping {
		cm.status.SoftRequirements = fmt.Errorf("CPU hardcapping unsupported")
	}
	b := KernelTunableModify
	if cm.GetNodeConfig().ProtectKernelDefaults {
		b = KernelTunableError
	}
	if err := setupKernelTunables(b); err != nil {
		return err
	}
```

`setupKernelTunables`にもぐってみます。

## setupKernelTunables

https://github.com/kubernetes/kubernetes/blob/release-1.19/pkg/kubelet/cm/container_manager_linux.go#L408

```go
func setupKernelTunables(option KernelTunableBehavior) error {
	desiredState := map[string]int{
		utilsysctl.VMOvercommitMemory: utilsysctl.VMOvercommitMemoryAlways,
		utilsysctl.VMPanicOnOOM:       utilsysctl.VMPanicOnOOMInvokeOOMKiller,
		utilsysctl.KernelPanic:        utilsysctl.KernelPanicRebootTimeout,
		utilsysctl.KernelPanicOnOops:  utilsysctl.KernelPanicOnOopsAlways,
		utilsysctl.RootMaxKeys:        utilsysctl.RootMaxKeysSetting,
		utilsysctl.RootMaxBytes:       utilsysctl.RootMaxBytesSetting,
	}

	sysctl := utilsysctl.New()

	errList := []error{}
	for flag, expectedValue := range desiredState {
		val, err := sysctl.GetSysctl(flag)
		if err != nil {
			errList = append(errList, err)
			continue
		}
		if val == expectedValue {
			continue
		}

		switch option {
		case KernelTunableError:
			errList = append(errList, fmt.Errorf("invalid kernel flag: %v, expected value: %v, actual value: %v", flag, expectedValue, val))
		case KernelTunableWarn:
			klog.V(2).Infof("Invalid kernel flag: %v, expected value: %v, actual value: %v", flag, expectedValue, val)
		case KernelTunableModify:
			klog.V(2).Infof("Updating kernel flag: %v, expected value: %v, actual value: %v", flag, expectedValue, val)
			err = sysctl.SetSysctl(flag, expectedValue)
			if err != nil {
				errList = append(errList, err)
			}
		}
	}
	return utilerrors.NewAggregate(errList)
}
```

`desiredState`の項目について`sysctl.GetSysctl`で取得した値とを比較して同じであれば`continue`、異なっていたらエラーを返しているようです。  

## utilsysctl


https://github.com/kubernetes/kubernetes/blob/release-1.19/pkg/util/sysctl/sysctl.go#L26

ありました。このパラメータ通りにkernel tuningをしたうえで`--protect-kernel-defaults`を`true`にすれば良いでしょう。

```go
const (
	sysctlBase = "/proc/sys"
	// VMOvercommitMemory refers to the sysctl variable responsible for defining
	// the memory over-commit policy used by kernel.
	VMOvercommitMemory = "vm/overcommit_memory"
	// VMPanicOnOOM refers to the sysctl variable responsible for defining
	// the OOM behavior used by kernel.
	VMPanicOnOOM = "vm/panic_on_oom"
	// KernelPanic refers to the sysctl variable responsible for defining
	// the timeout after a panic for the kernel to reboot.
	KernelPanic = "kernel/panic"
	// KernelPanicOnOops refers to the sysctl variable responsible for defining
	// the kernel behavior when an oops or BUG is encountered.
	KernelPanicOnOops = "kernel/panic_on_oops"
	// RootMaxKeys refers to the sysctl variable responsible for defining
	// the maximum number of keys that the root user (UID 0 in the root user namespace) may own.
	RootMaxKeys = "kernel/keys/root_maxkeys"
	// RootMaxBytes refers to the sysctl variable responsible for defining
	// the maximum number of bytes of data that the root user (UID 0 in the root user namespace)
	// can hold in the payloads of the keys owned by root.
	RootMaxBytes = "kernel/keys/root_maxbytes"

	// VMOvercommitMemoryAlways represents that kernel performs no memory over-commit handling.
	VMOvercommitMemoryAlways = 1
	// VMPanicOnOOMInvokeOOMKiller represents that kernel calls the oom_killer function when OOM occurs.
	VMPanicOnOOMInvokeOOMKiller = 0

	// KernelPanicOnOopsAlways represents that kernel panics on kernel oops.
	KernelPanicOnOopsAlways = 1
	// KernelPanicRebootTimeout is the timeout seconds after a panic for the kernel to reboot.
	KernelPanicRebootTimeout = 10

	// RootMaxKeysSetting is the maximum number of keys that the root user (UID 0 in the root user namespace) may own.
	// Needed since docker creates a new key per container.
	RootMaxKeysSetting = 1000000
	// RootMaxBytesSetting is the maximum number of bytes of data that the root user (UID 0 in the root user namespace)
	// can hold in the payloads of the keys owned by root.
	// Allocate 25 bytes per key * number of MaxKeys.
	RootMaxBytesSetting = RootMaxKeysSetting * 25
)
```

| パラメータ | kubeletを動かす上で必要な値 |
| --- | --- |
|vm/overcommit_memory| 1 |
|vm/panic_on_oom| 0 |
|kernel/panic| 1 |
|kernel/panic_on_oops| 10 |
|kernel/keys/root_maxkeys| 1000000 |
|kernel/keys/root_maxbytes| root_max_keys * 25 |

# まとめ

マネージドK8sはこのあたりを気にする必要がないので良いですね。

# おまけ

https://github.com/kubernetes/kubernetes/issues/66241#issuecomment-460832038

```bash
cat > /etc/sysctl.d/90-kubelet.conf << EOF
vm.overcommit_memory=1
kernel.panic=10
kernel.panic_on_oops=1
EOF
sysctl -p /etc/sysctl.d/90-kubelet.conf
```

上記では`overcommit_memory`/`panic`/`panic_on_oops`を修正すればいいよとのことですが。
調べた結果と比べると`panic_on_oom`/`root_maxkeys`/`root_maxbytes`については言及がない様子。

気になったのでこれについても調べてみたところ、どうやらkubeletの要求値とデフォルトが一緒だから問題になっていなかったようです。

https://www.kernel.org/doc/Documentation/sysctl/vm.txt
https://www.man7.org/linux/man-pages/man7/keyrings.7.html