taxin's notes

読書、勉強メモ etc.

Kubernetes the hard way (GCP版) #10 (リモートアクセス用のkubectl設定)

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

前回の記事はこちら taxintt.hatenablog.com

Chap10. Configuring kubectl for Remote Access(リモートアクセス用のkubectl設定)

このチャプターでは、ユーザーがリモートからKubernetesクラスターにアクセスする際に利用するkubectlコマンドの設定を行います。
(ハンズオンの手順も少ないので、今回の記事は比較的短めです。)

Kubernetesの設定ファイル(Adminユーザー用)

ユーザー用のクライアント証明書と鍵ファイルを指定してkubeconfigファイル(接続設定ファイル)を作成します。
kubeconfigファイルの作成時に実行しているコマンドの内容については、Chap5の内容を参考にして頂ければと思います。

taxintt.hatenablog.com

以前のチャプターで作成した証明書・鍵ファイルを用いてkubectl config set-clusterコマンドで通信先を--serverとして指定して、Adminユーザーとしての権限を付与しています。

~/w/k/h/04 ❯❯❯ >{
  KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
    --region $(gcloud config get-value compute/region) \
    --format 'value(address)')

  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://${KUBERNETES_PUBLIC_ADDRESS}:6443

  kubectl config set-credentials admin \
    --client-certificate=admin.pem \
    --client-key=admin-key.pem

  kubectl config set-context kubernetes-the-hard-way \
    --cluster=kubernetes-the-hard-way \
    --user=admin

  kubectl config use-context kubernetes-the-hard-way
}
Cluster "kubernetes-the-hard-way" set.
User "admin" set.
Context "kubernetes-the-hard-way" created.
Switched to context "kubernetes-the-hard-way".

検証1

kubectlコマンドの設定が正常にできたかを確認するために、リモートからコマンドを実行します。 kubectl get componentstatusesコマンドを実行して、手順内の想定では下記のような表示になるはずです。

NAME                 STATUS    MESSAGE             ERROR
controller-manager   Healthy   ok
scheduler            Healthy   ok
etcd-1               Healthy   {"health":"true"}
etcd-2               Healthy   {"health":"true"}
etcd-0               Healthy   {"health":"true"}


しかし、実際に実行したところ下記のような結果になりました。

サーバ側のversion情報を取得できなかったので、Kind(Kubernetes in Docker)で別のクラスタを用意して同じコマンドを実行した際には正常に結果が取得できていました。
そのため、特定のversionのKubernetesクラスターに依存する問題かと考えられます。

github.com

~/w/k/h/04 ❯❯❯ kubectl get componentstatuses
NAME                 AGE
scheduler            <unknown>
controller-manager   <unknown>
etcd-2               <unknown>
etcd-1               <unknown>
etcd-0               <unknown>

~/w/k/h/04 ❯❯❯ kubectl version                                   
Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.2", GitCommit:"59603c6e503c87169aea6106f57b9f242f64df89", GitTreeState:"clean", BuildDate:"2020-01-18T23:30:10Z", GoVersion:"go1.13.5", Compiler:"gc", Platform:"darwin/amd64"}
Unable to connect to the server: dial tcp 35.233.237.51:6443: i/o timeout


調べてみたところ、KubernetesのServer versionによってはkubectl get componentstatusesコマンドの出力結果が正常に表示されないケースがあるようです。

github.com

今回は、Chap10の内容をまとめました。

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の設定と関連する部分もあるので、各コンポーネント同士の設定を紐づけて理解しておく必要があると感じました。

Kubernetes the hard way (GCP版) #8 (Kubernetesコントロールプレーンのブートストラップ)

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

前回の記事はこちら

taxintt.hatenablog.com

Chap8. Bootstrapping the Kubernetes Control Plane(コントロールプレーンのブートストラップ)

このチャプターでは、Control Planeのブートストラップを行います。
Control PlaneのコンポーネントであるAPI Server・Controller Manager・Schedulerの設定を行い、サービスとして起動します。
Control PlaneはHA構成なので3つのインスタンスで同じ作業を行います。

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

最初にconfigファイル用のディレクトリを作成します。
この後の手順で作成されるconfigファイルはこのディレクトリに配置されます。

...@controller-0:~$ sudo mkdir -p /etc/kubernetes/config


次にControl Planeを構成するコンポーネントのバイナリーをダウンロードします。
ダウンロードしたバイナリーは/usr/local/bin/に配置します。

...@controller-0:~$ wget -q --show-progress --https-only --timestamping \
>   "https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kube-apiserver" \
>   "https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kube-controller-manager" \
>   "https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kube-scheduler" \
>   "https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kubectl"
kube-apiserver                                 100%[=================================================================================================>] 156.90M   203MB/s    in 0.8s
kube-controller-manager                        100%[=================================================================================================>] 111.03M   173MB/s    in 0.6s
kube-scheduler                                 100%[=================================================================================================>]  36.99M  99.8MB/s    in 0.4s
kubectl                                        100%[=================================================================================================>]  40.99M   118MB/s    in 0.3s
...@controller-0:~$ {
>   chmod +x kube-apiserver kube-controller-manager kube-scheduler kubectl
>   sudo mv kube-apiserver kube-controller-manager kube-scheduler kubectl /usr/local/bin/
> }


Kubernetes API Serverの設定

ここからは各コンポーネントの設定を行っていきます。
API Serverの設定を行う前に/var/lib/kubernetes/ディレクトリを作成して、そのディレクトリに各種証明書ファイルとetcdの暗号化用の設定ファイル(encryption-config.yaml)を配置します。

...@controller-0:~$ {
>   sudo mkdir -p /var/lib/kubernetes/
>
>   sudo mv ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem \
>     service-account-key.pem service-account.pem \
>     encryption-config.yaml /var/lib/kubernetes/
> }


インスタンスのPrivate IPアドレスINTERNAL_IPとして取得し、API Serverのサービスファイルを作成します。
サービスファイルでは、下記のような項目が設定されています。

  • --authorization-mode: 利用する認可モジュール
  • --enable-admission-plugins: 利用するAdmission Controlモジュール

クライアントからのAPIリクエストに対して、API Serverは認証→認可→Admission Controlの順にクライアントの認証認可とリクエストの妥当性検証を行います。
上記のチェックを行なった上で許可された処理が実行されます。


認可モジュールに関しては、下記の項目が設定されています。
認可モジュールは複数のモジュールを指定でき、先に指定したモジュール(この場合はNodeモジュール)が処理の順番として優先されます。

  • Node: kubeletからのリクエストを認可するためのモジュール
  • RBAC: Role/ClusterRoleなどのリソースを用いて定義されたRBACのルールを利用して認可を行うためのモジュール

試せてはいないのですが、外部サービスへの問い合わせ結果を基に権限認可を行うWebhookモジュールも存在します。

tech.jxpress.net


また、Admission Controlモジュールに関しては、下記の項目が設定されています。
リソースの定義内の項目 (e.g. Namespaceの指定, リソースの使用量)に関連したものが多く、不正リクエストの実行を防止します。

  • NamespaceLifecycle: 存在しないNamespaceを利用したリソース作成の制限やシステム側で予約しているNamespaceの削除防止などを行う
  • NodeRestriction: kubeletによるNodeとpodの操作を制限する
  • LimitRanger: Namespaceに設定したLimitRange (podごとに適用されるリソースの使用量制限) に準拠しているかを検証する
  • ServiceAccount: ServiceAccountが設定されていないpodにDefaultServiceAccountを設定するなどの処理の自動化を行う
  • DefaultStorageClass: StorageClassが未指定のPVCリソースの作成時にDefaultStorageClassを指定する
  • ResourceQuota: ResourceQuota (Namespaceに対して利用可能なリソース量) に準拠しているかを検証する

この部分に関しては、下記の記事や最近発売された「Docker/Kubernetes 開発・運用のためのセキュリティ実践ガイド」にも詳しく記載されています。

knowledge.sakura.ad.jp

Amazon CAPTCHA

...@controller-0:~$ INTERNAL_IP=$(curl -s -H "Metadata-Flavor: Google" \
>   http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ip)
...@controller-0:~$ echo $INTERNAL_IP
10.240.0.10
...@controller-0:~$ cat <<EOF | sudo tee /etc/systemd/system/kube-apiserver.service
> [Unit]
> Description=Kubernetes API Server
> Documentation=https://github.com/kubernetes/kubernetes
>
> [Service]
> ExecStart=/usr/local/bin/kube-apiserver \\
>   --advertise-address=${INTERNAL_IP} \\
>   --allow-privileged=true \\
>   --apiserver-count=3 \\
>   --audit-log-maxage=30 \\
>   --audit-log-maxbackup=3 \\
>   --audit-log-maxsize=100 \\
>   --audit-log-path=/var/log/audit.log \\
>   --authorization-mode=Node,RBAC \\
>   --bind-address=0.0.0.0 \\
>   --client-ca-file=/var/lib/kubernetes/ca.pem \\
>   --enable-admission-plugins=NamespaceLifecycle,NodeRestriction,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota \\
>   --etcd-cafile=/var/lib/kubernetes/ca.pem \\
>   --etcd-certfile=/var/lib/kubernetes/kubernetes.pem \\
>   --etcd-keyfile=/var/lib/kubernetes/kubernetes-key.pem \\
>   --etcd-servers=https://10.240.0.10:2379,https://10.240.0.11:2379,https://10.240.0.12:2379 \\
>   --event-ttl=1h \\
>   --encryption-provider-config=/var/lib/kubernetes/encryption-config.yaml \\
>   --kubelet-certificate-authority=/var/lib/kubernetes/ca.pem \\
>   --kubelet-client-certificate=/var/lib/kubernetes/kubernetes.pem \\
>   --kubelet-client-key=/var/lib/kubernetes/kubernetes-key.pem \\
>   --kubelet-https=true \\
>   --runtime-config=api/all \\
>   --service-account-key-file=/var/lib/kubernetes/service-account.pem \\
>   --service-cluster-ip-range=10.32.0.0/24 \\
>   --service-node-port-range=30000-32767 \\
>   --tls-cert-file=/var/lib/kubernetes/kubernetes.pem \\
>   --tls-private-key-file=/var/lib/kubernetes/kubernetes-key.pem \\
>   --v=2
> Restart=on-failure
> RestartSec=5
>
> [Install]
> WantedBy=multi-user.target
> EOF
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-apiserver \
  --advertise-address=10.240.0.10 \
   ...
  --tls-cert-file=/var/lib/kubernetes/kubernetes.pem \
  --tls-private-key-file=/var/lib/kubernetes/kubernetes-key.pem \
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target


Kubernetes Controller Managerの設定

次に、Controller Managerの設定を行います。
API Server同様に必要な設定ファイルを/var/lib/kubernetes/に配置して、Controller Managerのサービスファイルを作成します。

サービスファイルでは、下記のような項目が設定されています。

  • --leader-elect: HA構成の場合にリーダーを選出するかどうかを指定するフラグ
  • --service-account-private-key-file: ServiceAccountの秘密鍵ファイル
  • --use-service-account-credentials: Controller毎にServiceAccount用のCredentialsを利用するかどうかを指定するフラグ

Controller Managerの起動時に--use-service-account-credentialsを指定して起動することで、Managerで管理される各ControllerはServiceAccount用のCredentials(トークン)を利用します。
(Credentialsの作成に必要な鍵ファイルはChap4ですでに作成済みなので、上記のフラグで指定することで鍵ファイルを基にトークンが生成されると考えられます。)

taxintt.hatenablog.com

...@controller-0:~$ sudo mv kube-controller-manager.kubeconfig /var/lib/kubernetes/
...@controller-0:~$ cat <<EOF | sudo tee /etc/systemd/system/kube-controller-manager.service
> [Unit]
> Description=Kubernetes Controller Manager
> Documentation=https://github.com/kubernetes/kubernetes
>
> [Service]
> ExecStart=/usr/local/bin/kube-controller-manager \\
>   --address=0.0.0.0 \\
>   --cluster-cidr=10.200.0.0/16 \\
>   --cluster-name=kubernetes \\
>   --cluster-signing-cert-file=/var/lib/kubernetes/ca.pem \\
>   --cluster-signing-key-file=/var/lib/kubernetes/ca-key.pem \\
>   --kubeconfig=/var/lib/kubernetes/kube-controller-manager.kubeconfig \\
>   --leader-elect=true \\
>   --root-ca-file=/var/lib/kubernetes/ca.pem \\
>   --service-account-private-key-file=/var/lib/kubernetes/service-account-key.pem \\
>   --service-cluster-ip-range=10.32.0.0/24 \\
>   --use-service-account-credentials=true \\
>   --v=2
> Restart=on-failure
> RestartSec=5
>
> [Install]
> WantedBy=multi-user.target
> EOF
[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-controller-manager \
  --address=0.0.0.0 \
  --cluster-cidr=10.200.0.0/16 \
  --cluster-name=kubernetes \
  --cluster-signing-cert-file=/var/lib/kubernetes/ca.pem \
  --cluster-signing-key-file=/var/lib/kubernetes/ca-key.pem \
  --kubeconfig=/var/lib/kubernetes/kube-controller-manager.kubeconfig \
  --leader-elect=true \
  --root-ca-file=/var/lib/kubernetes/ca.pem \
  --service-account-private-key-file=/var/lib/kubernetes/service-account-key.pem \
  --service-cluster-ip-range=10.32.0.0/24 \
  --use-service-account-credentials=true \
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target


Kubernetes Schedulerの設定

同様に、Schedulerの設定を行います。
ここでは、kube-scheduler.yamlというscheduler用の設定ファイルを作成した上でサービスファイルを作成します。

...@controller-0:~$ sudo mv kube-scheduler.kubeconfig /var/lib/kubernetes/
...@controller-0:~$ cat <<EOF | sudo tee /etc/kubernetes/config/kube-scheduler.yaml
> apiVersion: kubescheduler.config.k8s.io/v1alpha1
> kind: KubeSchedulerConfiguration
> clientConnection:
>   kubeconfig: "/var/lib/kubernetes/kube-scheduler.kubeconfig"
> leaderElection:
>   leaderElect: true
> EOF
apiVersion: kubescheduler.config.k8s.io/v1alpha1
kind: KubeSchedulerConfiguration
clientConnection:
  kubeconfig: "/var/lib/kubernetes/kube-scheduler.kubeconfig"
leaderElection:
  leaderElect: true
nishikawatakushi@controller-0:~$ ls
admin.kubeconfig  etcd-v3.4.0-linux-amd64  etcd-v3.4.0-linux-amd64.tar.gz

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

[Service]
ExecStart=/usr/local/bin/kube-scheduler \
  --config=/etc/kubernetes/config/kube-scheduler.yaml \
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target


Control Plane用のサービス起動

サービスファイルの作成が完了した段階で、Control Plane用のサービス (kube-apiserver, kube-controller-manager, kube-scheduler) を起動します。
手順にはありませんが、systemctl status <service_name>でサービスの起動を確認したり、journalctl -u <service_name>でログを確認するのも面白いと思います。

qiita.com

...@controller-0:~$ {
>   sudo systemctl daemon-reload
>   sudo systemctl enable kube-apiserver kube-controller-manager kube-scheduler
>   sudo systemctl start kube-apiserver kube-controller-manager kube-scheduler
> }
Created symlink /etc/systemd/system/multi-user.target.wants/kube-apiserver.service → /etc/systemd/system/kube-apiserver.service.
Created symlink /etc/systemd/system/multi-user.target.wants/kube-controller-manager.service → /etc/systemd/system/kube-controller-manager.service.
Created symlink /etc/systemd/system/multi-user.target.wants/kube-scheduler.service → /etc/systemd/system/kube-scheduler.service.

...@controller-0:~$ systemctl status kube-apiserver
● kube-apiserver.service - Kubernetes API Server
   Loaded: loaded (/etc/systemd/system/kube-apiserver.service; enabled; vendor preset: enabled)
   Active: active (running) since Sat 2020-02-29 04:27:20 UTC; 16s ago
     Docs: https://github.com/kubernetes/kubernetes
 Main PID: 4205 (kube-apiserver)
    Tasks: 8 (limit: 4394)
   CGroup: /system.slice/kube-apiserver.service
           └─4205 /usr/local/bin/kube-apiserver --advertise-address=10.240.0.10 --allow-privileged=true --apiserver-count=3 --audit-log-maxage=30 --audit-log-maxbackup=3 --audit-log-max

Feb 29 04:27:32 controller-0 kube-apiserver[4205]: [+]poststarthook/start-kube-aggregator-informers ok
Feb 29 04:27:32 controller-0 kube-apiserver[4205]: [+]poststarthook/apiservice-registration-controller ok
Feb 29 04:27:32 controller-0 kube-apiserver[4205]: [+]poststarthook/apiservice-status-available-controller ok
Feb 29 04:27:32 controller-0 kube-apiserver[4205]: [+]poststarthook/apiservice-openapi-controller ok
Feb 29 04:27:32 controller-0 kube-apiserver[4205]: [+]poststarthook/kube-apiserver-autoregistration ok
Feb 29 04:27:32 controller-0 kube-apiserver[4205]: [+]autoregister-completion ok
Feb 29 04:27:32 controller-0 kube-apiserver[4205]: healthz check failed
Feb 29 04:27:32 controller-0 kube-apiserver[4205]: I0229 04:27:32.410648    4205 controller.go:606] quota admission added evaluator for: endpoints
Feb 29 04:27:32 controller-0 kube-apiserver[4205]: W0229 04:27:32.428474    4205 lease.go:223] Resetting endpoints for master service "kubernetes" to [10.240.0.10]


HTTPヘルスチェックの有効化

サービスを起動したら、各Control Plane用のインスタンスにHTTPエンドポイント用のnginxを導入します。
(この部分に関しては日本語訳のページを見た方が、手順の意図がわかりやすいかと思います。)

github.com

API Serverはサービスの起動時に、HTTPSのエンドポイントを用意します。
しかし、今回の構成では前段に存在するLBがTLS終端となってしまい、API ServerのendpointにはHTTPでの通信のみ可能となるという状況になります。

そのため、今回はnginxをproxyとして利用し、ヘルスチェックのリクエスト (HTTP) をAPI ServerのEndpoint (HTTPS) に中継します。
プロキシの設定では下記の項目を設定します。

  • proxy_pass: 転送先のパス
  • proxy_ssl_trusted_certificate: プロキシサーバの証明書検証のために利用されるCA証明書

nginxのconfファイル内でproxy_ssl_certificateのように利用するTLS証明書を指定しなくてもいいのか...?という疑問は個人的に残っています。
(これに関しては、後日追記予定)

...@controller-0:~$ cat > kubernetes.default.svc.cluster.local <<EOF
> server {
>   listen      80;
>   server_name kubernetes.default.svc.cluster.local;
>
>   location /healthz {
>      proxy_pass                    https://127.0.0.1:6443/healthz;
>      proxy_ssl_trusted_certificate /var/lib/kubernetes/ca.pem;
>   }
> }
> EOF
...@controller-0:~$ {
>   sudo mv kubernetes.default.svc.cluster.local \
>     /etc/nginx/sites-available/kubernetes.default.svc.cluster.local
>
>   sudo ln -s /etc/nginx/sites-available/kubernetes.default.svc.cluster.local /etc/nginx/sites-enabled/
> }
...@controller-0:~$ sudo systemctl restart nginx
...@controller-0:~$ sudo systemctl enable nginx
Synchronizing state of nginx.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable nginx


検証1

HTTPエンドポイントの設定が完了したら、Control Planeの検証を行います。
最初にインスタンス内でadminユーザー用のconfigファイルを利用して、Control Planeのコンポーネントのステータスチェックを行います。

...@controller-0:~$ kubectl get componentstatuses --kubeconfig admin.kubeconfig
NAME                 STATUS    MESSAGE             ERROR
scheduler            Healthy   ok
controller-manager   Healthy   ok
etcd-2               Healthy   {"health":"true"}
etcd-0               Healthy   {"health":"true"}
etcd-1               Healthy   {"health":"true"}


次に、ヘルスチェックのリクエスト(HTTP)をnginxが指定のエンドポイントに中継しているかを確認します。
curlAPI Serverに対してリクエストを投げる際には、ホストヘッダーを指定します。
(これによって、kubernetes.default.svc.cluster.local (= API Serverのサービス名?) に対するリクエストの結果が返却されます。)

gihyo.jp

ここで実行している/healthzのHTTPリクエストの実態は、Kubernetesで利用できるヘルスチェックの内の一つであるLiveness Probeです。
ヘルスチェックによってpodの生死を確認することが可能です。

別のヘルスチェックとしてpodがリクエストを受け入れられるかを確認するReadiness Probeというものも存在します。

www.ianlewis.org

...@controller-0:~$ curl -H "Host: kubernetes.default.svc.cluster.local" -i http://127.0.0.1/healthz
HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Date: Sat, 29 Feb 2020 04:45:44 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 2
Connection: keep-alive
X-Content-Type-Options: nosniff


RBACを使ったKubeletの認可

次に、API Serverに関連してRBACの設定を行います。
API Server (Control Plane) から Kubelet (Worker Node) に対してアクセスできるようにするために、ClusterRole / ClusterRoleBindingを作成します。

system:kube-apiserver-to-kubeletという名前のClusterRoleには、Kubeletに対する基本的な処理を実行するための権限を付与します。

...@controller-0:~$ cat <<EOF | kubectl apply --kubeconfig admin.kubeconfig -f -
> apiVersion: rbac.authorization.k8s.io/v1beta1
> kind: ClusterRole
> metadata:
>   annotations:
>     rbac.authorization.kubernetes.io/autoupdate: "true"
>   labels:
>     kubernetes.io/bootstrapping: rbac-defaults
>   name: system:kube-apiserver-to-kubelet
> rules:
>   - apiGroups:
>       - ""
>     resources:
>       - nodes/proxy
>       - nodes/stats
>       - nodes/log
>       - nodes/spec
>       - nodes/metrics
>     verbs:
>       - "*"
> EOF
clusterrole.rbac.authorization.k8s.io/system:kube-apiserver-to-kubelet created


作成したClusterRoleはkubernetesユーザーに紐づけられます。
これにより、API Serverは--kubelet-client-certificateで指定されたクライアント証明書を利用してkubeletに認証を行います。

...@controller-0:~$ cat <<EOF | kubectl apply --kubeconfig admin.kubeconfig -f -
> apiVersion: rbac.authorization.k8s.io/v1beta1
> kind: ClusterRoleBinding
> metadata:
>   name: system:kube-apiserver
>   namespace: ""
> roleRef:
>   apiGroup: rbac.authorization.k8s.io
>   kind: ClusterRole
>   name: system:kube-apiserver-to-kubelet
> subjects:
>   - apiGroup: rbac.authorization.k8s.io
>     kind: User
>     name: kubernetes
> EOF
clusterrolebinding.rbac.authorization.k8s.io/system:kube-apiserver created


ネットワークロードバランサーのプロビジョニング

最後にAPI Serverの前段に配置するLoad Balancerを作成します。
(個々のコマンドを説明していると長くなってしまうので設定項目などの詳細については、この記事の下部にまとめます。)

{
  KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
    --region $(gcloud config get-value compute/region) \
    --format 'value(address)')

  gcloud compute http-health-checks create kubernetes \
    --description "Kubernetes Health Check" \
    --host "kubernetes.default.svc.cluster.local" \
    --request-path "/healthz"

  gcloud compute firewall-rules create kubernetes-the-hard-way-allow-health-check \
    --network kubernetes-the-hard-way \
    --source-ranges 209.85.152.0/22,209.85.204.0/22,35.191.0.0/16 \
    --allow tcp

  gcloud compute target-pools create kubernetes-target-pool \
    --http-health-check kubernetes

  gcloud compute target-pools add-instances kubernetes-target-pool \
   --instances controller-0,controller-1,controller-2

  gcloud compute forwarding-rules create kubernetes-forwarding-rule \
    --address ${KUBERNETES_PUBLIC_ADDRESS} \
    --ports 6443 \
    --region $(gcloud config get-value compute/region) \
    --target-pool kubernetes-target-pool
}


検証2

Load Balancer (LB) の作成・設定が正常に行われたかを確認するために、LB経由でAPI Requestを実行します。
curlコマンドでCA証明書を指定した上でLoad Balancerが持つPublic IPアドレスクラスタのバージョンを確認するリクエストを投げます。

json形式のresponseが返ってきたら検証成功となります。

~/w/k/h/04 ❯❯❯ KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
  --region $(gcloud config get-value compute/region) \
  --format 'value(address)')

~/w/k/h/04 ❯❯❯ curl --cacert ca.pem https://${KUBERNETES_PUBLIC_ADDRESS}:6443/version
{
  "major": "1",
  "minor": "15",
  "gitVersion": "v1.15.3",
  "gitCommit": "2d3c76f9091b6bec110a5e63777c332469e0cba2",
  "gitTreeState": "clean",
  "buildDate": "2019-08-19T11:05:50Z",
  "goVersion": "go1.12.9",
  "compiler": "gc",
  "platform": "linux/amd64"
}%


(+α) GCPロードバランサーの設定

GCPの勉強がてら、手順の中で実施したロードバランサーの作成手順についても順を追って説明を記載していきます。

Hard wayの手順の中ではロードバランシングを構成する複数のコンポーネントを作成することでロードバランサーを構築します。
AWSのようにリソースとしてロードバランサーを作成する方法ではありません。

下記のコンポーネントの作成・設定によって、ロードバランシングを実現します。

  • バックエンドの設定
  • ホストとパスのルール
  • フロンドエンドの設定

上記のコンポーネントと作業手順を対応させると下記のようになります。

  • バックエンドの設定 → ターゲットプールの作成
  • ホストとパスのルール → フォワーディングルールの作成
  • フロンドエンドの設定 → Public IPアドレスの設定


ここからは実際のコマンドの説明をしていきます。 Public IPアドレスを取得した後に、HTTPのヘルスチェックルールを作成 (gcloud compute http-health-checks create) します。

  • --host: ヘルスチェックの対象となるホスト名 (kubernetes.default.svc.cluster.local)
  • --request-path: ヘルスチェックのパス (/healthz)
  gcloud compute http-health-checks create kubernetes \
    --description "Kubernetes Health Check" \
    --host "kubernetes.default.svc.cluster.local" \
    --request-path "/healthz"


次にFirewallルールを作成 (gcloud compute firewall-rules create) して、インスタンスが属するNetworkに対してLoad Balancerからのヘルスチェックを許可します。

ヘルスチェックのソースIPアドレスの範囲は下記に記載されています。

cloud.google.com

  gcloud compute firewall-rules create kubernetes-the-hard-way-allow-health-check \
    --network kubernetes-the-hard-way \
    --source-ranges 209.85.152.0/22, 209.85.204.0/22, 35.191.0.0/16 \
    --allow tcp


最後に、リクエスト転送先の管理単位であるターゲットプールを作成 (gcloud compute target-pools create / add-instances) します。
その上で、作成したプールにリクエストを転送するようにフォワーディングルールを作成します。

最終的には--address / --portsで指定したIPアドレス(ポート)に対するリクエストをターゲットプールに転送するような処理になります。

cloud.google.com

  KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
    --region $(gcloud config get-value compute/region) \
    --format 'value(address)')
  
  ~~~~~

  gcloud compute target-pools create kubernetes-target-pool \
    --http-health-check kubernetes

  gcloud compute target-pools add-instances kubernetes-target-pool \
   --instances controller-0,controller-1,controller-2

  gcloud compute forwarding-rules create kubernetes-forwarding-rule \
    --address ${KUBERNETES_PUBLIC_ADDRESS} \
    --ports 6443 \
    --region $(gcloud config get-value compute/region) \
    --target-pool kubernetes-target-pool


今回は、Chap8の内容をまとめました。
Control Planeのブートストラップの手順では証明書ファイルの指定やRBACの設定など各コンポーネント間が正常に通信して動作するために必要な設定がされています。

次回のWorker Nodeのブートストラップのチャプターと合わせて、クラスタの全体像をきちんと理解しようと思います。

Kubernetes the hard way (GCP版) #7 (etcdクラスターのブートストラップ)

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

前回の記事はこちら

taxintt.hatenablog.com

Chap7. Bootstrapping the etcd Cluster(etcdクラスターのブートストラップ)

このチャプターでは、Control Planeを構成するコンポーネントの一つであるetcdのクラスターのブートストラップを行います。

etcdバイナリのダウンロードとインストール

このチャプターでの作業はControl Planeの各ノードにsshでログインした上で行います。
(筆者はtmuxを使わずに、各ノードごとにターミナルのウィンドウを開いて作業をしました。)

最初にetcd のバイナリをダウンロードします。
(CLIツールであるetcdctlも含まれているので、別でインストールを行う必要はありません。)

wget -q --show-progress --https-only --timestamping \
  "https://github.com/etcd-io/etcd/releases/download/v3.4.0/etcd-v3.4.0-linux-amd64.tar.gz"


バイナリを解凍して、etcdの本体とetcdctl (CLIツール)を/usr/local/binに移動することでセットアップ前の準備は完了となります。

{
  tar -xvf etcd-v3.4.0-linux-amd64.tar.gz
  sudo mv etcd-v3.4.0-linux-amd64/etcd* /usr/local/bin/
}


etcdサーバーの設定

バイナリのダウンロードが完了したら、etcdサーバーの設定を行っていきます。

最初に、ノード内で必要なディレクトリ (/etc/etcd, /var/lib/etcd) を作成して、Chap4で作成したAPI Server用の証明書ファイル類を配置します。

taxintt.hatenablog.com

...@controller-0:~$ {
>   sudo mkdir -p /etc/etcd /var/lib/etcd
>   sudo cp ca.pem kubernetes-key.pem kubernetes.pem /etc/etcd/
> }


次に、Control Plane(インスタンス)のPrivate IPとホスト名を変数として取得します。

インスタンスメタデータに関しては、メタデータサーバーから取得できるようになっています。
インスタンスからメタデータサーバへのAPIアクセスに関しては、追加で認証処理を行うことなくメタデータを取得することが可能です。

cloud.google.com

...@controller-0:~$ INTERNAL_IP=$(curl -s -H "Metadata-Flavor: Google" \
>   http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ip)
...@controller-0:~$ echo $INTERNAL_IP
10.240.0.10

...@controller-0:~$ hostname -s
controller-0
...@controller-0:~$ ETCD_NAME=$(hostname -s)


最後にetcdをサービスとして登録するために、etcdのサービスファイル (etcd.service) を作成します。
先ほど取得したノードのPrivate IPとホスト名はここで利用します。

他にも下記のような項目が設定されています。

  • --client-cert-auth: etcdへの通信時TLSクライアント認証を有効化する
  • --peer-client-cert-auth: ピア同士の通信時にTLSクライアント認証を有効化する
  • --cert-file / --key-file: etcdへの通信時に利用するTLS証明書ファイル・秘密鍵ファイル
  • --peer-cert-file / --peer-key-file: ピア同士の通信に利用されるTLS証明書ファイル・秘密鍵ファイル
  • --listen-client-urls (port: 2379) : etcdへの通信時に接続を受け付けるURL
  • --listen-peer-urls (port: 2380) : ピア同士の通信時に接続を受け付けるURL
  • --advertise-client-urls (port: 2379) : etcdへの通信時に外部に広告するURL
  • --initial-advertise-peer-urls (port: 2380) : ピア同士の通信時に全てのピアに広告するURL

上記の設定項目に関する説明は下記のリンクに記載されています。

github.com

...@controller-0:~$ cat <<EOF | sudo tee /etc/systemd/system/etcd.service
> [Unit]
> Description=etcd
> Documentation=https://github.com/coreos
>
> [Service]
> Type=notify
> ExecStart=/usr/local/bin/etcd \\
>   --name ${ETCD_NAME} \\
>   --cert-file=/etc/etcd/kubernetes.pem \\
>   --key-file=/etc/etcd/kubernetes-key.pem \\
>   --peer-cert-file=/etc/etcd/kubernetes.pem \\
>   --peer-key-file=/etc/etcd/kubernetes-key.pem \\
>   --trusted-ca-file=/etc/etcd/ca.pem \\
>   --peer-trusted-ca-file=/etc/etcd/ca.pem \\
>   --peer-client-cert-auth \\
>   --client-cert-auth \\
>   --initial-advertise-peer-urls https://${INTERNAL_IP}:2380 \\
>   --listen-peer-urls https://${INTERNAL_IP}:2380 \\
>   --listen-client-urls https://${INTERNAL_IP}:2379,https://127.0.0.1:2379 \\
>   --advertise-client-urls https://${INTERNAL_IP}:2379 \\
>   --initial-cluster-token etcd-cluster-0 \\
>   --initial-cluster controller-0=https://10.240.0.10:2380,controller-1=https://10.240.0.11:2380,controller-2=https://10.240.0.12:2380 \\
>   --initial-cluster-state new \\
>   --data-dir=/var/lib/etcd
> Restart=on-failure
> RestartSec=5
>
> [Install]
> WantedBy=multi-user.target
> EOF
[Unit]
Description=etcd
Documentation=https://github.com/coreos

[Service]
Type=notify
...
RestartSec=5

[Install]
WantedBy=multi-user.target

etcdサーバーの起動

サービスファイルの作成が完了したら、systemctlコマンドでetcdのサービスを起動します。
作成したサービスファイルをsystemdに反映させるために、daemon-reloadコマンドを実行してからサービスを起動します。

enakai00.hatenablog.com

{
  sudo systemctl daemon-reload
  sudo systemctl enable etcd
  sudo systemctl start etcd
}


注意点として、etcdのメンバーを単独で起動するとサービスの起動は失敗します。
サービスのステータス確認時にfailedの状態になった場合は、systemctl reset-failedコマンドで異常状態のリセットを行います。

dev.classmethod.jp

...@controller-0:~$ {
>   sudo systemctl daemon-reload
>   sudo systemctl enable etcd
>   sudo systemctl start etcd
> }
Created symlink /etc/systemd/system/multi-user.target.wants/etcd.service → /etc/systemd/system/etcd.service.

Job for etcd.service failed because a timeout was exceeded.
See "systemctl status etcd.service" and "journalctl -xe" for details.

...@controller-0:~$ journalctl -xe
Feb 29 03:58:45 controller-0 etcd[3667]: raft2020/02/29 03:58:45 INFO: f98dc20bce6225a0 became candidate at term 85
Feb 29 03:58:45 controller-0 etcd[3667]: raft2020/02/29 03:58:45 INFO: f98dc20bce6225a0 received MsgVoteResp from f98dc20bce6225a0 at term 85
Feb 29 03:58:45 controller-0 etcd[3667]: raft2020/02/29 03:58:45 INFO: f98dc20bce6225a0 [logterm: 1, index: 3] sent MsgVote request to 3a57933972cb5131 at term 85
Feb 29 03:58:45 controller-0 etcd[3667]: raft2020/02/29 03:58:45 INFO: f98dc20bce6225a0 [logterm: 1, index: 3] sent MsgVote request to ffed16798470cab5 at term 85
Feb 29 03:58:47 controller-0 etcd[3667]: raft2020/02/29 03:58:47 INFO: f98dc20bce6225a0 is starting a new election at term 85
Feb 29 03:58:47 controller-0 etcd[3667]: raft2020/02/29 03:58:47 INFO: f98dc20bce6225a0 became candidate at term 86
Feb 29 03:58:47 controller-0 etcd[3667]: raft2020/02/29 03:58:47 INFO: f98dc20bce6225a0 received MsgVoteResp from f98dc20bce6225a0 at term 86
Feb 29 03:58:47 controller-0 etcd[3667]: raft2020/02/29 03:58:47 INFO: f98dc20bce6225a0 [logterm: 1, index: 3] sent MsgVote request to 3a57933972cb5131 at term 86
Feb 29 03:58:47 controller-0 etcd[3667]: raft2020/02/29 03:58:47 INFO: f98dc20bce6225a0 [logterm: 1, index: 3] sent MsgVote request to ffed16798470cab5 at term 86
Feb 29 03:58:48 controller-0 etcd[3667]: raft2020/02/29 03:58:48 INFO: f98dc20bce6225a0 is starting a new election at term 86
Feb 29 03:58:48 controller-0 etcd[3667]: raft2020/02/29 03:58:48 INFO: f98dc20bce6225a0 became candidate at term 87
Feb 29 03:58:48 controller-0 etcd[3667]: raft2020/02/29 03:58:48 INFO: f98dc20bce6225a0 received MsgVoteResp from f98dc20bce6225a0 at term 87
Feb 29 03:58:48 controller-0 etcd[3667]: raft2020/02/29 03:58:48 INFO: f98dc20bce6225a0 [logterm: 1, index: 3] sent MsgVote request to 3a57933972cb5131 at term 87
Feb 29 03:58:48 controller-0 etcd[3667]: raft2020/02/29 03:58:48 INFO: f98dc20bce6225a0 [logterm: 1, index: 3] sent MsgVote request to ffed16798470cab5 at term 87

// 単独でサービスを起動しているため、他のメンバーへの通信に失敗する
Feb 29 03:58:49 controller-0 etcd[3667]: health check for peer 3a57933972cb5131 could not connect: dial tcp 10.240.0.12:2380: connect: connection refused
Feb 29 03:58:49 controller-0 etcd[3667]: health check for peer ffed16798470cab5 could not connect: dial tcp 10.240.0.11:2380: connect: connection refused
Feb 29 03:58:49 controller-0 etcd[3667]: health check for peer ffed16798470cab5 could not connect: dial tcp 10.240.0.11:2380: connect: connection refused
Feb 29 03:58:49 controller-0 etcd[3667]: health check for peer 3a57933972cb5131 could not connect: dial tcp 10.240.0.12:2380: connect: connection refused

検証

全てのControl Planeでetcdのセットアップを実行してから、正常にクラスターが構成されているか検証を行います。
検証では、etcdctlを利用してetcdクラスターのメンバー一覧を取得します。

導入したetcdのバージョンがv3.4.0以下の場合は、環境変数をセットする必要があります。(ETCDCTL_API=3)

github.com

...@controller-1:~$ sudo ETCDCTL_API=3 etcdctl member list \
>   --endpoints=https://127.0.0.1:2379 \
>   --cacert=/etc/etcd/ca.pem \
>   --cert=/etc/etcd/kubernetes.pem \
>   --key=/etc/etcd/kubernetes-key.pem
3a57933972cb5131, started, controller-2, https://10.240.0.12:2380, https://10.240.0.12:2379, false
f98dc20bce6225a0, started, controller-0, https://10.240.0.10:2380, https://10.240.0.10:2379, false
ffed16798470cab5, started, controller-1, https://10.240.0.11:2380, https://10.240.0.11:2379, false


正常にetcdクラスターのメンバー一覧を取得できたので、etcdクラスタのセットアップは完了となります。

今回は、Chap7の内容をまとめました。
etcdで利用されている分散合意アルゴリズム (Raft) については関連リンクだけ記載しておきます。

qiita.com

Raft(分散合意アルゴリズム)について · GitHub

Kubernetes the hard way (GCP版) #6 (データ暗号化の設定とキーの生成)

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

前回の記事はこちら。

taxintt.hatenablog.com

Chap6. Generating the Data Encryption Config and Key(データ暗号化の設定とキーの生成)

このチャプターでは、etcdの暗号化のために使用する設定ファイルと暗号化キーを利用します。
(作成したconfigファイルはChap8.でAPI Serverの設定時に利用されます。)

暗号化キーの作成

最初にetcdの暗号化のベースとなる暗号化キーを作成します。
ざっくり説明するとランダムな文字列 (32byte) を生成して、それをbase64エンコードしたものを暗号化キーとして利用します。

~/w/k/h/04 ❯❯❯ ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)
~/w/k/h/04 ❯❯❯ echo $ENCRYPTION_KEY
AOSngvXL8pGASyTzG0AqvNzPL6IYk3Spg+Jlhd+pI0Q=


最初のheadコマンドで指定している/dev/urandomはランダムな文字列を出力する擬似デバイスファイルの一種です。
同じようにランダムな文字列を出力するデバイスファイルとしてdev/randomも存在します。

oplern.hatenablog.com

暗号化設定ファイルの作成

次に、暗号化設定ファイルであるEncryptionConfigを作成します。
resources.providersの部分では暗号化アルゴリズムの一つであるAES-CBCを指定しています。

KubernetesのDocumentの中でも、暗号強度の観点からAES-CBC (aescbc)の利用が推奨となっています。

The recommended choice for encryption at rest but may be slightly slower than secretbox

kubernetes.io

~/w/k/h/04 ❯❯❯ cat > encryption-config.yaml <<EOF
kind: EncryptionConfig
apiVersion: v1
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: ${ENCRYPTION_KEY}
      - identity: {}
EOF


EncryptionConfigを作成したら、各Control Planeに配布していきます。

~/w/k/h/04 ❯❯❯ for instance in controller-0 controller-1 controller-2; do
  gcloud compute scp encryption-config.yaml ${instance}:~/
done
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
encryption-config.yaml                                                                                                                                 100%  240     0.7KB/s   00:00
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
encryption-config.yaml                                                                                                                                 100%  240     2.4KB/s   00:00
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
encryption-config.yaml

(+α) etcdの暗号化キーについて

etcdの暗号化キーに関連する内容を2点補足しておきます。

暗号化方式 (共通鍵暗号化方式)について

暗号化と復号に同じ鍵を利用する方式のことを共通鍵暗号化方式を呼び、この方式の暗号化キーを用いてetcdを暗号化します。
(共通鍵暗号で使用する暗号化アルゴリズムの一つとしてAESがあります。)

ちなみにAPI Serverとの暗号化通信(TLS)では、公開鍵暗号方式によって共通鍵の交換を行った上で暗号化通信は共通鍵暗号化方式で行います

milestone-of-se.nesuke.com

暗号化アルゴリズム (AES-CBC with PKCS#7 padding)について

暗号化設定ファイルの中では、暗号化アルゴリズムとしてaescbc (AES-CBC with PKCS#7 padding)が指定されています。

最初に暗号化アルゴリズムとして、ブロック暗号の一種であるAESが指定されています。
平文をある単位のブロックに分割して暗号化する方式をブロック暗号と呼び、AESでは128bit (=16byte)単位のブロックに分割します。

また、ブロック暗号のモードの一つであるCBCも指定されています。
平文の各ブロックは前の暗号文とのXORを取ってから暗号化されます。(平文の最初のブロックに関しては「前の暗号文」が存在しないので、初期化ベクトルと呼ばれるものを利用します。)

ja.wikipedia.org

先ほども記載した通りAES暗号はブロック方式のため、16の倍数byteの平文を暗号化します。
そのため、16の倍数byteに満たない場合は16の倍数byteになるようにデータを追加 (padding) する必要があります。

そのpaddingの方式の一つとしてPKCS#7 paddingがあり、こちらもaescbcを指定することで自動的に指定されています。

qiita.com

設定時には、ブロック暗号のモードやpaddingの方式などユーザー側で変更することはできないので注意が必要です。

今回は、Chap6の内容をまとめました。

Kubernetes the hard way (GCP版) #5 (認証用Kubernetes設定ファイルの作成)

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

前回の記事はこちら。

taxintt.hatenablog.com

Chap5. Generating Kubernetes Configuration Files for Authentication(認証用Kubernetes設定ファイルの作成)

このチャプターではChap4で作成した証明書を利用して、接続設定ファイルとして利用するkubeconfigファイルを作成します。

クライアント認証用のkubeconfigファイルの作成

controller managerkubeletkube-proxyscheduler・adminユーザー用のkubeconfigファイルを作成します。

最初に外部公開用のIPアドレスKUBERNETES_PUBLIC_ADDRESSとして取得します。
API Serverの前段に配置するELBで利用するIPアドレスをkubeconfig内で指定することで、Control PlaneのHA構成にも対応することが可能です。

(完全に余談ですが、Kubernetesクラスタの構築ツールの一つであるkubeadmでもHA構成のクラスタを作成できるようです。)

kubernetes.io

~/w/k/h/04 ❯❯❯ KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
      --region $(gcloud config get-value compute/region) \
      --format 'value(address)')
~/w/k/h/04 ❯❯❯ echo $KUBERNETES_PUBLIC_ADDRESS
    35.233.237.51


次に、kubelet用のkubeconfigファイルを作成します。
各ノードごとにファイル名は${instance}.kubeconfigとして、kubeconfigファイルにクラスタに関する設定を追加していきます。

~/w/k/h/04 ❯❯❯ for instance in worker-0 worker-1 worker-2; do
  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://${KUBERNETES_PUBLIC_ADDRESS}:6443 \
    --kubeconfig=${instance}.kubeconfig

  kubectl config set-credentials system:node:${instance} \
    --client-certificate=${instance}.pem \
    --client-key=${instance}-key.pem \
    --embed-certs=true \
    --kubeconfig=${instance}.kubeconfig

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=system:node:${instance} \
    --kubeconfig=${instance}.kubeconfig

  kubectl config use-context default --kubeconfig=${instance}.kubeconfig
done
Cluster "kubernetes-the-hard-way" set.
User "system:node:worker-0" set.
Context "default" created.
Switched to context "default".
Cluster "kubernetes-the-hard-way" set.
User "system:node:worker-1" set.
Context "default" created.
Switched to context "default".
Cluster "kubernetes-the-hard-way" set.
User "system:node:worker-2" set.
Context "default" created.
Switched to context "default".


引数や利用するファイルなどは変わりますが、同じコマンドを実行するため実行される各コマンドについて説明しておきます。

最初にkubectl config set-clusterコマンドでkubeconfigファイルにクラスタの情報を追加します。(下記の情報を引数で渡します。)

  • --certificate-authority: 作成したCA証明書ファイル (ca.pem)
  • --server: https://${KUBERNETES_PUBLIC_ADDRESS}:6443 (外部公開用のIPアドレスを用いたURL) を指定

kubeletの通信相手はControl PlaneのAPI Serverとなるため、--serverの部分ではAPI Serverの前段に配置予定のELB用のIPアドレスを用いたURLを指定します。

  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://${KUBERNETES_PUBLIC_ADDRESS}:6443 \
    --kubeconfig=${instance}.kubeconfig


次に作成したクライアント証明書と鍵ファイルを指定して、kubectl config set-credentialsコマンドでkubeconfigファイルにクライアント証明書の情報を追加します。

  • --client-certificate: 作成したkubelet用のクライアント証明書 ( e.g.worker-0.pem)
  • --client-key: 作成したkubelet用のクライアント証明書の秘密鍵 ( e.g.worker-0-key.pem)
  kubectl config set-credentials system:node:${instance} \
    --client-certificate=${instance}.pem \
    --client-key=${instance}-key.pem \
    --embed-certs=true \
    --kubeconfig=${instance}.kubeconfig


最後に、kubectl config set-context defaultコマンドでクラスタのアクセス先情報であるcontextを作成します。

  • --cluster: kubeconfig内で指定するクラスタ名(kubernetes-the-hard-way)
  • --user: クライアント証明書内で指定したCN(Common Name) ( e.g.node:worker-0)

--userで指定するユーザー名は基本的に作成した証明書で指定したCNと同じ名前となります。

kubernetes.io

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=system:node:${instance} \
    --kubeconfig=${instance}.kubeconfig


次に、kube-proxy用のkubeconfigファイルを作成します。
各ノードごとにファイル名はkube-proxy.kubeconfigとして、先ほどと同じように設定を追加していきます。

同じコマンドを実行していますが、下記の引数は理解しておく必要があると思います。

  • --server: kube-proxyの通信相手は同じノード内のAPI Serverであるため、https://${KUBERNETES_PUBLIC_ADDRESS}:6443を指定
  • --user: クライアント証明書内で指定したCN(Common Name) であるsystem:kube-proxyを指定
~/w/k/h/04 ❯❯❯ {
  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://${KUBERNETES_PUBLIC_ADDRESS}:6443 \
    --kubeconfig=kube-proxy.kubeconfig

  kubectl config set-credentials system:kube-proxy \
    --client-certificate=kube-proxy.pem \
    --client-key=kube-proxy-key.pem \
    --embed-certs=true \
    --kubeconfig=kube-proxy.kubeconfig

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=system:kube-proxy \
    --kubeconfig=kube-proxy.kubeconfig

  kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig
}
Cluster "kubernetes-the-hard-way" set.
User "system:kube-proxy" set.
Context "default" created.
Switched to context "default".


同様に、kube-scheduler・kube-controller-manager用のkubeconfigファイルを作成します。
Control Planeで利用するkubeconfigファイルを作成して、先ほどと同じように設定を追加していきます。

一部の引数について説明を記載しておきます。

  • --server: 2つのコンポーネントの通信相手は同じノード内のAPI Serverとなるため、ループバックアドレスを用いてhttps://127.0.0.1:6443を指定
  • --user: クライアント証明書内で指定したCN(Common Name) を指定 (system:kube-controller-manager, system:kube-scheduler)

qiita.com

~/w/k/h/04 ❯❯❯ {
  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://127.0.0.1:6443 \
    --kubeconfig=kube-controller-manager.kubeconfig

  kubectl config set-credentials system:kube-controller-manager \
    --client-certificate=kube-controller-manager.pem \
    --client-key=kube-controller-manager-key.pem \
    --embed-certs=true \
    --kubeconfig=kube-controller-manager.kubeconfig

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=system:kube-controller-manager \
    --kubeconfig=kube-controller-manager.kubeconfig

  kubectl config use-context default --kubeconfig=kube-controller-manager.kubeconfig
}
Cluster "kubernetes-the-hard-way" set.
User "system:kube-controller-manager" set.
Context "default" created.
Switched to context "default".

~/w/k/h/04 ❯❯❯ {
  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://127.0.0.1:6443 \
    --kubeconfig=kube-scheduler.kubeconfig

  kubectl config set-credentials system:kube-scheduler \
    --client-certificate=kube-scheduler.pem \
    --client-key=kube-scheduler-key.pem \
    --embed-certs=true \
    --kubeconfig=kube-scheduler.kubeconfig

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=system:kube-scheduler \
    --kubeconfig=kube-scheduler.kubeconfig

  kubectl config use-context default --kubeconfig=kube-scheduler.kubeconfig
}
Cluster "kubernetes-the-hard-way" set.
User "system:kube-scheduler" set.
Context "default" created.
Switched to context "default".


最後に、adminユーザー用のkubeconfigファイルを作成します。
このkubeconfigファイルはクラスタと通信するクライアント側で利用するkubeconfigファイルではないので注意が必要です。
(Chap8. でControl Plane内で各種設定を行う際に利用する用のconfigファイルだと考えられます。)

一部の引数について説明を記載しておきます。

  • --server: Control Plane内で利用するkubeconfigファイルなので、ループバックアドレスを用いてhttps://127.0.0.1:6443を指定
  • --user: クライアント証明書内で指定したCN(Common Name) を指定 (admin)
w/k/h/04 ❯❯❯ {
  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://127.0.0.1:6443 \
    --kubeconfig=admin.kubeconfig

  kubectl config set-credentials admin \
    --client-certificate=admin.pem \
    --client-key=admin-key.pem \
    --embed-certs=true \
    --kubeconfig=admin.kubeconfig

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=admin \
    --kubeconfig=admin.kubeconfig

  kubectl config use-context default --kubeconfig=admin.kubeconfig
}
Cluster "kubernetes-the-hard-way" set.
User "admin" set.
Context "default" created.
Switched to context "default".


kubeconfigファイルの作成が完了したら、コンポーネントが存在するNodeにファイルを配布していきます。

w/k/h/04 ❯❯❯ for instance in worker-0 worker-1 worker-2; do
  gcloud compute scp ${instance}.kubeconfig kube-proxy.kubeconfig ${instance}:~/
done
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
worker-0.kubeconfig                                                                                                                                    100% 6383    59.0KB/s   00:00
kube-proxy.kubeconfig                                                                                                                                  100% 6321    65.0KB/s   00:00
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
worker-1.kubeconfig                                                                                                                                    100% 6383    31.4KB/s   00:00
kube-proxy.kubeconfig                                                                                                                                  100% 6321    61.9KB/s   00:00
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
worker-2.kubeconfig                                                                                                                                    100% 6383    61.1KB/s   00:00
kube-proxy.kubeconfig                                                                                                                                  100% 6321    60.4KB/s   00:00

~/w/k/h/04 ❯❯❯ for instance in controller-0 controller-1 controller-2; do
  gcloud compute scp admin.kubeconfig kube-controller-manager.kubeconfig kube-scheduler.kubeconfig ${instance}:~/
done
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
admin.kubeconfig                                                                                                                                       100% 6265    61.8KB/s   00:00
kube-controller-manager.kubeconfig                                                                                                                     100% 6387    57.8KB/s   00:00
kube-scheduler.kubeconfig                                                                                                                              100% 6337    59.1KB/s   00:00
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
admin.kubeconfig                                                                                                                                       100% 6265    22.8KB/s   00:00
kube-controller-manager.kubeconfig                                                                                                                     100% 6387    60.8KB/s   00:00
kube-scheduler.kubeconfig                                                                                                                              100% 6337    63.9KB/s   00:00
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
admin.kubeconfig                                                                                                                                       100% 6265    52.7KB/s   00:00
kube-controller-manager.kubeconfig                                                                                                                     100% 6387    58.5KB/s   00:00
kube-scheduler.kubeconfig                                                                                                                              100% 6337    64.2KB/s   00:00
~/w/k/h/04 ❯❯❯


今回は、Chap5の内容をざっくりまとめました。
他に参考にした資料のリンクはこちらです。

Kubernetesのユーザー管理と認証・権限確認機構を理解しよう | さくらのナレッジ

Kubernetes the hard way (GCP版) #4 (CAのプロビジョニングとTLS証明書の生成)

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

前回の記事はこちら。 taxintt.hatenablog.com

Chap4. Provisioning a CA and Generating TLS Certificates (CAのプロビジョニングとTLS証明書の生成)

このチャプターでは、Kubernetesクラスター内で利用するTLS証明書の作成を行います。
認証局(CA)の構成ファイル・CA証明書を作成し、それらを利用してTLS証明書を作成するという流れとなっています。

先に、ハンズオンの手順を一通り記載した後に解説を記載していきます。

CAのプロビジョニング

最初にTLS証明書の作成に必要な認証局(CA)の構成ファイルを作成します。
構成ファイルの中では、証明書の利用用途と有効期限をセットした証明書発行に利用するプロファイル(Kubernetes)が定義されています。

github.com

TLS証明書の作成時には、作成した構成ファイルとプロファイル(Kubernetes)を指定します。

// 認証局(CA)の構成ファイルを作成する
~/w/k/h/04 ❯❯❯ {

cat > ca-config.json <<EOF
{
  "signing": {
    "default": {
      "expiry": "8760h"
    },
    "profiles": {
      "kubernetes": {
        "usages": ["signing", "key encipherment", "server auth", "client auth"],
        "expiry": "8760h"
      }
    }
  }
}
EOF


次に、CA証明書とCAの秘密鍵の作成を行います。 証明書の作成に必要なCSR (証明書署名要求) を作成した上で、CSRを基に証明書と秘密鍵を作成します。

// CSRを作成する
cat > ca-csr.json <<EOF
{
  "CN": "Kubernetes", 
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "Kubernetes",
      "OU": "CA",
      "ST": "Oregon"
    }
  ]
}
EOF

// CSRを基にCA証明書とCAの秘密鍵を生成する
cfssl gencert -initca ca-csr.json | cfssljson -bare ca

}
2020/02/29 11:00:07 [INFO] generating a new CA key and certificate from CSR
2020/02/29 11:00:07 [INFO] generate received request
2020/02/29 11:00:07 [INFO] received CSR
2020/02/29 11:00:07 [INFO] generating key: rsa-2048
2020/02/29 11:00:07 [INFO] encoded CSR
2020/02/29 11:00:07 [INFO] signed certificate with serial number 254290516728701407670608002579038214846567871824

// CA証明書(ca.pem)とCAの秘密鍵(ca-key.pem)が生成されている
~/w/k/h/04 ❯❯❯ ls
ca-config.json ca-csr.json    ca-key.pem     ca.csr         ca.pem

TLS証明書の作成

認証局(CA)の構成ファイル、CA証明書、CAの秘密鍵を基に各種証明書を作成します。
作成する証明書は下記の通りです。

  • adminユーザー用クライアント証明書
  • Kubelet用クライアント証明書
  • Controller Manager用クライアント証明書
  • Kube Proxy用クライアント証明書
  • Scheduler用クライアント証明書
  • KubernetesAPI Server用証明書
  • サービスアカウントのキーペア

CSR (証明書署名要求) を作成し、それを基にTLS証明書と秘密鍵を作成する部分は先程の手順と同じです。
これ以降は、認証局(CA)の構成ファイル、CA証明書と秘密鍵を指定して証明書の作成を行います。

最初に、adminユーザー用のクライアント証明書を作成します。
特記事項は下記の通りです。

  • CSRの識別名(DN)の一つであるOrganizationでsystem:mastersを指定する
~/w/k/h/04 ❯❯❯ {

cat > admin-csr.json <<EOF
{
  "CN": "admin",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:masters",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

// クライアント証明書の作成時にCAの証明書・秘密鍵・構成ファイルを引数として渡している
cfssl gencert \
  -ca=ca.pem \ 
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes \
  admin-csr.json | cfssljson -bare admin

}
2020/02/29 11:13:46 [INFO] generate received request
2020/02/29 11:13:46 [INFO] received CSR
2020/02/29 11:13:46 [INFO] generating key: rsa-2048
2020/02/29 11:13:47 [INFO] encoded CSR
2020/02/29 11:13:47 [INFO] signed certificate with serial number 679971876887522464623843539440976113444619121269
2020/02/29 11:13:47 [WARNING] This certificate lacks a "hosts" field. This makes it unsuitable for
websites. For more information see the Baseline Requirements for the Issuance and Management
of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser Forum (https://cabforum.org);
specifically, section 10.2.3 ("Information Requirements").

//admin-key.pem, admin.pemが作成されている
~/w/k/h/04 ❯❯❯ ls
admin-csr.json admin-key.pem  admin.csr      admin.pem      ca-config.json ca-csr.json    ca-key.pem     ca.csr         ca.pem


次に、Kubelet用のクライアント証明書を作成します。 特記事項は下記の通りです。

  • 識別名(DN)のOrganizationとしてsystem:nodesを指定する
  • API Server側でNode Authorizerを利用したリクエストの許可設定を行うために、証明書のCommon Name(CN)としてsystem:node:${instance}を指定する
  • hostnameとしてノードのホスト名(${instance})とIP(${INTERNAL_IP})、(Chap3で作成した)外部公開用のIPアドレス(${EXTERNAL_IP})を渡している


qiita.com

Kubeletは(API Serverに対しての)クライアントでもありますが、サーバとしての役割も果たすため-hostnameで複数のホスト名を渡します。
これにより、証明書にはCNの別名であるSANs(Subject Alternative Names)が指定されます。

また、-profile=kubernetesKubernetesのプロファイルからusageを参照することで、作成した証明書がクライアント兼サーバ証明書として利用できるようになってると考えられます。

~/w/k/h/04 ❯❯❯ for instance in worker-0 worker-1 worker-2; do
cat > ${instance}-csr.json <<EOF
{
  "CN": "system:node:${instance}",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:nodes",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

EXTERNAL_IP=$(gcloud compute instances describe ${instance} \
  --format 'value(networkInterfaces[0].accessConfigs[0].natIP)')

INTERNAL_IP=$(gcloud compute instances describe ${instance} \
  --format 'value(networkInterfaces[0].networkIP)')

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -hostname=${instance},${EXTERNAL_IP},${INTERNAL_IP} \
  -profile=kubernetes \
  ${instance}-csr.json | cfssljson -bare ${instance}
done
2020/02/29 11:16:58 [INFO] generate received request
2020/02/29 11:16:58 [INFO] received CSR
2020/02/29 11:16:58 [INFO] generating key: rsa-2048
2020/02/29 11:16:58 [INFO] encoded CSR
2020/02/29 11:16:58 [INFO] signed certificate with serial number 359886775191352144581584441997016046901027647498
2020/02/29 11:17:01 [INFO] generate received request
2020/02/29 11:17:01 [INFO] received CSR
2020/02/29 11:17:01 [INFO] generating key: rsa-2048
2020/02/29 11:17:01 [INFO] encoded CSR
2020/02/29 11:17:01 [INFO] signed certificate with serial number 496107270789966248075618951568412965762056311780
2020/02/29 11:17:04 [INFO] generate received request
2020/02/29 11:17:04 [INFO] received CSR
2020/02/29 11:17:04 [INFO] generating key: rsa-2048
2020/02/29 11:17:04 [INFO] encoded CSR
2020/02/29 11:17:04 [INFO] signed certificate with serial number 356879757777462442721200413749147882778190289592

// 各ノード用の証明書(worker-X.pem)と秘密鍵(worker-X-key.pem)が作成されている
~/w/k/h/04 ❯❯❯ ls
admin-csr.json    admin.pem         ca-key.pem        worker-0-csr.json worker-0.pem      worker-1.csr      worker-2-key.pem
admin-key.pem     ca-config.json    ca.csr            worker-0-key.pem  worker-1-csr.json worker-1.pem      worker-2.csr
admin.csr         ca-csr.json       ca.pem            worker-0.csr      worker-1-key.pem  worker-2-csr.json worker-2.pem


Controller Manager〜Scheduler用のクライアント証明書の作成部分は省略します。
(adminユーザー用のクライアント証明書の作成と同じことをしているため)

次に、KubernetesAPI Server用の証明書を作成します。
Kubelet用のクライアント証明書同様、-hostnameでSANsを指定してTLS証明書を作成しています。

特記事項は下記の通りです。

Kubernetesクラスタを作成するとデフォルトでServiceが作成されていますが、その正体はAPI Servier用のService(Cluster IP)です。
内部DNSでは、kubernetes.defaultのホスト名でAレコードが登録されるため、それらのホスト名も指定する必要があります。

kubernetes.io

また、クラスタ内で利用するホスト名(${KUBERNETES_HOSTNAMES})については、別で作成予定のKubernetesのネットワークに関する記事の中で調べた内容をまとめる予定です。

~/w/k/h/04 ❯❯❯ {

KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
  --region $(gcloud config get-value compute/region) \
  --format 'value(address)')

KUBERNETES_HOSTNAMES=kubernetes,kubernetes.default,kubernetes.default.svc,kubernetes.default.svc.cluster,kubernetes.svc.cluster.local

cat > kubernetes-csr.json <<EOF
{
  "CN": "kubernetes",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "Kubernetes",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -hostname=10.32.0.1,10.240.0.10,10.240.0.11,10.240.0.12,${KUBERNETES_PUBLIC_ADDRESS},127.0.0.1,${KUBERNETES_HOSTNAMES} \
  -profile=kubernetes \
  kubernetes-csr.json | cfssljson -bare kubernetes

}
2020/02/29 11:25:41 [INFO] generate received request
2020/02/29 11:25:41 [INFO] received CSR
2020/02/29 11:25:41 [INFO] generating key: rsa-2048
2020/02/29 11:25:41 [INFO] encoded CSR
2020/02/29 11:25:41 [INFO] signed certificate with serial number 603669054680267544191086535903497288645234008053


最後に、サービスアカウントに必要なキーペアを作成します。
Controller Manager(正確にはToken Controller)では、サービスアカウントのAPIリクエストに必要なトークンの生成を行います。
その際に作成したここで作成するキーペアを使用して、サービスアカウント用のトークンを生成して署名します。

qiita.com

~/w/k/h/04 ❯❯❯ {

cat > service-account-csr.json <<EOF
{
  "CN": "service-accounts",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "Kubernetes",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes \
  service-account-csr.json | cfssljson -bare service-account

}
2020/02/29 11:26:20 [INFO] generate received request
2020/02/29 11:26:20 [INFO] received CSR
2020/02/29 11:26:20 [INFO] generating key: rsa-2048
2020/02/29 11:26:20 [INFO] encoded CSR
2020/02/29 11:26:20 [INFO] signed certificate with serial number 372200916003664381145141033359158655751518600952
2020/02/29 11:26:20 [WARNING] This certificate lacks a "hosts" field. This makes it unsuitable for
websites. For more information see the Baseline Requirements for the Issuance and Management
of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser Forum (https://cabforum.org);
specifically, section 10.2.3 ("Information Requirements").


作成が完了した証明書は各ノードに配布します。
(CA証明書を各ノードに配布している理由については後述します。)

~/w/k/h/04 ❯❯❯ for instance in worker-0 worker-1 worker-2; do
  gcloud compute scp ca.pem ${instance}-key.pem ${instance}.pem ${instance}:~/
done
Warning: Permanently added 'compute.7526976075834096736' (ECDSA) to the list of known hosts.
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
ca.pem                                                                                                                                                 100% 1318    13.7KB/s   00:00
worker-0-key.pem                                                                                                                                       100% 1675    16.2KB/s   00:00
worker-0.pem                                                                                                                                           100% 1493    14.5KB/s   00:00
Warning: Permanently added 'compute.6311120260820139134' (ECDSA) to the list of known hosts.
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
ca.pem                                                                                                                                                 100% 1318    12.1KB/s   00:00
worker-1-key.pem                                                                                                                                       100% 1675    15.8KB/s   00:00
worker-1.pem                                                                                                                                           100% 1493    11.5KB/s   00:00
Warning: Permanently added 'compute.3606445772456098939' (ECDSA) to the list of known hosts.
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
ca.pem                                                                                                                                                 100% 1318    12.2KB/s   00:00
worker-2-key.pem                                                                                                                                       100% 1675    16.4KB/s   00:00
worker-2.pem          

~/w/k/h/04 ❯❯❯ for instance in controller-0 controller-1 controller-2; do
  gcloud compute scp ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem \
    service-account-key.pem service-account.pem ${instance}:~/
done
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
ca.pem                                                                                                                                                 100% 1318    12.9KB/s   00:00
ca-key.pem                                                                                                                                             100% 1675    16.6KB/s   00:00
kubernetes-key.pem                                                                                                                                     100% 1679    16.9KB/s   00:00
kubernetes.pem                                                                                                                                         100% 1663    16.5KB/s   00:00
service-account-key.pem                                                                                                                                100% 1679    15.7KB/s   00:00
service-account.pem                                                                                                                                    100% 1440    13.7KB/s   00:00
Warning: Permanently added 'compute.3758247453048208529' (ECDSA) to the list of known hosts.
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
ca.pem                                                                                                                                                 100% 1318    13.6KB/s   00:00
ca-key.pem                                                                                                                                             100% 1675    17.3KB/s   00:00
kubernetes-key.pem                                                                                                                                     100% 1679    17.5KB/s   00:00
kubernetes.pem                                                                                                                                         100% 1663    15.9KB/s   00:00
service-account-key.pem                                                                                                                                100% 1679    17.3KB/s   00:00
service-account.pem                                                                                                                                    100% 1440    14.6KB/s   00:00
Warning: Permanently added 'compute.3703518325472787566' (ECDSA) to the list of known hosts.
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
ca.pem                                                                                                                                                 100% 1318    11.8KB/s   00:00
ca-key.pem                                                                                                                                             100% 1675    16.3KB/s   00:00
kubernetes-key.pem                                                                                                                                     100% 1679    16.7KB/s   00:00
kubernetes.pem                                                                                                                                         100% 1663    15.1KB/s   00:00
service-account-key.pem                                                                                                                                100% 1679    16.6KB/s   00:00
service-account.pem       


Control Planeに関連する証明書 f:id:taxintt:20200315100124p:plain

Worker Nodeに関連する証明書
(図の横幅の都合でWorker-2の記載は省いています) f:id:taxintt:20200315100128p:plain

ここまでがChap4のハンズオンの説明となります。

(+α) 証明書の役割とTLSの概要

そもそも、KubernetesにおいてTLS証明書がどのような役割を果たすかを整理しておきます。
結論から記載すると、KubernetesにおいてTLS証明書は通信の暗号化と各コンポーネントの認証のために利用されています。

kubernetes.io

Kubernetesの公式ドキュメント内のHow certificates are used by your clusterに証明書を利用しているコンポーネントと目的が記載されています。

  • Client certificates for the kubelet to authenticate to the API server
  • Server certificate for the API server endpoint
  • Client certificates for administrators of the cluster to authenticate to the API server
  • Client certificates for the API server to talk to the kubelets
  • Client certificate for the API server to talk to etcd
  • Client certificate/kubeconfig for the controller manager to talk to the API server
  • Client certificate/kubeconfig for the scheduler to talk to the API server

下の方にこのような記載もありましたが、Hard wayの内容に関係ない部分なので割愛します。 (front-proxyとetcdのクラスタ内で利用される証明書に関する説明だと思われます。)

  • Client and server certificates for the front-proxy
  • etcd also implements mutual TLS to authenticate clients and peers


上記から、API server (もしくはAPI Serverと通信するコンポーネント) がTLS証明書を利用していることがわかります。
また、Client certificateServer certificateの2種類のTLS証明書が発行されていることがわかると思います。


TLSの仕様と照らし合わせながら、KubernetesでのTLS証明書の利用用途を説明します。
この記事では、コンポーネントの認証に関連するTLSにおける認証に関する話をメインでまとめます。

TLSの仕様では通信相手 (=サーバー側) の認証が必須となっています。
具体的には、サーバー証明書の正当性検証とサーバーからの署名情報の検証によってサーバの認証(真正性の確認)を行います。
上記の目的のために、サーバー側であるAPI ServerはServer certificateを利用します。

また、TLSではクライアント証明書を利用したクライアント側の認証が可能となっています。
milestone-of-se.nesuke.com

こちらの記事にも、TLSクライアント認証に関する説明が記載されています。 jovi0608.hatenablog.com

このオプションを利用してAPI Serverに対して通信するクライアントの認証を行うため、クライアント側 (=通信元) である各コンポーネントClient certificateを利用します。

TLSの一連のシーケンスについて図示すると下記のようになります。

f:id:taxintt:20200315110117p:plain

証明書の正当性検証と署名情報の検証部分を詳細に図示するとこのようになります。

f:id:taxintt:20200315135645p:plain

サーバー証明書の正当性検証のために、CA証明書(正確には証明書にバンドルされている公開鍵)を利用していることがわかります。
上記のフローを実現するために、手順の中でWorker Nodeに認証局(CA)の証明書 (=ca.pem)を配布する必要があります。

また、署名情報の検証の部分では鍵交換で使うデータを秘密鍵で署名してクライアントに送付し、クライアントはサーバー証明書にバンドルされた公開鍵を用いて署名の検証を行っています。

> for instance in worker-0 worker-1 worker-2; do
     gcloud compute scp ca.pem ${instance}-key.pem ${instance}.pem ${instance}:~/
done

Warning: Permanently added 'compute.7526976075834096736' (ECDSA) to the list of known hosts.
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':

// Worker Node(クライアント側となるNode)に認証局(CA)の証明書 (=ca.pem)を配布している
ca.pem                      
worker-0-key.pem           
worker-0.pem


また、クライアント証明書の検証について図示するとこのようになります。
Client Certificate以前のハンドシェイクのデータを秘密鍵で署名してサーバ側に送付し、サーバ側はクライアント証明書にバンドルされた公開鍵を用いて署名の検証を行っています。

f:id:taxintt:20200315135924p:plain


上記のようなフローにより、クライアント・サーバ側の両者の認証が行われます。
余談ですが、クライアント認証も行うTLSのことをmTLS (Mutual TLS authentication)と言います。(Istioに詳しい方は頻繁に聞く単語かもしれません。)

今回は、Chap4の内容をまとめました。
TLS周りの話は正確に理解する必要があるので、図や参考にした資料のリンクを全て載せておきました。

他に参考にした資料のリンクはこちらです。

TLS, HTTP/2演習

今なぜHTTPS化なのか?インターネットの信頼性のために、技術者が知っておきたいTLSの歴史と技術背景 - エンジニアHub|若手Webエンジニアのキャリアを考える!

パンドラの箱?TLS鍵交換の落とし穴、KCI攻撃とは何か - ぼちぼち日記