Amazon EKSを使わずにk8sクラスタを立ててみた話
そうだ、k8sクラスタを立てよう
時は2023年12月21日、突如としてTLの読み込みができなくなるX(旧Twitter)。
幸いにしてすぐ復旧はなされたものの、X(旧Twitter)以外のSNSへの移行を検討し始めるには十分な出来事であった…
有力な移行先としてmisskey.ioが頭によぎったものの、せっかくの分散型SNSであるならば自前でmisskey鯖立ててみるのも一興である。ちょろっと調べてみた感じコンテナイメージとして提供されているようだったので、であるならば一興ついでにKubernetes(k8s)環境も構築してみるのも面白そうである。
どのサービスで立てよう…?
AWSアカウントを持っていることもあり、とりあえずお試しで立ててみる分にはAWSで立ててみるのが良さそうか…?と考えた筆者。
Amazon EKSを利用すればコマンド一発でk8sクラスタを払い出すことも可能で、あとはアプリケーションをデプロイするだけというのは楽そうである。
しかしながら、EKS自体に利用料がかかることやスポットインスタンスを利用したk8sクラスタが払い出せるかがイマイチ不明瞭なこともあり、さっと試すには不向きだなとなった。
EC2だけでどうにかする方法がないものかと調べてみたところ、それっぽいページを見つけることができた。
AWS環境での構築ログではないが、これを参考にEC2スポットインスタンス上にk8sクラスタを構築することはできそうである。
やってみよう
前提条件
WSL2 on Windows 11 から操作
terraform、ansibleがインストール済み
AWS Web コンソール上にてIAMユーザ作成済み
ssh接続に必要な秘密鍵ダウンロード済み
アクセスキー、シークレットキー作成済み
AWS Web コンソール上にてVPC作成済み
セキュリティグループ準備
k8s公式ページを参考に、各種ポートで通信ができるようセキュリティグループを作成しておく。control-plane用とworker用に分けて作っておくのが良い。
また、ec2インスタンスにssh接続して各種設定を行えるよう22番ポートで通信可能にするセキュリティグループも作成しておくこと。
ec2インスタンス起動
以下設定でcontrol-plane用とworker用でそれぞれ起動する。
image: ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20231207
flavor: t3a.2xlarge
ストレージサイズ: 30GB以上
サブネットは作成済みVPCに含まれるものを指定
キーペア、セキュリティグループは作成済みのものを指定
お手本に従ってコマンド実施
https://blog.estampie.work/archives/2585 の手順に従って構築を進める。
IPアドレスを書き換えるところはパブリックIPアドレスを指定することで対応。
問題発生…
手順通りにcontrol-plane nodeおよびworker nodeがセットアップできたので、https://cstoku.dev/posts/2018/k8sdojo-09/ を参考にしてnginxをデプロイしてみたところ、全然繋がらない…
何かエラー吐いてないかとPodのログを見てみたところ、以下エラーが出力されていた。
Error from server: no preferred addresses found; known addresses: []
おもむろにエラーメッセージでググってみたところ、どうやらworker nodeにIPアドレスが割り当てられてない時に出るエラーらしい。worker nodeの10-kubeadm.conf にはプライベートIPアドレスを指定する必要がありそうだ。
試行錯誤を簡単に繰り返せるようにしよう
ところで、筆者は利用料金節約のためにec2インスタンスを起動させっぱなしにすることはなく毎回律儀にシャットダウンするようにしている。そうすると、ec2インスタンスを立て直す度セットアップ作業を行う必要に駆られていた。
さすがにめんどくなってきたので、ec2インスタンス起動時にある程度セットアップも済ませるようにしたい。
terraformおよびansibleによるIaC化
kubernetes.tf 作成
provider "aws" {
region = "ap-northeast-1"
access_key = "*******"
secret_key = "*******"
}
# image
data "aws_ami" "ubuntu_22_04" {
owners = ["amazon"]
most_recent = true
filter {
name = "name"
values = ["*ubuntu-jammy-22.04-amd64-server*"]
}
}
# instance
resource "aws_instance" "master_node" {
instance_type = "t3a.2xlarge"
ami = data.aws_ami.ubuntu_22_04.id
key_name = "hogehoge"
instance_market_options {
market_type = "spot"
spot_options {
max_price = 0.710
}
}
vpc_security_group_ids = [
"sg-hoge",
"sg-fuga"
]
subnet_id = "subnet-hogefuga"
root_block_device {
volume_size = 30
volume_type = "gp3"
}
lifecycle {
ignore_changes = [
ami
]
}
provisioner "local-exec" {
command = "ansible-playbook -i ${self.public_dns}, ~/ansible/setup_k8s.yaml"
}
}
resource "aws_instance" "worker_node" {
instance_type = "t3a.2xlarge"
ami = data.aws_ami.ubuntu_22_04.id
key_name = "hogehoge"
instance_market_options {
market_type = "spot"
spot_options {
max_price = 0.710
}
}
vpc_security_group_ids = [
"sg-hoge",
"sg-fuga"
]
subnet_id = "subnet-hogefuga"
root_block_device {
volume_size = 30
volume_type = "gp3"
}
lifecycle {
ignore_changes = [
ami
]
}
provisioner "local-exec" {
command = "./check_ssh_connection.sh ${self.public_dns}"
}
provisioner "local-exec" {
command = "ansible-playbook -i ${self.public_dns}, ~/ansible/setup_k8s.yaml"
}
}
# display public DNS name
output "public_dns_master_node" {
value = aws_instance.master_node.public_dns
}
output "public_dns_worker_node" {
value = aws_instance.worker_node.public_dns
}
こんな感じのテキストファイルを用意する。拡張子はtf。
access_key, secret_keyなどは作成済みのものを指定する。
インスタンス作成後に実施したい処理はprovisionerを指定することでterraformに実施させることが可能。
注意点として、ec2インスタンス起動からssh可能になるまではタイムラグがあるため、ansible実行を何らかの方法で遅らせる必要がある。
playbook 作成
ec2インスタンス起動後に行うセットアップ処理をyamlファイルに定義したものを準備する。
今回準備したファイルの一覧は以下。
~/ansible/
├── roles
│ ├── containerd
│ │ ├── files
│ │ │ ├── config.toml
│ │ │ ├── k8s.conf
│ │ │ ├── kubelet
│ │ │ └── sysctl.conf
│ │ └── tasks
│ │ └── main.yaml
│ ├── docker
│ │ └── tasks
│ │ └── main.yaml
│ └── kubernetes
│ └── tasks
│ └── main.yaml
└── setup_k8s.yaml
各yamlの中身は以下の通り。
# setup_k8s.yaml
- hosts: all
become: yes
vars:
ansible_python_interpreter: /usr/bin/python3
roles:
- kubernetes
- docker
- containerd
# roles/containerd/tasks/main.yaml
- name: copy k8s.conf
copy:
src: k8s.conf
dest: /etc/modules-load.d/k8s.conf
- name: modprobe k8s config
shell: |
modprobe overlay
modprobe br_netfilter
- name: copy sysctl conf
copy:
src: sysctl.conf
dest: /etc/sysctl.d/k8s.conf
- name: reload sysctl config
command: sysctl --system
- name: copy containerd config
copy:
src: config.toml
dest: /etc/containerd/config.toml
- name: reload containerd
systemd:
name: containerd
state: restarted
- name: copy kubelet config
copy:
src: kubelet
dest: /etc/sysconfig/kubelet
- name: reload kubelet
systemd:
name: kubelet
daemon_reload: yes
state: restarted
# roles/docker/tasks/main.yaml
- name: apt update
command: apt-get update
- name: install package
apt:
name:
- ca-certificates
- curl
- gnupg
update_cache: yes
state: present
- name: insatall keyrings
command: install -m 0755 -d /etc/apt/keyrings
- name: download ubuntu official gpg key
shell: curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
- name: add permission
command: chmod a+r /etc/apt/keyrings/docker.gpg
- name: download docker-ce repository list
shell: |
echo \
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
- name: apt update
command: apt-get update
- name: install docker-ce and related pkg
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
update_cache: yes
state: present
# roles/kubernetes/tasks/main.yaml
- name: swap off
command: swapoff -a
- name: apt update
command: apt-get update
- name: install package
apt:
name:
- apt-transport-https
- ca-certificates
- curl
- gnupg
update_cache: yes
state: present
- name: download ubuntu official gpg key
shell: curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
- name: downwload kubernetes repository list
shell: echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
- name: apt update
command: apt-get update
- name: install kubernetes
apt:
name:
- kubelet
- kubeadm
- kubectl
update_cache: yes
state: present
- name: hold kubernetes version
command: apt-mark hold kubelet kubeadm kubectl
yaml以外のファイル(~/ansible/roles/containerd/files に格納したファイル群)は、https://blog.estampie.work/archives/2585 の containerdの設定 でヒアドキュメント使って作成したりデフォルトの設定ファイルを書き換えて配置したりしているのを格納している。ansible使っている割に冪等性を投げ捨てているのは気にしてはいけない
コマンド実行でEC2インスタンスをお手軽に作成
必要なファイルが準備できたら、kubernetes.tfを配置したディレクトリにcdした上で terraform applyする。
すると、https://blog.estampie.work/archives/2585 の マスターノードの作成 の手前まで完了したインスタンスが出来上がる。
やってみよう part2
Podのログが見れない問題を解消
worker node構築時、10-kubeadm.conf で指定するIPをプライベートIPにすることで無事Podのログが見えるようになった。
NodePortでServiceを作ってアクセス確認
https://cstoku.dev/posts/2018/k8sdojo-09/ を参考にDeploymentとNodePortのServiceを作成し、worker用インスタンスのパブリックDNS:作成されたNodePortのポート番号をブラウザに入力すると以下画像のようにNginxのページが開くことが確認できた。
今後の展望
misskey鯖公開を見据えてingressを導入したり、misskeyをセットアップしてみたりまでやり切りたいお気持ち。
この記事が気に入ったらサポートをしてみませんか?