KubernetesのService入門|ClusterIP・NodePort・名前解決を実習で理解する


はじめに

KubernetesでPodやDeploymentを作成できても、それだけでアプリケーションへ安定して接続できるとは限りません。Podが作り直されると、PodのIPアドレスが変わることがあります。また、複数のPodを動かしている場合には、接続先をまとめる仕組みが必要です。

そこで使うのが、Serviceです。

この記事では、Nginxを3つのPodで起動し、次の順序で通信を確認します。

  1. ClusterIPでServiceを作成する
  2. クラスタ内の確認用Podから、ServiceのIPアドレスへ接続する
  3. IPアドレスの代わりに、Service名で接続する
  4. ServiceをNodePortへ変更し、Nodeのポート経由で接続する

PodとDeploymentの基本操作を理解している方を対象にしています。基本概念は、Kubernetes入門で確認できます。実習用のDeploymentはこの記事内で作成するため、前の記事で作ったPodを残しておく必要はありません。

1. Serviceとは

Serviceは、Podへの接続先を提供するKubernetesのリソースです。

今回のようにselectorを持つServiceでは、指定したラベルに一致するPodが接続先の候補になります。ServiceはReplicaSetそのものに接続するのではなく、Podをラベルで選択します。

例えば、NginxのPodを3つ動かしている場合、利用側はそれぞれのPodのIPを直接指定せず、ServiceのIPや名前へ接続できます。

接続方法接続先の考え方
PodのIPへ直接接続利用側が対象PodのIPを指定する
Serviceを経由して接続利用側はServiceのIPや名前を指定し、Serviceの通信処理を通じて対象Podへ接続する

通常のClusterIP Serviceでは、Serviceが存在する間、割り当てられたClusterIPは維持されます。Deploymentの更新やPodの再作成のたびに変わるわけではありません。

ただし、Serviceを削除して作成し直した場合は、別のClusterIPが割り当てられる可能性があります。Kubernetes

2. Serviceの主な種類

Serviceには、次の種類があります。

種類主な役割
ClusterIPクラスタ内部向けのIPアドレスで接続先を提供する。標準の種類
NodePortNodeのIPアドレスと所定のポート番号を通じて接続できるようにする
LoadBalancer対応する外部ロードバランサーと連携して公開する
ExternalName外部のホスト名を返すDNSの別名を設定する

この記事の実習では、まずClusterIPを作成し、その後、同じServiceをNodePortへ変更します。

LoadBalancerとExternalNameは、最後に概要を確認します。

3. 実習環境を準備する

前提条件

  • ローカルのKubernetesクラスタが起動していること
  • kubectl でクラスタを操作できること
  • NginxとBusyBoxのイメージを取得できること
  • 標準的なクラスタDNSとServiceの通信機能が動作していること

MacやWindowsでは、Docker DesktopのKubernetes環境を利用できます。Kubernetesの準備がまだの場合は、Docker公式の設定手順を確認してください。

NodePortをPCから利用する方法は、Docker Desktopの作成方式や、kind・minikubeなどの環境によって異なります。この記事では、クラスタ内からの確認手順も用意します。

接続先を確認する

まず、操作するクラスタを確認します。

kubectl config current-context
kubectl get nodes

Docker Desktopを使う場合は、登録済みの接続設定を確認して切り替えられます。

kubectl config get-contexts
kubectl config use-context docker-desktop

以降は学習用のローカルクラスタで実行し、Nodeの STATUS が Ready であることを確認してください。

作業場所とNamespaceを作成する

mkdir k8s-service
cd k8s-service
kubectl create namespace k8s-service

今回は、実習用リソースを k8s-service というNamespaceにまとめます。すでに存在する場合は、今回の実習に使ってよいNamespaceかを確認してください。

4. 接続先となるNginxのPodを作成する

エディターで deployment.yaml を作成し、次の内容を保存します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-web
  namespace: k8s-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-service-demo
  template:
    metadata:
      labels:
        app: nginx-service-demo
    spec:
      containers:
        - name: nginx
          image: nginx:stable-alpine
          ports:
            - containerPort: 80

3つのPodに、それぞれ app: nginx-service-demo というラベルが付きます。後でServiceのselectorをこのラベルに合わせます。

作成して、展開完了を待ちます。

kubectl apply -f deployment.yaml
kubectl rollout status deployment/nginx-web -n k8s-service --timeout=120s
kubectl get pods -n k8s-service -l app=nginx-service-demo -o wide

3つのPodが Running、`READY` がそれぞれ 1/1 になっていることを確認してください。`-o wide` を付けると、PodのIPと配置先Nodeも表示されます。

この記事に載せるIPアドレスや出力は例です。実際の環境では、手元の表示を使ってください。

nginx:stable-alpine は更新されるタグです。実行環境を厳密に固定したい場合は、使用するバージョンやダイジェストを指定してください。

5. ClusterIPのServiceを作成する

service.yamlを作成する

同じディレクトリに service.yaml を作成し、次の内容を保存します。

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  namespace: k8s-service
spec:
  type: ClusterIP
  selector:
    app: nginx-service-demo
  ports:
    - name: http
      protocol: TCP
      port: 8080
      targetPort: 80

今回は、Serviceの8080番ポートを、Pod内のNginxが待ち受ける80番ポートにつなぎます。

項目今回の値役割
metadata.namenginx-serviceServiceの名前
metadata.namespacek8s-serviceServiceの作成先
typeClusterIPクラスタ内部向けの接続先を作る
selector.appnginx-service-demo対象Podを選択するラベル条件
port8080Serviceへ接続するときのポート
targetPort80転送先Podで、アプリケーションが待ち受けるポート

Serviceの port とPod側の targetPort は、同じ番号である必要はありません。

また、Serviceのselectorは、同じNamespaceにあるPodのラベルを対象にします。Deployment自体の名前や、Deploymentの最上位に付けたラベルだけを見て接続先を選ぶわけではありません。

Serviceを作成して確認する

kubectl apply -f service.yaml
kubectl get service nginx-service -n k8s-service

出力例です。

NAME            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
nginx-service   ClusterIP   10.96.120.10   <none>        8080/TCP   20s
表示今回の読み方
TYPEClusterIPとして作成されている
CLUSTER-IPServiceに割り当てられたクラスタ内部向けのIP
EXTERNAL-IP今回は外部IPを割り当てていないため <none>
PORT(S)Service側の8080番ポートでTCP通信を受け付ける

PCのブラウザから、このClusterIPへ直接アクセスする手順ではありません。次に、クラスタ内に確認用Podを作り、そこから接続します。

6. 確認用PodからClusterIPへ接続する

BusyBoxの確認用Podを作成する

BusyBoxには、今回利用する wget や nslookup などのコマンドが含まれています。

kubectl run netcheck -n k8s-service --image=busybox:1.37.0 --restart=Never --command -- sleep 3600
kubectl wait --for=condition=Ready pod/netcheck -n k8s-service --timeout=120s

netcheck は確認用Podの名前です。`sleep 3600` で約1時間動作させ、その間にPod内のコマンドを実行します。

ServiceのIPへアクセスする

もう一度、ServiceのIPを確認します。

kubectl get service nginx-service -n k8s-service

次のコマンド内の CLUSTER_IP を、表示された実際のIPアドレスに置き換えて実行してください。

kubectl exec netcheck -n k8s-service -- wget -qO- http://CLUSTER_IP:8080/

例えば、手元のClusterIPが 10.96.120.10 なら、次のようになります。

kubectl exec netcheck -n k8s-service -- wget -qO- http://10.96.120.10:8080/

Nginxの初期ページのHTMLが表示され、`Welcome to nginx!` が含まれていれば、HTTP応答を取得できています。

今回の通信では、確認用PodがServiceの8080番ポートへ接続し、Serviceの通信処理を通じて、選択されたNginxのPodの80番ポートへリクエストが届きます。

kubectl exec の -- より後ろが、Pod内で実行するコマンドです。`wget -qO-` は、進捗表示などを抑え、取得した内容を標準出力へ表示します。

7. Service名で接続する

IPアドレスを直接指定する代わりに、Service名で接続してみましょう。

同じNamespaceではService名を使う

kubectl exec netcheck -n k8s-service -- wget -qO- http://nginx-service:8080/

先ほどと同じNginxのHTMLが返れば、Service名を使った接続ができています。

標準的なクラスタDNSの設定では、Podが所属するNamespaceなどを使ってService名を名前解決します。今回の netcheck と nginx-service は、どちらも k8s-service にあります。

名前解決の結果を確認する

kubectl exec netcheck -n k8s-service -- nslookup nginx-service

通常のClusterIP Serviceでは、Serviceの名前がClusterIPへ解決されます。出力中のDNSサーバーのアドレスと、問い合わせたServiceのアドレスを区別して確認してください。

Namespaceを含めて指定する

次のように、Service名にNamespaceを付けることもできます。

kubectl exec netcheck -n k8s-service -- wget -qO- http://nginx-service.k8s-service:8080/

別のNamespaceにあるPodから接続する場合も、接続先ServiceのNamespaceを含めて指定します。Kubernetes

完全なDNS名を指定する

クラスタのドメインが cluster.local の場合、完全なDNS名は次の形です。

nginx-service.k8s-service.svc.cluster.local

接続を確認します。

kubectl exec netcheck -n k8s-service -- wget -qO- http://nginx-service.k8s-service.svc.cluster.local:8080/
部分意味
nginx-serviceService名
k8s-serviceServiceのNamespace
svcServiceのDNS名を構成する部分
cluster.localクラスタで設定されたドメインの例

cluster.local は環境によって変更される場合があります。今回の短いService名で接続でき、完全なDNS名では失敗する場合は、クラスタのDNS設定を確認してください。

また、このDNS名が自分のPCからそのまま名前解決できるとは限りません。ここでは、クラスタ内のPodから確認しています。

8. NodePortへ変更して接続する

次は、同じServiceをNodePortへ変更します。ClusterIPのServiceを削除して別のServiceを作るのではなく、既存の設定を更新します。

service.yamlを変更する

service.yaml の内容を、次に変更してください。

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  namespace: k8s-service
spec:
  type: NodePort
  selector:
    app: nginx-service-demo
  ports:
    - name: http
      protocol: TCP
      port: 8080
      targetPort: 80
      nodePort: 30080

type を NodePort に変更し、`nodePort: 30080` を追加しました。

ポートの種類今回の番号接続先
nodePort30080NodeのIPと組み合わせて指定するポート
port8080クラスタ内でServiceへ接続するポート
targetPort80NginxがPod内で待ち受けるポート

NodePortの標準的な範囲は 30000〜32767 です。今回は説明を追いやすくするため、範囲内の 30080 を指定しています。

変更を反映する

kubectl apply -f service.yaml
kubectl get service nginx-service -n k8s-service

出力例です。

NAME            TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
nginx-service   NodePort   10.96.120.10   <none>        8080:30080/TCP   5m

8080:30080/TCP は、Serviceのポートが8080、NodePortが30080という意味です。Pod側の80番ポートは、この一覧には表示されていません。

既存のServiceを今回のように更新した場合、ClusterIPは維持されます。NodePortへ変更した後も、クラスタ内からはService名と8080番ポートで接続できます。

NodeのIPを確認する

kubectl get nodes -o wide

学習用Nodeの INTERNAL-IP を確認してください。

次の NODE_IP を、そのIPアドレスに置き換えます。

kubectl exec netcheck -n k8s-service -- wget -qO- http://NODE_IP:30080/

標準的なIPv4のローカルクラスタでは、この方法で確認用PodからNodePort経由の通信を確認できます。NodePortを受け付けるNodeのアドレスは、クラスタのネットワーク設定によって制限されることがあります。

NginxのHTMLが返れば、Nodeの30080番ポートを通じてPodへ接続できています。

自分のPCから接続する

PCからNodeのIPへ到達できる環境では、ブラウザで http://NODE_IP:30080/ を開いて確認できます。`NODE_IP` は、実際のNodeのIPに置き換えてください。

Docker DesktopなどでNodePortがPCのlocalhostへ転送される構成なら、次のURLでも確認できます。

http://localhost:30080/

ただし、NodePortを作れば、どの環境でもlocalhostから接続できるわけではありません。 Nodeが仮想マシンやコンテナの内部にある場合は、PCからの到達経路やポート転送の設定が必要になることがあります。Kubernetes

クラスタ内の確認用Podからは接続できるのに、PCから接続できない場合は、まずPCとNodeの間のネットワークを確認します。

port-forwardでアプリケーションだけを確認する場合

PCからの接続経路をすぐに用意できない場合は、切り分けとして次の方法も使えます。

kubectl port-forward service/nginx-service 18080:8080 -n k8s-service

コマンドを実行したまま、http://localhost:18080/へアクセスします。確認後は Ctrl + C で終了します。

この方法は、Serviceの設定を使って選択したPodへ転送します。ClusterIPやNodePortの通信経路を通る確認ではないため、表示できても「NodePortへの接続が成功した」とは判定できません。Kubernetes

9. Serviceが選んだ接続先を確認する

ServiceとPodのラベルの対応を確認するには、次のコマンドを使います。

kubectl get pods -n k8s-service -l app=nginx-service-demo --show-labels
kubectl describe service nginx-service -n k8s-service

さらに、接続先情報を管理するEndpointSliceも確認できます。

kubectl get endpointslices -n k8s-service -l kubernetes.io/service-name=nginx-service

今回のselectorを持つServiceでは、対応するPodのIPやポートなどの情報がEndpointSliceに反映されます。詳細を見る場合は、次のようにします。

kubectl get endpointslices -n k8s-service -l kubernetes.io/service-name=nginx-service -o yaml

PodのIPに対応する addresses、接続先の ports、`conditions.ready` などを確認してください。今回の設定で起動が完了していれば、3つのNginxのPodが接続先になります。Kubernetes

今回のNginxは、どのPodも同じ初期ページを返します。HTMLが表示されたことだけでは、3つすべてのPodへ順番に分散されたかどうかまでは確認できません。Serviceの分散を「毎回必ず順番に別のPodへ届く」と考えないようにしてください。

10. 接続できない場合の確認方法

Serviceの接続先がない

次の順に確認します。

kubectl get pods -n k8s-service --show-labels
kubectl describe service nginx-service -n k8s-service
kubectl get endpointslices -n k8s-service -l kubernetes.io/service-name=nginx-service -o yaml

特に、次の3点を確認してください。

  • Serviceと対象Podが同じNamespaceにあるか
  • Serviceの selector とPodのラベルが一致しているか
  • 対象PodがReadyになっているか

ポート番号が違う

今回の対応は、次のとおりです。

接続方法使用するポート
ClusterIPまたはService名へ接続8080
NodeのIPへNodePortで接続30080
NginxがPod内で待ち受ける80

Service名へ http://nginx-service:80/ で接続しても、今回のService設定では8080番を指定しているため、期待した接続になりません。

また、`targetPort: 80` は、Nginxが実際に80番ポートで待ち受けていることが前提です。`containerPort` の数字を書くだけで、アプリケーションの待受ポートが変更されるわけではありません。

IPでは接続できるが、名前では接続できない

クラスタ内の確認用Podで調べます。

kubectl exec netcheck -n k8s-service -- nslookup nginx-service
kubectl exec netcheck -n k8s-service -- cat /etc/resolv.conf

Service名のスペル、Namespace、Pod内のDNS設定、クラスタDNSの稼働状態を確認してください。Kubernetes

NodePortの30080番が使用済み

kubectl apply でポートの割り当てエラーが出る場合は、クラスタ内のServiceを確認します。

kubectl get services -A

30080番がほかのServiceで使用されている場合は、`service.yaml` の nodePort を未使用の番号へ変更し、再度反映してください。以降の接続先URLも、その番号へ変更します。

確認用Podが終了している

今回の netcheck は約1時間で終了します。

kubectl get pod netcheck -n k8s-service

終了済みなら、確認用Podを削除して作成し直します。

kubectl delete pod netcheck -n k8s-service
kubectl run netcheck -n k8s-service --image=busybox:1.37.0 --restart=Never --command -- sleep 3600
kubectl wait --for=condition=Ready pod/netcheck -n k8s-service --timeout=120s

11. LoadBalancer・ExternalName・Ingressとの違い

LoadBalancer

LoadBalancerは、対応する外部ロードバランサーと連携してServiceを公開する種類です。

クラウド環境などでは、ロードバランサーの作成や設定を行う実装と連携します。`type: LoadBalancer` を指定するだけで、どのクラスタでも外部IPが得られるわけではありません。

通常はClusterIPに加えてNodePortも割り当てられますが、実装に応じてNodePortを割り当てない設定もあります。Kubernetes

ExternalName

ExternalNameは、Service名に対して外部ホスト名のCNAMEを返す仕組みです。

例えば、クラスタ内のService名を、外部の api.example.com という名前へ対応付ける用途があります。ただし、Service自体が通信を中継するプロキシーになるわけではありません。

Ingress

Ingressは、主にHTTP・HTTPSのホスト名やパスに応じたルーティングを定義する、Serviceとは別のリソースです。

Ingressのルールを実際に処理するには、Ingress Controllerが必要です。ローカル環境でも、対応するControllerと到達経路を用意すれば利用できます。Kubernetes

今回の段階では、ClusterIPとNodePortの接続を理解したうえで、HTTPの公開方法を学ぶ次のテーマとして押さえておけば十分です。

12. 実習用リソースを削除する

port-forward が動作中なら、先に Ctrl + C で終了します。

次に、今回作成したリソースを削除します。

kubectl delete -f service.yaml
kubectl delete -f deployment.yaml
kubectl delete pod netcheck -n k8s-service

Serviceを削除するだけでは、接続先のDeploymentやPodは削除されません。それぞれ別のリソースとして扱います。

確認します。

kubectl get deployments,replicasets,pods,services -n k8s-service

今回の実習専用に作成したNamespaceで、ほかの用途に使っていないことを確認し、削除します。

kubectl delete namespace k8s-service

Namespace内にリソースが残っている場合は、それらも削除対象になります。PC上のYAMLファイルは残ります。

13. まとめ

この記事では、NginxのPodを3つ起動し、ClusterIP・名前解決・NodePortの通信を確認する手順を紹介しました。

  • Serviceは、Podへの接続先を提供する
  • selectorを持つServiceは、同じNamespace内のPodをラベルで選ぶ
  • ClusterIPは、Serviceが存在する間は維持される
  • 同じNamespaceでは、Service名だけで名前解決して接続できる
  • 別NamespaceのServiceには、Namespaceを含めた名前を使う
  • port はService側、`targetPort` は接続先Pod側のポート
  • NodePortでは、NodeのIPと nodePort を使って接続する
  • PCからNodePortへ接続できるかは、Nodeまでのネットワーク経路にも依存する

まずは「ServiceのIPで接続する」「名前で接続する」「Nodeのポートで接続する」の3つを、それぞれどこから試しているかと合わせて整理してください。

参考資料


コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です