Service を使ったアプリケーションの公開

KubernetesBeginner
オンラインで実践に進む

はじめに

これまでの実験で、Pod は作成と削除を繰り返すことを学びました。Pod が停止して新しい Pod に置き換えられると、新しい Pod にはまったく異なる IP アドレスが割り当てられます。電話を切るたびに電話番号が変わる友人に電話をかける場面を想像してみてください。

この問題を解決するために、Kubernetes では Service を使用します。

Service は、背後にある Pod が入れ替わっても、ワークロードに安定したネットワーク上の識別情報を提供します。Pod のライフサイクルと Service のエンドポイントを分離する考え方は、Kubernetes ネットワークの基本です。

これは、Kubernetes ネットワークを理解するうえで特に重要な考え方の 1 つです。Pod は交換可能な存在ですが、クライアントには信頼できるアドレスが必要です。Service は変化する Pod の前段に配置され、安定した名前と仮想 IP を提供することで、この不一致を解決します。

この実験では、次のことを行います。

  • アプリケーションに安定したアドレスを与える Service を作成します。
  • Service が接続先の Pod をどのように判断するかを学びます(Selectors を使用します)。
  • NodePort という特定の種類の Service を使って、クラスターの外部からアプリケーションにアクセスします。

Service YAML を作成する

このステップでは、Service の設定ファイルを作成します。ここでは NodePort という種類を使用します。

NodePort Service は、クラスター内のすべての Node(サーバー)で特定のポートを開きます。家に特定のドアを作り、外部から入れるようにするイメージです。

NodePort は各 Node のポートに直接対応するため理解しやすい一方、通常は公開トラフィック向けの最終的な本番構成ではありません。大規模な環境では、NodePort は多くの場合、クラウドロードバランサーや Ingress 層の背後に配置されます。この実験では、ネットワーク経路を初学者にも分かりやすくするために使用します。

プロジェクトディレクトリに service.yaml という名前のファイルを作成します。

nano service.yaml

次の内容を貼り付けます。selector の部分は意図的に app: unknown のままにしています。これがどのように動作するかを確認するためです。

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: NodePort
  ports:
    - port: 80 ## The port the Service exposes internally
      targetPort: 80 ## The port the container is listening on
      nodePort: 30008 ## The static door we are opening to the outside
  selector:
    app: unknown ## This needs to match your Pod's label

保存して終了します(Ctrl+X、Y、Enter)。

このファイルは、my-service という名前の Service を定義します。Service はポート 30008(nodePort)で待機します。このポートに到達したトラフィックは、app: unknown というラベルが付いた Pod に転送されます。ただし、現在そのような Pod は存在しません。

セレクターを修正する

Service は Selector を使って、トラフィックの転送先となる Pod を見つけます。これは宛先ラベルのようなものです。封筒に書かれた住所(Service の Selector)が家の住所(Pod の Label)と一致しなければ、郵便物は届きません。

ラベルとセレクターは Service だけでなく、Kubernetes 全体で使われます。これらは、Kubernetes の中核となるグループ化の仕組みの 1 つです。Kubernetes のリソースは、特定の Pod 名を固定的に指定する代わりに、通常はラベルを照合して関連するオブジェクトを見つけます。そのため、Pod が再作成されても柔軟に対応できます。

Setup ステップでは、Web アプリケーションを自動的に起動しました。ここで、その「宛先ラベル」を確認します。

実行中の Pod のラベルを確認するため、次のコマンドを実行します。

kubectl get pods --show-labels

次のような出力が表示されます。

web-app-xxxxx ... app=web-app

ラベルは app=web-app です。一方、Service は app: unknown を探す設定になっています。これを修正します。

service.yaml ファイルを編集します。

nano service.yaml

selector セクションを、Pod のラベルに一致するように変更します。

selector:
  app: web-app

保存して終了します。これで Service は、トラフィックを正しい宛先に送れるようになります。

適用して確認する

宛先が正しくなったので、Service を有効にします。

このマニフェストを適用すると、Kubernetes は Service オブジェクトを作成し、Service に到達したリクエストを一致する Pod のエンドポイントへ転送できるように、内部ネットワークのルールを設定します。重要なのは、Service 自体がアプリケーションを実行するわけではないという点です。Service は、アプリケーション Pod の前段にあるネットワーク抽象化です。

設定をクラスターに適用します。

kubectl apply -f service.yaml

次のように表示されます。

service/my-service created

Service が起動して実行中であることを確認するには、Service の一覧を確認します。

kubectl get svc my-service

次の内容を確認できます。

  • NAME: my-service
  • TYPE: NodePort
  • PORT(S): 80:30008/TCP

これで、「電話番号」が有効になり、ポート 30008 での接続を待機していることが分かります。

アプリケーションにアクセスする

いよいよ確認です。「外部」、つまりターミナルからアプリケーションにアクセスできるでしょうか。

アプリケーションは、Kubernetes Node のポート 30008 に公開されています。

ここでは 3 種類のポートが関係しており、初学者は混同しがちです。nodePort は Node 上で外部向けに開かれるポート、port は Service 抽象化が公開するポート、targetPort は実際にアプリケーションを提供するコンテナ内部のポートです。それぞれの役割を区別して覚えると、後で混乱しにくくなります。

まず、Minikube Node の IP アドレスを取得します。

minikube ip

IP アドレスが 192.168.49.2 だとすると、アプリケーションの完全なアドレスは http://192.168.49.2:30008 です。

curl を使ってこのアドレスにアクセスし、アプリケーションが応答していることを確認します。

curl http://$(minikube ip):30008

「Welcome to nginx!」ページの HTML コードが表示されます。

これで、リクエストが次の経路を通ったことを確認できます。

  1. Node の外部ポート(30008)にアクセスしました。
  2. Service がそのリクエストを受け取りました。
  3. Selector(app: web-app)を確認しました。
  4. そのラベルに一致する Pod の 1 つへリクエストを転送しました。
  5. Pod が Web ページを返しました。

まとめ

この実験では、Service を使って、消えてしまう Pod の IP アドレスの問題を解決しました。

次の操作を完了しました。

  1. アプリケーションへのゲートウェイを開く NodePort Service(service.yaml)を作成しました。
  2. Service がどの Pod を管理するかを指定する Selector を設定しました。
  3. 安定したアドレスを使って、実行中のアプリケーションに外部からアクセスしました。

Service は Kubernetes アプリケーションを結び付ける接着剤のような役割を果たし、システムの各部分が安定して通信できるようにします。