KubernetesのDiscovery&LBリソース(その2)
Discovery&LBリソース(その2)
前回に続いて、KubernetesのDiscovery & LBリソースに含まれるリソースを紹介していきます。
リソースの分類 | 内容 |
---|---|
Workloadsリソース | コンテナの実行に関するリソース |
Discovery&LBリソース | コンテナを外部公開するようなエンドポイントを提供するリソース |
Config&Storageリソース | 設定・機密情報・永続化ボリュームなどに関するリソース |
Clusterリソース | セキュリティやクォータなどに関するリソース |
Metadataリソース | リソースを操作する系統のリソース |
Discovery&LBリソース
- Service
- ClusterIP
- NodePort
- LoadBalancer
- ExternalIP
- ExternalName
- Headless(None)
- Ingress
Headless Service
KubernetesのHeadless Serviceとは、PodのIP Addressが返ってくるServiceです。通常はPodのIP Addressはすぐ変動することが多いため、永続性の高いStatefulSetのみで利用可能となっています。前回の記事で紹介した他のServiceで負荷分散のために提供されるエンドポイントは、VIP的な動作をするような複数Pod宛のIPエンドポイントでした。
Service | エンドポイント |
---|---|
ClusterIP | Kubernetes Cluster内でのみ疎通可能なVIP |
ExternalIP | 特定のKubernetes NodeのIP |
NodePort | 全Kubernetes Nodeの全IP(0.0.0.0) |
LoadBalancer | クラスタ外で提供されているLoadBalancerのVIP |
一方でHeadless ServiceはIPエンドポイントを提供せず、DNS Round Robin(DNS RR)を使ったエンドポイントを提供します。Headless ServiceのDNS RRでは、転送先のPodのIPアドレスがクラスタ内DNSから返ってくる形で負荷分散が行われるため、クライアント側でのキャッシュなどに注意する必要があります。
基本的にKubernetesは、PodのIP Addressを意識する必要はないように作られているため、PodのIP AddressをディスカバリするにはAPIを叩くしかありません。しかし、Headless Serviceを利用することで、StatefulSetに限りService経由でIP Addressをディスカバリすることが可能です。
LoadBalancer Serviceの作成
Headless Serviceを作成するには、下記の3つが条件となります。
- Serviceのspec.typeがClusterIPであること
- Serviceのmetadata.nameがStatefulSetのspec.serviceNameと同じであること
- Serviceのspec.clusterIPがNoneであること
いずれかの条件が満たされていない場合は、ただのServiceとしてしか動作せず、Pod名を引くことができません。具体的には下記のような定義ファイルを作成する必要があります。
apiVersion: v1 kind: Service metadata: name: sample-svc spec: type: ClusterIP clusterIP: None ports: - name: "http-port" protocol: "TCP" port: 80 targetPort: 80 selector: app: sample-app # Headless Serviceを作成 kubectl apply -f ./headless_sample.yml
Headless Serviceによるpod名の名前解決
通常はクラスタ内DNSでpodの名前解決をすることは出来ない仕様となっています。Serviceを作成した際には複数Podに対するエンドポイントが払い出され、そのエンドポイントに対しての名前解決は提供されていますが、個々のPodの名前解決は出来ません。
通常Serviceの名前解決は[Service名].[Namespace名].svc.[domain名]で引けるようになっていますが、Headless Serviceで正引きを行うとDNS Round RobinでPodのどれかのIPが返ってくるため、負荷分散には向かなくなります。
# dig sample-svc.default.svc.cluster.local …… ;; QUESTION SECTION: ;sample-svc.default.svc.cluster.local. IN A ;; ANSWER SECTION: sample-svc.default.svc.cluster.local. 30 IN A 10.8.0.30 sample-svc.default.svc.cluster.local. 30 IN A 10.8.1.34 sample-svc.default.svc.cluster.local. 30 IN A 10.8.2.55 ……
ReplicaSetなどのリソースでも、上記のようにDNS Round RobinでIP Addressが返ってくるようにすることは可能です。しかし、StatefulSetの場合のみ、[Pod名].[Service名].[Namespace名].svc.[domain名]という形式で、下記のようにPod単位での名前解決を行えるようになっています。
# dig sample-statefulset-0.sample-svc.default.svc.cluster.local …… ;; QUESTION SECTION: ;sample-statefulset-0.sample-svc.default.svc.cluster.local. IN A ;; ANSWER SECTION: sample-statefulset-0.sample-svc.default.svc.cluster.local. 30 IN A 10.8.0.30 ……
なお、コンテナのresolv.confなどにはsearchとして下記のようなエントリが入っていることが多いため、[Pod名].[Service名]や[Pod名].[Service名].[Namespace名]などでも引くことが可能となっています。
nameserver 10.11.240.10 search default.svc.cluster.local svc.cluster.local cluster.local options ndots:5
ExternalName
ExternalNameは通常のService系リソースとは異なり、Service名の名前解決に対してCNAMEを返すリソースになります。使用する場面としては、別の名前を設定したい場合や、クラスタ内からのエンドポイントを切り替えやすくしたい場合などがあります。
ExternalName Serviceの作成
ExternalName Serviceの作成は、下記のようなYAMLファイルから作成します。
kind: Service apiVersion: v1 metadata: name: sample-externalname namespace: default spec: type: ExternalName externalName: external.example.com # ExternalName Serviceを作成 kubectl apply -f externalname_sample.yml
サービスを確認すると、External-IP部分にCNAME用のDNSが表示されています。
$ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE sample-externalname ExternalName <none> external.example.com <none> 18m
そのためコンテナ内からは、[Service名]や[Service名].[Namespace名].svc.[domain名]で正引きを行うとCNAMEが返ってくることが確認できます。
# dig sample-externalname.default.svc.cluster.local CNAME …… ;; ANSWER SECTION: sample-externalname.default.svc.cluster.local. 30 IN CNAME external.example.com. ……
外部サービスとの疎結合性の確保
クラスタ内では、Pod宛の通信にServiceの名前解決を使うことでサービス間の疎結合性を保っていました。一方で、SaaSやIaaSなどの外部にあるサービスを利用する際にも、可能な限り疎結合にする必要があります。
アプリケーションなどに外部のエンドポイントを書き込んでしまうと、切り替えを実施する際にアプリケーション側の設定変更が必要になってしまいます。一方でExternalNameを利用すると、DNSの切り替えはExternalName Serviceの変更を行うだけで可能なため、Kubernetes上のオペレーションで完結することができ、外部とKubernetesクラスタを疎結合性を保てます。
外部サービスと内部サービス間の切り替え
またExternalNameの利用により、外部サービスとの疎結合性の確保に加え、外部サービスとKubernetes上で展開されたクラスタ内サービスの切り替えも柔軟に行えるようになります。
Ingress
IngressはL7 LoadBalancerを提供するリソースですが、厳密には「Kind: Service」タイプのリソースではなく、「Kind: Ingress」タイプのリソースです。また、KubernetesのNetwork PolicyリソースにIngress/Egressという設定項目がありますが、このIngressとは関係ありません。
Ingressの種類
Ingressは現行のKubernetes v1.10でもまだBetaリリースとなっており、仕様などが確定されていません。Ingressの実装は複数あり、その使い勝手も大きく異なるものもありますが、今回は実際によく使われているGKE Ingress Controller、Nginx Ingress Controllerの2つを紹介します。
なお、Ingressは下記の2種類に大別できます。
- クラスタ外のロードバランサを利用したIngress
- GKE
- クラスタ内にIngress用のPodを展開するIngress
- Nginx Ingress
- Nghttpx Ingress
クラスタ外のロードバランサを利用したIngress
GKEのようにクラスタ外のロードバランサを利用したIngressの場合、Ingressリソースを作成するだけでLoadBalancerのVIPが払い出されて利用することが可能です。
そのため、Ingressのトラフィック的にはGCPのGCLB(Google Cloud Load Balancer)がトラフィックを受けた後、GCLBでHTTPS終端やパスベースルーティングなどを行い、NodePortへトラフィックを転送することで対象のPodまで到達します。
- [L7 LoadBalancer(NodePort経由)]
- [転送先の Pod]
クラスタ内に展開するIngress
クラスタ内に展開する場合は、L7 相当の処理を行うPodをクラスタ内で立てる形で実現しています。そのため、クラスタ外からアクセスできるように別途Ingress用のPod宛にLoadBalancer Serviceを作成するなどの準備が必要になります。またIngress用のPodがHTTPSの終端やパスベースのルーティングなどL7相当の処理を行うため、Podのレプリカ数のオートスケールも考慮する必要があります。
そのため、Ingressのトラフィック的には、LBは一旦Nginx Podまで転送し、NginxがL7相当の処理を行い対象のPodへ転送します。このとき、Nginx Podから対象のPodまではNodePortは通らず、直接PodのIP宛に送られます。
- [L4 LoadBalancer(type: LoadBalancer)]
- [Nginx Pod(Nginx Ingress Controller)]
- [転送先のPod]
Ingressリソースの作成
Ingressリソースの作成には、事前準備が必要です。Ingressは事前に作成されたServiceをバックエンドとして転送を行う仕組みになっています。バックエンドで利用するServiceはtype:NodePortを指定して下さい。
# cat << _EOF_ | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: sample-ingress-apps spec: replicas: 1 selector: matchLabels: ingress-app: sample template: metadata: labels: ingress-app: sample spec: containers: - name: nginx-container image: zembutsu/docker-sample-nginx:1.0 ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: svc1 spec: type: NodePort ports: - name: "http-port" protocol: "TCP" port: 8888 targetPort: 80 selector: ingress-app: sample _EOF_
また、IngressでHTTPSを利用する場合には、証明書は事前にSecretとして登録しておく必要があります。Secretは、証明書の情報を元にYAMLファイルを自分で作成するか、証明書ファイルを指定して作成します。
# 証明書の作成 $ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /tmp/tls.key -out /tmp/tls.crt -subj "/CN=sample.example.com" # Secret の作成 (証明書ファイルを指定した場合) $ kubectl create secret tls tls-sample --key /tmp/tls.key --cert /tmp/tls.crt
IngressリソースはL7 LoadBalancerのため、特定のホスト名に対して、「リクエストパス > Serviceバックエンド」のペアで転送ルールを設定します。また、1つのIP Addressで複数のホスト名を扱うことも可能です。spec.rules[].http.paths[].backend.servicePortに設定するPort番号は、Serviceのspec.ports[].portを指定して下さい。
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: sample-ingress annotations: ingress.kubernetes.io/rewrite-target: / spec: rules: - host: sample.example.com http: paths: - path: /path1 backend: serviceName: svc1 servicePort: 8888 backend: serviceName: svc1 servicePort: 8888 tls: - hosts: - sample.example.com secretName: tls-sample # Ingressリソースを作成 $ kubectl apply -f ingress_sample.yml
IngressリソースとIngress Controller
Ingressと一言で言っても、その指し示していることは、様々です。例を挙げると「Ingressリソース」とは、YAMLファイルで登録されるAPIリソースのことを意味しますし、「Ingress Controller」はIngressリソースがKubernetesに登録された際に、何らかの処理を行うものになります。処理の例としては、GCPのGCLBを操作することによるL7 LoadBalancerの設定や、NginxのConfigを変更してリロードを実施するなどが挙げられます。
GKEの場合
GKEの場合には、デフォルトでGKE用のIngress Controllerがデプロイされており、特に意識することなくIngressリソースごとに自動でIP エンドポイントが払い出されます。
$ kubectl get ingress NAME HOSTS ADDRESS PORTS AGE sample-ingress sample.example.com xxx.xxx.xxx.xxx 80 5m
実際にリクエストを送ってみると、以下のように正しく返ってくることが確認できます。
# curl http://xxx.xxx.xxx.xxx/path1 -H "Host: sample.example.com" -LI -o /dev/null -w '%{http_code}\n' -s 200 # curl https://xxx.xxx.xxx.xxx/path1 -H "Host: sample.example.com" -LI -o /dev/null -w '%{http_code}\n' -s --insecure 200
Nginx Ingressの場合
Nginx Ingressを利用する場合には、Nginx Ingress Controllerを作成する必要があります。Nginx Ingressでは、Ingress Controller自体が「L7 相当の処理を行うPod」にもなっており、Controllerという名前ですが、実際の処理も行います。
GKEのようにクラスタ外からの疎通性を確保するには、Nginx Ingress Controller宛のLoadBalancer Service(NodePortなどでも可)を作成する必要があります。個別でServiceを作成する形になるため、kubectl get ingressでエンドポイントのIP Addressは確認できないので注意して下さい。
$ kubectl get ingress NAME HOSTS ADDRESS PORTS AGE sample-ingress sample.example.com 80 5m
Nginx Ingressを利用する場合のYAMLは、下記のような形になります。ルールにマッチングしない場合のデフォルトの転送先を作成して置く必要がある点に注意して下さい。また、今回は説明のため簡略化したものを書いていますが、実際はRBAC・リソース制限・ヘルスチェック間隔など細かく設定しておく方が望ましいです。推奨設定はここを確認して下さい。
apiVersion: apps/v1 kind: Deployment metadata: name: default-http-backend labels: app: default-http-backend spec: replicas: 1 selector: matchLabels: app: default-http-backend template: metadata: labels: app: default-http-backend spec: containers: - name: default-http-backend image: gcr.io/google_containers/defaultbackend:1.4 livenessProbe: httpGet: path: /healthz port: 8080 scheme: HTTP ports: - containerPort: 8080 --- apiVersion: v1 kind: Service metadata: name: default-http-backend labels: app: default-http-backend spec: ports: - port: 80 targetPort: 8080 selector: app: default-http-backend --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-ingress-controller spec: replicas: 1 selector: matchLabels: app: ingress-nginx template: metadata: labels: app: ingress-nginx spec: containers: - name: nginx-ingress-controller image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.14.0 args: - /nginx-ingress-controller - --default-backend-service=$(POD_NAMESPACE)/default-http-backend env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace ports: - name: http containerPort: 80 - name: https containerPort: 443 livenessProbe: httpGet: path: /healthz port: 10254 scheme: HTTP readinessProbe: httpGet: path: /healthz port: 10254 scheme: HTTP --- apiVersion: v1 kind: Service metadata: name: ingress-endpoint labels: app: ingress-nginx spec: type: LoadBalancer ports: - port: 80 targetPort: 8080 selector: app: ingress-nginx
「デフォルトバックエンド用のPod」や「L7の処理を行うNginx Ingress Controller Pod」のReplica数が固定だと、トラフィックが増えたときにさばききれなくなる可能性があるため、Podのオートスケーリングを行うHorizontal Pod Autoscaler(HPA)の利用も検討して下さい。
また、デプロイしたIngress Controllerはクラスタ上の全てのIngressリソースを見てしまうため、衝突する可能性があります。その場合はIngress Classを利用することで、処理する対象のIngressリソースを分けることが可能です。
例えばService AとService Bがそれぞれあり、GKEのIngress Controllerで下記のようなIngressリソースを2つ作成した場合、2つのGCLBが作成され、エンドポイントは2つ払い出された状態となります。
Nginx Ingress Controllerにも上記の挙動を期待していると、予期せぬ状態となります。Ingress Controller自体がL7ロードバランサの実体であるように実装されているため、何も指定していない場合は全てのIngressリソースをwatchしてNginx Podの設定を更新するため、分離が行われません。
この分離性を確保するために、IngressリソースにIngress Classのアノテーションを付与し、Nginx Ingress Controllerに対象とするIngress Classを設定することで、対象の分離を行うことが可能です。
- Nginx Ingress Controllerの起動時に--ingress-classオプションを渡す
- /nginx-ingress-controller --ingress-class=system_a ……
- Ingressリソースにannotationsをつける
- kubernetes.io/ingress.class: "system_a"
この他、より詳細な仕様は下記を確認して下さい。
まとめ
KubernetesではPodのサービスディスカバリやL4ロードバランシング機能を提供するためにServiceリソースが用意されています。また似たようなリソースとして、L7ロードバランシング機能を提供するIngressも用意されています。
- Service
- L4 LoadBalancing
- クラスタ内DNSによる名前解決
- ラベルを利用したPodのサービスディスカバリ
- Ingress
- L7 LoadBalancing
- HTTPS終端
- パスベースルーティング
Serviceではエンドポイントを提供する複数のtypeが用意されており、要件に合わせて選択することが可能です。基本的には、内部向けエンドポイントを払い出したいときに使う「type: ClusterIP」と、外部向けエンドポイントを払い出したいときに使う「type: LoadBalancer」を利用することが多いです。
Service | エンドポイント |
---|---|
ClusterIP | Kubernetes Cluster内でのみ疎通可能なVIP |
ExternalIP | 特定のKubernetes NodeのIP |
NodePort | 全Kubernetes Nodeの全IP(0.0.0.0) |
LoadBalancer | クラスタ外で提供されているLoadBalancerのVIP |
ExternalName | CNAMEを用いた疎結合性の確保 |
Headless | PodのIPを用いたDNS Round Robin |
一方でIngressにはtypeなどはありませんが、実装により仕様が大きく異なるケースがあるため注意して下さい。
Ingressの種類 | 実装 |
---|---|
クラスタ外のロードバランサを利用したIngress | GKE |
クラスタ内にIngress用のPodを展開するIngress | Nginx Ingress Nghttpx Ingress |
次回は、Config&Storageリソースの解説を予定しています。
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- KubernetesのDiscovery&LBリソース(その1)
- Kubernetes上のコンテナをIngressでインターネットに公開するまで
- Kubernetesの基礎
- Oracle Cloud Hangout Cafe Season5 #3「Kubernetes のセキュリティ」(2022年3月9日開催)
- Oracle Cloud Hangout Cafe Season7 #1「Kubnernetes 超入門」(2023年6月7日開催)
- NGINX Ingress Controllerの柔軟なアプリケーション制御、具体的なユースケースと設定方法を理解する
- Oracle Cloud Hangout Cafe Season 4 #5「Kubernetesのオートスケーリング」(2021年8月4日開催)
- KubernetesのConfig&Storageリソース(その1)
- KubernetesのWorkloadsリソース(その1)
- 「Inspektor Gadget」でKubernetesクラスタをデバッグする