Unyablog.

のにれんのブログ

個人で運用しているサーバーを 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 は書きづらいし読みづらいと常々思っている

別のサブドメインの cookie をセットできるのか

できない。

疑問

  • foo.example.comDomain=bar.example.com をセットできるのか?
    • Domain=example.com はできる
    • Domain=example.net はできない

答え

できない。

The user agent will reject cookies unless the Domain attribute specifies a scope for the cookie that would include the origin server. For example, the user agent will accept a cookie with a Domain attribute of "example.com" or of "foo.example.com" from foo.example.com, but the user agent will not accept a cookie with a Domain attribute of "bar.example.com" or of "baz.foo.example.com".

RFC 6265 - HTTP State Management Mechanism

Poetry を使ってみた

この記事は KMC 2 Advent Calendar 2019 3日目の記事ということにしました。

Poetry

PythonDependency Manager は pipenv が有名で、自分もリリース当初から使っていた。ただ、最近はリリースが一年間されておらず、割と不安な状態*1

最近は pipenv への対抗馬として Poetry が開発されているらしい。こちらは活発でリリースも頻繁にされており良い感じ。

pipenv で決まりだと思っていたのに… と思いながらも Poetry を使ってみた。

pipenv は Pipfile で依存パッケージを記述するが、Poetry では pyproject.toml を用いる。その他細かい使い方は README 参照。

感想

良かったところ

  • ちゃんと使える
  • PyPI にアップロードするのがとても楽になる

    pyproject.toml に書いたら poetry build && poetry publish で終わり!

  • 依存解決が早い

  • グラフィカルなコンソール表示
  • 機能の取捨選択が良い

    pipenv はおもてなし機能が多すぎると思う。

  • ソースコードがきれい

    これは出たばかりだからかも。

poetry run の実装を見たところ、ちゃんと os.exec しているので安心した。

イマイチなところ

  • CLI のインターフェースは pipenv のほうが好き

    poetry addinstall コマンドにまとめて欲しい、とか。

    pyproject.toml が無いときに addinstall しても、良しなに init されて動いて欲しい、とか。

  • カスタマイズが弱い

    単純にフラグが少なくて、要求とマッチせず困ることがある。

    また、 pipenv ではフラグ類は基本的に環境変数で設定できるが、Poetry は一部のフラグしか設定できない

  • まだ絶賛開発中である

    時々バグっぽい挙動がある。作者が想定していそうなパッケージストラクチャを使っていれば動くが、そこから外れるとよく分からないエラーが出る。結局ソースコードを読むことになる。

  • (良い意味でも悪い意味でも)かっちりしている

    パッケージ追加時において、pipenv はデフォルトのバージョン指定は * (どのバージョンでも ok)である。それに対して、 Poetry は ^{current version} となる。個人の開発レベルでは * のほうが嬉しい(* にする手段はある)。

結論

まだ開発中ではあるものの、パッケージマネージャとして欲しい機能がちょうど良く揃っていて良いソフトウェアだと感じた。特に setup.py を生成してくれるのが魅力的で、PyPI にアップロードしようという気持ちにしてくれる。

もうすぐ 1.0 が出るらしいし、当面は Poetry で過ごそうと思う。

*1:master だけは進んでいる…

iTerm2 JISキーボード 文字 拡大 Karabiner-Elements

iTerm2 で文字を拡大するショートカットは ⌘ + である。

JISキーボードでは +Shift ; を入力することになるので、⌘ + は JIS では Shift ⌘ ; が入力される。

一方 iTerm2 では、Shift ⌘ ; が Open Command History に割り当てられている。このため、JISキーボードで文字を拡大しようとすると謎の機能が発火してしまう。

これの対策として、

  • iTerm2 を表示している
  • JISキーボードである

場合、Karabiner-ElementsShift ⌘ ;⌘ + に変換するようにする。

{
  "title": "Send Cmd+'+' on iTerm2",
  "rules": [
    {
      "description": "Send Cmd+'+' on iTerm2",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "semicolon",
            "modifiers": {
              "mandatory": [
                "command", "shift"
              ]
            }
          },
          "conditions": [
            {
              "type": "frontmost_application_if",
              "bundle_identifiers": [
                "com.googlecode.iterm2"
              ]
            },
            {
              "type": "keyboard_type_if",
              "keyboard_types": [
                "jis"
              ]
            }
          ],
          "to": [
            {
              "key_code": "keypad_plus",
              "modifiers": [
                "left_command"
              ]
            }
          ]
        }
      ]
    }
  ]
}

YAML から JSON に変換するツールを Nim で書き、GitHub Actions でリリースする

最近 config が YAML になっているアプリケーションが多い。ただ、自分は YAML の読み書きが苦手である。

設定を書くには機能が多すぎるのと、(config なので)ネストしやすいのにインデントが関わってくるのが苦手なのだと思う。

何度かハマった結果、これからは JSON に変換して読むことにした。JSON を読むのには慣れているし、jq など色付・抽出するツールも持っているので最高 *1

YAMLJSON に変換するツールは世の中に多くあるが、そんなに大変じゃないので自分で作ることにした*2

github.com

何で作るか

案1 Ruby で書く

Ruby は標準ライブラリに YAML パーサーが入っているので、適当な rubyスクリプトを書けば目的は達成される。

ただ、そのためだけに Ruby 入れるのも嫌だな…って思ってやめた。

案2 Go で書く

Go のようなコンパイラ言語であれば、標準ライブラリに入ってなくともコンパイルすれば良いので楽である。

また、Go は goreleaser や go get などインストールしやすい環境が整っており最高。

というわけで初めは Go で書いていたのだけど、YAMLJSON で型が微妙に違う(YAMLmap[int]interface{} のこともあるが、JSONmap[string]interface{})のが面倒でやめた。

案3 Nim で書く(採用)

Nim は先日 1.0.0 が出たイケイケの言語で、Python っぽく書けて便利という評判が自分の中である。Go が無理なら Nim チャンス!ということで Nim で書くことにした。

Nim の YAML ライブラリ は最高で、loadToJson 関数があるので一瞬で終わった。

バイナリを作る

リリース周りは CI でやりたい。今回は GitHub Actions を使ってみることにした。GitHub Actions では、Action を組み合わせたり、自分でコマンドを記述したりして CI の動作を記述する。

Action は 公式がいくつか配布している 他、サードパーティのもある。今回はサードパーティ Action は使わない方向でやってみた。

Nim with GitHub Actions

公式では Golang など様々な言語をインストールする Action があるが、残念ながら Nim はない。なので自分で環境構築を行った。

macOS

brew が入っているので、 brew install nim する。

Ubuntu

Ubuntu では Docker を使った(apt で Nim が配布されているものの、バージョンが古くて依存ライブラリがコンパイルできない)。

amd64 だと 公式 Nim Docker Image を使えばよかったが、クロスコンパイルはできなかったので、 arm 用には クロスコンパイルできる Image を Docker Hub に作った *3

ちなみに、GitHub Workflow には Docker 上で動かしたり Volume を作ってくれたりする機能があるが、それを使うにはパブリックイメージを使う必要がある。そうでない場合は自前で docker build して docker run ... することになると思う。

その他

良かった

悪かった

まだ出たばかりというのもあって、使いづらい点も多かった。

  • 公式の Actions が機能不足

    artifact は一つずつしか Upload できない等、「一応できるけど不便」みたいな例が多い。 特にリリース周りは不便で、本当はもっとうまくできるのにな…と言いながら長い Workflow になってしまった。

  • job 間で値の受け渡しがしずらい

    値を直接受け渡しすることはできず、 artifact 経由になってしまう。「一回リリースつくって、そこに並列で artifact を追加する」といったケースでは upload_url を渡すことができずに困った。

  • 情報が多くない

    ドキュメントはちゃんとしているが、ユースケースがまだ少ないため情報が探しにくい。

  • YAML エディタが使いにくい

    エラーや補完を出てくれるのは嬉しいが、改行時は普通に改行してほしいな…。

多くの問題は時が経てば解決するだろう。

感想

  • YAML から逃げるために作ったのに、 GitHub Actions で数十行の YAML を書いていたのは悲しかった
  • こんな面倒なことをしなくても、Go 言語を使えばクロスコンパイルもリリースも一瞬でできるので最高だと思った。でも Nim も悪くないよ。

*1:書くのに向いているとは言っていないが、YAML よりはハマらない…

*2:手段が目的になった面もある

*3:実は Docker Hub 初めてです