taxin's notes

読書、勉強メモ etc.

Kubernetes the hard way (GCP版) #9 (Kubernetesワーカーノードのブートストラップ)

Kubernetes the hard way (GCP版)の続きです。

前回の記事はこちら

taxintt.hatenablog.com

Chap9. Bootstrapping the Kubernetes Worker Nodes(ワーカーノードのブートストラップ)

このチャプターでは、ユーザーが作成したコンテナが動作するWorker Nodeのブートストラップを行います。
Worker NodeのコンポーネントであるKubelet・kube-proxyとコンテナランタイムのインストール・設定を行い、サービスとして起動します。

これからの作業は、Worker Node用の3つのインスタンスで同じ作業を行います。

パッケージのインストール

必要なパッケージのインストールを行います。

...@worker-0:~$ {
    >   sudo apt-get update
    >   sudo apt-get -y install socat conntrack ipset
    > }
    Hit:1 http://us-west1.gce.archive.ubuntu.com/ubuntu bionic InRelease
    Get:2 http://us-west1.gce.archive.ubuntu.com/ubuntu bionic-updates InRelease [88.7 kB]
    ...


Swapの無効化

ユーザーが作成したコンテナが動作するWorker Nodeでは、スワップの無効化が推奨されています。

It is recommended that swap be disabled to ensure Kubernetes can provide proper resource allocation and quality of service.

そのため、各Nodeでスワップが有効化されていないかを確認します。
(スワップは無効になっていたことを確認したので、次の手順に進みます。)

...@worker-0:~$ sudo swapon --show


Worker Node用のバイナリーのダウンロードとインストール

Worker Node用のコンポーネントなどを含んだバイナリーをダウンロードします。

...@worker-0:~$ wget -q --show-progress --https-only --timestamping \
    >   https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.15.0/crictl-v1.15.0-linux-amd64.tar.gz \
    >   https://github.com/opencontainers/runc/releases/download/v1.0.0-rc8/runc.amd64 \
    >   https://github.com/containernetworking/plugins/releases/download/v0.8.2/cni-plugins-linux-amd64-v0.8.2.tgz \
    >   https://github.com/containerd/containerd/releases/download/v1.2.9/containerd-1.2.9.linux-amd64.tar.gz \
    >   https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kubectl \
    >   https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kube-proxy \
    >   https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kubelet
    crictl-v1.15.0-linux-amd64.tar.gz              100%[=================================================================================================>]  11.50M  18.5MB/s    in 0.6s
    runc.amd64                                     100%[=================================================================================================>]   9.79M  15.8MB/s    in 0.6s
    cni-plugins-linux-amd64-v0.8.2.tgz             100%[=================================================================================================>]  34.96M  29.8MB/s    in 1.2s
    containerd-1.2.9.linux-amd64.tar.gz            100%[=================================================================================================>]  32.96M  30.5MB/s    in 1.1s
    kubectl                                        100%[=================================================================================================>]  40.99M   170MB/s    in 0.2s
    kube-proxy                                     100%[=================================================================================================>]  35.27M   117MB/s    in 0.3s
    kubelet                                        100%[=================================================================================================>] 114.10M   202MB/s    in 0.6s


次に、バイナリーのインストール用のディレクトリを作成します。

...@worker-0:~$ sudo mkdir -p \
    >   /etc/cni/net.d \
    >   /opt/cni/bin \
    >   /var/lib/kubelet \
    >   /var/lib/kube-proxy \
    >   /var/lib/kubernetes \
    >   /var/run/kubernetes


ディレクトリを作成した上で、ダウンロードしたバイナリーをインストールします。

Kubernetesコンポーネントに加えてcrictlもダウンロードしていますが、これはCRI互換のコンテナランタイムに対して利用可能なCLIツールのようです。

github.com

...@worker-0:~$ {
    >   mkdir containerd
    >   tar -xvf crictl-v1.15.0-linux-amd64.tar.gz
    >   tar -xvf containerd-1.2.9.linux-amd64.tar.gz -C containerd
    >   sudo tar -xvf cni-plugins-linux-amd64-v0.8.2.tgz -C /opt/cni/bin/
    >   sudo mv runc.amd64 runc
    >   chmod +x crictl kubectl kube-proxy kubelet runc
    >   sudo mv crictl kubectl kube-proxy kubelet runc /usr/local/bin/
    >   sudo mv containerd/bin/* /bin/
    > }
    crictl
    bin/
    bin/containerd-shim-runc-v1
    bin/ctr
    bin/containerd
    bin/containerd-stress
    bin/containerd-shim
    ./
    ./flannel
    ./ptp
    ./host-local
    ./firewall
    ./portmap
    ./tuning
    ./vlan
    ./host-device
    ./bandwidth
    ./sbr
    ./static
    ./dhcp
    ./ipvlan
    ./macvlan
    ./loopback
    ./bridge


CNIネットワーキングの設定

次にWorker Nodeに関連するネットワーク周りの設定を行います。
最初に、Node(VM)で確保されているpodに割り当てるためのCIDRブロック(IPアドレスレンジ)を確認します。

...@worker-0:~$POD_CIDR=$(curl -s -H "Metadata-Flavor: Google" \
    >   http://metadata.google.internal/computeMetadata/v1/instance/attributes/pod-cidr)
...@worker-0:~$ echo $POD_CIDR
10.200.0.0/24


事前に定義しておいたCIDRブロック(IPアドレスレンジ)を基に、下記のネットワーク設定ファイルを作成します。

  • 10-bridge.conf: Bridgeのネットワークの設定
  • 99-loopback.conf: Loopbackネットワークの設定
...@worker-0:~$ cat <<EOF | sudo tee /etc/cni/net.d/10-bridge.conf
    > {
    >     "cniVersion": "0.3.1",
    >     "name": "bridge",
    >     "type": "bridge",
    >     "bridge": "cnio0",
    >     "isGateway": true,
    >     "ipMasq": true,
    >     "ipam": {
    >         "type": "host-local",
    >         "ranges": [
    >           [{"subnet": "${POD_CIDR}"}]
    >         ],
    >         "routes": [{"dst": "0.0.0.0/0"}]
    >     }
    > }
    > EOF
    {
        "cniVersion": "0.3.1",
        "name": "bridge",
        "type": "bridge",
        "bridge": "cnio0",
        "isGateway": true,
        "ipMasq": true,
        "ipam": {
            "type": "host-local",
            "ranges": [
              [{"subnet": "10.200.0.0/24"}]
            ],
            "routes": [{"dst": "0.0.0.0/0"}]
        }
    }

...@worker-0:~$ cat <<EOF | sudo tee /etc/cni/net.d/99-loopback.conf
    > {
    >     "cniVersion": "0.3.1",
    >     "name": "lo",
    >     "type": "loopback"
    > }
    > EOF
    {
        "cniVersion": "0.3.1",
        "name": "lo",
        "type": "loopback"
    }


10-bridge.confでは、IPAM (IPアドレス管理) の設定項目でranges(IPアドレスの範囲) に対して事前に定義した値 ($POD_CIDR) を設定しています。

containerdの設定

続いて、コンテナランタイムの設定を行います。
最初に、containerdの設定ファイル(config.toml)を作成します。

github.com

今回作成するクラスタでは、High-levelのランタイムとしてcontainerd・Low-levelのランタイムとしてruncを利用します。
そのため、containerdの設定ファイル内の項目(runtime_engine)ではruncを指定しています。

High-level / Low-levelのコンテナランタイムの概念は事前に頭に入れておけば、この部分の設定内容も混乱しないと思います。

medium.com

...@worker-0:~$ sudo mkdir -p /etc/containerd/
...@worker-0:~$ cat << EOF | sudo tee /etc/containerd/config.toml
    > [plugins]
    >   [plugins.cri.containerd]
    >     snapshotter = "overlayfs"
    >     [plugins.cri.containerd.default_runtime]
    >       runtime_type = "io.containerd.runtime.v1.linux"
    >       runtime_engine = "/usr/local/bin/runc"
    >       runtime_root = ""
    > EOF
    [plugins]
      [plugins.cri.containerd]
        snapshotter = "overlayfs"
        [plugins.cri.containerd.default_runtime]
          runtime_type = "io.containerd.runtime.v1.linux"
          runtime_engine = "/usr/local/bin/runc"
          runtime_root = ""


別のLow-levelであるkata-containerと連携する場合も、この項目で利用するLow-levelランタイムを指定しています。

github.com


次に、containerd.serviceのsystemdのユニットファイルを作成します。

...@worker-0:~$ cat <<EOF | sudo tee /etc/systemd/system/containerd.service
    > [Unit]
    > Description=containerd container runtime
    > Documentation=https://containerd.io
    > After=network.target
    >
    > [Service]
    > ExecStartPre=/sbin/modprobe overlay
    > ExecStart=/bin/containerd
    > Restart=always
    > RestartSec=5
    > Delegate=yes
    > KillMode=process
    > OOMScoreAdjust=-999
    > LimitNOFILE=1048576
    > LimitNPROC=infinity
    > LimitCORE=infinity
    >
    > [Install]
    > WantedBy=multi-user.target
    > EOF
    [Unit]
    Description=containerd container runtime
    Documentation=https://containerd.io
    After=network.target

    [Service]
    ExecStartPre=/sbin/modprobe overlay
    ExecStart=/bin/containerd
    Restart=always
    RestartSec=5
    Delegate=yes
    KillMode=process
    OOMScoreAdjust=-999
    LimitNOFILE=1048576
    LimitNPROC=infinity
    LimitCORE=infinity

    [Install]
    WantedBy=multi-user.target


これでcontainerdの設定が完了となります。

Kubeletの設定

次にkubeletの設定を行います。
最初にkubelet用の秘密鍵・証明書ファイル、kubeconfigファイルを/var/lib/kubelet/に移動します。

...@worker-0:~$ {
    >   sudo mv ${HOSTNAME}-key.pem ${HOSTNAME}.pem /var/lib/kubelet/
    >   sudo mv ${HOSTNAME}.kubeconfig /var/lib/kubelet/kubeconfig
    >   sudo mv ca.pem /var/lib/kubernetes/
    > }


次に、kubeletの設定ファイルを作成します。

...@worker-0:~$ cat <<EOF | sudo tee /var/lib/kubelet/kubelet-config.yaml
    > kind: KubeletConfiguration
    > apiVersion: kubelet.config.k8s.io/v1beta1
    > authentication:
    >   anonymous:
    >     enabled: false
    >   webhook:
    >     enabled: true
    >   x509:
    >     clientCAFile: "/var/lib/kubernetes/ca.pem"
    > authorization:
    >   mode: Webhook
    > clusterDomain: "cluster.local"
    > clusterDNS:
    >   - "10.32.0.10"
    > podCIDR: "${POD_CIDR}"
    > resolvConf: "/run/systemd/resolve/resolv.conf"
    > runtimeRequestTimeout: "15m"
    > tlsCertFile: "/var/lib/kubelet/${HOSTNAME}.pem"
    > tlsPrivateKeyFile: "/var/lib/kubelet/${HOSTNAME}-key.pem"
    > EOF
    kind: KubeletConfiguration
    apiVersion: kubelet.config.k8s.io/v1beta1
    authentication:
      anonymous:
        enabled: false
      webhook:
        enabled: true
      x509:
        clientCAFile: "/var/lib/kubernetes/ca.pem"
    authorization:
      mode: Webhook
    clusterDomain: "cluster.local"
    clusterDNS:
      - "10.32.0.10"
    podCIDR: "10.200.0.0/24"
    resolvConf: "/run/systemd/resolve/resolv.conf"
    runtimeRequestTimeout: "15m"
    tlsCertFile: "/var/lib/kubelet/worker-0.pem"
    tlsPrivateKeyFile: "/var/lib/kubelet/worker-0-key.pem"


設定ファイル内で確認すべき点としては下記の部分です。

1. Authentication Mode

Authentication ModeとしてWebhook(webhook-token-authentication) を指定します。
これにより、Kubeletはトークンを受け取った上でTokenReviewAPIをcallし、API Serverに対して認証処理をDeligate(委任)します。

kubernetes.io

また、Authentication Modeの一つであるanonymousを無効化しています。
この設定が有効になっていると、Kubeletに対するリクエストを (Authenticationのフェーズで) 全て許可して、Kubeletに対するAPI Requestをanonymousとして扱います。

基本的にはAPI Requestはユーザー(もしくはService Account)と紐づけられますが、この場合は下記のユーザーとグループに紐づけられます。

  • user: system:anonymous
  • group: system:unauthenticated

kubernetes.io

labs.f-secure.com


公式のドキュメントでも、anonymousの設定は非推奨となっています。

kubernetes.io

加えて、クライアント側のCA証明書ファイル(clientCAFile)としてユーザー側で作成したca.pemを指定しています。
これにより、kubelet側でX509クライアント証明書を用いた認証が可能になります。
(kubelet側では、webhookとX509クライアント証明書認証の両方の方式を利用することができると認識していますが、この部分はまだ整理できていないので調査中です。)


2. Authorization Mode

Authorization ModeとしてWebhookを指定します。
先ほどのAuthenticationのフェーズで認証された後にユーザー情報が付与されて、それを基に認可(Authorization)を行うという流れになっています。

... and user information is added to its context. This gives future steps (such as authorization and admission controllers) the ability to access the previously established identity of the user.

github.com

Authorization ModeとしてWebhookを指定することで、API Requestに対する認可処理をKubernetesAPI ServerにDeligate(委任)します。
KubeletはSubjectAccessReviewAPIをcallして、API Serverに対して認可処理を依頼します。

kubernetes.io

3. resolvConf

kubeletではWorker Nodeではなく外部のDNSゾルバを参照するために/run/systemd/resolve/resolv.confを指定しています。
これにより、KubeletはControl Plane内のDNSを参照することになります。

qiita.com

次に、kubelet.serviceのsystemdのユニットファイルを作成します。
ユニットファイルの中では、先ほど作成した設定ファイル (kubelet-config.yaml) を参照しています。

...@worker-0:~$ cat <<EOF | sudo tee /etc/systemd/system/kubelet.service
    > [Unit]
    > Description=Kubernetes Kubelet
    > Documentation=https://github.com/kubernetes/kubernetes
    > After=containerd.service
    > Requires=containerd.service
    >
    > [Service]
    > ExecStart=/usr/local/bin/kubelet \\
    >   --config=/var/lib/kubelet/kubelet-config.yaml \\
    >   --container-runtime=remote \\
    >   --container-runtime-endpoint=unix:///var/run/containerd/containerd.sock \\
    >   --image-pull-progress-deadline=2m \\
    >   --kubeconfig=/var/lib/kubelet/kubeconfig \\
    >   --network-plugin=cni \\
    >   --register-node=true \\
    >   --v=2
    > Restart=on-failure
    > RestartSec=5
    >
    > [Install]
    > WantedBy=multi-user.target
    > EOF
    [Unit]
    Description=Kubernetes Kubelet
    Documentation=https://github.com/kubernetes/kubernetes
    After=containerd.service
    Requires=containerd.service

    [Service]
    ExecStart=/usr/local/bin/kubelet \
      --config=/var/lib/kubelet/kubelet-config.yaml \
      --container-runtime=remote \
      --container-runtime-endpoint=unix:///var/run/containerd/containerd.sock \
      --image-pull-progress-deadline=2m \
      --kubeconfig=/var/lib/kubelet/kubeconfig \
      --network-plugin=cni \
      --register-node=true \
      --v=2
    Restart=on-failure
    RestartSec=5

    [Install]
    WantedBy=multi-user.target


kube-proxyの設定

最後に、kube-proxyの設定を行います。
kubeletと同様に、設定ファイルを作成した上でsystemdのユニットファイルを作成します。

...@worker-0:~$ sudo mv kube-proxy.kubeconfig /var/lib/kube-proxy/kubeconfig
...@worker-0:~$ cat <<EOF | sudo tee /var/lib/kube-proxy/kube-proxy-config.yaml
    > kind: KubeProxyConfiguration
    > apiVersion: kubeproxy.config.k8s.io/v1alpha1
    > clientConnection:
    >   kubeconfig: "/var/lib/kube-proxy/kubeconfig"
    > mode: "iptables"
    > clusterCIDR: "10.200.0.0/16"
    > EOF
    kind: KubeProxyConfiguration
    apiVersion: kubeproxy.config.k8s.io/v1alpha1
    clientConnection:
      kubeconfig: "/var/lib/kube-proxy/kubeconfig"
    mode: "iptables"
    clusterCIDR: "10.200.0.0/16"

...@worker-0:~$ cat <<EOF | sudo tee /etc/systemd/system/kube-proxy.service
    > [Unit]
    > Description=Kubernetes Kube Proxy
    > Documentation=https://github.com/kubernetes/kubernetes
    >
    > [Service]
    > ExecStart=/usr/local/bin/kube-proxy \\
    >   --config=/var/lib/kube-proxy/kube-proxy-config.yaml
    > Restart=on-failure
    > RestartSec=5
    >
    > [Install]
    > WantedBy=multi-user.target
    > EOF
    [Unit]
    Description=Kubernetes Kube Proxy
    Documentation=https://github.com/kubernetes/kubernetes

    [Service]
    ExecStart=/usr/local/bin/kube-proxy \
      --config=/var/lib/kube-proxy/kube-proxy-config.yaml
    Restart=on-failure
    RestartSec=5

    [Install]
    WantedBy=multi-user.target


設定ファイル内で確認すべき点としては下記の部分です。

1. kube-proxyのmode

kube-proxyはWorker Node内で動作して、クラスタ内部または外部からPodにアクセスできるようにネットワーク周りを管理しています。
下記のモードから任意の実装を選択することが可能です。

  • iptables(デフォルト)
  • ipvs
  • userspace

kubernetes.io

Worker Node用のサービス起動

コンポーネントの設定を行なった上で、作成したサービスを起動します。

    {
      sudo systemctl daemon-reload
      sudo systemctl enable containerd kubelet kube-proxy
      sudo systemctl start containerd kubelet kube-proxy
    }

検証1

一通り設定が完了したら、検証を行います。
Control Planeにsshでログインして、Adminユーザー用のkubeconfigを指定した上でkubectl get nodesコマンドを実行します。

~/w/k/h/04 ❯❯❯ gcloud compute ssh controller-0 \
      --command "kubectl get nodes --kubeconfig admin.kubeconfig"
    Enter passphrase for key '/Users/nishikawatakushi/.ssh/google_compute_engine':
    NAME       STATUS   ROLES    AGE   VERSION
    worker-0   Ready    <none>   22s   v1.15.3
    worker-1   Ready    <none>   21s   v1.15.3
    worker-2   Ready    <none>   19s   v1.15.3


(+α) swap(スワップ)とは

メモリ不足時に、物理メモリ上のデータをHDDなどに書き出すことをスワップと言います。
OSのメモリ管理機能として提供されており、Worker Nodeの実態であるVMでもメモリのスワップは発生し得ます。

e-words.jp

しかし、Kubernetesでは一部のpodがメモリを消費することで、他のpodのメモリがswapして速度が低下するため、swap自体を無効化することが推奨されています。

github.com

Kubernetesのv1.8から、swapが有効になっている場合に、kubeletが起動しないように設定されているようです。

qiita.com


今回は、Chap9の内容をまとめました。
Chap8でまとめたControl Planeの設定と関連する部分もあるので、各コンポーネント同士の設定を紐づけて理解しておく必要があると感じました。