
はじめに
kubernetesクラスタのうえで動くモノの勉強をするためにPhotonOSでkubernetesクラスタをつくってみました。
2021年10月アタマ時点でできるだけ最新のものを利用しています。サンプルとしてnginxのHTTPコンテナを独自ドメインの証明書で終端してHTTPS化するingressのテストもしています。
自身が再利用できるように構築手順を残す目的で書きます。
やった!できた!おわり!な内容なので解説は少なめですが、各コマンドの根拠はできるだけ書くようにしました。
検証環境では、ESXi(v7.0.0)のうえに、PhotonOS(v4 Rev1)をnode用のOSとして準備してkubernetesクラスタを構築しました。
対象 | バージョン | 用途 | URL | GitHub |
---|---|---|---|---|
kubernetes kubeadm/kubectl/kubelet |
v1.22.2 | コンテナ管理 | https://kubernetes.io/ | https://github.com/kubernetes/kubernetes |
canal | flannel : v0.14.0 calico : v3.20.1 |
コンテナネットワーク ネットワークポリシー |
https://docs.projectcalico.org/getting-started/kubernetes/flannel/flannel | |
MetalLB | v0.10.2 | ServiceとしてのLoadBalancer | https://metallb.universe.tf/ | https://github.com/metallb/metallb |
NGINX Ingress | v1.0.2 | Ingressコントローラー | https://www.nginx.co.jp/products/products-nginx/kubernetes-ingress-controller/ | https://github.com/kubernetes/ingress-nginx |
Kubernetes Dashboard | v2.3.1 | リソース情報の収集/WebUI | https://kubernetes.io/ja/docs/tasks/access-application-cluster/web-ui-dashboard/ | https://github.com/kubernetes/dashboard |
canal は latest をインストールした2021年10月の時点でこのバージョンのイメージだった、というだけです。 |
作業内容
PhotonOSのデプロイと初期設定
今回、ESXiを利用するにあたり、PhotonOSを利用してみました。
**PhotonOSのGitHub**のREADMEをdeeplで翻訳すると、
Photon OSは、クラウド・ネイティブ・アプリケーション、クラウド・プラットフォーム、VMwareインフラストラクチャ向けに最適化された、オープンソースのLinuxコンテナ・ホストです。
だそうです。
インストール直後からDockerが稼働できる状態ですし、iptablesもLegacyモードなので最初のセットアップの手間があまりかかりませんでした。
今回は1台の control-plane node と 3台の worker node としてPhotonOSをESXiにデプロイして、クラスタ構築します。
すでにPhotonOS(でなくてもいいんです)が構築済みであれば**kubernetesのインストール**までスキップします。
PhotonOSをESXiにデプロイ
OVAファイルは**GitHubのDownloadページ**にダウンロードリンクがあります。
Photon OS 4.0 Rev1 というのが一番新しいので、このセクションの OVA with virtual hardware v11 のOVAをダウンロードします。
なんやかんやしてデプロイします。ゲストOSのリソースを変更するので、デプロイ後は電源OFFにしておきます。
仮想マシンをパワーオンするとアドレスがDHCPで割り当てられるので、IPアドレスを確認します。
この例では10.254.10.44
が割り当てられています。
デプロイしたPhotonOSの初期設定
割り当てられたIPアドレスにrootでSSHログインします。
初期アカウントはrootのみで、パスワードchangeme
でログインできます。
ログイン直後にパスワード変更を求められるので、適当なパスワードに変更しておきます。
$ ssh root@10.254.10.44
Password: <-- 初期パスワードは changeme
You are required to change your password immediately (administrator enforced).
Changing password for root.
Current password: <-- changeme
New password: <-- 新しいパスワードを入力
Retype new password: <-- 新しいパスワードを再度入力
03:09:48 up 1 min, 0 users, load average: 0.02, 0.01, 0.00
tdnf update info not available yet!
root@photon-machine [ ~ ]#
基本的な設定として以下を実施します。
ネットワーク設定は1台ごとに異なるため、修正して流し込みます。
- ネットワーク設定
- アドレス設定をDHCPからスタティックに変更
- ホスト名の変更
- ネットワークサービスの再起動
#######################
# ノードごとの設定項目
#######################
Hostname=pos01
Domain=example.com
Address=10.254.10.121/24
Gateway=10.254.10.254
DNS=10.254.10.241
#######################
# ここから先は共通
#######################
# ネットワークの設定ファイルを新規作成
cat <<EOF > /etc/systemd/network/10-static-en.network
[Match]
Name=eth0
[Network]
Address=${Address}
Gateway=${Gateway}
Domains=${Domain}
DNS=${DNS}
EOF
chmod 644 /etc/systemd/network/10-static-en.network
# DHCPの設定ファイルを削除
rm -f /etc/systemd/network/99-dhcp-en.network
# ホスト名の設定
hostnamectl set-hostname ${Hostname}.${Domain}
# アドレス変更が反映されて疎通できなくなるので、裏側でセッションを切る
systemctl restart systemd-networkd & exit
これでネットワーク設定が更新されました。
ホスト名が引けない環境であれば、hostsファイルに対象ノード分のアドレスを追記しておきます。
cat <<EOF >> /etc/hosts
10.254.10.121 pos01 pos01.example.com
10.254.10.122 pos02 pos02.example.com
10.254.10.123 pos03 pos03.example.com
10.254.10.124 pos04 pos04.example.com
EOF
ここまでの作業を必要なノードの台数分実施します。(今回は4台にしました)
kubernetesのインストール
このセクションの作業は全ノードで必要です
「kubernetesのインストール」も「kubeadmのインストール」も、なんかしっくりこないですが、公式ドキュメントにはそう書いてあるので、それに倣います。
大体この公式ドキュメントに沿って作業していきます。
システム設定
パッケージのアップグレードと必要なものをインストールします。
これ以外にも使うものがありますが、最初からインストール済みなので指定していません。
tdnf upgrade -y
tdnf install -y tar conntrack ebtables ethtool socat
システムの設定をします。
このあたりは公式ドキュメントそのままです。
echo -e "export br_netfilter" > /etc/modules-load.d/k8s.conf
echo -e "net.bridge.bridge-nf-call-ip6tables = 1\nnet.ipv4.ip_forward = 1" > /etc/sysctl.d/k8s.conf
sysctl --system
kubernetesに必要なポートは以下のドキュメントを参照します。
英語と日本語のドキュメントで記載が異なりますが、よくわからないので両方採用します。
たぶんバージョンが違うんだと思います。
https://kubernetes.io/docs/reference/ports-and-protocols/
https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
CNIとして利用するcanalに必要なポートは以下のドキュメントを参照します。
使わなさそうなものもありますが、記載されてるものはひとまず網羅しておきます。
https://docs.projectcalico.org/getting-started/kubernetes/requirements
オフィシャルじゃないですが、flannelのポート情報が記載されていたので、これも含めちゃいます。
https://github.com/coreos/coreos-kubernetes/blob/master/Documentation/kubernetes-networking.md
LoadBalancerとして利用するMetalLBに必要なポートは以下のドキュメントを参照します。
https://metallb.universe.tf/#requirements
必要なポートをiptablesで設定して設定を保存します。
control-plane nodeのみでしか使わないポートが多いですが面倒なので全ノードで共通にします。
それでも面倒なら、検証用だしノーガードでもいいんじゃなかろうか。
# kubernetes用
iptables -A INPUT -p tcp --dport 443 -m state --state NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 6443 -m state --state NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 2379:2380 -m state --state NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 10250:10259 -m state --state NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 30000:32767 -m state --state NEW -j ACCEPT
# canal用
iptables -A INPUT -p tcp --dport 179 -m state --state NEW -j ACCEPT
iptables -A INPUT -p udp --dport 4789 -j ACCEPT
iptables -A INPUT -p tcp --dport 5473 -m state --state NEW -j ACCEPT
iptables -A INPUT -p udp --dport 8285 -j ACCEPT
iptables -A INPUT -p udp --dport 8472 -j ACCEPT
# flannel用(canalのドキュメントにはないが入れておく)
iptables -A INPUT -p tcp --dport 7946 -m state --state NEW -j ACCEPT
iptables -A INPUT -p udp --dport 7946 -j ACCEPT
# 再起動しても設定が残るようにする
iptables-save > /etc/systemd/scripts/ip4save
kubeadm / kubectl / kubelet インストール
以下ドキュメントにしたがってkubeadmをインストールします。
日本語ドキュメントと英語ドキュメントでバイナリを保管するパスが違ってたり、指定されているバージョンがちょっと古かったりしていたので、ドキュメントの内容からちょっとだけ変更しています。
CNI ( Container Network Interface )
CNIプラグインの最新バージョンを以下で確認しておきます。
将来的にコマンドラインを使いまわせるように、最新バージョンを一時変数にいれてコマンド発行します。
https://github.com/containernetworking/plugins/releases/
VERSION_CNI=v1.0.1
mkdir -p /opt/cni/bin
curl -sSL https://github.com/containernetworking/plugins/releases/download/${VERSION_CNI}/cni-plugins-linux-amd64-${VERSION_CNI}.tgz | tar -C /opt/cni/bin -xz
CRI ( Container Runtime Interface )
CLI群を提供するCRIの最新バージョンを以下で確認しておきます。
将来的にコマンドラインを使いまわせるように、最新バージョンを一時変数にいれてコマンド発行します。
https://github.com/kubernetes-sigs/cri-tools/releases
VERSION_CRICTL=v1.22.0
curl -sSL https://github.com/kubernetes-sigs/cri-tools/releases/download/${VERSION_CRICTL}/crictl-${VERSION_CRICTL}-linux-amd64.tar.gz | tar -C /usr/local/bin -xz
kubeadm / kubelet / kubectl
kubernetesの最新バージョンを以下で確認しておきます。
将来的にコマンドラインを使いまわせるように、最新バージョンを一時変数にいれてコマンド発行します。
https://dl.k8s.io/release/stable.txt
VERSION_K8S=v1.22.2
mkdir -p /usr/local/bin
cd /usr/local/bin
curl -sSL --remote-name-all https://storage.googleapis.com/kubernetes-release/release/${VERSION_K8S}/bin/linux/amd64/{kubeadm,kubelet,kubectl}
chmod +x {kubeadm,kubelet,kubectl}
kubeletのサービス設定
kubeletをサービス化するためのファイルを取得し、サービス登録と開始の設定をします。
問答無用で最新バージョンを取得してしまいます。
curl -sSL "https://raw.githubusercontent.com/kubernetes/release/master/cmd/kubepkg/templates/latest/deb/kubelet/lib/systemd/system/kubelet.service" | sed "s:/usr/bin:/usr/local/bin:g" > /etc/systemd/system/kubelet.service
mkdir -p /etc/systemd/system/kubelet.service.d
curl -sSL "https://raw.githubusercontent.com/kubernetes/release/master/cmd/kubepkg/templates/latest/deb/kubeadm/10-kubeadm.conf" | sed "s:/usr/bin:/usr/local/bin:g" > /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
echo 'KUBELET_EXTRA_ARGS="--cgroup-driver=systemd --image-pull-progress-deadline=10m"' > /etc/default/kubelet
systemctl enable kubelet
systemctl start kubelet
dockerのサービス設定
dockerのcgroup driverがデフォルトのcgroupfsだとうまく動作しなかったので、systemdに変更しています。
これでdockerのサービス登録と開始の設定をします。
mkdir /etc/docker
echo '{ "exec-opts": ["native.cgroupdriver=systemd"] }' > /etc/docker/daemon.json
systemctl enable docker
systemctl start docker
再起動
ここまで設定したら一度システムを再起動しておきます。
shutdown -r now
master node ( control-plane node ) の準備
このセクションはcontrol-plane nodeでの作業です
kubernetesクラスタのマスターノードに初期設定を行います。
ドキュメント的にはコントロールプレーンノードって言うっぽいです。
kubeadm init によるクラスタの構築
podが利用するネットワークを明示的に指定します。今回は 10.244.0.0/16
を指定します。
# kubeadm initすると生成されるadmin.confをあらかじめ読み込むようにしておく
echo -e "export KUBECONFIG=/etc/kubernetes/admin.conf" > .bash_profile
source .bash_profile
# podが利用するネットワーク情報は一時変数に格納しておく
POD_NETWORK_CIDR=10.244.0.0/16
# join用のトークンはあとで表示させるので、最初は表示させなくてOK(表示してもよいですが)
kubeadm init --pod-network-cidr=${POD_NETWORK_CIDR} --skip-token-print
# control-plane用のpodが起動するまで待つ
kubectl wait -n kube-system pod -l tier=control-plane --for=condition=ready --timeout=60s
kubeadmでの初期化が完了すると kube-system
というネームスペースにポッドが生成されます。
corednsのポッド以外がRunningになるのを確認してから次のステップに進みます。
root@pos01 [ ~ ]# kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-78fcd69978-5pq2m 0/1 Pending 0 87s
kube-system coredns-78fcd69978-vcnfl 0/1 Pending 0 87s
kube-system etcd-pos01.example.com 1/1 Running 0 102s
kube-system kube-apiserver-pos01.example.com 1/1 Running 0 102s
kube-system kube-controller-manager-pos01.example.com 1/1 Running 0 103s
kube-system kube-proxy-fv684 1/1 Running 0 87s
kube-system kube-scheduler-pos01.example.com 1/1 Running 0 104s
canal のインストール
コンテナ間のネットワークを動作させるためのCNI( Container Network Interface ) をインストールします。
以下のドキュメントに従って canal をインストールしました。
# podネットワークが 10.244.0.0/16 なのでそのままインストールできるはずなのですが、動作が怪しかったので修正しておきます
curl -sSL https://docs.projectcalico.org/manifests/canal.yaml | sed 's:#\ \(-\ name.\ CALICO_IPV4POOL_CIDR\):\1:g' | sed 's:#\ \(\ \ value. \)\(\"192.168.0.0\/16\"\):\1${POD_NETWORK_CIDR}:g' | kubectl apply -f - ;
# calicoのcontrollerとcorednsのpodが起動するまで待つ
kubectl wait -n kube-system pod -l k8s-app=calico-kube-controllers --for=condition=ready --timeout=60s
kubectl wait -n kube-system pod -l k8s-app=kube-dns --for=condition=ready --timeout=60s
全部のポッドがRunningになるのを確認してから次のステップに進みます。
root@pos01 [ ~ ]# kubectl get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system calico-kube-controllers-74b8fbdb46-nhnxw 1/1 Running 0 61s
kube-system canal-svdqf 2/2 Running 0 61s
kube-system coredns-78fcd69978-5pq2m 1/1 Running 0 30m
kube-system coredns-78fcd69978-vcnfl 1/1 Running 0 30m
kube-system etcd-pos01.example.com 1/1 Running 0 30m
kube-system kube-apiserver-pos01.example.com 1/1 Running 0 30m
kube-system kube-controller-manager-pos01.example.com 1/1 Running 0 30m
kube-system kube-proxy-fv684 1/1 Running 0 30m
kube-system kube-scheduler-pos01.example.com 1/1 Running 0 30m
次は他ノードをこのクラスタにjoinする作業を実施します。
worker node ( non control-plane node ) をクラスタにjoinする
じょいんじょいん
control-plane nodeでトークン付きコマンドを取得
まず、control-plane nodeでトークン付きコマンドを取得します。
トークンには有効期限があるようです。(24時間?)
root@pos01 [ ~ ]# kubeadm token create --print-join-command
kubeadm join 10.254.10.121:6443 --token qho156.cgetkgpr92ga1dlv \
--discovery-token-ca-cert-hash sha256:dcc69c052df1acc5436c2e07009fd07a2c20d3dbeca3f0b7c74c224824da8075
worker nodeからJOINする
次に、取得したトークン付きコマンドを各worker node上でコマンド発行します。
root@pos02 [ ~ ]# kubeadm join 10.254.10.121:6443 --token qho156.cgetkgpr92ga1dlv \
--discovery-token-ca-cert-hash sha256:dcc69c052df1acc5436c2e07009fd07a2c20d3dbeca3f0b7c74c224824da8075
[preflight] Running pre-flight checks
( ... たくさんログがでる ... )
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.
Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
control-plane nodeでちゃんとJOINしたか確認
control-plane 以外のすべてのノードでjoinコマンドが発行されたら、ノードの数と同じだけ kube-proxy
と canal
のpodが生成されます。
kubectl wait -n kube-system pod -l k8s-app=kube-proxy --for=condition=ready --timeout=60s
kubectl wait -n kube-system pod -l k8s-app=canal --for=condition=ready --timeout=60s
全部のノードがReadyになっていて、全部のポッドがRunningになるのを確認してから次のステップに進みます。
root@pos01 [ ~ ]# kubectl get node,pods -A
NAME STATUS ROLES AGE VERSION
node/pos01.example.com Ready control-plane,master 37m v1.22.2
node/pos02.example.com Ready <none> 4m41s v1.22.2
node/pos03.example.com Ready <none> 114s v1.22.2
node/pos04.example.com Ready <none> 81s v1.22.2
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system pod/calico-kube-controllers-74b8fbdb46-nhnxw 1/1 Running 0 8m3s
kube-system pod/canal-h9hs5 2/2 Running 0 114s
kube-system pod/canal-l8hn5 2/2 Running 0 4m41s
kube-system pod/canal-lwpd8 2/2 Running 0 81s
kube-system pod/canal-svdqf 2/2 Running 0 8m3s
kube-system pod/coredns-78fcd69978-5pq2m 1/1 Running 0 37m
kube-system pod/coredns-78fcd69978-vcnfl 1/1 Running 0 37m
kube-system pod/etcd-pos01.example.com 1/1 Running 0 37m
kube-system pod/kube-apiserver-pos01.example.com 1/1 Running 0 37m
kube-system pod/kube-controller-manager-pos01.example.com 1/1 Running 0 37m
kube-system pod/kube-proxy-8vsx7 1/1 Running 0 81s
kube-system pod/kube-proxy-fv684 1/1 Running 0 37m
kube-system pod/kube-proxy-g4bk7 1/1 Running 0 4m41s
kube-system pod/kube-proxy-rbz9j 1/1 Running 0 114s
kube-system pod/kube-scheduler-pos01.example.com 1/1 Running 0 37m
metrics-server
**Metris Server**は、kubernetesからのリソース情報を収集して提供するものです。リソース状況を調査する手段である kubectl top pods
と kubectl top nodes
がよく利用されているようです。
metrics-serverのインストール
**公式ドキュメント**にあるとおり、 --kubelet-insecure-tls
と hostNetwork: true
を適用したものをデプロイします。
# --kubelet-insecure-tls と hostNetwork: true を追加
curl -sSL https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml | sed 's/\(^ *image:\)/ - --kubelet-insecure-tls\n\1/' | sed 's/\(^ *containers:\)/ hostNetwork: true\n\1/' | kubectl apply -f -
# metrics serverのpodが起動するまで待つ
kubectl wait -n kube-system pod -l k8s-app=metrics-server --for=condition=ready --timeout=60s
metrics-serverでCPUとメモリの情報を見る
メトリクスが取得できているか確認します。デプロイしてから1分くらいかかりますので、ちょっと待ってください。
root@pos01 [ ~ ]# kubectl top node
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
pos01.example.com 157m 7% 1574Mi 40%
pos02.example.com 50m 2% 825Mi 21%
pos03.example.com 48m 2% 907Mi 23%
pos04.example.com 48m 2% 827Mi 21%
root@pos01 [ ~ ]# kubectl top pods -A
NAMESPACE NAME CPU(cores) MEMORY(bytes)
ingress-nginx ingress-nginx-controller-74cb6699df-s2glc 1m 77Mi
kube-system calico-kube-controllers-74b8fbdb46-nhnxw 1m 21Mi
kube-system canal-h9hs5 13m 112Mi
kube-system canal-l8hn5 13m 111Mi
kube-system canal-lwpd8 15m 116Mi
kube-system canal-svdqf 18m 119Mi
kube-system coredns-78fcd69978-5pq2m 1m 19Mi
kube-system coredns-78fcd69978-vcnfl 1m 19Mi
kube-system etcd-pos01.example.com 10m 64Mi
kube-system kube-apiserver-pos01.example.com 46m 491Mi
kube-system kube-controller-manager-pos01.example.com 14m 65Mi
kube-system kube-proxy-8vsx7 1m 18Mi
kube-system kube-proxy-fv684 1m 20Mi
kube-system kube-proxy-g4bk7 1m 19Mi
kube-system kube-proxy-rbz9j 1m 19Mi
kube-system kube-scheduler-pos01.example.com 2m 26Mi
kube-system metrics-server-7f55f7bd5-rtx6b 2m 18Mi
kubernetes-dashboard dashboard-metrics-scraper-856586f554-dxhxh 1m 14Mi
kubernetes-dashboard kubernetes-dashboard-67484c44f6-dnxvj 1m 19Mi
metallb-system controller-6b78bff7d9-p4cbg 1m 16Mi
metallb-system speaker-5qnx5 2m 17Mi
metallb-system speaker-gvkgn 2m 19Mi
metallb-system speaker-lg5hd 2m 18Mi
metallb-system speaker-lp8dj 2m 20Mi
NGINX Ingress 導入
**Kubernetes Ingressは、負荷分散、SSL終端、名前ベースの仮想ホスティングの機能を提供する、クラスター内のServiceに対する外部からのアクセス(主にHTTP)を管理するAPIオブジェクト。
だそうです。この実装の一つがNGINX Ingress Controller**で、NGINXをリバースプロキシとロードバランサとして使う、kubernetes用のIngressコントローラー。とのことで、L7でルーティングしたり、SSLの肩代わりしたり、などのいろいろ機能がある、ってことなんでしょうか。
NGINX Ingressは Bare-metal用の実装もあるみたいなのですが、NodePortが前提のようでClusterIPのままでは外に出れません。
なので、 Type=LoadBalancer
の実装であるMetalLBといっしょに使います。
MetalLBのインストール
**MetalLB**は、標準的なルーティングプロトコルを利用したベアメタルkubernetesクラスタ用のロードバランサ実装。
らしいです。
どうやらkubernetesはベアメタルクラスター用のType=LoadBalancerのServiceの実装を提供していないそうな。
外部からアクセス可能にする手段としてはNodePortとexternalIPsが準備されてはいるけれども、ぜんぜんイケてないから俺たちが作ってやるぜ!みたいな感じですか。
ここでは、Layer2モード、という、IP ⇔ worker nodeという固定的な割り当ての代替となる機能を利用します。
MetalLBがプールしたIPに対するARP要求は、MetalLBによってnodeごとに用意されたspeakerが応答することによって、適切なnodeを紐づける動作のようです。
以下ドキュメントにしたがってMetalLB本体をインストールします。
https://metallb.universe.tf/installation/
# 公式ドキュメントのとおり
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.2/manifests/namespace.yaml
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.2/manifests/metallb.yaml
# MetalLBのpodが起動するまで待つ
kubectl wait -n metallb-system pod -l app=metallb --for=condition=ready --timeout=60s
MetalLBのネームスペースにあるpodがすべてRunningになっていることを確認してから次のステップに進みます。
root@pos01 [ ~ ]# kubectl get pods -n metallb-system
NAME READY STATUS RESTARTS AGE
controller-6b78bff7d9-p4cbg 1/1 Running 0 80s
speaker-5qnx5 1/1 Running 0 80s
speaker-gvkgn 1/1 Running 0 80s
speaker-lg5hd 1/1 Running 0 80s
speaker-lp8dj 1/1 Running 0 80s
次に、Layer2モードで動作させるために、外部に露出するアドレス範囲を指定します。
構築パラメータの通り 10.254.10.181-10.254.10.199
を指定します。
以下の公式ドキュメントのとおりに設定します。
https://metallb.universe.tf/configuration/#layer-2-configuration
INGRESS_ADDRESS=10.254.10.181-10.254.10.199
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: default
protocol: layer2
addresses:
- ${INGRESS_ADDRESS}
EOF
なお、MetalLBのVer.0.10以前では memberlist という secret の作成が必要でしたが、現在のリリースでは自動的に作成してくれるようになっています。
https://metallb.universe.tf/release-notes/#version-0-10-0
NGINX Ingress Controller のインストール
以下ドキュメントにしたがってNGINX Ingress Controllerをインストールします。
https://kubernetes.github.io/ingress-nginx/deploy/
後述する**dashboardのIngress設定**でSSLパススルー機能を利用したいので、以下ページのようにapply後にフラグを追加します。
https://github.com/kubernetes/ingress-nginx/issues/6722
# 公式ドキュメントのとおり
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.0.2/deploy/static/provider/cloud/deploy.yaml
# --enable-ssl-passthroughを引数に追加
kubectl patch deployment -n ingress-nginx ingress-nginx-controller --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--enable-ssl-passthrough"}]'
# NGINX Ingressのpodが起動するまで待つ
kubectl wait -n ingress-nginx --for=condition=ready pod --selector=app.kubernetes.io/component=controller --timeout=60s
ingress-nginx-controllerのpodがRunningになっていることを確認してから次のステップに進みます。
また、EXTERNAL-IPにMetalLBのLayer2モードに設定したアドレスが割り当てられていることが確認できます。
root@pos01 [ ~ ]# kubectl get pods,service -n ingress-nginx
NAME READY STATUS RESTARTS AGE
pod/ingress-nginx-admission-create--1-vmz5x 0/1 Completed 0 3m32s
pod/ingress-nginx-admission-patch--1-vfpwq 0/1 Completed 0 3m32s
pod/ingress-nginx-controller-74cb6699df-j7lc7 1/1 Running 0 3m32s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/ingress-nginx-controller LoadBalancer 10.108.66.241 10.254.10.181 80:30874/TCP,443:31509/TCP 3m32s
service/ingress-nginx-controller-admission ClusterIP 10.103.25.101 <none> 443/TCP 3m32s
このままではWebhookのValidationによりIngressがうまく動作できませんでした。手っ取り早く回避するために、下記URLの通りWebhookのvaridatingを削除します。
https://stackoverflow.com/questions/61616203/nginx-ingress-controller-failed-calling-webhook
kubectl delete -A ValidatingWebhookConfiguration ingress-nginx-admission
サンプル用のIngress
動作を理解するためのIngressサンプルです。
HTTP(80/tcp)で稼働するnginxのPod(ここではDeploymentで定義されるcontainers)を生成します。それをServiceとしてClusterIPの80/tcpでアクセスできるようにしていますが、ClusterIPなので、外とは直接通信できません。
なので、これをIngressで外部からアクセスできるようにして、なおかつHTTPSでも待ち受けられるようにしています。バックエンドはHTTPですがHTTPSでもアクセスできるようになります。
まず、サーバー証明書と秘密鍵を作成してsecretに登録をします。
ルートまでたどれるサーバー証明書があらかじめ準備できるならば、kubectl create secret ~
の登録の部分だけで大丈夫です
# CNはwww.example.comとしています
CN=www.example.com
openssl req -new -newkey rsa:2048 -sha256 -nodes -outform PEM -keyform PEM -out ${CN}.csr -keyout ${CN}.key -subj "/C=JP/ST=Tokyo/O=${CN}/CN=${CN}"
openssl x509 -req -days 30 -signkey ${CN}.key < ${CN}.csr > ${CN}.crt
# これをtlsのsecretとして登録する
kubectl create secret tls ${CN} --cert=${CN}.crt --key=${CN}.key
次に、secretに登録したサーバー証明書と秘密鍵を利用してIngressを作成します。
ついでにこのIngressのバックエンドとなるDeployment / Service もまとめてファイルとして作成します。
cat <<EOF > sample-ingress_${CN}.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.17
ports:
- containerPort: 80
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: ClusterIP
ports:
- port: 80
selector:
app: nginx
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
tls:
- hosts:
- ${CN}
secretName: ${CN}
rules:
- host: ${CN}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80
EOF
これを適用します。
kubectl apply -f sample-ingress_${CN}.yaml
状態を確認します。
root@pos01 [ ~ ]# kubectl get pod,deployment,service,ingress -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/nginx-deployment-db749865c-67n8w 1/1 Running 0 4h23m 10.244.2.5 pos03.example.com <none> <none>
pod/nginx-deployment-db749865c-k865l 1/1 Running 0 4h23m 10.244.1.7 pos02.example.com <none> <none>
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/nginx-deployment 2/2 2 2 4h23m nginx nginx:1.17 app=nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 5h15m <none>
service/nginx-service ClusterIP 10.103.225.66 <none> 80/TCP 4h23m app=nginx
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress.networking.k8s.io/nginx-ingress <none> www.example.com 10.254.10.181 80, 443 4h23m
では、アクセスできるか curl で確認してみます。これは適当なクラスタ外のホストからテストします。
なおCNとして設定したFQDNの www.example.com
は、アドレスを調べてからhostsファイルに追記して動作確認します。先ほどの結果からアドレスが 10.254.10.181
ということがわかるので、これをhostsファイルに追記します。ADDRESS欄にアドレスが表示されるまで、1分程度かかる場合があります。表示されない場合は、少し時間をおいてから再試行します。
# CN=www.example.com としたので、これに対応する行を追加します
10.254.10.181 www.example.com
curlでページ情報を取得してみましょう。HTTPでもHTTPSでもアクセスできます。
アクセスできないときは一度podを削除してみます。(→**サンプル用のIngressにアクセスできない**)
~$ curl -I http://www.example.com/
HTTP/1.1 200 OK
Date: Fri, 01 Oct 2021 11:31:26 GMT
Content-Type: text/html
Content-Length: 612
Connection: keep-alive
Last-Modified: Tue, 14 Apr 2020 14:19:26 GMT
ETag: "5e95c66e-264"
Accept-Ranges: bytes
~$ curl -Ik https://www.example.com/
HTTP/2 200
date: Fri, 01 Oct 2021 11:31:36 GMT
content-type: text/html
content-length: 612
last-modified: Tue, 14 Apr 2020 14:19:26 GMT
etag: "5e95c66e-264"
accept-ranges: bytes
strict-transport-security: max-age=15724800; includeSubDomains
nginx.ingress.kubernetes.io/ssl-redirect: "false"
の部分を
nginx.ingress.kubernetes.io/ssl-redirect: "true"
に変更すると
HTTPアクセスはHTTPSにリダイレクトされます。
root@pos01 [ ~ ]# sed ./sample-ingress_${CN}.yaml -e 's/\(ssl-redirect: \).*/\1"true"/' | kubectl apply -f -
deployment.apps/nginx-deployment unchanged
service/nginx-service unchanged
ingress.networking.k8s.io/nginx-ingress configured
これで先ほどと同様にcurlでアクセスしてみると、HTTPがHTTPSへリダイレクトされていることがわかります。
~$ curl -IkL http://www.example.com/
HTTP/1.1 308 Permanent Redirect
Date: Fri, 01 Oct 2021 11:32:44 GMT
Content-Type: text/html
Content-Length: 164
Connection: keep-alive
Location: https://www.example.com
HTTP/2 200
date: Fri, 01 Oct 2021 11:32:44 GMT
content-type: text/html
content-length: 612
last-modified: Tue, 14 Apr 2020 14:19:26 GMT
etag: "5e95c66e-264"
accept-ranges: bytes
strict-transport-security: max-age=15724800; includeSubDomains
証明書が自己署名のサーバー証明書かどうか確認するために openssl で接続してみます。
先ほど生成した証明書が取得できていることがわかります。
$ openssl s_client -connect www.example.com:443 -quiet
depth=0 C = JP, ST = Tokyo, O = example, CN = www.example.com
verify error:num=18:self signed certificate
verify return:1
depth=0 C = JP, ST = Tokyo, O = example, CN = www.example.com
verify return:1
^C
サンプル用のIngressにアクセスできない
理由はよくわからないのですが、504になってしまい、うまくアクセスできない場合があります。podが特定のnodeにデプロイされるとこの症状が出るようなので、別のnodeにデプロイされるまで削除しましょう。が、なぜそうなるのか理由がよくわかりません。。。
$ curl -I http://www.example.com/
HTTP/1.1 504 Gateway Time-out
Date: Sat, 02 Oct 2021 10:16:21 GMT
Content-Type: text/html
Content-Length: 160
Connection: keep-alive
このときのpodはpos04とpos02に配置されています。
root@pos01 [ ~ ]# kubectl get pod -n default -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-db749865c-htsb5 1/1 Running 0 28s 10.244.3.4 pos04.example.com <none> <none>
nginx-deployment-db749865c-kvw8g 1/1 Running 0 27s 10.244.1.4 pos02.example.com <none> <none>
podを削除すると、deploymentによってpodが自動的に生成されるので、 kubectl delete pod
で削除して配置を見てみましょう。
root@pos01 [ ~ ]# kubectl delete pod -n default --all
pod "nginx-deployment-db749865c-htsb5" deleted
pod "nginx-deployment-db749865c-kvw8g" deleted
root@pos01 [ ~ ]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-db749865c-sd4bh 1/1 Running 0 1s 10.244.2.7 pos03.example.com <none> <none>
nginx-deployment-db749865c-z7g74 1/1 Running 0 1s 10.244.2.8 pos03.example.com <none> <none>
nodeがpos04とpos02からpos03に変わりましたので、再度アクセスしてみます。
$ curl -I http://www.example.com/
HTTP/1.1 200 OK
Date: Sat, 02 Oct 2021 10:20:28 GMT
Content-Type: text/html
Content-Length: 612
Connection: keep-alive
Last-Modified: Tue, 14 Apr 2020 14:19:26 GMT
ETag: "5e95c66e-264"
Accept-Ranges: bytes
どっかのiptablesのフィルタリングかフォワードルールかが、おかしくなってしまっているんでしょうか?でもそこまで詳しくないので調べ切れていません。。。
Kubernetes Dashboard の導入
**Kubernetes Dashboard**は、kubernetesクラスタのための一般的はWebベースのUIで、クラスタ内のアプリケーション管理やトラブルシュート、クラスタ自体の管理もすることができます。
だそうです。
dashboardのインストール
以下ドキュメントにしたがってKubernetes Dashboardをインストールします。
https://github.com/kubernetes/dashboard#install
# 公式ドキュメントの通りにインストール
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.3.1/aio/deploy/recommended.yaml
# dashboardのpodが起動するまで待つ
kubectl wait -n kubernetes-dashboard pod --all --for=condition=ready --timeout=60s
全部のポッドがRunningになるのを確認してから次のステップに進みます。
root@pos01 [ ~ ]# kubectl get pods -n kubernetes-dashboard
NAME READY STATUS RESTARTS AGE
dashboard-metrics-scraper-856586f554-dxhxh 1/1 Running 0 12s
kubernetes-dashboard-67484c44f6-dnxvj 1/1 Running 0 12s
サービスアカウントとして権限を付与した管理者ユーザを作成し、そのユーザーに紐づいたログイン用のトークンを利用してdashboardにログインさせます。
ということが以下のドキュメントに書いてあります。
https://github.com/kubernetes/dashboard/blob/master/docs/user/access-control/creating-sample-user.md
# サービスアカウントとして管理者ユーザを作成
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kubernetes-dashboard
EOF
# そのユーザーにクラスタ管理者という役割を与えています。
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kubernetes-dashboard
EOF
dashboard用のIngress設定
ただDashboardをデプロイするだけならこれで終了です。が、公式のyamlをそのまま適用させるとServiceがClulsterIPになるため、外部からアクセスすることができません。
**Accessing Dashboard**では、kubectl proxy
や kubectl port-forward
で外部からアクセスさせたり、ServiceをNodePortにする解説があります。Ingressの解説は「Ingressのドキュメントみてね」で終わってるので、これを実際にやってみようと思います。
もともとDashboardのServieはHTTPSで動作していますので、ssl-passthroughでSSL終端はDashboard側にしてもらうことにしましょう。tlsのセクションにsecretを指定せず、annotetionsのセクションにssl-passthrouhを追加すればOKです。
以下の内容でIngressを設定します。
# Dashboard用のCNは検証用のものを利用
CN=dashboard.example.com
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: dashboard-ingress
namespace: kubernetes-dashboard
labels:
k8s-app: kubernetes-dashboard
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
tls:
- hosts:
- ${CN}
rules:
- host: ${CN}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: kubernetes-dashboard
port:
number: 443
EOF
ingressの情報を取得してホスト名が設定されている通りかどうか確認します。
root@pos01 [ ~ ]# kubectl get ingress -n kubernetes-dashboard
NAME CLASS HOSTS ADDRESS PORTS AGE
dashboard-ingress <none> dashboard.example.com 10.254.10.181 80, 443 4h43m
サンプル用のIngressと同様に、CNとして設定したFQDNのdashboard.example.com
に対応する10.254.10.181
をhostsファイルに追記します。
# CN=dashboard.example.com としたので、これに対応する行を追加します
10.254.10.181 dashboard.example.com
curlでページ情報を取得してみましょう。
アクセスできないときは先ほどと同様にpodが配置されているnodeが変わるまでpodを削除してみます。(→**dashboardにアクセスできない**)
~$ curl -Ik https://dashboard.example.com/
HTTP/2 200
accept-ranges: bytes
cache-control: no-cache, no-store, must-revalidate
content-type: text/html; charset=utf-8
last-modified: Wed, 16 Jun 2021 10:53:38 GMT
content-length: 1338
date: Fri, 01 Oct 2021 13:26:22 GMT
サーバー証明書の発行元を確認するために openssl で接続してみます。
Dashboardが発行した証明書が取得できているように見えます。が、中身がカラでよくわかんないですね。
~$ openssl s_client -connect dashboard.example.com:443 -quiet
depth=0
verify error:num=18:self signed certificate
verify return:1
depth=0
verify return:1
^C
ブラウザからアクセスすると認証画面が表示されます。証明書の発行者やサブジェクトは空欄になっています。
ログインするためのトークンは、control-plane node から以下のように取得します。
root@pos01 [ ~ ]# kubectl -n kubernetes-dashboard get secret $(kubectl -n kubernetes-dashboard get sa/admin-user -o jsonpath="{.secrets[0].name}") -o go-template="{{.data.token | base64decode}}"
eyJhbGci( ... トークン文字列をコピーする ... )tNTZ
ログイン直後はdefaultのネームスペースが対象となっているので、先ほどのサンプル用のIngressの情報が表示されています。
上部のメニュで「すべてのネームスペース」を選択すればたくさん表示されます。metrics-serverを導入しているため、CPUとメモリのグラフも見れるようになります。
ダッシュボードまで見えるのに、CPU/Memoryのグラフが表示されない、というときは、metrics-serverのpodが配置されているnodeを疑ってみます。(→**dashboardにアクセスできない**)
ちなみに、先ほどのSSL-passthroughをしないと、どうなるでしょうか。ingressのannotationsの部分を直接書き換えてみましょう。
kubectl patch ingress -n kubernetes-dashboard dashboard-ingress -p '{"metadata":{"annotations":{"nginx.ingress.kubernetes.io/ssl-passthrough":"false"}}}'
なるほど。Ingressが代わりに証明書を作ってくれるんですね。
~$ openssl s_client -connect dashboard.example.com:443 -quiet
depth=0 O = Acme Co, CN = Kubernetes Ingress Controller Fake Certificate
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 O = Acme Co, CN = Kubernetes Ingress Controller Fake Certificate
verify error:num=21:unable to verify the first certificate
verify return:1
^C
dashboardにアクセスできない
サンプル用Ingressと同様に、うまくアクセスできない時はpodが配置される場所が変更されるまで削除すると、うまくいく場合があります。
$ curl -Ik https://dashboard.example.com/
curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to dashboard.example.com:443
このときのdashboardのpodはpos04に配置されています。
root@pos01 [ ~ ]# kubectl get pod -n kubernetes-dashboard -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
dashboard-metrics-scraper-856586f554-flq4p 1/1 Running 0 15m 10.244.1.3 pos02.example.com <none> <none>
kubernetes-dashboard-67484c44f6-9c2qj 1/1 Running 0 15m 10.244.3.3 pos04.example.com <none> <none>
podを削除すると、deploymentによってpodが自動的に生成されるので、 kubectl delete pod
で削除して配置を見てみましょう。
root@pos01 [ ~ ]# kubectl delete pod -n kubernetes-dashboard --all
pod "dashboard-metrics-scraper-856586f554-flq4p" deleted
pod "kubernetes-dashboard-67484c44f6-9c2qj" deleted
root@pos01 [ ~ ]# kubectl get pod -n kubernetes-dashboard -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
dashboard-metrics-scraper-856586f554-8lxzh 1/1 Running 0 34s 10.244.2.10 pos03.example.com <none> <none>
kubernetes-dashboard-67484c44f6-9ncmk 1/1 Running 0 34s 10.244.2.11 pos03.example.com <none> <none>
nodeがpos04とpos02からpos03に変わりましたので、再度アクセスしてみます。
$ curl -Ik https://dashboard.example.com/
HTTP/2 200
accept-ranges: bytes
cache-control: no-cache, no-store, must-revalidate
content-type: text/html; charset=utf-8
last-modified: Wed, 16 Jun 2021 10:53:38 GMT
content-length: 1338
date: Sat, 02 Oct 2021 10:42:28 GMT
何度削除しても変わってくれない時は kubectl drain [NODE]
でpodが配置されないようにしてから、削除すれば変更できます。
でも、そのnodeがちゃんと使えないって、結局そのnodeが存在する意味はないし、そもそも正しく構築できてないってことなんですよね。
pod間通信の仕組みが理解できておらず、ホントになんでこうなるのかわかりませんし、どうやって調査したらよいかすら見当もつかずでして。。。
おわりに
でもこれ、コピペで作れるんだったら、全自動でも作れるよな。
…… おもしろそう!
DockerHubのRateLimitにひっかかった
検証中にImageのPullがうまくいかなくなった。タイムアウトを伸ばしてみたが、意味がなかった。
以下を参考にさせていただきました。
nodeが多いとjoinしたときにCNIのcontainerをpullしてくるので、繰り返し検証しているとすぐに引っかかってしまった。たまにこれで確認しながら進めた。
TOKEN=$(curl -sSL "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq -r .token)
curl -sSL -v -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest 2>&1 | egrep "ratelimit-|rate limit"
ubuntu 20.04
構築時のホストとして利用したubuntuは server edition をインストールしたけど、日本語がなかった。
ので、初期設定として、以下を投入してから利用した。
sudo apt-get install -y language-pack-ja-base language-pack-ja
sudo update-locale LANG=ja_JP.UTF-8 LANGUAGE=ja_JP.UTF-8
source /etc/default/locale
sudo timedatectl set-timezone Asia/Tokyo
timedatectl timesync-status