最近kubernetesを触りたい欲が上がってきていますが、試せる環境がありません!!
(クラウド?GKE?クラウド破産怖い!)
ということでオンプレk8sを構築しよう!となったのですが、なかなか試す気力がなく、連休のタイミングで頑張りました。
とりあえず立てたいだけでしたが、せっかくなのでちょっと欲張ってみました。
2022/9/26更新
@superbrothers さんのコメントをもとに一部修正、加筆しました。
(コメントありがとうございます!)
TL;DR (にしたかったけど長すぎる。)
今回は下記のようなことをしています。
- コントロールプレーンをHA(高可用性)構成、HAProxyを使用
- ポッド間通信(CNI)はCalicoを使用
- MetalLBでLoadBalancerに対応
- Horizontal Pod Autoscaler対応のためにMetrics serverを導入
- Ubuntu 20.04(Ubuntu 22.04はそのうちリベンジします。
Ubuntu 22.04は辛かった…)
プロキシx1、コントロールプレーンノードx3、ワーカーノードx3の計7台分の仮想マシンを使用しています。
プロキシ用
# HAProxyのインストール
sudo apt install -y haproxy
# HAProxyの設定
# IPアドレス(192.168.0.21など)はコントロールプレーンノードのIPアドレスに合わせてください。
sudo mv /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.old
cat << EOF | sudo tee /etc/haproxy/haproxy.cfg
global
log /dev/log local0
log /dev/log local1 notice
daemon
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8
option redispatch
retries 1
timeout http-request 10s
timeout queue 20s
timeout connect 5s
timeout client 60m
timeout server 60m
timeout http-keep-alive 10s
timeout check 10s
frontend apiserver
bind *:6443
mode tcp
option tcplog
default_backend apiserver
backend apiserver
option httpchk GET /healthz
http-check expect status 200
mode tcp
option ssl-hello-chk
balance roundrobin
server kubec1 192.168.0.21:6443 check
server kubec2 192.168.0.22:6443 check
server kubec3 192.168.0.23:6443 check
EOF
# 設定反映
sudo systemctl restart haproxy
コントロールプレーンノード/ワーカーノード共通(6台全てで行う)
# host名を変更(今回はコントロールプレーンの1 -> c1)
sudo hostnamectl set-hostname kubec1
# iptablesのための設定
cat <<EOF | sudo tee -a /etc/modules
br_netfilter
EOF
sudo modprobe br_netfilter
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
# nftablesを使用しないように設定
sudo apt-get install -y iptables arptables ebtables
sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
sudo update-alternatives --set arptables /usr/sbin/arptables-legacy
sudo update-alternatives --set ebtables /usr/sbin/ebtables-legacy
# Containerdのインストール
sudo apt install -y containerd
# kubernetesパッケージのインストール
sudo apt-get update && sudo apt-get install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
1台目のコントロールプレーンノード
# 初めのコントロールプレーンを作成
# pod-network-cidrのアドレスは使用していない範囲でかつ/16の範囲で指定、
# control-plane-endpointのアドレスはプロキシサーバのIPアドレスを指定する。
sudo kubeadm init --pod-network-cidr=10.20.0.0/16 --upload-certs --control-plane-endpoint "192.168.0.11:6443"
# 最後に下記の様なものが表示されるのでコピーしておく。
# You can now join any number of the control-plane node running the following command on each as root:
#
# kubeadm join 192.168.0.11:6443 --token xxxxxx.xxxxxxxxxxxxxxxx \
# --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
# --control-plane --certificate-key xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
#
# Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
# As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
# "kubeadm init phase upload-certs --upload-certs" to reload certs afterward.
#
# Then you can join any number of worker nodes by running the following on each as root:
#
# kubeadm join 192.168.0.11:6443 --token xxxxxx.xxxxxxxxxxxxxxxx \
# --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# kubectlのコンフィグを用意する
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# Calicoをインストール
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.24.1/manifests/tigera-operator.yaml
# 設定ファイルをダウンロードして修正する
# cidrのIPアドレスを--pod-network-cidrで指定したものと同じにする
wget https://raw.githubusercontent.com/projectcalico/calico/v3.24.1/manifests/custom-resources.yaml
vi custom-resources.yaml
# ...
# apiVersion: operator.tigera.io/v1
# kind: Installation
# metadata:
# name: default
# spec:
# # Configures Calico networking.
# calicoNetwork:
# # Note: The ipPools section cannot be modified post-install.
# ipPools:
# - blockSize: 26
# cidr: 10.20.0.0/16
# encapsulation: VXLANCrossSubnet
# natOutgoing: Enabled
# nodeSelector: all()
# ...
# 設定を反映する
kubectl create -f custom-resources.yaml
2台目以降のコントロールプレーンノード
2台目以降のコントロールプレーンの追加は上記でkubeadm init
してから2時間以内に行ってください。
それ以上時間が経ってしまった場合は1台目のコントロールプレーンでkubeadm init phase upload-certs --upload-certs
を実行し、表示されるIDを--certificate-keyに指定して行ってください。
# 先ほどメモをしていたうちの--control-planeオプションが入っている方のコマンドを実行する
sudo kubeadm join 192.168.0.11:6443 --token xxxxxx.xxxxxxxxxxxxxxxx \
--discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
--control-plane --certificate-key xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# kubectlのコンフィグを用意する
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# 少ししてから下記コマンドを実行するとノードが追加されていることが確認できる
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# kubec1 Ready control-plane 21m v1.25.2
# kubec2 Ready control-plane 7m v1.25.2
ワーカーノード
# 先ほどメモをしていたうちの--control-planeオプションが入っている方のコマンドを実行する
sudo kubeadm join 192.168.0.11:6443 --token xxxxxx.xxxxxxxxxxxxxxxx \
--discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# 少ししてから下記コマンドをコントロールプレーンノードで実行するとノードが追加されていることが確認できる
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# kubec1 Ready control-plane 32m v1.25.2
# kubec2 Ready control-plane 18m v1.25.2
# kubec3 Ready control-plane 13m v1.25.2
# kubew1 Ready control-plane 5m v1.25.2
諸々のインストール
ワーカーノードがないとデプロイできないため後半に持ってきています。
# MetalLBをインストール
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.5/config/manifests/metallb-native.yaml
# 設定ファイルを作成する
# IPアドレスは使用できてかつ空いているIPアドレスを指定する(下記は192.168.10.0/24が使用できる場合)
cat << EOF > metallb-conf.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: first-pool
namespace: metallb-system
spec:
addresses:
- 192.168.10.1-192.168.10.254
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: example
namespace: metallb-system
spec:
ipAddressPools:
- first-pool
EOF
# 反映する
kubectl apply -f metallb-conf.yaml
# Metrics Serverをインストール
# そのままだと動かないので修正
wget https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
vi components.yaml
# ...
# ---
# apiVersion: apps/v1
# kind: Deployment
# metadata:
# labels:
# k8s-app: metrics-server
# name: metrics-server
# namespace: kube-system
# spec:
# selector:
# matchLabels:
# k8s-app: metrics-server
# strategy:
# rollingUpdate:
# maxUnavailable: 0
# template:
# metadata:
# labels:
# k8s-app: metrics-server
# spec:
# containers:
# - args:
# - --cert-dir=/tmp
# - --secure-port=4443
# - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
# - --kubelet-use-node-status-port
# - --metric-resolution=15s
# - --kubelet-insecure-tls # ******************* ここを追加 *******************
# image: k8s.gcr.io/metrics-server/metrics-server:v0.6.1
# imagePullPolicy: IfNotPresent
# livenessProbe:
# failureThreshold: 3
# httpGet:
# path: /livez
# port: https
# ...
kubectl apply -f components.yaml
解説
各コマンドが何をしているかは上記である程度コメントを入れましたのでいくつか抜粋して解説します。
プロキシ
今回はHA Proxyを使用してコントロールプレーンをHA構成にしています。
これで1台ぐらい落ちても大丈夫?(まだ試していません。)
他にもkeepalivedを使用することもできるようです。(HAで言うとこちらを使うのが本当は正解。)
一般的にはHA ProxyでLoadBalanceをしてkeepalivedで可用性を確保するというのが正解のようです。
(今回はとりあえずなので…。いつかちゃんとした構成にします。)
設定等は公式の資料から拝借しました。
iptables
Podの通信をいい感じにするためiptablesが使用されます。
最近はnftablesがデフォルトになっていますが、k8sが対応していないためiptablesに切り替えています。
containerd
コンテナの実行環境ですね。
以前のバージョンではdockerが使われていましたが、最近はcontainerdがデフォルトになっています。
ちなみにトラブルシューティングのために試してみたところcrictlコマンドでdockerと同じように使えそうです。
1台目のコントロールプレーンノードの作成
1台目のコントロールプレーンノードの作成が一番大変です。ここが終われば90%終わったと言っても過言ではありません。
1台目のkubeadm init
では
- ポッドが使用するネットワークのアドレス空間を指定(--pod-network-cidr)
- 証明書を共有するできるように指定(--upload-certs)
- プロキシ経由でAPIサーバにアクセスするように指定(--control-plane-endpoint)
しています。
Pod間通信をするため、今回はCalicoをCNIとして使用しています。(一応こちらがkubeadmプロジェクトで検証済みのCNIのようです。)
https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/#pod-network
Podで使用するネットワークを合わせるため(spec.calicoNetwork.ipPools[].cidr)、yamlファイルを修正しています。
クラウドサービスではServiceのTypeにLoadBalancer
を指定するとグローバルIPアドレスを振ってくれる仕組みがあります。
MetalLBはグローバルIPアドレスではないですが、プライベートIPアドレスを振ってくれるのでローカルでの使用で便利です。
せっかくならLoadBalancerも使えるようにしたいということで今回追加しました。
こちらも割り振りするIPアドレスを指定する必要があるため、コンフィグファイルを作成しています。
Metrics Serverは負荷に応じてレプリカ数を調整するHorizontal Pod Autoscaler
を使用するために追加しています。
個人レベルではまず使用することはないと思いますが、これもオートスケールしたいという単なる欲張りです。
2台目以降のコントロールプレーンノードの追加
特に難しいことはありませんが、一つ注意すべき点があります。
コントロールプレーンで共有している証明書はkubeadm init
を実行してから2時間以内に取得しないといけない点です。
2時間経つとシークレットから削除されるというセキュリティ機能ですが、2時間過ぎてしまった場合にコントロールプレーンノードを追加したい場合は再度シークレットにアップロードする必要があります。
そのコマンドが、kubeadm init phase upload-certs --upload-certs
になります。
構築したクラスタで遊んでみる
下記のような内容のyamlファイルを用意してください
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 50%
maxUnavailable: 0%
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
resources:
limits:
cpu: 100m
requests:
cpu: 100m
---
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- port: 80
type: LoadBalancer
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: nginx
spec:
minReplicas: 1
maxReplicas: 10
metrics:
- resource:
name: cpu
target:
averageUtilization: 10
type: Utilization
type: Resource
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: nginx
このファイルをkubectl apply -f nginx.yaml
のようにして適用します。
kubectl get deploy -w
、kubectl get pods -w
あたりを見るとポッドが生成されていく様子が見られて面白いかもしれません。
kubectl get svc
をすると外部からアクセスできるようにIPアドレスが付与されていることもわかります。
実際にブラウザでアクセスしてみるとnginxのデフォルトのページが確認できます。
続いてkubectl get hpa -w
で負荷の状態を見ながらポッドに負荷をかけます。
F5アタック程度では大した負荷にならないので、直接ポッドに入って負荷のかかるコマンドを実行します。
kubectl get pod
でnginxのポッドを探し、kubectl exec -it ngix-xxx-xxx -- bash
でポッドに入り、yes > /dev/null
を実行します。
しばらくするとTARGETS
のところで負荷が上がり、レプリカ数が増えます。
上記のコマンドを終了するとしばらくしてからレプリカ数が戻ります。
あとがき
こだわりが強かったのか、ちょっとやってみたつもりがガッツリあちこちハマりまくりで大変だったのでまとめてみました。
(予想以上にハマりどころ満載で1週間ぐらいかかってしまった気がします。)
仮想マシンを作っては壊しで合計20台ぐらい使って検証したのでこれでおそらくうまくいくと思います。
といろいろ言いましたが、k8sのいい勉強になるのでみなさんもハマってください(?)
誤字、脱字、間違い等があればご指摘いただければ幸いです。
Reference
- https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/high-availability/
- https://projectcalico.docs.tigera.io/getting-started/kubernetes/quickstart
- https://github.com/kubernetes/kubeadm/blob/main/docs/ha-considerations.md#options-for-software-load-balancing
- https://metallb.universe.tf/configuration/
- https://blog.framinal.life/entry/2020/04/16/022042#MetalLBのインストール
- https://github.com/kubernetes-sigs/metrics-server
- https://qiita.com/nakamasato/items/c49cf322cd5d7770b465