はじめに
KubernetesでPodやDeploymentを作成できても、それだけでアプリケーションへ安定して接続できるとは限りません。Podが作り直されると、PodのIPアドレスが変わることがあります。また、複数のPodを動かしている場合には、接続先をまとめる仕組みが必要です。
そこで使うのが、Serviceです。
この記事では、Nginxを3つのPodで起動し、次の順序で通信を確認します。
- ClusterIPでServiceを作成する
- クラスタ内の確認用Podから、ServiceのIPアドレスへ接続する
- IPアドレスの代わりに、Service名で接続する
- 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アドレスで接続先を提供する。標準の種類 |
| NodePort | Nodeの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.name | nginx-service | Serviceの名前 |
metadata.namespace | k8s-service | Serviceの作成先 |
type | ClusterIP | クラスタ内部向けの接続先を作る |
selector.app | nginx-service-demo | 対象Podを選択するラベル条件 |
port | 8080 | Serviceへ接続するときのポート |
targetPort | 80 | 転送先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
| 表示 | 今回の読み方 |
|---|---|
TYPE | ClusterIPとして作成されている |
CLUSTER-IP | Serviceに割り当てられたクラスタ内部向けの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-service | Service名 |
k8s-service | ServiceのNamespace |
svc | Serviceの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` を追加しました。
| ポートの種類 | 今回の番号 | 接続先 |
|---|---|---|
nodePort | 30080 | NodeのIPと組み合わせて指定するポート |
port | 8080 | クラスタ内でServiceへ接続するポート |
targetPort | 80 | Nginxが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でも確認できます。
ただし、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つを、それぞれどこから試しているかと合わせて整理してください。