Kubernetes(以下k8s)のServiceのタイプの違いをまとめていきます。
Serviceとは何ぞやという方は、以前の記事もご覧ください。
もくじ
1.ClusterIP (概要/IPアドレスの割り当て/ClusterIPタイプのService生成方法)
3.LoadBalancer (概要/LoadBalancerタイプの強み)
4.ExternalName (概要/ExternalNameタイプのService生成方法/ExternalNameタイプの強みと使いどころ)
■Serviceの種類一覧
Serviceには以下のとおり種類があり、
それらを使い分けることによって柔軟なコンテナアクセスを実現できる。
各Serviceの特徴を簡単にまとめると、以下のようになる。
・ClusterIP:一番基本となるService。クラスタ内部でpodにアクセスを振り分ける。
・NodePort:外部から直接アクセスできるService。
・LoadBalancer:クラウドプロバイダのLBと連携し、より可用性の高いロードバランシングを実現するServce。
・ExternalName:外部ドメインへアクセスできるService。
・Headless: PodのIPアドレスを直接返してくれるService。
Serviceは、いずれもL4レイヤでのロードバランシング機能を提供するものである。
ちなみに、Serviceと似たような機能として、Ingressというリソースもよく出てくるが、
IngressはL7レイヤでのロードバランシング機能を提供する。
※Ingressについては以下を参照。
https://ameblo.jp/bakery-diary/entry-12612002466.html
以下、各Serviceの特徴をみていく。
■Cluster IP
◎概要
k8sクラスタの基本となる種類のService。
ClusterIPを生成すると、クラスタ内でのみ疎通できる仮想IPが、Serviceに割り当てられる。
ClusterIP宛の通信は、kube-proxyというWorker Node上で動作するシステムによって、
同じラベルが貼られたpodに転送される仕組みになっている。
クラスタ外からアクセスされない箇所などで、クラスタ内LBとして利用されることが多い。
イメージは以下の通り。
◎ClusterIPタイプのService生成方法
マニフェストファイルのspec.typeに、ClusterIPを指定する。
その際、spec.ports[].portには、Cluster IPで受け付けるポート番号を、
spec.ports[].targetPortには、転送先のコンテナのポート番号を指定する。
<例>
apiVersion: v1 kind: Service metadata: name: clusterip-example spec: type: ClusterIP ports: - name: http protocol: TCP port: 8080 targetPort: 80 selector: app: example1
◎IPアドレスの割り当て
ClusterIPは、デフォルトではk8sにより自動で割り当てられるようになっている。
基本的には自動割り当てで問題ないはずだが、
必要に応じて固定のIPアドレスを手動で割り当てることもできる。
手動で割り当てたい場合は、以下のように、マニフェストファイルに静的にアドレスを指定すればよい。
spec:
type: ClusterIP
clusterIP: 10.96.0.20
なお、ClusterIPを生成後、更新はできない。
従って更新したい場合は、削除した後に再生成する必要がある。
■NodePort
◎概要
Worker NodeのIPアドレスと、あらかじめ指定したポート番号で、外部からアクセスできる特徴をもつService。
(Worker NodeのIPが192.168.0.100、指定ポート番号が35358の場合、http://192.168.0.100:35358 で外からアクセスできるようなイメージ)
なお、NodePortタイプのServiceで受信したトラフィックは、クラスタ内部に転送され、適切なpodに振り分けられる必要がある。
そのため、NodePortタイプのService生成時には、クラスタ内部への転送先としてClusterIPも自動的に割り当てられ、さらにそこから適切なpodに転送されるという仕組みになっている。
例えばこんなかんじ。
■LoadBalancer
◎概要
クラウドプロバイダ(GCP、AWS、Azure、OpenStack等)が提供するLBを利用して、クラスタ外部に公開できる特徴をもつ。
アクセスの流れとしては、
①外部から来たアクセスを、外部LBにて受信。
②外部LBと、k8sのLoadBalancerタイプのServiceの連携により、外部アクセスが各Worker NodeのNodePortを宛先としてバランシングされる。
③NodePortからCulsterIPへ転送され、さらにPod、コンテナへと転送されていく。(NodePortの章で解説したのと同様の流れ。)
◎NodePortタイプとLoadBalancerタイプの違い
NodePortタイプも、LoadBalancerタイプも、外部からのアクセスをClusterIPタイプのサービスに振り分けるという意味では同じである。
両者の違いを整理したい。
NodePortタイプでは、Worker Noeに割り当てられたIPを宛先として、直接通信を行う。
そのWoker Nodeがお亡くなりになった場合、他のWorker Nodeが生きていたとしても、Serviceにアクセスできず、障害となってしまう。
対してLoadBalancerタイプであれば、他のWorker Nodeが生きている限り、
k8sのノードとは別に存在する外部LBが、生きているほうへバランシングしてくれる。
従って、全体としての可用性を落とさずに済む。
LoadBalancerタイプの弱みとしては、外部LBを使うので費用がかかることが挙げられる。
とはいえメリットが大きいので、プロダクション環境ではLoadBalancerタイプをきちんと使ったほうが良いだろう。
■ExternalName
◎概要
通常のServiceでは、podのラベル指定によって、クラスタ内部へのトラフィックの転送先を決める。
一方、ExternalNameタイプのServiceでは、外部ドメイン宛のCNAME指定によって、外部サイトへのマッピングが行われる。
※CNAMEや、DNSの仕組みを知りたい方はこちらの記事もどうぞ
・【基礎】DNSの仕組みを知る① (DNSとは?DNSサーバの階層構造とは?DNコンテンツサーバとDNSキャッシュサーバとは?などを解説)
・【基礎】DNSの仕組みを知る② (DNSレコード設定方法、名前解決のためのクライアント側の設定方法などを解説)
ラベルではなくDNSのCNAMEレコードを利用すること、
そもそもクラスタ内部への転送ではなく外部サイトにアクセスすることから、
これまでに紹介したServiceの性格とは大きく異なる。
◎ExternalNameタイプのService生成方法
マニフェストファイルのspec.typeに「ExternalName」を指定するに加えて、
spec.externalNameに、宛先ドメインのCNAMEを記述する必要がある。
spec.externalNameで宛先を指定してしまえばマッピングできるため、
ClusterIPなど内部へ転送する際に必要だった、spec.ports[]の指定は不要である。
<例>
apiVersion: v1 kind: Service metadata: name: clusterip-example spec: type: ExternalName
externalName: external.com
◎ExternalNameタイプの強みと使いどころ
ExternalNameタイプの強みを考える前に、CNAMEレコードが何のためにあるかを確認しておく。
CNAMEレコードは、DNSの名前解決の仕組みにおいて、正式なドメイン名の別名として定義できる。
IPまたは正式なドメイン名に変更があったとしても、CNAMEを利用していれば、
変更修正は、IP⇔正式なドメイン名の対応関係テーブルの1か所だけ済む。
CNAMEを利用している処理には影響が及ばず、疎結合を実現してくれるのである。
ExternalNameタイプのServiceは、このCNAMEを利用している。
そのため、万一、CNAMEのもととなる、IPアドレスまたは正式なドメイン名に変更が生じても、
特にソースを書き換える必要なく、そのまま動かすことができる。
ちなみに、ExternalNameタイプのServiceを介さなくても、podから直接外部に通信することも可能ではある。
だが、外部アクセスに関し、何かの変更が生じた場合、個々のPodに修正を入れるのは大変である。
そこで、ExternalNameタイプのServiceを間に仲介させておき、
何かあったときに、Serviceだけ修正すれば良い状態にしておくほうが良いだろう。
■Headless
◎概要
ServiceとしてのIPアドレスを持たず、個々のPodのIPアドレスを直接返却するService。
通常のClusterIPタイプのServiceと図で比べてみる。
<通常のClusterIP>
<Headless>
通常、ServiceはClusterIPを持ち、複数Podに対するロードバランシングを行う機能を提供する。
従って、Serviceにアクセスする側(クラスタ内部のアクセス元)からは、Pod個々のIPアドレスは見えておらず、ServiceのClusterIPアドレスのみが見えている形となる。
一方、Headlessの場合は、ClusterIPを持たず("None"と指定)、複数Podに対するロードバランシング機能も提供しない。
その代わり、DNSラウンドロビン(DNS RR)形式で、対応するPodのIPを、直接アクセス元に返却する。
アクセス元は返却されたIPをもとに、直接Podへアクセスする形となる。
Headlessの仕組みを利用する際は、一般的なDNS RRの注意点(参考記事:【基礎】DNSの仕組みを知る)同様、
効率的な負荷分散にならないこともあるので気を付けるべき。
このHeadlessの使いどころがよく分からない…けど、
たぶん何かしら意義があるからこそ存在するタイプなはず。。
■参考文献
・公式
https://kubernetes.io/docs/concepts/services-networking/service/
https://kubernetes.io/docs/tasks/access-application-cluster/service-access-application-cluster/
https://kubernetes.io/docs/tutorials/kubernetes-basics/expose/expose-intro/
・青山真也著『Kubernetes完全ガイド』