はじめに
こんにちは!
本記事は「本気で学ぶKubernetes」シリーズの第5回です。このシリーズでは、Kubernetesの基礎から実践まで、段階的に学んでいきます。
このシリーズは、第1回から順に読むことで体系的に学べる構成にしています。
まだご覧になっていない方は、ぜひ最初からご覧ください!
今回はServiceタイプについて深掘りして、それぞれの仕組みと使い分けを整理していければと思います。
この記事は人間がKubernetesの公式ドキュメントを読み漁りながら、人間の手で書いていますのでご安心ください!
前提知識
- Kubernetes、Pod、Deployment、Serviceの基本概念を理解している方
Serviceとは(おさらい)
KubernetesにおけるServiceとは、 クラスター内で1つ以上のPodとして実行されているアプリケーションを内部や外部に公開する方法です。
以前の記事でServiceはPodへのアクセスを抽象化するための概念と記載しましたが、さらに詳しくいうと公式ドキュメントにも記載のある通り、Podを外部公開するための機能になります。
Podは独自のIPアドレスを保有しており、Podが破棄・削除されてIPアドレスが変更されてしまった場合でも問題なくアクセスできるようにします。
Serviceタイプの全体像
Kubernetesには主に4つのServiceタイプがあります。
| タイプ | 説明 | 公開先 | 主な用途 |
|---|---|---|---|
| ClusterIP | クラスター内でのみアクセス可能な内部通信の制御 | 内部向け | Pod間の通信、クラスター内のアクセスなど |
| NodePort | 各ノードの特定ポート(30000-32767)を開放して外部アクセス可能にする | 外部向け | 簡易的な外部公開 |
| LoadBalancer | クラウド環境のロードバランサーを作成して外部公開 | 外部向け | 本番環境でのWebアプリケーション公開 |
| ExternalName | 外部のDNS名にマッピング(CNAME レコードを返す) | - | 外部サービスへのエイリアス作成 |
出典: Kubernetes公式ドキュメント - Service
ClusterIP
ClusterIPは、クラスタ内部でのみアクセス可能なServiceタイプです。
マニフェストでtypeを指定しない場合はデフォルトで利用されます。
外部からは直接アクセスできませんが、クラスタ内の他のPodからはこのServiceを経由してアクセスできます。
出典: Kubernetes公式ドキュメント - Service type: ClusterIP
仕組み
ClusterIPを作成すると、Kubernetesの内部のアドレスプールに仮想IPアドレスが割り当てられます。
Serviceを作成するタイミングで、KubenetesのAPI Serverからkube-proxyを呼び出して、この仮想IPへのアクセスを実際のPodにルーティングするためのiptablesのルールを生成するようになっています。
そのiptablesがNodeに適用されることで、Podへのアクセスが可能になるという仕組みです。
出典: Kubernetes公式ドキュメント - Virtual IPs and Service Proxies
マニフェスト
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
上記のマニフェストを適用すると、my-serviceという名前でアクセスできるServiceが作成されます。
ユースケース
ClusterIPは主にクラスタ内部でのサービス間通信に使われます。
具体例として以下のようなユースケースが挙げられます。
- フロントエンドから外部非公開のバックエンドAPIにアクセスする場合
- 同じクラスター内に構成したデータベースへアクセスする場合
フロントエンドからバックエンドAPIへのアクセスをする場合は、フロントエンド側のPodがbackend-service(ClusterIP)を経由してバックエンドPodへアクセスします。
また後者のユースケースにおいても、同じクラスター内に構成したデータベースは外部に公開する必要はありませんので、ClusterIPでの内部通信で十分そうですね。
つまり、内部に閉じる通信だけであればClusterIPを使って構成するのが一般的だと思います。
NodePort
NodePortは、各ノードの特定のポートを開放して、クラスタ外部からアクセスできるようにするServiceタイプです。
ClusterIPの機能に加えて、ノード上のポートを使った外部アクセスが可能になります。
指定できるのは30000-32767の範囲です。
出典: Kubernetes公式ドキュメント - Service type: NodePort
仕組み
NodePortを作成すると、ClusterIPも同時に作成されます。その上で、全ノードで同じポート番号が開放されます。
デフォルト設定では30000-32767の範囲から自動的に割り当てられますが、明示的に指定することも可能のようです。
トラフィックの流れは<NodeIP>:<NodePort> → ClusterIP → Podというルーティングになり、適切なPodにトラフィックが届くようになっています。
マニフェスト例
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector:
app.kubernetes.io/name: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
nodePort: 30007
前回の記事ではminikubeコマンドでブラウザを開いてアクセスすることを確認しましたが、その際に使用していた機能です。
ユースケース
NodePort単体だとは主に開発環境やテスト環境で使われます。デフォルトだと30000台のポート番号が使われるということで、標準的なネットワーク設定だと管理が煩雑になりますし、これだけで本番公開することはあまりないと思います。
インターネット公開するような本番環境では後述するLoadBalancerを使う方が適切ですね。(内部的にはNodePortも使用されます)
出典: Kubernetes公式ドキュメント - NodePort
LoadBalancer
LoadBalancerは、クラウドのロードバランサーを自動的に作成して外部公開するServiceタイプです。
主に本番環境でWebアプリケーションやAPIサーバーを公開する際に使用します。
出典: Kubernetes公式ドキュメント - Service type: LoadBalancer
仕組み
LoadBalancerを作成すると、前述したClusterIPとNodePortが自動的に作成されます。その上で、クラウドが提供するロードバランサー(GKEならCloud Load Balancing、EKSならNetwork Load Balancerなど)が作成されて、外部IPアドレスが割り当てられます。
トラフィックの流れは、外部ロードバランサー → NodePort → ClusterIP → Podというルーティングになります。
マニフェスト例
apiVersion: v1
kind: Service
metadata:
name: my-loadbalancer-service
spec:
type: LoadBalancer
selector:
app.kubernetes.io/name: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
ユースケース
エンドユーザーがアクセスするためのWebアプリケーションの公開や、外部の開発者やパートナーが利用する公開されたREST API、その他SaaSサービスでの利用が想定されます。
クラウドのロードバランサー自体は従量課金でコストが発生します。Serviceごとにロードバランサーが作成されるということで厳格に管理していくべき箇所になります。
出典: Kubernetes公式ドキュメント - LoadBalancer
ExternalName
最後に、ExternalNameについても簡単に説明します。
ここまでご紹介したようなルーティング制御というよりはDNSのCNAMEレコードを設定する機能になります。
例えば外部のデータベースを使っている場合にKubernetes内からmydb.default.svcといった名前でアクセスしたい場合に使います。
DNSのエイリアスを作るための機能というイメージで十分だと思います。
マニフェスト例
apiVersion: v1
kind: Service
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com
上記の場合に、my-service.prod.svc.cluster.localでホスト名検索すると、クラスター内のDNSサービスはCNAMEとして登録されたmy.database.example.comのレコードを返します。
これのメリットとしては、ExternalNameを書き換えることで使用するDBやAPIを開発環境や本番環境で使い分けれる部分にあります。
出典: Kubernetes公式ドキュメント - Service type: ExternalName
Serviceの内部動作について
ここからは少し踏み込んで、Serviceがどのように動作しているのか内部の仕組みを見ていきたいと思います。
kube-proxyの役割
ClusterIPの説明の中でkube-proxyについて触れました。
これはServiceの設定を参照して、仮想IPへのアクセスとPodにルーティングするためiptablesを自動で作成するための機能です。
kube-proxyにはいくつか動作のモードがありますが、ここではiptablesついて触れます。
iptablesモードは、文字通りiptablesルールを使ってトラフィックを転送します。
ServiceのCluster IPへリクエストを送信すると、iptablesのルーティングのルールに従って対象PodのIPにトラフィックが転送されます。
シンプルで使いやすそうですが、Serviceやエンドポイントが大量にある場合はiptablesのルールが大量になるので性能劣化の懸念もあるようです。
sessionAffinity
デフォルトでは、Serviceは毎回異なるPodにリクエストが転送されるようになっています。
しかし同じクライアントからのアクセスは同じPodにアクセスさせたいという要件も多いと思いま。
その際に使えるのがこのsessionAffinityの機能です。
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
type: ClusterIP
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800 # 3時間
selector:
app: web
ports:
- protocol: TCP
port: 80
targetPort: 8080
上記のようにsessionAffinity: ClientIPを設定すると、同じクライアントのIPアドレスからのリクエストは、常に同じPodに転送されるようになります。
その他にもいろいろなモードや機能設定ができますので公式ドキュメントもご確認ください。
出典: Kubernetes公式ドキュメント - Virtual IPs and service proxies
まとめと次回予告
今回は、Serviceの4つのタイプについて確認しました。
内部通信専用のClusterIP、開発環境向けのNodePort、本番環境向けのLoadBalancer、エイリアス設定のExternalNameという部分だけは最低限理解しておいた方が良いと思います。
次回は「Secret」や「Volume」について確認していきたいと思います。
それでは、また明日!
