4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWSのEC2インスタンスでKubernetesを作ってみる

Last updated at Posted at 2024-01-08

概要

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.nanot3.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を実行できるようになります。

例えば kubeconfig1kubeconfig2という二つの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
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

以下のように変更

/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[].encapsulationVXLANCrossSubnetから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で見つけました。まだあまりやり取りされていないようなので、今後の解決を期待したいです。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?