Kubernetes The Hard Way をやって知ったこと

巷ではケーサンショー、もとい k3s が盛り上がっていることとは存じますが、初級修行僧の身として今更ながら Kubernetes The Hard Way をやりました。知らなかったところと調べたことをメモしておきます。

github.com

  • 実施した commit
    • bf2850974e19c118d04fdc0809ce2ae8a0026a27
  • 注記
    • 気になった点や調べたこと、感想はこの色で書いてます

01~03

真顔でコピペ。

04 Provisioning a CA and Generating TLS Certificates

  • コンポーネントがそれぞれに (主に API Server との) 通信を行う際に使用する認証を生成している。
    • Kubernetes admin user
    • Node (Kubelet)
    • kube-controller-manager
    • kube-proxy
    • kube-scheduler
    • kube-apiserver
    • Service Account
      • GCPの Service Account ではない
  • Kubernetes では Node の Kubelet から生成される API リクエストの認証を行うのに Node Authorizer という認証方式を使う
    • Using Node Authorization - Kubernetes
    • Kubelet が Node Authorizer に認証されるためには system:nodes:NodeName の形式で自身を識別させる認証情報を持つ必要がある
  • コンポーネントの中で中心的にリクエストを受け付ける API Server の Certificate には、そのIPアドレス ( kubernetes-the-hard-way という名前で取得した外部 IP アドレス) を含めることで remote client との間で認証が可能になる
  • Kubernetes Controller Manager は鍵ペアを使用して Service Account tokens を生成/認証する

05 Generating Kubernetes Configuration Files for Authentication

  • 04章で作成した認証情報を使って Kubernetes clients (各コンポーネント) が kube-apiserver を認識し認証されるための kubeconfig を生成する
  • API Server の IP アドレスは冗長性のため通常ロードバランサに付与して負荷分散するのがベター
    • 今回は GCPTCP ロードバランサを使用する
  • Kubelet の kubeconfig を生成する際には、 Kubelet が動作する Node の名前 system:nodes:NodeName にマッチする Certificate を使う必要がある。これにより Node Authorizer による認証が行われる

06 Generating the Data Encryption Config and Key

  • Kubernetes は cluster state, application config, secrets といったデータを暗号化する仕組みを持つ
  • 暗号鍵と Secret を暗号化するための kind: EncryptionConfig の manifest を生成し Controller Node にばら撒く

07 Bootstrapping the etcd Cluster

  • Kubernetesコンポーネントはステートレスであり、 ステートに関する情報は etcd に格納される
  • etcd サーバはインスタンスの内部IPアドレス (03章で作成した kubernetes-the-hard-way VPCkubernetes subnet 範囲内のIPアドレス) を使ってクライアントからのリクエストを受け付けたり、 etcd クラスタ内の他の peer とコミュニケーションを行う
  • etcd は API Version を指定しないと v2 で起動する
    • etcdctl -h で表示される実行可能なサブコマンド一覧も v2 と v3 で異なる
    • 今回のように認証情報を渡して起動すると、それなしではクラスタに関する情報を閲覧することができない
controller-2:~$ 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
f98dc20bce6225a0, started, controller-0, https://10.240.0.10:2380, https://10.240.0.10:2379
ffed16798470cab5, started, controller-1, https://10.240.0.11:2380, https://10.240.0.11:2379

controller-2:~$ sudo ETCDCTL_API=3 etcdctl member list
Error: context deadline exceeded

08 Bootstrapping the Kubernetes Control Plane

  • kube-apiserver, kube-controller-manager, kube-scheduler を Systemd で Service 化して起動する
    • ちなみに kubeadm は上記コンポーネントを Pod として動かしているらしい。他には GKE とかはどうだろうか。
  • kube-scheduler には kind: KubeSchedulerConfiguration の manifest を引数に与える
  • オプションより
    • --cluster-cidr は kube-controller-manager で指定
    • --service-cluster-ip-range は kube-apiserver, kube-controller-manager の両方で指定
    • --leader-elect=true は kube-controller-manager でのみ指定
      • Kubernetes完全ガイドの第19章を見ると kube-scheduler も leader 制をとることができるという
  • Google Network Load Balancer (様々なプロトコルに対応したRegional LB) は API Server に対するトラフィック分散と SSL の終端を行う目的で使用するが、 HTTPS の health check には非対応なため nginx をインストールして HTTP 宛の health check を HTTPS にプロキシする
    • /healthz エンドポイントはデフォルトでは認証不要になっている
  • kube-apiserver が Kubelet の API にアクセス (ログやメトリクスを収集したりコマンドを実行したり) するためには RBAC の設定が必要
    • kind: ClusterRole の manifest を作製してよく使う一般的な Kubelet API を定義し、 kind: ClusterRoleBinding manifest で kubernetes ユーザに紐づけを行う

09 Bootstrapping the Kubernetes Worker Nodes

  • Worker に必要なコンポーネントをインストールする
    • runc
      • 低レイヤコンテナランタイム
      • 高レイヤコンテナランタイムである containerd から OCI 経由で命令を受け取りコンテナを作成する
    • gVisor
      • runc と同じく低レイヤコンテナランタイム
      • 比較的セキュアなランタイム
    • container networking plugins
      • CNI プラグイン
      • コンテナのネットワークインターフェースに関する仕様とライブラリから成る
    • containerd
    • kubelet
    • kube-proxy
  • cni の設定では type として bridge を指定し、03章で定義した pod-cidr を記述している

The loopback CNI is automatically used by Kubernetes to do low-level networking (like for the Kubelet to talk to the containers local to the node) and Kubernetes will fail if it's not installed or running,

  • containerd の設定では低レイヤコンテナランタイムである runc 及び runsc (gVisor) を指定
  • kubelet の設定では kind: KubeletConfiguration を作成、その中で clusterDNSpodCIDR 、認証情報などを指定
    • 今回各コンポーネントを systemd で動かしており、 systemd 環境で DNS 設定を担当する systemd-resolved とサービスディスカバリに使用する coreDNS が仲良くするために resolvConf を設定する
  • kubelet の unit ファイルを作成。
    • --container-runtime で指定可能な値は docker, remote, rkt(deprecated) であり、 containerd を使う場合には remote を設定の後、 --container-runtime-endpoint で使用する Unix Socket を指定する
  • kube-proxy の設定では kind: KubeProxyConfiguration を作成、その中で動作モードを iptables と指定。

10 Configuring kubectl for Remote Access

  • 前半で作製した admin ユーザで kubectl を実行できるようにする設定する
  • ロードバランスされている API Server のIPアドレスと admin ユーザの認証情報を使用する
  • これを全て手で設定することを思うとgcloud container clusters get-credentials は偉大である

11 Provisioning Pod Network Routes

  • Pod が Node にスケジューリングされると Pod は Node の Pod Cidr からIPアドレスを受け取って設定するが、09章で見たように Pod Cidr は Node 毎に異なるため、通常であれば別 Node にある Pod とは通信することができない
  • そこで今回はそれを GCP のルートを用いて Pod Cidr を Node の Internal IP アドレスに解決する経路を生成する
  • この lab では敢えて原始的な方法を用いているが Calico や Flannel 等の CNI プラグインを用いる方法が一般的ですよね

12 Deploying the DNS Cluster Add-on

  • サービスディスカバリのための DNS として CoreDNS を Add-on 的にデプロイする
  • 作成されている Service に対して名前解決ができることを確認する
  • が、やけに遅い
$ time kubectl exec -ti $POD_NAME -- nslookup kubernetes
Server:    10.32.0.10
Address 1: 10.32.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes
Address 1: 10.32.0.1
kubectl exec -ti $POD_NAME -- nslookup kubernetes
0.09s user 0.03s system 0% cpu 45.347 total
  • Service に対して問い合わせる場合と Endpoint に問い合わせる場合で差があるようだ。 Service に問い合わせをする場合、実際の DNS 応答は Endpoint のIPアドレスから返ってくるため、そこらへんが nslookup の機嫌を損ねているのではと推測。
## dns の Service と Endpoint を確認しておく
$ kubectl -n kube-system get svc kube-dns
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)         AGE
kube-dns   ClusterIP   10.32.0.10   <none>        53/UDP,53/TCP   12d

$ kubectl -n kube-system describe ep kube-dns
(snip)
Subsets:
  Addresses:          10.200.0.9,10.200.2.7
  NotReadyAddresses:  <none>
  Ports:
    Name     Port  Protocol
    ----     ----  --------
    dns      53    UDP
    dns-tcp  53    TCP

$ kubectl exec -it $POD_NAME /bin/sh

## 結果が得られることに変わりはないが Service を介した場合だけやけに遅い
/ # time nslookup kubernetes 10.32.0.10 1> /dev/null
real  0m 10.01s
user  0m 0.00s
sys   0m 0.00s
/ # time nslookup kubernetes 10.200.0.9 1> /dev/null
real  0m 0.00s
user  0m 0.00s
sys   0m 0.00s
/ # time nslookup kubernetes 10.200.2.7 1> /dev/null
real  0m 0.00s
user  0m 0.00s
sys   0m 0.00s

12 Smoke Test

  • crictl コマンドを使用して CRI の Unix Socket 経由で実際に動いているコンテナの一覧を取得可能
root@worker-0:~# sudo crictl -r unix:///var/run/containerd/containerd.sock ps
CONTAINER ID        IMAGE               CREATED             STATE               NAME                ATTEMPT             POD ID
c67595b3b33c2       8c811b4aec35f       32 minutes ago      Running             busybox             24                  a58da8259ee26
a15e2d786eb05       367cdc8433a45       About an hour ago   Running             coredns             0                   c3a0d3c772526
66f1bbe5ed396       881bd08c0b082       5 hours ago         Running             nginx               2                   e8a5bbdb3733a

まとめ

Hard とは名前だけで優しさに溢れた素晴らしい教材でした。またやろう。