2021 年振り返りと 2022 年の抱負

2021 年も残すところ数日ということで、少し早いですが今年 1 年間の振り返りと来年の抱負を掲げます。

2021 年の振り返り

Japan Rook Meetup で登壇

今年はオンライン登壇を 2 回させていただく機会がありました。

その中でも、2021/4/2 に開催された Japan Rook Meetup #5 での登壇は自分にとって大きな経験でした。 去年登壇した LT 枠では 5~10 分の発表だったのですが、Rook Meetup ではセッション枠だったため 20 分喋りました。

ひょんなことから枠をいただいたのですが、「何を喋ろうか?Rook も Ceph も初心者の域を出てないな?そもそもストレージ全然知らん!降りようかな?!もうなんとでもなれ!」みたいな葛藤がありました。 ちょうど Ceph に cephadm という新しめの機能が追加されていたので、Rook にギリギリ関連する形でそれを紹介することでなんとか発表を終えました。

20 分の登壇をした結果、20 分も話をするためにはどれくらいの準備が必要か、という感覚が掴めたことが一番の成果だったと思います。 最近はプレゼンする機会がほとんど無くなってしまったので。

そんな登壇の資料はこちらです。 speakerdeck.com

こんな機会をいただいて、運営の皆様には本当に感謝しています。

登壇への緊張もあったのですが、運営メンバーが界隈では名を轟かせている方々ばかりだったので、その点でも震えてました。 あと、自分に kubectl を教えてくださった方が運営にいらっしゃって、一方的に感動していたりもしました。

もっと登壇したい気持ちもあったのですが、オンラインと言えど家族への負担も掛かることもあり、今年後半は控えることにしました。 発表内容のクオリティに満足いかないところもあり、もっと時間を取れる状況になったら再チャレンジしようと思います。

転職活動をはじめた(現在は休止中)

去年あたりからプライベートで Kubernetes を勉強する機会が増えました。 オンライン勉強会に参加する(視聴する)ことも多くなりました。

Kubernetes を触れば触るほど、勉強会に参加するほど、自分にはスキルが足りないことを実感し、プライベートだけでは経験値が貯まらないと痛感しました。 次第に「仕事としても Kubernetes に関わりたい」という気持ちが高まってきました。

残念ながら現職では Kubernetes どころかコンテナもほとんど使う機会がありません(使う必要がない、ということかもしれません)。 また、IT に意欲的なメンバーが(自分が観測できる範囲では)少なく、Kubernetes 関係なくスキルアップに時間が掛かりそうな環境だなと感じてしまいました。

一旦こんな考えをもってしまうと気持ちが離れてしまうのは早く、スキルアップを図れる新しい環境に身を置くために転職活動をはじめました。

LinkedIn や Wantedly などの転職サイトに登録しました。 Twitter でも「転職したい」と呟いたところ、ありがたいことにお声掛けもいただきました。 そんなこともあり、何社かカジュアル面談や面接をさせていただきました。

結局、転職先はまだ決まっておらず、そんなこんなしていたら事情が変わって直近の転職が難しくなり、来年後半くらいまでは活動休止することにしました(来年の抱負に繋がる)。

転職活動はまだ終わってないですが、途中までの感想としては「スピード感が大事」ということと、「永久フルリモート求人は少ない」ということですね。 両方とも「そんなこと当たり前だろ!」って感じですが。

スピード感は、求人が公開されてから手を挙げるスピードもですし、「いつ入社できるか」という早さも必要ということを痛感しました。

永久フルリモート求人のほうは、「フルリモートです!」と案内しておきながら話聞いたら「コロナ明けたら分かりません」って言ってくるところがあって少し萎えました。 フルリモートとは? コロナの影響でリモート勤務が増えたとは言え、永久にフルリモートな求人ってのはまだまだ少ないですね。

Azure & Kubernetes の資格を取った

去年はそこまで資格に固執していなかったですが、転職を考えだしてからは積極的に資格取得もしていきました。 オンライン受験できるものをターゲットに、今年は 3 つの資格を取りました。

  • Azure Administrator Associate (AZ-104)
  • Certified Kubernetes Administrator (CKA)
  • Certified Kubernetes Application Developer (CKAD)

AZ-104 は落としたと思ったのですが、ギリギリ合格ラインに届いていました。 なので正直 Azure はまだまだ身についてない感が否めません。 仕事でも Azure 触ると見込んでの受験だったのですが、途中で雲行きが怪しくなり、結局今年はほとんど Azure に関われませんでした。

CKA/CKAD は Udemy の定番コースを受講したり Kubernetes 完全ガイドを読み込んだりと万全を期したので、そこそこの点数で合格しました(万全を期したのに満点ではないという矛盾)。 Kubernetes の資格を取って転職サイトの保有資格に追記したころから LinkedIn でメッセージを受け取る頻度が増えました。 近頃は Kubernetes 市場がアツそう、というのを体感しました。

Linux の資格を取ろうと 10 月くらいから Red Hat Certified System Administrator (RHCSA) 受験を考えはじめました。 受験費用は会社が負担してくれるのですが、研修費用は対象外なので IBM SkillsBuild で無料公開されている RH124/134 を受講しました。 あとは受験するだけなのですが、家族のほうとスケジュールを合わせられず、年内受験は見送りました。

2022 年の抱負

家族との時間を大切にする

来年は 4 月から 3 ヶ月ほど仕事をお休みする予定です(ナイーブな話ではないです)。

お休み中はガッツリ勉強するつもりはなく、家族と過ごすための時間として専念しようと考えています。 気分転換に自宅ラボ触るくらいになりそうです。

3 ヶ月も何もやらないと世間から完全に置いていかれそうという焦りもありますが、「家族が大事」と考えた結果です。 復帰してからは今まで以上に時間を有効活用できるように取り組みます。

転職の目処をつける

来年後半には仕事復帰して、転職活動も再開しようと思っています。

長期休暇明けてすぐ転職、っていうことに負い目を感じなくもないですが、逆に考えると「一旦引き継ぎしてるんだから大丈夫だよね?」ってことで進めます。 まぁ転職先が見つかるか次第なので、完全に取らぬ狸の皮算用です。

転職活動再開したら Twitter でつぶやくと思うので、もし「弊社フルリモート求人あるよ!」って方いらっしゃったらリプや DM ください🙇

RHCSA は休みに入る前に取っておきたいです。 あとは CKS や Azure 系の資格も年内に取れるよう計画したいところです。

総括

2021 年は気持ちの変化が大きい年でした。

2022 年は環境の変化が大きい年になるような気がするので、計画的に物事を進めたい所存です。

最後に、(主に Twitter で)絡んでくださった皆様、今年は大変お世話になりました。 来年もぜひよろしくお願いいたします。

NUC で Single Node OpenShift を触ってみよう

Red Hat が提供するエンタープライズなコンテナ・プラットフォーム「OpenShift」がシングルノードに対応したようです。

www.redhat.com

そうとなれば試してみたい欲求が抑えられませんよね?

というわけで要件を満たす機器を選定するところから SNO (Single Node OpenShift) を導入してみました。

ハードウェア要件

SNO をインストールするには以下のスペックが必要です。

f:id:nnstt1:20211126224542p:plain
SNO ハードウェア要件 *1

普通(?) の OpenShift だとコントロールプレーンで 4 コア/16 GB、コンピュートノードで 2 コア/8 GB が最小要件となっているので、一台辺りのスペックは高くなってしまいます。 一台でコントロールプレーンとコンピュートノードの役割を担うのでそこら辺は仕方ないですね。

f:id:nnstt1:20211126223927p:plain
OpenShift 最小要件 *2

cloud.redhat.com

機器選定

上記のハードウェア要件を満たす機器を探しました。 すでに ASRock DeskMini A300 というベアボーンを導入しているのですが、今回は別の機器を検討しました。(新しいものが欲しかったので!)

DeskMini 導入時の記事はこちら。 nnstt1.hatenablog.com

結論から言うと、第 11 世代 i5-1135G7 を搭載した NUC11PAHi5 を選びました。 www.amazon.co.jp

選定基準ですが、メモリとストレージは置いといて、まずは CPU コア数をベースに考えました。 自宅ラボで有名な NUC のスペックを確認してみると、都合よくハードウェア要件を満たすモデルがありました。

11 世代 NUC には CPU が異なる 3 モデルがありますが、一番お手頃な NUC11PAHi3 (Core i3-1115G4) は 2 コア 4 スレッドのため要件を満たしません。 NUC11PAHi5 (i5-1135G7) と NUC11PAHi7 (i7-1165G7) は 4 コア 8 スレッドで要件を満たしています。

あとはお財布との相談で、NUC11PAHi5 となりました(5000 兆円あったら NUC11PAHi7 にしてた)。

最近は Minisforum というメーカーが面白そうなベアボーンを出していたのでそちらも検討したのですが、Windows 搭載モデルが多く「OS 入れ替えるのに Windows 分の費用も上乗せされてるんだよなぁ…」とか考えてるうちに候補から外れてしまいました。

NUC11PAHi5 の最大搭載可能メモリ 64 GB と、SNO のハードウェア要件を十分に満たせます。 DeskMini が 32 GB で検証環境としてはギリギリなこともあり、一気に 64 GB にしようかと思ったのですが、ここでもお財布のストップがかかって 32 GB メモリを 1 枚にしました。 いずれ 2 枚目買って 64 GB にするんだ…。

www.amazon.co.jp

ちなみに、この記事を書いている日はアマゾンのブラックフライデー初日です。 上記メモリが安くなってないかなぁと覗いたら値上がりしてました。なんでや

ストレージは家の中で転がっていた 256 GB の SSD にしました。 m.2 の空きがあるので、いずれストレージが不足してきたら拡張する予定です。

あとは NUC には電源ケーブルが付属しないということで、アマゾンの売れ筋から見繕いました。

www.amazon.co.jp

以上が SNO を構築するために用意したハードウェアになります。

インストール

機器の準備ができたので、早速 SNO のインストールをおこないます。 SNO に関する公式ドキュメントは以下になります。

access.redhat.com

他の環境に OpenShift をインストールするのと比べてかなりページ数が少ないのが気になりますね…。

とは言え必要十分な情報は記載されているので、ドキュメント読みながらやればこの投稿は必要ないくらい簡単にインストールできますが、この投稿を意味あるものにするためにドキュメントの手順に合わせてインストール時のスクショを載せていきましょう。

SNO 用 DNS レコード登録

唯一のソフトウェア的な要件として、SNO が利用する DNS レコードの事前登録があります。

f:id:nnstt1:20211126235623p:plain
必要な DNS レコード

自宅ラボでは PowerDNS を使って DNS サーバを構築しており、そこに上記のレコードを静的登録しました。

検出 ISO 生成

Red Hat Hybrid Cloud Console *3Install OpenShift with the Assisted Installer というページから「検出 ISO」を作成します。

ベアメタルへの OpenShift インストールには IPI (Installer-provisioned infrastructure) と UPI (User-provisioned infrastructure) の 2 通りの方法があったのですが、新しく Assisted Bare Metal Installer が登場したようです(テックプレビュー段階でした)。

f:id:nnstt1:20211127002923p:plain
Assisted Bare Metal Installer

検出 ISO は Hybrid Cloud Console からノードを検出できるようにするためのイメージで、USB メモリなどに焼いて SNO をインストールする機器にぶっ刺して使います。

それでは Assisted Installer のページで SNO のインストールをおこなっていきます。

f:id:nnstt1:20211127003330p:plain
Cluster details(入力前)

まずはクラスタの情報として Cluster nameBase domain を入力します。 Base domain は前項で登録した DNS レコードと合わせてください。

そして Install single node OpenShift (SNO) にチェックを入れます。 チェックを入れると「SNO は高可用性ないですよー」「SNO にはノード追加できませんよー」という注意書きが表示されます。

f:id:nnstt1:20211127000705p:plain
Cluster details(入力後)

また、OpenShift version で選択できるバージョンも限定されます。 SNO をチェック入れてると上記画像のとおり 4.8.12 と 4.9.0 が選択できましたが、チェックを外すと 4.6.16 も選択可能となりました。(2021/11/9 時点)

f:id:nnstt1:20211127005912p:plain
Install single node OpenShift をチェックしなかった場合のバージョン

4.7 はどこいったんだ?という疑問はひとまずスルーして、クラスタの情報を入力したら「Next」です。 次ページの「Generate Discovery ISO」から検出 ISO のイメージをダウンロードできます。

f:id:nnstt1:20211127004042p:plain
Host discovery

「Generate Discovery ISO」をクリックするとポップアップが表示されます。 ここでは「フル」と「最小」のどちらのイメージをダウンロードするか選択し、SNO にアクセスするための SSH 公開鍵を登録します。 今回は「フルイメージ」を選択しました。

f:id:nnstt1:20211127004113p:plain
Generate Discovery ISO

ポップアップの「Generate Discovery ISO」をクリックすると準備中となり、

f:id:nnstt1:20211127004719p:plain
Discovery image is being prepared

しばらく待つとダウンロード可能になります。

f:id:nnstt1:20211127004907p:plain
Discovery ISO is ready to download

URL を見るとイメージは S3 に格納されているようです(AWS 詳しくないので合ってるか不安…)。 「Download Discovery ISO」をクリックして検出 ISO をダウンロードします。 検出 ISO のサイズは約 1 GB (今回は 987 MB) でした。

ダウンロードしたイメージを USB メモリなどに焼きます。 今回は Raspberry Pi Imager を使いましたが、問題なく SNO ノードで起動することができました。

USB メディアを使用したインストール

検出 ISO の準備ができたら、NUC に USB メモリを刺して電源を入れます。 USB からブートする設定になっていれば、Hybrid Cloud Console 上に検出されたノードの情報が表示されます。 検出直後は Hostname と Status の項目が警告になっています。

f:id:nnstt1:20211127010927p:plain
Information and warnings

Hostname は「localhost」が使えないということらしいので変更します。

f:id:nnstt1:20211127011059p:plain
Hostname is not valid

f:id:nnstt1:20211127011259p:plain
Edit Host

Hostname を変更すると Status の警告も無くなりました。

f:id:nnstt1:20211127011442p:plain
Status ready

最後にネットワークの設定をおこないます。 SNO のネットワークは User-Managed Networking しか選択できない状態となっています。

f:id:nnstt1:20211127011716p:plain
Networking(サブネット選択前)

Available subnets に SNO ノードが利用できるサブネットが表示されます。 今回は自宅ラボ用ネットワーク 192.168.1.0/24 に繋いでいたのですが、その情報も取得されてプルダウンに表示されていました。

サブネットを選択すると Status が Ready となり、ネットワーク関連の情報も表示されました。

f:id:nnstt1:20211127012655p:plain
Networking(サブネット選択後)

最後に設定内容をレビューして問題なければ、いよいよ SNO がインストールされます。

f:id:nnstt1:20211127013217p:plain
Review and create

インストール進行状況も Hybrid Cloud Console から確認できます。

f:id:nnstt1:20211127013435p:plain
Installation progress

今回のインストールでは途中でインストールが停止してしまいました。 インストール途中に NUC が再起動されたのですが、「BIOS のブートを USB 起動に設定」かつ「USB メモリ挿しっぱなし」という状況だったため、検出 ISO が再度起動してしまったことが原因でした。 上記のどちらかを対処すればインストールが再開します。

f:id:nnstt1:20211127013657p:plain
Pending user action

無事にインストール完了しました!

f:id:nnstt1:20211127014213p:plain
インストール成功

これで NUC 一台で OpenShift を稼働させることができました。 あとは kubeconfig をダウンロードしてクラスタに繋いだり、コンソールにアクセスして GUI で Operator を入れたり、OpenShift ならでは検証ができるようになります。

その他

OpenShift の評価版

Red Hat Developer Suite」という Red Hat 製品のいくつかを無料で使える素晴らしいサブスクリプションがあります。

www.redhat.com

上記のページで OpenShift も対象っぽい記載があるので「太っ腹じゃん!」と思いながら今回の SNO 構築を決めたのですが、どうやら OpenShift は対象外のようで評価版という形で SNO は使うことになりました。

評価版と言っても期間は 60 日間で、クラスタを再インストールしたら再度 60 日に巻き戻ったので遊ぶ分には十分かなという感じです。

kubeadmin のパスワード

インストールで作成される管理ユーザ「kubeadmin」で oc login しようとしたのですが、パスワードがなかなか見つかりませんでした。

Hybrid Cloud Console のクラスタページの「Installation progress」の左にある > マークをクリックすると…

f:id:nnstt1:20211127021838p:plain
パスワードの場所

こんなところに隠れていました。

f:id:nnstt1:20211127022007p:plain
パスワードの場所

結構探したよ…。

ルート証明書

インストール直後のコンソールにブラウザでアクセスしても、証明書エラーとなりログイン画面にすら辿り着けませんでした。

f:id:nnstt1:20211127020744p:plain
ingress-operator 発行の証明書

SNO のページではないですが、OpenShift のドキュメントにルート証明書を変更する手順はありました。 しかし今回はそのままのルート証明書でアクセスするようにしました。

oc get secret router-ca -n openshift-ingress-operator -o jsonpath='{.data.tls\.crt}' | base64 -d > router-ca.crt というコマンドで証明書を抽出して、クライアント PC にインストールしました。 これでコンソールにもアクセスできるようになりました。

所感

自分が初めて OpenShift を触ったのは 4.2 くらいのときで、やれブートストラップノードが必要だの、やれ Ignition がどうだのと、勉強にはなったのですが中々つらい気持ちもありました。

それに比べて SNO (Single Node OpenShift) のインストールはとても簡単でした(Assisted Installer のおかげ?)。 もっとヒィヒィ言いながらインストールさせてくれてもよかったのに…って気持ちも湧きそうなくらいです。

わざわざ NUC で OpenShift 用意して自宅でなにするよって疑問も残ってますが、kubeadm で作ったクラスタと比較しながら Kubernetes の勉強に使えればよいですね。

SNO の中で各コンポーネントがどのように動いているか等は改めて投稿したいと思います。

何はともあれ、OpenShift を気軽に触れる環境が自宅にあるというのはとても精神衛生上よろしいので、ぜひこれを読んだ人も SNO やってみてはどうでしょう。

f:id:nnstt1:20211127025833p:plain

AWX Operator を使って外部 PostgreSQL と連携する AWX をデプロイ

Ansible AWX バージョン 18.0 から AWX Operator を使うインストール方法に変更されました。

github.com

AWX Operator を使うと Kubernetes クラスタへの AWX デプロイが簡単にできます。

標準的な設定では AWX 関連のコンテナをまとめた Pod と一緒に PostgreSQL Pod もデプロイされますが、今回は AWX Operator 管理外の PostgreSQL インスタンスと連携するように AWX をデプロイします。

環境情報

ソフトウェア バージョン
Kubernetes 1.21.2
AWX 19.3.0
AWX Operator 0.13.0
Zalando Postgres Operator 1.6.3

AWX Operator

AWX Operator は公式の マニュアル 手順通りにインストールしていきます。

特筆する点はないのでマニュアルを参照してください。

PostgreSQL

AWX で利用する PostgreSQL インスタンスは Zalando Postgres Operator を使って用意します。

Operator のインストール方法は下記の投稿を参照してください。

nnstt1.hatenablog.com

以下の postgresql リソースのマニフェストで、AWX 用の PostgreSQL インスタンスがデプロイされます。

apiVersion: acid.zalan.do/v1
kind: postgresql
metadata:
  labels:
    team: acid
  name: acid-awx
  namespace: awx
spec:
  allowedSourceRanges: []
  databases:
    awx: awx
  numberOfInstances: 1
  postgresql:
    version: '13'
  resources:
    limits:
      cpu: 500m
      memory: 500Mi
    requests:
      cpu: 100m
      memory: 100Mi
  teamId: acid
  users:
    awx: []
  volume:
    size: 10Gi
  patroni:
    pg_hba:
      - local     all         all                    trust
      - hostssl   all         +zalandos 127.0.0.1/32 pam
      - host      all         all       127.0.0.1/32 md5
      - hostssl   all         +zalandos ::1/128      pam
      - host      all         all       ::1/128      md5
      - hostssl   replication standby   all          md5
      - hostssl   all         +zalandos all          pam
      - hostssl   all         all       0.0.0.0/0    md5
      - host      all         all       0.0.0.0/0    md5

postgresql リソースでデプロイされる registry.opensource.zalan.do/acid/spilo-13 コンテナは pg_hba.conf が以下のようになっています。

# Do not edit this file manually!
# It will be overwritten by Patroni!
local   all             all                                   trust
hostssl all             +zalandos    127.0.0.1/32       pam
host    all             all                127.0.0.1/32       md5
hostssl all             +zalandos    ::1/128            pam
host    all             all                ::1/128            md5
hostssl replication     standby all                md5
hostnossl all           all                all                reject
hostssl all             +zalandos    all                pam
hostssl all             all                all                md5

この状態では AWX コンテナからアクセスできないため、上記のように spec.patroni.pg_hba で定義します。

また、postgresql マニフェストでは StorageClass を指定するか、クラスタの StorageClass にデフォルト設定をしておきます。

$ kubectl get storageclass
NAME                        PROVISIONER                    RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-storage               kubernetes.io/no-provisioner   Delete          WaitForFirstConsumer   false                  69d
rook-ceph-block (default)   rook-ceph.rbd.csi.ceph.com     Delete          Immediate              true                   12d

うまくデプロイできると以下のリソースが作成されています。

$ kubectl get pods,svc,secret,pvc
NAME                                READY   STATUS              RESTARTS   AGE
pod/acid-awx-0                      1/1     Running             0          2d
pod/awx-operator-69c646c48f-cgkbl   1/1     Running             2          8d

NAME                            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             AGE
service/acid-awx                ClusterIP   10.107.168.117   <none>        5432/TCP            2d
service/acid-awx-config         ClusterIP   None             <none>        <none>              2d
service/acid-awx-repl           ClusterIP   10.104.194.254   <none>        5432/TCP            2d
service/awx-operator-metrics    ClusterIP   10.105.177.117   <none>        8383/TCP,8686/TCP   8d

NAME                                           TYPE                                  DATA   AGE
secret/awx-operator-token-8rdhp                kubernetes.io/service-account-token   3      8d
secret/awx-postgres-configuration              Opaque                                7      2d
secret/awx.acid-awx.credentials                Opaque                                2      2d
secret/default-token-m56vf                     kubernetes.io/service-account-token   3      8d
secret/postgres.acid-awx.credentials           Opaque                                2      2d
secret/standby.acid-awx.credentials            Opaque                                2      2d

NAME                                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
persistentvolumeclaim/pgdata-acid-awx-0           Bound    pvc-d47938d5-fd94-4bcb-865f-467fd8647b82   10Gi       RWO            rook-ceph-block   2d

このうち、secret/awx.acid-awx.credentials に AWX から PostgreSQL にアクセスするための認証情報が格納されているので、以下のコマンドで確認しておきます。

$ kubectl get secret awx.acid-awx.credentials -o jsonpath='{.data.password}' | base64 -d

AWX

PostgreSQL インスタンスを用意できたら AWX をデプロイします。

必要なものは AWX Operator が参照する AWX リソースと Secret リソースのマニフェストです。

apiVersion: awx.ansible.com/v1beta1
kind: AWX
metadata:
  name: awx
  namespace: awx
spec:
  admin_user: admin
  admin_password_secret: awx-admin-password
  
  ingress_type: ingress
  ingress_tls_secret: awx-secret-tls
  hostname: awx.k8s.nnstt1.work

  postgres_configuration_secret: awx-postgres-configuration

AWX リソースの spec で AWX の各種設定をおこないます。 今回は Ingress の設定、admin ユーザと外部 PostgreSQL の設定を格納した secret の指定をおこないました。

Secret リソースでは、上記の secret を作成します。

apiVersion: v1
kind: Secret
metadata:
  name: awx-admin-password
  namespace: awx
stringData:
  password: password
---
apiVersion: v1
kind: Secret
metadata:
  name: awx-postgres-configuration
  namespace: awx
stringData:
  host: acid-awx
  port: "5432"
  database: awx
  username: awx
  password: oqIgEGDuCDG40LI2tIQ4OdIUtdKFGiLDkAbUiCyrZgMnKFSTZrQsQmihtTnOkvdH
  sslmode: prefer
  type: unmanaged
type: Opaque

stringData は Zalando Postgres Operator で構築した PostgreSQL インスタンスの情報を記載します。

stringData.type には unmanaged を指定します。 この値により、AWX Operator が「PostgreSQL インスタンスを自前で構築する」か「外部 PostgreSQL を参照する」かを判断します。

(「Secret リソースの内容で Operator の挙動を変える」って仕様がよろしくなさそうに感じたんですがどうなんでしょう?)

上記のマニフェストを Apply することで、AWX Operator がいい感じに AWX をデプロイしてくれます。

うまく AWX から PostgreSQL にアクセスできたら初期構築が始まります、そこそこ時間が掛かるようなので気楽に待ちましょう。 自分の環境では 15 分ほど掛かかりました。

初期構築も完了したら Ingress 経由でアクセスします。 今回のマニフェストでは AWX へのアクセスは Ingress 経由になるため、NGINX Ingress Controller などを使ってクラスタIngress を使えるように事前に準備しておいてください。

AWX のログイン画面が表示できたらデプロイ成功です。 お疲れさまでした。

f:id:nnstt1:20210825010945p:plain

NGINX Ingress controller + MetalLB で Ingress の IP アドレスがうまく払い出されなかった話

NGINX Ingress Controller と MetalLB を使った Ingress の構築に失敗していた話です。

背景

自宅ラボの Kubernetes クラスタでは、NGINX Ingress ControllerMetalLB をデプロイしています。 これらを使って、Ingress リソースに NGINX Ingress Controller 向け LoadBalancer Service の External-IP を割り当てて、各種サービスを Ingress 経由で公開しています。

一方、Kubernetes クラスタとは別に自宅ラボ用の DNS サーバを構築しており、夏季休暇中に CoreDNS から PowerDNS に入れ替える作業をしました(この話は別途投稿したいと思います)。

するとどうでしょう、DNS サーバの入れ替え作業後に Ingress で公開していたサービスに接続できなくなってしまいました。

状況

Ingress リソースを確認したところ、設定されている IP アドレスが想定していたアドレスと異なっていました。 想定アドレスは上述の通り「NGINX Ingress Controller 向け LoadBalancer Service の External-IP」です。

しかし、実際には「Kubernetes クラスタの Worker Node の IP アドレス」が設定されていました。

$ kubectl get ingress -A
NAMESPACE    NAME                   CLASS    HOSTS                           ADDRESS         PORTS     AGE
argocd       argocd-server-ingress  <none>   argocd-ingress.k8s.nnstt1.work  192.168.2.29   80, 443   22h
monitoring   grafana                <none>   grafana.k8s.nnstt1.work         192.168.2.29   80        20h
monitoring   k8s                    <none>   prometheus.k8s.nnstt1.work      192.168.2.29   80        20h
sandbox      sample-app             <none>   sample-app.k8s.nnstt1.work      192.168.2.29   80, 443   20h
# 192.168.2.29 は Worker Node の IP アドレス

Node Port のように Worker Node の IP アドレスでアクセスしても NGINX Ingress Controller のサービスには到達できないため、Ingress で指定したサービスにも接続できていませんでした。

原因

根本的な原因は、NGINX Ingress Controller の引数に --publish-service を設定できていなかったことでした。

調査のため Ingress や NGINX Ingress Controller を消しては作り直しを繰り返したのですが、最終的には GitHubこちらの Issue を見つけたことで原因を特定できました。

自宅ラボの Kubernetes クラスタでは NGINX Ingress Controller 公式ドキュメントの ベアメタル向け Installation Guide を参照し、ベアメタル向けのマニフェスト を使って Ingress Controller をデプロイしていました。

しかし、ベアメタル向けのマニフェストは「MetalLB を使ってベアメタルでも LoadBalancer Service を使えるようにしている環境」向けではないため、--publish-service の引数が設定されていませんでした。

MetalLB を使っている場合は --publish-service が記載されている Cloud 向けのマニフェスト を使うほうがよさそうです。

NGINX Ingress Controller の引数に --publish-service をつけることで、無事に LoadBalancer の External-IP が割り当てられるようになりました。

# 説明箇所のみ抜粋
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ingress-nginx-controller
spec:
  template:
    spec:
      containers:
        - name: controller
          image: k8s.gcr.io/ingress-nginx/controller:v0.48.1@sha256:e9fb216ace49dfa4a5983b183067e97496e7a8b307d2093f4278cd550c303899
          args:
            - /nginx-ingress-controller
            - --election-id=ingress-controller-leader
            - --ingress-class=nginx
            - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
            - --validating-webhook=:8443
            - --validating-webhook-certificate=/usr/local/certificates/cert
            - --validating-webhook-key=/usr/local/certificates/key
            - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller    # この行を追加

--publish-service とは

NGINX Ingress Controller 公式ドキュメントの Command line arguments ページ に記載があります。

Service fronting the Ingress controller. Takes the form "namespace/name". When used together with update-status, the controller mirrors the address of this service's endpoints to the load-balancer status of all Ingress objects it satisfies.

(機械翻訳) Ingressコントローラの前にあるサービス。 "namespace/name"という形式をとる。 update-statusと一緒に使用すると、コントローラは、このサービスのエンドポイントのアドレスを、満足するすべてのIngressオブジェクトのロードバランサステータスにミラーリングします。

NGINX Ingress Controller に --publish-service を付けなかった場合、今回のように Worker Node の IP アドレスが Ingress リソースに割り当てられるようですね。

このページをしっかり読んでいれば、今回の事象は防げていたはずです。

そもそも何で今までアクセスできていたの?

なんと、DNS サーバの入れ替え前(CoreDNS を使っていたとき)は、DNS サーバの /etc/hosts に直接 Ingress 用の DNS レコードを記述していたのです。

$ cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

192.168.2.244    argocd-ingress.k8s.nnstt1.work
192.168.2.244    grafana.k8s.nnstt1.work
192.168.2.244    prometheus.k8s.nnstt1.work
192.168.2.244    sample-app.k8s.nnstt1.work

元々 Ingress Controller を正しく設定できていなかったので、ワークアラウンド的に無理やり名前解決させていたようですね(他人事)。

それが PowerDNS への入れ替え時に /etc/hosts を綺麗に整理したことで名前解決できなくなり、Ingress 経由のサービスにアクセスできない事象が発生しました(正確には元々繋がらない設定だった)。

教訓

  • 趣味でワークアラウンドな対応は(極力)しない。

  • ドキュメントはちゃんと読もう(N 回目の登場)。

MinIO Operator を試してみた(インストール編)

(2021/5/18 追記) Operator のインストールしかしてないじゃん、ってことでタイトル変更しました。 実際に Operator で構築した Minio を試して、続編として投稿したいと思います。

前回の投稿から 3 ヶ月ほど空いてしまいました。

その間に、誕生日に Japan Rook Meetup で登壇したり…

CKA (Certified Kubernetes Administrator) の資格を取ったり…

したのですが、まったくブログにアウトプットできてませんでした。

現在は CKAD (Certified Kubernetes Application Developer) に向けて勉強中なのですが、裏で MinIO Operator を触ってみたので気晴らしに久しぶりの投稿をします。

MinIO とは

f:id:nnstt1:20210517015246p:plain

MinIO は、Amazon S3 互換のオープンソースオブジェクトストレージです。

min.io

プライベート/ハイブリッドクラウドの標準オブジェクトストレージとなることを前提とした設計のようです。

そのためか、公式サイトではファイルシステムブロックストレージには結構キツい物言いをしている感じがします(個人の感想です)。

ハイブリッドクラウドストレージは、パブリッククラウドで確立されたモデルに従い、パブリッククラウドプロバイダーは全会一致でクラウドネイティブオブジェクトストレージを採用しています。

パブリッククラウドの成功により、ファイルとブロックストレージは事実上時代遅れになりました。

すべての新しいアプリケーションは、POSIXではなくAWS S3API用に作成されています。

クラウドネイティブテクノロジーのようにスケーリングして実行するには、古いアプリケーションをS3 API用に書き直し、コンテナ互換になるようにマイクロサービスにリファクタリングする必要があります。

また、かつて Rook (v1.2)でもストレージプロバイダとして MinIO が対象となっていたのですが、残念ながら現在は対象外となってしまったようです。

github.com

ちなみに、MinIO の読み方は「ミン・アイオー」のようです(ミニオって言ってしまってました…)。

qiita.com

MinIO Operator

そんな MinIO を Kubernetes にデプロイして運用までしてくれる MinIO Operator がリリースされたようです。

blocksandfiles.com

今回は公式リポジトリの手順に沿って MinIO Operator をインストールして、オブジェクトを格納するためのテナントを作成するところまで試してみます。

github.com

前提条件

MinIO Operator のインストール、およびテナント作成の前提条件は以下の 4 つです。

  • Kubernetes クラスタのバージョンが 1.17.0 以上であること

    1.17.0 未満だったら Kubernetes 公式を参照してアップグレードします。

    今回使用したクラスタは v1.21.0 のため、そのまま使用します。

$ kubectl version
Client Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.0", GitCommit:"cb303e613a121a29364f75cc67d3d580833a7479", GitTreeState:"clean", BuildDate:"2021-04-08T16:31:21Z", GoVersion:"go1.16.1", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.0", GitCommit:"cb303e613a121a29364f75cc67d3d580833a7479", GitTreeState:"clean", BuildDate:"2021-04-08T16:25:06Z", GoVersion:"go1.16.1", Compiler:"gc", Platform:"linux/amd64"}
  • Kubernetes クラスタに MinIO テナント用の namespace があること

    MinIO Operator はテナント毎に namespace を使用します。

    今回は検証用の minio-tenant-1 namespace を作成します。

$ kubectl create namespace minio-tenant-1
namespace/minio-tenant-1 created
  • Kubernetes クラスタvolumeBindingMode: WaitForFirstConsumer の StorageClass があること

    MinIO Operator は volumeBindingMode: WaitForFirstConsumer の StorageClass にデータを格納します。 今回使用する Kubernetes クラスタには Rook/Ceph が導入済みのため、Rook/Ceph を使って MinIO Operator 用の StorageClass を作成します。

apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: minio-pool
  namespace: rook-ceph
spec:
  failureDomain: host
  replicated:
    size: 3
    requireSafeReplicaSize: true
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: minio-rook-ceph-block
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
    clusterID: rook-ceph
    pool: replicapool
    imageFormat: "2"
    imageFeatures: layering
    csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner
    csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
    csi.storage.k8s.io/controller-expand-secret-name: rook-csi-rbd-provisioner
    csi.storage.k8s.io/controller-expand-secret-namespace: rook-ceph
    csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node
    csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
    csi.storage.k8s.io/fstype: ext4
allowVolumeExpansion: true
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
  • Krew がインストールされていること

    MinIO Operator は kubectl コマンドラインツールの Krew を使用してインストールします。

    以下のサイトを参照して Krew をインストールします。

    krew.sigs.k8s.io

インストール

Krew を使って MinIO Operator をインストールしていきます。

$ kubectl krew update
Updated the local copy of plugin index.

$ kubectl krew install minio
Updated the local copy of plugin index.
Installing plugin: minio
Installed plugin: minio
\
 | Use this plugin:
 |      kubectl minio
 | Documentation:
 |      https://github.com/minio/operator/tree/master/kubectl-minio
 | Caveats:
 | \
 |  | * For resources that are not in default namespace, currently you must
 |  |   specify -n/--namespace explicitly (the current namespace setting is not
 |  |   yet used).
 | /
/
WARNING: You installed plugin "minio" from the krew-index plugin repository.
   These plugins are not audited for security by the Krew maintainers.
   Run them at your own risk.

MinIO プラグインをインストールできたら、Operator を初期構築します。

$ kubectl minio init
namespace/minio-operator created
serviceaccount/minio-operator created
clusterrole.rbac.authorization.k8s.io/minio-operator-role created
clusterrolebinding.rbac.authorization.k8s.io/minio-operator-binding created
customresourcedefinition.apiextensions.k8s.io/tenants.minio.min.io created
service/operator created
deployment.apps/minio-operator created
serviceaccount/console-sa created
clusterrole.rbac.authorization.k8s.io/console-sa-role created
clusterrolebinding.rbac.authorization.k8s.io/console-sa-binding created
configmap/console-env created
service/console created
deployment.apps/console created
-----------------

To open Operator UI, start a port forward using this command:

kubectl minio proxy -n minio-operator 

-----------------

上記出力の通り、proxy 経由で MinIO Operator の管理画面にアクセスできるようになります。

$ kubectl minio proxy -n minio-operator
Starting port forward of the Console UI.

To connect open a browser and go to http://localhost:9090

Current JWT to login: eyJhbGciOiJSUzI1NiIsImtpZCI6Ii0zbFc4cm51LXNPTWliSUVOYXdTNmI3ejRWeWRuVEF0dU9aenpKY0xaNGMifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJtaW5pby1vcGVyYXRvciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJjb25zb2xlLXNhLXRva2VuLWJmNDZyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImNvbnNvbGUtc2EiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI3ZDgwZDNjOS0wMzUyLTRjNDItOWQ5Yy0xM2I3ZDg2ZjA4OWIiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6bWluaW8tb3BlcmF0b3I6Y29uc29sZS1zYSJ9.F8XsI2buT71U4QUi7tfShaDYAOs0rcgjPmZO5HiJYyWYZ0ZYN5LDZbIRf4il23CGRjGVUWksKsfeUqRFD0iG2lP21WxvTgIbUov3DbZtItB3Sj1dO4xI5WH7PLjyYAyE5eYGRQwd3K0qSpaexh4cKsG8vj5pxzd8g-f6vwWjE1Co7Ax83PzP_ATmNnOWVQE-6lI287QijtH-PfdK3UEHM1d1WU67yvei0YGLTyMgC3t6F8xKwqay0B2OLEUGirDcq74xZA5qB_Ipuvys5K05nOCKbQiWujv0J_gyyaEAwszRpGc4Gy8Mrc-bhAYe7EGLh8_BoQiwJ2d4UVcBEuEmmw

Forwarding from 0.0.0.0:9090 -> 9090

ブラウザで kubectl minio proxy コマンドを実行しているサーバにアクセスするとログイン画面が表示されるので、コマンド実行時に出力された文字列を入力してログインします。

f:id:nnstt1:20210517010411p:plain
GUI Login

f:id:nnstt1:20210517010427p:plain
GUI

テナント作成

次に、MinIO Operator を使ってテナントを作成します。

デプロイされる Pod やストレージサイズを指定するオプションがあるのですが、クラスタと相談のうえ設定してください。

$ kubectl minio tenant create minio-tenant-1 \
    --servers 1 \
    --volumes 4 \
    --capacity 4Gi \
    --namespace minio-tenant-1 \
    --storage-class minio-rook-ceph-block

Tenant 'minio-tenant-1' created in 'minio-tenant-1' Namespace

  Username: admin 
  Password: ac8a676c-d4a3-4cb0-98b9-c432f3c9cd5c 
  Note: Copy the credentials to a secure location. MinIO will not display these again.

+-------------+------------------------+----------------+--------------+--------------+
| APPLICATION | SERVICE NAME           | NAMESPACE      | SERVICE TYPE | SERVICE PORT |
+-------------+------------------------+----------------+--------------+--------------+
| MinIO       | minio                  | minio-tenant-1 | ClusterIP    | 443          |
| Console     | minio-tenant-1-console | minio-tenant-1 | ClusterIP    | 9443         |
+-------------+------------------------+----------------+--------------+--------------+

これでオブジェクトを格納するためのテナントが完成しました。

自分の環境では、テナントを正常に作成できた状態では以下のリソースが作られていました。

$ kubectl get secret -n minio-tenant-1
NAME                            TYPE                                  DATA   AGE
default-token-ntlzm             kubernetes.io/service-account-token   3      2m24s
minio-tenant-1-console-secret   Opaque                                4      2m22s
minio-tenant-1-console-tls      Opaque                                2      62s
minio-tenant-1-creds-secret     Opaque                                2      2m22s
minio-tenant-1-tls              Opaque                                2      2m2s
operator-tls                    Opaque                                1      2m17s
operator-webhook-secret         Opaque                                3      2m17s

$ kubectl get pods -n minio-tenant-1
NAME                    READY   STATUS    RESTARTS   AGE
minio-tenant-1-console-6b7488946f-2mvkf   1/1     Running   0          2m43s
minio-tenant-1-console-6b7488946f-6djht   1/1     Running   0          2m43s
minio-tenant-1-ss-0-0                     1/1     Running   0          3m43s

$ kubectl get csr
NAME                                        AGE    SIGNERNAME                     REQUESTOR                                             CONDITION
minio-tenant-1-console-minio-tenant-1-csr   17s    kubernetes.io/legacy-unknown   system:serviceaccount:minio-operator:minio-operator   Approved,Issued
minio-tenant-1-minio-tenant-1-csr           77s    kubernetes.io/legacy-unknown   system:serviceaccount:minio-operator:minio-operator   Approved,Issued
operator-minio-operator-csr                 4m5s   kubernetes.io/legacy-unknown   system:serviceaccount:minio-operator:minio-operator   Approved,Issued

$ kubectl get tenant -n minio-tenant-1
NAME             STATE         AGE
minio-tenant-1   Initialized   4m43s

ちなみに、--volumes が 4 未満だった場合、エラーとなります。

$ kubectl minio tenant create minio-tenant-1 \
    --servers 1 \
    --volumes 3 \
    --capacity 3Gi \
    --namespace minio-tenant-1 \
    --storage-class minio-rook-ceph-block
Error: pool #0 setup must have a minimum of 4 volumes per server

サーバ 1 台につき 4 ボリュームが必要なようです。

注意点

テナント作成時にユーザ情報が表示されますが、このとき表示される Password は再度表示することができないので、必ず控えておいてください。

また、万人に該当する注意点ではないかもですが、自分の環境では Operator の処理が遅く、上記のユーザ情報が表示されたあともテナントに必要なリソースがすべてデプロイされていませんでした。 厳密に言うと、minio-tenant-1-tls Secret が作成されなかったため、Secret をマウントできずに Pod が起動してきませんでした。

原因は不要な CSR リソースが存在していたことで、テナント作成時にデプロイされるべき CSR リソースがすでに存在していたため、minio-tenant-1-tls Secret が作成されなかったのだと推測しています。 この不要な CSR リソースは、テナント作成時のオプションを失敗して何度かテナント作成をやり直していたことで残っていたと思われます。

テナントを作成する際には CSR リソースも確認しておいたほうが良さそうです。

外部公開用サービス

ここまでの手順では Kubernetes クラスタ外のアプリケーションから MinIO テナントに接続するサービスがデプロイされていません。 そのため、Ingress や Load Balancer が必要です。

今回は MinIO テナント用の Load Balancer を作成します。

クラスタには MetalLB と ExternalDNS をデプロイ済みで、LoadBalancer を作成するとクラスタ外から接続可能な IP アドレスが振られて、 クラスタ外の DNS にレコードを登録してくれます。

外部から接続するサービスは minioconsole の 2 種類があります。

MinIO

こちらは実際にオブジェクトストレージを操作するための APIGUI を提供するサービスです。

テナント作成時に表示された admin ユーザでログインします。

f:id:nnstt1:20210517013507p:plain

f:id:nnstt1:20210517013551p:plain

apiVersion: v1
kind: Service
metadata:
  namespace: minio-tenant-1
  name: minio-loadbalancer
  labels:
    component: minio-tenant-1
  annotations:
    external-dns.alpha.kubernetes.io/hostname: minio-tenant-1.nnstt1.work.    # ExternalDNS 用アノテーション
spec:
  type: LoadBalancer
  ports:
    - port: 9000
      targetPort: 9000
      protocol: TCP
  selector:
    v1.min.io/tenant: minio-tenant-1

Console

こちらは MinIO Operator と同時にリリースされた MinIO Console という管理ツールです。

テナントの管理や状態監視をおこなえます。 ログインは同じく admin ユーザを使用します。

f:id:nnstt1:20210518065947p:plain
Console Login

ログインすると管理ポータルが表示されます。

f:id:nnstt1:20210518070106p:plain
Console

apiVersion: v1
kind: Service
metadata:
  namespace: minio-tenant-1
  name: console-loadbalancer
  annotations:
    external-dns.alpha.kubernetes.io/hostname: minio-console.nnstt1.work.    # ExternalDNS 用アノテーション
spec:
  type: LoadBalancer
  ports:
    - name: https-console
      port: 9443
      protocol: TCP
      targetPort: 9443
  selector:
    v1.min.io/console: minio-tenant-1-console

アンインストール

作成したテナントを削除し、MinIO Operator をアンインストールしてみます。

テナント削除も MinIO Operator アンインストールも kubectl minio コマンドを使用します。

テナント

kubectl minio tenant delete で削除するテナントを指定します。 ここで指定する tenant リソースは namespace 毎に作成されるため、-n オプションで対象の namespace を指定してください。

# テナント削除
$ kubectl minio tenant delete minio-tenant-1 -n minio-tenant-1

This will delete the Tenant minio-tenant-1 and ALL its data. Do you want to proceed?: y
Deleting MinIO Tenant minio-tenant-1
Deleting MinIO Tenant Credentials Secret minio-tenant-1-creds-secret
Deleting MinIO Tenant Console Secret minio-tenant-1-console-secret

# namespace 確認
$ kubectl get pods -n minio-tenant-1
No resources found in minio-tenant-1 namespace.
$ kubectl get secret -n minio-tenant-1
NAME                  TYPE                                  DATA   AGE
default-token-swx2m   kubernetes.io/service-account-token   3      2d

MinIO Operator

kubectl minio delete で MinIO Operator がアンインストールされます。

$ kubectl minio delete

Are you sure you want to delete ALL the MinIO Tenants and MinIO Operator?: y
namespace "minio-operator" deleted
serviceaccount "minio-operator" deleted
clusterrole.rbac.authorization.k8s.io "minio-operator-role" deleted
clusterrolebinding.rbac.authorization.k8s.io "minio-operator-binding" deleted
customresourcedefinition.apiextensions.k8s.io "tenants.minio.min.io" deleted
service "operator" deleted
deployment.apps "minio-operator" deleted
serviceaccount "console-sa" deleted
clusterrole.rbac.authorization.k8s.io "console-sa-role" deleted
clusterrolebinding.rbac.authorization.k8s.io "console-sa-binding" deleted
configmap "console-env" deleted
service "console" deleted
deployment.apps "console" deleted

アンインストールはあっさりですね。

おわりに

今回は Amazon S3 互換のオブジェクトストレージ MinIO の オペレータ MinIO Operator を試してみました。

MinIO 自体、Kubernetes にとても簡単にデプロイできるため Operator が活躍する場面はまだ見えてませんが、 そもそも MinIO を使い込んでいないので、色々試してみようと思います。

本来は Operator は簡単にインストールできるのですが、注意点の節でも述べた CSR リソースの件でかなり時間を持っていかれました。 なかなか思うようにできないですね。 だけど、今回の件で CSR リソースのことを注意するようになったのでヨシ!

全然関係ないですが、MinIO のロゴってかっこいいですよね。フラミンゴかな?

f:id:nnstt1:20210517013856p:plain

Kubernetes クラスタのバックアップツール Velero を試してみた

Kubenews #8 を視聴していたら Velero というツールの話題が出て、そういえば試そうと思って手付かずのままだったなぁと思い出し、試せるのはいつになるかなぁと Twitter で呟いたら「今でしょ!」とも言われたので、Velero を試してみました。

Velero とは

Velero は、Kubernetes クラスタのリソースと永続データをバックアップ、リカバリ、移行するためのツールです。 VMware のプロジェクトとして管理されており、VMware Tanzu (VMwareKubernetes 製品群) の一員です。

github.com

Velero は Kubernetes クラスタに対して以下のような使い方ができます。

Velero の特徴としては、Kubernetes API を使ってバックアップ・リストアをする API-driven な点があります。 他の Kubernetes 用バックアップツールはクラスタ内の etcd を直接参照してバックアップ・リストアするようで、その点が異なります。

API-driven なバックアップツールには以下のようなメリットがあります。

  • 名前空間、リソースタイプ、ラベルによってバックアップ・リストア対象を柔軟に選択可能
  • マネージド K8s クラスタの場合、etcd を直接参照できずバックアップ・リストアできないことがあるが、API 経由なら可能
  • 別の etcd にリソースが保存されていてもバックアップ・リストア可能

正直なところ、3点目はどのような場合を想定しているのか理解できていません…。

また、Velero は Kubernetes のリソースだけでなく、永続データもバックアップ・リストアの対象とすることができますが、今回はリソースを対象にしたバックアップ・リストアのみ試しています。

オブジェクトストレージの用意

Velero はバックアップデータをオブジェクトストレージに格納します。

Amazon S3 や Azure Blob Storage といったクラウドサービスとしてのオブジェクトストレージを使ってもよいのですが、今回はオンプレ環境の K8s クラスタ内に2種類のオブジェクトストレージを用意しました。

対応しているストレージプロバイダの一覧は以下に記載されていますが、今回使用する S3 互換のオブジェクトストレージは十分なテストはされていない点は要注意です。

velero.io

MinIO

まずは S3 互換のオブジェクトストレージとしてよく使われているであろう MinIO です。(今回、初めて存在を知りました…)

MinIO の場合は Velero 公式サイトに参考手順が載っています。

velero.io

上記手順でも使われていますが、公式リポジトリK8s クラスタに MinIO をデプロイするサンプルマニフェストが用意されています。

github.com

今回はこちらのサンプルマニフェストを一部変更して MinIO を用意しました。

変更箇所は kind: Service に対する以下の2点です。

  • ExternalDNS 用に external-dns.alpha.kubernetes.io/hostname アノテーションを付与
  • spec.type: ClusterIP から spec.type: LoadBalancer へ変更

以下は変更済みサンプルマニフェストのデプロイ結果です。

$ kubectl apply -f examples/minio/00-minio-deployment.yaml

$ kubectl get all -n velero
NAME                         READY   STATUS      RESTARTS   AGE
pod/minio-5b84955bdd-2qh6f   1/1     Running     0          26h
pod/minio-setup-nfcjq        0/1     Completed   0          26h

NAME            TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)          AGE
service/minio   LoadBalancer   10.103.161.124   192.168.2.244   9000:31475/TCP   26h

NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/minio   1/1     1            1           26h

NAME                               DESIRED   CURRENT   READY   AGE
replicaset.apps/minio-5b84955bdd   1         1         1       26h

NAME                    COMPLETIONS   DURATION   AGE
job.batch/minio-setup   1/1           13s        26h

service/minio は type: LoadBalancer に変更したため External-IP が振られており、 ExternalDNS による DNS 登録もされている状態です。

デプロイから数分待てば、アノテーションで指定したホスト名で MinIO のサービスにアクセスできるようになります。

ExternalDNS については以下の記事を参考にしてください。

nnstt1.hatenablog.com

Rook Ceph

もう一つは、Rook Ceph のオブジェクトストレージです。

既にクラスタ用のブロックストレージとして Rook Ceph は使っていたのですが、オブジェクトストレージとしての利用はしていなかったので今回を機に用意してみました。

Rook Ceph オブジェクトストレージのデプロイには、公式ドキュメントと以下の記事を参考にしました。

rook.io

qiita.com

$ kubectl apply -f rook/cluster/examples/kubernetes/ceph/object-test.yaml
$ kubectl apply -f rook/cluster/examples/kubernetes/ceph/storageclass-bucket-delete.yaml
$ kubectl apply -f rook/cluster/examples/kubernetes/ceph/object-bucket-claim-delete.yaml

既に Rook が入っていれば、これだけでオブジェクトストレージが使えるようになります。

が、自分の環境では OBC (Object Bucket Claim ) の status が Pending からずっと変わらない事象が発生しました。 (正しく動けば Bound になるはず)

Pending の状態だからオブジェクトストレージとしては使えないだろう、と思い数日原因を探っていたのですが、結局分からないままでした。

試しに Rook 公式ドキュメントに記載されている s3cmd を使ってテストしたところ、Pending の状態でもオブジェクトの格納や取得はできてしまいました。

今回の検証は上記のような状態でおこなっていますが、後日ちゃんと対応しようと思います。

また本筋から逸れますが、なぜ MinIO 以外のオブジェクトストレージを用意したのかというと「K8s クラスタ上に MinIO をデプロイしても内部では Rook Ceph のブロックストレージを使うためオーバーヘッドが掛かるのでは」と考えたからです。

Ceph のアーキテクチャ図を見ると分かりやすいのですが、Ceph 自体は RADOS (Reliable Autonomic Distributed Object Store) というオブジェクトストアを土台として構築し、その上で S3 互換オブジェクトストレージの RADOSGWブロックストレージの RDB、ファイルストレージの CephFS が動いています。

f:id:nnstt1:20210212222818p:plain
Ceph Architecture

そのため、MinIO を使って K8s クラスタ内にオブジェクトストレージを構築しても、Rook Ceph の StorageClass (ブロックストレージ) を使っていては「オブジェクトストレージ on ブロックストレージ on オブジェクトストア」のようになってしまうのでは、RADOSGW を使ってオブジェクトストレージを使ったほうが性能が良いのでは、と考えました。

考えました……。

が、執筆時に MinIO のサンプルマニフェストを見返して気付いたのですが、MinIO の volumes では emptyDir を使っていました。 つまり、Rook Ceph のブロックストレージを使っていませんでした。

最初に試した MinIO のデプロイは上記サンプルのものではなく別の方のマニフェストを参考にしていて、そちらでは PVC を使っていたので Rook Ceph を使っているものと勘違いしたまま考察してしまいました。

上記の考察は、Velero 公式リポジトリの MinIO サンプルマニフェストでは意味がないです。

こちらも後日 PVC を使った MinIO を用意して RADOSGW との性能差を測定をしてみます。

Velero を試す

オブジェクトストレージの用意ができたので、いよいよ Velero を試してみます。

インストール

Velero を K8s クラスタにインストールする方法は以下の2通りあります。

  • velero コマンドを使った velero install
  • Helm chart

今回は、velero コマンドを Linux クライアント環境にインストールする方法で試しました。 (Helm に苦手意識があるので…)

velero.io

まずは velero コマンドを準備します。 MacWindows だと Homebrew や Chocolatey といったパッケージマネージャが使えるのですが、Linux のため公式からバイナリをダウンロードしてきました。 velero ファイルを PATH の通ったとこに配置するだけで完了です。

次に、オブジェクトストレージの認証情報を記載したファイル (./credentials-velero) を用意します。

[default]
aws_access_key_id = minio
aws_secret_access_key = minio123

上記の認証情報は MinIO (サンプルマニフェスト)の場合ですが、Rook Ceph の場合は以下のコマンドで認証情報を取得できます。

$ kubectl get secret ceph-delete-bucket -o jsonpath='{.data.AWS_ACCESS_KEY_ID}' | base64 --decode
$ kubectl get secret ceph-delete-bucket -o jsonpath='{.data.AWS_SECRET_ACCESS_KEY}' | base64 --decode

velero コマンドの準備ができたらクライアントから velero install すればよいのですが、MinIO と Rook Ceph の場合でバケット名や s3Url など微妙に異なるので、適宜変更してください。

  • MinIO
$ velero install \
    --provider aws \
    --plugins velero/velero-plugin-for-aws:v1.0.0 \
    --bucket velero \
    --secret-file ./credentials-velero \
    --use-volume-snapshots=false \
    --backup-location-config region=minio,s3ForcePathStyle="true",s3Url=http://minio.minio.svc:9000
  • Rook Ceph
$ velero install \
    --provider aws \
    --plugins velero/velero-plugin-for-aws:v1.0.0 \
    --bucket ceph-bkt-c71df44b-8657-4f02-b680-fe0894debc07 \
    --secret-file ./credentials-velero \
    --use-volume-snapshots=false \
    --backup-location-config region=us-east-1,s3ForcePathStyle="true",s3Url=http://rook-ceph-rgw-my-store.rook-ceph.svc

namespace: velero に velero Pod がデプロイされていればインストール完了です。

バックアップ

Velero 公式リポジトリに用意された Nginx のサンプルマニフェストを使って、バックアップ・リストアの確認をします。

# テスト用リソースの準備
$ kubectl apply -f https://github.com/vmware-tanzu/velero/blob/main/examples/nginx-app/base.yaml

$ kubectl get deployments -l component=velero --namespace=velero
NAME     READY   UP-TO-DATE   AVAILABLE   AGE
velero   1/1     1            1           24h

$ kubectl get all --namespace=nginx-example
NAME                                   READY   STATUS    RESTARTS   AGE
pod/nginx-deployment-57d5dcb68-tks6f   1/1     Running   0          24h
pod/nginx-deployment-57d5dcb68-wd456   1/1     Running   0          24h

NAME               TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
service/my-nginx   LoadBalancer   10.101.167.57   192.168.2.242   80:32308/TCP   24h

NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-deployment   2/2     2            2           24h

NAME                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-deployment-57d5dcb68   2         2         2       24h


# バックアップ
$ velero backup create nginx-backup --selector app=nginx
Backup request "nginx-backup" submitted successfully.
Run `velero backup describe nginx-backup` or `velero backup logs nginx-backup` for more details.

# バックアップリソースの確認
$ velero backup get
NAME           STATUS      ERRORS   WARNINGS   CREATED                         EXPIRES   STORAGE LOCATION   SELECTOR
nginx-backup   Completed   0        0          2021-02-13 00:05:24 +0900 JST   29d       default            app=nginx

無事にバックアップは取れたようです。

リストア

Velero によるバックアップが成功したら、サンプルリソースを削除してバックアップデータからリストアできるか確認します。

# テスト用リソースの削除
$ kubectl delete namespace nginx-example
namespace "nginx-example" deleted

$ kubectl get all --namespace=nginx-example
No resources found in nginx-example namespace.


# リストア
$ velero restore create --from-backup nginx-backup
Restore request "nginx-backup-20210213000816" submitted successfully.
Run `velero restore describe nginx-backup-20210213000816` or `velero restore logs nginx-backup-20210213000816` for more details.

# リストアリソースの確認
$ velero restore get
NAME                          BACKUP         STATUS      STARTED                         COMPLETED                       ERRORS   WARNINGS   CREATED                         SELECTOR
nginx-backup-20210213000816   nginx-backup   Completed   2021-02-13 00:08:16 +0900 JST   2021-02-13 00:08:16 +0900 JST   0        0          2021-02-13 00:08:16 +0900 JST   <none>

$ kubectl get all --namespace=nginx-example
NAME                                   READY   STATUS    RESTARTS   AGE
pod/nginx-deployment-57d5dcb68-tks6f   1/1     Running   0          29s
pod/nginx-deployment-57d5dcb68-wd456   1/1     Running   0          29s

NAME               TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
service/my-nginx   LoadBalancer   10.109.90.104   192.168.2.242   80:30316/TCP   29s

NAME                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-deployment-57d5dcb68   2         2         2       29s

一部リストアもできたようですが、Deployments だけがリストアできていません。

これは Nginx サンプルマニフェストで Deployments にラベルが設定されておらず、バックアップ対象のリソースとして選択されなかったからです。 Nginx サンプルマニフェストを弄ってラベルをつけてあげると、ちゃんとバックアップ・リストアされるようになりました。

おわりに

今回は Kubernetes クラスタのバックアップツール Velero を試してみました。

Velero 自身はとても簡単に使えるのですが、オブジェクトストレージへの理解が低いためか、オブジェクトストレージの準備にとても時間が掛かってしまいました。 記事もオブジェクトストレージ中心で Velero はオマケ程度になってしまいました。

また、K8s クラスタ内にオブジェクトストレージを用意したため、クラスタが壊れてしまったらバックアップデータも吹き飛ぶ構成となっています。 ちゃんとバックアップとして利用するためには外部ストレージが必要ですので、自宅でも別途ストレージを用意してみたい気持ちが出てきましたので、お財布と相談してみます。

Zalando Postgres Operator を試してみた

ふとしたきっかけで、自宅ラボに PostgreSQL as a Service が欲しくなったので Zalando Postgres Operator を試してみました。

Zalando Postgres Operator とは

Zalando Postgres Operator(以下、Postgres Operator)とは、Kubernetes クラスタに HA 構成の PostgreSQL クラスタを簡単に構築してくれる Operator です。

Postgres Operator を Kubernetes クラスタにデプロイすると、postgresql というカスタムリソースを宣言したマニフェストを使うだけで簡単に PostgreSQL クラスタを構築できます。

また、専用の Web UI を使って PostgreSQL クラスタを管理することもできます。

この Postgres Operator を自宅ラボの Kubernetes クラスタにデプロイして PostgreSQL as a Service として利用します。

Zalando SE

Zalando SE とは、ヨーロッパを中心に展開するオンラインファッションプラットフォームを運営する企業です(日本で言うところの ZOZO でしょうか?)。

Zalando SE は GitHub で複数のオープンソースのプロジェクトを公開しています。

https://github.com/zalando

Zalando Postgres Operator も Zalando が開発しているプロジェクトの一つです。

構成

公式ドキュメント の画像を拝借させてもらうのですが、 Postgres Operator デプロイ後に postgresql カスタムリソースをデプロイすると、下図のリソースが Postgres Operator によって作成されます。

f:id:nnstt1:20210122034306p:plain

HA 構成の PostgreSQL クラスタを構築するため、Pod には Spilo というコンテナイメージが使用されます。

Spilo コンテナの中では PostgreSQLPatroni というサービスが動いています。

Patroni は PostgreSQL クラスタのノードを監視して、マスタが故障した際に自動フェイルオーバーを実現するためのサービスです。

f:id:nnstt1:20210122034401p:plain

Spilo と Patroni は共に Zalando のプロジェクトとなっています。

今回初めて Zalando を知りましたが、ほとんど自分たちで開発していてすごいですね…。

インストール

Postgres Operator のインストール方法には以下の3通りあります。

  • マニュアル
  • Kustomize
  • Helm

今回はマニュアル、つまりマニフェストをデプロイする方法で Postgres Operator をインストールします。

公式リポジトリ内のマニフェストkubectl apply するだけで Operator と Web UI をインストールできます。

$ git clone https://github.com/zalando/postgres-operator.git
$ cd postgres-operator/

$ kubectl create namespace zalando-postgres

# Postgres Operator
$ kubectl apply -f manifests/configmap.yaml
$ kubectl apply -f manifests/operator-service-account-rbac.yaml  # namespace: defualt のものがあるので注意
$ kubectl apply -f manifests/postgres-operator.yaml
$ kubectl apply -f manifests/api-service.yaml

# UI
$ kubectl apply -f ui/manifests/

# 確認
$ kubectl get all -n zalando-postgres
NAME                                        READY   STATUS    RESTARTS   AGE
pod/postgres-operator-f6bfcd5b4-zsh2n       1/1     Running   0          3d7h
pod/postgres-operator-ui-6dc47bbbc6-phdvw   1/1     Running   0          4d9h

NAME                           TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)        AGE
service/postgres-operator      ClusterIP      10.96.100.47     <none>          8080/TCP       6d23h
service/postgres-operator-ui   LoadBalancer   10.104.183.177   192.168.2.252   80:31363/TCP   14d

NAME                                   READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/postgres-operator      1/1     1            1           6d23h
deployment.apps/postgres-operator-ui   1/1     1            1           14d

NAME                                              DESIRED   CURRENT   READY   AGE
replicaset.apps/postgres-operator-f6bfcd5b4       1         1         1       6d23h
replicaset.apps/postgres-operator-ui-6dc47bbbc6   1         1         1       13d

postgres-operatorpostgres-operator-ui の Pod がデプロイされました。

注意点 1

manifests/operator-service-account-rbac.yaml には namespace: default と指定されているため、Postgres Operator 用の namespace を default 以外にする場合には変更が必要です。

# manifests/operator-service-account-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: postgres-operator
  namespace: zalando-postgres

注意点 2

Web UI のマニフェストはそのままでは namespace: default しか管理対象となりません。 全 namespace を対象とする場合は、ui/manifests/deployment.yamlTARGET_NAMESPACE"*" を指定します。

# ui/manifests/deployment.yaml
env:
  - name: "TARGET_NAMESPACE"
    value: "default"    # "*" に変更

以上で Postgres Operator のインストールは完了です。

PostgreSQL クラスタ構築

Postgres Operator の準備ができたので、PostgreSQL クラスタを構築していきます。 クラスタ構築には2通りあります。

  • Web UI
  • マニュアル

Web UI

さきほど作成した postgres-operator-ui に Service または Ingress 経由でアクセスすると、クラスタ作成画面が表示されます。

必要な項目を入力して「Create Cluster」を押すと PostgreSQL クラスタが作成されます。

f:id:nnstt1:20210122024955p:plain

マニュアル

マニュアルの場合は、postgresql リソースを宣言したマニフェストを準備します。

公式リポジトリのサンプルや、Web UI のクラスタ作成画面に表示されるマニフェストが参考になります。

以下は自宅ラボの k8s クラスタにデプロイしたお試しマニフェストです。 Rook で用意した storageClass を追加しています。

# postgres.yaml
apiVersion: acid.zalan.do/v1
kind: postgresql
metadata:
  name: acid-example
  namespace: default
  labels:
    team: acid
spec:
  teamId: acid
  postgresql:
    version: "13"    # note: String
  numberOfInstances: 1
  volume:
    size: 1Gi
    storageClass: rook-ceph-block
  users:
    example: []
  databases:
    example: example
  allowedSourceRanges:
  resources:
    requests:
      cpu: 100m
      memory: 100Mi
    limits:
      cpu: 500m
      memory: 500Mi

postgresql リソースのデプロイが完了すると、Postgres Operator により StatefulSet や Service の各種リソースが作成されます。

$ kubectl get all
NAME                      READY   STATUS    RESTARTS   AGE
pod/acid-example-0            1/1     Running   0          164m

NAME                      TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)        AGE
service/acid-example          ClusterIP      10.98.124.54     <none>          5432/TCP       7d
service/acid-example-config   ClusterIP      None             <none>          <none>         7d
service/acid-example-repl     ClusterIP      10.106.121.165   <none>          5432/TCP       7d

NAME                        READY   AGE
statefulset.apps/acid-example   1/1     7d

NAME                                TEAM   VERSION   PODS   VOLUME   CPU-REQUEST   MEMORY-REQUEST   AGE   STATUS
postgresql.acid.zalan.do/acid-example   acid   13        1      1Gi      100m          100Mi            7d    Running

注意点

Web UI の注意点として、volume.storageClass の指定ができません。

そのため、Rook Ceph で作成した storageClass などを指定する場合はマニフェストを使う必要があります。

Web UI では基本的な情報を埋め込んだマニフェストを作成し、細かい設定はマニフェストに直接記述するのが良さそうです。

接続確認

kubectl port-forward を使い、ローカル環境から PostgreSQL クラスタへ接続できるか確認します。

本当はフェイルオーバーなど HA 構成の特性を検証してみたかったのですが、PostgreSQL について勉強不足のため実施していません。

Postgres Operator によりログイン用のパスワードが Secret として作成されています。

$ export PGMASTER=$(kubectl get pods -o jsonpath={.items..metadata.name} -l application=spilo)
$ kubectl port-forward $PGMASTER 6432:5432 &

$ kubectl get secret postgres.acid-example.credentials -o 'jsonpath={.data.password}' | base64 -d
$ export PGSSLMODE=require
$ psql -U postgres -h localhost -p 6432
Handling connection for 6432
ユーザ postgres のパスワード: 
Handling connection for 6432
psql (13.1)
SSL 接続 (プロトコル: TLSv1.2、暗号化方式: ECDHE-RSA-AES256-GCM-SHA384、ビット長: 256、圧縮: オフ)
"help"でヘルプを表示します。

postgres=# 

無事に Postgres Operator で作成した PostgreSQL クラスタに接続することができました。

参考にさせていただいたサイト

speakerdeck.com

今回 PostgreSQL as a Service を構築するにあたって情報を提供くださった @tzkb さんのスライドです。

そもそもの「Kubernetes 上でデータベースを動かす際の課題」から、より深い話まで説明されています。

仕事では SQL Server メインなのですが、Database on Kubernetes という観点では課題は同じはずですので、とても勉強になりました。

www.sraoss.co.jp

Zalando Postgres Operator の紹介をされている記事です。

今回のネタは、ほぼ上記記事の内容を元にしています。 フェイルオーバーなどのクラスタ構成の検証もされていて興味深かったです。

recruit.gmo.jp

Patroni を試されている記事です。

自分は Patroni が何者か分からない状態だったので参考になりました。

さいごに

PostgreSQL as a Service のための Zalando Postgres Operator の使い方について確認しました。

PostgreSQL については本当に初心者なのですが、そのような状態でも Kubernetes クラスタがあれば簡単に PostgreSQL as a Service が実現できました。

もし手元に Kubernetes クラスタをお持ちなら、クラウドサービスではなく Zalando Postgres Operator も選択肢に入れてみると面白いかもしれません。