はじめに
Kubernetes を使っていて、Service (ClusterIP) 経由でアプリケーションにアクセスしたいこと、ありますよね。ClusterIP はクラスタ内の Pods からしかアクセスできないので、ちょっと手元のブラウザからアクセスしたいというときに困ります。
ここでは、ハンズオン形式で手を動かしながら Kubernetes クラスタの外から ClusterIP の Serivce を使ってアプリケーションにいい感じにアクセスする方法を学びます。
TL;DR
- Service の ClusterIP にはクラスタ外からアクセスできない
-
kubectl port-forward svc/<svc-name>
コマンドは実際には ClusterIP を使っていないので注意 - 実は
kubectl proxy
コマンド + Service proxy サブリソースを使うとクラスタ外から Service にアクセスできる -
kubectl open-svc
プラグインを使うと ClusterIP の Service にいい感じにアクセスできる
事前準備
ここでは minikube を使用してクラスタを構築します。minikube および kubectl のインストールは次のドキュメントを参照してください。
$ minikube version
minikube version: v1.6.1
commit: 42a9df4854dcea40ec187b6b8f9a910c6038f81a
$ minikube start --kubernetes-version=v1.17.0
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.0", GitCommit:"70132b0f130acc0bed193d9ba59dd186f0e634cf", GitTreeState:"clean", BuildDate:"2019-12-07T21:20:10Z", GoVersion:"go1.13.4", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.0", GitCommit:"70132b0f130acc0bed193d9ba59dd186f0e634cf", GitTreeState:"clean", BuildDate:"2019-12-07T21:12:17Z", GoVersion:"go1.13.4", Compiler:"gc", Platform:"linux/amd64"}
Service の ClusterIP にはクラスタ外からアクセスできない
Service の ClusterIP にはクラスタ外からアクセスできないとはどういうことでしょうか。ここでは、nginx
Service を作成してその Service の ClusterIP にクラスタの外からアクセスしてみます。
まず nginx
Service を作成します。
$ kubectl create deploy nginx --image=nginx
deployment.apps/nginx created
$ kubectl expose deploy/nginx --port=80
service/nginx exposed
$ kubectl get svc nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP 10.96.225.250 <none> 80/TCP 12s
ここでは nginx
Service の ClusterIP は 10.96.255.250
になりました。ではこのアドレスにアクセスしてみます。
$ curl 10.96.225.250
curl: (7) Failed to connect to 10.96.225.250 port 80: No route to host
正しくアクセスできませんでした。それは ClusterIP がクラスタ内部でのみ有効な仮想的な IP アドレスだからです(Service には ClusterIP 以外に外部 IP を持たせる ExternalIP などがあります)。この仕組みを提供しているのがノードコンポーネントの1つである kube-proxy です。Service(と Endpoints)が作成されると全ノードの kube-proxy が各ノードの設定を変更します(デフォルトは iptables を使用)。この設定変更によって、ノードから出て行くパケットの宛先アドレスが ClusterIP と一致すると、その ClusterIP を持つ Service のバックエンドである Pod グループのアドレスのいずれかに転送されるようになります。
Kubernetes 公式ドキュメント: kube-proxy iptablesプロキシーモードから引用
ここから分かる通り、これは Kubernetes ノードのなかだけで有効な設定であるため、クラスタの外からでは ClusterIP 宛てのパケットは宛先不明としてエラーになります(クラスタ内で実行される Pod から送信されるパケットはそれが実行されているノードを経由するため、ClusterIP を使用できます)。
ClusterIP にアクセスする(クラスタ内編)
クラスタ外からアクセスできないのであればクラスタ内からアクセスすればいいということで、ここではクラスタ内からアクセスする方法を紹介します。
まず最初は kubectl exec
コマンドを使用する方法です。このコマンドはクラスタ内で実行されている Pod のコンテナのなかで任意のコマンドを実行します。ここでは debian コンテナを実行して、そこからアクセスしてみます。
$ kubectl run debian --image debian --command tail -f /dev/null
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/debian created
$ kubectl exec debian-5f56977f6b-sx7p2 -it /bin/sh
# apt update -qq && apt install curl
# curl --silent 10.96.225.250 | head -n 3
<!DOCTYPE html>
<html>
<head>
# exit
もう1つは kubectl run
コマンドを使って Pod を作成し、その Pod からアクセスする方法です。kubectl exec
コマンドとほぼ同じですが、こちらのほうが使い勝手がいいかもしれません。
$ kubectl run busybox --restart=Never -it --image=busybox --rm /bin/sh
If you don't see a command prompt, try pressing enter.
/ # wget -q -O- 10.96.225.250 | head -n 3
<!DOCTYPE html>
<html>
<head>
/ # exit
TIP としてこのコマンドを kbb
のようなエイリアスに設定しておくと便利です。
alias kbb="kubectl run busybox --restart=Never -it --image=busybox --rm /bin/sh"
2つのコマンドを使ってクラスタ内から ClusterIP にアクセスする方法を紹介しました。これでもよさそうですが、クラスタ外から、例えばブラウザから確認したい場合にこれらの方法では難しいです。では次にクラスタ外いからアクセスする方法を紹介します。
クラスタ外から ClusterIP に紐づく Pod にアクセスする
そもそも ClusterIP ではなく Pod に直接アクセスするのでも構わないかもしれません。それには kubectl port-forward
コマンドが使えます。このコマンドはローカルのポートを任意の Pod のポートに転送します。ここではローカルの8080番ポートを nginx
コンテナの80番ポートに転送します。
$ kubectl port-forward nginx-86c57db685-glcnv 8080:80 &
$ curl --silent 127.0.0.1:8080 | head -n 3
<!DOCTYPE html>
<html>
<head>
また、kubectl port-forward
コマンドには Service 名を指定することもできます。
$ kubectl port-forward svc/nginx 8080:80
ここで注意しなければならないのは、kubectl port-forward
コマンドで Service 名を指定したとしても ClusterIP を使用してアクセスしているではなく、Service のラベルセレクタに一致する Pod の1つに対してポート転送しているということです。本当にそうなのかを確認するのにもっとも簡単なのは kubectl port-forward service/<service>
コマンドで内部的にどの Kubernetes API を使用しているかを見ることです。
$ kubectl port-forward svc/nginx 8080:80 -v=6 2>&1 | grep portforward
I1217 17:07:07.307619 30292 round_trippers.go:443] POST https://192.168.99.100:8443/api/v1/namespaces/default/pods/nginx-86c57db685-glcnv/portforward 101 Switching Protocols in 19 milliseconds
上記のログから Service 名を指定していても内部的には単一の Pod の portforward
サブリソースを使用していることが分かります(サブリソースが何かについては次の節で説明します)。
結果として、この方法では ClusterIP を使用しているわけではないため、もし Service のバックエンドに複数の Pod が存在していたとしても、この方法ではそれらに対してアクセスが分散するわけではありません。
ClusterIP にアクセスする(クラスタ外編)
ここからは、本丸であるクラスタ外から ClusterIP にアクセスする方法を説明します。
実をいうと Service にはクラスタ外からアクセスするための proxy
という機能を持っているのです。これと kubectl proxy
コマンドを組み合わせることでクラスタ外から ClusterIP でアプリケーションにアクセスできます。さっそくアクセスする方法を紹介します。
$ kubectl proxy &
Starting to serve on 127.0.0.1:8001
$ curl 127.0.0.1/api/v1/namespaces/default/services/nginx/proxy/
/ # wget -q -O- 10.96.225.250 | head -n 3
<!DOCTYPE html>
<html>
<head>
Service proxy
サブリソース
このプロキシ機能は、Service の proxy
サブリソースとして用意されています。そもそもサブリソースとは何でしょうか。サブリソースとは、一般的なリソースの HTTP パスに対して追加でサフィックスを付与した特別な HTTP パスのことです。ここでは Pod を例として説明します。Kubernetes API ではリソースの取得、作成、更新、削除は REST として次のように表現されます。
/api/v1/namespaces/<namespace>/pods/<pod>
これに対して、kubectl logs/port-forward/exec
といった操作は REST では表現できません。そのため、サブリソースをパスとして標準パスのサフィックスに追加することでこれを表現しています。
- Pod のログを取得する:
/api/v1/namespaces/<namespace>/pods/<pod>/logs
- Pod のポートを転送する:
/api/v1/namespaces/<namespace>/pods/<pod>/portforward
- Pod で任意のコマンドを実行する:
/api/v1/namespaces/<namespace>/pods/<pod>/exec
またプロトコルも標準のパスとは異なるものが使われる場合があります。例えば logs/portforward/exec
サブリソースでは WebSocket が使用されてます。このほかに、オブジェクトの status
フィールドのみを更新するための status
サブリソースやスケールさせるための scale
サブリソースなどがあります。
ここまでで分かる通り、Service の proxy
サブリソースは、Service に対するアクセスをプロキシするための機能です。
/api/v1/namespaces/<namespace>/services/<scheme>:<service>:<port>/proxy/
kubectl proxy
コマンドと同時に使用しているのは、開発者の権限でエンドポイントにアクセスするためです(kubectl proxy
コマンドは、ローカルでプロキシサーバを実行し、kubectl の設定ファイルにあるトークンや証明書を自動的に付与してくれます)。また、今回はあらゆる操作が可能なクラスタ管理者(cluster-admin)の権限で操作しているため、何の権限付与もなしに Service proxy
サブリソースが使用できています。権限を持っているかどうかは次のコマンドで確認できます(標準では ClusterRole edit
以上の権限で使用できるようになっています)。
kubectl auth can-i '*' services --subresource=/proxy/
クラスタ外から ClusterIP の Service にいい感じにアクセスする
Service proxy
サブリソースを使うことでクラスタ外からアクセスできることは分かりましたが、いい感じかと言われると長々とした URL をわざわざ入力しなければなりませんし、もっといい感じにアクセスする方法はないのでしょうか。
kubectl open-svc プラグイン
そんなあなたに kubectl open-svc
プラグインです。これは、内部的に kubectl proxy
と同じことをやりつつ Service proxy
サブリソースの URL を自動的にブラウザで展開してくれるという優れものです。
使い方はとっても簡単でブラウザで展開したいサービス名を引数として指定するだけです。
kubectl open-svc <service>
実際にどのように動作するかは次の GIF をご覧ください。
このコマンドは kubectl プラグイン(kubectl の拡張機能として任意のサブコマンドを kubectl に追加します)として実装されており、追加でインストールが必要です。
kubectl open-svc プラグインをインストールする
kubectl プラグインをインストールするのに簡単な方法は Krew を使う方法です。Krew は kubectl プラグインマネージャでレジストリに登録された50以上のプラグインを簡単にインストールして使いはじめることができます。
まず最初に Krew をインストールします。
bash または zsh を使用しているなら次のコマンドを実行します。
(
set -x; cd "$(mktemp -d)" &&
curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/download/v0.3.3/krew.{tar.gz,yaml}" &&
tar zxvf krew.tar.gz &&
KREW=./krew-"$(uname | tr '[:upper:]' '[:lower:]')_amd64" &&
"$KREW" install --manifest=krew.yaml --archive=krew.tar.gz &&
"$KREW" update
)
次に、.bashrc
または .zshrc
に次の行を追加してシェルを再起動させてください。
export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"
fish または Windows を使用している場合は、ドキュメントの手順にしたがってインストールしてください。
正しくインストールされていれば次のコマンドで実行可能なプラグイン一覧が出力されます。kubectl-krew
が含まれていれば成功です。Krew 自体もプラグインとして実装されています。
kubectl plugin list | grep kubectl-krew
最後に kubectl open-svc
プラグインを次のコマンドでインストールします。
kubectl krew install open-svc
正しくインストールされていれば次のコマンドでヘルプメッセージが出力されます。
kubectl open-svc -h
kubectl open-svc
プラグインで ClusterIP にいい感じにアクセスする
では、早速インストールした kubectl open-svc
プラグインを使用して ClusterIP にいい感じにアクセスしてみてください。
kubectl open-svc nginx
おわりに
いかがだったでしょうか。このエントリは Cloud Native Days Kansai 2019 前夜際での LT として話した内容をハンズオン形式で試していただけるようにしてみました。また合わせて、各要素について少し踏み込んだ説明を入れてみました。少しでも楽しんでもらえていれば幸いです。