Kubernetes接続トラブルシューティング:execとport-forwardを効果的に使う

Kubernetesの接続性や内部アプリケーションの問題を自信を持ってトラブルシューティングしましょう。このガイドでは、`kubectl exec`を使ってコンテナ内でコマンドを実行する方法と、`kubectl port-forward`を使ってローカルマシンからサービスに安全にアクセスする方法を実践的な例で紹介します。ネットワーク問題の診断、設定の確認、クラスター内のアプリケーションの動作を深く理解する方法を学びます。

Kubernetes接続トラブルシューティング:execとport-forwardを効果的に使う

Kubernetesサービスがタイムアウトした場合、通常は修正前に「接続はどこで途切れているのか?」という単純な質問に答える必要があります。kubectl execを使うと、クラスター内部、アプリケーションに近い場所からテストできます。kubectl port-forwardを使うと、Ingress、LoadBalancer、ファイアウォールルール、DNSレコードを変更せずに、1つのPodやサービスをラップトップに持ち帰ることができます。

これら2つのコマンドを組み合わせることで、推測を避けられます。アプリがリッスンしているか、クラスターDNSが機能しているか、Serviceが正しいPodを指しているか、問題がKubernetesネットワーキングにあるのかアプリケーション自体にあるのかをテストできます。

kubectl execを理解する

kubectl execコマンドを使用すると、Pod内の実行中のコンテナでコマンドを実行できます。これは、ログの確認、設定のチェック、アプリケーションが動作している場所で直接診断ツールを実行するのに非常に便利です。

本番環境では注意して使用してください。実際のワークロードコンテナ内でコマンドを実行しています。無害なenvcatcurlssは問題ありません。ライブPod内でパッケージをインストールしたり、ファイルを変更したり、高コストな診断を実行すると、元の問題が隠れたり、新しい問題が発生する可能性があります。

基本構文

kubectl execの基本的な構文は次のとおりです。

kubectl exec <pod-name> -- <command> [args...]
  • <pod-name>: コマンドを実行するPodの名前。
  • --: このセパレーターは重要です。kubectlフラグとコンテナ内で実行するコマンドを区別します。
  • <command>: コンテナ内で実行するコマンド(例:lscatping)。
  • [args...]: コマンドの引数。

インタラクティブシェルアクセス

kubectl execの最も一般的な使用法の1つは、コンテナ内でインタラクティブシェル(bashshなど)を取得することです。これにより、コンテナのファイルシステムを探索し、複数のコマンドを実行できます。

インタラクティブシェルを取得するには:

kubectl exec -it <pod-name> -- /bin/bash
  • -i(または--stdin): アタッチされていなくてもstdinを開いたままにします。
  • -t(または--tty): 疑似TTYを割り当てます。インタラクティブシェルセッションに必要です。

例: my-app-podというPodでbashシェルにアクセスする:

kubectl exec -it my-app-pod -- /bin/bash

内部に入ったら、標準のLinuxコマンドを使用できます。シェルを終了するには、exitと入力するか、Ctrl+Dを押します。/bin/bashがない場合は、/bin/shを試してください。多くの小さなイメージにはBashが含まれていません。

単一コマンドの実行

インタラクティブシェルなしで単一のコマンドを実行することもできます。これは、簡単な確認やスクリプト作成に便利です。

例: my-app-pod/appディレクトリ内のファイルを確認する:

kubectl exec my-app-pod -- ls /app

例: 設定ファイルconfig.yamlの内容を表示する:

kubectl exec my-app-pod -- cat /etc/my-app/config.yaml

Pod内のコンテナを指定する

Podに複数のコンテナがある場合は、-cフラグを使用してコマンドを実行するコンテナを指定する必要があります。

kubectl exec <pod-name> -c <container-name> -- <command>

例: multi-container-podsidecar-containerenvを実行する:

kubectl exec multi-container-pod -c sidecar-container -- env

kubectl port-forwardを理解する

kubectl port-forwardコマンドを使用すると、ローカルマシンからKubernetesクラスター内の特定のPodまたはサービスへの安全なトンネルを確立できます。これは、外部に公開されていないアプリケーションのデバッグ、データベースへのアクセス、内部APIのテストに非常に役立ちます。

これは本番トラフィックパスではありません。Kubernetes API接続を介したデバッグ用トンネルです。APIサーバー接続が切断されると、ローカルトンネルも切断されます。

基本構文

一般的な構文は次のとおりです。

kubectl port-forward <pod-name> <local-port>:<remote-port>
  • <pod-name>: 接続するPodの名前。
  • <local-port>: ローカルマシンで接続をリッスンするポート。
  • <remote-port>: 転送されたトラフィックを受信するPodのポート。

例: ローカルポート8080をmy-app-podのポート80に転送する:

kubectl port-forward my-app-pod 8080:80

このコマンドが実行されると、Webブラウザでhttp://localhost:8080にアクセスするか、ローカルマシンでcurlなどのツールを使用してアプリケーションにアクセスできます。

サービスへの転送

特定のPodではなく、Kubernetes Serviceにトラフィックを転送することもできます。kubectlは自動的にそのサービスをバックアップするPodを選択します。

kubectl port-forward service/<service-name> <local-port>:<service-port>

例: ローカルポート3000をmy-serviceサービスのポート80に転送する:

kubectl port-forward service/my-service 3000:80

サービスに転送する場合、kubectlは1つのバックアップPodを選択することに注意してください。レプリカが1つだけ壊れている場合、サービスレベルの転送では見逃す可能性があります。たとえば、3つのPodを持つDeploymentに、2つの正常なPodと1つの設定が悪いPodがあるとします。service/my-serviceに転送すると、正常なPodが選択され、サービスが正常に見える可能性があります。レプリカ固有の問題が疑われる場合は、正確なPod名に転送してください。

DeploymentまたはStatefulSetへの転送

同様に、DeploymentまたはStatefulSetに転送することもできます。kubectlは、指定されたリソースによって管理されるPodの1つを選択します。

kubectl port-forward deployment/<deployment-name> <local-port>:<container-port>
kubectl port-forward statefulset/<statefulset-name> <local-port>:<container-port>

特定のアドレスへのバインド

デフォルトでは、port-forwardlocalhostにバインドされます。--addressフラグを使用して、異なるローカルアドレスを指定できます。

kubectl port-forward --address 127.0.0.1 <pod-name> <local-port>:<remote-port>

複数ポートの転送

kubectl port-forwardは、複数のポートを同時に転送できます。

kubectl port-forward my-app-pod 8080:80 9090:90

このコマンドは、ローカルポート8080をPodポート80に、ローカルポート9090をPodポート90に転送します。

一般的なトラブルシューティングシナリオと解決策

シナリオ1: アプリケーションが応答しないが、Podは正常に見える。

  • 問題: Podは実行中だが、そのサービスへのリクエストが失敗またはタイムアウトしている。アプリケーションに内部設定の問題があるか、スタックしている可能性がある。
  • kubectl execを使った解決策:
    1. Podにインタラクティブシェルを取得する: kubectl exec -it <pod-name> -- /bin/bash
    2. シェル内で、アプリケーションログを確認する(例: tail -f /var/log/myapp.log)。
    3. アプリケーションの内部設定ファイルを確認する。
    4. Pod内から他のサービスへのネットワーク接続をpingcurl(インストールされている場合)で確認する。
  • kubectl port-forwardを使った解決策:
    1. アプリケーションのリッスンポートにポートを転送する: kubectl port-forward <pod-name> 8080:<app-port>
    2. http://localhost:8080を介してローカルでアプリケーションにアクセスしてみる。これにより、問題がKubernetesのサービスディスカバリーやIngressにあるのか、アプリケーション自体が応答していないのかを判断できる。

ポートフォワードが機能するが、通常のサービスURLが失敗する場合は、Serviceセレクターとエンドポイントを確認する:

kubectl get service <service-name> -n <namespace> -o yaml
kubectl get endpoints <service-name> -n <namespace>
kubectl get pods -n <namespace> --show-labels

非常に一般的な障害はラベルの不一致です。Podは正常で、サービスは存在しますが、セレクターがPodラベルと一致しないため、Serviceにエンドポイントがありません。

シナリオ2: Podで実行されているデータベースをデバッグする必要がある。

  • 問題: ローカルのデータベースクライアントをKubernetes Pod内で実行されているデータベースに接続して、データを検査したりクエリを実行したりする必要がある。
  • kubectl port-forwardを使った解決策:
    1. データベースを実行しているPodとそのポートを特定する(例: mysql-pod、ポート3306)。
    2. ローカルポートをデータベースポートに転送する: kubectl port-forward mysql-pod 3306:3306
    3. 適切なデータベース認証情報を使用して、ローカルのデータベースクライアントをlocalhost:3306に接続するように設定する。

シナリオ3: Pod内のDNS解決の問題を診断する。

  • 問題: Pod内のアプリケーションがサービス名で他のサービスに到達できず、DNSの問題が示唆される。
  • kubectl execを使った解決策:
    1. Podにインタラクティブシェルを取得する: kubectl exec -it <pod-name> -- /bin/bash
    2. シェル内で、既知のサービス名を解決してみる: nslookup <service-name>.<namespace>.svc.cluster.local または dig <service-name>.<namespace>.svc.cluster.local
    3. /etc/resolv.confの内容を確認して、Pod内のクラスターDNS設定が正しいことを確認する。

イメージにnslookupdigcurlが含まれていない場合は、同じ名前空間で一時的なデバッグPodを使用する:

kubectl run net-debug -n <namespace> --rm -it --image=curlimages/curl -- sh

そこから、アプリケーションが使用しているのと同じサービス名をテストします。これにより、「アプリケーションイメージにツールがない」と「クラスターDNSが壊れている」を区別できます。

シナリオ4: ポートフォワードは接続するが、すぐに閉じる。

  • 問題: kubectl port-forwardが転送メッセージを表示するが、ブラウザを開いたりcurlを実行したりすると接続が閉じる。
  • 考えられる原因: ターゲットプロセスがリモートポートでリッスンしていない、アプリケーションがコンテナ内の127.0.0.1にのみバインドしている、またはトンネルが開いている間にPodが再起動する。
  • 確認:
    kubectl exec <pod-name> -n <namespace> -- ss -lntp
    kubectl get pod <pod-name> -n <namespace> -w
    kubectl logs <pod-name> -n <namespace> --tail=100
    

プロセスがポート8080でリッスンしているが、80に転送している場合、トンネル自体は問題ありません。ターゲットが間違っています。テスト中にPodが再起動する場合は、ネットワーキングを追いかける前に再起動の原因を修正してください。

シナリオ5: サービスがあるPodからは機能するが、別のPodからは機能しない。

  • 問題: バックエンドはRedisに到達できるが、別の名前空間のワーカーPodは到達できない。
  • execを使った確認:
    kubectl exec <source-pod> -n <source-namespace> -- curl -v http://<service>.<target-namespace>.svc.cluster.local:<port>
    kubectl exec <source-pod> -n <source-namespace> -- cat /etc/resolv.conf
    

DNSは解決するがTCPが失敗する場合は、NetworkPolicy、サービスエンドポイント、およびターゲットアプリケーションがその名前空間からのトラフィックを受け入れているかどうかを確認します。DNSが解決しない場合は、アプリケーションを非難する前に完全修飾サービス名をテストします。

シンプルな接続ラダー

リクエストが失敗した場合、最も近いものから最も遠いものへテストします:

  1. Pod内で、ローカルアプリケーションプロセスをテストする:
    kubectl exec <pod> -n <namespace> -- curl -v http://127.0.0.1:<port>/health
    
  2. 同じ名前空間の別のPodから、Serviceをテストする:
    kubectl run curl-test -n <namespace> --rm -it --image=curlimages/curl -- curl -v http://<service>:<port>/health
    
  3. ラップトップから、ポートフォワード経由でテストする:
    kubectl port-forward service/<service> -n <namespace> 8080:<service-port>
    curl -v http://localhost:8080/health
    
  4. これらが成功した後にのみ、Ingress、ロードバランサー、DNS、ファイアウォールルールに進みます。

この順序は時間を節約します。各ステップが1つのレイヤーを証明するからです。Pod内のlocalhostが失敗した場合、Ingressは無関係です。Podローカルが成功したがServiceアクセスが失敗した場合、アプリケーションはおそらく正常で、Kubernetesサービスディスカバリーに注意が必要です。

もう1つの習慣として、デバッグ中は名前空間を明示的に指定することが役立ちます。多くの混乱は、壊れたワークロードが別の場所にあるのに、デフォルトの名前空間でkubectl execを実行することから発生します。セッションのコンテキストで名前空間を設定するか、すべてのコマンドに-n <namespace>を追加してください。余分なタイピングは、間違ったPodをテストするよりは安上がりです。

また、障害を証明した正確なコマンドを保存してください。次のオンコール担当者は、あなたのコンテキストを記憶から再構築することなく、それを再実行できます。

ベストプラクティスとヒント

  • port-forwardを実行し続ける: kubectl port-forwardはフォアグラウンドで実行されます。ターミナルウィンドウを開いたままにする必要があります。バックグラウンドで実行するには、nohupscreen/tmuxなどのツールを使用できます。
  • デバッグ時は特定のPodを使用する: サービスに転送するのは便利ですが、特定のインスタンスの問題を特定するには、名前を使用して特定のPodに転送する方が効果的です。
  • セキュリティ: 公開するポートに注意してください。特に必要な場合を除き、機密ポートの転送は避け、ローカルマシンを保護してください。
  • リソース使用量: kubectl execはリソースを消費する可能性があります。特に本番クラスターでは、慎重に使用してください。
  • 権限: kubectlコンテキストに、Podでコマンドを実行したりポートを転送したりするために必要な権限があることを確認してください。

修正後に書き留めること

優れたデバッグは証跡を残します。失敗したURL、ソースPod、ターゲットサービス、名前空間、問題を再現した正確なコマンド、および失敗したレイヤーを記録します。「接続の問題」では次回に役立ちません。「ラベル名変更後にServiceセレクターがPodと一致しなかった」は修正可能なパターンです。