Kubernetes内のServiceにinternal ELBを使ってクラスタ外からアクセスする

最初からKubernetesやGKEを使っているプロジェクトには無縁だと思うのだが、途中から部分的にKubernetes化を進めて、Kubernetesクラスタ外からServiceを利用できるようにしたいというケースもあるかと思う。

Nginx Ingress Controller

このようなケースではserviceをKubernetesクラスタ外に公開する必要があるので、ingressを用意する必要がある。ingressから受けたリクエストをうまくルーティングする層が必要になるわけだが、その際に役に立つのがingress-nginxだ。

設定

kube-systemに配置しているが、別のnamespaceでingressを設定すればクロスネームスペースからでも利用できる。この辺は各自の環境に合わせて忖度してほしい。

default-backend

default-backendはingressで設定してないルール外へのリクエストを受けるためのバックエンド。ingress-nginxはこれを利用するのでまずはdefault-backendをserviceとして用意しておく。

apiVersion: v1
kind: Service
metadata:
  name: nginx-default-backend
  namespace: kube-system
  labels:
    k8s-addon: ingress-nginx.addons.k8s.io
spec:
  ports:
  - port: 80
    targetPort: http
  selector:
    app: nginx-default-backend

---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx-default-backend
  namespace: kube-system
  labels:
    k8s-addon: ingress-nginx.addons.k8s.io
spec:
  replicas: 1
  template:
    metadata:
      labels:
        k8s-addon: ingress-nginx.addons.k8s.io
        app: nginx-default-backend
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - name: default-http-backend
        image: gcr.io/google_containers/defaultbackend:1.0
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 30
          timeoutSeconds: 5
        ports:
        - name: http
          containerPort: 8080
          protocol: TCP
---

configmap

ingress-nginxにしかけるconfigmap。nginxのバックエンドにupstreamされる際のログのフォーマットが設定できる。publicでHTTP2にする場合はuse-proxy-protocol: "true"を設定するが、今回のようにinternalで素のHTTP/1.1であればナシでいい。

apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-internal
  namespace: kube-system 
  labels:
    k8s-addon: ingress-nginx.addons.k8s.io
data:
  # HTTP/1.1であればproxy protocolナシで
  # use-proxy-protocol: "true"
  log-format-upstream: '{ "time": "$time_iso8601", "remote_addr": "$proxy_protocol_addr",
      "x-forward-for": "$proxy_add_x_forwarded_for", "request_id": "$request_id", "remote_user":
      "$remote_user", "bytes_sent": $bytes_sent, "request_time": $request_time, "status":
      $status, "vhost": "$host", "request_proto": "$server_protocol", "path": "$uri",
      "request_query": "$args", "request_length": $request_length, "duration": $request_time,
      "method": "$request_method", "http_referrer": "$http_referer", "http_user_agent":
      "$http_user_agent" }'

deployment

続いてdeployment。ingress-nginxコンテナを配置する。ingress-nginxを複数配置したいようなケースも当然あるので、Kubernetesのingressが利用するingress-nginxを特定できるようにオプションで--ingress-classを設定しておき、任意の名前をつけておくとよい。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: ingress-nginx-internal
  namespace: kube-system
  labels:
    k8s-addon: ingress-nginx.addons.k8s.io
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: ingress-nginx-internal
        k8s-addon: ingress-nginx.addons.k8s.io
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.11
        name: ingress-nginx-internal
        imagePullPolicy: Always
        ports:
          - name: http
            containerPort: 80
            protocol: TCP
        livenessProbe:
          httpGet:
            path: /healthz
            port: 10254
            scheme: HTTP
          initialDelaySeconds: 30
          timeoutSeconds: 5
        env:
          - name: POD_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: POD_NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
        args:
        - /nginx-ingress-controller
        - --default-backend-service=$(POD_NAMESPACE)/nginx-default-backend
        - --configmap=$(POD_NAMESPACE)/ingress-nginx-internal
        - --publish-service=$(POD_NAMESPACE)/ingress-nginx-internal
        - --ingress-class=nginx-internal

service

ServiceのAnnotationとしてservice.beta.kubernetes.io/aws-load-balancer-internalを設定している。AWS環境であれば、service作成時にinternalなClassic ELBが自動で作成される。セキュリティグループも新規作成したELB用に新たに作られるので、適切なACLを設定してほしい。

apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx-internal
  namespace: kube-system
  labels:
    k8s-addon: ingress-nginx.addons.k8s.io
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-internal: "xx.xx.xx.xx/xx"
spec:
  type: LoadBalancer
  selector:
    app: ingress-nginx-internal
  ports:
  - name: http
    port: 80
    targetPort: http

ingress

最後に任意のnamespace上に、ingress-nginxを利用して個別のserviceにVirtualHost的なプロキシをするためのingressを用意する。deploymentで--ingress-classを指定しているので、この値をkubernetes.io/ingress.classのアノテーションで設定しておく。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-internal 
  namespace: your_namespace 
  annotations:
    ingress.kubernetes.io/ingress: nginx
    kubernetes.io/ingress.class: "nginx-internal"
spec:
  rules:
  - host: hogehoge.your_internal.local
    http:
      paths:
      - path: /
        backend:
          serviceName: hogehoge 
          servicePort: 80

こうしておくと、hogehoge.your_internal.localへのHTTPリクエストはhogehogeというサービスにプロキシされるようになる。個別のnamespaceにそれぞれingress-nginxを立てるよりは、どこか一箇所に集約し、ELBを1本でも節約できる方向に持っていった方がよいと思う。

ちなみにここに定義したからといってRoute53に設定DNSが定義されるわけではないので注意。これは各々の運用によると思うが、kubernetes-incubator/external-dnsを利用するのも良いだろう。

まとめ

部分的にKubernetesを使う上でのエッセンスなので覚えておくと良いでしょう(ウチはただいま絶賛移行中)