Antikythera on Kubernetes - 2 クラスター作成
まずは, docker-compose で3台固定で Erlang クラスター作成した後、k8s 上で複数 Pod 構成の Erlang クラスター用の実装に置き換え, クラスタリングしてみます。
docker-composeでErlangクラスターを作る
Antikythera instanceをセットアップする
Antikythera instance example に従ってAntikythera instanceをセットアップします。
git clone https://github.com/access-company/antikythera.git
git clone https://github.com/access-company/antikythera_instance_example.git
git clone https://github.com/access-company/testgear.git
cd antikythera_instance_example
vim mix.exs # [git: "/home/kmt/Documents/antikythera" に
mix deps.get && mix deps.get && mix compile
cd ../testgear
export ANTIKYTHERA_INSTANCE_DEP='{:antikythera_instance_example, [git: "/home/kmt/Documents/antikythera_instance_example"]}'
unlink .tool-versions
cp ../antikythera/.tool-versions .
mix deps.get && mix deps.get
sudo vim /etc/hosts # 127.0.0.1 testgear.localhost
iex -S mix
2024-04-27T05:55:13.693+00:00 [error] module=FileSystem.Backends.FSInotify `inotify-tools` is needed to run `file_system` for your system, check https://github.com/rvoicilas/inotify-tools/wiki for more information about how to install it. If it's already installed but not be found, appoint executable file with `config.exs` or `FILESYSTEM_FSINOTIFY_EXECUTABLE_FILE` env.
- https://github.com/inotify-tools/inotify-tools/wiki
apt-get install inotify-tools
docker pull rtvu/docker-elixir
docker build rtvu/docker-elixir --build-arg ERLANG_VERSION=24.3.4.13 --build-arg ELIXIR_VERSION=1.13.4-otp-24
asdfを使ってerlangとelixirのベースイメージを作る
ベースイメージを作ったあと dc run <container> sh で中に入ってdc build --progress=plain で出力を確認しながらインクリメンタルにDockerfileを作るのがおすすめです。以下注意点
- vborja/asdf-ubuntu みると asdfユーザーとしてasdfをインストールしている。のでrootとasdfを切り替えながら環境構築する
- Erlang には Before asdf install が必要
DEBIAN_FRONTEND=noninteractiveを指定しないと止まるので注意- Elixir:
hex,rebar導入
Erlang + ElixirのDockerfileが作れたらイメージをDocker Hubにプッシュしましょう
https://github.com/wakame-tech/antikythera_k8s/blob/feature/dockerize/Dockerfile
FROM vborja/asdf-ubuntu:latest
ARG ERLANG_VERSION
ARG ELIXIR_VERSION
USER root
# https://github.com/asdf-vm/asdf-erlang#before-asdf-install
RUN \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y \
build-essential \
autoconf \
m4 \
libncurses5-dev \
libwxgtk3.0-gtk3-dev \
libwxgtk-webview3.0-gtk3-dev \
libgl1-mesa-dev \
libglu1-mesa-dev \
libpng-dev \
libssh-dev \
unixodbc-dev \
xsltproc \
fop \
libxml2-utils \
libncurses-dev
USER asdf
RUN asdf plugin-add erlang https://github.com/asdf-vm/asdf-erlang.git
RUN export KERL_CONFIGURE_OPTIONS="--without-javac"
ENV ERLANG_VERSION $ERLANG_VERSION
RUN asdf install erlang $ERLANG_VERSION
RUN asdf global erlang $ERLANG_VERSION
RUN asdf plugin-add elixir https://github.com/asdf-vm/asdf-elixir.git
ENV ELIXIR_VERSION $ELIXIR_VERSION
RUN asdf install elixir $ELIXIR_VERSION
RUN asdf global elixir $ELIXIR_VERSION
RUN mix local.hex --force && mix local.rebar --force
docker build --tag=wakametech/asdf-erlang-elixir:latest ./docker/ --build-arg ERLANG_VERSION=24.3.4.13 --build-arg ELIXIR_VERSION=1.13.4-otp-24
docker push wakametech/asdf-erlang-elixir:latest
a9aのDockerイメージを作る
今作ったイメージをベースイメージとしてAntikythera instanceが動くDockerイメージを作ります。
- ビルド時に必要な依存
inotify-toolslibexpat1-dev: fast_xmlのために必要らしい
リリースの作成
1からリリースを作るのは大変そう & 理解していないので AntikytheraLocal.RunningEnvironment を使い回す。
mix antikythera_local.start <gears>でa9a本体の起動とgearの配置- たまにスペックの問題?で
wait_until_directories_are_created()が失敗する - daemonで起動するのでコンテナが終了しないように
tty: trueにしておく deps/antikythera/tmp/local/release_per_nodeがリリースディレクトリになる- ログ確認
tail -f deps/antikythera/tmp/local/release_per_node/tmp/log/erlang.log.1
- たまにスペックの問題?で
- 起動スクリプト
- a9a_k8sをclone, ビルド, gearをclone, ビルド
http://localhost:8082/healthcheck(=>:8080) が返ってくるようになりました
git clone -b feature/dockerize https://github.com/wakame-tech/antikythera_k8s.git .
mix deps.get && mix deps.get
export ANTIKYTHERA_INSTANCE_DEP="{:antikythera_k8s, [github: \"wakame-tech/antikythera_k8s\"]}"
mkdir gears
git clone https://github.com/access-company/testgear.git
remote_consoleに接続できない
AntikytheraLocal.NodeName.get()はantikythera@$(hostname -f).local- ドットが含まれている時は
.localをつけない - ホスト名はドットが含まれていなければならない
** System running to use fully qualified hostnames **** Hostname antikythera-k8s-b9cdc79dd-dgzf6 is illegal **
hostname -fは3820709c2d5a(コンテナID) でドットが含まれていない- -> compose.yamlに
services.app.hostname=a9a-k8s.localを設定
- -> compose.yamlに
- ドットが含まれている時は
MIX_ENV=prod \
RELEASE_NODE="antikythera@$(hostname -f)" \
RELEASE_COOKIE="local" \
rel_local_erlang-24/antikythera_k8s/bin/antikythera_k8s remote
docker-composeでerlangクラスターを作る
3台からなるerlangクラスターをdocker-composeで作ってみます。
- ホスト名:
antikythera-k8s-{1,2,3}.local - IPアドレス: 192.168.100.{11,12,13}
IPアドレスの固定は <service>.networks.<network>.ipv4_address で, extra_hosts を書くとコンテナ内の /etc/hosts に追加されるらしいです
services:
app1:
hostname: antikythera-k8s-1.local
networks:
app_net:
ipv4_address: 192.168.100.11
extra_hosts:
- "antikythera-k8s-2.local:192.168.100.12"
- "antikythera-k8s-3.local:192.168.100.13"
app2:
hostname: antikythera-k8s-2.local
networks:
app_net:
ipv4_address: 192.168.100.12
extra_hosts:
- "antikythera-k8s-1.local:192.168.100.11"
- "antikythera-k8s-3.local:192.168.100.13"
app3:
hostname: antikythera-k8s-3.local
networks:
app_net:
ipv4_address: 192.168.100.13
extra_hosts:
- "antikythera-k8s-1.local:192.168.100.11"
- "antikythera-k8s-2.local:192.168.100.12"
networks:
app_net:
driver: bridge
ipam:
driver: default
config:
- subnet: 192.168.100.0/24
iexで確認
$ dc up --build -d
# それぞれで
$ dc exec app1 bash
asdf@antikythera-k8s-1:/app$ iex --name antikythera@$(hostname -f) --cookie cookie
iex --name antikythera@$(hostname -i) --cookie cookie
> Node.ping(:"node@antikythera-k8s-2.local")
:pong
> Node.list()
[:"antikythera@antikythera-k8s-2.local", :"antikythera@antikythera-k8s-3.local"]
AntikytheraEal.ClusterConfiguration の実装
- EAL: Environment Abstraction Layerらしい
- クラスターの設定は
AntikytheraEal.ClusterConfiguration.Behaviourでインターフェースになっていてデフォルト実装はAntikytheraEal.ClusterConfiguration.StandAloneでした running_host()はNode.self()しか返さない- config.exsの
erl_impl_modules.cluster_configurationで差し替えられるみたい - 固定でホストを返す実装
AntikytheraK8s.ClusterConfigurationをStandAloneを参考に書きます
- クラスターの設定は
use Croma
alias Croma.Result, as: R
defmodule AntikytheraK8s.ClusterConfiguration do
@behaviour AntikytheraEal.ClusterConfiguration.Behaviour
@impl true
defun running_hosts() :: R.t(%{String.t() => boolean}) do
{:ok,
%{
"antikythera-k8s-1.local" => true,
"antikythera-k8s-2.local" => true,
"antikythera-k8s-3.local" => true
}}
end
@impl true
defun(zone_of_this_host() :: String.t(), do: "zone")
@impl true
defun(health_check_grace_period_in_seconds() :: non_neg_integer, do: 30)
end
動作確認
config :antikythera,
antikythera_instance_name: :antikythera_k8s,
eal_impl_modules: [
cluster_configuration: AntikytheraK8s.ClusterConfiguration
]
asdf@antikythera-k8s-1:/app$ ./remote.sh
iex(antikythera@antikythera-k8s-1.local)1> RaftFleet.active_nodes
%{
"zone" => [:"antikythera@antikythera-k8s-3.local",
:"antikythera@antikythera-k8s-1.local",
:"antikythera@antikythera-k8s-2.local"]
}
k8sでerlangクラスターを作る
再デプロイの手順
- antikythera-k8sイメージを作成
docker build --tag=wakametech/antikythera-k8s:latest . --no-cachedocker push wakametech/antikythera-k8s:latest
- manifestを書く, replicasは3
- 更新:
k apply -f k8s/manifest.yaml - リデプロイ:
kubectl rollout restart deploy antikythera-k8s- 1分位でPodが入れ替わる
- 更新:
- コンテナにアタッチする
k get pod,k exec -it <POD_NAME> bash- ホスト名はPod名と同じ,
/etc/hostsには自身しかない
- ホスト名はPod名と同じ,
修正中は上記のDockerfile更新->push->再デプロイ->アタッチを無限に繰り返す...
# イメージを作成
docker build --tag=wakametech/antikythera-k8s:latest . --no-cache
# イメージをプッシュ
docker push wakametech/antikythera-k8s:latest
# 再デプロイ
kubectl rollout restart deploy antikythera-k8s
# 1分位でPodが入れ替わる, 数分でantikythera_k8sも起動する
k get pod -w
# コンテナにアタッチ
k exec -it `k get pod -o json | jq ' .items[] | select(.metadata.labels.app == "antikythera-k8s") | .metadata.name' | jq -sr '.[0]'` bash
NAME READY STATUS RESTARTS AGE
antikythera-k8s-85cf9994bc-5mvvr 1/1 Running 3 (109s ago) 16m
antikythera-k8s-85cf9994bc-lgjvc 1/1 Running 2 (66s ago) 11m
antikythera-k8s-85cf9994bc-mj8rx 1/1 Running 1 (4m54s ago) 11m
なんかRestartを繰り返す
- スリープ忘れだった. 起動時に
sleep infinityを入れることで解決
ClusterConfiguration を実装する
Pod内から kubectl を叩けばホスト名の一覧を取得できそう
- Running kubectl Commands From Within a Pod | by Thomas Stringer | ITNEXT
- kubectlをダウンロード
- Pod一覧が取得できる権限のサービスアカウントを作成, 再デプロイ
kubectl get pod -o jsonでJSON形式で結果が取得できるのでそこから起動中のPodのIPアドレスを取得
ClusterConfigurationの実装
defmodule AntikytheraK8s.ClusterConfiguration do
@behaviour AntikytheraEal.ClusterConfiguration.Behaviour
defun get_pod_ip_and_statuses(label_app :: String.t()) :: Map.t(String.t(), boolean()) do
{res, 0} =
System.cmd("kubectl", [
"get",
"pods",
"-o",
"json"
])
Jason.decode!(res)
|> Map.get("items")
|> Enum.filter(fn item ->
item["kind"] == "Pod" && item["metadata"]["labels"]["app"] == label_app
end)
|> Enum.map(fn item -> {item["status"]["podIP"], item["status"]["phase"] == "Running"} end)
|> Map.new()
end
@impl true
defun running_hosts() :: R.t(%{String.t() => boolean}) do
hosts_with_status = get_pod_ip_and_statuses("antikythera-k8s")
{:ok, hosts_with_status}
end
end
asdf@antikythera-k8s-784d7bd9f8-9987c:/app$ MIX_ENV=prod \
RELEASE_NODE="antikythera@$(hostname -i)" \
RELEASE_COOKIE="local" \
rel_local_erlang-24/antikythera_k8s/bin/antikythera_k8s remote
Erlang/OTP 24 [erts-12.3.2.13] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1] [jit]
Interactive Elixir (1.13.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(antikythera@192.168.1.153)1> Node.list
[:"antikythera@192.168.1.30", :"antikythera@192.168.1.94"]
iex(antikythera@192.168.1.153)2> RaftFleet.active_nodes
%{
"zone" => [:"antikythera@192.168.1.153", :"antikythera@192.168.1.30",
:"antikythera@192.168.1.94"]
}
🙌🙌🙌
ホスト名でもつなぎたい
TODO: PodのDNSについて