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

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

【小ネタ】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 は基本的には使わない、開発用途に限定

参考

Kubernetes が普段使いの人のための CKAD 対策の話

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

tech-blog.cloud-config.jp

はじめに

Kubernetes を普段業務で使っている方で、業務で培った技術力の証明のために CKA(Certified Kubernetes Administrator)や CKAD(Certified Kubernetes Application Developer)といった資格を受験したいと考える方っていると思います。
実際普段から Kubernetes を触っている人がこれらの受験しようとすることは試験に合格しようとする上では Docker やコンテナ等の前提知識が備わっている点や試験内容である実際にコマンドラインで問題を解くという点において大きなアドバンテージになります。
しかし、一方で試験の形式や問題を受ける場面は普段の Kubernetes を用いた運用や開発を行う場面と大きく異なっている点もあります。
例えて言うならば、普段業務で Kubernetes を使うことはそれなりに力を使うので仕事をしているだけで筋肉が鍛え上げられていくのですが、試験では仕事で使うのとは違う筋肉を使うこともあるようなイメージです。
なので、試験を受ける場合はそちらで使うことになる筋肉も鍛えておかなければならないのですね。
今回は、Kubernetes 系資格の中でも特に CKAD において普段から Kubernetes を業務で使っている人向けに受験に向けた学習のコツのようなものを共有できればと思います。

できる限りコマンドでリソースを作成、操作する

普段 Kubernetes を用いて業務を行っているとき、アプリケーションを展開するときにはまずマニフェストファイルを作成してそれを kubectl apply -f <ファイル名> のようなコマンドで展開しているかと思います。
アプリケーションの kubernetes へのデプロイはできる限り宣言的な方法でコードで記述した方が分かりやすく、管理もしやすいためです。
一方で、この CKAD という試験は、試験の内容でも書いたとおり、この試験の制限時間は 2 時間でその間に 15~20 問の問題を解かなければなりません。そのため、1 問にかけられる時間は 6~8 分になります。
そんな中で、ある程度複数の概念が組み合わさったようなリソースを展開しなければならなくなった場合、いくら公式ドキュメントが参照できると言っても、マニフェストを作成して適用する方法だと以下のような手順を踏むことになります。

  1. Pod リソースを kubernetes ドキュメントで検索する
  2. Pod リソースのマニフェストのサンプルを vi にコピペしてくる
  3. サンプルマニフェストの余計な部分を削除し、Pod 名、イメージ名等を書き換える
  4. 個別の概念(livenessProbe とか env とか)について調べてマニフェストのサンプルを探す
  5. それぞれの概念のマニフェストのサンプルを vi にコピペしてくる
  6. 適用

この方法だとさすがに、1 問を解くのに時間がかかりすぎてしまいます。
なので、できる限り kubectl コマンドでリソースを作る方法を覚えるべきです。
例えば、イメージだけを指定した簡単な pod を作成したい場合は以下のようなコマンドだけで一発で Pod を作成できます。

kubectl run <podの名前> --image=<image名>  

このコマンドを覚えて使うことは、マニフェストファイルの内容を丸覚えしていたとしてもそれを vi で記述してkubectl applyしたり、あるいは忘れていた場合は kubernetes の公式ドキュメントで Pod リソースのページを開いてマニフェストのサンプルをコピペして余計な部分を消してリソース名とイメージ名を修正してそれからkubectl applyしたり・・・なんてするよりはずっと早いです。
このようなリソースの展開方法をドキュメントではImperative Commandsと呼んでいます。
これを習得しましょう。
一応この方法にもデメリットがあり、それはこのコマンドの引数だけでは設定できない部分もあることです。
例えば kubectl run コマンドだけでは作成する Pod に対する livenessProbe や ReadinessProbe の設定ができません。
他にも、ConfigMap リソースから env を引っ張ってくる等の設定もkubectl runコマンドだけでは行えません。
これらはマニフェストファイルにパラメータを設定することでしか設定することができません。
では Imperative Command は実際の試験で使えないのかというとそんなこともないです。
例えば、先程のそこそこ複雑な要件の Pod を作成する例で例えると、Imperative Command を使うと以下ただ、な手順になります。

  1. kubectl run <pod名> --image=<image名> <その他色々、オプションで設定できる限りの設定を行う> --out-put=client -o=yaml > <ローカルに作成するファイル名>のコマンドを実行する
  2. 個別の概念(livenessProbe とか env とか)について調べてマニフェストのサンプルを探す
  3. それぞれの概念のマニフェストのサンプルを vi にコピペしてくる
  4. 適用

1 番のコマンドについては追加で説明するべき点が 3 つあります。
一つは、--out-put=clientのオプションの部分で、これによってコマンドの実行を実際には行わず、コマンドを実行した場合どのような結果になるかだけを取得します。
二つ目は、-o=yaml の部分です。これは、コマンドの実行結果を yaml 形式で出力するものです。pod を作成するコマンドにこのオプションを実行すると、yaml 形式で表現された Pod の情報が出力されます
この出力をコピペしてマニフェストを生成すると、適用するとコマンドで生成するのと同じリソースが作成できます。
三つめは、リダイレクト>です。ご存知の方はご存知かと思うのですが、これで出力結果をそのままファイルに出力できます。
これらを行うことで、マニフェストを 1 から作る際の手順の 1~3 を省略することができ、時間も大きく節約できます。
戦術としては、Imperative Commands で設定できる限りの設定をオプションで行い、それで設定しきれない部分についてはマニフェストに吐き出して個別に調べて設定、適用するというような形になります。
Imperative Command で設定できる項目については、kubectl コマンドのリファレンスに載っているので、試験に備えてブックマークしておくのであればここはマークしておくべきかと思います。
CKAD は制限時間がタイトな都合上、Imperative Commands を使いこなせるようになることがほぼほぼ必須なところがあります。
ぜひ覚えましょう。

普段あまり使っていない分野の学習に集中する

普段 Kubernetes を触っている人が CKAD のような試験の学習をすることは、それらを全く触っていない状態から学習を始めることよりははるかに初期の知識において優位にあるかと思います。
学習の方法としては様々な他の人が書いているようにCKAD-excerciseや udemy コンテンツのKubernetes Certified Application Developer (CKAD) with Testsあたりが有名どころですが、これらのコンテンツの全体を同じ濃度でやるよりは、自分の弱点になっている分野を重点的に学習した方が有利です。(当たり前といえば当たり前)
普段触っていても忘れていたり知らなかったりする部分を学ぶために一周は全体を流しても、二週目は普段触っていなかったり各種サンプル問題を解いてみて分かりづらかった部分を重点的に学習するのが効果的です。
自分の場合は Security Context や Volume 周り、NetworkPolicy 周りが弱かったのでその周りを集中して学習しました。
また、2021 年の試験の更新で試験範囲に helm も加わったため、そこも重点的に学習しています。

コマンドラインでのファイル編集には慣れておく

普段業務でマニフェストを触る時、コマンドラインで扱える vi 以外で編集を行っている人も多いと思います。
ただ、CKA/CKAD 本番では試験を受けるためのブラウザのタブとドキュメント確認用のタブを一つ開くことだけを許されているので、マニフェストファイルなどのファイル編集になれておく必要があります。
自分の場合 vi を使うことにしたのですが、普段そこまで vi 操作になれていなかったので自分用のショートカット集的なものを作って学習中はちょくちょく読み直していました。
自作のよく使うショートカットは以下の 4 つでした。

's,'ed  
vi中でms,meコマンドを打ってマーク付けした間の行を削除(マークは目立つ形では表示されない)  
  
's,'ey  
vi中でms,meコマンドを打ってマーク付けした間の行をコピー(マークは目立つ形では表示されない)  
pキーでペースト  
  
u  
アンドゥ  
  
dd  
1行削除  

おわりに

ちなみにここまで対策を書いておいてなのですが、実は試験はまだ受けていません。来週あたりを予定しています。
こういった記事は合格してから書けよという話ですが、最近この辺りの話を周りの人とする機会ができたためそれに合わせて先に書いています。
ここまで書いておいてもし受験して落ちたりしたら、その時は笑ってやってください。

3/23 追記
その後、無事合格しました。

ついでにCKAも取得しました。

参考

AKS で NodePool の VM のサイズを変えるには

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

tech-blog.cloud-config.jp

はじめに

Azure Kubernetes Service こと AKS を運用していて、スペック不足や運用費節約の目的で NodePool で使用する VM のサイズを変更したくなることが時たまあるかと思います。
その際のやり方には実はひと手間必要だったりするので、同じようなことで詰まった他の人向けに共有できればと思います。

1. まずは変更先の NodePool を作成する

まず前提知識として、一度作成した NodePool の VM サイズを変更することはできません。
なので、NodePool で使用する VM サイズを変更したい場合、新しい NodePool を作成して古いものを削除するという手順が必要になります。
「ノードプールの追加」から、新しい NodePool の追加画面に移ります。
f:id:nam_yu_sql:20211221130930p:plain
追加画面では、まず「モード」をシステムにします。
この設定は NodePool に他にシステムモードが設定されているものがない場合は必要です。
AKS の NodePool には、「システムモード」と「ユーザーモード」があり、少なくとも一つの NodePool がシステムモードで稼働する必要があるためです。
システムモードの NodePool に含まれる Node は Kubernetes Cluster でいうところの「マスターノード」となり、Kubernetes Cluster の管理者的な役割を果たします。
そのため、少なくとも 1 台の Node がマスターノードである必要があり、AKS では NodePool 単位でマスターノードとする Node の集まりをシステムモードの NodePool として設定している形です。
f:id:nam_yu_sql:20211221130958p:plain
余談は置いておいて、あとは VM のサイズとスケール範囲を指定して NodePool を作成します。

2. 変更元の NodePool を削除

新しい NodePool が作成できたら、NodePool の一覧画面に戻り、古い方の NodePool を選択します。
f:id:nam_yu_sql:20211221131023p:plain
NodePool の概要画面に移動したら、その NodePool の削除を行います。
f:id:nam_yu_sql:20211221131102p:plain
この時、もし他の NodePool が全部ユーザーモードだったりそもそも他の NodePool が存在しなかった場合はこの削除ボタンがクリックできません。
他に NodePool があるのに削除できない!という時は他の NodePool のモードが原因である可能性があるので、確認してみましょう。
f:id:nam_yu_sql:20211221131111p:plainf:id:nam_yu_sql:20211221131114p:plain
また、余談として、ユーザーモードとシステムモードの違いとして、ノード数の範囲の最小数を 0 にできるかできないかという違いがあります。
システムモードの NodePool は、少なくとも 1 台は動いている必要があるので最小数を 0 にすることができませんが、ユーザーモードの NodePool は最低で 1 台も動いていなくても問題ないということなのですね。
という豆知識でした。

おわりに

今回は若干素直ではない AKS の NodePool の VM サイズの変え方について共有しました。
覚えておくこととしては、NodePool のうち一つはシステムモードである必要があること、NodePool の VM サイズそれ自体は変更できないというあたりでしょうか。
分かってしまえば難しいことではないので、同じことで困っている人が居たら助けになれば幸いです。

参考