概要
AWSのEC2インスタンスで、kubeadmとAnsible Playbookを使ってKubernetesを作ってみました。手順を本記事にまとめます。
EKSだとControl Planeの料金だけでひと月に73ドル(2024年6月23日時点で)掛かり結構高額なので、なるべく安くKubernetesを作って遊びたいというのがモチベーションです。
本記事の対象者
- マネージドなKubernetesサービス(EKS,AKS,GKE等)以外でK8sを作ってみたい
- KubernetesをMinikubeやkindではなく、物理マシンでControl PlaneとWorker Nodeで分けて自力で作ってみたい
- しかしながら自宅にKubernetesの構築を手軽に試せるような物理マシンがない
Kubernetes全般の環境情報
- OS : Ubuntu 22.04
- コンテナランタイム : containerd
- Kubernetesのバージョンは以下
$ kubectl version
Client Version: v1.29.6
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Server Version: v1.29.6
$ kubelet --version
Kubernetes v1.29.6
$ kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"29", GitVersion:"v1.29.6", GitCommit:"062798d53d83265b9e05f14d85198f74362adaca", GitTreeState:"clean", BuildDate:"2024-06-11T20:22:13Z", GoVersion:"go1.21.11", Compiler:"gc", Platform:"linux/amd64"}
Kubernetes構築手順
(1) EC2インスタンスの作成
あくまでお試し用なので、Control PlaneとWorker Node用のインスタンスをそれぞれ1台作ることにします。
24/06/01追記
TerraformでControl PlaneとWorker NodeのEC2インスタンスを作るソースを以下GitHubにアップしました。もしよろしければお使いください。
インスタンス情報
AMI
- ID
- ami-07c589821f2b353aa
- name
- amazon/ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20231207
インスタンスタイプ
- Control Plane
- t3a.small : 2vCPU, 2GiBメモリ
- Worker Node
- t3.micro : 2vCPU, 1GiBメモリ
kubeadmのインストールにも記載があるように、Control Planeを構築するEC2インスタンスには2GiBのメモリが必要でした。試しにそれよりもメモリが小さいt3.nano
やt3.micro
でやってみましたが、kubeadm init
の実行途中にメモリ不足とみなされてエラーになってしまうようです。
一方でワーカーノードだと2GiB未満のメモリのマシンでもkubeadm join
に成功しました。(作れはしても、すぐにリソースが枯渇してしまうはずですが...)
(2) セキュリティグループ設定
Kubernetes公式の以下のドキュメントに従って設定します。
若干自信がないですが、私はControl PlaneとWorker Nodeに対して以下のように設定しました。
通信の方向 | プロトコル | ポート番号 | 用途 | 通信許可するIP範囲 | 適用対象 |
---|---|---|---|---|---|
inbound | TCP | 6443 | kube-apiserverで利用 | 全てのノード | Control Planeのみ |
inbound | TCP | 10250 | kubeletで利用 | 全てのノード | Control Plane, Worker両方 |
inbound | TCP | 30000-32767 | NodePortのServiceリソースで利用 | 全てのノード | Control Plane, Worker両方 |
outbound | 全て | 全て | インスタンスから外部にアクセス | 0.0.0.0/0 | Control Plane, Worker両方 |
※ 上記はKubernetesのシステムで最低限開ける必要があるポートです。最終的にはCNI(Container Network Interface)のインストールも必要になりますが、使うCNIによって適宜開けるプロトコルとポートの追加が必要です。
(3) Kubernetesの構築
kubeadmのインストールを参考にして、Ansibleでスクリプトを作りました。詳しくは筆者作成の下記GitHubをご覧ください。
こちらのツールを使うにはAnsible Playbookを実行するマシンが別途必要となります。実行するマシンが(ネットワーク的な意味での)存在する場所によって、それに応じたSSHのセキュリティルールの追加やインターネットゲートウェイの作成等が必要となります。
README.mdに従い事前準備ができたら、Ansible PlaybookでControl PlaneとWorker Nodeを構築します。
Control Planeの構築
ansible-playbook site_control-plane.yml
Worker Nodeの構築
ansible-playbook site_worker.yml
構築完了後、Control PlaneにSSH等で接続すると、以下のようにKubernetesが出来上がっているのを確認できます。今回ケースでは、Control Planeのホスト名はip-10-0-0-5
です。
(Control Planeで実行)
$ kubectl get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-5dd5756b68-r9gdq 0/1 Pending 0 41m
kube-system coredns-5dd5756b68-zhksc 0/1 Pending 0 41m
kube-system etcd-ip-10-0-0-5 1/1 Running 0 42m
kube-system kube-apiserver-ip-10-0-0-5 1/1 Running 0 42m
kube-system kube-controller-manager-ip-10-0-0-5 1/1 Running 0 42m
kube-system kube-proxy-96bcm 1/1 Running 0 41m
kube-system kube-proxy-ghl74 1/1 Running 0 39m
kube-system kube-scheduler-ip-10-0-0-5 1/1 Running 0 42m
kubectlを実行するクライアントの変更方法について(読み飛ばしてもOK)
Control PlaneのEC2インスタンスに毎回SSH接続してkubectlを実行したり、Helmをインストールしたりするのは結構面倒です。k8sのAPIサーバにアクセスするために必要なツールが既にインストールされているマシンがあるならば、それを使いたくなるでしょう。
そこで以下のサイトを参考にすると、Control Planeにある ~/.kube/config
を別マシンの ~/.kube/config
にマージすることができ、Control PlaneのEC2インスタンス以外からkubectlを実行できるようになります。
例えば kubeconfig1
とkubeconfig2
という二つのkubeconfigファイルがあったとすると、以下のコマンドで一つのkubeconfigにマージできます。
KUBECONFIG=kubeconfig1:kubeconfig2 |
kubectl config view --merge --flatten > ~/.kube/config
しかし今回のケースではこれだけでは不十分でした。APIサーバのadvertise addressがプライベートIPの 10.0.0.5
であるにも関わらず、Control Planeとkubectl
を実行したいクライアントはインターネットでのみ繋がっているからです。
苦肉の策ですが、この問題はクライアント上で、以下のようにiptablesを使って「Control PlaneのパブリックIPを 10.0.0.5
にNATする」ことで解決可能です。
sudo iptables -t nat -A OUTPUT -p tcp \
-d 10.0.0.5 -j DNAT \
--to-destination [Control PlaneのパブリックIP]
これで、Control Planeとプライベートネットワークでつながっていないマシンからもkubectlコマンド等で接続可能になります。
ちなみに設定を消すときは -A OUTPUT
の部分を -D OUTPUT
に変更します。
(4-1) CNIのインストール(Flannelの場合)
Kubernetes内のネットワークを設定するためのツールをインストールします。今回はFlannelを使ってみます。(後述しますがCalicoだとうまくいかずに断念しました...)
セキュリティグループにFlannel関連のルールを追加
こちらを参考に、Flannelがノード間で通信を行えるようにするため、以下のルールを追加します。
通信の方向 | プロトコル | ポート番号 | 用途 | 通信許可するIP範囲 |
---|---|---|---|---|
inbound | UDP | 8472 | flannel overlay network - vxlan backend | 全てのノード |
※ Flannelの設定によっては8472ではなく、8285を開ける必要があるかもしれません
Flannelのデプロイ
Flannelのマニフェストkube-flannel.yml
で編集が必要な部分があるため、一旦Control Planeにマニフェストをダウンロードします。
(Control Planeで実行)
sudo apt -y install wget
wget https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
ダウンロードしたkube-flannel.yml
で、PodのCIRDの値をkubeadm init
コマンドで設定したものに変更します。
vi kube-flannel.yml
...
net-conf.json: |
{
"Network": "10.244.0.0/16", # 設定したPodのCIDRに変更
"Backend": {
"Type": "vxlan"
}
}
...
修正したFlannelのマニフェストをapplyします。
$ kubectl apply -f kube-flannel.yml
(実行結果)
namespace/kube-flannel created
serviceaccount/flannel created
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
configmap/kube-flannel-cfg created
daemonset.apps/kube-flannel-ds created
Flannelのリソースが新たにデプロイされているのを確認できれば成功です。
$ kubectl get pod -n kube-flannel
NAME READY STATUS RESTARTS AGE
kube-flannel-ds-c2d6d 1/1 Running 0 37s
kube-flannel-ds-cntpk 1/1 Running 0 37s
以上でEC2インスタンス上にKubernetesを構築できました🙌
(4-2) CNIのインストール(Ciliumの場合)(24/06/08 追記)
Ciliumもインストールできることを確認できました。
セキュリティグループはこちらを参考に以下のルールを追加します。
通信の方向 | プロトコル | ポート番号 | 用途 | 通信許可するIP範囲 |
---|---|---|---|---|
inbound | UDP | 8472 | VXLAN overlay | 全てのノード |
inbound | TCP | 4240 | health checks | 全てのノード |
あとは公式ドキュメント等にしたがってCiliumをインストールします。
ちなみに私は、自分が過去に投稿した以下の記事のやり方を参考にしてCiliumをインストールできました。
注意点ですが、Workerノードのインスタンスタイプは少なくともt3.micro
(2vCPU, 1GiBメモリ)相当のスペックはあった方がよさそうです。t3.micro
よりもスペックが一段階低いt3.nano
(2vCPU, 0.5GiBメモリ)の場合、ciliumのDaemonSetがいつまで経ってもReadyにならずに正常稼働しませんでした。
(24/06/16 追記)
上記に関してですが、正常稼働しなかったのはスペックの問題ではなく、CiliumのデフォルトのCIDRが10.0.0.0/8
で(ドキュメントにそう書いてあった...)、EC2インスタンスに使っていたサブネット10.0.0.0/27
と完全にカブっていたことが原因だったかもしれません。今回のケースでは、Helmチャートのvalueで例えば以下のように、10.0.0.0/27
と重複しないものに変更する必要がありました。
ipam:
operator:
clusterPoolIPv4PodCIDRList: ["192.168.0.0/16"] # CIRDはあくまで一例です
構築で行き詰まった点
ほぼ筆者の備忘メモなので読み飛ばしていただいてもいいです。
containerdの設定不備でkube-apiserverが立ち上がらない...
今回はコンテナランタイムでcontainerdを使いましたが、aptでcontainerdをインストールしただけのデフォルトの状態だと、kube-systemのPodが以下のようにリスタートを繰り返してKubernetesが正常稼働しない事態になりました。
$ kubectl get po -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-5dd5756b68-8jgt4 0/1 Pending 0 3m25s
kube-system coredns-5dd5756b68-9wq97 0/1 Pending 0 3m25s
kube-system etcd-ip-10-0-0-5 0/1 Running 106 (9s ago) 2m25s
kube-system kube-apiserver-ip-10-0-0-5 0/1 Running 80 (25s ago) 4m40s
kube-system kube-controller-manager-ip-10-0-0-5 0/1 Running 96 (9s ago) 2m43s
kube-system kube-proxy-8bwp7 1/1 Running 3 (3s ago) 3m25s
kube-system kube-scheduler-ip-10-0-0-5 0/1 Running 108 (9s ago) 5m15s
調査と試行錯誤した結果、「今回使ったAMIのデフォルト設定だとsystemdがcgroupドライバーを使うようにする必要があるのに、containerdの設定はそうなっていない」ことがわかりました。そこで以下の対応を行い解決できました。
containerdの設定ファイルを作成
$ sudo su -
# containerd config dump > /etc/containerd/config.toml
# logout
作成した設定ファイルで、SystemdCgroup
の値がfalse
になっているのをtrue
に変更する
sudo vi /etc/containerd/config.toml
以下のように変更
...
SystemdCgroup = true
...
containerdおよびkubeleteのサービスをrestart
sudo systemctl restart containerd
sudo systemctl restart kubelet
参考情報
CNIがCalicoだと異なるノードのPodが通信できない(未解決)
Quickstart for Calico on Kubernetesや筆者記事にしたがってCNIをCalicoで作ろうとしましたが、どうやっても異なるノードのPod同士が通信できなくて断念しました...
試してみたことは以下です。
- Calicoバージョンを以下のように変えてみる
- v3.27.0(現時点の最新), v3.26.4, v3.24.6
- Installationリソースのパラメータ
spec.calicoNetwork.ipPools[].encapsulation
をVXLANCrossSubnet
からIPIP
に変えてみる
怪しいと思っている部分はネットワークのルーティングに関する設定で、以下に具体的に説明します。
既にCalicoの構築に成功しているK8sクラスター上では、vxlan.calico
というネットワークインターフェースがあり、ここを経由するネットワーク経路が以下のように定義されていました。
$ sudo ip route | grep vxlan
10.128.75.0/26 via 10.128.75.1 dev vxlan.calico onlink
...
一列目のCIDR(10.128.75.0/26等)は、PodのCIDRの範囲に含まれています。
このルートがEC2インスタンスで構築した場合だと存在せず、代わりに以下のように ens5
というAWSサブネットのインターフェースに紐づけられています。
$ sudo ip route | grep onlink
192.168.91.64/26 via 10.0.0.5 dev ens5 proto 80 onlink
ちなみにEC2インスタンスのノード上にもvxlan.calico
のインターフェースは作られているのですが、それを使うルートは一つも定義されていません。
ヤマ勘なので間違っている可能性が高いのですが「本当はこのルートはens5
ではなくて、vxlan.calico
に作られるべきなのでは?」というのが現状の考えです。
これに関連しそうな内容をGitHubのissueで見つけました。まだあまりやり取りされていないようなので、今後の解決を期待したいです。