kubernetesにingressを導入する方法


なぜingress?

kubernetesはpods, deployments, servicesの基本要素で構成されており、標準的には、serviceにロードバランサーを設定してIPを晒し、外部インターネットからアクセスできるようにします。
しかし、HTTPSにリダイレクトするには内部でnginx-proxyを持つ必要がありますし、service毎にIPアドレスが生成されてしまうという問題があります。

ingressはHTTPSレイヤーのロードバランサーであり、

  • IP管理などを個別のserviceではなくingressで管理できる
  • Googleが推奨している

などのメリットがあります。
参考:

他にもリージョン間にまたがるロードバランスが可能であるなどのメリットもありますが、流石にそこまでサービスを大規模に展開していないのでその恩恵は今のところありません。

環境

kubernetes エンジン 1.8.6-gke.0

ingress導入

インターネット → service ではなく、インターネット → ingress → service であることに留意すると分かりやすくなります。以降は、既に動いているserviceがあるのを前提に進めます。

1. service, deploymentの設定

現在動いているserviceのデプロイメントを、type: LoadBalancer ではなく、type: NodePortにし、portをtargetPortと同じにします。以下はserviceとdeploymentを一緒に記述したyamlです。

apiVersion: v1
kind: Service
metadata:
  name: fuga-api
  namespace: fuga-namespace
  labels:
    app: fuga-api
spec:
  type: (LoadBalancer->) NodePort
  ports:
  - port: (80->)8080 
    targetPort: 8080
    protocol: TCP
  selector:
    app: fuga-api
---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: fuga-api
  namespace: fuga-namespace
spec:
  replicas: 1
  revisionHistoryLimit: 3
  template:
    metadata:
      namespace: fuga-namespace
      labels:
        app: fuga-api
        version: latest
    spec:
      containers:
      - name: fuga-api
        image: asia.gcr.io/hogehoge/fuga-image:a1
        ports:
        - containerPort: 8080

ポートの設定がミソです。もう80からアクセスは受け付けません。
これを記述したファイルを保存し、kubectl apply -f deploy-service.yamlとして更新します。
serviceのIPアドレスは"<none>"になっているはずです。

2. ingressの設定

serviceで設定した'fuga-api'をserviceNameに指定します。また、8080をservicePortに指定します。8080という数字は、imageで指定したウェブアプリケーションがそのポートを受け付けているからそう設定しているだけなので、例えばRailsのpumaが3000を受け付けていれば全て3000に置き換えます。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: fuga-ingress
  namespace: fuga-namespace
  annotations:
    kubernetes.io/ingress.global-static-ip-name: fuga-ipaddress # optional
spec:
  rules:
  - host: fuga.example.com
    http:
      paths:
      - backend:
          serviceName: fuga-api
          servicePort: 8080

(オプション)また、1つのingressで1つのIPアドレスが割り当てられますが、ingressを削除→上げ直すたびに変わってしまうので、固定のIPを設定するようにします。やり方は以下を参考にしてください。

Ingressにstatic-ipを指定してやった on GKE and GCE

先程と同様、同じくkubectl apply -f ingress.yamlとします。

以上です。簡単でしたね。ingressはIPアドレスが割り当てられるまで1~2分かかります。もし5分以上たっても割り当てられない場合、GCPの「IAMと管理」→「割り当て」から、静的IPアドレスの上限を確認してみてください。

3. 確認

HTTPでアクセスできるかブラウザで確認して見てください。見られない場合、いくつかチェックポイントがあります。まず、DNSの設定、pod, service, deploymentにエラーがない、綴ミスなどの確認は必須です。

どのページにアクセスしても502が返る

→ ingressの適用に時間がかかっている
IPアドレスが割り当てられても、サービスとingressを結びつけるのに時間がかかるみたいです。最大5分位待ちましょう。

fuga.example.com/healthzはokと表示される場合

→ ingress自体の設定は問題なし、アプリケーションの問題です。
ingressの適用は、ウェブアプリケーションのrootがstatus 200を返す必要があります。(つまり、rootに来たときにリダイレクトはアウト)作り直してもう一度確認してください。rootでリダイレクトしてもOKな設定にできるかどうかは調査中です

追記(2018/2/3)

DeploymentにreadinessProbeの設定をすればうまくいきました。ヘルスチェック時には、ここで指定したパスを見に行って、無ければrootという動きをするようです。以下はreadinessProbeの設定例です。

      containers:
      - name: fuga-api
        image: asia.gcr.io/hogehoge/fuga-image:a1
        ports:
        - containerPort: 8080
        readinessProbe:
          httpGet:
            path: /alive-path
            port: 8080

SSL化

ingress - TLSにしたがい、secretsを作成します。キーと証明書はlets encryptなどで入手しましょう。

apiVersion: v1
kind: Secret
metadata:
  name: fuga-ssl
  namespace: fuga-namespace
type: Opaque
data:
  tls.crt: base64 encode crt
  tls.key: base64 encode key

ingressのyamlに以下のようにsecret名を記述します。service単位ではなくて全部ingressで管理できるのが嬉しいところですよね。一つのingressで複数ホストに対する複数証明書を記述することも出来ます。

spec:
  tls:
  - secretName: fuga-ssl
  rules:
  - host: fuga.example.com
    http:
      paths:
      - backend:
          serviceName: fuga-api
          servicePort: 8080

ingressはじめはとっつきにくくて苦労しましたが、慣れてくれば便利です。効率的にサービスを管理したい場合、導入しておくのをおすすめします。