Podのカーネルパラメータを変更する

Podのカーネルパラメータ（sysctl）を変更する方法について調べたまとめ。ICP v3.1.0で確認。

大きくは以下の方法がある。

- PodのsecurityContextで設定
- Init Containerで設定

## カーネルパラメータについて

- [Using sysctls in a Kubernetes Cluster](https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/)

上記のドキュメントによると以下とのこと。

- `sysctl`には`namespaced`なものとそうでないものがある
  - Pod毎に`sysctl`を設定できるのは`namespaced`なもののみ
  - `namespaced sysctl`ではないものは`node-level sysctl`という
- `namespaced sysctl`には`safe`なものと`unsafe`なものがある
  - 大部分は必ずしも`safe`とは考えられず、`safe sysctl`は今のところ以下のみ
     - `kernel.shm_rmid_forced`
     - `net.ipv4.ip_local_port_range`
     - `net.ipv4.tcp_syncookies`（kernel version 4.4以下では`namespaced`ではない）

知られている`namespaced sysctl`のリストは以下だがLinux Kernelのバージョンアップにより変わる可能性がある。

- kernel.shm*,
- kernel.msg*,
- kernel.sem,
- fs.mqueue.*,
- net.*.

あるいはコンテナの中の`/proc/sys`以下にファイルがあるかでも`namespaced`かどうかは確認できるかもしれない。`net.core.somaxconn`は`namespaced`でありファイルがある。ホストOSでカーネルパラメータを変更してもPodのカーネルパラメータはそれとは別になる。

```shell-session
ubuntu@icp31-single:~/workspace/helm-study$ kubectl get po
NAME                            READY     STATUS      RESTARTS   AGE
test-liberty-7d8f7497b9-cs5mn   1/1       Running     0          1m
test-liberty-7d8f7497b9-ctlqs   1/1       Running     0          1m
test-mysql-cfc8c8c86-9rv7b      1/1       Running     2          9d
test-mysql-init-7hbzf           0/1       Completed   1          9d
ubuntu@icp31-single:~/workspace/helm-study$ cat /proc/sys/net/core/somaxconn
256
ubuntu@icp31-single:~/workspace/helm-study$ kubectl exec -it test-liberty-7d8f7497b9-cs5mn -- cat /proc/sys/net/core/somaxconn
128
ubuntu@icp31-single:~/workspace/helm-study$
```

以下では`net.core.somaxconn`を`512`に変更する方法を試す。

## PodのsecurityContextで設定

- [Using sysctls in a Kubernetes Cluster](https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/)

上記のドキュメントに記載されている方法で、以下のようにPodの`spec.securityContext.sysctls`で設定できる。ただし、IBM Cloud Private 3.1のようにPodSecurityPolicyのAdmissionControlが有効な場合、PodSecurityPolicyを適切に設定する必要がある。紛らわしいが、PodのsecurityContextとPodSecurityPolicyは別の設定であるので注意。

```yaml
    spec:
      securityContext:
        sysctls:
        - name: net.core.somaxconn
          value: "512"
```

### PodSecurityPolicyのAdmissionControlの有効化（Minikube）

Minikubeの場合はデフォルトの状態ではPodSecurityPolicyのAdmissionControlは設定されていないためPodSecurityPolicyの設定は不要。AdmissionControlはAPI Serverの設定で有効にすることができる。Minikubeのバージョンにより与えるべき引数が異なるかもしれないので注意。

```
minikube start --extra-config=apiserver.enable-admission-plugins=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota,PodSecurityPolicy
```

- `/etc/kubernetes/manifests/kube-apiserver.yaml`でデフォルトAPI Serverの`--enable-admission-plugins`の設定値を確認し、`PodSecurityPolicy`を追加

### PodSecurityPolicyの作成

ICP v3.1.0ではデフォルトで`default`と`privileged`の2つのPodSecurityPolicyがあり、それらを利用可能にしているClusterRoleがそれぞれあり、それらをバインディングしているClusterRoleBindingがある。

（補足）
ICP v3.1.1では`ibm-restricted-psp`や`ibm-restricted-psp`などのPodSecurityPolicyに変わっている。

```
ubuntu@icp31-single:~$ kubectl get psp
NAME         PRIV      CAPS      SELINUX    RUNASUSER   FSGROUP    SUPGROUP   READONLYROOTFS   VOLUMES
default      false               RunAsAny   RunAsAny    RunAsAny   RunAsAny   false            *
privileged   true      *         RunAsAny   RunAsAny    RunAsAny   RunAsAny   false            *
ubuntu@icp31-single:~$ kubectl get clusterrole default privileged
NAME         AGE
default      24d
privileged   24d
ubuntu@icp31-single:~$
ubuntu@icp31-single:~$ kubectl get clusterrolebinding | grep psp
default-psp-users                                                      24d
privileged-psp-users                                                   24d
ubuntu@icp31-single:~$
```

今回は`allowedUnsafeSysctls`の設定を追加したPodSecurityPolicyを新たに作成する。`default`のPodSecurityPolicyで設定されていた内容に追加する形で作成する。

```yaml:somaxconn-psp.yaml
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: somaxconn-psp
spec:
spec:
  allowPrivilegeEscalation: true
  fsGroup:
    rule: RunAsAny
  runAsUser:
    rule: RunAsAny
  seLinux:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  volumes:
  - '*'
  allowedUnsafeSysctls:
  - net.core.somaxconn
```

```
kubectl apply -f somaxconn-psp.yaml
```

### ClusterRoleの作成

作成したPodSecurityPolicyを利用可能なClusterRoleを作成する。

```yaml:somaxconn-clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: somaxconn-clusterrole
rules:
- apiGroups:
  - extensions
  resourceNames:
  - somaxconn-psp
  resources:
  - podsecuritypolicies
  verbs:
  - use
```

```
kubectl apply -f somaxconn-clusterrole.yaml
```

### ClusterRoleBindingの作成

ClusterRoleをバインドする先のServiceAccountを作成する。今回は`sugi`というNamespaceに`psp-user`というServiceAccountを作成する。

```
kubectl create sa psp-user -n sugi
```

ClusterRoleを作成したServiceAccountにバインドするClusterRoleBindingを作成する。

```yaml:somaxconn-clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: somaxconn-clusterrolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: somaxconn-clusterrole
subjects:
- kind: ServiceAccount
  name: psp-user
  namespace: sugi
```

```
kubectl apply -f somaxconn-clusterrolebinding.yaml
```

もしNamespaceの全てのServiceAccountにバインドする場合は以下のようにする。

```yaml
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: system:serviceaccounts:sugi
```

その他、バインドの設定例は以下のドキュメントを参照。

- [Role Binding Examples](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-binding-examples)

### デプロイテスト

ここまでの設定で一応Podがスケジュールされるようになる。PodSecurityPolicyの設定が適切でない場合は、Podが`Pending`等になるわけではなく、そもそそもスケジュールされないことに注意。

PodのSpecで`serviceAccountName`を指定した以下のマニフェストを適用する。

```yaml:liberty.yam
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: liberty
  name: liberty
spec:
  replicas: 1
  selector:
    matchLabels:
      app: liberty
  template:
    metadata:
      labels:
        app: liberty
    spec:
      serviceAccountName: psp-user
      securityContext:
        sysctls:
        - name: net.core.somaxconn
          value: "512"
      containers:
      - name: websphere-liberty
        image: websphere-liberty
```

```
kubectl apply -f liberty.yaml -n sugi
```

しかしこれを適用すると以下のように`SysctlForbidden`となってしまう。Kubeletがsysctlを許可していないため。

```
ubuntu@icp31-single:~$ kubectl get po
NAME                            READY     STATUS            RESTARTS   AGE
liberty-655cdb8456-66llz        0/1       SysctlForbidden   0          5s
liberty-655cdb8456-8frdd        0/1       SysctlForbidden   0          2s
liberty-655cdb8456-cszcm        0/1       SysctlForbidden   0          4s
liberty-655cdb8456-d9c2n        0/1       SysctlForbidden   0          5s
liberty-655cdb8456-h4d6t        0/1       SysctlForbidden   0          5s
liberty-655cdb8456-hbwn6        0/1       SysctlForbidden   0          2s
liberty-655cdb8456-hgzjj        0/1       Pending           0          1s
liberty-655cdb8456-k7262        0/1       SysctlForbidden   0          5s
liberty-655cdb8456-knfxs        0/1       SysctlForbidden   0          3s
liberty-655cdb8456-mmmgj        0/1       SysctlForbidden   0          3s
liberty-655cdb8456-qgzpc        0/1       SysctlForbidden   0          4s
liberty-655cdb8456-t527w        0/1       SysctlForbidden   0          4s
liberty-655cdb8456-tnq6x        0/1       SysctlForbidden   0          5s
liberty-655cdb8456-vtbxt        0/1       SysctlForbidden   0          2s
ubuntu@icp31-single:~$
```

### Kubeletの設定変更

securityContextで`unsafe sysctls`を設定したPodを起動するには各ノードのKubeletの設定で許可をする必要がある。Kubeletの起動オプションを変更する。

ICPの場合は各ノードのKubeletのSystemdユニットファイル`/etc/systemd/system/kubelet.service`に`--allowed-unsafe-sysctls=net.core.somaxconn`を追加し、デーモンを再起動する。

```:/etc/systemd/system/kubelet.service
[Unit]
Description=Kubelet Service
Documentation=https://github.com/kubernetes/kubernetes

[Service]
EnvironmentFile=-/etc/environment
ExecStart=/opt/kubernetes/hyperkube kubelet \
  --cadvisor-port=0 \
  --docker-disable-shared-pid \
  --bootstrap-kubeconfig=/etc/cfc/kubelet/kubelet-bootstrap-config \
  --kubeconfig=/etc/cfc/kubelet/kubelet-config \
  --cert-dir=/etc/cfc/kubelet \
  --config=/etc/cfc/kubelet/kubelet-service-config \
  --dynamic-config-dir=/etc/cfc/kubelet/kubelet-dynamic-config \
  --network-plugin=cni \
  --hostname-override=10.0.0.142 \
  --node-ip=10.0.0.142 \
  --pod-infra-container-image=ibmcom/pause:3.1 \
  --node-labels=role=master,master=true,node-role.kubernetes.io/master=true,proxy=true,node-role.kubernetes.io/proxy=true,node-role.kubernetes.io/worker=true,etcd=true,node-role.kubernetes.io/etcd=true,management=true,node-role.kubernetes.io/management=true, \
  --register-with-taints= \
  --keep-terminated-pod-volumes=false \
  --allowed-unsafe-sysctls=net.core.somaxconn


Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
```

```
sudo systemctl daemon-reload
sudo systemctl stop kubelet
sudo systemctl start kubelet
```

### 稼働確認

これでデプロイすればPodがちゃんと稼働して、カーネルパラメータが変更されていることが確認できる。

```
kubectl apply -f liberty.yaml
```

```shell-session
$ kubectl exec -it liberty-655cdb8456-gbg9d -- cat /proc/sys/net/core/somaxconn
512
$
```

## Init Containerで設定

以下のようにICPのNginx Ingress Controllerのマニフェストを確認すると、Init Containerを使ってパラメータを変更している。Pod内でカーネルパラメータは共通であり、Init Containerでの変更が実際のContainerでも引き継がれるようだ。

```shell-session
$ kubectl get ds -n kube-system nginx-ingress-controller -o yaml --export
apiVersion: extensions/v1beta1
kind: DaemonSet
（省略）
      initContainers:
      - command:
        - sh
        - -c
        - sysctl -w net.core.somaxconn=32768; sysctl -w net.ipv4.ip_local_port_range="32768
          65535"
        image: ibmcom/defaultbackend:1.2.1
        imagePullPolicy: IfNotPresent
        name: sysctl
        resources: {}
        securityContext:
          privileged: true
（省略）
```

この方法の場合、Kubeletの設定変更は不要だが、Init Containerで`sysctl`コマンドを実行するため`privileged: true`を設定しているため、（PodSecurityPolicyが有効な環境では）PodSecurityPolicyの設定は必要。`privileged: true`なのはInit Containerだけで、実際のコンテナは普通の権限で動作する。

### PodSecurityPolicy

今回はICP v3.1.0のデフォルトで存在する`privileged`というPodSecurityPolicyが利用可能な`privileged`というClusterRoleを、`sugi`というNamespaceの`default`というServiceAccount（特に指定しない場合にPodに設定されるServiceAccount）にバインドする。

```yaml:privileged-clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: privileged-clusterrolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: privileged
subjects:
- kind: ServiceAccount
  name: default
  namespace: sugi
```

```
kubectl apply -f privileged-clusterrolebinding.yaml
```

（補足）
ClusterRoleをバインドする先がNamespace単位のリソースであるServiceAccountなので、ClusterRoleBindingではなく以下のようなRoleBindingを作成してもよい。

```yaml:privileged-rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: privileged-rolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: privileged
subjects:
- kind: ServiceAccount
  name: default
```

### 稼働確認

libertyをデプロイする。

```yaml:liberty.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: liberty
  name: liberty
spec:
  replicas: 1
  selector:
    matchLabels:
      app: liberty
  template:
    metadata:
      labels:
        app: liberty
    spec:
      initContainers:
      - name: init-sysctl
        image: busybox
        securityContext:
          privileged: true
        command:
        - sh
        - -c
        - |
          sysctl -w net.core.somaxconn=512
      containers:
      - name: websphere-liberty
        image: websphere-liberty
```

```
kubectl apply -f liberty.yaml -n sugi
```

確認する。ちゃんと変更されている。

```shell-session
ubuntu@icp31-single:~/workspace/sysctl$ kubectl exec -it liberty-57c69978fd-4mf8b -- cat /proc/sys/net/core/somaxconn
512
ubuntu@icp31-single:~/workspace/sysctl$
```

## 参考リンク

- [Kernel tuning in Kubernetes](http://bogdan-albei.blogspot.com/2017/09/kernel-tuning-in-kubernetes.html)