inductor's blog

nothing but self note :)

kubeadmで作ったKubernetesクラスターでcontainerd + gVisorのRuntimeClassを動かすぞ2021年エディション~~~~

はじめに

このエントリーは俺得メモなのでなんですかこれって思った人は読まなくて大丈夫です。

Kubernetesのコンテナランタイムを自由に選べるようにする「RuntimeClass」をもっと色んな人に使ってもらうべくこの記事を書いています(突然の矛盾)。

今回の記事のゴールは以下のとおりです

  • kubeadmを使ってKubernetesクラスターを作る(執筆時点では1.21)
  • containerdをメインのCRIランタイムとして設定し、OCIランタイムにはruncとrunsc(gVisor)を入れて自由に選択できるようにする
    • そのためのRuntimeClass

というわけで早速やっていきましょう。

前提準備

  • まっさらなUbuntu 20.04のマシン

containerdのインストール

cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# Setup required sysctl params, these persist across reboots.
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables  = 1
net.ipv4.ip_forward                 = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF

# Apply sysctl params without reboot
sudo sysctl --system

# (Install Docker CE)
## Set up the repository:
### Install packages to allow apt to use a repository over HTTPS
apt-get update && apt-get install -y \
  apt-transport-https ca-certificates curl software-properties-common gnupg2

# Add Docker's official GPG key:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key --keyring /etc/apt/trusted.gpg.d/docker.gpg add -

# Add the Docker apt repository:
add-apt-repository \
  "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) \
  stable"

## Install containerd
sudo apt-get update && sudo apt-get install -y containerd.io

これでcontainerdはとりあえず入ります。途中で入ってるカーネルパラメーターの調整は、Kubernetesの公式ドキュメントを参考にしています。

kubernetes.io

containerd-shim-runsc-v1のインストール

CRIランタイム(containerd)とOCIランタイム(runc, gVisor)が連携するために必要な「shim」を入れて上げる必要があるので、追加で入れておきます。

## Install gVisor shim for containerd
(
  set -e
  ARCH=$(uname -m)
  URL=https://storage.googleapis.com/gvisor/releases/release/latest/${ARCH}
  wget ${URL}/runsc ${URL}/runsc.sha512 \
    ${URL}/containerd-shim-runsc-v1 ${URL}/containerd-shim-runsc-v1.sha512
  sha512sum -c runsc.sha512 \
    -c containerd-shim-runsc-v1.sha512
  rm -f *.sha512
  chmod a+rx runsc containerd-shim-runsc-v1
  sudo mv runsc containerd-shim-runsc-v1 /usr/local/bin
)

これはgVisorの公式ドキュメントから引用。

containerdの設定を変更する

containerd config defaultを使うとデフォルトの設定値が全部表示されるので、それを所定の場所に保存します。ついでにKubernetesで必要な設定もちらほらいれておきましょう。

sudo mkdir -p /etc/containerd
sudo containerd config default > /etc/containerd/config.toml

if grep -q SystemdCgroup "/etc/containerd/config.toml"; then
  echo "Config found, skip rewriting..."
else
  sed -i -e "/^          \[plugins\.\"io\.containerd\.grpc\.v1\.cri\"\.containerd\.runtimes\.runc\.options\]$/a\            SystemdCgroup \= true\n        \[plugins\.\"io\.containerd\.grpc\.v1\.cri\"\.containerd\.runtimes\.runsc\]\n          runtime_type \= \"io\.containerd\.runsc\.v1\"" /etc/containerd/config.toml
  sed -i -e 's/shim_debug \= false/shim_debug \= true/g' /etc/containerd/config.toml
fi

これをやると主にこんな感じの設定が入るはず。

      [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
          runtime_type = "io.containerd.runc.v2"
          runtime_engine = ""
          runtime_root = ""
          privileged_without_host_devices = false
          base_runtime_spec = ""
          [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
            SystemdCgroup = true
        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]
          runtime_type = "io.containerd.runsc.v1"
  [plugins."io.containerd.runtime.v1.linux"]
    shim = "containerd-shim"
    runtime = "runc"
    runtime_root = ""
    no_shim = false
    shim_debug = true

これを見るとruncがデフォルトランタイムになってgVisor用の設定をいれて、ついでにKubernetes用にCgroupの設定をいじってる感じ。これが終わったらcontainerdのプロセスを立ち上げてあげます(今回は念の為再起動しています)。

# Restart containerd
sudo systemctl restart containerd

Kubernetes向けのセットアップとパッケージのインストール

一部インストールに向けた必須項目と、KubernetesでprotectKernelDefaultsを有効にするためにいくつかのカーネルパラメーターをいじっています。

# Set Kubernetes kernel params
cat <<EOF | tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
vm.overcommit_memory = 1
vm.panic_on_oom = 0
kernel.panic = 10
kernel.panic_on_oops = 1
kernel.keys.root_maxkeys = 1000000
kernel.keys.root_maxbytes = 25000000
EOF
sysctl --system

# Install kubeadm 
apt-get update && apt-get install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF | tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
apt-get update
apt-get install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl

kubeadm用コンフィグの設定とクラスターの作成

トークンは設定しなくても動的に生成できますが今回はめんどいので静的にトークンを仕込んじゃいます。再現性重視。criSocketにランタイムを設定したり、cgroupDriverprotectKernelDefaultsの設定などを気持ち入れています。

# Set kubeadm config
cat > ~/init_kubelet.yaml <<EOF
apiVersion: kubeadm.k8s.io/v1beta2
kind: InitConfiguration
bootstrapTokens:
# DO NOT USE THE STATIC TOKEN IN PRODUCTION
- token: "9a08jv.c0izixklcxtmnze7"
  description: "kubeadm bootstrap token"
  ttl: "24h"
nodeRegistration:
  criSocket: "/var/run/containerd/containerd.sock"
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: "systemd"
protectKernelDefaults: true
EOF

# Create a Kubernetes cluster
kubeadm init --config init_kubelet.yaml

クラスターアクセス用にkubeconfigの取得をしてCNIでCiliumを入れる

ついでにワークロードをコントロールプレーンで動かすためにtaintを解除しています。今回はシングルクラスター想定なので・・・。ワーカーノードが別に存在する場合はやらなくてもよし。

mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

# Setup CNI
curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
helm repo add cilium https://helm.cilium.io/
helm install cilium cilium/cilium --namespace kube-system

# Untaint master node to schedule workloads
kubectl taint nodes --all node-role.kubernetes.io/master-

RuntimeClassの設定をしてワークロードを動かしてみる

gVisor用のRuntimeClassリソースを適用してみます。設定ファイルの中にあるhandlerってやつはCRIランタイムの設定ファイル(containerdの場合は/etc/containerd/config.toml)で設定したハンドラーの名前を入れるので今回はrunscになります。

一応言うとplugins."io.containerd.grpc.v1.cri".containerd.runtimes.runscの部分ね。

cat <<EOF | kubectl apply -f -
apiVersion: node.k8s.io/v1beta1
kind: RuntimeClass
metadata:
  name: gvisor
handler: runsc
EOF

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: nginx-gvisor
spec:
  runtimeClassName: gvisor
  containers:
  - name: nginx
    image: nginx
EOF

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
EOF

実際にPodが動いてるか確認してみる。RuntimeClassを指定したPodは、RuntimeClassをRespectするので、もしまともに動かなかったらエラーになります。

$ kubectl get pod nginx-gvisor -o wide
NAME           READY   STATUS    RESTARTS   AGE    IP         NODE                         NOMINATED NODE   READINESS GATES
nginx-gvisor   1/1     Running   0          118s   10.0.0.6   ubuntu-s-4vcpu-8gb-sgp1-01   <none>           <none>
$ kubectl get pod nginx -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP           NODE                         NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          5s    10.0.0.131   ubuntu-s-4vcpu-8gb-sgp1-01   <none>           <none>

動作確認

$ kubectl exec -it nginx-gvisor -- dmesg
[    0.000000] Starting gVisor...
[    0.346752] Granting licence to kill(2)...
[    0.454322] Creating bureaucratic processes...
[    0.736069] Accelerating teletypewriter to 9600 baud...
[    1.184759] Letting the watchdogs out...
[    1.296606] Reading process obituaries...
[    1.364670] Checking naughty and nice process list...
[    1.467231] Generating random numbers by fair dice roll...
[    1.957884] Segmenting fault lines...
[    2.217956] Synthesizing system calls...
[    2.526996] Preparing for the zombie uprising...
[    2.808670] Ready!

わーい!

とりあえず動かした手順だけ雑に貼ってるのでちょっと読み物としてはアレですが、これでcontainerd+gVisorが動いたので僕は嬉しいです。

なお、これの実現にあたっては@IanMLewisに大変助けていただきました。ありがとうございます。

github.com

では、今日はこのへんで。