Unyablog.

のにれんのブログ

SQLAlchemy で MySQL の JSON_CONTAINS をやる

SQLAlchemy で MySQLJSON_CONTAINS をやるには func.json_contains を用いれば良いのだけど、 JSON_CONTAINS のドキュメント にあるように candidateJSON ドキュメントである必要がある。

以下のようにすると hogeJSON ドキュメントではないためエラーが出る。

from sqlalchemy import func

# Foo.values は JSON の配列である想定

session.query(Foo).filter(
    func.json_contains(Foo.values, 'hoge')
).all()
sqlalchemy.exc.OperationalError: (MySQLdb._exceptions.OperationalError) (3141, 'Invalid JSON text in argument 1 to function json_contains: "Invalid value." at position 1.')

JSON ドキュメントにするには JSON_QUOTE を使うと良い。JSON_QUOTE すると foo"foo" になる。

SQLAlchemy でやるには func.json_quote を挟む。

from sqlalchemy import func

# Foo.values は JSON の配列である想定

session.query(Foo).filter(
    func.json_contains(Foo.values, func.json_quote('hoge'))
).all()

ISUCON 10 本戦に出場した(16位)

チーム 😇😇😇 として ISUCON 10 本戦に参加した。

isucon.net

結果は 14206 点で 16位。くやしい!

レポジトリは以下。よく分からない Author になってるコミットは大体自分によるもの。

github.com

以下はチームメイトのブログ。コードの改善などは基本的にそちらを見てください。

blog.utgw.net

やったことなど

自分はいつもどおりサーバーを触る人だった。恒例になってきた一連の動作とパフォーマンスを除いて、今回特別にやったことや所感を書いていこうと思う。

Envoy

Envoy が入っていて「自分は Envoy の YAML を手で書いたことがあるぞ!!任せろ!!!」って張り切ったんだけど、実際には Envoy らしいことは特にしておらず普通の Reverse Proxy だった。さっさと Nginx に載せ替えても良かったと思う。 Envoy の設定って手で書くものじゃないしね…。

実際静的ファイルの配信をするために Nginx を導入したけど、当日は全てを Nginx に載せ替えることはしなかった。Envoy の設定には困っていなかったし、 ISUCON で使うと面白そうだったので😈。

気になった点としては、

  • 最後のほうで LimitNOFILE に到達して突然死した

    その時は時間がなくて雑に Restart することで解決したけど、後で調査した結果 LimitNOFILE 到達が原因だった。そういえば上げ忘れてたね…。それでも突然死はやめてほしいが、そういう思想らしい。

  • ベンチマーク中 CPU 使用率が 40% と高かった

    今まで ISUCON で Nginx を使ってたときは困ったことがなかったので意外だった。TLS 終端してるとそんなもんだったっけ?

手で設定しにくいということを除いても、普通の Reverse Proxy として使うなら Nginx のほうが扱いやすいなあと思った。

サーバー構成

今回はサーバースペックが3台とも違っていて、

  1. CPU 2コア メモリ 1GB
  2. CPU 2コア メモリ 2GB
  3. CPU 4コア メモリ 1GB

という構成だった。コンテスト中、構成は以下の変遷をたどった。

  1. サーバー1 単体

    初期状態。

  2. サーバー2: MySQL サーバー3: Web, API, Envoy, Nginx

    MySQL といえば InnoDB バッファプール!ということでメモリの大きい方に載せた。サーバー1は開発用。

  3. サーバー2: Web, API, Envoy, Nginx サーバー3: MySQL

    サーバー2を見ていたらメモリは 30% ぐらいしか使っていないのに CPU がサチってたので載せ替え。サーバー3はメモリも CPU も80%程度で良い感じ。

  4. サーバー1: API, Envoy, Nginx サーバー2: Web サーバー3: MySQL

    サーバー2 を見ていたら意外にも Envoy が食っていたので Nginx と API ごと 1 に載せ替えた。

この状態でリソース不足が起きることはなくなった。謎エラーがなければもっと伸びたよなぁ~~~~。

Protocol Buffers

今回 Web アプリの API には Protocol Buffers が使われていたので、デバッグのために Protocol Buffers を解読し打てるようにする必要があった。

色々考えた結果 Wireshark でパケットキャプチャして直接眺めることに。

ただ、色々難しいことがあり…

  • WiresharkTLS の解読ができない

    そういえば TLS の中身見れる機能があったな、って秘密鍵だけ持ってきたけど解読できなかった。まあ言われてみれば DH だしね…*1。新たな知見を得た。

    これは Envoy とアプリ間の通信を見ることで解決。

  • Wireshark で Protocol Buffers のパースができなかった

    最新の Wireshark だと見れる機能があるはずなのだけど、うまく行かなかった。Proto を Import するパスが間違っていた?それとも HTTP/2 や gRPC じゃないから見れない?*2

結局パケットキャプチャした内容をそのまま bytes で保存して、 protoc に噛ますことで見れるようになった。

$ cat /tmp/foo.bin | protoc --decode=xsuportal.proto.services.admin.InitializeRequest /Users/nonylene/codes/innocent-team/isucon10f/proto/xsuportal/services/admin/initialize.proto --proto_path /Users/nonylene/codes/innocent-team/isucon10f/proto

めっちゃむずかった!普通に Printf デバッグなりすればよかったね。

デバッグ用のリクエストを作るのも色々手間取ってしまった。初めはこれ gRPC だ!って勘違いしていたのでずっと grpcurl でやろうとして使い方がわからん…と言っていた*3。結局 curlprotoc を組み合わせて解決。

github.com

MySQL 8

予選は MySQL 5.7 だったけど本戦は MySQL 8 だった。

ISUCON 8 本戦で MySQL 8 にめちゃくちゃハマった経験があったけど、そのおかげもあって今回は全く困らなかった。 GRANT 文とユーザー作成を分けなきゃいけなくなったんだな~、ぐらい。

全体の感想とか

結局色々やったものの utgw のブログに上げられているエラーが解決できず、サーバーも本領発揮せずに終わってしまった。みんなで色々見てたけど分からず厳しかった!!

感謝

今年のコンテストも楽しかったです!今年は本戦もリモートとなりましたが、Tシャツや名札が事前に送付されたり、当日に YouTube ライブをしていたりとリモートでも盛り上がるように色々施策があって良かったと思います。問題解説ビデオ最高でした。

www.youtube.com

問題も解きごたえがありました。サーバーによってスペックが違うのも新鮮で、「とりあえず3つに分けとこ」だけではなくちゃんとサーバーのリソースを検討することを求められていて楽しかったです。

今年もチームメイトに助けられました。自分だけだと絶対出れてない(学生一人チームすごいよね…)し、バランスの良いチームだなあと思います。来年もやっていきたい!

*1:じゃあ RSA にするか、パフォーマンスも良くなるだろうし、と思ったけど Envoy での設定方法がわからなかっ上、ベンチマーカーが対応してるのかも不明だったのでやめた

*2:Protocol Buffers のライブラリを使っているだけなので、 Envoy とアプリ間の通信は HTTP/2 や gRPC ではなく HTTP 1.1 だった。

*3:途中で息抜きに外に出て考えたら「あっこれ gRPC じゃないじゃん」って気づいた。休憩は大事。

ISUCON 10 で予選突破した(24位)

ISUCON に今年も出場して、めでたく予選突破できた。チーム名は 😇😇😇 で、チームメイトはいつもと同様 utgwkk と wass80。

isucon.net

チームメイトのエントリは以下。

wass80.hateblo.jp

blog.utgw.net

レポジトリは以下。

github.com

やったこと

いつも通り自分は Schema とかアプリケーションはあまり見ずにインフラに徹していた。New Relic も全然見てない。

  • ssh config 書く
  • ユーザー作る
  • pt-query-digest や netdata 入れる
  • レポジトリ整備(スクリプトや symlink)
  • 余計なプロセス消す
  • Nginx で Bot の ban やったり proxy_cache やったり
  • sysctl などのチューニング
  • MySQL のチューニング
    • クエリキャッシュとかスロークエリ出すとか
  • MySQL の複数台分散

いろいろ

Nginx

proxy_cache_valid を設定しないとキャッシュが効かなかったけど本当かな?

MySQL

今回も色々悩まされた。 symlink でやったら何故か上手く行かなくなったり、GRANT で手間取ったり。

一番時間を食われたのはインスタンスをまたいだ接続ができなかったことで、ポートも開いてるし許可もしてるのにサーバー側に Bad handshake エラーが出て全く接続できなかった。

色々見た結果、 最初に MTU を雑に 9000 に設定していたのが原因で、 1500 に戻したら直った。 PMTUD やら TCP MSS やらが動かなかったんかな~。

クエリキャッシュ

今回は Write が少ないためクエリキャッシュがとてもよく効いてくれた。 600点が1500点ぐらいになって一時は2位になってたはず。

query_cache_size だけではダメで、 query_cache_type = ON にしないと動かないことに初めて気づいたのだけど、今までずっとミスし続けていた…。 MySQL 8 にはクエリキャッシュ無いのでこれからはあまり使うこともなさそうな新知見。

複数台分散

今回は MySQLボトルネックだったので複数台分散をどうするか考えていた。結局 *sqlx.DB を複数持って、書込側はレプリケーションせずに両方に並列に書き込む形に、取得側は2台からランダムに取ってくるようにした。

準同期レプリケーションや、nginx の proxy とか LVS を使っても良かったのだけど、アプリケーションがそこまで大変ではなかったのでそちらの変更で済ますことにした。

一見ヤバそうな方針だけど結果的にはちゃんと動いてくれて、スコアを伸ばすことができた。goroutine 最高*1

とはいえ、垂直分割が一番賢いよなぁ。コンテスト中は全く思いつかなかった。

その他

  • Go を使った

    今までずっと RubyPython でやってきたけどバグったときの修正が大変だった。Go は早いし、コンパイル時に型チェックもしてくれるし、IDE は整っているし最高だった。過去の回でよく悩まされてきた謎のエラーもほとんど起きずに、スムーズにスコアを伸ばすことができた。これからも ISUCON では Go を使うと思う。

  • 終盤の Fail

    終盤に複数台分散を入れたら時々落ちることがあったけど、最終的には安定して通るようになって良かった。とっさの reboot が効いたのか、 sysctl を全部戻したのが効いたのか、MySQLinnodb_doublewrite あたりを全部デフォルトに戻したのが効いたのか。

  • Mitigations の無効化

    Mitigations を無効にするカーネルパラメータを入れると早くなるという話題があって、やることも考えていたのだけど、 grub-install が必要で流石に怖くてやめた。全然点数出なかったら最後に試してたかも。

今回も予選にも関わらず複数台構成ということで、インフラ担当の自分としてもやりがいのある問題でした。運営のみなさんありがとうございました!本戦もよろしくお願いします!!

2年ぶり3回目の本戦出場でとても嬉しいし、本戦もこの調子でやっていきたい!

*1:ミスって wass に直してもらったけど😇

eBPF: bpf_skb_store_bytes の BPF_F_RECOMPUTE_CSUM は tc_cls の egress では動かなさそう

最近 bpf についてめっちゃ書いてるけど、ドキュメントが弱く検索しても情報がないからです…

bpf_skb_store_bytes には BPF_F_RECOMPUTE_CSUM というフラグがあって、ドキュメントによると store 後にチェックサムを更新してくれるらしい。

しかし、これを tc_cls (classifier, BPF_PROG_TYPE_SCHED_CLS) の egress に使うとチェックサムが更新されなかった。

理由は(SKB のライフサイクルに関する知識が足りないため)確証まで至っていないけど、コードを見る限り skb->ip_summedCHECKSUM_COMPLETECHECKSUM_PARTIAL の場合に変更するようなコードになっていてそれは egress には関係ないのでは?とか、 skb->csum を tc で更新してもデバイスにわたす直前だから反映されないのではないか?とか考えている。

対処

手動でチェックサムを更新する関数(bpf_l3_csum_replace)があるのでそれを使う。

eBPF: sk_skb は parser と verdict どちらも attach する必要がある

eBPF で sk_skb (BPF_PROG_TYPE_SK_SKB) を使うとき、 parser と verdict どちらも attach する必要がある。一方だけを attach しても上手く動かない。

環境は ArchLinux で Kernel release は 5.7.10-arch1-1。

$ bpftool prog attach pinned /sys/fs/bpf/sk_skb stream_parser pinned /sys/fs/bpf/sk_skb_map
$ bpftool prog attach pinned /sys/fs/bpf/sk_skb stream_verdict pinned /sys/fs/bpf/sk_skb_map

detatch 時も同様。

(補足: /sys/fs/bpf/sk_skb に pin したプログラムには verdict しか書いていなかった。それでも parser としても attach しないと動かなかった)

stackoverflow.com

bpftool がおかしいのかと思って自分で attach するコードを書いてみて気づいた。今回も bpftool はおかしくなかった。

2020/08/04 追記

どうやら stream_parser はちゃんと書かないとダメらしい(そうじゃないと stream_verdict が複数回呼ばれてしまう)。別々のファイルに書いてそれぞれ attach することにした。

eBPF: BPF_MAP_TYPE_SK_STORAGE が Invalid argument

Linux の eBPF で BPF_MAP_TYPE_SK_STORAGE を使おうとして、map を定義して bpftool で流し込んだけど Invalid argument でうまく動かなかった。調べてもヒットしなくて長時間費やしたのでメモ。

環境は ArchLinux で Kernel release は 5.7.10-arch1-1

TL;DR

BTF に対応する必要がある。

詳細

struct bpf_map_def SEC("maps") sk_storage_map = {
  .type = BPF_MAP_TYPE_SK_STORAGE,
  .map_flags = BPF_F_NO_PREALLOC,
  .key_size = sizeof(int),
  .value_size = sizeof(int),
}; 

このような map を作って、それを使ったプログラムを load しようとしたところ下のエラーが。

$ sudo bpftool prog load sk_storage.o /sys/fs/bpf/sk_storage
...
libbpf: failed to create map (name: 'sk_storage_map'): Invalid argument(-22)
...

コンパイルできるのに動かないので、自分の環境がおかしいのかと思って テスト を走らせてみたけど、それはちゃんと PASS した。 BPF_TYPE_MAP_SK_STORAGE 自体は使えるらしい。

ただ、そのテストでは bpf_create_map_xattr を使って C 言語から直接 map を作成していた。自分は bpftool を使いたいので単純に真似することはできない。

まず、エラーになってる原因を探るために strace する。

$ sudo strace bpftool prog load sk_storage.o /sys/fs/bpf/sk_storage
...
bpf(BPF_MAP_CREATE, {map_type=BPF_MAP_TYPE_UNSPEC, key_size=0, value_size=0, max_entries=0, map_flags=0, inner_map_fd=0, map_name="sk_storage_map", map_ifindex=0, btf_fd=0, btf_key_type_id=0, btf_value_type_id=0, btf_vmlinux_value_type_id=0}, 120) = -1 EINVAL (Invalid argument)
write(2, "libbpf: failed to create map (na"..., 73libbpf: failed to create map (name: 'sk_storage_map'): Invalid argument(-22)
) = 73
...

strace すると BPF の argument も見れるんですね、便利。どうやら bpftool は prog を load しているだけで、実際に map を作っているのは libbpf らしい。

ただ、これだけじゃなぜ EINVAL が出ているのか分からない。その後カーネルのコードを探っていくと BPF_MAP_TYPE_SK_STORAGE の map を作成するときのバリデーションで引っかかってそうなことが分かった。

github.com

ここでは map を作る際に attr をバリデーションしている。この関数の内容を見ると btf_key_type_idbtf_value_type_id が 0 だと EINVAL になるようになっている。

strace の結果を見るとたしかに btf_{key,value}_type_id がたしかに 0 になっており、落ちそう。

btf_{key,value}_type_id がないのはなぜか?

btf_{key,value}_type_idBTF 関係のパラメータで、要するに key と value の型情報を残す必要があるらしい。

そうするには、 https://www.kernel.org/doc/html/latest/bpf/btf.html#bpf-map-create にあるように bpf_map_def に対して BPF_ANNOTATE_KV_PAIR を使ってもいいし、bpf_helpers にある __type を使ったやり方でもいい。

bpf_helpers にある __type を使ったやり方だとこうなる。

struct {
  __uint(type, BPF_MAP_TYPE_SK_STORAGE);                                                                                                                                                         
  __uint(map_flags, BPF_F_NO_PREALLOC);
  __type(key, int);                                                                                                                                                                              
  __type(value, int);
} sk_storage_map SEC(".maps");

SEC 名を "maps" じゃなくて ".maps" (MAPS_ELF_SEC) にする*1ことに注意。

個人で運用しているサーバーを Kubernetes に整理する

個人で運用している VPSKubernetes を乗せて既存のシステム(Web アプリとか Slack Bot とか Cron ジョブとか)をガッと移行した。

もともとは itamae 使って Systemd でサービス立ち上げていたのだけど、ソフトウェア依存とか権限分離とか冪等性とか面倒になってきて、うおお Docker でデプロイするぞ!となっていた。

初めは Docker Compose で立てようと思っていたけど、k8s のほうが楽しそうなので k8s にした。実際楽しいので良いと思う。

何を使って Kubernetes を立てるか

Microk8s

Microk8sCanonical が配布しているもので、Ubuntu にログインすると広告が出てくるアレ。

インストールしたらすぐにシングルノードで動かせるように作られている。アドオンもいくつかあって、k8s を自前で運用するときのネットワークのめんどいところを解決してくれるのが魅力的。

初めはこれを使って立てていたのだけど、しばらく運用していると色々デメリットが見えてきてやめてしまった。

メリット

  • 入れるのが簡単
  • いくつかアドオンが用意されていて、ネットワークプラグインなどを簡単に有効化できる
  • シングルノードで動かすようにパラメータなどが予め設定されている
  • (k3s とは違って)動いているのは k8s そのものである

デメリット

  • 入れ方が通常と異なるのでハマる
    • kubectlmicrok8s.kubectl になってるし kubelet の動く Systemd unit も普通と違う
    • Docker ではなく containerd を直接使っていてハマる(ハマった例
    • コンポーネント/snap に入っていて全体的にムズい
  • 時々変な挙動になる ()
    • Cilium 対応してるけど Pod の IP range 変だったり…
  • 依存ソフトウェアが古かったりする ()
    • 新しいのを入れようとすると Microk8s の環境に合わせる必要があってダルかったり
  • アドオンを入れるときに細かい設定ができず、結局自分で入れることになる
  • snap 使いたくない

やってることは複雑ではないので自力で修正可能だけど、変な挙動を割と多く見てしまったし、そこで苦労する必要ないなと思った。

kubeadm

kubeadmKubernetes の標準的なインストール方法。kubeadm をインストールしてコマンドで kubeadm init すると証明書等が作成されてクラスタが立ち上がる。

初めは面倒くさそうで避けていたけど、やってみると特段難しいこともなかった *1 ので Microk8s ではなくこれを使うことにした。

メリット

  • 素の Kubernetes が入るのでパッケージに特有なことでハマらない
    • Ubuntu は利用者が多いのでちゃんと動く
  • apt で入るので分かりやすい

デメリット

  • アップグレードやネットワークプラグインの選定など、自分で管理する必要がある
    • とは言ってもドキュメント読んでたら難しくない

他に考えたこと

  • k3s

    軽いのは魅力的だが、使いたい機能が無くて困りそうなことや、周辺コンポーネントが動かなかったりしてハマりそうなのでやめた。

  • minikube

    VM 上で動かすのを前提としている*2っぽいのでやめた。

  • kubespray

    kubespray は内部で kubeadm を使っている Ansible 用の Playbook。これを使うとアップグレードとかもよしなにやってくれるらしいが、今回はシングルノードかつ kubeadm がそんなに複雑でなかったので不要と判断した。

kubeadm を使ったセットアップは itamae に任せることにした。とは言ってもこれぐらいのシンプルなもの。

# kubeadm や kubelet のインストール
include_recipe "../kubeadm"

kubeadm_conf = '/tmp/kubeadm.conf'

remote_file kubeadm_conf do
  owner "root"
  group "root"
  mode "644"
  not_if "test -f /etc/kubernetes/admin.conf"
end

execute "kubeadm init --ignore-preflight-errors Swap --config #{kubeadm_conf}" do
  not_if "test -f /etc/kubernetes/admin.conf"
end

ネットワークプラグイン

ネットワークプラグインには Cilium を使うことにした。Flannel とは違って NetworkPolicy 使えるのと、BPF 使ってて面白そう + 軽そうだから。

実際、 Calico と比べると iptables のルールがとてもスッキリしている。Cilium コマンドを使えば今どんな policy を適用しているかが分かりやすく出てきて良い感じ。

通信周りで mTLS などより細かい制御をするには Istio あたりが必要になるが、今回は重くなるだろうと思ったので入れていない。

Volume 周り

static だけどたまに編集していくようなウェブコンテンツ(https://nonylene.net/ とか)を serve することを考えると、ノード上の適当な場所にコンテンツを置きつつ、 nginx のコンテナから Persistent Volume としてマウントするのが良さそう。

ただ、 Persistent Volume を使うためだけに NFS 立てたりするのは大変なので、去年出た Local Persistent Volume を使うことにした*3

具体的には、 nonylene.net/storage-web のラベルがあるノードに PV を作って PVC を発行し、Pod に紐付けるようにした。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: web
  labels:
    app.kubernetes.io/name: web
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-storage
  local:
    path: /data/web/html
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: nonylene.net/storage-web
          operator: In
          values:
          - "true"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: web
spec:
  storageClassName: local-storage
  accessModes:
    - ReadWriteOnce
  selector:
    matchLabels:
      app.kubernetes.io/name: web
  resources:
    requests:
      storage: 1Gi

オブジェクト管理

Kubernetes は Deployment や Service のオブジェクトを置くことでデプロイを行うのだけど、設定ファイル流し込みとかパッケージ管理とかしたくなる。

まず、設定ファイル流し込みなど YAML の統合的な管理には kustomize を使っている。kustomize を使うと

  • 複数の YAML を一つの YAML にマージして適用できる
  • アプリを構成するオブジェクトに共通のラベルを付けて管理ができる
  • ConfigMap をファイルから生成する
  • Secret を env やファイルから一元的に管理・生成する

ので k8s の管理がだいぶ楽になる。itamae あたりとちょっと近い。

次に、パッケージ管理としては Helm を使う。これは自分で YAML を触ることがないような Cilium や ingress-nginx あたりのインストールに使う。

初めはこの辺も直接 YAML ダウンロードして kustomzie 配下に置けばいいやと思っていたのだけど、

  • アップデートしないまま忘れる
  • ビルドした YAML が巨大になる→適用が遅くなる
    • CRD やそのドキュメント定義してたりすると非常に大きな YAML になってしまう

のでやめた。

とはいっても、 Helm のコマンドだけでは管理しづらいので Helmfile を使ってパラメータの管理をしている。

アプリケーションの移行

今まで Systemd で適当に動かしていたアプリケーションを、 Docker で動かすようにコンテナ化していく。

とはいっても

  • できるかぎり環境変数からパラメータを取れるようにする
    • 今までは config.py を作ることが多かった
  • Dockerfile 作る
  • オープンソースで作っていたので) Dockerhub に上げてビルドしてもらう

ぐらいでそこまで大変な作業ではなかった*4

今は Dockerhub に上げているけど、ビルドキューが捌けるのがとても遅いのがネック。我慢できなくなったら GitHub Actions でやるようになると思う。

感想・補足

  • コンテナ化

    従来と比べてデプロイ周りがとても楽になった。 git clone してセットアップして…というサーバー上で動かさないといけないことが随分と減った。 さらに、冪等性がどうこうとか考えなくてよく、統一的な方法でデプロイできるので、各種ミドルウェアを導入するのが今までよりも気軽にできるようになった。apt で配ってないソフトウェアでも Docker では多く配布されてるので、それらが引っ張ってくるだけで動くのが嬉しい。

  • Kubernetes

    Kuberentes の一番大きなメリットはコンテナを良い感じのホストに配置してくれることだが、今回はシングルノードのため関係ない。その他の点で Docker Compose と比べると

    • 複数のサービスを統合的に管理できる
      • Docker Compose とかだとレポジトリごとの管理になるから Systemd unit 増えていくし、共通のデータストアとか作るのめんどい
    • オブジェクトの変更を行うと自動的に変更が反映される
      • ssh しなくても kubectl apply だけで完結できる
    • Ingress という、初めに HTTP リクエストを振り分けるためのロードバランサが定義されており、周辺ツールとも統合的に使える
      • 例えば cert-manager を用いれば Let's encrypt の証明書取得を自動的に行なってくれる

    のが良いと思った。

    ただ、YAML を書きまくるのは体験が良くない*5。TOML で書けたり aws-cdk 的なのを使えればいいのにな。

    また、たまに「これが欲しいけど Issue になったあと動きがない」というケースがあり、まだ開発中なのだなぁと思う。

  • リソース

    悪い点としてはリソースをたくさん食うことで、k8s だけで 900MB ぐらいはメモリを持っていかれる。今の所メモリ2GBのVPSで動かしているけど、アプリケーションを色々乗せるとカツカツになってしまった。3GB~4GBあると余裕だと思う。

  • HTTP 以外

    メールや DNS の権威サーバーを外部に出したい場合は、Ingress は使えないので MetalLB あたりで仮想のロードバランサーを作る必要がある。今回は DNS とメール転送は Google Domains のマネジメントサービスに移した結果、HTTP だけになっていたので使わなかった。

  • 開発環境

    あくまでも k8s はデプロイする環境として使うので、開発環境としては Docker Compose や直接プロセス立ち上げたりする。

これで気軽にサービスをデプロイできる環境が整った。自分で Web アプリケーション作ってもデプロイや運用めんどいな…とか、MongoDB 使うとそのデプロイが…とか最近思っていたけど、これからはどんどん使っていこうと思う。

*1:シングルノードだからといって何かしないといけないこともなかった

*2:vm-driver=none すれば使えるけど非推奨 https://minikube.sigs.k8s.io/docs/reference/drivers/none/

*3:シングルノードなので hostPath を使っても良かったけど、こちらはノード本体の設定を触る用途で使われることが多そう

*4:Docker は普段から開発に使っているので

*5:YAML は書きづらいし読みづらいと常々思っている