概要
KubernetesにはL4ロードバランサのServiceとL7のIngressがあります。
IngressはControllerによって挙動が大きく変わるので実際に手を動かして学んでみます。
環境
- minikube 1.3.0
- Kubernetes 1.15.2
- nginx-ingress 0.24.1
- nginx-ingress chart 1.6.19
- Helm 2.14.3
Ingress Controller
Ingress ControllerはIngressリソースを動かすためのものです。
例えば以下のようなIngress Controllerがあります。
Ingress Controllers | Kubernetes
Ingressは大別すると2種類
Ingressは大別すると
の2種類に分けられます。
例えばGKEのデフォルトは前者で、Nginx Ingressは後者です。
後者の場合は作成したIngressPod自体に、クラスタ外からアクセスするためのLoadBalancer Serviceが必要です。
Nginx Ingress Controller
数あるIngress Controllerの中で、今回はよく使われるNginx Ingress Controllerを使って検証します。
Welcome - NGINX Ingress Controller
アーキテクチャ
ref: Ingress with NGINX controller on Google Kubernetes Engine
このようにリクエストが登録したIngress Resource(マニフェスト)のどれに当てはまるかをIngress Controllerが判断し、後ろのServiceへとルーティングします。
この図からも分かりますが、Nginx Ingress Controllerを使った場合Ingressの実体もNginx Ingress Controllerであり、トラフィックをクラスタ内のServiceへ流します。
Nginx Ingressを使う上で必要なもの
Nginx Ingressを利用する上でそもそも以下の4つが必要になります。
- Nginx Ingress ControllerのPodを作成するためのDeployment
- クラスタ外から↑のPodにアクセスするためのService(Type: LoadBalancer)
- default-http-backendのDeployment
- default-http-backendのService(Type: ClusterIP)
default-http-backendはこの図のように、Ingress Resourceのルーティングと一致しない場合に流すためのものです。
これはhelmを使うことで全て作成可能です。
$ helm install --name nginx-ingress stable/nginx-ingress
Ingress ControllerのPodとdefault backendのPodができ、
$ kubectl get po NAME READY STATUS RESTARTS AGE nginx-ingress-controller-7b8649fd49-m8xht 1/1 Running 0 42s nginx-ingress-default-backend-6c7657c899-bqpq7 1/1 Running 0 42s
Ingress ControllerのServiceとdefault backendのServiceができます。
$ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 22h nginx-ingress-controller LoadBalancer 10.103.253.24 <pending> 80:32727/TCP,443:30863/TCP 88s nginx-ingress-default-backend ClusterIP 10.111.46.223 <none> 80/TCP 88s
minikubeではexternal-ipは用意できないのでIPをチェックしておきます。
$ minikube ip 192.168.99.102
検証環境の用意
Nginx Ingress Controllerを用意できたので、
- 検証用Deployment
- ↑用のService
- Ingressリソース
の3つを用意します。
検証用Deployment
マニフェスト
apiVersion: apps/v1 kind: Deployment metadata: name: hello-world-deployment spec: selector: matchLabels: app: hello-world replicas: 3 template: metadata: labels: app: hello-world spec: containers: - image: "strm/helloworld-http" imagePullPolicy: Always name: hello-world-container ports: - containerPort: 80
作成
$ kubectl get po NAME READY STATUS RESTARTS AGE hello-world-deployment-5c8cd966cd-7zffj 1/1 Running 0 35s hello-world-deployment-5c8cd966cd-gf4jj 1/1 Running 0 35s hello-world-deployment-5c8cd966cd-skhp5 1/1 Running 0 35s
↑用のService
マニフェスト
apiVersion: v1 kind: Service metadata: name: hello-world-svc spec: type: NodePort ports: - port: 8080 protocol: TCP targetPort: 80 selector: app: hello-world
Nginx Ingress Controllerの場合はtype: NodePort
は不要ですが、別のIngress(例えばGKEのようにクラスタ外のロードバランサを利用するもの)はNodePortを経由するので必要です。
今回はできるだけ共通になるよう付けておいてください。
作成
$ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE hello-world-svc ClusterIP 10.98.63.214 <none> 8080/TCP 2m17s
Ingressリソース
マニフェスト
Virtual Hostをhello-world.info
にしておきます。
apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: nginx-ingress annotations: kubernetes.io/ingress.class: "nginx" spec: rules: - host: hello-world.info http: paths: - backend: serviceName: hello-world-svc servicePort: 8080 path: /
rules
によってnginxのconfigurationが設定されます。
例えばhost: hello-world.info
は
server { server_name hello-world.info ; ... }
に設定されますし、path: /
は
location / {
...
}
に変換されます。
$ kubectl exec -it nginx-ingress-controller-xxx-yyy -- cat nginx.conf
とすると変換された設定ファイルを全て確認できます。
作成
$ kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
nginx-ingress hello-world.info 80 3m
作成するとnginx ingress controllerのpodでは以下のログが流れます。
nginx-ingress-controller-7b8649fd49-m8xht nginx-ingress-controller I0808 13:32:05.194069 6 event.go:209] Event(v1.ObjectReference{Kind:"Ingress", Namespace:"default", Name:"nginx-ingress", UID:"f91efd54-7c8d-4332-87fe-5c522392393b", APIVersion:"extensions/v1beta1", ResourceVersion:"53784", FieldPath:""}): type: 'Normal' reason: 'CREATE' Ingress default/nginx-ingress
検証
Virtual Hostを設定しているので、minikube ip
でのIPを/etc/hosts
に設定しておきます。
192.168.99.102 hello-world.info
この状態でcurlを叩くと、検証用Podへリクエストが流れることが確認できます。
$ curl http://hello-world.info:32727 <html><head><title>HTTP Hello World</title></head><body><h1>Hello from hello-world-deployment-5c8cd966cd-7zffj</h1></body></html $ curl http://hello-world.info:32727 <html><head><title>HTTP Hello World</title></head><body><h1>Hello from hello-world-deployment-5c8cd966cd-skhp5</h1></body></html
このようにバランシングされてます。
Nginx Ingress Controllerには以下のログが流れます。
nginx-ingress-controller-7b8649fd49-m8xht nginx-ingress-controller 172.17.0.1 - [172.17.0.1] - - [08/Aug/2019:13:52:27 +0000] "GET / HTTP/1.1" 200 129 "-" "curl/7.54.0" 86 0.002 [default-hello-world-svc-8080] 172.17.0.7:80 129 0.001 200 7fbf1eba2e9642c34ac19504a06c5884 nginx-ingress-controller-7b8649fd49-m8xht nginx-ingress-controller 172.17.0.1 - [172.17.0.1] - - [08/Aug/2019:13:56:00 +0000] "GET / HTTP/1.1" 200 129 "-" "curl/7.54.0" 86 0.001 [default-hello-world-svc-8080] 172.17.0.8:80 129 0.002 200 d7ead53538f33f2fc32524caf68ccd92
その他
検証中に気になったことまとめ
Nginx Ingressを冗長化するには?
先に述べたように、Nginx Ingress Controllerの場合Ingressの実体もNginx Ingress Controllerです。
なのでIngressの冗長化をする場合はNginx Ingress ControllerのReplica数自体を上げます。
なぜminikubeではexternal-ipを用意できないの?
Serviceのtype: LoadBalancer
はKubernetesクラスタが構築されているインフラがこの仕組みに対応している必要があります。
基本的にGCP・AWS・Azureといったクラウドプロバイダが対応していますが、ローカル開発では利用できないです。
Docker for Macではlocalhostが、minikubeではminikube ip
でのNodePort
のようなServiceになります。
GKEで作るとIngressのAddressとNginx Ingress ControllerのIPが異なる
GKEでも作れるか検証したところ、期待通りLoadBalancerにexternal-ipが振られて
$ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE hello-world-svc NodePort 10.170.12.105 <none> 8080:31223/TCP 5m33s kubernetes ClusterIP 10.170.0.1 <none> 443/TCP 50m nginx-ingress-controller LoadBalancer 10.170.7.157 34.85.122.81 80:30714/TCP,443:31264/TCP 9m25s nginx-ingress-default-backend ClusterIP 10.170.11.220 <none> 80/TCP 9m25s
そこからアクセスできることは確認できました。
$ curl http://34.85.122.81/ -H "Host: hello-world.info" <html><head><title>HTTP Hello World</title></head><body><h1>Hello from hello-world-deployment-57584b8758-4kbd6</h1></body></html
しかしなぜかIngressに載っているAddressは別でした。
$ kubectl get ing NAME HOSTS ADDRESS PORTS AGE nginx-ingress hello-world.info 35.194.123.227 80 64s
また、疎通もできませんでした。
$ curl http://35.194.123.227/ -H "Host: hello-world.info" curl: (52) Empty reply from server
Helmインストール時に追加設定が必要
このissueに書いてあるように--set controller.publishService.enabled=true
が必要でした。
$ helm install --name nginx-ingress-test \ --set controller.publishService.enabled=true stable/nginx-ingress
これで作成してみると両方一致し、疎通もできました。
$ kubectl get ing NAME HOSTS ADDRESS PORTS AGE nginx-ingress hello-world.info 34.85.122.81 80 16s $ $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE hello-world-svc NodePort 10.170.2.250 <none> 8080:31103/TCP 23s kubernetes ClusterIP 10.170.0.1 <none> 443/TCP 60m nginx-ingress-controller LoadBalancer 10.170.8.120 34.85.122.81 80:30095/TCP,443:32259/TCP 105s nginx-ingress-default-backend ClusterIP 10.170.1.34 <none> 80/TCP 105s
ホスト名のDNS登録どうするの?
今回は簡単のため手動で/etc/hosts
に設定しましたが、例えばexternal-dnsを使うとannotationをつけるだけでRoute53やCloudDNSで登録して公開してくれます。
サンプルコード
今回のサンプルコードはこちら↓