落とし穴に立て札を立てるが如く

ハマりどころの解決が少しでも早くなることを願って書いていきます

Kubestronaut の称号を得たのでちょっと自慢させてください

この記事は後ほど cloud.config tech blog にもマルチポストします。

はじめに

この度私ことなむゆこと南條祐輝は Cloud Native Computing Foundation (CNCF) が主催する Kubernetes 関連資格を見事全て取得し、Kubestronaut の称号を授与されましたのでこちらにて自慢させていただきたく存じます。
https://www.credly.com/earner/earned/badge/d7bb7a25-8784-4be3-b43b-fd6c438bfe2c

Kubestronaut とは

Kubestronaut は、CNCF が主催している Kubernetes 関連の資格 5 つ全てを同時に有効にした人に贈られる称号です。
2024 年 3 月に行われたKubeCon Europe の 4 日目の Keynoteにて教育アンバサダープログラム関連の動きの一つとして発表されました。
同時に有効にするというのは説明が必要なのですが・・・
Kubernetes の資格には合格してから 3 年間(2024 年 4 月 1 日以降に取得したものは 2 年間になりました、残念)の有効期限があり、その期限を迎えると資格が失効します。
そのため、同時に有効にするということは 5 つ全ての資格が全てそれぞれ最後に取得してから有効期限を迎えていない状態であるということです。
Kubernetes 関連の技術は日々変化しているため、試験に合格したことがあるというだけでなく、最新の Kubernetes の知識のキャッチアップがちゃんとできているかどうかも問われるようです。
5 つ全ての資格を取得した状態にすると最後の資格を取得してから 1 か月以内に CNCF からメールが届き、上記のように Credly で Kubestronaut の称号を授与されます。

Kubernetes 資格の特徴

CNCF が主催している Kubernetes 資格は 5 つあり、そのうちの 3 つ(CKAD、CKA、CKS)はハンズオン形式の試験になります。
ハンズオン形式なので、試験では実際の Kubernetes 環境に接続し、コマンドラインで操作を行って問題を解きます。
問題では Kubernetes の知識を問われるだけでなく、試験時間に対して問題数が多いため個々の操作を効率的に行うテクニックも要求されます。
詳しくはKubernetes が普段使いの人のための CKAD 対策の話の記事もどうぞ。
残りの二つの試験(KCNA、KCSA)は 4 択問題を解く形式で、こちらは残りの資格の入門レベルの位置づけになります。
Kubernetes 資格の目立つ特徴としては、受験コストがそれなりに高いことが挙げられます。
CKA、CKS、CKAD の試験の価格は 2024 年 4 月現在それぞれ 395$です。
KCNA、KCSAは250$です。
もちろん日本円で支払うと為替レートの影響も受けるため、ざっくり手計算で 60000 円強、37500 円強かかることになります。
試験を管理している Linux Foundation は頻繁に試験のセールを行っているため実際にはこの価格の 7 割~半額で受験していますが、それでもこれらの資格を全て取得しようとすると受験だけでもそれなりのコストがかかります。

特典は?

Kubestronaut になると以下のような特典があるそうです。

  • Kubestronaut ジャケット(こちらに写真があります)
  • CNCF のイベントや資格のディスカウント(資格の維持に助かります・・・が、具体的に何割引きになるかはまだ発表されていません)
  • CNCF の Ambassadorとして登録される(らしい)

なぜこの称号を狙おうと思ったのか

Kubernetes は元々業務で頻繁に使用していて自分にとって強みのある技術であると考えていたので、そのスキルが CNCF に称号として認めてもらえたら嬉しいかな、というのが一番のモチベーションでした。
副次的には具体的に有用な特典(特にディスカウントとか)もあるため、これから先 Kubernetes 資格を維持していくために有用かなと思えたのも理由の一つです。
あとは Kubestronaut Program が発表された段階で CKS、CKAD、CKA の資格を既に保持していたため、残りは入門レベルの資格である KCNA と KCSA の 2 つだけであったことも大きかったかと思います。
もし今 Kubernetes 資格を 1 つも取得していない状態で Kubestronaut Program のことを知ったとして、特典のために 1 つから資格を取得し始めていたかと思うとそうでもない気がします。
あくまでその時の状態で追加で支払うコストが比較的安くなっていたため目指しただけかもしれません。
それでもその時点で残りの 2 つの資格の取得のために 2 週間ほどの猛勉強の時間と 250$x2(セールの 3 割ほどのディスカウントはありました)のコストが必要だったのですが、その時はそれでも十分見合うだろうと考えていました。今もそう考えています。不思議です。

おわりに

今回は CNCF から発表された Kubestronaut という称号について自慢したり紹介したりしてきました。
Kubernetes の知見や取得した資格がまだない状態から狙っていくようなものではないかとは思いますが、それなりに Kubernetes について詳しかったり、すでに Kubernetes 資格を半分近く持っているという人にとってはこれまで積み上げてきたものが一つ公に認められるようなそういう称号であるように感じます。
そういった方は目指してみるべきかと思います。

参考

KubeCon + CloudNativeCon Europe 2024 Day four: Looking to the past and the future of Kubernetes (and more news
Announcing the Kubestronaut program
Kubestronaut program | CNCF
Kubestronaut Program - Linux Foundation
CNCF Ambassadors

前からあるけど意外と便利な iPhone のショートカット機能を使う

この記事は後ほど cloud.config tech blog にもマルチポストします。

はじめに

PC の OS は Windows 専ですが、携帯電話は iPhone しか使わないなむゆです。 iPhone にはしばらく見ないうちにいろいろな便利機能が追加されてたりします。 大抵の機能は一度触ったらそれっきりになる気がしますが、一部はそこそこ便利で毎日使うようになるものもあったりします。 今回はそんな中の一つであるショートカット機能について紹介しようと思います。

ショートカット機能とは

ショートカット機能は、iOS 13 から追加された iPhone の機能です。 処理を実行する条件とその時にどのような処理を行うかを設定してやることで、iPhone 上で大なう作業を自動化することができます。 この記事を見に来るような方にとっては、Azure Logic Apps や Power Automate が感覚的に近いでしょうか。 処理を実行する条件としては時刻のようなオーソドックスなもののほかに、「特定の場所から出発/特定の場所に到着したとき」や「Wi-Fi/Bluetooth に接続したとき」、「充電器に接続したとき」など、携帯電話ならではのものが多く、iPhoneユースケースに沿った自動化が組めるようになっています。 実行する内容も iPhone の機能と結びついており、大半のデフォルトのアプリ(例えばアラームとか)をいじったりだとか、音量、画面の明るさ等 iPhone 自体の設定をいじるようなこともできます。 これらを組み合わせることで、普段 iPhone を通して頻繁に行う作業を自動化してより便利に処理を行うことができます。

で、何が便利なの

これだけ聞くと、そこまで便利かと言われるとそうでもないようにも思います。 実際自分も前は一回だけ触ってもののそんなに自動化することないなと思ってほったらかしてました。 ただ、後からよく考えてみるとなんだかんだで iPhone 使って毎日やることもなくはない名と気付きました。 例えば自分の場合、夜になってから次の日のアラームを仕掛けるのですが、仕掛けると言っても 5:30 か 7:00 のどちらかの二択になります。 常に片方のアラートだけをセットするなら、時計アプリの機能で毎日アラームを鳴らすようにすればいいのですが、若干選択をする部分があるのでシンプルに自動化することができません。 iPhone のオートメーションの機能はそういった若干要件が複雑になった自動処理や、途中に入力を求めるような処理にも対応でき、そんな意味で便利な機能なんじゃないかと思います。

使用例

さて、そんなオートメーション機能を使って実際に何か作ってみましょう。 今回は例として、自分も普段使いしている「夜 10 時になったら次の日何時にアラートを仕掛けるか決めてセットする」オートメーションを作っていきます。

まずはホーム画面から「ショートカット」のアプリアイコンを探してタップします。(当たり前) アイコンとしては下記のようなものです。iPhone のデフォルトでインストールされている便利機能系アプリは数が多いので探しやすいように念のため。

ショートカットアプリを開いたら、下のタブで「オートメーション」を選択します。 これまで作成したオートメーションの一覧が表示しますが、一旦無視して新規作成のため右上の「+」ボタンを押します。

「個人用オートメーションを作成」を選択してオートメーションの作成を始めます。

すると新規オートメーションの実行条件を選択することになるので「時刻」を選択します。

すると、周期と何時に実行するかを聞かれるので、この処理を実行したい時間を選択します。自分の場合は 22:00 を選択しました。

次に、その時間に実行するアクションを選択します。 実行時はまずはアラームを鳴らす時間を選択したいので、「すべてのアクション」から「メニューから選択」を選びます。

すると、アクションの一覧に「メニューから選択」のアクションが追加されます。 「-」ボタンの横に選択肢のラベルが編集できるようになっているので、設定したいアラームの時刻辺りをテキストで打ち込んでいきます。 上部の「プロンプト」にはメニューの表示時に表示するメッセージが設定できるので、分かりやすいメッセージを書き込んでおきましょう。

入力が完了したら、次はアクションを追加していきます。実際に行いたいのはアラームの設定なので、アクションの検索から「アラーム」と入力して検索し、「アラームを切り替える」を選択します。

するとアラームを設定するアクションが追加されるので、時間をタップして設定するアラームを選択します。 アラームを選択したら、そのアクションをドラッグし、5:30 など、作成した選択肢の下に移動させます。これによって、先に作成したメニューでそのラベルが選択された時の処理を記述することができます。

同様にして、もう片方の選択肢の実行時にセットするアラームのアクションを設定します。 ここまででオートメーションの作成は完了です、右下の実行ボタンを押してこのオートメーションを実行してみましょう。

画面上部からメニューが生えてきて、作成した選択肢が表示されるはずです。 どちらか片方を選択しましょう。

オートメーションの処理としてはそこで完了になります。iPhone のホーム画面に戻って「時計」アプリからアラームの一覧を見てみましょう。 選択した時間のアラームがオンになっているはずです。

おわりに

今回は iPhone のオートメーション機能について共有しました。 機能として追加されたのはもう 2 年前になりますが、使ってみると今更のように便利さを感じることもあります。 既に機能としてはご存知の方もいるかと思いますが、久々に思い出した方はこの機会に普段 iPhone を使用して行っている処理の一部を自動化できないか考えてみるのも楽しいかもしれないですね。

参考

【小ネタ】Kubernetes でバッチアプリをデバッグしたいときに使える Pod の command 上書きの話

f:id:nam_yu_sql:20220414083002j:plain この記事は後ほどcloud.config tech blogにもマルチポストする予定です。

使いどころ

Kubernetes 上で動かすバッチアプリケーションを開発しているとき、コンテナ動作がおかしくてデバッグしたくなることがあるかと思います。
バッチでない、普段起動状態でとどまるようなアプリケーションなら kubectl exec コマンドを使ってコンテナに入り、各種デバッグ作業ができるのですが・・・
バッチのような、デプロイ後すぐに動作をはじめ、動作が終わるとすぐに停止してしまうようなアプリケーションの場合起動している時間が短すぎるのでコンテナには言ってもすぐに Pod が停止して締め出されてしまいます。
そういった時に、コンテナの生存時間を無理やり伸ばしてコンテナ内でデバッグするのに使える便利な小ネタの話です。

やり方

アプリケーションの展開に使うマニフェストを用意します。
マニフェストの中のコンテナの設定をしている箇所で、以下の 5 行目のような行を挟み込みます。

...<前略>  
  containers:  
  - name: <何かしらコンテナ名>  
    image: <何かしらのイメージ>  
    command: ["/bin/sh", "-c", "sleep 100000"]  
...<後略>  

その後、マニフェストをデプロイします。
すると、コンテナが処理を始めなくなるのでコンテナ内を歩き回って各種調査をすることができます。

詳細の話

これは、Kubernetesマニフェストcommandのパラメータを使用した小技です。
コンテナを作るときに書く Dockerfile には、起動後に実行するファイルを指定するためにENTRYPOINT命令を書きます。
例えばバッチアプリケーションを実行する場合はそのアプリケーションの実行ファイルを指定しておくことで、コンテナが起動後すぐにそのアプリケーションを実行できます。
一方で、今回指定した Kubernetesマニフェストcommandパラメータは、これを上書きする効果があります。
これによって本来は起動後に Dockerfile で指定したバッチ用アプリケーションを実行するはずが、今回はここで書いた bin/sh -c sleep 100000 のコマンドが実行され、100000 秒間 sleep します。
コンテナは sleep 実行中は等に何も問題が起きない限り起動し続けるので、sleep している間にkubectl execコマンドを使ってコンテナ内に入り、探索することができるということです。
同じような効果のものとして、Kubernetes を使う前のコンテナの段階では、コンテナ実行時に docker run --endpoint <各種コマンド> と書くことでも、コンテナ起動時にエンドポイントを上書きできます。
こちらはローカルの Docker で動作確認を行う時に使える方法です。
話は戻りまして、 commandパラメータは他にも、busybox などの小型で便利ツールだけが詰まったようなイメージにおいては各種シェルコマンドを使うことでアプリケーション実装要らずの小規模なバッチ処理を書くこともできたりするので何かと便利です。
困ったときの小手三寸として検討してみるといいかと思います。

おわりに

今回は、起動後すぐに処理を実行してその後すぐに終了してしまうようなバッチ系のアプリが入ったコンテナをデバッグする方法として、コンテナの ENTRYPOINT を上書きして起動状態を維持する方法について紹介しました。
しばらく方法を考えていくつか検索をかけたら誰かしら思いついそうな気もする小ネタですが、最初に検索してこの記事に当たったあなたはちょっとラッキー(だといいな)ということで。

参考

Kubernetes のマニフェストファイルを手軽に色々チェックできる ValidKube を使ってみる

f:id:nam_yu_sql:20220318070916j:plain この記事は cloud.config tech blog にもマルチポストしています。

tech-blog.cloud-config.jp

はじめに

Kubernetesマニフェストを書いているとき、書いている内容が正しいか、ベストプラクティスに沿っているか確認したくなることがあるかと思います。
そんな時に使えるツールはいくつかありますが、その中でもブラウザ上で手軽に利用できそうなサービスがあったので紹介してみる回です。

ValidKube とは

ValidKubeは、ブラウザ上で動作する Kubernetesマニフェスト用のチェックを行える Web アプリです。
Kubernetesマニフェストについて、構造が正しいかどうか検証したり、不要な記述を除いたり、セキュリティ上の問題点がないかを調べたりすることができます。

このツールの実体はこの後ろで動いている個別の検証ツールで、それぞれ以下のツールがバックエンドで動いています。

Web アプリケーション上でマニフェストを入力して実行するとマニフェストがバックエンドに送られ、上記ツールを使用して検証を行い、検証結果を Web アプリケーション上で表示する仕組みで動いているようです。

使い方

ページを開くと右と左に 2 つのフォームがあります。
この中の左のフォームに検証したいマニフェストファイルの中身を張り付け、右側のフォームの上部にあるタブで検証の内容を選び、「Run」ボタンをクリックするとマニフェストの検証が行われます。
f:id:nam_yu_sql:20220318070943p:plain
今回は例としてサンプルのマニフェストを使用してみましょう。
左側のフォームの下の Example のボタンをクリックしてから Run ボタンをクリックすると以下のような結果が出力されています。
f:id:nam_yu_sql:20220318070955p:plain
結果の中に

'spec.replicas: Invalid type. Expected: [integer,null], given: string'  

とあることから、spec.replicas のパラメータの型が integer でなければならないのに string が入寮されているので不正であると言われていますね。

同様に、右側のフォームの上部のタブから「Secure」を選択してみましょう。
するとすぐに検証が実行され、以下のような結果が出力されるはずです。
f:id:nam_yu_sql:20220318071006p:plain
出力結果には様々な脆弱性yaml 形式で表示されていますが例えば以下のようなものが出力されています。

  - Description: A program inside the container can elevate its own privileges and  
      run as root, which might give the program control over the container and node.  
    ID: KSV001  
    IacMetadata: {}  
    Layer: {}  
    Message: Container 'nginx' of Deployment 'nginx-deployment' should set 'securityContext.allowPrivilegeEscalation'  
      to false  
    Namespace: appshield.kubernetes.KSV001  
    PrimaryURL: https://avd.aquasec.com/appshield/ksv001  
    Query: data.appshield.kubernetes.KSV001.deny  
    References:  
    - https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted  
    - https://avd.aquasec.com/appshield/ksv001  
    Resolution: Set 'set containers[].securityContext.allowPrivilegeEscalation' to  
      'false'.  
    Severity: MEDIUM  
    Status: FAIL  
    Title: Process can elevate its own privileges  
    Type: Kubernetes Security Check  

例えば上記の脆弱性だと、「コンテナ内のプログラムが権限昇格してコンテナやノードの中身を弄り回す危険があるので、マニフェストのコンテナの設定パラメータでsecurityContext.allowPrivilegeEscalationfalseに設定してください」といった内容になります。
ここで報告される脆弱性は、Aqua security という企業が公開している脆弱性データベースが元になっており、公開されている脆弱性の一覧はこちらで閲覧することができます。

このようにして、作成したマニフェストの検証をブラウザ上で簡単に行うことができます。

使い道

このアプリケーションの実体は 3 つの個別のコマンドラインツールです。
コマンドラインツールとして動くということで、CI パイプラインに組み込みやすいため、Git でのコミット毎、または PR 作成時にビルドパイプラインを実行するようにし、それらで個別のツールを使用し、マニフェストの検証を行うといった使い方ができます。
というか、これらのツールのユースケースはそういった場面が主に想定されています。
一方で、ビルドパイプラインを実行する前に開発を進める段階でマニフェストを検証するのにはマニフェストを簡単に検証できるため ValidKube を使用することになるのかなと思います。

また、ValidKube は Githubオープンソースとして公開されており、アプリケーションをビルドして自前の環境に展開する方法が Github リポジトリ上で紹介されています。
このアプリケーションはサイトで公開されているものなので、他人のサービスに自前のマニフェストを送信したくないという場合もあると思いますが、その場合は時前の環境に ValidKube を展開して身内にのみ公開することでマニフェストを外部に公開せずに ValidKube を利用することもできます。

マニフェストファイルの品質を上げたい場合や、Kubernetes に展開する前にマニフェストの整合性を検証しておきたい場合には検討できるツールかと思います。

おわりに

今回は kubernetesマニフェストの検証ツールである validkube を触ってみました。
マニフェストファイルの中身が妥当か判断するのは人の目だけだと難しい部分もあり、そのような部分をカバーするのに自動化されたツールはとても有用だと思います。
作成したマニフェストの検証方法に悩んでいる方の参考になれば幸いです。

参考

PersistentVolume と Pod の NodeAffinity はどっちが優先されるの

f:id:nam_yu_sql:20220215084324j:plain

この記事は cloud.config tech blog にもマルチポストしています。

tech-blog.cloud-config.jp

はじめに結論

Q PersistentVolume と Pod、どちらでも NodeAffinity が設定できますが、これらで互いに矛盾するような設定をすると、どのように Pod がスケジューリングされるのでしょうか?

A node affinity conflict のエラーであるとしてスケジューリングされなくなります。

説明

Kubernetes には NodeAffinity という概念があります。これは、その Pod がどの Node に配置されるか(スケジューリングされるか)をコントロールするために使われるものです。
Node 毎に label を設定し、その label が付けられた Node を対象とした NodeAffinity を Pod に設定してやることで、その Pod が展開される先の Node を指定したり、優先順位付けを行ってやることができます。
この NodeAffinity は、アプリケーションの本体である Pod に対して直接設定してやることもできますが、他にも設定できるリソースがあります。
PersistentVolume がその一つです。
PersistentVolume は Pod で作成されたデータを永続化するための概念で、Node として動作しているマシン内や外部のファイルシステム等にそのデータを保持する場所を定義し、Pod 内の特定のディレクトリと繋ぎ合わせます。
この中で特に、Node 内部にデータを保存する場合、どの Node に対してその Pod を展開するかが重要になってきます。
なぜなら、複数の Node がある環境の場合、Pod が配置された Node のマシン上にデータを保存すると言っても Pod がどの Node に配置されるか分からないため、Pod から作成されたデータがどこの Node に保存されるかも分からなくなってしまうためです。
そのため、PersistentVolume というリソースでも NodeAffinity を指定することができ、この PersistentVolume を使用することが指定されている Pod は使用している PersistentVolume の NodeAffinity に従って Node に配置されます。
ということで、NodeAffinity は Pod それそのものと、PersistentVolume の両方で設定することができます。
では、これらで互いに矛盾するような NodeAffinity を設定すると、どちらが優先されるのかというのが今回の話の趣旨になります。

検証方法

まず最初に、複数の Node がある Kubernetes クラスターを用意します。
クラッチで用意してもいいのですが、自分の場合今回は Azure に頼って AKS で簡単に二つ Node がある環境を用意しています。

次に、作成した二つの Node にそれぞれ別の label を設定します。

kubectl label node <ひとつめのNodeの名前> nodeName=A  
kubectl label node <ふたつめのNodeの名前> nodeName=B  

続いて、各種マニフェストファイルを用意します。
0_pv.yaml

apiVersion: v1  
kind: PersistentVolume  
metadata:  
  name: local-pv  
spec:  
  capacity:  
    storage: 1Gi  
  volumeMode: Filesystem  
  accessModes:  
    - ReadWriteMany  
  persistentVolumeReclaimPolicy: Retain  
  storageClassName: local  
  local:  
    path: /  
  nodeAffinity:  
    required:  
      nodeSelectorTerms:  
        - matchExpressions:  
            - key: nodeName  
              operator: In  
              values:  
                - A  

1_pvc.yaml

apiVersion: v1  
kind: PersistentVolumeClaim  
metadata:  
  name: local-pvc  
spec:  
  accessModes:  
    - ReadWriteMany  
  volumeMode: Filesystem  
  resources:  
    requests:  
      storage: 1Gi  
  storageClassName: local  

2_deployment.yaml

apiVersion: apps/v1  
kind: Deployment  
metadata:  
  creationTimestamp: null  
  labels:  
    app: prefer-b-deployment  
  name: prefer-b-deployment  
spec:  
  replicas: 10  
  selector:  
    matchLabels:  
      app: prefer-b-deployment  
  strategy: {}  
  template:  
    metadata:  
      creationTimestamp: null  
      labels:  
        app: prefer-b-deployment  
    spec:  
      containers:  
        - command:  
            - /bin/sh  
            - -c  
            - sleep 1000  
          image: busybox  
          name: busybox  
          resources:  
            requests:  
              cpu: 50m  
              memory: 50Mi  
            limits:  
              cpu: 50m  
              memory: 50Mi  
          volumeMounts:  
            - mountPath: "/path"  
              name: mypd  
      volumes:  
        - name: mypd  
          persistentVolumeClaim:  
            claimName: local-pvc  
      affinity:  
        nodeAffinity:  
          requiredDuringSchedulingIgnoredDuringExecution:  
            nodeSelectorTerms:  
              - matchExpressions:  
                  - key: nodeName  
                    operator: In  
                    values:  
                      - B  
status: {}  

ここで確認しておきたいのは、0_pv.yaml と 2_deployment.yaml に設定した nodeAffinity の値です。
両方にそれぞれrequiredrequiredDuringSchedulingIgnoredDuringExecutionとして Node を指定するパラメータを設定しています。
Node にはそれぞれ nodeName として A,B という label を割り振っているので、PersistentVolume と Pod では別の Node に割り当てられようとしています。
requiredDuringSchedulingIgnoredDuringExecutionの別のパラメータとしては preferredDuringSchedulingIgnoredDuringExecutionというものがあるのですが、こちらはrequiredではなくprefferedなので、required 程優先はされません。
そのため今回はpreferredDuringSchedulingIgnoredDuringExecutionの方を使用しています。

続いて、マニフェストを展開します。

kubectl apply -f 0_pv.yaml  
kubectl apply -f 1_pvc.yaml  
kubectl apply -f 2_deployment.yaml  

マニフェストを展開したら、しばらくして pod の様子を確認します。

kubectl get pod  

結果はどうでしょうか。例としては以下のように、全部の Pod が Pending の状態になっているはずです。

NAME                                   READY   STATUS    RESTARTS   AGE  
prefer-b-deployment-7479ffc7f8-2hpdc   0/1     Pending   0          9h  
prefer-b-deployment-7479ffc7f8-5cgqq   0/1     Pending   0          9h  
prefer-b-deployment-7479ffc7f8-5fp6m   0/1     Pending   0          9h  
prefer-b-deployment-7479ffc7f8-9bllh   0/1     Pending   0          9h  
prefer-b-deployment-7479ffc7f8-gxnrp   0/1     Pending   0          9h  
prefer-b-deployment-7479ffc7f8-r5p8g   0/1     Pending   0          9h  
prefer-b-deployment-7479ffc7f8-rmnkd   0/1     Pending   0          9h  
prefer-b-deployment-7479ffc7f8-tjxdz   0/1     Pending   0          9h  
prefer-b-deployment-7479ffc7f8-tqbff   0/1     Pending   0          9h  
prefer-b-deployment-7479ffc7f8-xvs7c   0/1     Pending   0          9h  

個々の Pod の状態はどのようになっているのか確認しましょう。
このうちの一個の Pod の名前を指定して、様子を見てみましょう。

kubectl describe pod prefer-b-deployment-7479ffc7f8-2hpdc  

出力は分量が多いので省略しますが、最後の Event の部分が以下のようになっているはずです。

Events:  
  Type     Reason            Age   From               Message  
  ----     ------            ----  ----               -------  
  Warning  FailedScheduling  62m   default-scheduler  0/2 nodes are available: 1 node(s) didn't match Pod's node affinity/selector, 1 node(s) had volume node affinity conflict.  

0/2 nodes are available: 1 node(s) didn't match Pod's node affinity/selector, 1 node(s) had volume node affinity conflict.とあるとおり、Pod の NodeAffinity と PersistentVolume の NodeAffinity との間で Conflict が起きているためスケジューリングできる Node がないとしてエラーとなることが確認できます。

おわりに

Pod のスケジューリングの仕組みは組み合わせによってはこのようなエラーが出ることもあるので、node の conflict のエラーが起きたときに疑うべき要因の一つになるかもなと思った回でした。

参考

Kubernetes の Volume、azureFile は azureFile 上のファイルの更新に追従する

f:id:nam_yu_sql:20220118061015j:plain この記事は cloud.config tech blog にもマルチポストしています。

tech-blog.cloud-config.jp

はじめに

Kubernetes で azureFile をマウントする Volume を使っていた際、本来アプリケーションが azureFile 上に配置した設定ファイルが更新されるとそれを検知して設定を更新する振る舞いをするはずなのにアプリケーションの設定が更新されないということが起きました。
問題切り分けのために、azureFile をマウントする Volume を設定したときに azureFile 上のファイルを更新するとその Volume をマウントしている Pod でもその更新が確認できるかどうかを検証してみたので、知見のため共有です。

結論から言うと

azureFile 上のファイルを更新すると、Pod 上から見たときのファイルもちゃんと更新されます。(当たり前と言えば当たり前)

検証の前提

検証手順

  1. AzureFile を用意します

AzureFile を使用するため、ストレージアカウントを作成します。
まずは Azure Portal の左上のハンバーガーボタンから、「+リソースの作成」をクリックします。
f:id:nam_yu_sql:20220118061029p:plain
作成できるリソースの種類の一覧から、ストレージアカウントを探し出し、その下の「作成」をクリックします。
f:id:nam_yu_sql:20220118061039p:plain
ストレージアカウント名、配置する先のリソースグループを設定します。冗長性については、こだわりなければ LRS で問題ないかと思います。
設定できたら「確認および作成」、「作成」をクリックしてストレージアカウントリソースを作成します。
f:id:nam_yu_sql:20220118061049p:plain
ストレージアカウントが作成できたら、Kubernetes からストレージアカウントに接続するためのキーを取得します。
そのリソースを選択してリソースの概要を開きます。
ブレードメニューから、「アクセスキー」を選択します。
f:id:nam_yu_sql:20220118061059p:plain
アクセスキー画面上部にある「キーの表示」ボタンをクリックし、非表示になっているシークレットを表示します。
表示したシークレットはコピーして手元に保存しておきます。
f:id:nam_yu_sql:20220118061109p:plain
2. ファイルをアップロードします

次は、AzureFile にファイルをアップロードします。
ストレージアカウントのブレードメニューから「ファイル共有」を選択します。
開いたファイル共有画面上部の「+ ファイル共有」をクリックします。
f:id:nam_yu_sql:20220118061205p:plain ファイル共有名として今回はsample、レベルは「ホット」を選択して作成ボタンを押します。
f:id:nam_yu_sql:20220118061214p:plain
ファイル共有を作成したらメニューに追加された作成したファイル共有を選択し、中に入ります。
ファイル共有内の画面上部の「アップロード」ボタンを押し、ファイルをアップロードします。
今回はsample.txtという名前のファイルにtest1と文字列を書き込んでアップロードしています。
これから先のマニフェストをそのまま使う場合、ファイル名は sample.txt にしておいてください。
f:id:nam_yu_sql:20220118061227p:plain
3. Kubernetes クラスターに設定を行います

サンプル用のファイルがアップロードできたら、次は Kubernetes 側で azureFile をマウントする Pod を作成する準備を行います。
azureFile タイプの Volume を使用する方法はこちらにあるのですが、あちらではコマンドベースで作業を行っています。
この記事ではもう少しお行儀よく、マニフェストファイルを作成してそれを適用する方法で進めていきたいと思います。

それでは、まずは以下の 3 つのマニフェストファイルを作成してください。
<>で囲っている部分については、1.で AzureFile を作成したときの情報をコピー&ペーストしてきておいてください。

00_secret.yaml

apiVersion: v1  
stringData:  
  azurestorageaccountkey: <1でコピーしてきたストレージアカウントのシークレット>  
  azurestorageaccountname: <作成したストレージアカウントの名前>  
kind: Secret  
metadata:  
  name: azure-secret  

00_pv.yaml

apiVersion: v1  
kind: PersistentVolume  
metadata:  
  name: azurefile-pv  
spec:  
  capacity:  
    storage: 1Gi  
  volumeMode: Filesystem  
  accessModes:  
    - ReadWriteMany  
  persistentVolumeReclaimPolicy: Retain  
  storageClassName: azurefile  
  azureFile:  
    secretName: azure-secret  
    shareName: sample  
    readOnly: true  

00_pvc.yaml

apiVersion: v1  
kind: PersistentVolumeClaim  
metadata:  
  name: azurefile-pvc  
spec:  
  accessModes:  
    - ReadWriteMany  
  volumeMode: Filesystem  
  resources:  
    requests:  
      storage: 1Gi  
  storageClassName: azurefile  

01_pod.yaml

apiVersion: v1  
kind: Pod  
metadata:  
  name: azurefile-test  
spec:  
  containers:  
  - name: busybox  
    image: busybox  
    volumeMounts:  
    - name: storage  
      mountPath: /data/sample  
    command:  
    - sh  
    - -c  
    - "while true;do date; echo current sample file is:; cat /data/sample/sample.txt; echo; sleep 10; done"  
  volumes:  
  - name: storage  
    persistentVolumeClaim:  
      claimName: azurefile-pvc  

準備できたら、これらのマニフェストファイルを上から順に適用していきます。

kubectl apply -f 00_secret.yaml  
kubectl apply -f 00_pv.yaml  
kubectl apply -f 00_pvc.yaml  
kubectl apply -f 01_pod.yaml  

pod が立ち上がり、Running の状態になれば成功です。

  1. 確認

今回作成した Pod は、10 秒おきに AzureFile 上にある sample.txt の中身を cat してログ出力するようにしています。
なので、ログを確認してみましょう。

kubectl logs azurefile-test  

すると、以下のようなログが出力されているはずです。

Mon Jan XX XX:XX:XX UTC 2022  
current sample file is:  
test1  
Mon Jan XX XX:XX:XX UTC 2022  
current sample file is:  
test1  

それでは本題に入り、azureFile 上のファイルを更新して、Pod 上からもその更新が読み取れるか確認してみましょう。
まずは、再度 AzureFile のページを開きます。アップロードした sample.txt が存在するはずなので、その行の右端の「・・・」メニューから、「編集」を選択します。
f:id:nam_yu_sql:20220118061301p:plain
するとファイルを直接変更できるようになるので、ファイルの中身をtest2に書き換えて上の「保存」ボタンを押して保存しましょう。
f:id:nam_yu_sql:20220118061311p:plain
それが終わったら再度以下の kubectl コマンドを実行します。

kubectl logs azurefile-test  

すると以下のようなログが出力されるはずです。
ある時期を境に file の中身がtest2になっていますね。
これにて azureFile のファイルに変更を加えると Pod から再度ファイルを取得したときにはその変更が読み取れることが確認できました。

Mon Jan XX XX:XX:XX UTC 2022  
current sample file is:  
test1  
Mon Jan XX XX:XX:XX UTC 2022  
current sample file is:  
test1  
Mon Jan XX XX:XX:XX UTC 2022  
current sample file is:  
test2  
Mon Jan XX XX:XX:XX UTC 2022  
current sample file is:  
test2  

おわりに

疑ってごめんね azureFile...

参考

Kubernetes の local 系 Volume の 3 つを比べてみる

f:id:nam_yu_sql:20220117081810j:plain

この記事はcloud.config tech blogにもマルチポストしています。

tech-blog.cloud-config.jp

はじめに

Kubernetes の Pod のデータの永続化において、Kubernetes が動いているマシン上にデータを置く Volume のタイプには local と hostPath、emptyDir とありますが、それぞれ似たり寄ったりした概念で若干混乱してきたので、比較して纏めてみようという回です。

hostPath

hostPath は Pod からマウントできる Volume のタイプの一つで、その Pod をホストしている node のファイルシステムを Pod からマウントするものです。
これによって、node のファイルシステム上にあるファイルを利用できるようにしたり、そのファイルシステム上で追加または上書きしたファイルを Pod の削除後も維持できるようにしたりできます。
使い道としては、ホストしている node 内にある Docker 関連のファイルが欲しいときのために /var/lib/docker/ディレクトリをマウントしたり、cAdviser(Google 開発の監視ツール)でメトリックを取るために/sysなどのディレクトリをマウントしたりといったものが挙げられています。
あまり書き込み関連のユースエースが見られませんが、それは後に開設するセキュリティ上の理由があります。
永続ボリュームのページでは「マルチノードクラスターでは動作しません」とありますが、マルチノードクラスター上でも hostPath を volume として設定した Pod を作成することはできます。
ただ、その Pod がスケジュールされる先の Node は Pod の nodeAffinity 等によって決められるため、意図しない Node に Pod が展開されて意図しないディレクトリにマウントしてしまう可能性があります。
回避方法としてはこのタイプの Volume を PersistentVolume として設定して、nodeAffinity を設定することが挙げられます。
そういった意味において「マルチノードでは(Pod の nodeAffinity で設定しない限りどの node のファイルシステムをマウントするか分からないので)動作しません」の意味の動作しないということなのかなと個人的には解釈しています。
ただ、hostPath を使うと Pod の securityContext の設定次第ではホストのファイルシステム上の高い権限がなければ弄れないディレクトリを弄ることもできてしまうため、なるべく使わない、最悪使う場合でもできる限り ReadOnly で利用することが推奨されています。
また、そのようなセキュリティ上の危険性や制約のため、hostPath を使うのは単一ノードでのテスト時のみに限り、できる限り後述の local Volume を使うことを推奨されています。
使い方としては、PersistetnVolume のリソースのマニフェストに書くこともできるのですが、Pod リソースのマニフェストspec.volumes下に直接下記のように書き込むことで Pod から直接マウントすることもできます。

hostPath:  
  path: /data  
  type: Directory  

local

local という Volume のタイプも、基本の振る舞いとしては hostPath と同じで、その Pod をホストしている node のファイルシステムを Pod からマウントできるようにします。
hostPath との違いは、このタイプの Volume は PersistentVolume というリソースで静的にしか作ることができないという点です。
hostPath は前述のとおり Pod のマニフェストの volume の設定に直接書き込んで使用することができますが、local の場合はまず PersistentVolume のリソースを設定し、その際に指定した storageClass を PersistentVolumeClaim で指定して、その PersistentVolumeClaim を Pod で指定することで使用できるようになります。
PersistentVolume で静的に local の Volume を設定する際にはパラメータとして nodeAffinity の設定が必須になっており、これによってこの PersistentVolume を使用して作成する Pod がスケジュールされる先の node を指定する必要があります。
これによって、この Volume で使用する node を固定でき(ただし nodeAffinity の条件に当てはまる node は 1 台だけになるようにする必要があります)、Pod によって予期しない異なる node の local のディレクトリを参照する可能性をなくすことができます。
基本的に Pod からその Pod がスケジュールされている node 上にあるディレクトリを参照する必要がある場合は local の Volume タイプを使用することが推奨されているようです。

apiVersion: v1  
kind: PersistentVolume  
metadata:  
  name: example-pv  
spec:  
  capacity:  
    storage: 100Gi  
  volumeMode: Filesystem  
  accessModes:  
  - ReadWriteOnce  
  persistentVolumeReclaimPolicy: Delete  
  storageClassName: local-storage  
  local:  
    path: /sample/html  
  nodeAffinity:  
    required:  
      nodeSelectorTerms:  
      - matchExpressions:  
        - key: node-role.kubernetes.io/control-plane  
          operator: Exists  
apiVersion: v1  
kind: PersistentVolumeClaim  
metadata:  
  name: myclaim  
spec:  
  accessModes:  
    - ReadWriteOnce  
  volumeMode: Filesystem  
  resources:  
    requests:  
      storage: 8Gi  
  storageClassName: example-pv  
apiVersion: v1  
kind: Pod  
metadata:  
  name: mypod  
spec:  
  containers:  
    - name: myfrontend  
      image: nginx  
      volumeMounts:  
      - mountPath: "/var/www/html"  
        name: mypd  
  volumes:  
    - name: mypd  
      persistentVolumeClaim:  
        claimName: myclaim  

emptyDir

emptyDir は、Pod の生成と同時に Node 上に作られる、中身が空っぽの Volume です。
他の二つの Volume のタイプとは違い、Pod が削除されると同時に削除されます。
なので、コンテナ何で生成されたデータを永続化する目的で使うことはできません。
これだけだと、特に何も Volume を設定しないときのコンテナ内の記憶領域と何ら変わらないように見えます。
一方で、Pod ではなくその内部のコンテナが再起動した場合でも Volume の中身は削除されないという特徴があり、それによって起動中保持していてほしいファイルをコンテナ再起動で失わないようにすることができます。
emptyDir に設定していない場合のデータは全部コンテナ内で管理されているので、コンテナが再起動するとそのタイミングで削除されてしまいます。
ユースケースとして挙げられている例としては、ディスクに一時的にデータを保存しながら行うソート処理のデータ保存場所や長い時間かかる処理のチェックポイントファイル置き場、また、Web ページにおいてコンテンツマネージャーとして動いているコンテナが Web サーバーから引っ張っていたデータの一時保存先が挙げられています。
また、パラメータの中にmediumというものがあり、これをMemoryに設定していると、メモリ上にこの Volume のファイルシステムを作って動作してくれるので、ファイルシステムとしての動作が高速になります。
ただし、こちらは node が再起動するとメモリが揮発してディレクトリが空になってしまうので、その点には注意が必要です。
このように、emptyDir はデータの永続化ではなく一時的なファイルの作成先、特にコンテナのリセットでもそれらのデータを失わないために使うもののようです。

emptyDir:  
  medium: ""  
  sizeLimit: 1Gi  

使い分け

これまでの説明をまとめると使い分けとしてはこのようになるかと思います。

  • Node マシン上のファイル取得をしたいのであれば local
  • コンテナ再起動でも消えてほしくない一時ファイルの保存先に emptyDir
  • hostPath は基本的には使わない、開発用途に限定

参考