Serviceとは
Serviceは、クラスター内で1つ以上のPodとして実行されているネットワークアプリケーションを公開する方法です。
なぜServiceが必要なのか
KubernetesのPodは、クラスターの状態に合わせて作成されたり削除されたりします。例えば、あるノードがダウンしてしまったときに、そのノードで動いていたPodを他のノードで再作成する場合などが考えられます。そのようなシナリオの場合、Pod自体に外部向けのIPアドレスを割り当てていると、ユーザーは新しく再作成されたPodのIPアドレスをいちいち取得して修正する必要があります。

そこでワンクッション挟んで、Serviceというリソースに外部向けIPアドレスを割り当て、このIPアドレスを固定しておくことでユーザーはPodの状態にかかわらず同一のIPアドレスでPodにアクセスすることができるようになります。

ServiceとPodの結び付け
ServiceとPodの結び付けは、基本的にはセレクターを用いて行われます。Podに付与したラベルを見て、適切なPodにトラフィックをルーティングします。

Serviceの作成方法
Podの時と同様、Serviceを作成する方法は二つあります。
- ワンライナーで作成する
 - マニフェストファイルから作成する
 
ワンライナーで作成する
まずは、以下のコマンドでnginxのPodを作成します。
kubectl run nginx-pod --image=nginx --port=80
次に、Serviceを作成します。
kubectl expose pod nginx-pod --type=NodePort --name=nginx-service --port=80 --target-port=80
解放されたポートを確認します。
$ kubectl describe svc nginx-service
Name:                     nginx-service
Namespace:                default
Labels:                   run=nginx-pod
Annotations:              <none>
Selector:                 run=nginx-pod
Type:                     NodePort
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.96.150.153
IPs:                      10.96.150.153
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
NodePort:                 <unset>  30127/TCP <= この値
Endpoints:                10.0.10.79:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>
Podが配置されたNodeのIPを確認します。
$ kubectl get po -o wide
NAME                    READY   STATUS    RESTARTS   AGE   IP            NODE          NOMINATED NODE   READINESS GATES
nginx-pod               1/1     Running   0          23m   10.0.10.79    10.0.10.71    <none>           <none>
上記の出力から、10.0.10.71のノードに配置されていることが分かります。
以上から、以下のコマンドでアクセスできるかを確認します。
$ curl 10.0.10.71:30127
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
接続できていることが分かります。
ServiceとPodの結びつきはセレクターとラベルで行うというお話をしましたが、ワンライナーで作成する場合、それらを指定していません。では、セレクターとラベルはどのように定義されているのでしょうか?
まず、Serviceのセレクターを確認します。
$ kubectl describe svc nginx-service
Name:                     nginx-service
Namespace:                default
Labels:                   run=nginx-pod
Annotations:              <none>
Selector:                 run=nginx-pod <= セレクター
Type:                     NodePort
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.96.150.153
IPs:                      10.96.150.153
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
NodePort:                 <unset>  30127/TCP
Endpoints:                10.0.10.79:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>
セレクターは、run=nginx-podになっていることが分かります。
次に、nginx-podのラベルを確認してみます。
$ kubectl describe po nginx-pod
Name:             nginx-pod
Namespace:        default
Priority:         0
Service Account:  default
Node:             10.0.10.71/10.0.10.71
Start Time:       Tue, 25 Jun 2024 02:38:24 +0000
Labels:           run=nginx-pod  <= ラベル
Annotations:      <none>
Status:           Running
IP:               10.0.10.79
IPs:
  IP:  10.0.10.79
Containers:
  nginx-pod:
    Container ID:   cri-o://c444353ec04d9a00a4ddcdede7932930799b31c9cd1ac84e20a56d6d6c65672c
    Image:          nginx
    Image ID:       e0c9858e10ed8be697dc2809db78c57357ffc82de88c69a3dee5d148354679ef
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Tue, 25 Jun 2024 02:38:25 +0000
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-62dd9 (ro)
Conditions:
  Type                        Status
  PodReadyToStartContainers   True
  Initialized                 True
  Ready                       True
  ContainersReady             True
  PodScheduled                True
Volumes:
  kube-api-access-62dd9:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:                      <none>
Podには、run=nginx-podのラベルが付与されていることが分かります。kubectl runコマンドでPodを作成したとき、Pod名を指定したと思いますが、それがそのままrun=nginx-podに来ています。Serviceを作成する際は、kubectl expose pod nginx-podでこのPodを指定しているので、セレクターにもrun=nginx-podが設定できています。
マニフェストファイルから作成する
kubectl run nginx-pod --image=nginx --port=80で作成した先ほどのPodに接続するためのServiceを、次はマニフェストファイルから作成してみます。
まずは、先ほど作成したServiceを削除します。
$ kubectl get svc
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
kubernetes      ClusterIP   10.96.0.1       <none>        443/TCP,12250/TCP   47d
nginx-service   NodePort    10.96.150.153   <none>        80:30127/TCP        79m
$ kubectl delete svc nginx-service
service "nginx-service" deleted
次に、以下のマニフェストファイルを作成します。
apiVersion: v1
kind: Service
metadata:
  name: nginx-nodeport-service
spec:
  type: NodePort
  selector:
    run: nginx-pod
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30007
spec.selectorには、Podのラベルであるrun=nginx-podを指定します。spec.ports.nodePortには、適当に30007を指定しました(基本的には30000-32767を割り当てます)。
このマニフェストファイルをクラスターに適用します。
$ kubectl apply -f nginx-service.yaml
service/nginx-nodeport-service created
接続を確認します。
$ kubectl get svc # 作成したServiceを確認
NAME                     TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)             AGE
kubernetes               ClusterIP   10.96.0.1      <none>        443/TCP,12250/TCP   47d
nginx-nodeport-service   NodePort    10.96.227.37   <none>        80:30007/TCP        6s
$ kubectl get po -o wide # Podが配置されたノードのIPアドレスを確認
NAME                    READY   STATUS    RESTARTS   AGE    IP            NODE          NOMINATED NODE   READINESS GATES
nginx-pod               1/1     Running   0          101m   10.0.10.79    10.0.10.71    <none>           <none>
$ curl 10.0.10.71:30007 # 接続を確認
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
kubectl exposeコマンドの時と同様、正しく接続できていることが分かります。
その他のServiceタイプ
今回は例としてNodePortを使いましたが、Serviceには以下のタイプがあります。
- ClusterIP
 - NodePort
 - LoadBalancer
 - ExternalName
 
ClusterIP
クラスター内部のIPでServiceを公開します。このタイプでは、クラスター内部からのみ接続できます。
NodePort
今回作成したServiceのタイプです。各ノードのIPにて、静的なポート上でServiceを公開します。
ServiceにClusterIPが作成されるので、クラスター内部からの通信の場合はこのServiceのClusterIPを使って通信します。
クラスター外部からの通信の場合は、PodがデプロイされたノードのIPとNodePortで公開しているポートを調べて、通信します。
LoadBalancer
Service用にロードバランサ―をプロビジョニングして、ロードバランサ―経由でアクセスするためのタイプです。クラウドプロバイダー上で使用すると、自動でそのクラウドプロバイダーが提供するロードバランサ―が作成され、Serviceに紐づきます。
マネージドのKubernetesを使う場合は特に何の事前準備もなく利用できますが、そうでない場合はクラウドプロバイダー毎に必要なクラウドコントローラーマネージャをインストールする必要があります。
このタイプでPodを公開した場合、Podがデプロイされたノードに関わらず、ロードバランサ―のパブリックIPアドレスでPodにアクセスすることができます。

ExternalName
クラスタ内の名前解決を、クラスタ外部のDNS名にマッピングするために使用されます。これによって、クラスタ内のアプリケーションが内部的に定義された名前を使用して、クラスタ外部のリソースにアクセスできるようになります。
まとめ
今回はServiceについて概要を説明しました。次回はPodのレプリカを維持するための、Replicasetについて説明したいと思います。