これは自分のスキル不足が原因だと思っているのですが、Kubernetes や OpenShift といったコンテナクラスタ環境を使ったウェブアプリケーションのデプロイではトラブルシューティングに時間を費やすことが珍しくありません。過去に成功したのと同じようなパターンでデプロイすることばかりではなく、ストレージ含めた新しいチャレンジをすることが多く、一発で成功することはあまりありません。
この「トラブルシューティング」は、現象としては「デプロイしたが想定していた URL でウェブアプリが動いていない」ことで分かるのですが、この原因は(期待通りに動いていない箇所が)様々です。k8s の用語でいうと Pod, Deployment, Service, Ingress, ... といった構成要素があり、まずはこの中のどこで問題が発生しているのかを見極め、その上でその箇所で発生している問題の原因を特定して解決していく必要があります。
この「どの部分で問題が発生しているか」を特定する作業において、"port-forward" と呼ばれる機能に助けられています。この port-forward 機能について自分へのメモ目的も含めてまとめておきます。
【コンテナクラスタのトラブルシューティング】
k8s を使ったウェブアプリケーションのデプロイ作業は手動であったり(半)自動化されていたりしますが、リソースと呼ばれる以下のようなパーツの単位で作成され、これらが組み合わされて1つのウェブアプリケーションとなります(ウェブアプリケーションがうまく動かない場合は、このいずれか(または複数)が想定していたように動いていない、ということになります):
・デプロイメント(Deployment)
- アプリケーションイメージを1つ以上インスタンス化し、クラスタ内で稼働しているもの
・ポッド(Pod)
- インスタンス化されたアプリケーションイメージ1つ1つを指すもの
- デプロイメントを作成した場合はデプロイメントに含まれる
・サービス(Service)
- コンテナ内で稼働しているポッドやデプロイメントを外部公開方法を定義するもの
・イングレス(Ingress)
- 複数のサービスを管理し、外部からのリクエストに対してロードバランサーとなるもの
コンテナクラスタ環境にウェブアプリケーションが正しくデプロイされた場合、ユーザーはアプリケーションの URL にアクセスすると、まずイングレスがそのリクエストを受け取って正しいサービスに転送し、サービスを経由してアプリケーションインスタンスであるポッド(デプロイメント)にアクセスします(イングレス→サービス→ポッド)。そしてそのリクエストに対してポッドが返したレスポンスは逆の順序(ポッド→サービス→イングレス)を通って返される、ということになります。
「デプロイしたアプリケーションが期待していたように動かない」というのは、この中のどこかが期待していたように動いていなくて、全体として「動かない」ように見えていることになります。したがってトラブルシューティングにおいて、まずはどこで問題が発生しているのかを特定する必要があります。
【port-forward 機能】
kubectl や oc といった CLI ツールの機能である port-forwarding を使うと、ポート番号を使ったフォワーディング機能によりイングレスを経由せずにコンテナクラスタ内に定義されているサービスやポッドに直接アクセスできるようになります。
例えばイングレスを経由せずにサービスにアクセスすると期待していた挙動になる(期待していた結果が返ってくる)のであればイングレスやその設定に問題がある可能性が高いことが考えられ、サービスにアクセスしても動かないがポッドに直接アクセスすれば期待していた挙動になるのであればサービスやその設定に問題がある可能性が高いことが考えられます(それでも動かない場合はポッドに問題があると考えられます)。 このような障害発生個所の見極めにおいては port-forwarding が便利に使えることになります。
【port-forward 機能を使ってみる】
試しにわざと正しく動かないことがわかっている以下のようなマニフェストファイル(deployment.yaml)を使ってアプリケーションをデプロイし、port-forward で確認する、ということをやってみます:
ちなみに上記マニフェストの正しい記述は下から5行目の Route の spec.port.targetPort の値が 3000 になっているものです(今回はわざと 4000 を指定しています)。
なお以下の作業は IBM Cloud の Redhat OpenShift コンテナクラスタ環境を使って確認しました。なので Ingress 部分の定義は apiVersion が "route.openshift.io/v1" の Route というリソースを使って定義しています。またこの Route 内の spec.host の値には "guestbook.(アサインされた ingress サブドメイン)" と記載していますが、実行前にこの()内の部分を OpenShift 画面に表示されているサブドメイン名に置き換えて保存しておいてください:
oc コマンドでログインし、このマニフェストファイルをデプロイ(oc apply)して、その結果を確認(oc get all)します:
"oc get all" の結果は上からポッド、サービス、デプロイメント、レプリカセット、そしてイングレスになっています。ポッドのステータスは Running でちゃんと動いていて、サービスも ClusterIP を使って正しく公開され、イングレスもホスト名が割り当てられています。 この結果だけを見ると正しく動いてそうです。
でも実際にブラウザでイングレスの HOST 名に表示されているサーバーにアクセスするとこのように表示されます(知らない人もいるので補足すると、これは期待していたアプリ画面ではなく、アクセスできなかった場合のエラー画面です):
エラー無くデプロイできたけどアプリにアクセスできない、、 こんな時に port-forward の出番です。
まずは(上述の結果で Pod が Running になってるので可能性は低そうですが)ポッドのレベルで正しく動いているかどうかを確認するため、port-forwarding でポッドに直結して動作を確認してみます。
ポッドに対して port-forwarding するには kubectl(または oc )コマンドで以下のように実行します:
今回の例だと(上述の結果より)ポッド名は "guestbook-svc" だとわかります。また実行したマニフェストファイルより、このサービスは 3000 番ポートで動いているはずです(サービスの spec.ports.targetPort の値)。このポッドにホストの(何番でもいいのですが) 8000 番ポートからフォワーディングして接続させてみます。というわけで、今回のケースであれば以下のコマンドを実行します:
コマンド実行後、ウェブブラウザから http://localhost:8000/ にアクセスしてみます:
今回は期待通りの画面が表示されました。ということは「ポッドは正しく動いている」ことが推測できました。Ctrl+C で port-forwarding を解除します。
継はサービスのレベルで正しく動いているかどうかを確認してみます。イングレスを経由せずにサービスにアクセスするため、port-forwarding でサービスに直結して動作を確認してみます。この場合は以下のようなコマンドを実行します:
今回の例だと(上述の結果より)サービス名は "guestbook-59b85f9654-22wh5" だとわかります。また実行したマニフェストファイルより、このポッド(デプロイメント)は 3000 番ポートで動いているはずです(サービスの spec.ports.port の値)。このポッドにホストの(何番でもいいのですが) 9000 番ポートからフォワーディングして接続させてみます。というわけで、今回のケースであれば以下のコマンドを実行します:
コマンド実行後、ウェブブラウザから http://localhost:9000/ にアクセスしてみます:
今回は期待通りの画面が表示されました。ということは「サービスも正しく動いている」ことが推測できました。Ctrl+C で port-forwarding を解除します。
これらの結果から、
・ポッド(デプロイメント)は正しく動いていそう
・サービスも正しく動いていそう
・ということはイングレスの設定が間違っていそう?
という切り分けをすることができました。実際、マニフェストの Route 内をわざと間違った内容に弄ったことが分かっているので、正しい切り分けができていることになります。
このケースであればイングレスの設定内容を疑ってみることになります。試しにイングレスの設定名称を指定して describe するとこのようになりました(kubectl の場合は "kubectl describe ingress guestbook-route"):
接続先である Endpoints が空になっていて何も表示されていません。ということは設定内容になんらかの誤りがあってサービスに接続できていないのでは・・・ と考えることができます。実際、今回のマニフェストでは targetPort を 3000 と指定すべきところを 4000 と指定していたため接続できていませんでした。仮に 4000 を 3000 に変更して再度適用した上で接続を試みた所、今度は正しく接続できるようになりました:
現実問題として、イングレス部分はポッドやサービスとは異なり Kubernetes からはサードパーティ製品となるので色々な実装があり、トラブルシューティングも実装ごとの方法が考えられるため比較的難しいトラブルシューティングとはなります。それでも「どの部分に設定ミスがあったか」を特定することで解決はしやすくなりますし、その特定において port-forwarding は有効な切り分け方であると思っています。
この「トラブルシューティング」は、現象としては「デプロイしたが想定していた URL でウェブアプリが動いていない」ことで分かるのですが、この原因は(期待通りに動いていない箇所が)様々です。k8s の用語でいうと Pod, Deployment, Service, Ingress, ... といった構成要素があり、まずはこの中のどこで問題が発生しているのかを見極め、その上でその箇所で発生している問題の原因を特定して解決していく必要があります。
この「どの部分で問題が発生しているか」を特定する作業において、"port-forward" と呼ばれる機能に助けられています。この port-forward 機能について自分へのメモ目的も含めてまとめておきます。
【コンテナクラスタのトラブルシューティング】
k8s を使ったウェブアプリケーションのデプロイ作業は手動であったり(半)自動化されていたりしますが、リソースと呼ばれる以下のようなパーツの単位で作成され、これらが組み合わされて1つのウェブアプリケーションとなります(ウェブアプリケーションがうまく動かない場合は、このいずれか(または複数)が想定していたように動いていない、ということになります):
・デプロイメント(Deployment)
- アプリケーションイメージを1つ以上インスタンス化し、クラスタ内で稼働しているもの
・ポッド(Pod)
- インスタンス化されたアプリケーションイメージ1つ1つを指すもの
- デプロイメントを作成した場合はデプロイメントに含まれる
・サービス(Service)
- コンテナ内で稼働しているポッドやデプロイメントを外部公開方法を定義するもの
・イングレス(Ingress)
- 複数のサービスを管理し、外部からのリクエストに対してロードバランサーとなるもの
コンテナクラスタ環境にウェブアプリケーションが正しくデプロイされた場合、ユーザーはアプリケーションの URL にアクセスすると、まずイングレスがそのリクエストを受け取って正しいサービスに転送し、サービスを経由してアプリケーションインスタンスであるポッド(デプロイメント)にアクセスします(イングレス→サービス→ポッド)。そしてそのリクエストに対してポッドが返したレスポンスは逆の順序(ポッド→サービス→イングレス)を通って返される、ということになります。
「デプロイしたアプリケーションが期待していたように動かない」というのは、この中のどこかが期待していたように動いていなくて、全体として「動かない」ように見えていることになります。したがってトラブルシューティングにおいて、まずはどこで問題が発生しているのかを特定する必要があります。
【port-forward 機能】
kubectl や oc といった CLI ツールの機能である port-forwarding を使うと、ポート番号を使ったフォワーディング機能によりイングレスを経由せずにコンテナクラスタ内に定義されているサービスやポッドに直接アクセスできるようになります。
例えばイングレスを経由せずにサービスにアクセスすると期待していた挙動になる(期待していた結果が返ってくる)のであればイングレスやその設定に問題がある可能性が高いことが考えられ、サービスにアクセスしても動かないがポッドに直接アクセスすれば期待していた挙動になるのであればサービスやその設定に問題がある可能性が高いことが考えられます(それでも動かない場合はポッドに問題があると考えられます)。 このような障害発生個所の見極めにおいては port-forwarding が便利に使えることになります。
【port-forward 機能を使ってみる】
試しにわざと正しく動かないことがわかっている以下のようなマニフェストファイル(deployment.yaml)を使ってアプリケーションをデプロイし、port-forward で確認する、ということをやってみます:
apiVersion: apps/v1
kind: Deployment
metadata:
name: guestbook
labels:
app: guestbook
spec:
replicas: 1
selector:
matchLabels:
app: guestbook
template:
metadata:
labels:
app: guestbook
spec:
containers:
- name: guestbook
image: ibmcom/guestbook:v1
imagePullPolicy: Always
env:
- name: NODE_ENV
value: production
---
apiVersion: v1
kind: Service
metadata:
name: guestbook-svc
labels:
app: guestbook
spec:
type: ClusterIP
ports:
- protocol: TCP
port: 3000
targetPort: 3000
selector:
app: guestbook
---
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: guestbook-route
labels:
app: guestbook
spec:
host: guestbook.(アサインされた ingress サブドメイン)
to:
kind: Service
name: guestbook-svc
weight: 100
port:
targetPort: 4000 # 正しくは3000
tls:
termination: edge
insecureEdgeTerminationPolicy: Redirect
wildcardPolicy: None
ちなみに上記マニフェストの正しい記述は下から5行目の Route の spec.port.targetPort の値が 3000 になっているものです(今回はわざと 4000 を指定しています)。
なお以下の作業は IBM Cloud の Redhat OpenShift コンテナクラスタ環境を使って確認しました。なので Ingress 部分の定義は apiVersion が "route.openshift.io/v1" の Route というリソースを使って定義しています。またこの Route 内の spec.host の値には "guestbook.(アサインされた ingress サブドメイン)" と記載していますが、実行前にこの()内の部分を OpenShift 画面に表示されているサブドメイン名に置き換えて保存しておいてください:
oc コマンドでログインし、このマニフェストファイルをデプロイ(oc apply)して、その結果を確認(oc get all)します:
$ oc apply -f deployment.yml $ oc get all NAME READY STATUS RESTARTS AGE pod/guestbook-59b85f9654-22wh5 1/1 Running 0 13m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/guestbook-svc ClusterIP 172.21.150.2303000/TCP 13m service/kubernetes ClusterIP 172.21.0.1 443/TCP 6h7m service/openshift ExternalName kubernetes.default.svc.cluster.local 5h46m service/openshift-apiserver ClusterIP 172.21.46.95 443/TCP 6h6m service/openshift-oauth-apiserver ClusterIP 172.21.98.204 443/TCP 6h6m NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/guestbook 1/1 1 1 13m NAME DESIRED CURRENT READY AGE replicaset.apps/guestbook-59b85f9654 1 1 1 13m NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD route.route.openshift.io/guestbook-route guestbook.(ingress サブドメイン).jp-tok.containers.appdomain.cloud guestbook-svc 4000 edge/Redirect None
"oc get all" の結果は上からポッド、サービス、デプロイメント、レプリカセット、そしてイングレスになっています。ポッドのステータスは Running でちゃんと動いていて、サービスも ClusterIP を使って正しく公開され、イングレスもホスト名が割り当てられています。 この結果だけを見ると正しく動いてそうです。
でも実際にブラウザでイングレスの HOST 名に表示されているサーバーにアクセスするとこのように表示されます(知らない人もいるので補足すると、これは期待していたアプリ画面ではなく、アクセスできなかった場合のエラー画面です):
エラー無くデプロイできたけどアプリにアクセスできない、、 こんな時に port-forward の出番です。
まずは(上述の結果で Pod が Running になってるので可能性は低そうですが)ポッドのレベルで正しく動いているかどうかを確認するため、port-forwarding でポッドに直結して動作を確認してみます。
ポッドに対して port-forwarding するには kubectl(または oc )コマンドで以下のように実行します:
$ kubectl port-forward (ポッド名) (ホストのポート番号):(ポッド上で動いているポート番号)
今回の例だと(上述の結果より)ポッド名は "guestbook-svc" だとわかります。また実行したマニフェストファイルより、このサービスは 3000 番ポートで動いているはずです(サービスの spec.ports.targetPort の値)。このポッドにホストの(何番でもいいのですが) 8000 番ポートからフォワーディングして接続させてみます。というわけで、今回のケースであれば以下のコマンドを実行します:
$ kubectl port-forward guestbook-59b85f9654-22wh5 8000:3000
コマンド実行後、ウェブブラウザから http://localhost:8000/ にアクセスしてみます:
今回は期待通りの画面が表示されました。ということは「ポッドは正しく動いている」ことが推測できました。Ctrl+C で port-forwarding を解除します。
継はサービスのレベルで正しく動いているかどうかを確認してみます。イングレスを経由せずにサービスにアクセスするため、port-forwarding でサービスに直結して動作を確認してみます。この場合は以下のようなコマンドを実行します:
$ kubectl port-forward service/(サービス名) (ホストのポート番号):(サービスで公開しているポート番号)
今回の例だと(上述の結果より)サービス名は "guestbook-59b85f9654-22wh5" だとわかります。また実行したマニフェストファイルより、このポッド(デプロイメント)は 3000 番ポートで動いているはずです(サービスの spec.ports.port の値)。このポッドにホストの(何番でもいいのですが) 9000 番ポートからフォワーディングして接続させてみます。というわけで、今回のケースであれば以下のコマンドを実行します:
$ kubectl port-forward service/guestbook-svc 9000:3000
コマンド実行後、ウェブブラウザから http://localhost:9000/ にアクセスしてみます:
今回は期待通りの画面が表示されました。ということは「サービスも正しく動いている」ことが推測できました。Ctrl+C で port-forwarding を解除します。
これらの結果から、
・ポッド(デプロイメント)は正しく動いていそう
・サービスも正しく動いていそう
・ということはイングレスの設定が間違っていそう?
という切り分けをすることができました。実際、マニフェストの Route 内をわざと間違った内容に弄ったことが分かっているので、正しい切り分けができていることになります。
このケースであればイングレスの設定内容を疑ってみることになります。試しにイングレスの設定名称を指定して describe するとこのようになりました(kubectl の場合は "kubectl describe ingress guestbook-route"):
$ oc describe route guestbook-route : : Path:TLS Termination: edge Insecure Policy: Redirect Endpoint Port: 4000 Service: guestbook-svc Weight: 100 (100%) Endpoints:
接続先である Endpoints が空になっていて何も表示されていません。ということは設定内容になんらかの誤りがあってサービスに接続できていないのでは・・・ と考えることができます。実際、今回のマニフェストでは targetPort を 3000 と指定すべきところを 4000 と指定していたため接続できていませんでした。仮に 4000 を 3000 に変更して再度適用した上で接続を試みた所、今度は正しく接続できるようになりました:
現実問題として、イングレス部分はポッドやサービスとは異なり Kubernetes からはサードパーティ製品となるので色々な実装があり、トラブルシューティングも実装ごとの方法が考えられるため比較的難しいトラブルシューティングとはなります。それでも「どの部分に設定ミスがあったか」を特定することで解決はしやすくなりますし、その特定において port-forwarding は有効な切り分け方であると思っています。