tl;dr Kubernetes The Hard Wayを格安VPSでお馴染みTime4VPSでやったときのメモ。
もくじ
- もくじ
- 注意事項
- Kubernetes The Hard Wayとは?
- 下ごしらえ
- TLS証明書の発行
- k8s構成ファイルの生成
- データ暗号化に関するConfig・鍵生成
- etcdクラスターの構築
- コントロールプレーンの設定 (TBD)
- ワーカーの設定 (TBD)
- kubectlの設定 (TBD)
- Pod間のルーティング設定 (TBD)
- DNS add-onの導入 (TBD)
- クラスタ全体の動作確認 (TBD)
注意事項
(絶対にやる人はいないと思うんですが)あくまでk8sの勉強用としてやったものなので、このチュートリアルを通して作成したクラスタを実運用するのは避けてください
Kubernetes The Hard Wayとは?
k8sを構成する関連コンポーネントを一つずつ手動でデプロイしていってk8sに対する理解を深めようぜ、というマゾ向けのチュートリアルです。
単純にk8sを使いたいだけならオススメしません。kubeadmやkubespray等を使ってください。
さらに、上記チュートリアルはGCPを想定しています。基本的に使用するのは通常のComputing Engineのインスタンスであるため、自前で用意したオンプレサーバーや通常のVPSとさほど変わりません。
素直にGCP上でやる方が楽なので基本的にはGCPを使用することをおすすめします。GCPでやる場合、Free tierは余裕で超えるので300ドルの無料クレジットを使う形になります。
ただ、遊ばせている格安VPSのインスタンスがもったいないので、せっかくなら有効活用していこう、というわけです。
下ごしらえ
インスタンスを立てるまで
GCPでは色々と準備が必要ですが今回使用するのはTime4VPSなので、インスタンスを起動してSSHできるようにしておけば問題ありません。
立て方について特に特筆すべき注意事項はないので、公式サイトの指示に従って立ててください。
ちなみに以下リンクからTime4VPSのインスタンスを立ててもらうと僕に多少の実験資金が降ってきます。ご協力お願いします(?)
チュートリアルでは、Master NodeとWorker Nodeそれぞれ3台ずつ、合計6台で組みます。インスタンスタイプは全てn1-standard-1 (vCPUx1, RAM3.75GB)で揃えてあるようです。
今回は、Linux16が1台余っていた他は、全て最安のLinux2インスタンス (vCPUx1, RAM2GB)で揃えました。本来であれば全て同じスペックのほうがいいのですがまあ仕方ない。
必要なツールのインストール
手元のPCに必要なソフトをインストールします。
具体的には、TLS証明書を入手するためのcfsslとcfssljson、そしてk8s APIを叩くためのkubectlを手に入れます。
TLS証明書の発行
k8sを構成する各コンポーネント(etcd, kube-apiserver, kube-controller-manager, kube-scheduler, kubelet, kube-proxy)用のTLS証明書を発行します。
GCPとちがって面倒なのは、各インスタンスのExternal IPとInternal(Local) IPを、Time4VPSのClientAreaを見ながら入力しなければならない点です。*1
さらに、GCPでやる場合はLBが生えて、そこにPublic IPが割り当てられまが、今回は普通のVPSなので基本的に全部Public IPが割り当てられてます。
dotenvやdirenv等を用いてあらかじめ環境変数にセットしておくと楽です。
非常に冗長ですが以下のような.env
を用意し、環境変数として読み込んでいることを想定します。*2
MASTER_1_EXTERNAL_IP= MASTER_1_INTERNAL_IP= MASTER_1_HOSTNAME= MASTER_2_EXTERNAL_IP= MASTER_2_INTERNAL_IP= MASTER_2_HOSTNAME= MASTER_3_EXTERNAL_IP= MASTER_3_INTERNAL_IP= MASTER_3_HOSTNAME= WORKER_1_EXTERNAL_IP= WORKER_1_INTERNAL_IP= WORKER_1_HOSTNAME= WORKER_2_EXTERNAL_IP= WORKER_2_INTERNAL_IP= WORKER_2_HOSTNAME= WORKER_3_EXTERNAL_IP= WORKER_3_INTERNAL_IP= WORKER_3_HOSTNAME=
kube-admin, kube-controller-manager, kube-proxy, kube-scheduler
チュートリアルどおりにそれぞれ実行して発行します。
service-account用のキーペア
これもチュートリアル通りに作成します。
kubelet
ここだけ若干コマンドが違います。チュートリアルではGoogle Cloud CLI経由でIPアドレスを取得していますが、今回は環境変数に押し込んだ情報をもとにコマンドを叩きます。
各Worker用にそれぞれ作成します。
for number in $(seq 1 3); do instance="WORKER_${number}" 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 HOSTNAME=$(printenv ${instance}_HOSTNAME) EXTERNAL_IP=$(printenv ${instance}_EXTERNAL_IP) INTERNAL_IP=$(printenv ${instance}_INTERNAL_IP) cfssl gencert \ -ca=ca.pem \ -ca-key=ca-key.pem \ -config=ca-config.json \ -hostname=${HOSTNAME},${EXTERNAL_IP},${INTERNAL_IP} \ -profile=kubernetes \ ${instance}-csr.json | cfssljson -bare ${instance} done
kube-apiserver
VPSなので、各インスタンスにそれぞれPublic IPが割り当てられています。そのため、チュートリアルのコマンドを若干書き換えます。
{ for number in $(seq 1 3); do instance="MASTER_${number}" KUBERNETES_PUBLIC_ADDRESS=${KUBERNETES_PUBLIC_ADDRESS},$(printenv ${instance}_EXTERNAL_IP) KUBERNETES_PRIVATE_ADDRESS=${KUBERNETES_PRIVATE_ADDRESS},$(printenv ${instance}_INTERNAL_IP) done 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=${KUBERNETES_PRIVATE_ADDRESS},${KUBERNETES_PUBLIC_ADDRESS},127.0.0.1,${KUBERNETES_HOSTNAMES} \ -profile=kubernetes \ kubernetes-csr.json | cfssljson -bare kubernetes }
証明書を各ノードへコピーする
.ssh/config
にあらかじめ色々書いておくと楽です(今更)
Host MASTER_1 ... Host MASTER_2 ... Host MASTER_3 ... Host WORKER_1 ... Host WORKER_2 ... Host WORKER_3 ...
configを設定してから、各種証明書が置いてある場所で以下を実行します。
まずはWorker Nodeへ必要な証明書を配送します。
for number in $(seq 1 3); do instance=WORKER_${number} scp ca.pem ${instance}-key.pem ${instance}.pem ${instance}:~/ done
次にMaster Nodeへ必要な証明書を配送します。
for number in $(seq 1 3); do instance=MASTER_${number} scp ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem service-account-key.pem service-account.pem ${instance}:~/ done
k8s構成ファイルの生成
kubelet
for master_number in $(seq 1 3); do master_instance="MASTER_${master_number}" KUBERNETES_PUBLIC_ADDRESS=$(printenv ${master_instance}_EXTERNAL_IP) for worker_number in $(seq 1 3); do worker_instance="WORKER_${worker_number}" 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:${worker_instance} \ --client-certificate=${worker_instance}.pem \ --client-key=${worker_instance}-key.pem \ --embed-certs=true \ --kubeconfig=${worker_instance}.kubeconfig kubectl config set-context default \ --cluster=kubernetes-the-hard-way \ --user=system:node:${worker_instance} \ --kubeconfig=${worker_instance}.kubeconfig kubectl config use-context default --kubeconfig=${worker_instance}.kubeconfig done done
kube-proxy
for master_number in $(seq 1 3); do master_instance="MASTER_${master_number}" KUBERNETES_PUBLIC_ADDRESS=$(printenv ${master_instance}_EXTERNAL_IP) 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 done
kube-controller-manager
for master_number in $(seq 1 3); do master_instance="MASTER_${master_number}" KUBERNETES_PUBLIC_ADDRESS=$(printenv ${master_instance}_EXTERNAL_IP) 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 done
kube-scheduler
for master_number in $(seq 1 3); do master_instance="MASTER_${master_number}" KUBERNETES_PUBLIC_ADDRESS=$(printenv ${master_instance}_EXTERNAL_IP) 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 done
kube-admin
for master_number in $(seq 1 3); do master_instance="MASTER_${master_number}" KUBERNETES_PUBLIC_ADDRESS=$(printenv ${master_instance}_EXTERNAL_IP) 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 done
各ノードへ配送する
for number in $(seq 1 3); do instance=WORKER_${number} scp ${instance}.kubeconfig kube-proxy.kubeconfig ${instance}:~/ done
for number in $(seq 1 3); do instance=MASTER_${number} scp admin.kubeconfig kube-controller-manager.kubeconfig kube-scheduler.kubeconfig ${instance}:~/ done
データ暗号化に関するConfig・鍵生成
鍵生成
ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)
構成ファイル
cat > encryption-config.yaml <<EOF kind: EncryptionConfig apiVersion: v1 resources: - resources: - secrets providers: - aescbc: keys: - name: key1 secret: ${ENCRYPTION_KEY} - identity: {} EOF
作成した構成ファイルを各Nodeに転送します。
for number in $(seq 1 3); do instance=MASTER_${number} scp encryption-config.yaml ${instance}:~/ done
etcdクラスターの構築
k8sのコンポーネントは基本的にステートレスなので、クラスタの状態に関するデータは全てetcdに保存されています。このチュートリアルでは3つのMaster Nodeそれぞれにetcdを入れてクラスタを組みます。
以下のコマンドは、各Master Node上で実行されることを想定されています。
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"
{ tar -xvf etcd-v3.4.0-linux-amd64.tar.gz sudo mv etcd-v3.4.0-linux-amd64/etcd* /usr/local/bin/ }
{ sudo mkdir -p /etc/etcd /var/lib/etcd sudo cp ca.pem kubernetes-key.pem kubernetes.pem /etc/etcd/ }
INTERNAL_IP=<master_node_internal_ip>
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 etcd1 \\ --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 etcd1=https://${MASTER_1_INTERNAL_IP}:2380,etcd2=https://${MASTER_2_INTERNAL_IP}:2380,etcd3=https://${MASTER_3_INTERNAL_IP}:2380 \\ --initial-cluster-state new \\ --data-dir=/var/lib/etcd Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target EOF
nameの部分は各ノードごとに変えてください。
各ノードで実行が済んだら以下コマンドを実行してetcdを有効にしてください。
{ sudo systemctl daemon-reload sudo systemctl enable etcd sudo systemctl start etcd }
動作確認
etcdのクラスターがちゃんと機能しているか確認しましょう。
MASTER_1
のノード上で以下を実行すると、各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