⑭１日１０分で理解するコンテナ技術入門 - Serviceとは -



# Serviceとは
Serviceは、クラスター内で１つ以上のPodとして実行されているネットワークアプリケーションを公開する方法です。
## なぜServiceが必要なのか
KubernetesのPodは、クラスターの状態に合わせて作成されたり削除されたりします。例えば、あるノードがダウンしてしまったときに、そのノードで動いていたPodを他のノードで再作成する場合などが考えられます。そのようなシナリオの場合、Pod自体に外部向けのIPアドレスを割り当てていると、ユーザーは新しく再作成されたPodのIPアドレスをいちいち取得して修正する必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/bd23b5b6-9e61-0b64-f28d-47099dbd8d45.png)


そこでワンクッション挟んで、Serviceというリソースに外部向けIPアドレスを割り当て、このIPアドレスを固定しておくことでユーザーはPodの状態にかかわらず同一のIPアドレスでPodにアクセスすることができるようになります。
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/39f39ffb-24d6-abb8-17a4-8eafaf6d081a.png)
## ServiceとPodの結び付け
ServiceとPodの結び付けは、基本的にはセレクターを用いて行われます。Podに付与したラベルを見て、適切なPodにトラフィックをルーティングします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/ac4cb192-7a39-f784-5706-c3e5b2b7b282.png)

# Serviceの作成方法
Podの時と同様、Serviceを作成する方法は二つあります。
1. ワンライナーで作成する
1. マニフェストファイルから作成する
## ワンライナーで作成する
まずは、以下のコマンドでnginxのPodを作成します。
```bash
kubectl run nginx-pod --image=nginx --port=80
```
次に、Serviceを作成します。
```bash
kubectl expose pod nginx-pod --type=NodePort --name=nginx-service --port=80 --target-port=80
```
解放されたポートを確認します。
```bash
$ 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を確認します。
```bash
$ 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`のノードに配置されていることが分かります。
以上から、以下のコマンドでアクセスできるかを確認します。
```bash
$ 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のセレクターを確認します。
```bash
$ 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のラベルを確認してみます。
```bash
$ 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を削除します。
```bash
$ 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
```

次に、以下のマニフェストファイルを作成します。
```yaml:nginx-service.yaml
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を割り当てます）。

このマニフェストファイルをクラスターに適用します。
```bash
$ kubectl apply -f nginx-service.yaml
service/nginx-nodeport-service created
```

接続を確認します。
```bash
$ 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にアクセスすることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/3b21d370-b228-80d1-b4fe-529a84a741ba.png)

## ExternalName
クラスタ内の名前解決を、クラスタ外部のDNS名にマッピングするために使用されます。これによって、クラスタ内のアプリケーションが内部的に定義された名前を使用して、クラスタ外部のリソースにアクセスできるようになります。

# まとめ
今回はServiceについて概要を説明しました。次回はPodのレプリカを維持するための、Replicasetについて説明したいと思います。