[Kubernetes] クラスタ外から ClusterIP の Service にいい感じにアクセスする

はじめに

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 グループのアドレスのいずれかに転送されるようになります。

kube-proxy: iptablesプロキシーモード
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 open-svc プラグイン

このコマンドは 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 として話した内容をハンズオン形式で試していただけるようにしてみました。また合わせて、各要素について少し踏み込んだ説明を入れてみました。少しでも楽しんでもらえていれば幸いです。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account