kubernetesの基礎を学ぼう!~第2章 Service API~

2024/01/16に公開

こんにちは!ヒロケイと申します!

ここ最近kubernetesに興味を持ち始め、基礎知識を蓄えてきました。

そこで、本シリーズからkubernetesの概要について、Dockerをほんのちょっと触ったことがある初学者でも分かるように解説していきます。

本シリーズでは特に、Kubernetesが提供しているAPIリソースでできることを網羅していきます。

以下のような読者を想定しています。

想定する読者

  • コンテナ技術については知っているが、複数のコンテナを管理、運用する方法を探している方
  • Kubernetesについて勉強したいけど、何から始めれば良いかわからない方
  • 公式ドキュメントや技術書を読むのが面倒くさくて、全体像を掴みたい方

Dockerを使って開発環境を整えたりした経験があれば、難なく読み進められるかと思います。

このシリーズでKubernetesでできることについて大枠を理解していただき、公式ドキュメントや技術書、勉強会での理解のサポートができればとても嬉しく思います!

第1章~第4章まで理解すると得られるもの

  • Kubernetesを使うとできることの半分くらいが理解できる
  • 技術イベントや勉強会で出てくる言葉の意味を理解でき、内容が大体理解できるようになる。
  • APIリソースの全体像を掴むことができる

扱わないテーマ

  • kubernetes環境を構築する方法
  • コンテナオーケストレーションを実現する上でのテクニック
  • kubernetesが提供する詳細な機能

こちらはKubernetesの基礎を学ぼう!の第2章です。

前回の記事はこちらからご覧ください。

Service API

まずは、Service APIという塊が何を意味するのかを説明します。

Service APIの役割としては、

  • クラスタ上のコンテナに対するエンドポイントの提供
  • ラベルに一致するコンテナの発見
    を担っています。

Kubernetesクラスタには、複数のコンテナが起動しています。

そんな中、特定のコンテナにリクエストを送信したいと思う時があるでしょう。

そこでService APIを使うことで、リクエストの送信先を固定させたり、通信範囲(クラスタ外からの通信か、クラスタ内からなのか)を細かく設定することでクラスタ内のネットワーク構成を整備できます。

Service APIがないとどんなことになる?

では、Sevice APIがないとどんなことになるのかについて考えてみましょう。

前提として、一つのPodには一つのIPアドレスが割り当てられます。
同一Podのコンテナにはlocalhost宛に通信し、別Podへの通信にはPodのIpアドレス宛に通信を行います。

起動するPodのIPアドレスはそれぞれ異なるため、リクエストを均等に分けて負荷分散しようとすると、リクエストが飛んでくるたびに毎回PodのIPアドレスを調べたり、転送先の宛先を設定する必要があります。

そこでService APIを使うと、受信したトラフィックを複数のPod に自動的に負荷分散できます。

トラフィックとは?

trafficの直訳は、「交通」を意味する。

IT分野でのトラフィックは大きく分かれて2つに分かれている。

  • Web トラフィック
  • Network トラフィック

Web トラフィック

ウェブサイトやオンラインプラットフォームへのアクセスや閲覧の量を指す。
ウェブトラフィックは、特定のサイトが人気があるかどうかを示す指標として使用される。

Network トラフィック

データ通信ネットワークを介して送受信されるデータパケットの量や流れを指す。

ネットワークトラフィックは、ネットワークのパフォーマンスを監視し、最適化するために分析される。

Kubernetesやコンテナオーケストレーションにおけるトラフィックは、主にNetworkトラフィックを指す。
PodからPodにリクエストを送信した時のデータの流れがこれに当たる。

めちゃめちゃ便利ですね!

Service API 構成要素

Service APIが抱えるリソースは、以下の通りです。

理解する必要はありません!こんなものがあるんだなぁと思いながら眺めてみてください。

リソース名 機能
ClusterIP クラスタ内でアクセスできるIPアドレスを提供
NodePort クラスタ外からアクセスできるエンドポイントを提供
LoadBalancer NodePortで作成したエンドポイントのリクエストを負荷分散
Headless クラスタ内から特定のPodにアクセスするためのエンドポイントを提供
ExternalName 外部からの通信を可能にするための短縮されたエンドポイントを提供
None-Selector クラスタ内から任意のIPアドレスにアクセスできるエンドポイントを提供
Ingress ルーティングによって外部からのアクセスを制御

これらのAPIリソースで何が実現できるのかを知ることで、Kubernetesクラスタを構築する基礎力は間違いなく向上するでしょう!

それぞれどんなことができるリソースなのか、順番に見ていきましょう。

ClusterIP

最初に登場するServiceAPIリソースは、ClusterIPです。

ClusterIPの役割としては、クラスター内部からのみアクセスできるIPアドレスをPodに提供することです。

ユーザーから直接アクセスする必要がないPodに関しては、基本ClusterIPが設定されています。

具体的なPodの例としては、

  • Postgresなどのデータベースサーバー
  • 一時的に情報を保持し、サーバーのパフォーマンスを向上させるキャッシュサービス
  • マイクロサービスアーキテクチャにおけるサービス
    などが挙げられます。

複数のPodを運用しているケースはどうなるの?

PodにIPアドレスを提供できるのがClusterIPですが、ReplicaSetやDeploymentなどで複数のPodを起動している場合はどうなるのでしょうか?

ReplicaSet, Deploymentとは?

複数Podを運用しているケースについて具体的なイメージが湧かない場合は、Podに関するWorkload APIに関する記事をご覧ください!
Kubernetesに関する前提知識がなくても読めますよ〜
https://zenn.dev/keigo_hirohara/articles/423dc9944ec0f0

答えは、複数のPodを束ねて一つのエンドポイントを提供してくれるようになります。

では、Podのエンドポイントを束ねた場合、どのPodへリクエストを送信するのでしょうか?

基本的には、自動的に均等にリクエストを送信するようにしてくれます。

ラウンドロビン方式というキーワードがこれに当たります。

作ってみる

では、実際にClusterIPのマニフェストを作り、エンドポイントを確認してみましょう!

clusterip.yaml
// ClusterIPを作るためのPodを定義
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp-container
        image: nginx:latest
        ports:
        - containerPort: 80
---
// ClusterIPを定義
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: ClusterIP

$ kubectl get po
NAME                                READY   STATUS    RESTARTS   AGE
myapp-deployment-774767dd4f-2v8lb   1/1     Running   0          8s
myapp-deployment-774767dd4f-7577k   1/1     Running   0          8s
myapp-deployment-774767dd4f-nsx9b   1/1     Running   0          8s

$ kubectl get service
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
myapp-service   ClusterIP   10.96.109.255   <none>        80/TCP    86s

これで、ClusterIPを作ることができました!

次に、ClusterIPによってIPアドレスが提供されていることを確認してみましょう!

提供されたIPアドレスを確認
$ SERVICE_IP=$(kubectl get svc myapp-service -o=jsonpath='{.spec.clusterIP}')
echo "Service IP: $SERVICE_IP"
Service IP: 10.96.109.255

Podにアクセスし、リクエストを送ってみる
$ kubectl run -i --tty --rm debug --image=alpine -- sh
If you don't see a command prompt, try pressing enter.
/ # wget -qO- http://$SERVICE_IP
<!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>

これで、クラスタ内通信におけるIPアドレスを提供できました。

では、クラスタ外部通信におけるIPアドレスを提供するには、どうすれば良いのでしょうか?

次のセクションで見ていきましょう。

NodePort

クラスタ外からの通信を可能にする機能を提供をしてくれるのは、NodePortというリソースです。
NodePortとは名前の通り、クラスタ上で稼働しているNode上で指定したポートを公開してくれるリソースです。

クラスタ外部からノードのIPアドレスと指定したポートを使用することでアクセスできるようになります。

また、NodePortはClusterIPの機能を備えています。
NodePortが作られたら、同時にClusterIPも作られるのです!

作ってみる

nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp-nodeport-service
spec:
  selector:
    app: myapp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
  type: NodePort
$ kubectl apply -f ./nodeport.yaml
service/myapp-nodeport-service created

$ kubectl get service
myapp-nodeport-service   NodePort    10.96.193.184   <none>        80:31727/TCP   2m1s

ここでいう、80:31727/TCPの80が公開されているPortです。

LoadBalancer

次に紹介するのが、LoadBalancerです。

役割はNodePortと似ていて、クラスタ外部から通信可能なエンドポイントを提供してくれるのが機能です。
機能としては名前から想像できるように、外部からの通信を複数の場所に分散してくれる機能を持ちます。

NodePortとの違い

NodePortで外部通信ができるし、LoadBalancerも外部通信ができる。
なら、両者の違いはなんなのでしょうか?

それは、ロードバランシング機能です。
kubernetesクラスタは、内部にNodeがあり、その中にPodが格納されています。
特に、クラスタの中に複数のNodeが格納されている場合を考えてみましょう。(マルチノードクラスタ)
NodePortでサービスを外部公開すると、NodeのIPアドレスとPortを使うことでPodにアクセスできます。

なので、3つのNodeを含んだクラスタ上でNodePortを作成すると、エンドポイントが3つ作成されることになります。

もし、一つのNodeにアクセスしている場合、そのNodeが障害を起こした場合はクラスタにアクセスできなくなってしまいます。

またマルチノードクラスタの場合は、アクセスされていないNodeが発生してしまいますよね?

そこで、一部のNodeが稼働できなくなってもクラスタにアクセスできるよう、リクエストを分散することで解決できます。

ここで登場するのがLoadBalancerです。

外部疎通性のあるエンドポイントを一つ作り、公開されているNodePortを束ねることで、それぞれのNodeに均等にリクエストが飛んでくるようにすることができます。

NodePortにリクエストを捌く機能を追加したものがLoadBalancerであるので、LoadBalancerを作成したときには自動的にNodePort, ClusterIPも作成されるようになります。

作ってみる

loadbalancer.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: my-app-container
          image: nginx:latest
---
apiVersion: v1
kind: Service
metadata:
  name: my-headless-service
spec:
  clusterIP: None
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

$ kubectl apply -f ./loadbalancer.yaml
service/myapp-loadbalancer-service created

$ kubectl get service
myapp-loadbalancer-service   LoadBalancer   10.96.233.230   <pending>     80:32640/TCP   24s

$ kubectl get endpoints my-headless-service
NAME                  ENDPOINTS                                            AGE
my-headless-service   10.244.0.93:9376,10.244.0.94:9376,10.244.0.95:9376   5m20s

Headless Service

// スナイパーのように、特定のPodにクロスヘアが当たっている様子をイラスト

今までは、複数のPodを外部公開するために必要なリソースを取り扱ってきました。
しかし、Service APIの機能はそれだけではありません。

ここからは、Pod間の通信を行うために便利な機能についてご紹介します。

まずは、Headless Serviceです。

Headless Serviceは、Podに直接アクセスするためのエンドポイントを提供してくれるものです。

ClusterIPが提供してくれるエンドポイントは仮想のIPアドレスなのに対し、Headless Serviceは固有のDNSエントリを提供してくれます。

Headless Serviceのメリットは何なのでしょうか?

それは、特定のPodと直接通信することが可能になる点です。

StatefulSetなどの情報を保持しているPodにアクセスしたいとき、Headless Serviceを用意することで、特定のStatefulSetにアクセスでき、情報の永続性を確保できます。

なぜなら、エンドポイントが常に同じなので、通信先のStatefulSetを固定できるからです。

作ってみる

実際にHeadless Serviceを作り、エンドポイントが提供できていることを確認してみましょう!

headless.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-headless-service
spec:
  clusterIP: None
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
$ kubectl get services my-headless-service
NAME                  TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
my-headless-service   ClusterIP   None         <none>        80/TCP    44s

ExternalName

ExternalNameは、Cluster内のPodに対して、外部からの通信を可能にするための短縮されたエンドポイントを提供するService APIのリソースです。

外部のサービスやホストに対してDNSエイリアスを提供し、Kubernetesクラスタ内からその外部サービスにアクセスできるようになります。

ExternalNameを使用することで、Kubernetes内のPodは外部のサービスを Cluster 内のリソースと同様にアクセスできます。

これは、特定のサービスに対してクラスタ外からアクセスする必要がある場合に便利です。

作ってみる

具体的なExternalNameの使用例を見てみましょう。

externalname.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-external-service
spec:
  type: ExternalName
  externalName: example.com
  ports:
    - protocol: TCP
      port: 80

上記の例では、my-external-serviceというExternalNameサービスが作成され、example.comという外部ホストに対してTCPポート80でアクセスできるようになります。

None-Selector

None-Selectorは、特定のラベルセレクターがない(selectorがselector: {}となっている)Service APIのリソースです。通常のServiceリソースは、ラベルセレクターを指定して特定のPodを対象にしますが、None-SelectorはどのPodにも関連付けられず、クラスタ内の任意のIPアドレスにアクセスすることができます。

None-Selectorを使用すると、クラスタ内のあらゆるIPアドレスにアクセスする単一の仮想IPアドレスが提供されます。これは、特定のPodに直接アクセスする必要がなく、クラスタ内の任意の場所に通信を行いたい場合に便利です。

作ってみる

None-Selectorの使用例を見てみましょう。

noneselector.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-none-selector-service
spec:
  selector: {}
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 80

上記の例では、my-none-selector-serviceというNone-Selectorサービスが作成され、クラスタ内の任意の場所にTCPポート8080でアクセスできるようになります。

Ingress

Ingressは、クラスタ内のサービスに対する外部からのアクセスを制御するためのリソースです。Ingressを使用すると、HTTPおよびHTTPSトラフィックをクラスタ内のサービスにルーティングできます。通常、Ingressリソースを作成すると、クラスタ内にIngressコントローラがデプロイされ、外部からのトラフィックを適切なサービスに転送します。

Ingressは、複数のパスやホストベースのルーティング、SSL証明書の終端など、高度なトラフィック制御を提供します。

作ってみる

具体的なIngressの使用例を見てみましょう。

ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
spec:
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /app
        pathType: Prefix
        backend:
          service:
            name: myapp-service
            port:
              number: 80

上記の例では、my-ingressというIngressリソースが作成され、myapp.example.comのホストに対して/appへのパスがルーティングされます。このリクエストは、クラスタ内のmyapp-serviceサービスに転送されます。

まとめ

今回は、クラスタ内外でのPodの通信を細かく設定できる、Service APIについて紹介しました!
かなりボリューミーでしたが、これらの機能を理解できれば、実務でKubernetesを扱うための基礎力はかなりついてきるはずです!
お疲れ様でした!

いかがでしたでしょうか?

次回は、Config, Storage APIです。
クラスタを運用する上でとても重要になってくるリソースです!ぜひ読んでみてくださいね(^^)
kubernetesの基礎を学ぼう!~第3章 Config, Storate API~

Discussion