連休中に半ば強制的にk8sを学習することになった

OCIのContainer Instanceを便利に使ってたんだけど、いつものお買い得なA1インスタンスが在庫不足で起動できなくなりました。ちなみにComputeとかではいけるのにContainer Instanceだけだめ。サポートに連絡しても、在庫いまないけどいつ復活するかわからないから定期的にチェックしてくれよな!とのこと。
当面はx86_64インスタンスでしのぎつつもせっかく連休なのでk8sの勉強をすることに

今回はGrokにした。なんかUI的に雑に聞きたいものは最近Grokなんだよな。
まずは概念というか専門用語をCloudRunと比較して聞くことにした。以下は僕の理解。
k8s用語はすべてのクラウドで共通になるためそれぞれの単語が各クラウドにおける何なのかは別途マッピングが必要。
- コントロールプレーン=管理機能(特に画面はない)
- クラスター=VMのプール
- Node=VM
- Pod=おおよそ実行中のコンテナ(DockerCompose的なもの)という理解でいい
- サービス=ロードバランサとか仮想IP(Podが死んでも同一IPでアクセスできるように)
- deployment=docker-compose.ymlみたいなもの
学習開始から5日間たった今だからわかるけど、いわゆるクラウドベンダがやってる「マネージドk8s」は、k8sのうち覚えることが半分で済むので思ったより大変じゃなかった。etcdとかは知らなくていける。

k8sの管理は、OCIの場合はとくにGUIがなく(ほかのクラウドはどうなんだろう)基本はkubectlというコマンドで実行します。kubectlは~/kube/configみたいなところに接続というか認証情報などをストアするので、ローカルPCで操作するのプロジェクトの切り替え忘れとかめっちゃ怖いと思った。Bastionサーバとかからやりたい。
クラスタの作成
ここはOCIの管理画面でウィザードに従って作成した。GUI支援があったのでVMのスペックと台数を選んでいわれるがまま作る感じ。
OCIの場合、Node=VM=Compute Instanceが割り当てられ、サービスのLoadBalancerはOCI Load Balancer(L7)かNetwork Load Balancer(L4)を指定することができる。Quick Setupにすることによりネットワークセグメントも作成される。
Public:10.0.20.0/24
Node:10.0.10.0/24
コントロールプレーン:10.0.0.0/24
みたいな。ポートマッピングはNodeが行うっぽくて、Nodeの3万台ポートがPodの80とかにマッピングされるっぽい。なのでNLBからはそのポートに対して接続を行う。
このポート番号が、いったん後述するdeploymentを削除すると番号が変わってしまい疎通が取れなくなったことに3時間くらいはまってしまった。

アプリのデプロイ
サンプルアプリ(nginx)をデプロイし、サービス(LB)を作成すると外部から疎通した。
k8sでは、クラスタとVPCとNodeまでがインフラ仕事で、PodとServiceがアプリケーション仕事って感じがする。厳密には全部インフラ仕事なんだけど、CloudRunでも日常的に操作するのはPod(CloudRun)とService(LB)相当かなという感じ。
で、後者2つは、deploymentと呼ばれるyamlファイルを使って管理する。ここが苦手ポイントで敬遠してたんだけど、AI時代だから書いてもらったらすんなり動いた。し、CloudRunとかもコード管理し始めるとほぼ同じプロパティをいじるので、けっこう理解は早かった。
というわけで一度削除し、自前のコンテナとLBをNLBに切り替えてデプロイした。

環境変数、Secret
このあたりはdocker-composeとかなり似ている。yamlのspec.containers.envで書いていくか、ConfigMapというものも使えるらしい。
自分の場合.envに30個くらい項目があるので、.envをまるっとSecretに登録し、それをVolumeとしてマウントすることにした。余談だが--from-fileという引数でSecretを登録するところ、--from-env-fileという引数で登録してしまうと中身が展開され1行ごとのSecret変数になってしまうので注意。

疎通しなかったのでトラブルシューティングした
無事deploymentをapplyすることでk8sが動き出したのだがブラウザからつながらない。解決するまでにやったことをメモしていく。
- そもそもPodがReadyにならない=正常起動していない
結論から言うとenvの設定が間違っていた。
$ kubectl get pods,service
$ kubectl exec -it {deployment名}-deployment-{podのID} -- /bin/bash
$ kubectl logs {deployment名}-deployment-{podのID} -c {container名}
この辺を駆使してPodのなかに入ったりログを見て問題点を解決していく
- 正常起動してReadyになったがつながらない
Dockerfileをついでにいじったのがだめでhttpdだけ起動して500エラーになってた。
$ kubectl get pods,service
$ kubectl exec -it {deployment名}-deployment-{podのID} -- /bin/bash
$ curl https://localhost
$ exit
$ kubectl logs {deployment名}-deployment-{podのID} -c {container名}
ネットワークの問題を切り分けるため、まず起動中のPodにはいりcurlでlocalhostをたたいてログを見た。500だったのでもろもろ修正してlocalhostでは見れるようにした。
- ネットワークが疎通しない(NLBのBackendがCriticalになる)
当初はセキュリティグループにnginxの8080しか書かれてなかったことが問題かとおもって80と443が疎通できるようにしたが、全然だめだった。
Internet--->[80,443 OK]--->NLB---->[32441/31880 OK]--->Node---->Pod
のような感じで、Network LoadBalancerはIngressで80/443、Egressで32441/31880というか3万番台(詳しくはドキュメント参照)許可しないといけない。逆にNodeはそれらのIngressを許可しないといけない。
- ネットワークが疎通しない(ハマる)
結論、NLBのHealth watchが旧ポート番号を参照していた。そらCriticalになるわ。
正しいポート番号を死活監視するようにして無事表示できるように!
よかったです。

その他:Nodeリソースには余裕を持たせるものらしい。
Podのコンテナを入れ替えるときは、いい感じでRolling Updateを行ってくれるため、ほぼダウンタイムなく新しいPodにトラフィックが以降するみたいですが、そのためには一時的にNodeに対してPodがたくさん生えてる状態になります。そこでNodeに十分な空き容量がないと、Podが起動できないためRolling Updateが一生終わらないということになります。
追記:maxUnavailableを設定すると、既存のPodをおとしてアップデートしてくれる。NLBの死活監視にひっかかる数秒間だけダウンタイム発生するけどまぁ負荷が低い時間帯なら問題なし。
spec:
replicas: 3 # Pod数: 3個
strategy:
rollingUpdate:
maxUnavailable: 2 # アップデートは最大1Podまで減ってよい

永続化ストレージをAttachする
このへんもdocker-composeと似てて、volumeを定義して、それをvolumeMountで呼び出すという構文になります。マネージドk8sの場合、どのストレージ製品がバックエンドになるかによって少々変わるみたいです。
OCIの場合は、ブロックボリュームは最初から用意されているストレージクラス、oci-bvを指定することで容量を指定して永続化ストレージとして設定することができるのですが、ストレージの接続形態に「ReadWriteMany」「ReadWriteOnce」の2つがありブロックボリュームは前者に対応しておらず最初失敗しました。
要は、DBなど1つのノードで動かすものであればブロックボリュームでReadWriteOnceという単一Nodeからしか読み書きできない方式でアタッチすればいいのですが、今回のようにアプリケーションに画像キャッシュストレージを用意したい、複数のPodから読み書きしたい場合には使えませんでした。
複数のPodから読み書きできるReadWriteManyに対応しているのはFile Storage Serviceのほうで、こちらを使う場合にはstorageclassから作る必要がありました。まぁこの辺はあんま理解していませんが、k8sにプラグインを定義するような感じに見えました。ドキュメントに従ってstorageclassと必要なIAM Policyを作った後、ストレージの構成をつくってAttachという感じで行けました。
結果的にFile Storage Serviceは使った容量しか課金されないので、今回の用途だと結構安く済むかもしれません。

価格面も相変わらず安い。コントロールプレーンが月1.2万円、Armインスタンス3つにブートボリュームをつけても9000円くらいで合計2万円ちょい。そこに永続化ストレージを1GBくらいつけても数百円。ふつう永続化ボリュームって使わなくても容量を押さえないといけないのでは?ネットワークも10TBまで無料なのでLinodeとかVPS業者より安い可能性ある。