kubectlプラグインの開発と運用:安全なBashスクリプトとsystemdによる自動化

Tech

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

kubectlプラグインの開発と運用:安全なBashスクリプトとsystemdによる自動化

kubectlプラグインは、KubernetesのCLIツールkubectlの機能を拡張するための強力な手段です。独自のサブコマンドを追加することで、日常的な操作を効率化し、複雑なワークフローを自動化できます。本記事では、Bashスクリプトを用いたkubectlプラグインの開発方法、セキュリティを考慮した安全な実装、そしてsystemdを活用した定期実行と運用について解説します。

1. 要件と前提

本記事の手順を実行するには、以下の環境と知識を前提とします。

  • Kubernetes環境: kubectlがインストールされ、クラスターにアクセス可能な環境。

  • Bashシェル: スクリプトの実行環境。

  • jq: JSONデータのパースと操作のためのツール。バージョン1.6以上を推奨。

  • curl: HTTP/HTTPSリクエストを行うためのツール。バージョン7.x以上を推奨。

  • systemd: Linuxディストリビューションに広く採用されているシステムおよびサービスマネージャー。

  • セキュリティ意識: root権限の乱用を避け、最小権限の原則に基づいた運用を心がける。

2. 実装

kubectlプラグインは、その名前がkubectl-で始まる実行ファイルとしてPATH環境変数内のディレクトリに配置されることで認識されます。例えば、kubectl-mypluginという名前のスクリプトを作成し、PATHが通ったディレクトリに置けば、kubectl my-pluginとして実行できるようになります[1]。

ここでは、Kubernetes APIから特定の情報を取得し、整形して表示するシンプルなプラグインkubectl-get-node-infoを例に開発を進めます。

2.1. プラグインの基本構造と安全なBashスクリプト

Bashスクリプトでkubectlプラグインを開発する際は、セキュリティと信頼性を高めるために以下のプラクティスを導入します。

  • set -euo pipefail:

    • e: コマンドが失敗した場合、即座にスクリプトを終了します。

    • u: 未定義の変数を使用した場合、エラーとします。

    • o pipefail: パイプライン中のコマンドが一つでも失敗した場合、パイプライン全体のステータスコードを非ゼロにします。

  • trap: スクリプトの終了時に一時ファイルを確実にクリーンアップします。

  • mktemp -d: 安全な一時ディレクトリを作成し、名前の衝突や権限の問題を防ぎます。

#!/usr/bin/env bash

#


# kubectl-get-node-info: Kubernetesノード情報を取得するkubectlプラグイン

#


# 入力: なし (またはオプションでノード名)


# 出力: ノード名、CPU/メモリ使用率などの情報


# 前提: kubectl, jq, curlがインストールされ、kubectl proxyが実行中であること


# 計算量: Kubernetes APIへの1回のHTTPリクエスト、jqによるJSON処理


# メモリ: 少ない (一時ファイルは一時ディレクトリに作成され、スクリプト終了時に削除)

set -euo pipefail

# 一時ディレクトリの作成と終了時のクリーンアップ


# mktemp -dは安全な一時ディレクトリを作成する。


# trapコマンドはEXITシグナル(スクリプト終了時)に指定されたコマンドを実行し、


# 一時ディレクトリとその内容を確実に削除する。これは冪等性にも貢献する。

TMP_DIR=$(mktemp -d -t k8s-plugin-XXXXXXXXXX)
trap 'rm -rf "$TMP_DIR"' EXIT

# kubectl proxyが動いていることを想定し、localhost経由でAPIにアクセス


# プロダクション環境ではサービスアカウントトークンなど別の認証方法を検討する。

KUBE_API_SERVER="http://localhost:8001" # kubectl proxyのデフォルトポート

# APIパス

NODES_API_PATH="/api/v1/nodes"

# オプション引数としてノード名を受け取る

TARGET_NODE=""
if [[ "$#" -ge 1 ]]; then
    TARGET_NODE="$1"
fi

echo "--- Fetching Kubernetes Node Information ---"

# curlでKubernetes APIを呼び出す例


# --retry: 最大5回のリトライ


# --retry-delay: 最初のリトライまでの遅延秒数 (1秒)


# --retry-max-time: リトライ試行の最大時間 (60秒)


# -s: サイレントモード (プログレスメーター非表示)


# -S: エラー表示


# --tlsv1.2: TLSv1.2の使用を強制 (セキュリティ強化)


# --fail: HTTPエラー (4xx, 5xx) の場合、エラーとして終了


# --show-error: エラー発生時に詳細を表示

RESPONSE=$(curl -sS --fail --show-error --tlsv1.2 \
                --retry 5 --retry-delay 1 --retry-max-time 60 \
                "${KUBE_API_SERVER}${NODES_API_PATH}" \
                -H "Accept: application/json")

# jqでJSONレスポンスを処理し、ノード情報を抽出


# jqは強力なJSON処理ツール。ここではノードリストから必要な情報を整形して出力する。


# -r: raw出力 (引用符をつけない)

if [[ -z "$TARGET_NODE" ]]; then
    echo "$RESPONSE" | jq -r '
        .items[] | {
            name: .metadata.name,
            os_image: .status.nodeInfo.osImage,
            kernel_version: .status.nodeInfo.kernelVersion,
            cri_version: .status.nodeInfo.containerRuntimeVersion,
            kubelet_version: .status.nodeInfo.kubeletVersion,
            allocatable_cpu: .status.allocatable.cpu,
            allocatable_memory: .status.allocatable.memory,
            capacity_cpu: .status.capacity.cpu,
            capacity_memory: .status.capacity.memory
        } | to_entries | map("\(.key): \(.value)") | .[]
    ' | sed 's/^/  /' # インデントを追加
else
    echo "$RESPONSE" | jq -r --arg node_name "$TARGET_NODE" '
        .items[] | select(.metadata.name == $node_name) | {
            name: .metadata.name,
            os_image: .status.nodeInfo.osImage,
            kernel_version: .status.nodeInfo.kernelVersion,
            cri_version: .status.nodeInfo.containerRuntimeVersion,
            kubelet_version: .status.nodeInfo.kubeletVersion,
            allocatable_cpu: .status.allocatable.cpu,
            allocatable_memory: .status.allocatable.memory,
            capacity_cpu: .status.capacity.cpu,
            capacity_memory: .status.capacity.memory
        } | to_entries | map("\(.key): \(.value)") | .[]
    ' | sed 's/^/  /' # インデントを追加
fi

echo "--- Node Information Retrieval Complete ---"

2.2. プラグインの処理フロー

上記スクリプトの処理フローをMermaidで図示します。

graph TD
    A["ユーザー実行"] --> B{"kubectl get-node-info"};
    B --> C["スクリプト起動: kubectl-get-node-info"];
    C --> D{"一時ディレクトリ作成"};
    D -- エラーハンドリング --> E["API呼び出し with curl"];
    E -- JSONデータ --> F{"jqでデータ処理"};
    F --> G["結果出力"];
    G --> H["一時ディレクトリクリーンアップ"];

3. 検証

作成したプラグインをテストし、正しく動作することを確認します。

  1. 保存と実行権限の付与: 上記のスクリプトをkubectl-get-node-infoという名前で保存し、実行権限を与えます。

    chmod +x kubectl-get-node-info
    
  2. PATHへの配置: このスクリプトをPATHが通っているディレクトリ(例: /usr/local/bin~/bin)に配置します。ユーザー固有のプラグインであれば、~/.local/binなどが推奨されます[1]。

    mkdir -p ~/.local/bin
    mv kubectl-get-node-info ~/.local/bin/
    export PATH=$PATH:~/.local/bin # 必要に応じて~/.bashrcや~/.zshrcに追加
    
  3. kubectl proxyの起動: ローカルでKubernetes APIにアクセスするために、kubectl proxyを別のターミナルで起動します。

    kubectl proxy --port=8001 &
    

    &でバックグラウンド実行)

  4. プラグインの実行:

    kubectl get-node-info
    
    # 特定のノードを指定する場合
    
    
    # kubectl get-node-info my-node-name
    

    ノードの情報が整形されて表示されれば成功です。エラーが発生した場合は、kubectl proxyが起動しているか、curljqが正しくインストールされているか確認してください。

4. 運用

kubectlプラグインは手動で実行するだけでなく、systemd unit/timerを利用して定期的に実行し、特定の情報を収集・監視する用途にも利用できます。ここでは、非特権ユーザーでプラグインを定期実行する方法を解説します。

4.1. systemdによる定期実行

systemdは、サービス(unit)とタイマー(timer)を組み合わせて使用することで、定期的な処理を柔軟にスケジュールできます。

4.1.1. systemdサービスユニットファイルの作成 (/etc/systemd/system/k8s-node-info-collector.service)

まず、プラグインを実行するサービスユニットを作成します。ここでは、k8s-node-info-collector.serviceという名前でサービスを定義します。

# /etc/systemd/system/k8s-node-info-collector.service

#


# Description: Kubernetesノード情報を定期的に収集するサービス

#


# [Unit]セクション: サービスの説明と、他のユニットとの依存関係を定義。


#   After: network-online.target は、ネットワークが利用可能になってからサービスを起動することを示す。

[Unit]
Description=Kubernetes Node Information Collector Plugin
After=network-online.target

# [Service]セクション: サービス自体の実行方法を定義。


#   Type=oneshot: サービスが単一のコマンドを実行し、完了したら終了することを示す。


#   User: サービスを実行するユーザーを指定。root権限を避けるため、専用の非特権ユーザー(例: k8s-collector)を作成することを推奨。


#         ここでは例として "your_user" を使用しているが、実際の運用ではより制限されたユーザーが良い。


#   Group: サービスを実行するグループを指定。


#   ExecStart: 実行するコマンド。フルパスで指定する。


#   WorkingDirectory: コマンドが実行される作業ディレクトリ。


#   Environment: 環境変数を設定。ここではPATHを設定し、kubectlコマンドが適切に見つかるようにする。


#   StandardOutput/StandardError: 標準出力と標準エラー出力の処理方法。journalに送ることで、journalctlでログを確認できる。


#   ProtectHome=true: ホームディレクトリへの書き込みを禁止し、セキュリティを強化。


#   ReadOnlyPaths=/etc/: /etcディレクトリへの書き込みを禁止。


#   ReadWritePaths=/tmp/: /tmpディレクトリへの書き込みを許可(一時ファイル用)。

[Service]
Type=oneshot
User=your_user # 実行する非特権ユーザーを指定。例: k8s-collector
Group=your_group # 実行するグループを指定。例: k8s-collector
ExecStart=/home/your_user/.local/bin/kubectl-get-node-info
WorkingDirectory=/home/your_user
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/your_user/.local/bin"
StandardOutput=journal
StandardError=journal

# セキュリティ強化のための設定例 (必要に応じて調整)

#ProtectHome=true

#ReadOnlyPaths=/etc/

#ReadWritePaths=/tmp/

# [Install]セクション: サービスがどのように有効化されるかを定義。


#   WantedBy: このサービスが属するターゲットを指定。通常は multi-user.target (システムの通常起動時)

[Install]
WantedBy=multi-user.target

root権限の扱いと権限分離: systemdサービスは、原則として非特権ユーザーで実行すべきです。User=およびGroup=ディレクティブを使用し、root以外の専用ユーザー(例: k8s-collector)を作成して設定します。これにより、プラグインに脆弱性があった場合でもシステム全体への影響を最小限に抑えられます。root権限で実行すると、プラグインが誤ってシステムファイルを変更したり、悪意のあるコードが実行された場合に甚大な被害につながる可能性があります。また、ProtectHome=trueReadOnlyPaths=, ReadWritePaths=などのディレクティブを適切に設定することで、さらにサンドボックス化を進め、攻撃対象領域を減らすことができます。

4.1.2. systemdタイマーユニットファイルの作成 (/etc/systemd/system/k8s-node-info-collector.timer)

次に、上記サービスを定期的に実行するためのタイマーユニットを作成します。

# /etc/systemd/system/k8s-node-info-collector.timer

#


# Description: Kubernetesノード情報収集サービスを定期実行するタイマー

#


# [Unit]セクション: タイマーの説明。

[Unit]
Description=Run Kubernetes Node Information Collector Plugin every 15 minutes

# [Timer]セクション: タイマーのスケジュールを定義。


#   OnCalendar: 特定の日時や間隔でタイマーを起動する。ここでは15分ごとに起動。


#   AccuracySec: イベントの精度。ここでは1分以内に起動するよう指定。これにより、システム負荷を軽減しつつ、おおよそOnCalendarのタイミングで実行される。


#   Persistent=true: タイマーが最後に起動した日時を記録し、システムが停止していた期間にスキップされたイベントを、起動後に実行しようとする。

[Timer]
OnCalendar=*:0/15:00 # 毎時0分、15分、30分、45分に実行
AccuracySec=1min    # 最大1分のずれを許容
Persistent=true     # 起動時に実行できなかったスケジュールを後で実行

# [Install]セクション: タイマーがどのように有効化されるかを定義。


#   WantedBy: timers.target は、システム起動時にすべてのタイマーユニットを有効にする。

[Install]
WantedBy=timers.target

4.1.3. systemdユニットの有効化と起動

サービスとタイマーを配置したら、systemdにそれらを認識させ、有効化して起動します。

# systemd設定をリロード

sudo systemctl daemon-reload

# タイマーを有効化し、即座に起動


# タイマーを有効化すると、関連するサービスも必要に応じて有効化される。

sudo systemctl enable --now k8s-node-info-collector.timer

# タイマーとサービスの状態を確認

systemctl status k8s-node-info-collector.timer
systemctl status k8s-node-info-collector.service

# サービスが実行されたことをログで確認

journalctl -u k8s-node-info-collector.service --since "1 hour ago"

これにより、k8s-node-info-collector.servicek8s-node-info-collector.timerによって15分ごとに自動実行されるようになります。

5. トラブルシュート

プラグインやsystemdの運用中に問題が発生した場合の一般的なトラブルシュート方法です。

  • ログの確認: systemdによって実行されるスクリプトの出力はjournaldに送られます。

    journalctl -u k8s-node-info-collector.service --full --pager-end
    

    エラーメッセージやスクリプトの標準出力/エラー出力が確認できます。

  • 権限の問題: User=ディレクティブで指定したユーザーが、プラグインや必要なファイル(例: kubeconfigファイル、kubectlバイナリ)への読み取り・実行権限を持っているか確認します。

  • PATHの問題: systemdサービス内でのPATH環境変数は、対話型シェルとは異なる場合があります。Environment="PATH=..."で明示的にPATHを設定するか、コマンドのフルパスを指定してください。

  • kubectl proxyの状態: プラグインがkubectl proxyに依存している場合、kubectl proxyが正常に動作しているか、またはAPIアクセスに別の認証方法(サービスアカウントトークンなど)が正しく設定されているか確認します。

  • jqのデバッグ: jqの構文エラーは分かりにくいことがあります。jq -r . のように単純なパスでJSONが正しくパースできるか確認し、徐々に複雑なクエリを追加していきます。

6. まとめ

kubectlプラグインをBashスクリプトで開発し、安全に運用するための具体的な手法を解説しました。set -euo pipefailtrapmktemp -dを用いた堅牢なスクリプト記述、curljqによるAPI連携、そしてsystemd unit/timerによる定期実行とログ管理について学びました。特に、非特権ユーザーでのsystemdサービス実行による権限分離は、セキュリティのベストプラクティスとして非常に重要です。これらの技術を組み合わせることで、Kubernetes環境での作業をより効率的かつ安全に進めることができるでしょう。


[1] Kubernetes公式ドキュメント, “Extend kubectl with Plugins”, Kubernetes Authors, 最終更新日: 2024年1月10日. URL: https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/ [2] GNU Bash Manual, “The Set Builtin”, Free Software Foundation, 公開日: 2023年12月1日. URL: https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html [3] curl man page, “OPTIONS”, Daniel Stenberg, 公開日: 2024年5月15日. URL: https://curl.se/docs/manpage.html [4] jq GitHubリポジトリ, “Command-line JSON processor”, stedolan, 最終更新日: 2024年4月20日. URL: https://stedolan.github.io/jq/ [5] systemd.unit man page, “UNIT FILE CONFIGURATION”, Lennart Poettering, 公開日: 2024年3月1日. URL: https://www.freedesktop.org/software/systemd/man/systemd.unit.html

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました