「kubernetes-the-hard-way」実施記録
はじめに
本記事は、以下inductor様による、kubernetes-the-hard-way
の和訳に基づいて実施した環境構築の手順に関する、2021-09-19
時点における自分なりの理解をメモしたものです。
インフラ初学者はこんな誤解すんのか~とか思いながら、明日から後輩・新人に優しくしてあげるきっかけとして読んでいただければ。
本記事の正確性等は一切保証しませんが、理解の誤りや、誤植・手順漏れ等、コメント・ご指摘に関しては何でも頂ければ幸いです。マサカリは優しく投げてください。
背景
kubernetesを実運用するにあたって、クラスタを構成する各コンポーネントとコンポーネント間通信(特に証明書周りとネットワーク周り)に関する理解を深めたくなったので実施した。
ついでにCKAの勉強にもなればより嬉しい。
本文
GCP環境構築
GCPの利用自体、かなり歴が浅いのでSDK(gcloud
)を利用した環境構築から理解を得た。
gcloud init
gcloud auth logine
Go to the following link in your browser:
# ここにアクセスすると見慣れたGoogleログイン画面に飛ぶ
https://accounts.google.com/o/oauth2/auth?xxxxxxxxxxxxxxxxxxxxxxxxx
# Googleロvグイン画面から取得したコードを貼り付ける
Enter verification code:
Googleアカウント自体は普段からChrome認証等で使っていて、GCPと紐ついて認証基盤を管理できるのは、楽で良さそう。
tmux
手順上必須ではないとは記載があるものの、今回構築予定のクラスターはController:Worker=3:3の構成で、etcd
の設定等の段階で同じ手順を全Controller Nodeに繰り返す、といったことをすると気が狂ってしまうのでほぼ必須。
自分は普段Windowsで使うSSH TerminalとしてはRlogin
を使っていたが、似たような機能をCLI上で実行できるのは感動的な体験。チートシートを見ながらではあるが、慣れてくるとvim
みたいな感じで使いやすい。
02-クライアントツールのインストール
cfssl
, cfssljson
とkubectl
のインストールを行う手順。
オレオレ証明書の生成といえばopenssl
だと思っていたが、どういう基準で使い分けているんだろう。[要調査]
03-計算資源のプロビジョニング
Network
主にGCP上でのVM/VPCその他のプロビジョニング。
GCPの場合はAWSでいうSecurity Groupに該当する概念がFirewall Rulesみたいなので、Firewall Rulesに各コンポーネントが通信可能と成るようにIngress/Egressを設定する。
gcloud compute firewall-rules create kubernetes-the-hard-way-allow-external
--allow tcp:22,tcp:6443,icmp
--network kubernetes-the-hard-way
--source-ranges 0.0.0.0/0
source rangeが0.0.0.0
からのSSHおよびkube-apiserver
向け通信を許可する設定を入れている。
外部NW全域からのSSH許可は何となく気持ちが悪いので、MyIP Onlyに設定した。
この先の手順で動かなかったらここも疑う。
Kubernetesの公開IPアドレス
KubernetesのAPIサーバの前面に置かれる外部ロードバランサにアタッチする静的IPアドレスを割り当てます:
とのこと。
Controller:Worker=3:3構成なので、前段にController向けのAPIリクエストを一手に引き受けるロードバランサーを置くっぽい。まだ全体構成が見えてない。
インスタンス
Controller:Worker=3:3構成の合計6台を先程のNW上にSDKを使って立ち上げる。
本実習のインスタンスでは、コンテナランタイムのcontainerdにて推奨されるUbuntu Server 20.04を使用します。
とのことでOSはUbuntu 20.04を使う。
自宅でkubeadmを使ってクラスターを組んだときはUbuntu20.04+Docker(as CRI)として組んだので、推奨がcontainerdだとすると組み直したほうが良いかも。[要調査]
SSHアクセスの設定
GCP上のインスタンスにSSH接続するときは、gcloud compute ssh
コマンドを用いる。
AWSでは、Session managerを使って、閉域に置かれたサーバー(上のエージェント)とHTTPS経由での疑似SSHを行っていた[要出典]ので似たようなものかと思ったが、わざわざインスタンスにpublic IPを持たせているので、仕組みは違うのかもしれない。
初めてインスタンスに接続するとSSHキーが生成され、プロジェクトまたはインスタンスのメタデータに格納されます (インスタンスへの接続のマニュアルを参照してください)。
実際以下のコマンドを実行すると、アカウントに紐ついたインスタンス名を解決していい感じにSSHキーの発行と公開鍵のアップロードを行ってくれた。
GCPのプロジェクトという単位をもう少しまだ理解できていないところがあるが、今回の手順ではSSHキーは全インスタンスで暗黙的に共有されているみたいで、一台にSSH接続したあとはキーの発行の手順はスキップされた。
gcloud compute ssh controller-0
04-CA証明書のプロビジョニングとTLS証明書の生成
このあたり本当によく理解ってない。多分SSL/TLSの仕組みが怪しい。
ちゃんとそのあたりからまとめて復習すること。
etcd、kube-apiserver、kube-controller-manager、kube-scheduler、kubelet、kube-proxyの各コンポーネントのTLS証明書を生成します。
はなんとなく、Certificate Authority
章で作成したローカルの認証局(CA構成ファイル、証明書、および秘密鍵)を使って、各コンポーネント(etcd、kube-apiserver、kube-controller-manager、kube-scheduler、kubelet、kube-proxy
)と通信する際、通信先が本当に意図したコンポーネントであることを証明するためのクライアント証明書を作るための手順と理解した。
よくわからないのは以下。
> admin用クライアント証明書
急に出てきたadmin概念。おそらくkubernetesクラスターを色々いじれる権限を持ったユーザー。
後ほどadminユーザーを使って諸々やるんだろうので、そのときに理解する。
KubernetesのAPIサーバー用証明書
静的IPアドレスkubernetes-the-hard-wayはKubernetesのAPIサーバー用証明書のSubject Alternative Name(SAN)リストに含まれます。これによって証明書をリモートクライアントで検証できるようになります。
さっき作ったロードバランサー用IPアドレス用の証明書を作るらしいが、
- Subject Alternative Name(SAN)リスト
- 証明書をリモートクライアントで検証
のあたりがよくわからない。
自分(主体たるNode)以外のhostnameを名乗れるみたいな仕組み?
サービスアカウントのキーペア
Kubernetesのコントローラーマネージャーは、サービスアカウントの管理に関するドキュメントで説明されているように、キーペアを使用してサービスアカウントトークンを生成して署名します。
らしい。サービスアカウントって何?
本実習の中で出てきたらここに戻って追記する。
05-認証用Kubernetes設定ファイルの生成
本実習ではKubernetesのコンフィグファイル(kubeconfigとも呼ばれます)を生成します。これにより、KubernetesクライアントがKubernetesのAPIサーバーを特定して認証できるようになります。
kubeconfigファイルは、以下公式の記述を自分なりに解釈した感じだと、コンポーネントが動作するにあたって、どのクライアント証明書を使うか、どのユーザー(+クラスター=コンテキスト)で動作するか、Controller NodeのIPアドレスはどこか、といった情報が記載されたコンフィグファイルになると理解した。
kubeconfigを使用すると、クラスターに、ユーザー、名前空間、認証の仕組みに関する情報を組織できます。kubectlコマンドラインツールはkubeconfigファイルを使用してクラスターを選択するために必要な情報を見つけ、クラスターのAPIサーバーと通信します。
さっき作ったetcd、kube-apiserver、kube-controller-manager、kube-scheduler、kubelet、kube-proxy
あたりのクライアント証明書を、各Controller/Worker Nodeにアップロードしなかったのは、ここでそれら証明書を内包するkubeconfigファイルを作ってアップロードするからだろう。
実際、kubeconfigファイルの生成にあたっては、以下に示すように認証局とかHTTPS通信(kubectl APIリクエスト)先の情報、ユーザー名やdefault contextの作成とdefault contextを使う指示が記載されてたりする。
kubelet
は、Node Authorizerとかいう仕組みによって認可を受けるために、ユーザーはsystem:node:${instance}
という名前になっていないといけないんだとか。
for instance in worker-{0..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
06-データ暗号化の設定とキーの生成
Kubernetesはクラスタの状態、アプリケーションの構成、機密情報など、さまざまなデータを保存します。Kubernetesはクラスタデータを暗号化する機能をサポートします。
はい。
クラスタ全体の(コンテキスト全体の?)暗号化の設定は、EncryptionConfig
を適用することで実施できるらしい。
ここでは作っただけでまだ適用はしてない。
07-etcdクラスターのブートストラップ
クラスターの状態を格納するetcdを、controller node上にHA構成で配置する。
ここの手順は、3台のController Nodeに対して同様に行う操作なので、tmux
を使う。
tmux
のチートシートは以下を参照した。
set-window-option synchronize-panes on/off
の話が欲しかったので非常に助かりました。
以下のコマンド実行はおそらく誤植で、後段の処理とVersionの整合性がないので注意。(★メモ:重箱の隅をつつくようで恐縮ではあるが、プルリクは出すように)
wget -q --show-progress --https-only --timestamping \ "https://github.com/etcd-io/etcd/releases/download/v3.4.10/etcd-v3.4.10-linux-amd64.tar.gz"
GCP上のVMは、Instanceのmetadataに内部IPアドレスを持つので、以下手順を踏む。
クライアントリクエストの処理とetcdクラスター・ピアとの通信にはインスタンスの内部IPアドレスが使用されます。以下のコマンドで現在作業中のインスタンスが持つ内部IPアドレスを取得します:
INTERNAL_IP=$(curl -s -H "Metadata-Flavor: Google" \
ehttp://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ip)
systemdユニットファイルetcd.serviceを作成
systemdユニットファイルと言う存在を知らなかった。そもそもsystemd
のことがよくわかってない、なんかApacheとかDockerのサービスを上げるときにsystemctl
を打つけど、その仲間か?[要調査]
cert-file=/etc/etcd/kubernetes.pem
のように、etcdが用いる証明書ファイルとしては前段で作った『KubernetesのAPIサーバー用証明書』を使うらしい。このkubernetes.pem
が他に共通して出てくるコンポーネントが後々わかれば、たくさん作った証明書の用途も整理できるかも。
etcd
の証明書とかセキュアなファイルや変更されるべきではない設定ファイル関連は/etc/etcd
配下に置いてるらしいので、それに従う。
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
長いが、エラーメッセージが結構親切なので本来必要なoptionが欠けてるとわかりやすい。
systemd
等のkubernetesの外側の概念に関して理解できないところに目をつぶれば(つぶってはいけないが)、etcd
のクラスターを構成できた。
上記で--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
を打ったことで各Controller Node上のetcd
がよしなにしてくれたんだろう。かなり便利。
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
のクラスタメンバーIDが一致)したので、メンバーIDはコンフィグや実行環境から片方向的かつ一意に生成される値っぽい。
etcd
のバックアップ・リストアは以下の手順でできた。
etcdctl
のコマンドのいずれにおいても、各etcd
向けの通信がすべてSSL/TLS暗号化されるため、証明書をくっつけて送るためにcert/key/cacert/endpoints
の指定が必要らしい。めんどい。
sudo ETCDCTL_API=3 etcdctl snapshot save etcd-snapshot-0919 \
--cert=/etc/etcd/kubernetes.pem \
--key=/etc/etcd/kubernetes-key.pem \
--cacert=/etc/etcd/ca.pem \
--endpoints=https://10.240.0.10:2379
sudo ETCDCTL_API=3 etcdctl snapshot restore etcd-snapshot-0919 \
--cert=/etc/etcd/kubernetes.pem \
--key=/etc/etcd/kubernetes-key.pem \
--cacert=/etc/etcd/ca.pem \
--endpoints=https://10.240.0.10:2379
08-Kubernetesコントロールプレーンのブートストラップ
本実習では、3つのインスタンスでコントロールプレーンをブートストラップして可用性の高い構成を実現します。また、KubernetesのAPIサーバーをリモートクライアントに公開する外部ロードバランサも作成します。各ノードには、Kubernetes API Server、Scheduler、Controller Managerの各コンポーネントがインストールされます。
本実習ではController NodeにKubeletやKubeProxyを置かないので、Controller Node上にworker podsを配置することはできない?[要確認]
引き続きcontroller node全部における作業なのでtmux
を使う。
Kubernetes APIサーバーの設定
出た。
ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem
はすべて/var/lib/kubernetes
下に移動した。
/var/lib/kubernetes
は各種鍵ファイル、ではさっき作った/etc/kubernetes/config
にはkubeconfig
とかを置くのだろうか。
ca.pem kubernetes-key.pem kubernetes.pem
は、etcd
も使うファイル(etcd
用には/etc/etcd
下に置いたが)で、共通の証明書を使う。
色んな所にファイルが置かれるので、証明書をローテーションするなんて作業をやるときは大変そう。
{
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/
}
Kubernetesコントロールプレーンのプロビジョニング
REGION
をインスタンスの(プロジェクトの)メタデータから取得するのに失敗してしまう。
01-前提条件における下記手順の設定漏れで、Projectの設定が正しく行われていないと思われる。
次に、デフォルトのリージョンを設定します:
gcloud config set compute/region us-west1
ダサいかもしれないが、GCPのコンソール画面から設定して事なきを得る。
REGION=$(curl -s -H "Metadata-Flavor: Google" \
http://metadata.google.internal/computeMetadata/v1/project/attributes/google-compute-default-region)
systemd
ユニットファイル作成
各種コンポーネントのetcd
同様に、systemctl
で管理するkube-apiserver
を起動するための設定ファイルを作る。(kube-apiserver
, kube-controller-manager
, kube-scheduler
で同様の手順を行う)
kube-apiserver
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 \\
--runtime-config='api/all=true' \\
--service-account-key-file=/var/lib/kubernetes/service-account.pem \\
--service-account-signing-key-file=/var/lib/kubernetes/service-account-key.pem \\
--service-account-issuer=https://${KUBERNETES_PUBLIC_ADDRESS}:6443 \\
--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
これはネタバレだが、--etcd-servers=https://10.240.0.10:2379,https://10.240.0.11:2379,https://10.240.0.12:2379
等の、etcd
関連のOptionはKubernetes API Server以外には与えてないので、etcd
と直接通信するのはKubernetes API Serverのみだと思う。
その他はSSL/TLS関連のオプションがほとんど。
kube-controller-manager
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 \\
--bind-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
Optionの中に--kubeconfig=/var/lib/kubernetes/kube-controller-manager.kubeconfig
とあるので、kube-controller-manager.kubeconfig
は/var/lib/kubernetes
配下に置く。
言われてみれば確かにkubeconfig
は鍵ファイルを内包してるので、他の鍵ファイル(kubernetes API Server証明書とか)と同じ扱いとして/var/lib
配下に置くのが正しそう。
だから以下の予想ははずれ。
/var/lib/kubernetes
は各種鍵ファイル、ではさっき作った/etc/kubernetes/config
にはkubeconfig
とかを置くのだろうか。
知らんけど大事なmanifestとか置くんだよ多分。
kube-scheduler
他同様にsystemd
ユニットファイルを作る。
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
オプションに置いた--config=/etc/kubernetes/config/kube-scheduler.yaml
を作る。
/etc/kubernetes/config
以下には、kubernetesで管理しない(systemd
が管理する)Podの設定Manifestが置かれるのかな。
apiVersion: kubescheduler.config.k8s.io/v1beta1
になってるのが気になる。
kubescheduler
は中核をなすコンポーネントなのに、その設定を行うためのAPIがまだ(もう?)betaなのはなんでだろう。
そもそもkube-controller-manager
とkube-apiserver
だけYAMLのConfigを持たないのもよくわからない(もしかしたら設定できるのかも)。このあたりのAPIが統一されていないのはなにか背景があるのかもしれない[要調査]
cat <<EOF | sudo tee /etc/kubernetes/config/kube-scheduler.yaml
apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration
clientConnection:
kubeconfig: "/var/lib/kubernetes/kube-scheduler.kubeconfig"
leaderElection:
leaderElect: true
EOF
HTTPのヘルスチェックを有効化
散々出てきた$KUBERNETES_PUBLIC_ADDRESS
の伏線回収パート。
Google Network Load Balancerを使用して3つのAPIサーバーにトラフィックを分散し、各APIサーバーがTLS接続を終端してクライアント証明書を検証できるようにします。
GCPにも、AWS同様にレイヤ7負荷分散とレイヤ4負荷分散があるらしい。
ここでは、ネットワークロードバランサー(レイヤ4負荷分散)を用いる。
レイヤ7負荷分散だと、せっかく取得して、各コンポーネントに教えたLBエンドポイントのIPアドレスを固定できないとかが原因か?[要調査]
ネットワークロードバランサーは、HTTPヘルスチェックのみをサポートします。つまり、APIサーバーによって公開されたHTTPSエンドポイントは使用できません。回避策として、nginxを使用してHTTPのヘルスチェックをプロキシすることができます。本セクションではnginxをインストールし、ポート80でHTTPヘルスチェックを受け入れ、https://127.0.0.1:6443/healthz 上のAPIサーバへの接続をプロキシするように設定します。
ネットワークロードバランサーなので、クライアント証明書を持ってヘルスチェックみたいな気の利いたことはできないためか、回避策が書かれてる。
恥ずかしながらnginx
を使ってWebサーバーを立てたことがないのでここからは完全初見。
いつもは各種プログラミング言語が具備しているライブラリ実装を使ってWebサーバーを立てているゆとり世代であるため、実はApacheもnginxも、k8sの動作確認くらいでしかほぼ触ったことない。
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;
}
}
このファイルを/etc/nginx/sites-enabled/
以下に置いてnginx
を起動すると、Host=kubernetes.default.svc.cluster.local
, Port=80
向けの通信をhttps://127.0.0.1:6443/healthz
、即ちkube-apiserverにフォワーディング(プロキシ?)してくれるんだと思う。こんなイメージ。
[GCP NLB]
--> [kubernetes.default.svc.cluster.local/healthz:80]
--> [nginx]
--> [<Node>/healthz:6443 = kube-apiserver]
ローカルからリクエストを送ってプロキシ動作確認。
curl -H "Host: kubernetes.default.svc.cluster.local" -i http://127.0.0.1/healthz
RBACを使ったKubeletの認可
本セクションでは、RBACのアクセス権を設定して、KubernetesのAPIサーバーが各ワーカーノード上のKubeletにアクセスできるようにします。メトリクスやログを取得したり、Pod内でコマンドを実行するためにはKubelet APIへのアクセスが必要です。
本チュートリアルでは、Kubeletの--authorization-modeフラグをWebhookに設定します。Webhookモードでは、SubjectAccessReview APIを使用して認可を判定します。
言葉の意味が一切わからんので、調査したところ以下のように解釈した。
Webhookはkubelet自身では認可を判断せず、認可の責任を別のコンポーネントに移譲するための仕組みで、kubeletにリクエストを送ったユーザーのロールを元にkubelet(から判断を任されたコンポーネント)がそのリクエストを許可するかしないか、を決定するみたいな話。
ということでまずはユーザーロールを先に作る。
system:kube-apiserver-to-kubeletという名前のClusterRoleを作成し、Kubelet APIにアクセスしたり、Podの管理に関連する一般的なタスクを実行したりするための権限を付与します:
apiVersion: rbac.authorization.k8s.io/v1
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:
- "*"
で、そのあとにkubernetes
ユーザーにアタッチ。
--kubeconfig admin.kubeconfig admin.kubeconfig
とするのは、admin.kubeconfig
の中に書かれたクライアント証明書を使うため。
cat <<EOF | kubectl apply --kubeconfig admin.kubeconfig -f -
apiVersion: rbac.authorization.k8s.io/v1
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
ネットワークロードバランサーのプロビジョニング
One Commandで出来ちゃうので味気ないが、すごい。
{
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
}
$KUBERNETES_PUBLIC_ADDRESS
はお馴染みの外部IPアドレス。
--host "kubernetes.default.svc.cluster.local"
はnginx
でプロキシ設定を行ったリスナーホスト名。
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
はNLBヘルスチェック用の許可設定。
NLBのヘルスチェックは209.85.152.0/22,209.85.204.0/22,35.191.0.0/16
から飛んでくるのか、Private/Isolated Subnetの場合どうするんだろう?[要調査]
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
はNLBの転送設定。
--target-pool
は予め--instances controller-0,controller-1,controller-2
として作成済み。
かなり長かったが、ここまででようやくController Node内のコンポーネントは揃った。まだcontroller nodeからkubectl get node
とか打つとエラーが出るので、今後解消されるタイミングがあると信じたい。
sandship@controller-0:~$ kubectl get node
The connection to the server localhost:8080 was refused - did you specify the right host or port?
09-Kubenretesワーカーノードのブートストラップ
本実習では、3つのKubernetesワーカーノードをブートストラップします。各ノードにはrunc、CNI, containerd、kubeletおよびkube-proxyがインストールされます。
ということでtmux
を使って進めていく。
以下をインストールするわけだが、恥ずかしながら一つも知らないライブラリたちなので調べてみた。
{
sudo apt-get update
sudo apt-get -y install socat conntrack ipset
}
-
socat
-
Socat is a command line based utility that establishes two bidirectional byte streams and transfers data between them. Because the streams can be constructed from a large set of different types of data sinks and sources (see address types), and because lots of address options may be applied to the streams, socat can be used for many different purposes.
- 双方向のByte Streamを2つ作って、それぞれのStream同士をPipeしてくれるもの
- リバプロみたいな用途で使う?
-
-
conntrack
-
conntrack provides a full featured userspace interface to the netfilter connection tracking system that is intended to replace the old /proc/net/ip_conntrack interface. This tool can be used to search, list, inspect and maintain the connection tracking subsystem of the Linux kernel. Using conntrack , you can dump a list of all (or a filtered selection of) currently tracked connections, delete connections from the state table, and even add new ones.
In addition, you can also monitor connection tracking events, e.g. show an event message (one line) per newly established connection. - Linux Kernelが行う通信を覗き見るための機能がたくさんついたライブラリ
- wiresharkみたいな感じか?
-
-
ipset
-
ipset is used to set up, maintain and inspect so called IP sets in the Linux kernel. Depending on the type of the set, an IP set may store IP(v4/v6) addresses, (TCP/UDP) port numbers, IP and MAC address pairs, IP address and port number pairs, etc. See the set type definitions below.
Iptables matches and targets referring to sets create references, which protect the given sets in the kernel. A set cannot be destroyed while there is a single reference pointing to it. - IPアドレスとかPortとかNICのMACアドレスをペア(セット?)という単位として取り扱うことができるようになり、Iptablesコマンドがそのセットを使っていい感じに管理できるようになる?
- kubernetesでよく出てくる
iptables
コマンドの管理をもっとかんたんにするためのツールチェインの一つらしい
-
3つともNW関連で、ソケットの監視、接続の監視、管理を行う感じだと思う。
kubernetes
箱のあたりのライブラリに依存しているらしいのでインストールした。
swapはoffにしないとkubelet
は起動失敗する。
ラズパイとかだとswapが有効になっていたので、一度詰まった。
sudo swapon --show
sudo swapoff -a
ワーカーでブートしたい各種バイナリをダウンロード&インストールする。
ここでは、上記したrunc, CNI, containerd, kubelet, kube-proxy
とkubectl
を取ってくる。
事前に、runc
, CNI
, containerd
については理解しておきたいので調べた。
それぞれ以下のイメージで理解した。
-
runc
* -
CNI
-
CNI (Container Network Interface), a Cloud Native Computing Foundation project, consists of a specification and libraries for writing plugins to configure network interfaces in Linux containers, along with a number of supported plugins. CNI concerns itself only with network connectivity of containers and removing allocated resources when the container is deleted. Because of this focus, CNI has a wide range of support and the specification is simple to implement.
- この文章からだとむつかしくてよくわからなかったが、ContainerのNW Interfaceのライフサイクル管理を行うための各種CNI Pluginsの一種。
- 聞きかじったことのある
calico
とかflannel
とかの仲間で、Pluginバイナリを/opt/cni/bin
に配置、ネットワーク構成ファイルを/etc/cni/net.d/xxx.conf
に置けばいい感じにしてくれる統一の規格のようなものが定められているとか。 - 詳しい解説は[こちら]http://netstars.co.jp/kubestarblog/kubestarblog3/k8s/がわかりやすくてよかった。
-
-
containerd
- Container Runtime Interfaceの一種。Dockerみたいにコンテナを動かす主体で、Kubernetesと通信を行って必要なときにContainerをPullして立ち上げる。(PullはCRIのしごとではないかも)
- 今回の実習ではkubernetesのCRIとして
docker
ではなくcontaienrd
を用いる。 - KubernetesのデフォルトCRIが
docker
からcontainerd
になるみたいな話を以前聞いた。(誤解してるかも)
wget -q --show-progress --https-only --timestamping \
https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.21.0/crictl-v1.21.0-linux-amd64.tar.gz \
https://github.com/opencontainers/runc/releases/download/v1.0.0-rc93/runc.amd64 \
https://github.com/containernetworking/plugins/releases/download/v0.9.1/cni-plugins-linux-amd64-v0.9.1.tgz \
https://github.com/containerd/containerd/releases/download/v1.4.4/containerd-1.4.4-linux-amd64.tar.gz \
https://storage.googleapis.com/kubernetes-release/release/v1.21.0/bin/linux/amd64/kubectl \
https://storage.googleapis.com/kubernetes-release/release/v1.21.0/bin/linux/amd64/kube-proxy \
https://storage.googleapis.com/kubernetes-release/release/v1.21.0/bin/linux/amd64/kubelet
CNIのネットワーク設定
以下手順で/etc/cni/net.d/
配下にネットワーク構成ファイル(10-bridge.conf
, 99-loopback.conf
)を作成する。
後々に、Node間Podネットワークの設定を行う章が別であるので、多分ここでは各コンポーネント間のネットワーク設定を行っている?
構成ファイルに関しては、[公式リポジトリ]https://github.com/containernetworking/cni#how-do-i-use-cniにフォーマットが記載されている。
podネットワークのrouteを規定しており、${POD_CIDR}
向けの通信を0.0.0.0/0
にルーティングする設定になっている?
cat <<EOF | sudo tee /etc/cni/net.d/10-bridge.conf
{
"cniVersion": "0.4.0",
"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
cat <<EOF | sudo tee /etc/cni/net.d/99-loopback.conf
{
"cniVersion": "0.4.0",
"name": "lo",
"type": "loopback"
}
EOF
kubeletの設定
controller Node内のkubernetesコンポーネントの設定同様、/var/lib
配下に証明書を包含するconfig yamlを作って、systemctl
で起動するためのユニットファイルを作成する。
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"
/var/lib/kubelet/kubelet-config.yaml
は、kubeletの認証モード・証明書の設定と、クラスターの名前解決に関する設定が入っている。
kubeletへのアクセスはwebhook
によって認証・認可されるため、そのように記載。
clusterDomain
は, コンテナがこのクラスターにアクセスする際に利用可能なドメイン名を定義し、clusterDNS
はクラスター内のDNSのIPアドレスのリストを記述する。
clusterDNS
に関しては、"10.32.0.10"
と見知らぬIPアドレスが設定されているが、後々にkube-dns
を建てる上で指定するIPアドレスを先に指定しているっぽい。
[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
/etc/systemd/system/kubelet.service
に関しては、これまで同様kubelet起動時のオプションを定義する。
これまでの設定を踏まえて、container-runtime-endpoint
にはcontainerdのソケットを指定し、network-plugin
にはcni
を指定する。
kube-proxyの設定
kubelet同様、/var/lib
配下に証明書を包含するconfig yamlを作って、systemctl
で起動するためのユニットファイルを作成する。
kind: KubeProxyConfiguration
apiVersion: kubeproxy.config.k8s.io/v1alpha1
clientConnection:
kubeconfig: "/var/lib/kube-proxy/kubeconfig"
mode: "iptables"
clusterCIDR: "10.200.0.0/16"
mode
に関しては、公式に以下の記述があった。
userspace
, iptables
, ipvs
の3種類のモードがあり、ipvs
が最も最新のモードであり、パフォーマンス・スケーラビリティに優れるが、デフォルトではiptables
が用いられるらしい。
Currently, three modes of proxy are available in Linux platform: 'userspace' (older, going to be EOL), 'iptables' (newer, faster), 'ipvs'(newest, better in performance and scalability).
In Linux platform, if proxy mode is blank, use the best-available proxy (currently iptables, but may change in the future). If the iptables proxy is selected, regardless of how, but the system's kernel or iptables versions are insufficient, this always falls back to the userspace proxy. IPVS mode will be enabled when proxy mode is set to 'ipvs', and the fall back path is firstly iptables and then userspace.
[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
kube-proxy.service
に関しては特筆する設定はないので割愛。
いつもどおり--config
オプションに、/var/lib
配下のYAMLを渡してあげる。
確認
最終的にコントローラーノードからワーカークラスタに属するノード一覧を確認して終わり。
とはならず。Worker Nodeが一つも見つからない。
gcloud compute ssh controller-0 \
--command "kubectl get nodes --kubeconfig admin.kubeconfig"
No resources found
Controller Node上のKubernetes API Serverが、Worker Node上のkube-proxyまたはkubeletを認識できていない。
各Node間のKubernetes Management通信はポート:6443を介して行われるはずで、その疎通に失敗していると考えられるとか整理していると、よくよく考えればだいぶ前に悪いことをしていた。
source rangeが
0.0.0.0
からのSSHおよびkube-apiserver
向け通信を許可する設定を入れている。
外部NW全域からのSSH許可は何となく気持ちが悪いので、MyIP Onlyに設定した。
この先の手順で動かなかったらここも疑う。
ここがダメでした。
Controller NodeおよびWorker NodeのNICのIngressルールをfrom MyIP Onlyにしてしまっていたため、Controller Node上のKubernetes API Serverが、Worker Node上のkube-proxyまたはkubeletとの通信に失敗していた。
コンソールから修正して再度実行したら以下のように正常に認識できた。(各Worker上のコンポーネントの再起動もいらなかった。)
gcloud compute ssh controller-0 \
--command "kubectl get nodes --kubeconfig admin.kubeconfig"
Enter passphrase for key '/home/sandship/.ssh/google_compute_engine':
NAME STATUS ROLES AGE VERSION
worker-0 Ready <none> 22s v1.21.0
worker-1 Ready <none> 22s v1.21.0
worker-2 Ready <none> 22s v1.21.0
10-リモートアクセス用のkubectl設定
本実習では、adminユーザーの認証情報に基づいたkubectlコマンド用のkubeconfigファイルを生成します。
本実習で使用するコマンドは、管理クライアント証明書の生成に使用したディレクトリと同じディレクトリから実行してください。
ここではリモートアクセス用のkubectlクライアントアカウントadmin
の設定を行う。
kubectl config set-cluster kubernetes-the-hard-way
で、kubernetes-the-hard-way
という名前で先の手順にて作成したクラスターを参照する設定を追加。
kubectl config set-credentials admin
で、admin
のクライアント証明書および証明書キーを設定。
kubectl config set-context kubernetes-the-hard-way
で、kubernetes-the-hard-way
クラスターとadmin
ユーザーを紐つけたコンテキストkubernetes-the-hard-way
を作成。
kubectl config use-context kubernetes-the-hard-way
で、kubernetes-the-hard-way
コンテキストを使用するよう設定。
{
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
}
本手順によって、ローカル環境(クライアント)から、リモートのController Node上Kubernetes API Server向けにkubectl
コマンドの実行が許可されるようになった。
試しに以下コマンドを実行すると、リモート・クライアント側のkubectlのバージョンを確認できる。
kubectl version
Client Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.0", GitCommit:"cb303e613a121a29364f75cc67d3d580833a7479", GitTreeState:"clean", BuildDate:"2021-04-08T16:31:21Z", GoVersion:"go1.16.1", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.0", GitCommit:"cb303e613a121a29364f75cc67d3d580833a7479", GitTreeState:"clean", BuildDate:"2021-04-08T16:25:06Z", GoVersion:"go1.16.1", Compiler:"gc", Platform:"linux/amd64"}
11-Podが使うネットワーク経路のプロビジョニング
ノードにスケジュールされたPodは、ノードが持つPod CIDR範囲からIPアドレスを受け取ります。この時点ではネットワーク経路が欠落しているため、Podは異なるノード上で動作している他のPodと通信できません。
本実習では、ノードのPod CIDR範囲をノードの内部IPアドレスにマップするための経路を各ワーカーノード上に作成します。
ここまでの手順におけるネットワーク関連の設定は、Controller側のコンポーネント(主にkube-apiserver)とWorker側のコンポーネント(主にkube-proxyとkubelet)間の通信を行うための設定であったため、Podのネットワーク経路は別途設定する必要があるらしい。
本実習では、寡聞な身ではあるものの過去に少し聞きかじったことのあるようなcalico
とかflannel
は本実習では使わず、GCP側のルートテーブルそのものに設定を加えてノード間通信を実現する。
具体的にはコンテナネットワークのサブネットが、ノードごとに完全に分離しているので、以下のように他ノード内コンテナネットワークのサブネットが指定された場合のネクストホップを、別ノードのNICのIPアドレスに指定する。
ノードごとにサブネットが完全に分離してない場合(複数ノードで一つのサブネットセグメントを共有したい場合など)は、この設定では複雑になりすぎるのでもう少し上手い管理が必要。
for instance in worker-{0..2}; do
gcloud compute instances describe ${instance} \
--format 'value[separator=" "](networkInterfaces[0].networkIP,metadata.items[0].value)'
done
10.240.0.20 10.200.0.0/24
10.240.0.21 10.200.1.0/24
10.240.0.22 10.200.2.0/24
NAME NETWORK DEST_RANGE NEXT_HOP PRIORITY
default-route-36119a03cfa6823e kubernetes-the-hard-way 10.240.0.0/24 kubernetes-the-hard-way 0
default-route-5380f59988ea070e kubernetes-the-hard-way 0.0.0.0/0 default-internet-gateway 1000
kubernetes-route-10-200-0-0-24 kubernetes-the-hard-way 10.200.0.0/24 10.240.0.20 1000
kubernetes-route-10-200-1-0-24 kubernetes-the-hard-way 10.200.1.0/24 10.240.0.21 1000
kubernetes-route-10-200-2-0-24 kubernetes-the-hard-way 10.200.2.0/24 10.240.0.22 1000
ネットワーク設定に限らない話だが、プロダクトスタンダードだとどのようにkubernetes環境を構築するのかを知っておく必要はあるので、それはまた別途勉強します。
12-DNSクラスターアドオンのデプロイ
本実習では、CoreDNSによってサポートされるDNSベースのサービスディスカバリを提供するアドオンを、Kubernetesクラスター内で稼働するアプリケーションに導入します。
本実習では、もうすでに構築済みのcoredns
のManifestファイルをapplyして完了する。
coredns.yaml
の中では、サービスアカウントcoredns
と、そのアカウントのロールsystem:coredns
、あとConfigMapがnamespace=kube-system
内に配備されている。
また、先述したようにWorker Nodeのkubeletのkubeconfigに記載した、clusterDNSオプションに設定したIPアドレスは、すなわちこのCoreDNSのIPアドレスであり、同一の値(10.32.0.10
)を設定する。
# ...前略...
apiVersion: v1
kind: Service
metadata:
name: kube-dns
namespace: kube-system
annotations:
prometheus.io/port: "9153"
prometheus.io/scrape: "true"
labels:
k8s-app: kube-dns
kubernetes.io/cluster-service: "true"
kubernetes.io/name: "CoreDNS"
spec:
selector:
k8s-app: kube-dns
clusterIP: 10.32.0.10
ports:
- name: dns
port: 53
protocol: UDP
- name: dns-tcp
port: 53
protocol: TCP
- name: metrics
port: 9153
protocol: TCP
CoreDNSを起動したので、実際にPodからkubernetes
API Serverの名前解決を打ち、clusterDNSが正常に動作しているかを確認する。
kubectl exec -it $(kubectl get pods -l run=busybox -o jsonpath="{.items[0].metadata.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 kubernetes.default.svc.cluster.local
clusterDNSが10.32.0.10
、kubernetes
API Serverが無事見つかったのでOK。
13-スモークテスト
省略。
14-お掃除
諸行無常。
本記事の中では、上記で作ったクラスターを元に色々操作の実習を行うので、お掃除はしない。
手順はインスタンス・ロードバランサー・ファイアウォールルール・VPC・External IPの削除を行って終わりになる。
Discussion