kOpsでIRSAをやる(1.21)

以前こういう記事を書いた.

h3poteto.hatenablog.com

この当時はIssuerも自分で作らなければいけなかったが,kOps 1.21からIRSAのサポートが入り,これがだいぶ簡略化されるようになった. というわけでこれを紹介しておく.

続きを読む

Window Managerをi3にしてみた

もともとCinnamonが好きでManjaroもCinnamonを使っていたんだけど,タイル型のウィンドウマネージャーを使いたくなったので,i3を入れてみた.

最終的な見た目

f:id:h3poteto:20210822172414p:plain

今回使った設定は全部ここに詰め込んだ.

github.com

続きを読む

自分のためだけにcustom controllerを書く

ふだんkopsで自分用のKubernetesクラスタを立ち上げているんだけど,個人用途なのでAutoScaleはしてほしくないのでClusterAutoscalerは入れていない.だけど,その状態でも可用性はできる限り下げたくない. というわけで,ちょっと自分でノードを管理するカスタムコントローラを書いた.

こういう,あんまり需要がないけど自分のところだけでいろいろカスタマイズしたいときに,適当なコントローラを自作すると手動作業が減らせるので大変良い.

個人用クラスタについて

kopsを使ってAWS上に構築している.複数台構成で

  • control-plane * 3
  • node * 3

くらいで運用している.nodeの数は乗せるものによって増やしたり減らしたり.

ちなみに全部SpotInstanceで運用している. Spotだと不意にTerminateされることが多いんだけど,大抵の場合はすぐに次のインスタンスが上がってきて,事なきを得る. たまに特定のFlavorの値段が高騰したりすると,新しいインスタンスをe立ち上げられなくなるんだけど,これに関してはAutoScalingGroupのLaunchTemplateとMixedInstancePolicyの組み合わせを使うことで,複数のFlavorを利用するとこができる. 例えば,t3.largeの値段が高騰したとしても,MixedInstancePolicyで

  • t3.large
  • c5.large
  • m5.large

とか入れていれば,この3つの中で安いSpotInstanceを探してきて立ち上げてくれる. たしかにSpotPriceはたまに高騰するんだけど,複数Flavorを用意しておけばそれらがすべて高騰することはあまりない.

実現したいこと

ノードの可用性を下げたくないが,不要なインスタンスは消したい

kopsで作成している時点で,基本的にAutoScalingGroupでインスタンスを立ち上げている.そのため特定のnodeが突然ししたとしても,新しいnodeは自動的に補充される.

ただし,特定のAvailabilityZoneのSpotが足らなくなったり,価格が高騰したりした場合,そのAZのインスタンスは起動できなくなる. 特に最近のkopsはAZごとにAutoScalingGroupを作成している.

  • masters.ap-northeast-1a.hogehoge
  • masters.ap-northeast-1c.hogehoge
  • masters.ap-northeast-1d.hogehoge
  • nodes.ap-northeast-1a.hogehoge
  • nodes.ap-northeast-1c.hogehoge
  • nodes.ap-northeast-1d.hogehoge

こうなっていると,特定のASGだけひたすらインスタンスが起動できないような状態に陥ることがある.

このときに,可用性を下げたくないので,生きているASGで代替のインスタンスを一時的に起動しておいてほしい.

という要望はありつつ,不要なインスタンスまで起動していてほしくない.なので,例えば上記のトラブルが解消されたら元のインスンタンスに戻してほしいのである.

一定期間でノードをリフレッシュしたい

かつて,kopsのAutoScalingGroupでSpotInstanceを使うときに,LaunchTemplateではなくLaunchConfigurationしか使えない時代があった.このときにSpotInstanceを立ち上げると,Spotの期限がデフォルトの7dayになってしまっていた. これは編集不可能で,仕方なく7日おきにインスタンスを殺すようなLambdaを書いていた.

ただ,このタイミングでどうしてもインスタンスが死ぬと障害になってしまうので,Lambdaではなく内部のコントローラでこれを行いたかった.

最近のkopsはLaunchTemplateが使えて,Spotの期限も無期限にできるので,特にこのような問題はない. ただ,上記のようなことをやっていたため,どうしてもあまり長くインスタンスを生かしておきたくなかった.というわけで,週に一度くらいはnodeのローリングアップデートをしたかった.

これらを一つのcontroller-managerで実現したい

少し考えてみて,これらを一つのcontroller-managerで実現したほうがよいと思った.というのも,不要なインスタンスを消す機能と,ローリングアップデートを同時に動かすのは結構難しい.ローリングアップデート時にインスタンスを突然殺す分には特に問題ない.ただ,それをやるとnode数がたらなくなり,配置できないPodが生じる可能性はある.そのため,予備のnodeを予め起動しておいてから,古いnodeのローリングアップデートをしたい.それをやろうと思うと,予備のnodeが不要なnodeと判定されないためにも,これらの動作を排他的に制御できるのが望ましく,そのために同じcontroller-managerで実現するほうが楽だと判断した.

やることが複数なので,複数CRDで複数controllerという構成になるものを,controller-managerで束ねるという形にしたいと思う.

できたもの

github.com

まだだいぶ荒削りだがとりあえず動くものはできた.

今回はcontroller-runtimeを使ってしまっている.controllerとしてそこまで複雑なことをするわけではないので.

全体的な構成

node-manager
├── aws-node-manager-master
│   ├── replenisher-master
│   └── refresher-master
└── aws-node-manager-worker
    ├── replenisher-worker
    └── refresher-worker

aws-node-managerがやること

現在のKubernetesのnodeを取得してきて,これがAWS上のどのAutoScalingGroupに属しているのか,instanceIDが何なのか,ということを埋めている.これをCRDのStatusに埋めることで,下位のcontrollerがこれらの情報を利用する.

また,CustomResourceの定義に応じてmaster, nodeのrefresherとreplenisherのCRを作成する.

refresherがやること

CRに指定されたスケジュールに応じて以下のようなステップでローリングアップデートを行う.

  1. 次のスケジュール日程を決定する
  2. スケジュールが来るまで待つ
  3. スケジュールが来たら,surplusNodes分だけ予備のnodeを追加する
  4. nodeがreadyになるのを待つ
  5. 全nodeがreadyになったら一番古いnodeを1台消す
  6. ASGによりinstanceが自動的に補充されnodeとしてクラスタに参加するのを待つ
  7. 全nodeがrefresh開始時より新しくなるまで5~の流れを繰り返す
  8. 全nodeが新しくなったら補充したsurplusNodes分のnodeを削除する
  9. 次のスケジュール日程を決定する

replenisherがやること

  1. refresh中の場合は処理をすべてスキップする
  2. desiredで指定されたnode数とreadyなnode数を比較する
  3. nodeが不足している場合はASGのdesiredを上げてinstanceを追加で起動させる
  4. このとき,desiredと実際に起動されてるinstance数が異なるASGは,spotの都合でinstanceが起動できていない可能性がある.そのため,instanceの追加対象ASGから除外して考える.あくまで正常にinstanceを起動できているASGだけを対象にdesiredを上げる
  5. nodeが過剰にある場合は,適当なASGのdesiredを下げる
  6. また,起動後一定時間経ってもクラスタに参加できていないinstanceがある場合は,ASGからdetach & terminateする

kopsでは,cluster specにてetcdの数と,etcdを起動するinstance groupを指定している.これの定義以上のインスタンスを起動しても,masterとしてはクラスタに参加することができない(etcdの定義が被ってしまう)ため,そういったインスタンスはいつまで待っていても無駄である.そのため,これも削除するようにしている.

まとめ

実際,LaunchTemplateとMixedInstancePolicyが使えるようになってから,replenisherの仕事はほぼなくなった.複数のflavorを指定しているので,特定のASGでインスタンスが起動できなくなるという事態がそもそも発生しにくくなっている.そのため,普段はほとんどrefresherの仕事を眺めるだけになっている.

Spotは中断の2分前に通知を出すことができる.

docs.aws.amazon.com

本当はこれを拾って,対象nodeのdrainができると良いのだけど,まだそこまで実装できていない. また,refreshにおいても,インスタンスを殺す前にdrainができると最高なのだが,まだ実装できていない.

ManjaroのgrubがデュアルブートしているWindowsを認識しなくなった

症状

症状としては,アップデート後にgrubのOS選択画面が表示されなくなる. grubGRUB_TIMEOUT によってタイムアウトが設定されているのだが,そもそもOSが一つしかない場合はgrub自体の画面が表示されない. 今回は,どうやらgrubデュアルブートしているWindowsを検出しなくなったため,OSの選択肢がひとつだけになりgrubの選択画面が消えたようだ.

ちなみに,grubの画面自体を表示するだけなら,

# GRUB_TIMEOUT=10
GRUB_TIMEOUT_STYLE=menu
GRUB_HIDDEN_TIMEOUT_QUIET=false

みたいなことをするだけで,表示されるようになる.Linuxしか選択できなかったが.

原因と対策

これ系を調べるとsudo update-grubすれば良いとかsudo grub-mkconfigすればいいとか,そんな適当な情報しかでてこないのだが,これらでは解決しなかった. 原因はgrub-mkconfigがos-proberを実行しないことにあった.

実際update-grubしてみると,

$ sudo update-grub
Generating grub configuration file ...
Found theme: /usr/share/grub/themes/manjaro/theme.txt
Found linux image: /boot/vmlinuz-5.10-x86_64
Found initrd image: /boot/amd-ucode.img /boot/initramfs-5.10-x86_64.img
Found initrd fallback image: /boot/initramfs-5.10-x86_64-fallback.img
Found linux image: /boot/vmlinuz-5.9-x86_64
Found initrd image: /boot/amd-ucode.img /boot/initramfs-5.9-x86_64.img
Found initrd fallback image: /boot/initramfs-5.9-x86_64-fallback.img
Warning: os-prober will not be executed to detect other bootable partitions.
Systems on them will not be added to the GRUB boot configuration.
Check GRUB_DISABLE_OS_PROBER documentation entry.
Adding boot menu entry for UEFI Firmware Settings ...
Found memtest86+ image: /boot/memtest86+/memtest.bin
done

というような表示になる.linuxは検出しているが,Windowsを検出した気配がない. で,重要なことが書いてある.

Warning: os-prober will not be executed to detect other bootable partitions.

そう,実行されてないのだ. os-proberはbootできそうなosを見つけてくれるプログラムなのだが,linuxは自分自信だからいいとして,今起動していないWindowsはこいつで自動的に見つけてgrubが設定を作ってくれるはずだった.

ちなみにこいつを単体で実行すると,

$ sudo os-prober
/dev/sdb1@/EFI/Microsoft/Boot/bootmgfw.efi:Windows Boot Manager:Windows:efi

ちゃんとWindowsを見つけてくれる. というわけでこれを実行できるようにしてやれば良い.

/usr/bin/grub-mkconfigを開いてみると,

# Disable os-prober by default due to security reasons.
GRUB_DISABLE_OS_PROBER="true"

となっている.やはりgrubからos-proberの呼び出しが無効化されている.

というわけでこれを有効化するのだが,別にgrub-mkconfigを編集してもよいのだが,生成済みの/etc/default/grub からも上書きできるので,こちらを編集する.

GRUB_DEFAULT=saved
GRUB_TIMEOUT=10
GRUB_TIMEOUT_STYLE=hidden
GRUB_DISTRIBUTOR="Manjaro"
GRUB_CMDLINE_LINUX_DEFAULT="text apparmor=1 security=apparmor udev.log_priority=3 nouveau.modeset=0 libata.force=noncq irqpoll"
GRUB_CMDLINE_LINUX=""
GRUB_DISABLE_OS_PROBER=false

あとは,

$ sudo update-grub
Generating grub configuration file ...
Found theme: /usr/share/grub/themes/manjaro/theme.txt
Found linux image: /boot/vmlinuz-5.10-x86_64
Found initrd image: /boot/amd-ucode.img /boot/initramfs-5.10-x86_64.img
Found initrd fallback image: /boot/initramfs-5.10-x86_64-fallback.img
Found linux image: /boot/vmlinuz-5.9-x86_64
Found initrd image: /boot/amd-ucode.img /boot/initramfs-5.9-x86_64.img
Found initrd fallback image: /boot/initramfs-5.9-x86_64-fallback.img
Warning: os-prober will be executed to detect other bootable partitions.
It's output will be used to detect bootable binaries on them and create new boot entries.
Found Windows Boot Manager on /dev/sdb1@/EFI/Microsoft/Boot/bootmgfw.efi
Adding boot menu entry for UEFI Firmware Settings ...
Found memtest86+ image: /boot/memtest86+/memtest.bin
done

と,無事にWindowsを検出してくれる.

そもそもos-proberなんで無効になってるの?

同じ疑問を抱いた人がいた. forum.manjaro.org

grub2のセキュリティアップデートにより無効化されている. 該当するアップデートは

wiki.ubuntu.com

で,UEFI Secure BootのCVEに対応するためと思われる.

修正はこれ.

git.savannah.gnu.org

別の方法

os-proberを使わなくても,/etc/grub.d/40_custom に自分でWindowsのエントリを書けば,それでgrubからOS選択はできる(設定が間違ってなければ). というわけでそういう方法もある.詳しくはArchWikiを見て.

wiki.archlinux.jp

参考

rionaoki.net

askubuntu.com